VirtualBox

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

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

GuestCtrl: Added support for getting separated stdout/stderr output.

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