VirtualBox

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

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

Build fix.

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