VirtualBox

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

Last change on this file since 44885 was 44885, checked in by vboxsync, 12 years ago

VBoxManageGuestCtrl: Print plain PID on non-verbose runs to support script handling.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 94.0 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 44885 2013-03-01 11:21:06Z vboxsync $ */
2/** @file
3 * VBoxManage - Implementation of guestcontrol command.
4 */
5
6/*
7 * Copyright (C) 2010-2012 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#include <map>
47#include <vector>
48
49#ifdef USE_XPCOM_QUEUE
50# include <sys/select.h>
51# include <errno.h>
52#endif
53
54#include <signal.h>
55
56#ifdef RT_OS_DARWIN
57# include <CoreFoundation/CFRunLoop.h>
58#endif
59
60using namespace com;
61
62/**
63 * IVirtualBoxCallback implementation for handling the GuestControlCallback in
64 * relation to the "guestcontrol * wait" command.
65 */
66/** @todo */
67
68/** Set by the signal handler. */
69static volatile bool g_fGuestCtrlCanceled = false;
70
71typedef struct COPYCONTEXT
72{
73 COPYCONTEXT() : fVerbose(false), fDryRun(false), fHostToGuest(false)
74 {
75 }
76
77 ComPtr<IGuestSession> pGuestSession;
78 bool fVerbose;
79 bool fDryRun;
80 bool fHostToGuest;
81} COPYCONTEXT, *PCOPYCONTEXT;
82
83/**
84 * An entry for a source element, including an optional DOS-like wildcard (*,?).
85 */
86class SOURCEFILEENTRY
87{
88 public:
89
90 SOURCEFILEENTRY(const char *pszSource, const char *pszFilter)
91 : mSource(pszSource),
92 mFilter(pszFilter) {}
93
94 SOURCEFILEENTRY(const char *pszSource)
95 : mSource(pszSource)
96 {
97 Parse(pszSource);
98 }
99
100 const char* GetSource() const
101 {
102 return mSource.c_str();
103 }
104
105 const char* GetFilter() const
106 {
107 return mFilter.c_str();
108 }
109
110 private:
111
112 int Parse(const char *pszPath)
113 {
114 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
115
116 if ( !RTFileExists(pszPath)
117 && !RTDirExists(pszPath))
118 {
119 /* No file and no directory -- maybe a filter? */
120 char *pszFilename = RTPathFilename(pszPath);
121 if ( pszFilename
122 && strpbrk(pszFilename, "*?"))
123 {
124 /* Yep, get the actual filter part. */
125 mFilter = RTPathFilename(pszPath);
126 /* Remove the filter from actual sourcec directory name. */
127 RTPathStripFilename(mSource.mutableRaw());
128 mSource.jolt();
129 }
130 }
131
132 return VINF_SUCCESS; /* @todo */
133 }
134
135 private:
136
137 Utf8Str mSource;
138 Utf8Str mFilter;
139};
140typedef std::vector<SOURCEFILEENTRY> SOURCEVEC, *PSOURCEVEC;
141
142/**
143 * An entry for an element which needs to be copied/created to/on the guest.
144 */
145typedef struct DESTFILEENTRY
146{
147 DESTFILEENTRY(Utf8Str strFileName) : mFileName(strFileName) {}
148 Utf8Str mFileName;
149} DESTFILEENTRY, *PDESTFILEENTRY;
150/*
151 * Map for holding destination entires, whereas the key is the destination
152 * directory and the mapped value is a vector holding all elements for this directoy.
153 */
154typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> > DESTDIRMAP, *PDESTDIRMAP;
155typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> >::iterator DESTDIRMAPITER, *PDESTDIRMAPITER;
156
157/**
158 * Special exit codes for returning errors/information of a
159 * started guest process to the command line VBoxManage was started from.
160 * Useful for e.g. scripting.
161 *
162 * @note These are frozen as of 4.1.0.
163 */
164enum EXITCODEEXEC
165{
166 EXITCODEEXEC_SUCCESS = RTEXITCODE_SUCCESS,
167 /* Process exited normally but with an exit code <> 0. */
168 EXITCODEEXEC_CODE = 16,
169 EXITCODEEXEC_FAILED = 17,
170 EXITCODEEXEC_TERM_SIGNAL = 18,
171 EXITCODEEXEC_TERM_ABEND = 19,
172 EXITCODEEXEC_TIMEOUT = 20,
173 EXITCODEEXEC_DOWN = 21,
174 EXITCODEEXEC_CANCELED = 22
175};
176
177/**
178 * RTGetOpt-IDs for the guest execution control command line.
179 */
180enum GETOPTDEF_EXEC
181{
182 GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000,
183 GETOPTDEF_EXEC_NO_PROFILE,
184 GETOPTDEF_EXEC_OUTPUTFORMAT,
185 GETOPTDEF_EXEC_DOS2UNIX,
186 GETOPTDEF_EXEC_UNIX2DOS,
187 GETOPTDEF_EXEC_PASSWORD,
188 GETOPTDEF_EXEC_WAITFOREXIT,
189 GETOPTDEF_EXEC_WAITFORSTDOUT,
190 GETOPTDEF_EXEC_WAITFORSTDERR
191};
192
193enum GETOPTDEF_COPY
194{
195 GETOPTDEF_COPY_DRYRUN = 1000,
196 GETOPTDEF_COPY_FOLLOW,
197 GETOPTDEF_COPY_PASSWORD,
198 GETOPTDEF_COPY_TARGETDIR
199};
200
201enum GETOPTDEF_MKDIR
202{
203 GETOPTDEF_MKDIR_PASSWORD = 1000
204};
205
206enum GETOPTDEF_STAT
207{
208 GETOPTDEF_STAT_PASSWORD = 1000
209};
210
211enum OUTPUTTYPE
212{
213 OUTPUTTYPE_UNDEFINED = 0,
214 OUTPUTTYPE_DOS2UNIX = 10,
215 OUTPUTTYPE_UNIX2DOS = 20
216};
217
218static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest, const char *pszDir, bool *fExists);
219
220#endif /* VBOX_ONLY_DOCS */
221
222void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2)
223{
224 RTStrmPrintf(pStrm,
225 "%s guestcontrol %s <vmname>|<uuid>\n"
226 " exec[ute]\n"
227 " --image <path to program> --username <name>\n"
228 " [--passwordfile <file> | --password <password>]\n"
229 " [--domain <domain>] [--verbose] [--timeout <msec>]\n"
230 " [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
231 " [--wait-exit] [--wait-stdout] [--wait-stderr]\n"
232 " [--dos2unix] [--unix2dos]\n"
233 " [-- [<argument1>] ... [<argumentN>]]\n"
234 /** @todo Add a "--" parameter (has to be last parameter) to directly execute
235 * stuff, e.g. "VBoxManage guestcontrol execute <VMName> --username <> ... -- /bin/rm -Rf /foo". */
236 "\n"
237 " copyfrom\n"
238 " <guest source> <host dest> --username <name>\n"
239 " [--passwordfile <file> | --password <password>]\n"
240 " [--domain <domain>] [--verbose]\n"
241 " [--dryrun] [--follow] [--recursive]\n"
242 "\n"
243 " copyto|cp\n"
244 " <host source> <guest dest> --username <name>\n"
245 " [--passwordfile <file> | --password <password>]\n"
246 " [--domain <domain>] [--verbose]\n"
247 " [--dryrun] [--follow] [--recursive]\n"
248 "\n"
249 " createdir[ectory]|mkdir|md\n"
250 " <guest directory>... --username <name>\n"
251 " [--passwordfile <file> | --password <password>]\n"
252 " [--domain <domain>] [--verbose]\n"
253 " [--parents] [--mode <mode>]\n"
254 "\n"
255 " stat\n"
256 " <file>... --username <name>\n"
257 " [--passwordfile <file> | --password <password>]\n"
258 " [--domain <domain>] [--verbose]\n"
259 "\n"
260 " updateadditions\n"
261 " [--source <guest additions .ISO>] [--verbose]\n"
262 " [--wait-start]\n"
263 "\n", pcszSep1, pcszSep2);
264}
265
266#ifndef VBOX_ONLY_DOCS
267
268/**
269 * Signal handler that sets g_fGuestCtrlCanceled.
270 *
271 * This can be executed on any thread in the process, on Windows it may even be
272 * a thread dedicated to delivering this signal. Do not doing anything
273 * unnecessary here.
274 */
275static void guestCtrlSignalHandler(int iSignal)
276{
277 NOREF(iSignal);
278 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
279}
280
281/**
282 * Installs a custom signal handler to get notified
283 * whenever the user wants to intercept the program.
284 */
285static void ctrlSignalHandlerInstall()
286{
287 signal(SIGINT, guestCtrlSignalHandler);
288#ifdef SIGBREAK
289 signal(SIGBREAK, guestCtrlSignalHandler);
290#endif
291}
292
293/**
294 * Uninstalls a previously installed signal handler.
295 */
296static void ctrlSignalHandlerUninstall()
297{
298 signal(SIGINT, SIG_DFL);
299#ifdef SIGBREAK
300 signal(SIGBREAK, SIG_DFL);
301#endif
302}
303
304/**
305 * Translates a process status to a human readable
306 * string.
307 */
308static const char *ctrlExecProcessStatusToText(ProcessStatus_T enmStatus)
309{
310 switch (enmStatus)
311 {
312 case ProcessStatus_Starting:
313 return "starting";
314 case ProcessStatus_Started:
315 return "started";
316 case ProcessStatus_Paused:
317 return "paused";
318 case ProcessStatus_Terminating:
319 return "terminating";
320 case ProcessStatus_TerminatedNormally:
321 return "successfully terminated";
322 case ProcessStatus_TerminatedSignal:
323 return "terminated by signal";
324 case ProcessStatus_TerminatedAbnormally:
325 return "abnormally aborted";
326 case ProcessStatus_TimedOutKilled:
327 return "timed out";
328 case ProcessStatus_TimedOutAbnormally:
329 return "timed out, hanging";
330 case ProcessStatus_Down:
331 return "killed";
332 case ProcessStatus_Error:
333 return "error";
334 default:
335 break;
336 }
337 return "unknown";
338}
339
340static int ctrlExecProcessStatusToExitCode(ProcessStatus_T enmStatus, ULONG uExitCode)
341{
342 int vrc = EXITCODEEXEC_SUCCESS;
343 switch (enmStatus)
344 {
345 case ProcessStatus_Starting:
346 vrc = EXITCODEEXEC_SUCCESS;
347 break;
348 case ProcessStatus_Started:
349 vrc = EXITCODEEXEC_SUCCESS;
350 break;
351 case ProcessStatus_Paused:
352 vrc = EXITCODEEXEC_SUCCESS;
353 break;
354 case ProcessStatus_Terminating:
355 vrc = EXITCODEEXEC_SUCCESS;
356 break;
357 case ProcessStatus_TerminatedNormally:
358 vrc = !uExitCode ? EXITCODEEXEC_SUCCESS : EXITCODEEXEC_CODE;
359 break;
360 case ProcessStatus_TerminatedSignal:
361 vrc = EXITCODEEXEC_TERM_SIGNAL;
362 break;
363 case ProcessStatus_TerminatedAbnormally:
364 vrc = EXITCODEEXEC_TERM_ABEND;
365 break;
366 case ProcessStatus_TimedOutKilled:
367 vrc = EXITCODEEXEC_TIMEOUT;
368 break;
369 case ProcessStatus_TimedOutAbnormally:
370 vrc = EXITCODEEXEC_TIMEOUT;
371 break;
372 case ProcessStatus_Down:
373 /* Service/OS is stopping, process was killed, so
374 * not exactly an error of the started process ... */
375 vrc = EXITCODEEXEC_DOWN;
376 break;
377 case ProcessStatus_Error:
378 vrc = EXITCODEEXEC_FAILED;
379 break;
380 default:
381 AssertMsgFailed(("Unknown exit code (%u) from guest process returned!\n", enmStatus));
382 break;
383 }
384 return vrc;
385}
386
387static int ctrlPrintError(com::ErrorInfo &errorInfo)
388{
389 if ( errorInfo.isFullAvailable()
390 || errorInfo.isBasicAvailable())
391 {
392 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
393 * because it contains more accurate info about what went wrong. */
394 if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
395 RTMsgError("%ls.", errorInfo.getText().raw());
396 else
397 {
398 RTMsgError("Error details:");
399 GluePrintErrorInfo(errorInfo);
400 }
401 return VERR_GENERAL_FAILURE; /** @todo */
402 }
403 AssertMsgFailedReturn(("Object has indicated no error (%Rhrc)!?\n", errorInfo.getResultCode()),
404 VERR_INVALID_PARAMETER);
405}
406
407static int ctrlPrintError(IUnknown *pObj, const GUID &aIID)
408{
409 com::ErrorInfo ErrInfo(pObj, aIID);
410 return ctrlPrintError(ErrInfo);
411}
412
413static int ctrlPrintProgressError(ComPtr<IProgress> pProgress)
414{
415 int vrc = VINF_SUCCESS;
416 HRESULT rc;
417
418 do
419 {
420 BOOL fCanceled;
421 CHECK_ERROR_BREAK(pProgress, COMGETTER(Canceled)(&fCanceled));
422 if (!fCanceled)
423 {
424 LONG rcProc;
425 CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&rcProc));
426 if (FAILED(rcProc))
427 {
428 com::ProgressErrorInfo ErrInfo(pProgress);
429 vrc = ctrlPrintError(ErrInfo);
430 }
431 }
432
433 } while(0);
434
435 AssertMsgStmt(SUCCEEDED(rc), ("Could not lookup progress information\n"), vrc = VERR_COM_UNEXPECTED);
436
437 return vrc;
438}
439
440/**
441 * Un-initializes the VM after guest control usage.
442 */
443static void ctrlUninitVM(HandlerArg *pArg)
444{
445 AssertPtrReturnVoid(pArg);
446 if (pArg->session)
447 pArg->session->UnlockMachine();
448}
449
450/**
451 * Initializes the VM for IGuest operations.
452 *
453 * That is, checks whether it's up and running, if it can be locked (shared
454 * only) and returns a valid IGuest pointer on success.
455 *
456 * @return IPRT status code.
457 * @param pArg Our command line argument structure.
458 * @param pszNameOrId The VM's name or UUID.
459 * @param pGuest Where to return the IGuest interface pointer.
460 */
461static int ctrlInitVM(HandlerArg *pArg, const char *pszNameOrId, ComPtr<IGuest> *pGuest)
462{
463 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
464 AssertPtrReturn(pszNameOrId, VERR_INVALID_PARAMETER);
465
466 /* Lookup VM. */
467 ComPtr<IMachine> machine;
468 /* Assume it's an UUID. */
469 HRESULT rc;
470 CHECK_ERROR(pArg->virtualBox, FindMachine(Bstr(pszNameOrId).raw(),
471 machine.asOutParam()));
472 if (FAILED(rc))
473 return VERR_NOT_FOUND;
474
475 /* Machine is running? */
476 MachineState_T machineState;
477 CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
478 if (machineState != MachineState_Running)
479 {
480 RTMsgError("Machine \"%s\" is not running (currently %s)!\n",
481 pszNameOrId, machineStateToName(machineState, false));
482 return VERR_VM_INVALID_VM_STATE;
483 }
484
485 do
486 {
487 /* Open a session for the VM. */
488 CHECK_ERROR_BREAK(machine, LockMachine(pArg->session, LockType_Shared));
489 /* Get the associated console. */
490 ComPtr<IConsole> console;
491 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Console)(console.asOutParam()));
492 /* ... and session machine. */
493 ComPtr<IMachine> sessionMachine;
494 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
495 /* Get IGuest interface. */
496 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pGuest->asOutParam()));
497 } while (0);
498
499 if (FAILED(rc))
500 ctrlUninitVM(pArg);
501 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
502}
503
504/**
505 * Prints the desired guest output to a stream.
506 *
507 * @return IPRT status code.
508 * @param pProcess Pointer to appropriate process object.
509 * @param pStrmOutput Where to write the data.
510 * @param uHandle Handle where to read the data from.
511 * @param uTimeoutMS Timeout (in ms) to wait for the operation to complete.
512 */
513static int ctrlExecPrintOutput(IProcess *pProcess, PRTSTREAM pStrmOutput,
514 ULONG uHandle, ULONG uTimeoutMS)
515{
516 AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
517 AssertPtrReturn(pStrmOutput, VERR_INVALID_POINTER);
518
519 int vrc = VINF_SUCCESS;
520
521 SafeArray<BYTE> aOutputData;
522 HRESULT rc = pProcess->Read(uHandle, _64K, uTimeoutMS,
523 ComSafeArrayAsOutParam(aOutputData));
524 if (FAILED(rc))
525 vrc = ctrlPrintError(pProcess, COM_IIDOF(IProcess));
526 else
527 {
528 /** @todo implement the dos2unix/unix2dos conversions */
529 vrc = RTStrmWrite(pStrmOutput, aOutputData.raw(), aOutputData.size());
530 if (RT_FAILURE(vrc))
531 RTMsgError("Unable to write output, rc=%Rrc\n", vrc);
532 }
533
534 return vrc;
535}
536
537/**
538 * Returns the remaining time (in ms) based on the start time and a set
539 * timeout value. Returns RT_INDEFINITE_WAIT if no timeout was specified.
540 *
541 * @return RTMSINTERVAL Time left (in ms).
542 * @param u64StartMs Start time (in ms).
543 * @param cMsTimeout Timeout value (in ms).
544 */
545inline RTMSINTERVAL ctrlExecGetRemainingTime(uint64_t u64StartMs, RTMSINTERVAL cMsTimeout)
546{
547 if (!cMsTimeout || cMsTimeout == RT_INDEFINITE_WAIT) /* If no timeout specified, wait forever. */
548 return RT_INDEFINITE_WAIT;
549
550 uint32_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
551 if (u64ElapsedMs >= cMsTimeout)
552 return 0;
553
554 return cMsTimeout - u64ElapsedMs;
555}
556
557/* <Missing documentation> */
558static int handleCtrlExecProgram(ComPtr<IGuest> pGuest, HandlerArg *pArg)
559{
560 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
561
562 /*
563 * Parse arguments.
564 */
565 static const RTGETOPTDEF s_aOptions[] =
566 {
567 { "--dos2unix", GETOPTDEF_EXEC_DOS2UNIX, RTGETOPT_REQ_NOTHING },
568 { "--environment", 'e', RTGETOPT_REQ_STRING },
569 { "--flags", 'f', RTGETOPT_REQ_STRING },
570 { "--ignore-operhaned-processes", GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES, RTGETOPT_REQ_NOTHING },
571 { "--image", 'i', RTGETOPT_REQ_STRING },
572 { "--no-profile", GETOPTDEF_EXEC_NO_PROFILE, RTGETOPT_REQ_NOTHING },
573 { "--username", 'u', RTGETOPT_REQ_STRING },
574 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
575 { "--password", GETOPTDEF_EXEC_PASSWORD, RTGETOPT_REQ_STRING },
576 { "--domain", 'd', RTGETOPT_REQ_STRING },
577 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
578 { "--unix2dos", GETOPTDEF_EXEC_UNIX2DOS, RTGETOPT_REQ_NOTHING },
579 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
580 { "--wait-exit", GETOPTDEF_EXEC_WAITFOREXIT, RTGETOPT_REQ_NOTHING },
581 { "--wait-stdout", GETOPTDEF_EXEC_WAITFORSTDOUT, RTGETOPT_REQ_NOTHING },
582 { "--wait-stderr", GETOPTDEF_EXEC_WAITFORSTDERR, RTGETOPT_REQ_NOTHING }
583 };
584
585 int ch;
586 RTGETOPTUNION ValueUnion;
587 RTGETOPTSTATE GetState;
588 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
589
590 Utf8Str strCmd;
591 com::SafeArray<ProcessCreateFlag_T> aCreateFlags;
592 com::SafeArray<ProcessWaitForFlag_T> aWaitFlags;
593 com::SafeArray<IN_BSTR> args;
594 com::SafeArray<IN_BSTR> env;
595 Utf8Str strUsername;
596 Utf8Str strPassword;
597 Utf8Str strDomain;
598 RTMSINTERVAL cMsTimeout = 0;
599 OUTPUTTYPE eOutputType = OUTPUTTYPE_UNDEFINED;
600 bool fWaitForExit = false;
601 bool fVerbose = false;
602 int vrc = VINF_SUCCESS;
603
604 /* Wait for process start in any case. This is useful for scripting VBoxManage
605 * when relying on its overall exit code. */
606 aWaitFlags.push_back(ProcessWaitForFlag_Start);
607
608 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
609 && RT_SUCCESS(vrc))
610 {
611 /* For options that require an argument, ValueUnion has received the value. */
612 switch (ch)
613 {
614 case GETOPTDEF_EXEC_DOS2UNIX:
615 if (eOutputType != OUTPUTTYPE_UNDEFINED)
616 return errorSyntax(USAGE_GUESTCONTROL, "More than one output type (dos2unix/unix2dos) specified!");
617 eOutputType = OUTPUTTYPE_DOS2UNIX;
618 break;
619
620 case 'e': /* Environment */
621 {
622 char **papszArg;
623 int cArgs;
624
625 vrc = RTGetOptArgvFromString(&papszArg, &cArgs, ValueUnion.psz, NULL);
626 if (RT_FAILURE(vrc))
627 return errorSyntax(USAGE_GUESTCONTROL, "Failed to parse environment value, rc=%Rrc", vrc);
628 for (int j = 0; j < cArgs; j++)
629 env.push_back(Bstr(papszArg[j]).raw());
630
631 RTGetOptArgvFree(papszArg);
632 break;
633 }
634
635 case GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES:
636 aCreateFlags.push_back(ProcessCreateFlag_IgnoreOrphanedProcesses);
637 break;
638
639 case GETOPTDEF_EXEC_NO_PROFILE:
640 aCreateFlags.push_back(ProcessCreateFlag_NoProfile);
641 break;
642
643 case 'i':
644 strCmd = ValueUnion.psz;
645 break;
646
647 /** @todo Add a hidden flag. */
648
649 case 'u': /* User name */
650 strUsername = ValueUnion.psz;
651 break;
652
653 case GETOPTDEF_EXEC_PASSWORD: /* Password */
654 strPassword = ValueUnion.psz;
655 break;
656
657 case 'p': /* Password file */
658 {
659 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
660 if (rcExit != RTEXITCODE_SUCCESS)
661 return rcExit;
662 break;
663 }
664
665 case 'd': /* domain */
666 strDomain = ValueUnion.psz;
667 break;
668
669 case 't': /* Timeout */
670 cMsTimeout = ValueUnion.u32;
671 break;
672
673 case GETOPTDEF_EXEC_UNIX2DOS:
674 if (eOutputType != OUTPUTTYPE_UNDEFINED)
675 return errorSyntax(USAGE_GUESTCONTROL, "More than one output type (dos2unix/unix2dos) specified!");
676 eOutputType = OUTPUTTYPE_UNIX2DOS;
677 break;
678
679 case 'v': /* Verbose */
680 fVerbose = true;
681 break;
682
683 case GETOPTDEF_EXEC_WAITFOREXIT:
684 aWaitFlags.push_back(ProcessWaitForFlag_Terminate);
685 fWaitForExit = true;
686 break;
687
688 case GETOPTDEF_EXEC_WAITFORSTDOUT:
689 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut);
690 aWaitFlags.push_back(ProcessWaitForFlag_StdOut);
691 fWaitForExit = true;
692 break;
693
694 case GETOPTDEF_EXEC_WAITFORSTDERR:
695 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr);
696 aWaitFlags.push_back(ProcessWaitForFlag_StdErr);
697 fWaitForExit = true;
698 break;
699
700 case VINF_GETOPT_NOT_OPTION:
701 {
702 if (args.size() == 0 && strCmd.isEmpty())
703 strCmd = ValueUnion.psz;
704 else
705 args.push_back(Bstr(ValueUnion.psz).raw());
706 break;
707 }
708
709 default:
710 return RTGetOptPrintError(ch, &ValueUnion);
711 }
712 }
713
714 if (strCmd.isEmpty())
715 return errorSyntax(USAGE_GUESTCONTROL, "No command to execute specified!");
716
717 if (strUsername.isEmpty())
718 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
719
720 /* Any output conversion not supported yet! */
721 if (eOutputType != OUTPUTTYPE_UNDEFINED)
722 return errorSyntax(USAGE_GUESTCONTROL, "Output conversion not implemented yet!");
723
724 /*
725 * Start with the real work.
726 */
727 HRESULT rc = S_OK;
728 if (fVerbose)
729 {
730 if (cMsTimeout == 0)
731 RTPrintf("Waiting for guest to start process ...\n");
732 else
733 RTPrintf("Waiting for guest to start process (within %ums)\n", cMsTimeout);
734 }
735
736 /* Adjust process creation flags if we don't want to wait for process termination. */
737 if (!fWaitForExit)
738 aCreateFlags.push_back(ProcessCreateFlag_WaitForProcessStartOnly);
739
740 /** @todo This eventually needs a bit of revamping so that a valid session gets passed
741 * into this function already so that we don't need to mess around with closing
742 * the session all over the places below again. Later. */
743
744 ComPtr<IGuestSession> pGuestSession;
745 rc = pGuest->CreateSession(Bstr(strUsername).raw(),
746 Bstr(strPassword).raw(),
747 Bstr(strDomain).raw(),
748 Bstr("VBoxManage Guest Control Exec").raw(),
749 pGuestSession.asOutParam());
750 if (FAILED(rc))
751 {
752 ctrlPrintError(pGuest, COM_IIDOF(IGuest));
753 return RTEXITCODE_FAILURE;
754 }
755
756 /* Get current time stamp to later calculate rest of timeout left. */
757 uint64_t u64StartMS = RTTimeMilliTS();
758
759 /*
760 * Execute the process.
761 */
762 ComPtr<IGuestProcess> pProcess;
763 rc = pGuestSession->ProcessCreate(Bstr(strCmd).raw(),
764 ComSafeArrayAsInParam(args),
765 ComSafeArrayAsInParam(env),
766 ComSafeArrayAsInParam(aCreateFlags),
767 cMsTimeout,
768 pProcess.asOutParam());
769 if (FAILED(rc))
770 {
771 ctrlPrintError(pGuestSession, COM_IIDOF(IGuestSession));
772
773 pGuestSession->Close();
774 return RTEXITCODE_FAILURE;
775 }
776
777 /** @todo does this need signal handling? there's no progress object etc etc */
778
779 vrc = RTStrmSetMode(g_pStdOut, 1 /* Binary mode */, -1 /* Code set, unchanged */);
780 if (RT_FAILURE(vrc))
781 RTMsgError("Unable to set stdout's binary mode, rc=%Rrc\n", vrc);
782 vrc = RTStrmSetMode(g_pStdErr, 1 /* Binary mode */, -1 /* Code set, unchanged */);
783 if (RT_FAILURE(vrc))
784 RTMsgError("Unable to set stderr's binary mode, rc=%Rrc\n", vrc);
785
786 /* Wait for process to exit ... */
787 RTMSINTERVAL cMsTimeLeft = 1;
788 bool fReadStdOut, fReadStdErr;
789 fReadStdOut = fReadStdErr = false;
790 bool fCompleted = false;
791 while (!fCompleted && cMsTimeLeft != 0)
792 {
793 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
794 ProcessWaitResult_T waitResult;
795 rc = pProcess->WaitForArray(ComSafeArrayAsInParam(aWaitFlags), cMsTimeLeft, &waitResult);
796 if (FAILED(rc))
797 {
798 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
799
800 pGuestSession->Close();
801 return RTEXITCODE_FAILURE;
802 }
803
804 switch (waitResult)
805 {
806 case ProcessWaitResult_Start:
807 {
808 ULONG uPID = 0;
809 rc = pProcess->COMGETTER(PID)(&uPID);
810 if (FAILED(rc))
811 {
812 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
813
814 pGuestSession->Close();
815 return RTEXITCODE_FAILURE;
816 }
817
818 if (fVerbose)
819 {
820 RTPrintf("Process '%s' (PID: %ul) %s\n",
821 strCmd.c_str(), uPID,
822 fWaitForExit ? "started" : "started (detached)");
823 }
824 else
825 {
826 /* Just print plain PID to make it easier for scripts
827 * invoking VBoxManage. */
828 RTPrintf("%ul\n", uPID);
829 }
830
831 /* We're done here if we don't want to wait for termination. */
832 if (!fWaitForExit)
833 fCompleted = true;
834
835 break;
836 }
837 case ProcessWaitResult_StdOut:
838 fReadStdOut = true;
839 break;
840 case ProcessWaitResult_StdErr:
841 fReadStdErr = true;
842 break;
843 case ProcessWaitResult_Terminate:
844 /* Process terminated, we're done */
845 fCompleted = true;
846 break;
847 case ProcessWaitResult_WaitFlagNotSupported:
848 {
849 /* The guest does not support waiting for stdout/err, so
850 * yield to reduce the CPU load due to busy waiting. */
851 RTThreadYield(); /* Optional, don't check rc. */
852
853 /* Try both, stdout + stderr. */
854 fReadStdOut = fReadStdErr = true;
855 break;
856 }
857 default:
858 /* Ignore all other results, let the timeout expire */
859 break;
860 }
861
862 if (fReadStdOut) /* Do we need to fetch stdout data? */
863 {
864 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
865 vrc = ctrlExecPrintOutput(pProcess, g_pStdOut,
866 1 /* StdOut */, cMsTimeLeft);
867 fReadStdOut = false;
868 }
869
870 if (fReadStdErr) /* Do we need to fetch stdout data? */
871 {
872 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
873 vrc = ctrlExecPrintOutput(pProcess, g_pStdErr,
874 2 /* StdErr */, cMsTimeLeft);
875 fReadStdErr = false;
876 }
877
878 if (RT_FAILURE(vrc))
879 break;
880
881 } /* while */
882
883 /* Report status back to the user. */
884 if (fCompleted)
885 {
886 ProcessStatus_T status;
887 rc = pProcess->COMGETTER(Status)(&status);
888 if (FAILED(rc))
889 {
890 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
891
892 pGuestSession->Close();
893 return RTEXITCODE_FAILURE;
894 }
895 LONG exitCode;
896 rc = pProcess->COMGETTER(ExitCode)(&exitCode);
897 if (FAILED(rc))
898 {
899 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
900
901 pGuestSession->Close();
902 return RTEXITCODE_FAILURE;
903 }
904 if (fVerbose)
905 RTPrintf("Exit code=%u (Status=%u [%s])\n", exitCode, status, ctrlExecProcessStatusToText(status));
906
907 pGuestSession->Close();
908 return ctrlExecProcessStatusToExitCode(status, exitCode);
909 }
910 else
911 {
912 if (fVerbose)
913 RTPrintf("Process execution aborted!\n");
914
915 pGuestSession->Close();
916 return EXITCODEEXEC_TERM_ABEND;
917 }
918
919 pGuestSession->Close();
920
921 return RT_FAILURE(vrc) || FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
922}
923
924/**
925 * Creates a copy context structure which then can be used with various
926 * guest control copy functions. Needs to be free'd with ctrlCopyContextFree().
927 *
928 * @return IPRT status code.
929 * @param pGuest Pointer to IGuest interface to use.
930 * @param fVerbose Flag indicating if we want to run in verbose mode.
931 * @param fDryRun Flag indicating if we want to run a dry run only.
932 * @param fHostToGuest Flag indicating if we want to copy from host to guest
933 * or vice versa.
934 * @param strUsername Username of account to use on the guest side.
935 * @param strPassword Password of account to use.
936 * @param strDomain Domain of account to use.
937 * @param strSessionName Session name (only for identification purposes).
938 * @param ppContext Pointer which receives the allocated copy context.
939 */
940static int ctrlCopyContextCreate(IGuest *pGuest, bool fVerbose, bool fDryRun,
941 bool fHostToGuest, const Utf8Str &strUsername,
942 const Utf8Str &strPassword, const Utf8Str &strDomain,
943 const Utf8Str &strSessionName,
944 PCOPYCONTEXT *ppContext)
945{
946 AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
947
948 PCOPYCONTEXT pContext = new COPYCONTEXT();
949 AssertPtrReturn(pContext, VERR_NO_MEMORY); /**< @todo r=klaus cannot happen with new */
950 ComPtr<IGuestSession> pGuestSession;
951 HRESULT rc = pGuest->CreateSession(Bstr(strUsername).raw(),
952 Bstr(strPassword).raw(),
953 Bstr(strDomain).raw(),
954 Bstr(strSessionName).raw(),
955 pGuestSession.asOutParam());
956 if (FAILED(rc))
957 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
958
959 pContext->fVerbose = fVerbose;
960 pContext->fDryRun = fDryRun;
961 pContext->fHostToGuest = fHostToGuest;
962 pContext->pGuestSession = pGuestSession;
963
964 *ppContext = pContext;
965
966 return VINF_SUCCESS;
967}
968
969/**
970 * Frees are previously allocated copy context structure.
971 *
972 * @param pContext Pointer to copy context to free.
973 */
974static void ctrlCopyContextFree(PCOPYCONTEXT pContext)
975{
976 if (pContext)
977 {
978 if (pContext->pGuestSession)
979 pContext->pGuestSession->Close();
980 delete pContext;
981 }
982}
983
984/**
985 * Translates a source path to a destination path (can be both sides,
986 * either host or guest). The source root is needed to determine the start
987 * of the relative source path which also needs to present in the destination
988 * path.
989 *
990 * @return IPRT status code.
991 * @param pszSourceRoot Source root path. No trailing directory slash!
992 * @param pszSource Actual source to transform. Must begin with
993 * the source root path!
994 * @param pszDest Destination path.
995 * @param ppszTranslated Pointer to the allocated, translated destination
996 * path. Must be free'd with RTStrFree().
997 */
998static int ctrlCopyTranslatePath(const char *pszSourceRoot, const char *pszSource,
999 const char *pszDest, char **ppszTranslated)
1000{
1001 AssertPtrReturn(pszSourceRoot, VERR_INVALID_POINTER);
1002 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1003 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1004 AssertPtrReturn(ppszTranslated, VERR_INVALID_POINTER);
1005#if 0 /** @todo r=bird: It does not make sense to apply host path parsing semantics onto guest paths. I hope this code isn't mixing host/guest paths in the same way anywhere else... @bugref{6344} */
1006 AssertReturn(RTPathStartsWith(pszSource, pszSourceRoot), VERR_INVALID_PARAMETER);
1007#endif
1008
1009 /* Construct the relative dest destination path by "subtracting" the
1010 * source from the source root, e.g.
1011 *
1012 * source root path = "e:\foo\", source = "e:\foo\bar"
1013 * dest = "d:\baz\"
1014 * translated = "d:\baz\bar\"
1015 */
1016 char szTranslated[RTPATH_MAX];
1017 size_t srcOff = strlen(pszSourceRoot);
1018 AssertReturn(srcOff, VERR_INVALID_PARAMETER);
1019
1020 char *pszDestPath = RTStrDup(pszDest);
1021 AssertPtrReturn(pszDestPath, VERR_NO_MEMORY);
1022
1023 int vrc;
1024 if (!RTPathFilename(pszDestPath))
1025 {
1026 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1027 pszDestPath, &pszSource[srcOff]);
1028 }
1029 else
1030 {
1031 char *pszDestFileName = RTStrDup(RTPathFilename(pszDestPath));
1032 if (pszDestFileName)
1033 {
1034 RTPathStripFilename(pszDestPath);
1035 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1036 pszDestPath, pszDestFileName);
1037 RTStrFree(pszDestFileName);
1038 }
1039 else
1040 vrc = VERR_NO_MEMORY;
1041 }
1042 RTStrFree(pszDestPath);
1043
1044 if (RT_SUCCESS(vrc))
1045 {
1046 *ppszTranslated = RTStrDup(szTranslated);
1047#if 0
1048 RTPrintf("Root: %s, Source: %s, Dest: %s, Translated: %s\n",
1049 pszSourceRoot, pszSource, pszDest, *ppszTranslated);
1050#endif
1051 }
1052 return vrc;
1053}
1054
1055#ifdef DEBUG_andy
1056static int tstTranslatePath()
1057{
1058 RTAssertSetMayPanic(false /* Do not freak out, please. */);
1059
1060 static struct
1061 {
1062 const char *pszSourceRoot;
1063 const char *pszSource;
1064 const char *pszDest;
1065 const char *pszTranslated;
1066 int iResult;
1067 } aTests[] =
1068 {
1069 /* Invalid stuff. */
1070 { NULL, NULL, NULL, NULL, VERR_INVALID_POINTER },
1071#ifdef RT_OS_WINDOWS
1072 /* Windows paths. */
1073 { "c:\\foo", "c:\\foo\\bar.txt", "c:\\test", "c:\\test\\bar.txt", VINF_SUCCESS },
1074 { "c:\\foo", "c:\\foo\\baz\\bar.txt", "c:\\test", "c:\\test\\baz\\bar.txt", VINF_SUCCESS },
1075#else /* RT_OS_WINDOWS */
1076 { "/home/test/foo", "/home/test/foo/bar.txt", "/opt/test", "/opt/test/bar.txt", VINF_SUCCESS },
1077 { "/home/test/foo", "/home/test/foo/baz/bar.txt", "/opt/test", "/opt/test/baz/bar.txt", VINF_SUCCESS },
1078#endif /* !RT_OS_WINDOWS */
1079 /* Mixed paths*/
1080 /** @todo */
1081 { NULL }
1082 };
1083
1084 size_t iTest = 0;
1085 for (iTest; iTest < RT_ELEMENTS(aTests); iTest++)
1086 {
1087 RTPrintf("=> Test %d\n", iTest);
1088 RTPrintf("\tSourceRoot=%s, Source=%s, Dest=%s\n",
1089 aTests[iTest].pszSourceRoot, aTests[iTest].pszSource, aTests[iTest].pszDest);
1090
1091 char *pszTranslated = NULL;
1092 int iResult = ctrlCopyTranslatePath(aTests[iTest].pszSourceRoot, aTests[iTest].pszSource,
1093 aTests[iTest].pszDest, &pszTranslated);
1094 if (iResult != aTests[iTest].iResult)
1095 {
1096 RTPrintf("\tReturned %Rrc, expected %Rrc\n",
1097 iResult, aTests[iTest].iResult);
1098 }
1099 else if ( pszTranslated
1100 && strcmp(pszTranslated, aTests[iTest].pszTranslated))
1101 {
1102 RTPrintf("\tReturned translated path %s, expected %s\n",
1103 pszTranslated, aTests[iTest].pszTranslated);
1104 }
1105
1106 if (pszTranslated)
1107 {
1108 RTPrintf("\tTranslated=%s\n", pszTranslated);
1109 RTStrFree(pszTranslated);
1110 }
1111 }
1112
1113 return VINF_SUCCESS; /* @todo */
1114}
1115#endif
1116
1117/**
1118 * Creates a directory on the destination, based on the current copy
1119 * context.
1120 *
1121 * @return IPRT status code.
1122 * @param pContext Pointer to current copy control context.
1123 * @param pszDir Directory to create.
1124 */
1125static int ctrlCopyDirCreate(PCOPYCONTEXT pContext, const char *pszDir)
1126{
1127 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1128 AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
1129
1130 bool fDirExists;
1131 int vrc = ctrlCopyDirExists(pContext, pContext->fHostToGuest, pszDir, &fDirExists);
1132 if ( RT_SUCCESS(vrc)
1133 && fDirExists)
1134 {
1135 if (pContext->fVerbose)
1136 RTPrintf("Directory \"%s\" already exists\n", pszDir);
1137 return VINF_SUCCESS;
1138 }
1139
1140 /* If querying for a directory existence fails there's no point of even trying
1141 * to create such a directory. */
1142 if (RT_FAILURE(vrc))
1143 return vrc;
1144
1145 if (pContext->fVerbose)
1146 RTPrintf("Creating directory \"%s\" ...\n", pszDir);
1147
1148 if (pContext->fDryRun)
1149 return VINF_SUCCESS;
1150
1151 if (pContext->fHostToGuest) /* We want to create directories on the guest. */
1152 {
1153 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
1154 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
1155 HRESULT rc = pContext->pGuestSession->DirectoryCreate(Bstr(pszDir).raw(),
1156 0700, ComSafeArrayAsInParam(dirCreateFlags));
1157 if (FAILED(rc))
1158 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1159 }
1160 else /* ... or on the host. */
1161 {
1162 vrc = RTDirCreateFullPath(pszDir, 0700);
1163 if (vrc == VERR_ALREADY_EXISTS)
1164 vrc = VINF_SUCCESS;
1165 }
1166 return vrc;
1167}
1168
1169/**
1170 * Checks whether a specific host/guest directory exists.
1171 *
1172 * @return IPRT status code.
1173 * @param pContext Pointer to current copy control context.
1174 * @param bGuest true if directory needs to be checked on the guest
1175 * or false if on the host.
1176 * @param pszDir Actual directory to check.
1177 * @param fExists Pointer which receives the result if the
1178 * given directory exists or not.
1179 */
1180static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest,
1181 const char *pszDir, bool *fExists)
1182{
1183 AssertPtrReturn(pContext, false);
1184 AssertPtrReturn(pszDir, false);
1185 AssertPtrReturn(fExists, false);
1186
1187 int vrc = VINF_SUCCESS;
1188 if (bGuest)
1189 {
1190 BOOL fDirExists = FALSE;
1191 HRESULT rc = pContext->pGuestSession->DirectoryExists(Bstr(pszDir).raw(), &fDirExists);
1192 if (FAILED(rc))
1193 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1194 else
1195 *fExists = fDirExists ? true : false;
1196 }
1197 else
1198 *fExists = RTDirExists(pszDir);
1199 return vrc;
1200}
1201
1202/**
1203 * Checks whether a specific directory exists on the destination, based
1204 * on the current copy context.
1205 *
1206 * @return IPRT status code.
1207 * @param pContext Pointer to current copy control context.
1208 * @param pszDir Actual directory to check.
1209 * @param fExists Pointer which receives the result if the
1210 * given directory exists or not.
1211 */
1212static int ctrlCopyDirExistsOnDest(PCOPYCONTEXT pContext, const char *pszDir,
1213 bool *fExists)
1214{
1215 return ctrlCopyDirExists(pContext, pContext->fHostToGuest,
1216 pszDir, fExists);
1217}
1218
1219/**
1220 * Checks whether a specific directory exists on the source, based
1221 * on the current copy context.
1222 *
1223 * @return IPRT status code.
1224 * @param pContext Pointer to current copy control context.
1225 * @param pszDir Actual directory to check.
1226 * @param fExists Pointer which receives the result if the
1227 * given directory exists or not.
1228 */
1229static int ctrlCopyDirExistsOnSource(PCOPYCONTEXT pContext, const char *pszDir,
1230 bool *fExists)
1231{
1232 return ctrlCopyDirExists(pContext, !pContext->fHostToGuest,
1233 pszDir, fExists);
1234}
1235
1236/**
1237 * Checks whether a specific host/guest file exists.
1238 *
1239 * @return IPRT status code.
1240 * @param pContext Pointer to current copy control context.
1241 * @param bGuest true if file needs to be checked on the guest
1242 * or false if on the host.
1243 * @param pszFile Actual file to check.
1244 * @param fExists Pointer which receives the result if the
1245 * given file exists or not.
1246 */
1247static int ctrlCopyFileExists(PCOPYCONTEXT pContext, bool bOnGuest,
1248 const char *pszFile, bool *fExists)
1249{
1250 AssertPtrReturn(pContext, false);
1251 AssertPtrReturn(pszFile, false);
1252 AssertPtrReturn(fExists, false);
1253
1254 int vrc = VINF_SUCCESS;
1255 if (bOnGuest)
1256 {
1257 BOOL fFileExists = FALSE;
1258 HRESULT rc = pContext->pGuestSession->FileExists(Bstr(pszFile).raw(), &fFileExists);
1259 if (FAILED(rc))
1260 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1261 else
1262 *fExists = fFileExists ? true : false;
1263 }
1264 else
1265 *fExists = RTFileExists(pszFile);
1266 return vrc;
1267}
1268
1269/**
1270 * Checks whether a specific file exists on the destination, based on the
1271 * current copy context.
1272 *
1273 * @return IPRT status code.
1274 * @param pContext Pointer to current copy control context.
1275 * @param pszFile Actual file to check.
1276 * @param fExists Pointer which receives the result if the
1277 * given file exists or not.
1278 */
1279static int ctrlCopyFileExistsOnDest(PCOPYCONTEXT pContext, const char *pszFile,
1280 bool *fExists)
1281{
1282 return ctrlCopyFileExists(pContext, pContext->fHostToGuest,
1283 pszFile, fExists);
1284}
1285
1286/**
1287 * Checks whether a specific file exists on the source, based on the
1288 * current copy context.
1289 *
1290 * @return IPRT status code.
1291 * @param pContext Pointer to current copy control context.
1292 * @param pszFile Actual file to check.
1293 * @param fExists Pointer which receives the result if the
1294 * given file exists or not.
1295 */
1296static int ctrlCopyFileExistsOnSource(PCOPYCONTEXT pContext, const char *pszFile,
1297 bool *fExists)
1298{
1299 return ctrlCopyFileExists(pContext, !pContext->fHostToGuest,
1300 pszFile, fExists);
1301}
1302
1303/**
1304 * Copies a source file to the destination.
1305 *
1306 * @return IPRT status code.
1307 * @param pContext Pointer to current copy control context.
1308 * @param pszFileSource Source file to copy to the destination.
1309 * @param pszFileDest Name of copied file on the destination.
1310 * @param fFlags Copy flags. No supported at the moment and needs
1311 * to be set to 0.
1312 */
1313static int ctrlCopyFileToDest(PCOPYCONTEXT pContext, const char *pszFileSource,
1314 const char *pszFileDest, uint32_t fFlags)
1315{
1316 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1317 AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
1318 AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
1319 AssertReturn(!fFlags, VERR_INVALID_POINTER); /* No flags supported yet. */
1320
1321 if (pContext->fVerbose)
1322 RTPrintf("Copying \"%s\" to \"%s\" ...\n",
1323 pszFileSource, pszFileDest);
1324
1325 if (pContext->fDryRun)
1326 return VINF_SUCCESS;
1327
1328 int vrc = VINF_SUCCESS;
1329 ComPtr<IProgress> pProgress;
1330 HRESULT rc;
1331 if (pContext->fHostToGuest)
1332 {
1333 SafeArray<CopyFileFlag_T> copyFlags;
1334 rc = pContext->pGuestSession->CopyTo(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1335 ComSafeArrayAsInParam(copyFlags),
1336
1337 pProgress.asOutParam());
1338 }
1339 else
1340 {
1341 SafeArray<CopyFileFlag_T> copyFlags;
1342 rc = pContext->pGuestSession->CopyFrom(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1343 ComSafeArrayAsInParam(copyFlags),
1344 pProgress.asOutParam());
1345 }
1346
1347 if (FAILED(rc))
1348 {
1349 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1350 }
1351 else
1352 {
1353 if (pContext->fVerbose)
1354 rc = showProgress(pProgress);
1355 else
1356 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
1357 if (SUCCEEDED(rc))
1358 CHECK_PROGRESS_ERROR(pProgress, ("File copy failed"));
1359 vrc = ctrlPrintProgressError(pProgress);
1360 }
1361
1362 return vrc;
1363}
1364
1365/**
1366 * Copys a directory (tree) from host to the guest.
1367 *
1368 * @return IPRT status code.
1369 * @param pContext Pointer to current copy control context.
1370 * @param pszSource Source directory on the host to copy to the guest.
1371 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1372 * @param pszDest Destination directory on the guest.
1373 * @param fFlags Copy flags, such as recursive copying.
1374 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1375 * is needed for recursion.
1376 */
1377static int ctrlCopyDirToGuest(PCOPYCONTEXT pContext,
1378 const char *pszSource, const char *pszFilter,
1379 const char *pszDest, uint32_t fFlags,
1380 const char *pszSubDir /* For recursion. */)
1381{
1382 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1383 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1384 /* Filter is optional. */
1385 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1386 /* Sub directory is optional. */
1387
1388 /*
1389 * Construct current path.
1390 */
1391 char szCurDir[RTPATH_MAX];
1392 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
1393 if (RT_SUCCESS(vrc) && pszSubDir)
1394 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
1395
1396 if (pContext->fVerbose)
1397 RTPrintf("Processing host directory: %s\n", szCurDir);
1398
1399 /* Flag indicating whether the current directory was created on the
1400 * target or not. */
1401 bool fDirCreated = false;
1402
1403 /*
1404 * Open directory without a filter - RTDirOpenFiltered unfortunately
1405 * cannot handle sub directories so we have to do the filtering ourselves.
1406 */
1407 PRTDIR pDir = NULL;
1408 if (RT_SUCCESS(vrc))
1409 {
1410 vrc = RTDirOpen(&pDir, szCurDir);
1411 if (RT_FAILURE(vrc))
1412 pDir = NULL;
1413 }
1414 if (RT_SUCCESS(vrc))
1415 {
1416 /*
1417 * Enumerate the directory tree.
1418 */
1419 while (RT_SUCCESS(vrc))
1420 {
1421 RTDIRENTRY DirEntry;
1422 vrc = RTDirRead(pDir, &DirEntry, NULL);
1423 if (RT_FAILURE(vrc))
1424 {
1425 if (vrc == VERR_NO_MORE_FILES)
1426 vrc = VINF_SUCCESS;
1427 break;
1428 }
1429 switch (DirEntry.enmType)
1430 {
1431 case RTDIRENTRYTYPE_DIRECTORY:
1432 {
1433 /* Skip "." and ".." entries. */
1434 if ( !strcmp(DirEntry.szName, ".")
1435 || !strcmp(DirEntry.szName, ".."))
1436 break;
1437
1438 if (pContext->fVerbose)
1439 RTPrintf("Directory: %s\n", DirEntry.szName);
1440
1441 if (fFlags & CopyFileFlag_Recursive)
1442 {
1443 char *pszNewSub = NULL;
1444 if (pszSubDir)
1445 pszNewSub = RTPathJoinA(pszSubDir, DirEntry.szName);
1446 else
1447 {
1448 pszNewSub = RTStrDup(DirEntry.szName);
1449 RTPathStripTrailingSlash(pszNewSub);
1450 }
1451
1452 if (pszNewSub)
1453 {
1454 vrc = ctrlCopyDirToGuest(pContext,
1455 pszSource, pszFilter,
1456 pszDest, fFlags, pszNewSub);
1457 RTStrFree(pszNewSub);
1458 }
1459 else
1460 vrc = VERR_NO_MEMORY;
1461 }
1462 break;
1463 }
1464
1465 case RTDIRENTRYTYPE_SYMLINK:
1466 if ( (fFlags & CopyFileFlag_Recursive)
1467 && (fFlags & CopyFileFlag_FollowLinks))
1468 {
1469 /* Fall through to next case is intentional. */
1470 }
1471 else
1472 break;
1473
1474 case RTDIRENTRYTYPE_FILE:
1475 {
1476 if ( pszFilter
1477 && !RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
1478 {
1479 break; /* Filter does not match. */
1480 }
1481
1482 if (pContext->fVerbose)
1483 RTPrintf("File: %s\n", DirEntry.szName);
1484
1485 if (!fDirCreated)
1486 {
1487 char *pszDestDir;
1488 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
1489 pszDest, &pszDestDir);
1490 if (RT_SUCCESS(vrc))
1491 {
1492 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1493 RTStrFree(pszDestDir);
1494
1495 fDirCreated = true;
1496 }
1497 }
1498
1499 if (RT_SUCCESS(vrc))
1500 {
1501 char *pszFileSource = RTPathJoinA(szCurDir, DirEntry.szName);
1502 if (pszFileSource)
1503 {
1504 char *pszFileDest;
1505 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
1506 pszDest, &pszFileDest);
1507 if (RT_SUCCESS(vrc))
1508 {
1509 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
1510 pszFileDest, 0 /* Flags */);
1511 RTStrFree(pszFileDest);
1512 }
1513 RTStrFree(pszFileSource);
1514 }
1515 }
1516 break;
1517 }
1518
1519 default:
1520 break;
1521 }
1522 if (RT_FAILURE(vrc))
1523 break;
1524 }
1525
1526 RTDirClose(pDir);
1527 }
1528 return vrc;
1529}
1530
1531/**
1532 * Copys a directory (tree) from guest to the host.
1533 *
1534 * @return IPRT status code.
1535 * @param pContext Pointer to current copy control context.
1536 * @param pszSource Source directory on the guest to copy to the host.
1537 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1538 * @param pszDest Destination directory on the host.
1539 * @param fFlags Copy flags, such as recursive copying.
1540 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1541 * is needed for recursion.
1542 */
1543static int ctrlCopyDirToHost(PCOPYCONTEXT pContext,
1544 const char *pszSource, const char *pszFilter,
1545 const char *pszDest, uint32_t fFlags,
1546 const char *pszSubDir /* For recursion. */)
1547{
1548 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1549 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1550 /* Filter is optional. */
1551 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1552 /* Sub directory is optional. */
1553
1554 /*
1555 * Construct current path.
1556 */
1557 char szCurDir[RTPATH_MAX];
1558 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
1559 if (RT_SUCCESS(vrc) && pszSubDir)
1560 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
1561
1562 if (RT_FAILURE(vrc))
1563 return vrc;
1564
1565 if (pContext->fVerbose)
1566 RTPrintf("Processing guest directory: %s\n", szCurDir);
1567
1568 /* Flag indicating whether the current directory was created on the
1569 * target or not. */
1570 bool fDirCreated = false;
1571 SafeArray<DirectoryOpenFlag_T> dirOpenFlags; /* No flags supported yet. */
1572 ComPtr<IGuestDirectory> pDirectory;
1573 HRESULT rc = pContext->pGuestSession->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(),
1574 ComSafeArrayAsInParam(dirOpenFlags),
1575 pDirectory.asOutParam());
1576 if (FAILED(rc))
1577 return ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1578 ComPtr<IFsObjInfo> dirEntry;
1579 while (true)
1580 {
1581 rc = pDirectory->Read(dirEntry.asOutParam());
1582 if (FAILED(rc))
1583 break;
1584
1585 FsObjType_T enmType;
1586 dirEntry->COMGETTER(Type)(&enmType);
1587
1588 Bstr strName;
1589 dirEntry->COMGETTER(Name)(strName.asOutParam());
1590
1591 switch (enmType)
1592 {
1593 case FsObjType_Directory:
1594 {
1595 Assert(!strName.isEmpty());
1596
1597 /* Skip "." and ".." entries. */
1598 if ( !strName.compare(Bstr("."))
1599 || !strName.compare(Bstr("..")))
1600 break;
1601
1602 if (pContext->fVerbose)
1603 {
1604 Utf8Str strDir(strName);
1605 RTPrintf("Directory: %s\n", strDir.c_str());
1606 }
1607
1608 if (fFlags & CopyFileFlag_Recursive)
1609 {
1610 Utf8Str strDir(strName);
1611 char *pszNewSub = NULL;
1612 if (pszSubDir)
1613 pszNewSub = RTPathJoinA(pszSubDir, strDir.c_str());
1614 else
1615 {
1616 pszNewSub = RTStrDup(strDir.c_str());
1617 RTPathStripTrailingSlash(pszNewSub);
1618 }
1619 if (pszNewSub)
1620 {
1621 vrc = ctrlCopyDirToHost(pContext,
1622 pszSource, pszFilter,
1623 pszDest, fFlags, pszNewSub);
1624 RTStrFree(pszNewSub);
1625 }
1626 else
1627 vrc = VERR_NO_MEMORY;
1628 }
1629 break;
1630 }
1631
1632 case FsObjType_Symlink:
1633 if ( (fFlags & CopyFileFlag_Recursive)
1634 && (fFlags & CopyFileFlag_FollowLinks))
1635 {
1636 /* Fall through to next case is intentional. */
1637 }
1638 else
1639 break;
1640
1641 case FsObjType_File:
1642 {
1643 Assert(!strName.isEmpty());
1644
1645 Utf8Str strFile(strName);
1646 if ( pszFilter
1647 && !RTStrSimplePatternMatch(pszFilter, strFile.c_str()))
1648 {
1649 break; /* Filter does not match. */
1650 }
1651
1652 if (pContext->fVerbose)
1653 RTPrintf("File: %s\n", strFile.c_str());
1654
1655 if (!fDirCreated)
1656 {
1657 char *pszDestDir;
1658 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
1659 pszDest, &pszDestDir);
1660 if (RT_SUCCESS(vrc))
1661 {
1662 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1663 RTStrFree(pszDestDir);
1664
1665 fDirCreated = true;
1666 }
1667 }
1668
1669 if (RT_SUCCESS(vrc))
1670 {
1671 char *pszFileSource = RTPathJoinA(szCurDir, strFile.c_str());
1672 if (pszFileSource)
1673 {
1674 char *pszFileDest;
1675 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
1676 pszDest, &pszFileDest);
1677 if (RT_SUCCESS(vrc))
1678 {
1679 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
1680 pszFileDest, 0 /* Flags */);
1681 RTStrFree(pszFileDest);
1682 }
1683 RTStrFree(pszFileSource);
1684 }
1685 else
1686 vrc = VERR_NO_MEMORY;
1687 }
1688 break;
1689 }
1690
1691 default:
1692 RTPrintf("Warning: Directory entry of type %ld not handled, skipping ...\n",
1693 enmType);
1694 break;
1695 }
1696
1697 if (RT_FAILURE(vrc))
1698 break;
1699 }
1700
1701 if (RT_UNLIKELY(FAILED(rc)))
1702 {
1703 switch (rc)
1704 {
1705 case E_ABORT: /* No more directory entries left to process. */
1706 break;
1707
1708 case VBOX_E_FILE_ERROR: /* Current entry cannot be accessed to
1709 to missing rights. */
1710 {
1711 RTPrintf("Warning: Cannot access \"%s\", skipping ...\n",
1712 szCurDir);
1713 break;
1714 }
1715
1716 default:
1717 vrc = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
1718 break;
1719 }
1720 }
1721
1722 HRESULT rc2 = pDirectory->Close();
1723 if (FAILED(rc2))
1724 {
1725 int vrc2 = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
1726 if (RT_SUCCESS(vrc))
1727 vrc = vrc2;
1728 }
1729 else if (SUCCEEDED(rc))
1730 rc = rc2;
1731
1732 return vrc;
1733}
1734
1735/**
1736 * Copys a directory (tree) to the destination, based on the current copy
1737 * context.
1738 *
1739 * @return IPRT status code.
1740 * @param pContext Pointer to current copy control context.
1741 * @param pszSource Source directory to copy to the destination.
1742 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1743 * @param pszDest Destination directory where to copy in the source
1744 * source directory.
1745 * @param fFlags Copy flags, such as recursive copying.
1746 */
1747static int ctrlCopyDirToDest(PCOPYCONTEXT pContext,
1748 const char *pszSource, const char *pszFilter,
1749 const char *pszDest, uint32_t fFlags)
1750{
1751 if (pContext->fHostToGuest)
1752 return ctrlCopyDirToGuest(pContext, pszSource, pszFilter,
1753 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
1754 return ctrlCopyDirToHost(pContext, pszSource, pszFilter,
1755 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
1756}
1757
1758/**
1759 * Creates a source root by stripping file names or filters of the specified source.
1760 *
1761 * @return IPRT status code.
1762 * @param pszSource Source to create source root for.
1763 * @param ppszSourceRoot Pointer that receives the allocated source root. Needs
1764 * to be free'd with ctrlCopyFreeSourceRoot().
1765 */
1766static int ctrlCopyCreateSourceRoot(const char *pszSource, char **ppszSourceRoot)
1767{
1768 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1769 AssertPtrReturn(ppszSourceRoot, VERR_INVALID_POINTER);
1770
1771 char *pszNewRoot = RTStrDup(pszSource);
1772 AssertPtrReturn(pszNewRoot, VERR_NO_MEMORY);
1773
1774 size_t lenRoot = strlen(pszNewRoot);
1775 if ( lenRoot
1776 && pszNewRoot[lenRoot - 1] == '/'
1777 && pszNewRoot[lenRoot - 1] == '\\'
1778 && lenRoot > 1
1779 && pszNewRoot[lenRoot - 2] == '/'
1780 && pszNewRoot[lenRoot - 2] == '\\')
1781 {
1782 *ppszSourceRoot = pszNewRoot;
1783 if (lenRoot > 1)
1784 *ppszSourceRoot[lenRoot - 2] = '\0';
1785 *ppszSourceRoot[lenRoot - 1] = '\0';
1786 }
1787 else
1788 {
1789 /* If there's anything (like a file name or a filter),
1790 * strip it! */
1791 RTPathStripFilename(pszNewRoot);
1792 *ppszSourceRoot = pszNewRoot;
1793 }
1794
1795 return VINF_SUCCESS;
1796}
1797
1798/**
1799 * Frees a previously allocated source root.
1800 *
1801 * @return IPRT status code.
1802 * @param pszSourceRoot Source root to free.
1803 */
1804static void ctrlCopyFreeSourceRoot(char *pszSourceRoot)
1805{
1806 RTStrFree(pszSourceRoot);
1807}
1808
1809static int handleCtrlCopy(ComPtr<IGuest> guest, HandlerArg *pArg,
1810 bool fHostToGuest)
1811{
1812 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1813
1814 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
1815 * is much better (partly because it is much simpler of course). The main
1816 * arguments against this is that (1) all but two options conflicts with
1817 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
1818 * done windows CMD style (though not in a 100% compatible way), and (3)
1819 * that only one source is allowed - efficiently sabotaging default
1820 * wildcard expansion by a unix shell. The best solution here would be
1821 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
1822
1823 /*
1824 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
1825 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
1826 * does in here.
1827 */
1828 static const RTGETOPTDEF s_aOptions[] =
1829 {
1830 { "--dryrun", GETOPTDEF_COPY_DRYRUN, RTGETOPT_REQ_NOTHING },
1831 { "--follow", GETOPTDEF_COPY_FOLLOW, RTGETOPT_REQ_NOTHING },
1832 { "--username", 'u', RTGETOPT_REQ_STRING },
1833 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
1834 { "--password", GETOPTDEF_COPY_PASSWORD, RTGETOPT_REQ_STRING },
1835 { "--domain", 'd', RTGETOPT_REQ_STRING },
1836 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1837 { "--target-directory", GETOPTDEF_COPY_TARGETDIR, RTGETOPT_REQ_STRING },
1838 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1839 };
1840
1841 int ch;
1842 RTGETOPTUNION ValueUnion;
1843 RTGETOPTSTATE GetState;
1844 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
1845 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1846
1847 Utf8Str strSource;
1848 Utf8Str strDest;
1849 Utf8Str strUsername;
1850 Utf8Str strPassword;
1851 Utf8Str strDomain;
1852 uint32_t fFlags = CopyFileFlag_None;
1853 bool fVerbose = false;
1854 bool fCopyRecursive = false;
1855 bool fDryRun = false;
1856
1857 SOURCEVEC vecSources;
1858
1859 int vrc = VINF_SUCCESS;
1860 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1861 {
1862 /* For options that require an argument, ValueUnion has received the value. */
1863 switch (ch)
1864 {
1865 case GETOPTDEF_COPY_DRYRUN:
1866 fDryRun = true;
1867 break;
1868
1869 case GETOPTDEF_COPY_FOLLOW:
1870 fFlags |= CopyFileFlag_FollowLinks;
1871 break;
1872
1873 case 'u': /* User name */
1874 strUsername = ValueUnion.psz;
1875 break;
1876
1877 case GETOPTDEF_COPY_PASSWORD: /* Password */
1878 strPassword = ValueUnion.psz;
1879 break;
1880
1881 case 'p': /* Password file */
1882 {
1883 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
1884 if (rcExit != RTEXITCODE_SUCCESS)
1885 return rcExit;
1886 break;
1887 }
1888
1889 case 'd': /* domain */
1890 strDomain = ValueUnion.psz;
1891 break;
1892
1893 case 'R': /* Recursive processing */
1894 fFlags |= CopyFileFlag_Recursive;
1895 break;
1896
1897 case GETOPTDEF_COPY_TARGETDIR:
1898 strDest = ValueUnion.psz;
1899 break;
1900
1901 case 'v': /* Verbose */
1902 fVerbose = true;
1903 break;
1904
1905 case VINF_GETOPT_NOT_OPTION:
1906 {
1907 /* Last argument and no destination specified with
1908 * --target-directory yet? Then use the current
1909 * (= last) argument as destination. */
1910 if ( pArg->argc == GetState.iNext
1911 && strDest.isEmpty())
1912 {
1913 strDest = ValueUnion.psz;
1914 }
1915 else
1916 {
1917 /* Save the source directory. */
1918 vecSources.push_back(SOURCEFILEENTRY(ValueUnion.psz));
1919 }
1920 break;
1921 }
1922
1923 default:
1924 return RTGetOptPrintError(ch, &ValueUnion);
1925 }
1926 }
1927
1928 if (!vecSources.size())
1929 return errorSyntax(USAGE_GUESTCONTROL,
1930 "No source(s) specified!");
1931
1932 if (strDest.isEmpty())
1933 return errorSyntax(USAGE_GUESTCONTROL,
1934 "No destination specified!");
1935
1936 if (strUsername.isEmpty())
1937 return errorSyntax(USAGE_GUESTCONTROL,
1938 "No user name specified!");
1939
1940 /*
1941 * Done parsing arguments, do some more preparations.
1942 */
1943 if (fVerbose)
1944 {
1945 if (fHostToGuest)
1946 RTPrintf("Copying from host to guest ...\n");
1947 else
1948 RTPrintf("Copying from guest to host ...\n");
1949 if (fDryRun)
1950 RTPrintf("Dry run - no files copied!\n");
1951 }
1952
1953 /* Create the copy context -- it contains all information
1954 * the routines need to know when handling the actual copying. */
1955 PCOPYCONTEXT pContext = NULL;
1956 vrc = ctrlCopyContextCreate(guest, fVerbose, fDryRun, fHostToGuest,
1957 strUsername, strPassword, strDomain,
1958 "VBoxManage Guest Control Copy", &pContext);
1959 if (RT_FAILURE(vrc))
1960 {
1961 RTMsgError("Unable to create copy context, rc=%Rrc\n", vrc);
1962 return RTEXITCODE_FAILURE;
1963 }
1964
1965 /* If the destination is a path, (try to) create it. */
1966 const char *pszDest = strDest.c_str();
1967/** @todo r=bird: RTPathFilename and RTPathStripFilename won't work
1968 * correctly on non-windows hosts when the guest is from the DOS world (Windows,
1969 * OS/2, DOS). The host doesn't know about DOS slashes, only UNIX slashes and
1970 * will get the wrong idea if some dilligent user does:
1971 *
1972 * copyto myfile.txt 'C:\guestfile.txt'
1973 * or
1974 * copyto myfile.txt 'D:guestfile.txt'
1975 *
1976 * @bugref{6344}
1977 */
1978 if (!RTPathFilename(pszDest))
1979 {
1980 vrc = ctrlCopyDirCreate(pContext, pszDest);
1981 }
1982 else
1983 {
1984 /* We assume we got a file name as destination -- so strip
1985 * the actual file name and make sure the appropriate
1986 * directories get created. */
1987 char *pszDestDir = RTStrDup(pszDest);
1988 AssertPtr(pszDestDir);
1989 RTPathStripFilename(pszDestDir);
1990 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1991 RTStrFree(pszDestDir);
1992 }
1993
1994 if (RT_SUCCESS(vrc))
1995 {
1996 /*
1997 * Here starts the actual fun!
1998 * Handle all given sources one by one.
1999 */
2000 for (unsigned long s = 0; s < vecSources.size(); s++)
2001 {
2002 char *pszSource = RTStrDup(vecSources[s].GetSource());
2003 AssertPtrBreakStmt(pszSource, vrc = VERR_NO_MEMORY);
2004 const char *pszFilter = vecSources[s].GetFilter();
2005 if (!strlen(pszFilter))
2006 pszFilter = NULL; /* If empty filter then there's no filter :-) */
2007
2008 char *pszSourceRoot;
2009 vrc = ctrlCopyCreateSourceRoot(pszSource, &pszSourceRoot);
2010 if (RT_FAILURE(vrc))
2011 {
2012 RTMsgError("Unable to create source root, rc=%Rrc\n", vrc);
2013 break;
2014 }
2015
2016 if (fVerbose)
2017 RTPrintf("Source: %s\n", pszSource);
2018
2019 /** @todo Files with filter?? */
2020 bool fSourceIsFile = false;
2021 bool fSourceExists;
2022
2023 size_t cchSource = strlen(pszSource);
2024 if ( cchSource > 1
2025 && RTPATH_IS_SLASH(pszSource[cchSource - 1]))
2026 {
2027 if (pszFilter) /* Directory with filter (so use source root w/o the actual filter). */
2028 vrc = ctrlCopyDirExistsOnSource(pContext, pszSourceRoot, &fSourceExists);
2029 else /* Regular directory without filter. */
2030 vrc = ctrlCopyDirExistsOnSource(pContext, pszSource, &fSourceExists);
2031
2032 if (fSourceExists)
2033 {
2034 /* Strip trailing slash from our source element so that other functions
2035 * can use this stuff properly (like RTPathStartsWith). */
2036 RTPathStripTrailingSlash(pszSource);
2037 }
2038 }
2039 else
2040 {
2041 vrc = ctrlCopyFileExistsOnSource(pContext, pszSource, &fSourceExists);
2042 if ( RT_SUCCESS(vrc)
2043 && fSourceExists)
2044 {
2045 fSourceIsFile = true;
2046 }
2047 }
2048
2049 if ( RT_SUCCESS(vrc)
2050 && fSourceExists)
2051 {
2052 if (fSourceIsFile)
2053 {
2054 /* Single file. */
2055 char *pszDestFile;
2056 vrc = ctrlCopyTranslatePath(pszSourceRoot, pszSource,
2057 strDest.c_str(), &pszDestFile);
2058 if (RT_SUCCESS(vrc))
2059 {
2060 vrc = ctrlCopyFileToDest(pContext, pszSource,
2061 pszDestFile, 0 /* Flags */);
2062 RTStrFree(pszDestFile);
2063 }
2064 else
2065 RTMsgError("Unable to translate path for \"%s\", rc=%Rrc\n",
2066 pszSource, vrc);
2067 }
2068 else
2069 {
2070 /* Directory (with filter?). */
2071 vrc = ctrlCopyDirToDest(pContext, pszSource, pszFilter,
2072 strDest.c_str(), fFlags);
2073 }
2074 }
2075
2076 ctrlCopyFreeSourceRoot(pszSourceRoot);
2077
2078 if ( RT_SUCCESS(vrc)
2079 && !fSourceExists)
2080 {
2081 RTMsgError("Warning: Source \"%s\" does not exist, skipping!\n",
2082 pszSource);
2083 RTStrFree(pszSource);
2084 continue;
2085 }
2086 else if (RT_FAILURE(vrc))
2087 {
2088 RTMsgError("Error processing \"%s\", rc=%Rrc\n",
2089 pszSource, vrc);
2090 RTStrFree(pszSource);
2091 break;
2092 }
2093
2094 RTStrFree(pszSource);
2095 }
2096 }
2097
2098 ctrlCopyContextFree(pContext);
2099
2100 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2101}
2102
2103static int handleCtrlCreateDirectory(ComPtr<IGuest> pGuest, HandlerArg *pArg)
2104{
2105 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2106
2107 /*
2108 * Parse arguments.
2109 *
2110 * Note! No direct returns here, everyone must go thru the cleanup at the
2111 * end of this function.
2112 */
2113 static const RTGETOPTDEF s_aOptions[] =
2114 {
2115 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2116 { "--parents", 'P', RTGETOPT_REQ_NOTHING },
2117 { "--username", 'u', RTGETOPT_REQ_STRING },
2118 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
2119 { "--password", GETOPTDEF_MKDIR_PASSWORD, RTGETOPT_REQ_STRING },
2120 { "--domain", 'd', RTGETOPT_REQ_STRING },
2121 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2122 };
2123
2124 int ch;
2125 RTGETOPTUNION ValueUnion;
2126 RTGETOPTSTATE GetState;
2127 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
2128 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2129
2130 Utf8Str strUsername;
2131 Utf8Str strPassword;
2132 Utf8Str strDomain;
2133 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
2134 uint32_t fDirMode = 0; /* Default mode. */
2135 bool fVerbose = false;
2136
2137 DESTDIRMAP mapDirs;
2138
2139 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2140 {
2141 /* For options that require an argument, ValueUnion has received the value. */
2142 switch (ch)
2143 {
2144 case 'm': /* Mode */
2145 fDirMode = ValueUnion.u32;
2146 break;
2147
2148 case 'P': /* Create parents */
2149 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
2150 break;
2151
2152 case 'u': /* User name */
2153 strUsername = ValueUnion.psz;
2154 break;
2155
2156 case GETOPTDEF_MKDIR_PASSWORD: /* Password */
2157 strPassword = ValueUnion.psz;
2158 break;
2159
2160 case 'p': /* Password file */
2161 {
2162 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2163 if (rcExit != RTEXITCODE_SUCCESS)
2164 return rcExit;
2165 break;
2166 }
2167
2168 case 'd': /* domain */
2169 strDomain = ValueUnion.psz;
2170 break;
2171
2172 case 'v': /* Verbose */
2173 fVerbose = true;
2174 break;
2175
2176 case VINF_GETOPT_NOT_OPTION:
2177 {
2178 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2179 break;
2180 }
2181
2182 default:
2183 return RTGetOptPrintError(ch, &ValueUnion);
2184 }
2185 }
2186
2187 uint32_t cDirs = mapDirs.size();
2188 if (!cDirs)
2189 return errorSyntax(USAGE_GUESTCONTROL, "No directory to create specified!");
2190
2191 if (strUsername.isEmpty())
2192 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
2193
2194 /*
2195 * Create the directories.
2196 */
2197 HRESULT hrc = S_OK;
2198 if (fVerbose && cDirs)
2199 RTPrintf("Creating %u directories ...\n", cDirs);
2200
2201 ComPtr<IGuestSession> pGuestSession;
2202 hrc = pGuest->CreateSession(Bstr(strUsername).raw(),
2203 Bstr(strPassword).raw(),
2204 Bstr(strDomain).raw(),
2205 Bstr("VBoxManage Guest Control MkDir").raw(),
2206 pGuestSession.asOutParam());
2207 if (FAILED(hrc))
2208 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
2209
2210 DESTDIRMAPITER it = mapDirs.begin();
2211 while (it != mapDirs.end())
2212 {
2213 if (fVerbose)
2214 RTPrintf("Creating directory \"%s\" ...\n", it->first.c_str());
2215
2216 hrc = pGuestSession->DirectoryCreate(Bstr(it->first).raw(), fDirMode, ComSafeArrayAsInParam(dirCreateFlags));
2217 if (FAILED(hrc))
2218 {
2219 ctrlPrintError(pGuest, COM_IIDOF(IGuestSession)); /* Return code ignored, save original rc. */
2220 break;
2221 }
2222
2223 it++;
2224 }
2225
2226 if (!pGuestSession.isNull())
2227 pGuestSession->Close();
2228
2229 return FAILED(hrc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2230}
2231
2232static int handleCtrlCreateTemp(ComPtr<IGuest> pGuest, HandlerArg *pArg)
2233{
2234 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2235
2236 /*
2237 * Parse arguments.
2238 *
2239 * Note! No direct returns here, everyone must go thru the cleanup at the
2240 * end of this function.
2241 */
2242 static const RTGETOPTDEF s_aOptions[] =
2243 {
2244 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2245 { "--directory", 'D', RTGETOPT_REQ_NOTHING },
2246 { "--secure", 's', RTGETOPT_REQ_NOTHING },
2247 { "--tmpdir", 't', RTGETOPT_REQ_STRING },
2248 { "--username", 'u', RTGETOPT_REQ_STRING },
2249 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
2250 { "--password", GETOPTDEF_MKDIR_PASSWORD, RTGETOPT_REQ_STRING },
2251 { "--domain", 'd', RTGETOPT_REQ_STRING },
2252 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2253 };
2254
2255 int ch;
2256 RTGETOPTUNION ValueUnion;
2257 RTGETOPTSTATE GetState;
2258 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
2259 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2260
2261 Utf8Str strUsername;
2262 Utf8Str strPassword;
2263 Utf8Str strDomain;
2264 Utf8Str strTemplate;
2265 uint32_t fMode = 0; /* Default mode. */
2266 bool fDirectory = false;
2267 bool fSecure = false;
2268 Utf8Str strTempDir;
2269 bool fVerbose = false;
2270
2271 DESTDIRMAP mapDirs;
2272
2273 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2274 {
2275 /* For options that require an argument, ValueUnion has received the value. */
2276 switch (ch)
2277 {
2278 case 'm': /* Mode */
2279 fMode = ValueUnion.u32;
2280 break;
2281
2282 case 'D': /* Create directory */
2283 fDirectory = true;
2284 break;
2285
2286 case 's': /* Secure */
2287 fSecure = true;
2288 break;
2289
2290 case 't': /* Temp directory */
2291 strTempDir = ValueUnion.psz;
2292 break;
2293
2294 case 'u': /* User name */
2295 strUsername = ValueUnion.psz;
2296 break;
2297
2298 case GETOPTDEF_MKDIR_PASSWORD: /* Password */
2299 strPassword = ValueUnion.psz;
2300 break;
2301
2302 case 'p': /* Password file */
2303 {
2304 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2305 if (rcExit != RTEXITCODE_SUCCESS)
2306 return rcExit;
2307 break;
2308 }
2309
2310 case 'd': /* domain */
2311 strDomain = ValueUnion.psz;
2312 break;
2313
2314 case 'v': /* Verbose */
2315 fVerbose = true;
2316 break;
2317
2318 case VINF_GETOPT_NOT_OPTION:
2319 {
2320 if (strTemplate.isEmpty())
2321 strTemplate = ValueUnion.psz;
2322 else
2323 return errorSyntax(USAGE_GUESTCONTROL,
2324 "More than one template specified!\n");
2325 break;
2326 }
2327
2328 default:
2329 return RTGetOptPrintError(ch, &ValueUnion);
2330 }
2331 }
2332
2333 if (strTemplate.isEmpty())
2334 return errorSyntax(USAGE_GUESTCONTROL, "No template specified!");
2335
2336 if (strUsername.isEmpty())
2337 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
2338
2339 if (!fDirectory)
2340 return errorSyntax(USAGE_GUESTCONTROL, "Creating temporary files is currently not supported!");
2341
2342 /*
2343 * Create the directories.
2344 */
2345 HRESULT hrc = S_OK;
2346 if (fVerbose)
2347 {
2348 if (fDirectory && !strTempDir.isEmpty())
2349 RTPrintf("Creating temporary directory from template '%s' in directory '%s' ...\n",
2350 strTemplate.c_str(), strTempDir.c_str());
2351 else if (fDirectory)
2352 RTPrintf("Creating temporary directory from template '%s' in default temporary directory ...\n",
2353 strTemplate.c_str());
2354 else if (!fDirectory && !strTempDir.isEmpty())
2355 RTPrintf("Creating temporary file from template '%s' in directory '%s' ...\n",
2356 strTemplate.c_str(), strTempDir.c_str());
2357 else if (!fDirectory)
2358 RTPrintf("Creating temporary file from template '%s' in default temporary directory ...\n",
2359 strTemplate.c_str());
2360 }
2361
2362 ComPtr<IGuestSession> pGuestSession;
2363 hrc = pGuest->CreateSession(Bstr(strUsername).raw(),
2364 Bstr(strPassword).raw(),
2365 Bstr(strDomain).raw(),
2366 Bstr("VBoxManage Guest Control MkTemp").raw(),
2367 pGuestSession.asOutParam());
2368 if (FAILED(hrc))
2369 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
2370
2371 if (fDirectory)
2372 {
2373 Bstr directory;
2374 hrc = pGuestSession->DirectoryCreateTemp(Bstr(strTemplate).raw(),
2375 fMode, Bstr(strTempDir).raw(),
2376 fSecure,
2377 directory.asOutParam());
2378 if (SUCCEEDED(hrc))
2379 RTPrintf("Directory name: %ls\n", directory.raw());
2380 }
2381 // else - temporary file not yet implemented
2382 if (FAILED(hrc))
2383 ctrlPrintError(pGuest, COM_IIDOF(IGuestSession)); /* Return code ignored, save original rc. */
2384
2385 if (!pGuestSession.isNull())
2386 pGuestSession->Close();
2387
2388 return FAILED(hrc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2389}
2390
2391static int handleCtrlStat(ComPtr<IGuest> pGuest, HandlerArg *pArg)
2392{
2393 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2394
2395 static const RTGETOPTDEF s_aOptions[] =
2396 {
2397 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
2398 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
2399 { "--format", 'c', RTGETOPT_REQ_STRING },
2400 { "--username", 'u', RTGETOPT_REQ_STRING },
2401 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
2402 { "--password", GETOPTDEF_STAT_PASSWORD, RTGETOPT_REQ_STRING },
2403 { "--domain", 'd', RTGETOPT_REQ_STRING },
2404 { "--terse", 't', RTGETOPT_REQ_NOTHING },
2405 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2406 };
2407
2408 int ch;
2409 RTGETOPTUNION ValueUnion;
2410 RTGETOPTSTATE GetState;
2411 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
2412 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2413
2414 Utf8Str strUsername;
2415 Utf8Str strPassword;
2416 Utf8Str strDomain;
2417
2418 bool fVerbose = false;
2419 DESTDIRMAP mapObjs;
2420
2421 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2422 {
2423 /* For options that require an argument, ValueUnion has received the value. */
2424 switch (ch)
2425 {
2426 case 'u': /* User name */
2427 strUsername = ValueUnion.psz;
2428 break;
2429
2430 case GETOPTDEF_STAT_PASSWORD: /* Password */
2431 strPassword = ValueUnion.psz;
2432 break;
2433
2434 case 'p': /* Password file */
2435 {
2436 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2437 if (rcExit != RTEXITCODE_SUCCESS)
2438 return rcExit;
2439 break;
2440 }
2441
2442 case 'd': /* domain */
2443 strDomain = ValueUnion.psz;
2444 break;
2445
2446 case 'L': /* Dereference */
2447 case 'f': /* File-system */
2448 case 'c': /* Format */
2449 case 't': /* Terse */
2450 return errorSyntax(USAGE_GUESTCONTROL, "Command \"%s\" not implemented yet!",
2451 ValueUnion.psz);
2452 break; /* Never reached. */
2453
2454 case 'v': /* Verbose */
2455 fVerbose = true;
2456 break;
2457
2458 case VINF_GETOPT_NOT_OPTION:
2459 {
2460 mapObjs[ValueUnion.psz]; /* Add element to check to map. */
2461 break;
2462 }
2463
2464 default:
2465 return RTGetOptPrintError(ch, &ValueUnion);
2466 }
2467 }
2468
2469 uint32_t cObjs = mapObjs.size();
2470 if (!cObjs)
2471 return errorSyntax(USAGE_GUESTCONTROL, "No element(s) to check specified!");
2472
2473 if (strUsername.isEmpty())
2474 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
2475
2476 ComPtr<IGuestSession> pGuestSession;
2477 HRESULT hrc = pGuest->CreateSession(Bstr(strUsername).raw(),
2478 Bstr(strPassword).raw(),
2479 Bstr(strDomain).raw(),
2480 Bstr("VBoxManage Guest Control Stat").raw(),
2481 pGuestSession.asOutParam());
2482 if (FAILED(hrc))
2483 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
2484
2485 /*
2486 * Create the directories.
2487 */
2488 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2489 DESTDIRMAPITER it = mapObjs.begin();
2490 while (it != mapObjs.end())
2491 {
2492 if (fVerbose)
2493 RTPrintf("Checking for element \"%s\" ...\n", it->first.c_str());
2494
2495 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2496 hrc = pGuestSession->FileQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
2497 if (FAILED(hrc))
2498 hrc = pGuestSession->DirectoryQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
2499
2500 if (FAILED(hrc))
2501 {
2502 /* If there's at least one element which does not exist on the guest,
2503 * drop out with exitcode 1. */
2504 if (fVerbose)
2505 RTPrintf("Cannot stat for element \"%s\": No such element\n",
2506 it->first.c_str());
2507 rcExit = RTEXITCODE_FAILURE;
2508 }
2509 else
2510 {
2511 FsObjType_T objType;
2512 hrc = pFsObjInfo->COMGETTER(Type)(&objType);
2513 if (FAILED(hrc))
2514 return ctrlPrintError(pGuest, COM_IIDOF(IGuestFsObjInfo));
2515 switch (objType)
2516 {
2517 case FsObjType_File:
2518 RTPrintf("Element \"%s\" found: Is a file\n", it->first.c_str());
2519 break;
2520
2521 case FsObjType_Directory:
2522 RTPrintf("Element \"%s\" found: Is a directory\n", it->first.c_str());
2523 break;
2524
2525 case FsObjType_Symlink:
2526 RTPrintf("Element \"%s\" found: Is a symlink\n", it->first.c_str());
2527 break;
2528
2529 default:
2530 RTPrintf("Element \"%s\" found, type unknown (%ld)\n", it->first.c_str(), objType);
2531 break;
2532 }
2533
2534 /** @todo: Show more information about this element. */
2535 }
2536
2537 it++;
2538 }
2539
2540 if (!pGuestSession.isNull())
2541 pGuestSession->Close();
2542
2543 return rcExit;
2544}
2545
2546static int handleCtrlUpdateAdditions(ComPtr<IGuest> guest, HandlerArg *pArg)
2547{
2548 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2549
2550 /*
2551 * Check the syntax. We can deduce the correct syntax from the number of
2552 * arguments.
2553 */
2554 Utf8Str strSource;
2555 bool fVerbose = false;
2556 bool fWaitStartOnly = false;
2557
2558 static const RTGETOPTDEF s_aOptions[] =
2559 {
2560 { "--source", 's', RTGETOPT_REQ_STRING },
2561 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2562 { "--wait-start", 'w', RTGETOPT_REQ_NOTHING }
2563 };
2564
2565 int ch;
2566 RTGETOPTUNION ValueUnion;
2567 RTGETOPTSTATE GetState;
2568 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2569
2570 int vrc = VINF_SUCCESS;
2571 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2572 && RT_SUCCESS(vrc))
2573 {
2574 switch (ch)
2575 {
2576 case 's':
2577 strSource = ValueUnion.psz;
2578 break;
2579
2580 case 'v':
2581 fVerbose = true;
2582 break;
2583
2584 case 'w':
2585 fWaitStartOnly = true;
2586 break;
2587
2588 default:
2589 return RTGetOptPrintError(ch, &ValueUnion);
2590 }
2591 }
2592
2593 if (fVerbose)
2594 RTPrintf("Updating Guest Additions ...\n");
2595
2596 HRESULT rc = S_OK;
2597 while (strSource.isEmpty())
2598 {
2599 ComPtr<ISystemProperties> pProperties;
2600 CHECK_ERROR_BREAK(pArg->virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam()));
2601 Bstr strISO;
2602 CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam()));
2603 strSource = strISO;
2604 break;
2605 }
2606
2607 /* Determine source if not set yet. */
2608 if (strSource.isEmpty())
2609 {
2610 RTMsgError("No Guest Additions source found or specified, aborting\n");
2611 vrc = VERR_FILE_NOT_FOUND;
2612 }
2613 else if (!RTFileExists(strSource.c_str()))
2614 {
2615 RTMsgError("Source \"%s\" does not exist!\n", strSource.c_str());
2616 vrc = VERR_FILE_NOT_FOUND;
2617 }
2618
2619 if (RT_SUCCESS(vrc))
2620 {
2621 if (fVerbose)
2622 RTPrintf("Using source: %s\n", strSource.c_str());
2623
2624 com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags;
2625 if (fWaitStartOnly)
2626 {
2627 aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly);
2628 if (fVerbose)
2629 RTPrintf("Preparing and waiting for Guest Additions installer to start ...\n");
2630 }
2631
2632 ComPtr<IProgress> pProgress;
2633 CHECK_ERROR(guest, UpdateGuestAdditions(Bstr(strSource).raw(),
2634 /* Wait for whole update process to complete. */
2635 ComSafeArrayAsInParam(aUpdateFlags),
2636 pProgress.asOutParam()));
2637 if (FAILED(rc))
2638 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
2639 else
2640 {
2641 if (fVerbose)
2642 rc = showProgress(pProgress);
2643 else
2644 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
2645
2646 if (SUCCEEDED(rc))
2647 CHECK_PROGRESS_ERROR(pProgress, ("Guest additions update failed"));
2648 vrc = ctrlPrintProgressError(pProgress);
2649 if ( RT_SUCCESS(vrc)
2650 && fVerbose)
2651 {
2652 RTPrintf("Guest Additions update successful\n");
2653 }
2654 }
2655 }
2656
2657 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2658}
2659
2660/**
2661 * Access the guest control store.
2662 *
2663 * @returns program exit code.
2664 * @note see the command line API description for parameters
2665 */
2666int handleGuestControl(HandlerArg *pArg)
2667{
2668 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2669
2670#ifdef DEBUG_andy_disabled
2671 if (RT_FAILURE(tstTranslatePath()))
2672 return RTEXITCODE_FAILURE;
2673#endif
2674
2675 HandlerArg arg = *pArg;
2676 arg.argc = pArg->argc - 2; /* Skip VM name and sub command. */
2677 arg.argv = pArg->argv + 2; /* Same here. */
2678
2679 ComPtr<IGuest> guest;
2680 int vrc = ctrlInitVM(pArg, pArg->argv[0] /* VM Name */, &guest);
2681 if (RT_SUCCESS(vrc))
2682 {
2683 int rcExit;
2684 if (pArg->argc < 2)
2685 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
2686 else if ( !strcmp(pArg->argv[1], "exec")
2687 || !strcmp(pArg->argv[1], "execute"))
2688 rcExit = handleCtrlExecProgram(guest, &arg);
2689 else if (!strcmp(pArg->argv[1], "copyfrom"))
2690 rcExit = handleCtrlCopy(guest, &arg, false /* Guest to host */);
2691 else if ( !strcmp(pArg->argv[1], "copyto")
2692 || !strcmp(pArg->argv[1], "cp"))
2693 rcExit = handleCtrlCopy(guest, &arg, true /* Host to guest */);
2694 else if ( !strcmp(pArg->argv[1], "createdirectory")
2695 || !strcmp(pArg->argv[1], "createdir")
2696 || !strcmp(pArg->argv[1], "mkdir")
2697 || !strcmp(pArg->argv[1], "md"))
2698 rcExit = handleCtrlCreateDirectory(guest, &arg);
2699 else if ( !strcmp(pArg->argv[1], "createtemporary")
2700 || !strcmp(pArg->argv[1], "createtemp")
2701 || !strcmp(pArg->argv[1], "mktemp"))
2702 rcExit = handleCtrlCreateTemp(guest, &arg);
2703 else if ( !strcmp(pArg->argv[1], "stat"))
2704 rcExit = handleCtrlStat(guest, &arg);
2705 else if ( !strcmp(pArg->argv[1], "updateadditions")
2706 || !strcmp(pArg->argv[1], "updateadds"))
2707 rcExit = handleCtrlUpdateAdditions(guest, &arg);
2708 /** @todo Implement a "sessions list" command to list all opened
2709 * guest sessions along with their (friendly) names. */
2710 else
2711 rcExit = errorSyntax(USAGE_GUESTCONTROL, "Unknown sub command '%s' specified!", pArg->argv[1]);
2712
2713 ctrlUninitVM(pArg);
2714 return rcExit;
2715 }
2716 return RTEXITCODE_FAILURE;
2717}
2718
2719#endif /* !VBOX_ONLY_DOCS */
2720
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