VirtualBox

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

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

VBoxManage/GuestCtrl: Adjusted assertion, warn if not all elements could be copied.

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