VirtualBox

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

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

VBoxManage/GuestCtrl: Added multiple source handling for copyto and made it more similar to cp.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 56.4 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 36609 2011-04-07 09:20:29Z 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 (args.size() == 0 && Utf8Cmd.isEmpty())
500 Utf8Cmd = ValueUnion.psz;
501 args.push_back(Bstr(ValueUnion.psz).raw());
502 break;
503 }
504
505 default:
506 return RTGetOptPrintError(ch, &ValueUnion);
507 }
508 }
509
510 if (Utf8Cmd.isEmpty())
511 return errorSyntax(USAGE_GUESTCONTROL, "No command to execute specified!");
512
513 if (Utf8UserName.isEmpty())
514 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
515
516 /*
517 * <missing comment indicating that we're done parsing args and started doing something else>
518 */
519 HRESULT rc = S_OK;
520 if (fVerbose)
521 {
522 if (cMsTimeout == 0)
523 RTPrintf("Waiting for guest to start process ...\n");
524 else
525 RTPrintf("Waiting for guest to start process (within %ums)\n", cMsTimeout);
526 }
527
528 /* Get current time stamp to later calculate rest of timeout left. */
529 uint64_t u64StartMS = RTTimeMilliTS();
530
531 /* Execute the process. */
532 int rcProc; /** @todo: Not always initialized! */
533 ComPtr<IProgress> progress;
534 ULONG uPID = 0;
535 rc = guest->ExecuteProcess(Bstr(Utf8Cmd).raw(),
536 fFlags,
537 ComSafeArrayAsInParam(args),
538 ComSafeArrayAsInParam(env),
539 Bstr(Utf8UserName).raw(),
540 Bstr(Utf8Password).raw(),
541 cMsTimeout,
542 &uPID,
543 progress.asOutParam());
544 if (FAILED(rc))
545 return ctrlPrintError(guest, COM_IIDOF(IGuest));
546
547 if (fVerbose)
548 RTPrintf("Process '%s' (PID: %u) started\n", Utf8Cmd.c_str(), uPID);
549 if (fWaitForExit)
550 {
551 if (fVerbose)
552 {
553 if (cMsTimeout) /* Wait with a certain timeout. */
554 {
555 /* Calculate timeout value left after process has been started. */
556 uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
557 /* Is timeout still bigger than current difference? */
558 if (cMsTimeout > u64Elapsed)
559 RTPrintf("Waiting for process to exit (%ums left) ...\n", cMsTimeout - u64Elapsed);
560 else
561 RTPrintf("No time left to wait for process!\n"); /** @todo a bit misleading ... */
562 }
563 else /* Wait forever. */
564 RTPrintf("Waiting for process to exit ...\n");
565 }
566
567 /* Setup signal handling if cancelable. */
568 ASSERT(progress);
569 bool fCanceledAlready = false;
570 BOOL fCancelable;
571 HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
572 if (FAILED(hrc))
573 fCancelable = FALSE;
574 if (fCancelable)
575 ctrlSignalHandlerInstall();
576
577 /* Wait for process to exit ... */
578 BOOL fCompleted = FALSE;
579 BOOL fCanceled = FALSE;
580 int cMilliesSleep = 0;
581 while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted))))
582 {
583 SafeArray<BYTE> aOutputData;
584 ULONG cbOutputData = 0;
585
586 /*
587 * Some data left to output?
588 */
589 if ( fWaitForStdOut
590 || fWaitForStdErr)
591 {
592 /** @todo r=bird: The timeout argument is bogus in several
593 * ways:
594 * 1. RT_MAX will evaluate the arguments twice, which may
595 * result in different values because RTTimeMilliTS()
596 * returns a higher value the 2nd time. Worst case:
597 * Imagine when RT_MAX calculates the remaining time
598 * out (first expansion) there is say 60 ms left. Then
599 * we're preempted and rescheduled after, say, 120 ms.
600 * We call RTTimeMilliTS() again and ends up with a
601 * value -60 ms, which translate to a UINT32_MAX - 59
602 * ms timeout.
603 *
604 * 2. When the period expires, we will wait forever since
605 * both 0 and -1 mean indefinite timeout with this API,
606 * at least that's one way of reading the main code.
607 *
608 * 3. There is a signed/unsigned ambiguity in the
609 * RT_MAX expression. The left hand side is signed
610 * integer (0), the right side is unsigned 64-bit. From
611 * what I can tell, the compiler will treat this as
612 * unsigned 64-bit and never return 0.
613 */
614 /** @todo r=bird: We must separate stderr and stdout
615 * output, seems bunched together here which
616 * won't do the trick for unix BOFHs. */
617 rc = guest->GetProcessOutput(uPID, 0 /* aFlags */,
618 RT_MAX(0, cMsTimeout - (RTTimeMilliTS() - u64StartMS)) /* Timeout in ms */,
619 _64K, ComSafeArrayAsOutParam(aOutputData));
620 if (FAILED(rc))
621 {
622 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
623 cbOutputData = 0;
624 }
625 else
626 {
627 cbOutputData = aOutputData.size();
628 if (cbOutputData > 0)
629 {
630 /** @todo r=bird: Use a VFS I/O stream filter for doing this, it's a
631 * generic problem and the new VFS APIs will handle it more
632 * transparently. (requires writing dos2unix/unix2dos filters ofc) */
633 if (eOutputType != OUTPUTTYPE_UNDEFINED)
634 {
635 /*
636 * If aOutputData is text data from the guest process' stdout or stderr,
637 * it has a platform dependent line ending. So standardize on
638 * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
639 * Windows. Otherwise we end up with CR/CR/LF on Windows.
640 */
641 ULONG cbOutputDataPrint = cbOutputData;
642 for (BYTE *s = aOutputData.raw(), *d = s;
643 s - aOutputData.raw() < (ssize_t)cbOutputData;
644 s++, d++)
645 {
646 if (*s == '\r')
647 {
648 /* skip over CR, adjust destination */
649 d--;
650 cbOutputDataPrint--;
651 }
652 else if (s != d)
653 *d = *s;
654 }
655 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
656 }
657 else /* Just dump all data as we got it ... */
658 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputData);
659 }
660 }
661 }
662
663 /* No more output data left? */
664 if (cbOutputData <= 0)
665 {
666 /* Only break out from process handling loop if we processed (displayed)
667 * all output data or if there simply never was output data and the process
668 * has been marked as complete. */
669 if (fCompleted)
670 break;
671 }
672
673 /* Process async cancelation */
674 if (g_fGuestCtrlCanceled && !fCanceledAlready)
675 {
676 hrc = progress->Cancel();
677 if (SUCCEEDED(hrc))
678 fCanceledAlready = TRUE;
679 else
680 g_fGuestCtrlCanceled = false;
681 }
682
683 /* Progress canceled by Main API? */
684 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
685 && fCanceled)
686 break;
687
688 /* Did we run out of time? */
689 if ( cMsTimeout
690 && RTTimeMilliTS() - u64StartMS > cMsTimeout)
691 {
692 progress->Cancel();
693 break;
694 }
695 } /* while */
696
697 /* Undo signal handling */
698 if (fCancelable)
699 ctrlSignalHandlerUninstall();
700
701 /* Report status back to the user. */
702 if (fCanceled)
703 {
704 if (fVerbose)
705 RTPrintf("Process execution canceled!\n");
706 rcProc = EXITCODEEXEC_CANCELED;
707 }
708 else if ( fCompleted
709 && SUCCEEDED(rc)) /* The GetProcessOutput rc. */
710 {
711 LONG iRc;
712 CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
713 if (FAILED(iRc))
714 vrc = ctrlPrintProgressError(progress);
715 else
716 {
717 ExecuteProcessStatus_T retStatus;
718 ULONG uRetExitCode, uRetFlags;
719 rc = guest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &retStatus);
720 if (SUCCEEDED(rc) && fVerbose)
721 RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, retStatus, ctrlExecProcessStatusToText(retStatus), uRetFlags);
722 rcProc = ctrlExecProcessStatusToExitCode(retStatus, uRetExitCode);
723 }
724 }
725 else
726 {
727 if (fVerbose)
728 RTPrintf("Process execution aborted!\n");
729 rcProc = EXITCODEEXEC_TERM_ABEND;
730 }
731 }
732
733 if (RT_FAILURE(vrc) || FAILED(rc))
734 return RTEXITCODE_FAILURE;
735 return rcProc;
736}
737
738/**
739 * Appends a new file/directory entry to a given list.
740 *
741 * @return IPRT status code.
742 * @param pszFileSource Full qualified source path of file to copy (optional).
743 * @param pszFileDest Full qualified destination path (optional).
744 * @param pList Copy list used for insertion.
745 *
746 * @todo r=bird: Since everyone is maintaining an entry count, it would make
747 * sense to abstract the list. To simplify cleanup work, it would be
748 * preferable to use a standard C++ container template class.
749 */
750static int ctrlDirectoryEntryAppend(const char *pszFileSource, const char *pszFileDest,
751 PRTLISTNODE pList)
752{
753 AssertPtrReturn(pList, VERR_INVALID_POINTER);
754 AssertReturn(pszFileSource || pszFileDest, VERR_INVALID_PARAMETER);
755
756 PDIRECTORYENTRY pNode = (PDIRECTORYENTRY)RTMemAlloc(sizeof(DIRECTORYENTRY));
757 if (pNode == NULL)
758 return VERR_NO_MEMORY;
759
760 pNode->pszSourcePath = NULL;
761 pNode->pszDestPath = NULL;
762
763 if (pszFileSource)
764 {
765 pNode->pszSourcePath = RTStrDup(pszFileSource);
766 AssertPtrReturn(pNode->pszSourcePath, VERR_NO_MEMORY);
767 }
768 if (pszFileDest)
769 {
770 pNode->pszDestPath = RTStrDup(pszFileDest);
771 AssertPtrReturn(pNode->pszDestPath, VERR_NO_MEMORY);
772 }
773
774 pNode->Node.pPrev = NULL;
775 pNode->Node.pNext = NULL;
776 RTListAppend(pList, &pNode->Node);
777 return VINF_SUCCESS;
778}
779
780/**
781 * Destroys a directory list.
782 *
783 * @param pList Pointer to list to destroy.
784 */
785static void ctrlDirectoryListDestroy(PRTLISTNODE pList)
786{
787 AssertPtr(pList);
788
789 /* Destroy file list. */
790 PDIRECTORYENTRY pNode = RTListGetFirst(pList, DIRECTORYENTRY, Node);
791 while (pNode)
792 {
793 PDIRECTORYENTRY pNext = RTListNodeGetNext(&pNode->Node, DIRECTORYENTRY, Node);
794 bool fLast = RTListNodeIsLast(pList, &pNode->Node);
795
796 if (pNode->pszSourcePath)
797 RTStrFree(pNode->pszSourcePath);
798 if (pNode->pszDestPath)
799 RTStrFree(pNode->pszDestPath);
800 RTListNodeRemove(&pNode->Node);
801 RTMemFree(pNode);
802
803 if (fLast)
804 break;
805
806 pNode = pNext;
807 }
808}
809
810
811/**
812 * Reads a specified directory (recursively) based on the copy flags
813 * and appends all matching entries to the supplied list.
814 *
815 * @return IPRT status code.
816 * @param pszRootDir Directory to start with. Must end with
817 * a trailing slash and must be absolute.
818 * @param pszSubDir Sub directory part relative to the root
819 * directory; needed for recursion.
820 * @param pszFilter Search filter (e.g. *.pdf).
821 * @param pszDest Destination directory.
822 * @param fFlags Copy flags.
823 * @param pcObjects Where to store the overall objects to
824 * copy found.
825 * @param pList Pointer to the object list to use.
826 */
827static int ctrlCopyDirectoryRead(const char *pszRootDir, const char *pszSubDir,
828 const char *pszFilter, const char *pszDest,
829 uint32_t fFlags, uint32_t *pcObjects, PRTLISTNODE pList)
830{
831 AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
832 /* Sub directory is optional. */
833 /* Filter directory is optional. */
834 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
835 AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
836 AssertPtrReturn(pList, VERR_INVALID_POINTER);
837
838 /*
839 * Construct current path.
840 */
841 char szCurDir[RTPATH_MAX];
842 int rc = RTStrCopy(szCurDir, sizeof(szCurDir), pszRootDir);
843 if (RT_SUCCESS(rc) && pszSubDir != NULL)
844 rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
845
846 /*
847 * Open directory without a filter - RTDirOpenFiltered unfortunately
848 * cannot handle sub directories so we have to do the filtering ourselves.
849 */
850 PRTDIR pDir = NULL;
851 if (RT_SUCCESS(rc))
852 {
853 rc = RTDirOpen(&pDir, szCurDir);
854 if (RT_FAILURE(rc))
855 pDir = NULL;
856 }
857 if (RT_SUCCESS(rc))
858 {
859 /*
860 * Enumerate the directory tree.
861 */
862 while (RT_SUCCESS(rc))
863 {
864 RTDIRENTRY DirEntry;
865 rc = RTDirRead(pDir, &DirEntry, NULL);
866 if (RT_FAILURE(rc))
867 {
868 if (rc == VERR_NO_MORE_FILES)
869 rc = VINF_SUCCESS;
870 break;
871 }
872 switch (DirEntry.enmType)
873 {
874 case RTDIRENTRYTYPE_DIRECTORY:
875 /* Skip "." and ".." entries. */
876 if ( !strcmp(DirEntry.szName, ".")
877 || !strcmp(DirEntry.szName, ".."))
878 break;
879
880 if (fFlags & CopyFileFlag_Recursive)
881 {
882 char *pszNewSub = NULL;
883 if (pszSubDir)
884 RTStrAPrintf(&pszNewSub, "%s%s/", pszSubDir, DirEntry.szName);
885 else
886 RTStrAPrintf(&pszNewSub, "%s/", DirEntry.szName);
887
888 if (pszNewSub)
889 {
890 rc = ctrlCopyDirectoryRead(pszRootDir, pszNewSub,
891 pszFilter, pszDest,
892 fFlags, pcObjects, pList);
893 RTStrFree(pszNewSub);
894 }
895 else
896 rc = VERR_NO_MEMORY;
897 }
898 break;
899
900 case RTDIRENTRYTYPE_SYMLINK:
901 if ( (fFlags & CopyFileFlag_Recursive)
902 && (fFlags & CopyFileFlag_FollowLinks))
903 {
904 /* Fall through to next case is intentional. */
905 }
906 else
907 break;
908
909 case RTDIRENTRYTYPE_FILE:
910 {
911 if ( !pszFilter
912 || RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
913 {
914 char *pszFileSource = NULL;
915 char *pszFileDest = NULL;
916 if (RTStrAPrintf(&pszFileSource, "%s%s%s",
917 pszRootDir, pszSubDir ? pszSubDir : "",
918 DirEntry.szName) >= 0)
919 {
920 if (RTStrAPrintf(&pszFileDest, "%s%s%s",
921 pszDest, pszSubDir ? pszSubDir : "",
922 DirEntry.szName) <= 0)
923 {
924 rc = VERR_NO_MEMORY;
925 }
926 }
927 else
928 rc = VERR_NO_MEMORY;
929
930 if (RT_SUCCESS(rc))
931 {
932 rc = ctrlDirectoryEntryAppend(pszFileSource, pszFileDest, pList);
933 if (RT_SUCCESS(rc))
934 *pcObjects += 1;
935 }
936
937 if (pszFileSource)
938 RTStrFree(pszFileSource);
939 if (pszFileDest)
940 RTStrFree(pszFileDest);
941 }
942 }
943 break;
944
945 default:
946 break;
947 }
948 if (RT_FAILURE(rc))
949 break;
950 }
951
952 RTDirClose(pDir);
953 }
954 return rc;
955}
956
957/**
958 * Initializes the copy process and builds up an object list
959 * with all required information to start the actual copy process.
960 *
961 * @return IPRT status code.
962 * @param pszSource Source path on host to use.
963 * @param pszDest Destination path on guest to use.
964 * @param fFlags Copy flags.
965 * @param pcObjects Where to store the count of objects to be copied.
966 * @param pList Where to store the object list.
967 */
968static int ctrlCopyInit(const char *pszSource, const char *pszDest, uint32_t fFlags,
969 uint32_t *pcObjects, PRTLISTNODE pList)
970{
971 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
972 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
973 AssertPtrReturn(pcObjects, VERR_INVALID_PARAMETER);
974 AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
975
976 int rc = VINF_SUCCESS;
977 char *pszSourceAbs = RTPathAbsDup(pszSource);
978 if (pszSourceAbs)
979 {
980 if ( RTPathFilename(pszSourceAbs)
981 && RTFileExists(pszSourceAbs)) /* We have a single file ... */
982 {
983 char *pszDestAbs = RTStrDup(pszDest);
984 if (pszDestAbs)
985 {
986 /* Do we have a trailing slash for the destination?
987 * Then this is a directory ... */
988 size_t cch = strlen(pszDestAbs);
989 if ( cch > 1
990 && ( RTPATH_IS_SLASH(pszDestAbs[cch - 1])
991 || RTPATH_IS_SLASH(pszDestAbs[cch - 2])
992 )
993 )
994 {
995 rc = RTStrAAppend(&pszDestAbs, RTPathFilename(pszSourceAbs));
996 }
997 else
998 {
999 /* Since the desetination seems not to be a directory,
1000 * we assume that this is the absolute path to the destination
1001 * file -> nothing to do here ... */
1002 }
1003
1004 if (RT_SUCCESS(rc))
1005 {
1006 rc = ctrlDirectoryEntryAppend(pszSourceAbs, pszDestAbs, pList);
1007 *pcObjects = 1;
1008 }
1009 RTStrFree(pszDestAbs);
1010 }
1011 else
1012 rc = VERR_NO_MEMORY;
1013 }
1014 else /* ... or a directory. */
1015 {
1016 /* Append trailing slash to absolute directory. */
1017 if (RTDirExists(pszSourceAbs))
1018 RTStrAAppend(&pszSourceAbs, RTPATH_SLASH_STR);
1019
1020 /* Extract directory filter (e.g. "*.exe"). */
1021 char *pszFilter = RTPathFilename(pszSourceAbs);
1022 char *pszSourceAbsRoot = RTStrDup(pszSourceAbs);
1023 char *pszDestAbs = RTStrDup(pszDest);
1024 if ( pszSourceAbsRoot
1025 && pszDestAbs)
1026 {
1027 if (pszFilter)
1028 {
1029 RTPathStripFilename(pszSourceAbsRoot);
1030 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
1031 }
1032 else
1033 {
1034 /*
1035 * If we have more than one file to copy, make sure that we have
1036 * a trailing slash so that we can construct a full path name
1037 * (e.g. "foo.txt" -> "c:/foo/temp.txt") as destination.
1038 */
1039 size_t cch = strlen(pszSourceAbsRoot);
1040 if ( cch > 1
1041 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 1])
1042 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 2]))
1043 {
1044 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
1045 }
1046 }
1047
1048 if (RT_SUCCESS(rc))
1049 {
1050 /*
1051 * Make sure we have a valid destination path. All we can do
1052 * here is to check whether we have a trailing slash -- the rest
1053 * (i.e. path creation, rights etc.) needs to be done inside the guest.
1054 */
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 rc = RTStrAAppend(&pszDestAbs, RTPATH_SLASH_STR);
1061 }
1062 }
1063
1064 if (RT_SUCCESS(rc))
1065 {
1066 rc = ctrlCopyDirectoryRead(pszSourceAbsRoot, NULL /* Sub directory */,
1067 pszFilter, pszDestAbs,
1068 fFlags, pcObjects, pList);
1069 if (RT_SUCCESS(rc) && *pcObjects == 0)
1070 rc = VERR_NOT_FOUND;
1071 }
1072
1073 if (pszDestAbs)
1074 RTStrFree(pszDestAbs);
1075 if (pszSourceAbsRoot)
1076 RTStrFree(pszSourceAbsRoot);
1077 }
1078 else
1079 rc = VERR_NO_MEMORY;
1080 }
1081 RTStrFree(pszSourceAbs);
1082 }
1083 else
1084 rc = VERR_NO_MEMORY;
1085 return rc;
1086}
1087
1088/**
1089 * Copys a file from host to the guest.
1090 *
1091 * @return IPRT status code.
1092 * @param pGuest IGuest interface pointer.
1093 * @param fVerbose Verbose flag.
1094 * @param pszSource Source path of existing host file to copy.
1095 * @param pszDest Destination path on guest to copy the file to.
1096 * @param pszUserName User name on guest to use for the copy operation.
1097 * @param pszPassword Password of user account.
1098 * @param fFlags Copy flags.
1099 */
1100static int ctrlCopyFileToGuest(IGuest *pGuest, bool fVerbose, const char *pszSource, const char *pszDest,
1101 const char *pszUserName, const char *pszPassword,
1102 uint32_t fFlags)
1103{
1104 AssertPtrReturn(pGuest, VERR_INVALID_PARAMETER);
1105 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
1106 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
1107 AssertPtrReturn(pszUserName, VERR_INVALID_PARAMETER);
1108 AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER);
1109
1110 int vrc = VINF_SUCCESS;
1111 ComPtr<IProgress> progress;
1112 HRESULT rc = pGuest->CopyToGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
1113 Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
1114 fFlags, progress.asOutParam());
1115 if (FAILED(rc))
1116 vrc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
1117 else
1118 {
1119 rc = showProgress(progress);
1120 if (FAILED(rc))
1121 vrc = ctrlPrintProgressError(progress);
1122 }
1123 return vrc;
1124}
1125
1126static int handleCtrlCopyTo(ComPtr<IGuest> guest, HandlerArg *pArg)
1127{
1128 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1129
1130 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
1131 * is much better (partly because it is much simpler of course). The main
1132 * arguments against this is that (1) all but two options conflicts with
1133 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
1134 * done windows CMD style (though not in a 100% compatible way), and (3)
1135 * that only one source is allowed - efficiently sabotaging default
1136 * wildcard expansion by a unix shell. The best solution here would be
1137 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
1138
1139 /*
1140 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
1141 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
1142 * does in here.
1143 */
1144
1145 static const RTGETOPTDEF s_aOptions[] =
1146 {
1147 { "--dryrun", GETOPTDEF_COPYTO_DRYRUN, RTGETOPT_REQ_NOTHING },
1148 { "--follow", GETOPTDEF_COPYTO_FOLLOW, RTGETOPT_REQ_NOTHING },
1149 { "--password", GETOPTDEF_COPYTO_PASSWORD, RTGETOPT_REQ_STRING },
1150 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1151 { "--target-directory", GETOPTDEF_COPYTO_TARGETDIR, RTGETOPT_REQ_STRING },
1152 { "--username", GETOPTDEF_COPYTO_USERNAME, RTGETOPT_REQ_STRING },
1153 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1154 };
1155
1156 int ch;
1157 RTGETOPTUNION ValueUnion;
1158 RTGETOPTSTATE GetState;
1159 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
1160 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1161
1162 Utf8Str Utf8Source;
1163 Utf8Str Utf8Dest;
1164 Utf8Str Utf8UserName;
1165 Utf8Str Utf8Password;
1166 uint32_t fFlags = CopyFileFlag_None;
1167 bool fVerbose = false;
1168 bool fCopyRecursive = false;
1169 bool fDryRun = false;
1170
1171 RTLISTNODE listSources;
1172 uint32_t cSources = 0;
1173 RTListInit(&listSources);
1174
1175 int vrc = VINF_SUCCESS;
1176 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1177 {
1178 /* For options that require an argument, ValueUnion has received the value. */
1179 switch (ch)
1180 {
1181 case GETOPTDEF_COPYTO_DRYRUN:
1182 fDryRun = true;
1183 break;
1184
1185 case GETOPTDEF_COPYTO_FOLLOW:
1186 fFlags |= CopyFileFlag_FollowLinks;
1187 break;
1188
1189 case GETOPTDEF_COPYTO_PASSWORD:
1190 Utf8Password = ValueUnion.psz;
1191 break;
1192
1193 case 'R': /* Recursive processing */
1194 fFlags |= CopyFileFlag_Recursive;
1195 break;
1196
1197 case GETOPTDEF_COPYTO_TARGETDIR:
1198 Utf8Dest = ValueUnion.psz;
1199 break;
1200
1201 case GETOPTDEF_COPYTO_USERNAME:
1202 Utf8UserName = ValueUnion.psz;
1203 break;
1204
1205 case 'v': /* Verbose */
1206 fVerbose = true;
1207 break;
1208
1209 case VINF_GETOPT_NOT_OPTION:
1210 {
1211 /* Last argument and no destination specified with
1212 * --target-directory yet? Then use the current argument
1213 * as destination. */
1214 if ( pArg->argc == GetState.iNext
1215 && Utf8Dest.isEmpty())
1216 {
1217 Utf8Dest = ValueUnion.psz;
1218 }
1219 else
1220 {
1221 int vrc = ctrlDirectoryEntryAppend(ValueUnion.psz, /* Source */
1222 NULL, /* No destination given */
1223 &listSources);
1224 if (RT_SUCCESS(vrc))
1225 {
1226 cSources++;
1227 if (cSources == UINT32_MAX)
1228 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many sources specified! Aborting.");
1229 }
1230 else
1231 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to append source: %Rrc", vrc);
1232 }
1233 break;
1234 }
1235
1236 default:
1237 return RTGetOptPrintError(ch, &ValueUnion);
1238 }
1239 }
1240
1241 if (!cSources)
1242 return errorSyntax(USAGE_GUESTCONTROL,
1243 "No source(s) specified!");
1244
1245 if (Utf8Dest.isEmpty())
1246 return errorSyntax(USAGE_GUESTCONTROL,
1247 "No destination specified!");
1248
1249 if (Utf8UserName.isEmpty())
1250 return errorSyntax(USAGE_GUESTCONTROL,
1251 "No user name specified!");
1252
1253 /*
1254 * Done parsing arguments, do stuff.
1255 */
1256 HRESULT rc = S_OK;
1257 if (fVerbose)
1258 {
1259 if (fDryRun)
1260 RTPrintf("Dry run - no files copied!\n");
1261 RTPrintf("Gathering file information ...\n");
1262 }
1263
1264 RTLISTNODE listToCopy;
1265 RTListInit(&listToCopy);
1266 uint32_t cTotalObjects = 0;
1267
1268 PDIRECTORYENTRY pNodeSource;
1269 RTListForEach(&listSources, pNodeSource, DIRECTORYENTRY, Node)
1270 {
1271 uint32_t cObjects = 0;
1272 vrc = ctrlCopyInit(pNodeSource->pszSourcePath, Utf8Dest.c_str(), fFlags,
1273 &cObjects, &listToCopy);
1274 if (RT_FAILURE(vrc))
1275 {
1276 switch (vrc)
1277 {
1278 case VERR_NOT_FOUND:
1279 /* Not fatal, just continue to the next source entry (if available). */
1280 continue;
1281
1282 case VERR_FILE_NOT_FOUND:
1283 RTMsgError("Source path \"%s\" not found!\n", Utf8Source.c_str());
1284 break;
1285
1286 default:
1287 RTMsgError("Failed to initialize, rc=%Rrc\n", vrc);
1288 break;
1289 }
1290 }
1291 else if (fVerbose)
1292 {
1293 RTPrintf("Source \"%s\" has %ld elements to copy\n",
1294 pNodeSource->pszSourcePath, cObjects);
1295 }
1296 cTotalObjects += cObjects;
1297 }
1298
1299 if (fVerbose && cTotalObjects)
1300 RTPrintf("Total %ld elements to copy to \"%s\"\n",
1301 cTotalObjects, Utf8Dest.c_str());
1302
1303 if (cTotalObjects)
1304 {
1305 PDIRECTORYENTRY pNode;
1306 uint32_t uCurObject = 1;
1307
1308 RTListForEach(&listToCopy, pNode, DIRECTORYENTRY, Node)
1309 {
1310 if (fVerbose)
1311 RTPrintf("Copying \"%s\" to \"%s\" (%u/%u) ...\n",
1312 pNode->pszSourcePath, pNode->pszDestPath, uCurObject, cTotalObjects);
1313 /* Finally copy the desired file (if no dry run selected). */
1314 if (!fDryRun)
1315 vrc = ctrlCopyFileToGuest(guest, fVerbose, pNode->pszSourcePath, pNode->pszDestPath,
1316 Utf8UserName.c_str(), Utf8Password.c_str(), fFlags);
1317 if (RT_FAILURE(vrc))
1318 break;
1319 uCurObject++;
1320 }
1321 Assert(cTotalObjects == uCurObject - 1);
1322
1323 if (RT_SUCCESS(vrc) && fVerbose)
1324 RTPrintf("Copy operation successful!\n");
1325 }
1326
1327 ctrlDirectoryListDestroy(&listToCopy);
1328 ctrlDirectoryListDestroy(&listSources);
1329
1330 if (RT_FAILURE(vrc))
1331 rc = VBOX_E_IPRT_ERROR;
1332 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1333}
1334
1335static int handleCtrlCreateDirectory(ComPtr<IGuest> guest, HandlerArg *pArg)
1336{
1337 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1338
1339 /*
1340 * Parse arguments.
1341 *
1342 * Note! No direct returns here, everyone must go thru the cleanup at the
1343 * end of this function.
1344 */
1345 static const RTGETOPTDEF s_aOptions[] =
1346 {
1347 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
1348 { "--parents", 'P', RTGETOPT_REQ_NOTHING },
1349 { "--password", 'p', RTGETOPT_REQ_STRING },
1350 { "--username", 'u', RTGETOPT_REQ_STRING },
1351 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1352 };
1353
1354 int ch;
1355 RTGETOPTUNION ValueUnion;
1356 RTGETOPTSTATE GetState;
1357 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
1358 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1359
1360 Utf8Str Utf8UserName;
1361 Utf8Str Utf8Password;
1362 uint32_t fFlags = CreateDirectoryFlag_None;
1363 uint32_t fDirMode = 0; /* Default mode. */
1364 bool fVerbose = false;
1365
1366 RTLISTNODE listDirs;
1367 uint32_t cDirs = 0;
1368 RTListInit(&listDirs);
1369
1370 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1371 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1372 && rcExit == RTEXITCODE_SUCCESS)
1373 {
1374 /* For options that require an argument, ValueUnion has received the value. */
1375 switch (ch)
1376 {
1377 case 'm': /* Mode */
1378 fDirMode = ValueUnion.u32;
1379 break;
1380
1381 case 'P': /* Create parents */
1382 fFlags |= CreateDirectoryFlag_Parents;
1383 break;
1384
1385 case 'p': /* Password */
1386 Utf8Password = ValueUnion.psz;
1387 break;
1388
1389 case 'u': /* User name */
1390 Utf8UserName = ValueUnion.psz;
1391 break;
1392
1393 case 'v': /* Verbose */
1394 fVerbose = true;
1395 break;
1396
1397 case VINF_GETOPT_NOT_OPTION:
1398 {
1399 int vrc = ctrlDirectoryEntryAppend(NULL, /* No source given */
1400 ValueUnion.psz, /* Destination */
1401 &listDirs);
1402 if (RT_SUCCESS(vrc))
1403 {
1404 cDirs++;
1405 if (cDirs == UINT32_MAX)
1406 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many directories specified! Aborting.");
1407 }
1408 else
1409 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to append directory: %Rrc", vrc);
1410 break;
1411 }
1412
1413 default:
1414 rcExit = RTGetOptPrintError(ch, &ValueUnion);
1415 break;
1416 }
1417 }
1418
1419 if (rcExit == RTEXITCODE_SUCCESS && !cDirs)
1420 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No directory to create specified!");
1421
1422 if (rcExit == RTEXITCODE_SUCCESS && Utf8UserName.isEmpty())
1423 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
1424
1425 if (rcExit == RTEXITCODE_SUCCESS)
1426 {
1427 /*
1428 * Create the directories.
1429 */
1430 HRESULT hrc = S_OK;
1431 if (fVerbose && cDirs > 1)
1432 RTPrintf("Creating %u directories ...\n", cDirs);
1433
1434 PDIRECTORYENTRY pNode;
1435 RTListForEach(&listDirs, pNode, DIRECTORYENTRY, Node)
1436 {
1437 if (fVerbose)
1438 RTPrintf("Creating directory \"%s\" ...\n", pNode->pszDestPath);
1439
1440 ComPtr<IProgress> progress;
1441 hrc = guest->CreateDirectory(Bstr(pNode->pszDestPath).raw(),
1442 Bstr(Utf8UserName).raw(), Bstr(Utf8Password).raw(),
1443 fDirMode, fFlags, progress.asOutParam());
1444 if (FAILED(hrc))
1445 {
1446 ctrlPrintError(guest, COM_IIDOF(IGuest)); /* (return code ignored, save original rc) */
1447 break;
1448 }
1449 }
1450 if (FAILED(hrc))
1451 rcExit = RTEXITCODE_FAILURE;
1452 }
1453
1454 /*
1455 * Clean up and return.
1456 */
1457 ctrlDirectoryListDestroy(&listDirs);
1458
1459 return rcExit;
1460}
1461
1462static int handleCtrlUpdateAdditions(ComPtr<IGuest> guest, HandlerArg *pArg)
1463{
1464 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1465
1466 /*
1467 * Check the syntax. We can deduce the correct syntax from the number of
1468 * arguments.
1469 */
1470 Utf8Str Utf8Source;
1471 bool fVerbose = false;
1472
1473 static const RTGETOPTDEF s_aOptions[] =
1474 {
1475 { "--source", 's', RTGETOPT_REQ_STRING },
1476 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1477 };
1478
1479 int ch;
1480 RTGETOPTUNION ValueUnion;
1481 RTGETOPTSTATE GetState;
1482 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1483
1484 int vrc = VINF_SUCCESS;
1485 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1486 && RT_SUCCESS(vrc))
1487 {
1488 switch (ch)
1489 {
1490 case 's':
1491 Utf8Source = ValueUnion.psz;
1492 break;
1493
1494 case 'v':
1495 fVerbose = true;
1496 break;
1497
1498 default:
1499 return RTGetOptPrintError(ch, &ValueUnion);
1500 }
1501 }
1502
1503 if (fVerbose)
1504 RTPrintf("Updating Guest Additions ...\n");
1505
1506#ifdef DEBUG_andy
1507 if (Utf8Source.isEmpty())
1508 Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
1509#endif
1510
1511 /* Determine source if not set yet. */
1512 if (Utf8Source.isEmpty())
1513 {
1514 char strTemp[RTPATH_MAX];
1515 vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
1516 AssertRC(vrc);
1517 Utf8Str Utf8Src1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
1518
1519 vrc = RTPathExecDir(strTemp, sizeof(strTemp));
1520 AssertRC(vrc);
1521 Utf8Str Utf8Src2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
1522
1523 /* Check the standard image locations */
1524 if (RTFileExists(Utf8Src1.c_str()))
1525 Utf8Source = Utf8Src1;
1526 else if (RTFileExists(Utf8Src2.c_str()))
1527 Utf8Source = Utf8Src2;
1528 else
1529 {
1530 RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
1531 vrc = VERR_FILE_NOT_FOUND;
1532 }
1533 }
1534 else if (!RTFileExists(Utf8Source.c_str()))
1535 {
1536 RTMsgError("Source \"%s\" does not exist!\n", Utf8Source.c_str());
1537 vrc = VERR_FILE_NOT_FOUND;
1538 }
1539
1540 if (RT_SUCCESS(vrc))
1541 {
1542 if (fVerbose)
1543 RTPrintf("Using source: %s\n", Utf8Source.c_str());
1544
1545 HRESULT rc = S_OK;
1546 ComPtr<IProgress> progress;
1547 CHECK_ERROR(guest, UpdateGuestAdditions(Bstr(Utf8Source).raw(),
1548 /* Wait for whole update process to complete. */
1549 AdditionsUpdateFlag_None,
1550 progress.asOutParam()));
1551 if (FAILED(rc))
1552 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
1553 else
1554 {
1555 rc = showProgress(progress);
1556 if (FAILED(rc))
1557 vrc = ctrlPrintProgressError(progress);
1558 else if (fVerbose)
1559 RTPrintf("Guest Additions update successful.\n");
1560 }
1561 }
1562
1563 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1564}
1565
1566/**
1567 * Access the guest control store.
1568 *
1569 * @returns program exit code.
1570 * @note see the command line API description for parameters
1571 */
1572int handleGuestControl(HandlerArg *pArg)
1573{
1574 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1575
1576 HandlerArg arg = *pArg;
1577 arg.argc = pArg->argc - 2; /* Skip VM name and sub command. */
1578 arg.argv = pArg->argv + 2; /* Same here. */
1579
1580 ComPtr<IGuest> guest;
1581 int vrc = ctrlInitVM(pArg, pArg->argv[0] /* VM Name */, &guest);
1582 if (RT_SUCCESS(vrc))
1583 {
1584 int rcExit;
1585 if ( !strcmp(pArg->argv[1], "exec")
1586 || !strcmp(pArg->argv[1], "execute"))
1587 {
1588 rcExit = handleCtrlExecProgram(guest, &arg);
1589 }
1590 else if ( !strcmp(pArg->argv[1], "copyto")
1591 || !strcmp(pArg->argv[1], "cp"))
1592 {
1593 rcExit = handleCtrlCopyTo(guest, &arg);
1594 }
1595 else if ( !strcmp(pArg->argv[1], "createdirectory")
1596 || !strcmp(pArg->argv[1], "createdir")
1597 || !strcmp(pArg->argv[1], "mkdir")
1598 || !strcmp(pArg->argv[1], "md"))
1599 {
1600 rcExit = handleCtrlCreateDirectory(guest, &arg);
1601 }
1602 else if ( !strcmp(pArg->argv[1], "updateadditions")
1603 || !strcmp(pArg->argv[1], "updateadds"))
1604 {
1605 rcExit = handleCtrlUpdateAdditions(guest, &arg);
1606 }
1607 else
1608 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
1609
1610 ctrlUninitVM(pArg);
1611 return rcExit;
1612 }
1613 return RTEXITCODE_FAILURE;
1614}
1615
1616#endif /* !VBOX_ONLY_DOCS */
1617
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