VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp@ 36067

Last change on this file since 36067 was 36035, checked in by vboxsync, 14 years ago

VBoxManageGuestCtrl.cpp: Review Todos.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.9 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 36035 2011-02-21 14:44:15Z vboxsync $ */
2/** @file
3 * VBoxManage - Implementation of guestcontrol command.
4 */
5
6/*
7 * Copyright (C) 2010-2011 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "VBoxManage.h"
23
24#ifndef VBOX_ONLY_DOCS
25
26#include <VBox/com/com.h>
27#include <VBox/com/string.h>
28#include <VBox/com/array.h>
29#include <VBox/com/ErrorInfo.h>
30#include <VBox/com/errorprint.h>
31
32#include <VBox/com/VirtualBox.h>
33#include <VBox/com/EventQueue.h>
34
35#include <VBox/HostServices/GuestControlSvc.h> /* for PROC_STS_XXX */
36
37#include <iprt/asm.h>
38#include <iprt/dir.h>
39#include <iprt/file.h>
40#include <iprt/isofs.h>
41#include <iprt/getopt.h>
42#include <iprt/list.h>
43#include <iprt/path.h>
44#include <iprt/thread.h>
45
46#ifdef USE_XPCOM_QUEUE
47# include <sys/select.h>
48# include <errno.h>
49#endif
50
51#include <signal.h>
52
53#ifdef RT_OS_DARWIN
54# include <CoreFoundation/CFRunLoop.h>
55#endif
56
57using namespace com;
58
59/**
60 * IVirtualBoxCallback implementation for handling the GuestControlCallback in
61 * relation to the "guestcontrol * wait" command.
62 */
63/** @todo */
64
65/** Set by the signal handler. */
66static volatile bool g_fGuestCtrlCanceled = false;
67
68/*
69 * Structure holding a directory entry.
70 */
71typedef struct DIRECTORYENTRY
72{
73 char *pszSourcePath;
74 char *pszDestPath;
75 RTLISTNODE Node;
76} DIRECTORYENTRY, *PDIRECTORYENTRY;
77
78/**
79 * Special exit codes for returning errors/information of a
80 * started guest process to the command line VBoxManage was started from.
81 * Useful for e.g. scripting.
82 * @todo r=bird: RTEXITCODE is using the RT prefix, that is reserved for IPRT
83 * use only.
84 */
85enum RTEXITCODE_EXEC
86{
87 RTEXITCODE_EXEC_SUCCESS = RTEXITCODE_SUCCESS,
88 RTEXITCODE_EXEC_FAILED = 16,
89 RTEXITCODE_EXEC_TERM_SIGNAL = 17,
90 RTEXITCODE_EXEC_TERM_ABEND = 18,
91 RTEXITCODE_EXEC_TIMEOUT = 19,
92 RTEXITCODE_EXEC_CANCELED = 20
93};
94
95/**
96 * RTGetOpt-IDs for the guest execution control command line.
97 * @todo r=bird: RTGETOPTDEF_EXEC is using the RT prefix, that is reserved for
98 * IPRT use only.
99 */
100enum RTGETOPTDEF_EXEC
101{
102 RTGETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000,
103 RTGETOPTDEF_EXEC_OUTPUTFORMAT,
104 RTGETOPTDEF_EXEC_OUTPUTTYPE,
105 RTGETOPTDEF_EXEC_WAITFOREXIT,
106 RTGETOPTDEF_EXEC_WAITFORSTDOUT,
107 RTGETOPTDEF_EXEC_WAITFORSTDERR,
108 RTGETOPTDEF_EXEC_ARGS
109};
110
111#endif /* VBOX_ONLY_DOCS */
112
113void usageGuestControl(PRTSTREAM pStrm)
114{
115 RTStrmPrintf(pStrm,
116 "VBoxManage guestcontrol <vmname>|<uuid> exec[ute]\n"
117 " --image <path to program>\n"
118 " --username <name> --password <password>\n"
119 " [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
120 " [--timeout <msec>] [--verbose]\n"
121 " [--wait-exit] [--wait-stdout] [--wait-stdout]"
122 //" [--output-format=<dos>|<unix>]\n"
123 " [--output-type=<binary>|<text>]\n"
124 " [-- [<argument1>] ... [<argumentN>]\n"
125 /** @todo Add a "--" parameter (has to be last parameter) to directly execute
126 * stuff, e.g. "VBoxManage guestcontrol execute <VMName> --username <> ... -- /bin/rm -Rf /foo". */
127 "\n"
128 " <vmname>|<uuid> copyto|cp\n"
129 " <source on host> <destination on guest>\n"
130 " --username <name> --password <password>\n"
131 " [--dryrun] [--follow] [--recursive] [--verbose]\n"
132 "\n"
133 " <vmname>|<uuid> createdir[ectory]|mkdir|md\n"
134 " <directory to create on guest>\n"
135 " --username <name> --password <password>\n"
136 " [--parents] [--mode <mode>] [--verbose]\n"
137 "\n"
138 " <vmname>|<uuid> updateadditions\n"
139 " [--source <guest additions .ISO>] [--verbose]\n"
140 "\n");
141}
142
143#ifndef VBOX_ONLY_DOCS
144
145/**
146 * Signal handler that sets g_fGuestCtrlCanceled.
147 *
148 * This can be executed on any thread in the process, on Windows it may even be
149 * a thread dedicated to delivering this signal. Do not doing anything
150 * unnecessary here.
151 */
152static void guestCtrlSignalHandler(int iSignal)
153{
154 NOREF(iSignal);
155 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
156}
157
158/**
159 * Installs a custom signal handler to get notified
160 * whenever the user wants to intercept the program.
161 */
162static void ctrlSignalHandlerInstall()
163{
164 signal(SIGINT, guestCtrlSignalHandler);
165#ifdef SIGBREAK
166 signal(SIGBREAK, guestCtrlSignalHandler);
167#endif
168}
169
170/**
171 * Uninstalls a previously installed signal handler.
172 */
173static void ctrlSignalHandlerUninstall()
174{
175 signal(SIGINT, SIG_DFL);
176#ifdef SIGBREAK
177 signal(SIGBREAK, SIG_DFL);
178#endif
179}
180
181/**
182 * Translates a process status to a human readable
183 * string.
184 */
185static const char *ctrlExecProcessStatusToText(ULONG uStatus)
186{
187 switch (uStatus)
188 {
189 case guestControl::PROC_STS_STARTED:
190 return "started";
191 case guestControl::PROC_STS_TEN:
192 return "successfully terminated";
193 case guestControl::PROC_STS_TES:
194 return "terminated by signal";
195 case guestControl::PROC_STS_TEA:
196 return "abnormally aborted";
197 case guestControl::PROC_STS_TOK:
198 return "timed out";
199 case guestControl::PROC_STS_TOA:
200 return "timed out, hanging";
201 case guestControl::PROC_STS_DWN:
202 return "killed";
203 case guestControl::PROC_STS_ERROR:
204 return "error";
205 default:
206 break;
207 }
208 return "unknown";
209}
210
211static int ctrlExecProcessStatusToExitCode(ULONG uStatus)
212{
213 int rc = RTEXITCODE_EXEC_SUCCESS;
214 switch (uStatus)
215 {
216/** @todo r=bird: Why do you use guestControl status codes here? Is the Main
217 * API not exposing these constants? If so, it's something that
218 * needs fixing. */
219 case guestControl::PROC_STS_STARTED:
220 rc = RTEXITCODE_EXEC_SUCCESS;
221 break;
222 case guestControl::PROC_STS_TEN:
223 /** @todo check the exit code, 0 is success, !0 should be indicated
224 * by a specail VBoxManage exit code. */
225 rc = RTEXITCODE_EXEC_SUCCESS;
226 break;
227 case guestControl::PROC_STS_TES:
228 rc = RTEXITCODE_EXEC_TERM_SIGNAL;
229 break;
230 case guestControl::PROC_STS_TEA:
231 rc = RTEXITCODE_EXEC_TERM_ABEND;
232 break;
233 case guestControl::PROC_STS_TOK:
234 rc = RTEXITCODE_EXEC_TIMEOUT;
235 break;
236 case guestControl::PROC_STS_TOA:
237 rc = RTEXITCODE_EXEC_TIMEOUT;
238 break;
239 case guestControl::PROC_STS_DWN:
240 /* Service/OS is stopping, process was killed, so
241 * not exactly an error of the started process ... */
242 rc = RTEXITCODE_EXEC_SUCCESS; /** @todo r=bird: return failure if you don't know what happend. No false positives, please. */
243 break;
244 case guestControl::PROC_STS_ERROR:
245 rc = RTEXITCODE_EXEC_FAILED;
246 break;
247 default:
248 AssertMsgFailed(("Unknown exit code (%u) from guest process returned!\n", uStatus));
249 break;
250 }
251 return rc;
252}
253
254static int ctrlPrintError(com::ErrorInfo &errorInfo)
255{
256 if ( errorInfo.isFullAvailable()
257 || errorInfo.isBasicAvailable())
258 {
259 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
260 * because it contains more accurate info about what went wrong. */
261 if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
262 RTMsgError("%ls.", errorInfo.getText().raw());
263 else
264 {
265 RTMsgError("Error details:");
266 GluePrintErrorInfo(errorInfo);
267 }
268 return VERR_GENERAL_FAILURE; /** @todo */
269 }
270 AssertMsgFailedReturn(("Object has indicated no error!?\n"),
271 VERR_INVALID_PARAMETER);
272}
273
274static int ctrlPrintError(IUnknown *pObj, const GUID &aIID)
275{
276 com::ErrorInfo ErrInfo(pObj, aIID);
277 return ctrlPrintError(ErrInfo);
278}
279
280
281static int ctrlPrintProgressError(ComPtr<IProgress> progress)
282{
283 int rc;
284 BOOL fCanceled;
285 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
286 && fCanceled)
287 {
288 rc = VERR_CANCELLED;
289 }
290 else
291 {
292 com::ProgressErrorInfo ErrInfo(progress);
293 rc = ctrlPrintError(ErrInfo);
294 }
295 return rc;
296}
297
298/**
299 * Un-initializes the VM after guest control usage.
300 */
301static void ctrlUninitVM(HandlerArg *pArg)
302{
303 AssertPtrReturnVoid(pArg);
304 if (pArg->session)
305 pArg->session->UnlockMachine();
306}
307
308/**
309 * Initializes the VM for IGuest operations.
310 *
311 * That is, checks whether it's up and running, if it can be locked (shared
312 * only) and returns a valid IGuest pointer on success.
313 *
314 * @return IPRT status code.
315 * @param pArg Our command line argument structure.
316 * @param pszNameOrId The VM's name or UUID.
317 * @param pGuest Where to return the IGuest interface pointer.
318 */
319static int ctrlInitVM(HandlerArg *pArg, const char *pszNameOrId, ComPtr<IGuest> *pGuest)
320{
321 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
322 AssertPtrReturn(pszNameOrId, VERR_INVALID_PARAMETER);
323
324 /* Lookup VM. */
325 ComPtr<IMachine> machine;
326 /* Assume it's an UUID. */
327 HRESULT rc;
328 CHECK_ERROR(pArg->virtualBox, FindMachine(Bstr(pszNameOrId).raw(),
329 machine.asOutParam()));
330 if (FAILED(rc))
331 return VERR_NOT_FOUND;
332
333 /* Machine is running? */
334 MachineState_T machineState;
335 CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
336 if (machineState != MachineState_Running)
337 {
338 RTMsgError("Machine \"%s\" is not running (currently %s)!\n",
339 pszNameOrId, machineStateToName(machineState, false));
340 return VERR_VM_INVALID_VM_STATE;
341 }
342
343 do
344 {
345 /* Open a session for the VM. */
346 CHECK_ERROR_BREAK(machine, LockMachine(pArg->session, LockType_Shared));
347 /* Get the associated console. */
348 ComPtr<IConsole> console;
349 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Console)(console.asOutParam()));
350 /* ... and session machine. */
351 ComPtr<IMachine> sessionMachine;
352 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
353 /* Get IGuest interface. */
354 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pGuest->asOutParam()));
355 } while (0);
356
357 if (FAILED(rc))
358 ctrlUninitVM(pArg);
359 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
360}
361
362/* <Missing docuemntation> */
363static int handleCtrlExecProgram(ComPtr<IGuest> guest, HandlerArg *pArg)
364{
365 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
366
367 /*
368 * Parse arguments.
369 */
370 if (pArg->argc < 2) /* At least the command we want to execute in the guest should be present :-). */
371 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
372
373 static const RTGETOPTDEF s_aOptions[] =
374 {
375 { "--environment", 'e', RTGETOPT_REQ_STRING },
376 { "--flags", 'f', RTGETOPT_REQ_STRING },
377 { "--ignore-operhaned-processes", RTGETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES, RTGETOPT_REQ_NOTHING },
378 { "--image", 'i', RTGETOPT_REQ_STRING },
379 //{ "--output-format", RTGETOPTDEF_EXEC_OUTPUTFORMAT, RTGETOPT_REQ_STRING },
380 { "--output-type", RTGETOPTDEF_EXEC_OUTPUTTYPE, RTGETOPT_REQ_STRING },
381 /** @todo r=bird: output-type and output-format are the same thing.
382 * Please just call it --dos2unix and --unix2dos and
383 * implement it them. (If you insist on flexibility, it would
384 * be --input-type <x> and --output-type <y>, where x and y
385 * could indicate line ending styles (CR/LF/CRLF), encodings
386 * (UTF-8, UTF-16, ++), code pages and plugin provided
387 * conversions. See the conv option of dd and iconv for
388 * inspiration.)
389 *
390 * The default must be no conversion at all, i.e.
391 * --output-type=binary. */
392 { "--password", 'p', RTGETOPT_REQ_STRING },
393 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
394 { "--username", 'u', RTGETOPT_REQ_STRING },
395 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
396 { "--wait-exit", RTGETOPTDEF_EXEC_WAITFOREXIT, RTGETOPT_REQ_NOTHING },
397 { "--wait-stdout", RTGETOPTDEF_EXEC_WAITFORSTDOUT, RTGETOPT_REQ_NOTHING },
398 { "--wait-stderr", RTGETOPTDEF_EXEC_WAITFORSTDERR, RTGETOPT_REQ_NOTHING },
399 /* @todo r=bird: '--' is interpreted by RTGetOpt() to indicate the
400 * end of arguments, you don't need to handle this specially.
401 * RTGetOpt() will return VINF_GETOPT_NOT_OPTION for anything following
402 * a '--' argument. So, let me give you some other examples for illustrating
403 * what I mean should work now (all the same, userid/pw missing):
404 * VBoxManage guestcontrol myvm exec --image /bin/busybox cp /foo /bar
405 * VBoxManage guestcontrol myvm exec --image /bin/busybox -- cp /foo /bar
406 * VBoxManage guestcontrol myvm exec --image /bin/busybox cp -- /foo /bar
407 * VBoxManage guestcontrol myvm exec --image /bin/busybox cp /foo -- /bar
408 *
409 * Same, but with standard cp (image defaults to /bin/cp):
410 * VBoxManage guestcontrol myvm exec /bin/cp /foo /bar
411 * VBoxManage guestcontrol myvm exec -- /bin/cp /foo /bar
412 * VBoxManage guestcontrol myvm exec /bin/cp -- /foo /bar
413 * VBoxManage guestcontrol myvm exec /bin/cp /foo -- /bar
414 *
415 * The old example:
416 * VBoxManage guestcontrol myvm exec --image /bin/busybox -- ln -s /foo /bar
417 *
418 * As an example of where '--' is used by standard linux utils, try delete a
419 * file named '-f' without specifying the directory:
420 * > -f # creates the file
421 * rm -v -f # does nothing because '-f' is a rm option.
422 * rm -v -- -f # does the trick because '--' tells rm to not read '-f' as an option.
423 */
424 { "--", RTGETOPTDEF_EXEC_ARGS, RTGETOPT_REQ_STRING }
425 };
426
427 int ch;
428 RTGETOPTUNION ValueUnion;
429 RTGETOPTSTATE GetState;
430 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
431
432 Utf8Str Utf8Cmd;
433 uint32_t fFlags = 0;
434 com::SafeArray<IN_BSTR> args;
435 com::SafeArray<IN_BSTR> env;
436 Utf8Str Utf8UserName;
437 Utf8Str Utf8Password;
438 uint32_t cMsTimeout = 0;
439 bool fOutputBinary = false;
440 bool fWaitForExit = false;
441 bool fWaitForStdOut = false;
442 bool fWaitForStdErr = false;
443 bool fVerbose = false;
444
445 int vrc = VINF_SUCCESS;
446 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
447 && RT_SUCCESS(vrc))
448 {
449 /* For options that require an argument, ValueUnion has received the value. */
450 switch (ch)
451 {
452 case 'e': /* Environment */
453 {
454 char **papszArg;
455 int cArgs;
456
457 vrc = RTGetOptArgvFromString(&papszArg, &cArgs, ValueUnion.psz, NULL);
458 if (RT_FAILURE(vrc))
459 return errorSyntax(USAGE_GUESTCONTROL, "Failed to parse environment value, rc=%Rrc", vrc);
460 for (int j = 0; j < cArgs; j++)
461 env.push_back(Bstr(papszArg[j]).raw());
462
463 RTGetOptArgvFree(papszArg);
464 break;
465 }
466
467 case RTGETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES:
468 fFlags |= ExecuteProcessFlag_IgnoreOrphanedProcesses;
469 break;
470
471 case 'i':
472 Utf8Cmd = ValueUnion.psz;
473 break;
474
475 /** @todo Add DOS2UNIX and vice versa handling!
476 case RTGETOPTDEF_EXEC_OUTPUTFORMAT:
477 break;*/
478
479 case RTGETOPTDEF_EXEC_OUTPUTTYPE:
480 if (!RTStrICmp(ValueUnion.psz, "binary"))
481 fOutputBinary = true;
482 else if (!RTStrICmp(ValueUnion.psz, "text"))
483 fOutputBinary = false;
484 else
485 {
486 AssertPtr(GetState.pDef->pszLong);
487 return errorSyntax(USAGE_GUESTCONTROL, "Unknown value for '%s' specified! Use either 'binary' or 'text'",
488 GetState.pDef->pszLong);
489 }
490 break;
491
492 /** @todo Add a hidden flag. */
493
494 case 'p': /* Password */
495 Utf8Password = ValueUnion.psz;
496 break;
497
498 case 't': /* Timeout */
499 cMsTimeout = ValueUnion.u32;
500 break;
501
502 case 'u': /* User name */
503 Utf8UserName = ValueUnion.psz;
504 break;
505
506 case 'v': /* Verbose */
507 fVerbose = true;
508 break;
509
510 case RTGETOPTDEF_EXEC_WAITFOREXIT:
511 fWaitForExit = true;
512 break;
513
514 case RTGETOPTDEF_EXEC_WAITFORSTDOUT:
515 fWaitForExit = true;
516 fWaitForStdOut = true;
517 break;
518
519 case RTGETOPTDEF_EXEC_WAITFORSTDERR:
520 fWaitForExit = true;
521 fWaitForStdErr = true;
522 break;
523
524 case RTGETOPTDEF_EXEC_ARGS:
525 {
526 /* Push current parameter to vector. */
527 args.push_back(Bstr(ValueUnion.psz).raw());
528
529 /*
530 * Add all following parameters after this one to our guest process
531 * argument vector.
532 */
533 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
534 && RT_SUCCESS(vrc))
535 {
536 /*
537 * Is this option unknown or not recognized an option? Then just
538 * add the raw string value to our argument vector.
539 */
540 if ( ch == VINF_GETOPT_NOT_OPTION
541 || ch == VERR_GETOPT_UNKNOWN_OPTION)
542 args.push_back(Bstr(ValueUnion.psz).raw());
543 /*
544 * If this is an option/parameter we already defined for our actual
545 * execution command we need to take the pDef->pszLong value instead.
546 */
547 else if (ValueUnion.pDef)
548 args.push_back(Bstr(ValueUnion.pDef->pszLong).raw());
549 else
550 AssertMsgFailed(("Unknown parameter type detected!\n"));
551 }
552 break;
553 }
554
555 case VINF_GETOPT_NOT_OPTION:
556 if (Utf8Cmd.isEmpty())
557 Utf8Cmd = ValueUnion.psz;
558 else
559 return RTGetOptPrintError(ch, &ValueUnion);
560 break;
561
562 default:
563 return RTGetOptPrintError(ch, &ValueUnion);
564 }
565 }
566
567 if (RT_FAILURE(vrc)) /** @todo r=bird: You don't need to check vrc here or in any of the two while conditions above because you are now returning directly in those cases. Drop the checks, this will keep things simpler. */
568 {
569 RTMsgError("Failed to parse argument '%c', rc=%Rrc", ch, vrc);
570 return RTEXITCODE_FAILURE;
571 }
572
573 if (Utf8Cmd.isEmpty())
574 return errorSyntax(USAGE_GUESTCONTROL, "No command to execute specified!");
575
576 if (Utf8UserName.isEmpty())
577 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
578
579 /*
580 * <missing comment indicating that we're done parsing args and started doing something else>
581 */
582 HRESULT rc = S_OK;
583 if (fVerbose)
584 {
585 if (cMsTimeout == 0)
586 RTPrintf("Waiting for guest to start process ...\n");
587 else
588 RTPrintf("Waiting for guest to start process (within %ums)\n", cMsTimeout);
589 }
590
591 /* Get current time stamp to later calculate rest of timeout left. */
592 uint64_t u64StartMS = RTTimeMilliTS();
593
594 /* Execute the process. */
595 int rcProc = RTEXITCODE_EXEC_SUCCESS; /** @todo r=bird: Don't initialize this, please, set it explicitly in the various branches. It's easier to see what it's going to be that way. */
596 ComPtr<IProgress> progress;
597 ULONG uPID = 0;
598 rc = guest->ExecuteProcess(Bstr(Utf8Cmd).raw(),
599 fFlags,
600 ComSafeArrayAsInParam(args),
601 ComSafeArrayAsInParam(env),
602 Bstr(Utf8UserName).raw(),
603 Bstr(Utf8Password).raw(),
604 cMsTimeout,
605 &uPID,
606 progress.asOutParam());
607 if (FAILED(rc))
608 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest)); /** @todo return straight away and drop state (e.g. rcProc) confusion. */
609 else
610 {
611 if (fVerbose)
612 RTPrintf("Process '%s' (PID: %u) started\n", Utf8Cmd.c_str(), uPID);
613 if (fWaitForExit)
614 {
615 if (fVerbose)
616 {
617 if (cMsTimeout) /* Wait with a certain timeout. */
618 {
619 /* Calculate timeout value left after process has been started. */
620 uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
621 /* Is timeout still bigger than current difference? */
622 if (cMsTimeout > u64Elapsed)
623 RTPrintf("Waiting for process to exit (%ums left) ...\n", cMsTimeout - u64Elapsed);
624 else
625 RTPrintf("No time left to wait for process!\n"); /** @todo a bit misleading ... */
626 }
627 else /* Wait forever. */
628 RTPrintf("Waiting for process to exit ...\n");
629 }
630
631 /* Setup signal handling if cancelable. */
632 ASSERT(progress);
633 bool fCanceledAlready = false;
634 BOOL fCancelable;
635 HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
636 if (FAILED(hrc))
637 fCancelable = FALSE;
638 if (fCancelable)
639 ctrlSignalHandlerInstall();
640
641 /* Wait for process to exit ... */
642 BOOL fCompleted = FALSE;
643 BOOL fCanceled = FALSE;
644 int cMilliesSleep = 0;
645 while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted))))
646 {
647 SafeArray<BYTE> aOutputData;
648 ULONG cbOutputData = 0;
649
650 /*
651 * Some data left to output?
652 */
653 if ( fWaitForStdOut
654 || fWaitForStdErr)
655 {
656 /** @todo r=bird: The timeout argument is bogus in several
657 * ways:
658 * 1. RT_MAX will evaluate the arguments twice, which may
659 * result in different values because RTTimeMilliTS()
660 * returns a higher value the 2nd time. Worst case:
661 * Imagine when RT_MAX calculates the remaining time
662 * out (first expansion) there is say 60 ms left. Then
663 * we're preempted and rescheduled after, say, 120 ms.
664 * We call RTTimeMilliTS() again and ends up with a
665 * value -60 ms, which translate to a UINT32_MAX - 59
666 * ms timeout.
667 *
668 * 2. When the period expires, we will wait forever since
669 * both 0 and -1 mean indefinite timeout with this API,
670 * at least that's one way of reading the main code.
671 *
672 * 3. There is a signed/unsigned ambiguity in the
673 * RT_MAX expression. The left hand side is signed
674 * integer (0), the right side is unsigned 64-bit. From
675 * what I can tell, the compiler will treat this as
676 * unsigned 64-bit and never return 0.
677 */
678 /** @todo r=bird: We must separate stderr and stdout
679 * output, seems bunched together here which
680 * won't do the trick for unix BOFHs. */
681 rc = guest->GetProcessOutput(uPID, 0 /* aFlags */,
682 RT_MAX(0, cMsTimeout - (RTTimeMilliTS() - u64StartMS)) /* Timeout in ms */,
683 _64K, ComSafeArrayAsOutParam(aOutputData));
684 if (FAILED(rc))
685 {
686 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
687 cbOutputData = 0;
688 }
689 else
690 {
691 cbOutputData = aOutputData.size();
692 if (cbOutputData > 0)
693 {
694 /** @todo r=bird: Adding exec options to convert unix2dos
695 * and dos2unix. Use a VFS I/O stream filter for doing this, it's a
696 * generic problem and the new VFS APIs will handle it more
697 * transparently. (requires writing dos2unix/unix2dos filters ofc) */
698 if (!fOutputBinary)
699 {
700 /*
701 * If aOutputData is text data from the guest process' stdout or stderr,
702 * it has a platform dependent line ending. So standardize on
703 * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
704 * Windows. Otherwise we end up with CR/CR/LF on Windows.
705 */
706 ULONG cbOutputDataPrint = cbOutputData;
707 for (BYTE *s = aOutputData.raw(), *d = s;
708 s - aOutputData.raw() < (ssize_t)cbOutputData;
709 s++, d++)
710 {
711 if (*s == '\r')
712 {
713 /* skip over CR, adjust destination */
714 d--;
715 cbOutputDataPrint--;
716 }
717 else if (s != d)
718 *d = *s;
719 }
720 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
721 }
722 else /* Just dump all data as we got it ... */
723 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputData);
724 }
725 }
726 }
727
728 /* No more output data left? */
729 if (cbOutputData <= 0)
730 {
731 /* Only break out from process handling loop if we processed (displayed)
732 * all output data or if there simply never was output data and the process
733 * has been marked as complete. */
734 if (fCompleted)
735 break;
736 }
737
738 /* Process async cancelation */
739 if (g_fGuestCtrlCanceled && !fCanceledAlready)
740 {
741 hrc = progress->Cancel();
742 if (SUCCEEDED(hrc))
743 fCanceledAlready = TRUE;
744 else
745 g_fGuestCtrlCanceled = false;
746 }
747
748 /* Progress canceled by Main API? */
749 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
750 && fCanceled)
751 break;
752
753 /* Did we run out of time? */
754 if ( cMsTimeout
755 && RTTimeMilliTS() - u64StartMS > cMsTimeout)
756 {
757 progress->Cancel();
758 break;
759 }
760 } /* while */
761
762 /* Undo signal handling */
763 if (fCancelable)
764 ctrlSignalHandlerUninstall();
765
766 /* Report status back to the user. */
767 if (fCanceled)
768 {
769 if (fVerbose)
770 RTPrintf("Process execution canceled!\n");
771 rcProc = RTEXITCODE_EXEC_CANCELED;
772 }
773 else if ( fCompleted
774 && SUCCEEDED(rc)) /* The GetProcessOutput rc. */
775 {
776 LONG iRc;
777 CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
778 if (FAILED(iRc))
779 vrc = ctrlPrintProgressError(progress);
780 else
781 {
782 ULONG uRetStatus, uRetExitCode, uRetFlags;
783 rc = guest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &uRetStatus);
784 if (SUCCEEDED(rc) && fVerbose)
785 RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, uRetStatus, ctrlExecProcessStatusToText(uRetStatus), uRetFlags);
786 /** @todo r=bird: This isn't taking uRetExitCode into
787 * account. We MUST give the users a way to indicate
788 * whether the guest program terminated with an 0 or
789 * non-zero exit code. */
790 rcProc = ctrlExecProcessStatusToExitCode(uRetStatus);
791 }
792 }
793 else
794 {
795 if (fVerbose)
796 RTPrintf("Process execution aborted!\n");
797 /** @todo r=bird: Should set rcProc here, this is not a
798 * success branch. */
799 }
800 }
801 }
802
803 if (RT_FAILURE(vrc) || FAILED(rc))
804 return RTEXITCODE_FAILURE;
805 return rcProc;
806}
807
808/**
809 * Appends a new file/directory entry to a given list.
810 *
811 * @return IPRT status code.
812 * @param pszFileSource Full qualified source path of file to copy (optional).
813 * @param pszFileDest Full qualified destination path (optional).
814 * @param pList Copy list used for insertion.
815 */
816static int ctrlDirectoryEntryAppend(const char *pszFileSource, const char *pszFileDest,
817 PRTLISTNODE pList)
818{
819 AssertPtrReturn(pList, VERR_INVALID_POINTER);
820 AssertReturn(pszFileSource || pszFileDest, VERR_INVALID_PARAMETER);
821
822 PDIRECTORYENTRY pNode = (PDIRECTORYENTRY)RTMemAlloc(sizeof(DIRECTORYENTRY));
823 if (pNode == NULL)
824 return VERR_NO_MEMORY;
825
826 pNode->pszSourcePath = NULL;
827 pNode->pszDestPath = NULL;
828
829 if (pszFileSource)
830 {
831 pNode->pszSourcePath = RTStrDup(pszFileSource);
832 AssertPtrReturn(pNode->pszSourcePath, VERR_NO_MEMORY);
833 }
834 if (pszFileDest)
835 {
836 pNode->pszDestPath = RTStrDup(pszFileDest);
837 AssertPtrReturn(pNode->pszDestPath, VERR_NO_MEMORY);
838 }
839
840 pNode->Node.pPrev = NULL;
841 pNode->Node.pNext = NULL;
842 RTListAppend(pList, &pNode->Node);
843 return VINF_SUCCESS;
844}
845
846/**
847 * Destroys a directory list.
848 *
849 * @param pList Pointer to list to destroy.
850 */
851static void ctrlDirectoryListDestroy(PRTLISTNODE pList)
852{
853 AssertPtr(pList);
854
855 /* Destroy file list. */
856 PDIRECTORYENTRY pNode = RTListGetFirst(pList, DIRECTORYENTRY, Node);
857 while (pNode)
858 {
859 PDIRECTORYENTRY pNext = RTListNodeGetNext(&pNode->Node, DIRECTORYENTRY, Node);
860 bool fLast = RTListNodeIsLast(pList, &pNode->Node);
861
862 if (pNode->pszSourcePath)
863 RTStrFree(pNode->pszSourcePath);
864 if (pNode->pszDestPath)
865 RTStrFree(pNode->pszDestPath);
866 RTListNodeRemove(&pNode->Node);
867 RTMemFree(pNode);
868
869 if (fLast)
870 break;
871
872 pNode = pNext;
873 }
874}
875
876
877/**
878 * Reads a specified directory (recursively) based on the copy flags
879 * and appends all matching entries to the supplied list.
880 *
881 * @return IPRT status code.
882 * @param pszRootDir Directory to start with. Must end with
883 * a trailing slash and must be absolute.
884 * @param pszSubDir Sub directory part relative to the root
885 * directory; needed for recursion.
886 * @param pszFilter Search filter (e.g. *.pdf).
887 * @param pszDest Destination directory.
888 * @param fFlags Copy flags.
889 * @param pcObjects Where to store the overall objects to
890 * copy found.
891 * @param pList Pointer to the object list to use.
892 */
893static int ctrlCopyDirectoryRead(const char *pszRootDir, const char *pszSubDir,
894 const char *pszFilter, const char *pszDest,
895 uint32_t fFlags, uint32_t *pcObjects, PRTLISTNODE pList)
896{
897 AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
898 /* Sub directory is optional. */
899 /* Filter directory is optional. */
900 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
901 AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
902 AssertPtrReturn(pList, VERR_INVALID_POINTER);
903
904 PRTDIR pDir = NULL;
905
906 int rc = VINF_SUCCESS;
907 char szCurDir[RTPATH_MAX];
908 /* Construct current path. */
909 if (RTStrPrintf(szCurDir, sizeof(szCurDir), pszRootDir))
910 {
911 if (pszSubDir != NULL)
912 rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
913 }
914 else
915 rc = VERR_NO_MEMORY;
916
917 if (RT_SUCCESS(rc))
918 {
919 /* Open directory without a filter - RTDirOpenFiltered unfortunately
920 * cannot handle sub directories so we have to do the filtering ourselves. */
921 rc = RTDirOpen(&pDir, szCurDir);
922 for (;RT_SUCCESS(rc);)
923 {
924 RTDIRENTRY DirEntry;
925 rc = RTDirRead(pDir, &DirEntry, NULL);
926 if (RT_FAILURE(rc))
927 {
928 if (rc == VERR_NO_MORE_FILES)
929 rc = VINF_SUCCESS;
930 break;
931 }
932 switch (DirEntry.enmType)
933 {
934 case RTDIRENTRYTYPE_DIRECTORY:
935 /* Skip "." and ".." entrires. */
936 if ( !strcmp(DirEntry.szName, ".")
937 || !strcmp(DirEntry.szName, ".."))
938 {
939 break;
940 }
941 if (fFlags & CopyFileFlag_Recursive)
942 {
943 char *pszNewSub = NULL;
944 if (pszSubDir)
945 RTStrAPrintf(&pszNewSub, "%s%s/", pszSubDir, DirEntry.szName);
946 else
947 RTStrAPrintf(&pszNewSub, "%s/", DirEntry.szName);
948
949 if (pszNewSub)
950 {
951 rc = ctrlCopyDirectoryRead(pszRootDir, pszNewSub,
952 pszFilter, pszDest,
953 fFlags, pcObjects, pList);
954 RTStrFree(pszNewSub);
955 }
956 else
957 rc = VERR_NO_MEMORY;
958 }
959 break;
960
961 case RTDIRENTRYTYPE_SYMLINK:
962 if ( (fFlags & CopyFileFlag_Recursive)
963 && (fFlags & CopyFileFlag_FollowLinks))
964 {
965 /* Fall through to next case is intentional. */
966 }
967 else
968 break;
969
970 case RTDIRENTRYTYPE_FILE:
971 {
972 bool fProcess = false;
973 if (pszFilter && RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
974 fProcess = true;
975 else if (!pszFilter)
976 fProcess = true;
977
978 if (fProcess)
979 {
980 char *pszFileSource = NULL;
981 char *pszFileDest = NULL;
982 if (RTStrAPrintf(&pszFileSource, "%s%s%s",
983 pszRootDir, pszSubDir ? pszSubDir : "",
984 DirEntry.szName) >= 0)
985 {
986 if (RTStrAPrintf(&pszFileDest, "%s%s%s",
987 pszDest, pszSubDir ? pszSubDir : "",
988 DirEntry.szName) <= 0)
989 {
990 rc = VERR_NO_MEMORY;
991 }
992 }
993 else
994 rc = VERR_NO_MEMORY;
995
996 if (RT_SUCCESS(rc))
997 {
998 rc = ctrlDirectoryEntryAppend(pszFileSource, pszFileDest, pList);
999 if (RT_SUCCESS(rc))
1000 *pcObjects = *pcObjects + 1;
1001 }
1002
1003 if (pszFileSource)
1004 RTStrFree(pszFileSource);
1005 if (pszFileDest)
1006 RTStrFree(pszFileDest);
1007 }
1008 }
1009 break;
1010
1011 default:
1012 break;
1013 }
1014 if (RT_FAILURE(rc))
1015 break;
1016 }
1017 }
1018
1019 if (pDir)
1020 RTDirClose(pDir);
1021 return rc;
1022}
1023
1024/**
1025 * Initializes the copy process and builds up an object list
1026 * with all required information to start the actual copy process.
1027 *
1028 * @return IPRT status code.
1029 * @param pszSource Source path on host to use.
1030 * @param pszDest Destination path on guest to use.
1031 * @param fFlags Copy flags.
1032 * @param pcObjects Where to store the count of objects to be copied.
1033 * @param pList Where to store the object list.
1034 */
1035static int ctrlCopyInit(const char *pszSource, const char *pszDest, uint32_t fFlags,
1036 uint32_t *pcObjects, PRTLISTNODE pList)
1037{
1038 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
1039 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
1040 AssertPtrReturn(pcObjects, VERR_INVALID_PARAMETER);
1041 AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
1042
1043 int rc = VINF_SUCCESS;
1044 char *pszSourceAbs = RTPathAbsDup(pszSource);
1045 if (pszSourceAbs)
1046 {
1047 if ( RTPathFilename(pszSourceAbs)
1048 && RTFileExists(pszSourceAbs)) /* We have a single file ... */
1049 {
1050 char *pszDestAbs = RTStrDup(pszDest);
1051 if (pszDestAbs)
1052 {
1053 /* Do we have a trailing slash for the destination?
1054 * Then this is a directory ... */
1055 size_t cch = strlen(pszDestAbs);
1056 if ( cch > 1
1057 && ( RTPATH_IS_SLASH(pszDestAbs[cch - 1])
1058 || RTPATH_IS_SLASH(pszDestAbs[cch - 2])
1059 )
1060 )
1061 {
1062 rc = RTStrAAppend(&pszDestAbs, RTPathFilename(pszSourceAbs));
1063 }
1064 else
1065 {
1066 /* Since the desetination seems not to be a directory,
1067 * we assume that this is the absolute path to the destination
1068 * file -> nothing to do here ... */
1069 }
1070
1071 if (RT_SUCCESS(rc))
1072 {
1073 RTListInit(pList);
1074 rc = ctrlDirectoryEntryAppend(pszSourceAbs, pszDestAbs, pList);
1075 *pcObjects = 1;
1076 }
1077 RTStrFree(pszDestAbs);
1078 }
1079 else
1080 rc = VERR_NO_MEMORY;
1081 }
1082 else /* ... or a directory. */
1083 {
1084 /* Append trailing slash to absolute directory. */
1085 if (RTDirExists(pszSourceAbs))
1086 RTStrAAppend(&pszSourceAbs, RTPATH_SLASH_STR);
1087
1088 /* Extract directory filter (e.g. "*.exe"). */
1089 char *pszFilter = RTPathFilename(pszSourceAbs);
1090 char *pszSourceAbsRoot = RTStrDup(pszSourceAbs);
1091 char *pszDestAbs = RTStrDup(pszDest);
1092 if ( pszSourceAbsRoot
1093 && pszDestAbs)
1094 {
1095 if (pszFilter)
1096 {
1097 RTPathStripFilename(pszSourceAbsRoot);
1098 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
1099 }
1100 else
1101 {
1102 /*
1103 * If we have more than one file to copy, make sure that we have
1104 * a trailing slash so that we can construct a full path name
1105 * (e.g. "foo.txt" -> "c:/foo/temp.txt") as destination.
1106 */
1107 size_t cch = strlen(pszSourceAbsRoot);
1108 if ( cch > 1
1109 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 1])
1110 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 2]))
1111 {
1112 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
1113 }
1114 }
1115
1116 if (RT_SUCCESS(rc))
1117 {
1118 /*
1119 * Make sure we have a valid destination path. All we can do
1120 * here is to check whether we have a trailing slash -- the rest
1121 * (i.e. path creation, rights etc.) needs to be done inside the guest.
1122 */
1123 size_t cch = strlen(pszDestAbs);
1124 if ( cch > 1
1125 && !RTPATH_IS_SLASH(pszDestAbs[cch - 1])
1126 && !RTPATH_IS_SLASH(pszDestAbs[cch - 2]))
1127 {
1128 rc = RTStrAAppend(&pszDestAbs, RTPATH_SLASH_STR);
1129 }
1130 }
1131
1132 if (RT_SUCCESS(rc))
1133 {
1134 RTListInit(pList);
1135 rc = ctrlCopyDirectoryRead(pszSourceAbsRoot, NULL /* Sub directory */,
1136 pszFilter, pszDestAbs,
1137 fFlags, pcObjects, pList);
1138 if (RT_SUCCESS(rc) && *pcObjects == 0)
1139 rc = VERR_NOT_FOUND;
1140 }
1141
1142 if (pszDestAbs)
1143 RTStrFree(pszDestAbs);
1144 if (pszSourceAbsRoot)
1145 RTStrFree(pszSourceAbsRoot);
1146 }
1147 else
1148 rc = VERR_NO_MEMORY;
1149 }
1150 RTStrFree(pszSourceAbs);
1151 }
1152 else
1153 rc = VERR_NO_MEMORY;
1154 return rc;
1155}
1156
1157/**
1158 * Copys a file from host to the guest.
1159 *
1160 * @return IPRT status code.
1161 * @param pGuest IGuest interface pointer.
1162 * @param fVerbose Verbose flag.
1163 * @param pszSource Source path of existing host file to copy.
1164 * @param pszDest Destination path on guest to copy the file to.
1165 * @param pszUserName User name on guest to use for the copy operation.
1166 * @param pszPassword Password of user account.
1167 * @param fFlags Copy flags.
1168 */
1169static int ctrlCopyFileToGuest(IGuest *pGuest, bool fVerbose, const char *pszSource, const char *pszDest,
1170 const char *pszUserName, const char *pszPassword,
1171 uint32_t fFlags)
1172{
1173 AssertPtrReturn(pGuest, VERR_INVALID_PARAMETER);
1174 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
1175 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
1176 AssertPtrReturn(pszUserName, VERR_INVALID_PARAMETER);
1177 AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER);
1178
1179 int vrc = VINF_SUCCESS;
1180 ComPtr<IProgress> progress;
1181 HRESULT rc = pGuest->CopyToGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
1182 Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
1183 fFlags, progress.asOutParam());
1184 if (FAILED(rc))
1185 vrc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
1186 else
1187 {
1188 rc = showProgress(progress);
1189 if (FAILED(rc))
1190 vrc = ctrlPrintProgressError(progress);
1191 }
1192 return vrc;
1193}
1194
1195static int handleCtrlCopyTo(ComPtr<IGuest> guest, HandlerArg *pArg)
1196{
1197 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1198
1199 /*
1200 * Check the syntax.
1201 */
1202 if (pArg->argc < 2) /* At least the source + destination should be present :-). */
1203 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1204
1205 static const RTGETOPTDEF s_aOptions[] =
1206 {
1207 { "--dryrun", 'd', RTGETOPT_REQ_NOTHING },
1208 { "--follow", 'F', RTGETOPT_REQ_NOTHING },
1209 { "--password", 'p', RTGETOPT_REQ_STRING },
1210 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1211 { "--username", 'u', RTGETOPT_REQ_STRING },
1212 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1213 };
1214
1215 int ch;
1216 RTGETOPTUNION ValueUnion;
1217 RTGETOPTSTATE GetState;
1218 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1219
1220 Utf8Str Utf8Source;
1221 Utf8Str Utf8Dest;
1222 Utf8Str Utf8UserName;
1223 Utf8Str Utf8Password;
1224 uint32_t fFlags = CopyFileFlag_None;
1225 bool fVerbose = false;
1226 bool fCopyRecursive = false;
1227 bool fDryRun = false;
1228
1229 int vrc = VINF_SUCCESS;
1230 uint32_t idxNonOption = 0;
1231 bool fUsageOK = true;
1232 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1233 && RT_SUCCESS(vrc))
1234 {
1235 /* For options that require an argument, ValueUnion has received the value. */
1236 switch (ch)
1237 {
1238 case 'd': /* Dry run */
1239 fDryRun = true;
1240 break;
1241
1242 case 'F': /* Follow symlinks */
1243 fFlags |= CopyFileFlag_FollowLinks;
1244 break;
1245
1246 case 'p': /* Password */
1247 Utf8Password = ValueUnion.psz;
1248 break;
1249
1250 case 'R': /* Recursive processing */
1251 fFlags |= CopyFileFlag_Recursive;
1252 break;
1253
1254 case 'u': /* User name */
1255 Utf8UserName = ValueUnion.psz;
1256 break;
1257
1258 case 'v': /* Verbose */
1259 fVerbose = true;
1260 break;
1261
1262 case VINF_GETOPT_NOT_OPTION:
1263 {
1264 /* Get the actual source + destination. */
1265 switch (idxNonOption)
1266 {
1267 case 0:
1268 Utf8Source = ValueUnion.psz;
1269 break;
1270
1271 case 1:
1272 Utf8Dest = ValueUnion.psz;
1273 break;
1274
1275 default:
1276 /** @todo r=bird: {2..UINT32_MAX-1} goes unnoticed
1277 * to /dev/null? That doesn't make much
1278 * sense... Why not just return with a syntax
1279 * error straight away? */
1280 break;
1281 }
1282 idxNonOption++;
1283 if (idxNonOption == UINT32_MAX)
1284 {
1285 RTMsgError("Too many files specified! Aborting.\n");
1286 vrc = VERR_TOO_MUCH_DATA; /** @todo r=bird: return straight away, drop the RT_SUCCESS(vrc) from the while condition. Keep things simple. */
1287 }
1288 break;
1289 }
1290
1291 default:
1292 return RTGetOptPrintError(ch, &ValueUnion);
1293 }
1294 }
1295
1296 if (!fUsageOK) /** @todo r=bird: fUsageOK - never set to false, just drop it. Keep the state simple. */
1297 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1298
1299 if (Utf8Source.isEmpty())
1300 return errorSyntax(USAGE_GUESTCONTROL,
1301 "No source specified!");
1302
1303 if (Utf8Dest.isEmpty())
1304 return errorSyntax(USAGE_GUESTCONTROL,
1305 "No destination specified!");
1306
1307 if (Utf8UserName.isEmpty())
1308 return errorSyntax(USAGE_GUESTCONTROL,
1309 "No user name specified!");
1310
1311 /*
1312 * Done parsing arguments, do stuff.
1313 */
1314 HRESULT rc = S_OK;
1315 if (fVerbose)
1316 {
1317 if (fDryRun)
1318 RTPrintf("Dry run - no files copied!\n");
1319 RTPrintf("Gathering file information ...\n");
1320 }
1321
1322 RTLISTNODE listToCopy;
1323 uint32_t cObjects = 0;
1324 vrc = ctrlCopyInit(Utf8Source.c_str(), Utf8Dest.c_str(), fFlags,
1325 &cObjects, &listToCopy);
1326 if (RT_FAILURE(vrc))
1327 {
1328 switch (vrc)
1329 {
1330 case VERR_NOT_FOUND:
1331 RTMsgError("No files to copy found!\n");
1332 break;
1333
1334 case VERR_FILE_NOT_FOUND:
1335 RTMsgError("Source path \"%s\" not found!\n", Utf8Source.c_str());
1336 break;
1337
1338 default:
1339 RTMsgError("Failed to initialize, rc=%Rrc\n", vrc);
1340 break;
1341 }
1342 }
1343 else
1344 {
1345 PDIRECTORYENTRY pNode;
1346 if (RT_SUCCESS(vrc))
1347 {
1348 if (fVerbose)
1349 {
1350 if (fCopyRecursive)
1351 RTPrintf("Recursively copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1352 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1353 else
1354 RTPrintf("Copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1355 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1356 }
1357
1358 uint32_t iObject = 1;
1359 RTListForEach(&listToCopy, pNode, DIRECTORYENTRY, Node)
1360 {
1361 if (!fDryRun)
1362 {
1363 if (fVerbose)
1364 RTPrintf("Copying \"%s\" to \"%s\" (%u/%u) ...\n",
1365 pNode->pszSourcePath, pNode->pszDestPath, iObject, cObjects);
1366 /* Finally copy the desired file (if no dry run selected). */
1367 if (!fDryRun)
1368 vrc = ctrlCopyFileToGuest(guest, fVerbose, pNode->pszSourcePath, pNode->pszDestPath,
1369 Utf8UserName.c_str(), Utf8Password.c_str(), fFlags);
1370 }
1371 if (RT_FAILURE(vrc))
1372 break;
1373 iObject++;
1374 }
1375 if (RT_SUCCESS(vrc) && fVerbose)
1376 RTPrintf("Copy operation successful!\n");
1377 }
1378 ctrlDirectoryListDestroy(&listToCopy);
1379 }
1380
1381 if (RT_FAILURE(vrc))
1382 rc = VBOX_E_IPRT_ERROR;
1383 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1384}
1385
1386static int handleCtrlCreateDirectory(ComPtr<IGuest> guest, HandlerArg *pArg)
1387{
1388 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1389
1390 /*
1391 * Check the syntax. We can deduce the correct syntax from the number of
1392 * arguments.
1393 */
1394 if (pArg->argc < 2) /* At least the directory we want to create should be present :-). */
1395 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1396
1397 static const RTGETOPTDEF s_aOptions[] =
1398 {
1399 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
1400 { "--parents", 'P', RTGETOPT_REQ_NOTHING },
1401 { "--password", 'p', RTGETOPT_REQ_STRING },
1402 { "--username", 'u', RTGETOPT_REQ_STRING },
1403 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1404 };
1405
1406 int ch;
1407 RTGETOPTUNION ValueUnion;
1408 RTGETOPTSTATE GetState;
1409 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1410
1411 Utf8Str Utf8UserName;
1412 Utf8Str Utf8Password;
1413 uint32_t fFlags = CreateDirectoryFlag_None;
1414 uint32_t uMode = 0;
1415 bool fVerbose = false;
1416
1417 RTLISTNODE listDirs;
1418 uint32_t cDirs = 0;
1419 RTListInit(&listDirs);
1420
1421 int vrc = VINF_SUCCESS;
1422 bool fUsageOK = true;
1423 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1424 && RT_SUCCESS(vrc))
1425 {
1426 /* For options that require an argument, ValueUnion has received the value. */
1427 switch (ch)
1428 {
1429 case 'm': /* Mode */
1430 uMode = ValueUnion.u32;
1431 break;
1432
1433 case 'P': /* Create parents */
1434 fFlags |= CreateDirectoryFlag_Parents;
1435 break;
1436
1437 case 'p': /* Password */
1438 Utf8Password = ValueUnion.psz;
1439 break;
1440
1441 case 'u': /* User name */
1442 Utf8UserName = ValueUnion.psz;
1443 break;
1444
1445 case 'v': /* Verbose */
1446 fVerbose = true;
1447 break;
1448
1449 case VINF_GETOPT_NOT_OPTION:
1450 {
1451 vrc = ctrlDirectoryEntryAppend(NULL, /* No source given */
1452 ValueUnion.psz, /* Destination */
1453 &listDirs);
1454 if (RT_SUCCESS(vrc))
1455 {
1456 cDirs++;
1457 if (cDirs == UINT32_MAX)
1458 {
1459 RTMsgError("Too many directories specified! Aborting.\n");
1460 vrc = VERR_TOO_MUCH_DATA;
1461 }
1462 }
1463 break;
1464 }
1465
1466 default:
1467 return RTGetOptPrintError(ch, &ValueUnion);
1468 }
1469 }
1470
1471 if (!fUsageOK)
1472 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1473
1474 if (!cDirs)
1475 return errorSyntax(USAGE_GUESTCONTROL,
1476 "No directory to create specified!");
1477
1478 if (Utf8UserName.isEmpty())
1479 return errorSyntax(USAGE_GUESTCONTROL,
1480 "No user name specified!");
1481
1482 HRESULT rc = S_OK;
1483 if (fVerbose && cDirs > 1)
1484 RTPrintf("Creating %u directories ...\n", cDirs);
1485
1486 PDIRECTORYENTRY pNode;
1487 RTListForEach(&listDirs, pNode, DIRECTORYENTRY, Node)
1488 {
1489 if (fVerbose)
1490 RTPrintf("Creating directory \"%s\" ...\n", pNode->pszDestPath);
1491
1492 ComPtr<IProgress> progress;
1493 rc = guest->CreateDirectory(Bstr(pNode->pszDestPath).raw(),
1494 Bstr(Utf8UserName).raw(), Bstr(Utf8Password).raw(),
1495 uMode, fFlags, progress.asOutParam());
1496 if (FAILED(rc))
1497 {
1498 ctrlPrintError(guest, COM_IIDOF(IGuest)); /* (return code ignored, save original rc) */
1499 break;
1500 }
1501 }
1502 ctrlDirectoryListDestroy(&listDirs);
1503 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1504}
1505
1506static int handleCtrlUpdateAdditions(ComPtr<IGuest> guest, HandlerArg *pArg)
1507{
1508 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1509
1510 /*
1511 * Check the syntax. We can deduce the correct syntax from the number of
1512 * arguments.
1513 */
1514 if (pArg->argc < 1) /* At least the VM name should be present :-). */ /** @todo r=bird: This is no longer correct. Ditto for all other handlers. */
1515 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1516
1517 Utf8Str Utf8Source;
1518 bool fVerbose = false;
1519
1520 static const RTGETOPTDEF s_aOptions[] =
1521 {
1522 { "--source", 's', RTGETOPT_REQ_STRING },
1523 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1524 };
1525
1526 int ch;
1527 RTGETOPTUNION ValueUnion;
1528 RTGETOPTSTATE GetState;
1529 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1530
1531 int vrc = VINF_SUCCESS;
1532 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1533 && RT_SUCCESS(vrc))
1534 {
1535 switch (ch)
1536 {
1537 case 's':
1538 Utf8Source = ValueUnion.psz;
1539 break;
1540
1541 case 'v':
1542 fVerbose = true;
1543 break;
1544
1545 default:
1546 return RTGetOptPrintError(ch, &ValueUnion);
1547 }
1548 }
1549
1550 if (fVerbose)
1551 RTPrintf("Updating Guest Additions ...\n");
1552
1553#ifdef DEBUG_andy
1554 if (Utf8Source.isEmpty())
1555 Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
1556#endif
1557
1558 /* Determine source if not set yet. */
1559 if (Utf8Source.isEmpty())
1560 {
1561 char strTemp[RTPATH_MAX];
1562 vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
1563 AssertRC(vrc);
1564 Utf8Str Utf8Src1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
1565
1566 vrc = RTPathExecDir(strTemp, sizeof(strTemp));
1567 AssertRC(vrc);
1568 Utf8Str Utf8Src2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
1569
1570 /* Check the standard image locations */
1571 if (RTFileExists(Utf8Src1.c_str()))
1572 Utf8Source = Utf8Src1;
1573 else if (RTFileExists(Utf8Src2.c_str()))
1574 Utf8Source = Utf8Src2;
1575 else
1576 {
1577 RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
1578 vrc = VERR_FILE_NOT_FOUND;
1579 }
1580 }
1581 else if (!RTFileExists(Utf8Source.c_str()))
1582 {
1583 RTMsgError("Source \"%s\" does not exist!\n", Utf8Source.c_str());
1584 vrc = VERR_FILE_NOT_FOUND;
1585 }
1586
1587 if (RT_SUCCESS(vrc))
1588 {
1589 if (fVerbose)
1590 RTPrintf("Using source: %s\n", Utf8Source.c_str());
1591
1592 HRESULT rc = S_OK;
1593 ComPtr<IProgress> progress;
1594 CHECK_ERROR(guest, UpdateGuestAdditions(Bstr(Utf8Source).raw(),
1595 /* Wait for whole update process to complete. */
1596 AdditionsUpdateFlag_None,
1597 progress.asOutParam()));
1598 if (FAILED(rc))
1599 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
1600 else
1601 {
1602 rc = showProgress(progress);
1603 if (FAILED(rc))
1604 vrc = ctrlPrintProgressError(progress);
1605 else if (fVerbose)
1606 RTPrintf("Guest Additions update successful.\n");
1607 }
1608 }
1609
1610 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1611}
1612
1613/**
1614 * Access the guest control store.
1615 *
1616 * @returns program exit code.
1617 * @note see the command line API description for parameters
1618 */
1619int handleGuestControl(HandlerArg *pArg)
1620{
1621 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1622
1623 /* At least the VM name + sub command needs to be specified. */
1624 if (pArg->argc <= 2)
1625 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1626
1627 HandlerArg arg = *pArg;
1628 arg.argc = pArg->argc - 2; /* Skip VM name and sub command. */
1629 arg.argv = pArg->argv + 2; /* Same here. */
1630
1631 ComPtr<IGuest> guest;
1632 int vrc = ctrlInitVM(pArg, pArg->argv[0] /* VM Name */, &guest);
1633 if (RT_SUCCESS(vrc))
1634 {
1635 int rcExit;
1636 if ( !strcmp(pArg->argv[1], "exec")
1637 || !strcmp(pArg->argv[1], "execute"))
1638 {
1639 rcExit = handleCtrlExecProgram(guest, &arg);
1640 }
1641 else if ( !strcmp(pArg->argv[1], "copyto")
1642 || !strcmp(pArg->argv[1], "cp"))
1643 {
1644 rcExit = handleCtrlCopyTo(guest, &arg);
1645 }
1646 else if ( !strcmp(pArg->argv[1], "createdirectory")
1647 || !strcmp(pArg->argv[1], "createdir")
1648 || !strcmp(pArg->argv[1], "mkdir")
1649 || !strcmp(pArg->argv[1], "md"))
1650 {
1651 rcExit = handleCtrlCreateDirectory(guest, &arg);
1652 }
1653 else if ( !strcmp(pArg->argv[1], "updateadditions")
1654 || !strcmp(pArg->argv[1], "updateadds"))
1655 {
1656 rcExit = handleCtrlUpdateAdditions(guest, &arg);
1657 }
1658 else
1659 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
1660
1661 ctrlUninitVM(pArg);
1662 return rcExit;
1663 }
1664 return RTEXITCODE_FAILURE;
1665}
1666
1667#endif /* !VBOX_ONLY_DOCS */
1668
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette