VirtualBox

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

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

Guest Control: Respect detached processes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 93.5 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 43981 2012-11-28 11:44:04Z 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 if (FAILED(rc))
436 AssertMsgStmt(NULL, ("Could not lookup progress information\n"), vrc = VERR_COM_UNEXPECTED);
437
438 return vrc;
439}
440
441/**
442 * Un-initializes the VM after guest control usage.
443 */
444static void ctrlUninitVM(HandlerArg *pArg)
445{
446 AssertPtrReturnVoid(pArg);
447 if (pArg->session)
448 pArg->session->UnlockMachine();
449}
450
451/**
452 * Initializes the VM for IGuest operations.
453 *
454 * That is, checks whether it's up and running, if it can be locked (shared
455 * only) and returns a valid IGuest pointer on success.
456 *
457 * @return IPRT status code.
458 * @param pArg Our command line argument structure.
459 * @param pszNameOrId The VM's name or UUID.
460 * @param pGuest Where to return the IGuest interface pointer.
461 */
462static int ctrlInitVM(HandlerArg *pArg, const char *pszNameOrId, ComPtr<IGuest> *pGuest)
463{
464 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
465 AssertPtrReturn(pszNameOrId, VERR_INVALID_PARAMETER);
466
467 /* Lookup VM. */
468 ComPtr<IMachine> machine;
469 /* Assume it's an UUID. */
470 HRESULT rc;
471 CHECK_ERROR(pArg->virtualBox, FindMachine(Bstr(pszNameOrId).raw(),
472 machine.asOutParam()));
473 if (FAILED(rc))
474 return VERR_NOT_FOUND;
475
476 /* Machine is running? */
477 MachineState_T machineState;
478 CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
479 if (machineState != MachineState_Running)
480 {
481 RTMsgError("Machine \"%s\" is not running (currently %s)!\n",
482 pszNameOrId, machineStateToName(machineState, false));
483 return VERR_VM_INVALID_VM_STATE;
484 }
485
486 do
487 {
488 /* Open a session for the VM. */
489 CHECK_ERROR_BREAK(machine, LockMachine(pArg->session, LockType_Shared));
490 /* Get the associated console. */
491 ComPtr<IConsole> console;
492 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Console)(console.asOutParam()));
493 /* ... and session machine. */
494 ComPtr<IMachine> sessionMachine;
495 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
496 /* Get IGuest interface. */
497 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pGuest->asOutParam()));
498 } while (0);
499
500 if (FAILED(rc))
501 ctrlUninitVM(pArg);
502 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
503}
504
505/**
506 * Prints the desired guest output to a stream.
507 *
508 * @return IPRT status code.
509 * @param pProcess Pointer to appropriate process object.
510 * @param pStrmOutput Where to write the data.
511 * @param hStream Where to read the data from.
512 */
513static int ctrlExecPrintOutput(IProcess *pProcess, PRTSTREAM pStrmOutput,
514 ULONG uHandle)
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, 30 * 1000 /* 30s timeout. */,
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 if (fVerbose)
809 {
810 ULONG uPID = 0;
811 rc = pProcess->COMGETTER(PID)(&uPID);
812 if (FAILED(rc))
813 {
814 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
815
816 pGuestSession->Close();
817 return RTEXITCODE_FAILURE;
818 }
819
820 RTPrintf("Process '%s' (PID: %u) %s\n",
821 strCmd.c_str(), uPID,
822 fWaitForExit ? "started" : "started (detached)");
823
824 /* We're done here if we don't want to wait for termination. */
825 if (!fWaitForExit)
826 fCompleted = true;
827 }
828 break;
829 }
830 case ProcessWaitResult_StdOut:
831 fReadStdOut = true;
832 break;
833 case ProcessWaitResult_StdErr:
834 fReadStdErr = true;
835 break;
836 case ProcessWaitResult_Terminate:
837 /* Process terminated, we're done */
838 fCompleted = true;
839 break;
840 case ProcessWaitResult_WaitFlagNotSupported:
841 {
842 /* The guest does not support waiting for stdout/err, so
843 * yield to reduce the CPU load due to busy waiting. */
844 RTThreadYield(); /* Optional, don't check rc. */
845
846 /* Try both, stdout + stderr. */
847 fReadStdOut = fReadStdErr = true;
848 break;
849 }
850 default:
851 /* Ignore all other results, let the timeout expire */
852 break;
853 }
854
855 if (fReadStdOut) /* Do we need to fetch stdout data? */
856 {
857 vrc = ctrlExecPrintOutput(pProcess, g_pStdOut, 1 /* StdOut */);
858 fReadStdOut = false;
859 }
860
861 if (fReadStdErr) /* Do we need to fetch stdout data? */
862 {
863 vrc = ctrlExecPrintOutput(pProcess, g_pStdErr, 2 /* StdErr */);
864 fReadStdErr = false;
865 }
866
867 if (RT_FAILURE(vrc))
868 break;
869
870 } /* while */
871
872 /* Report status back to the user. */
873 if (fCompleted)
874 {
875 ProcessStatus_T status;
876 rc = pProcess->COMGETTER(Status)(&status);
877 if (FAILED(rc))
878 {
879 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
880
881 pGuestSession->Close();
882 return RTEXITCODE_FAILURE;
883 }
884 LONG exitCode;
885 rc = pProcess->COMGETTER(ExitCode)(&exitCode);
886 if (FAILED(rc))
887 {
888 ctrlPrintError(pProcess, COM_IIDOF(IProcess));
889
890 pGuestSession->Close();
891 return RTEXITCODE_FAILURE;
892 }
893 if (fVerbose)
894 RTPrintf("Exit code=%u (Status=%u [%s])\n", exitCode, status, ctrlExecProcessStatusToText(status));
895
896 pGuestSession->Close();
897 return ctrlExecProcessStatusToExitCode(status, exitCode);
898 }
899 else
900 {
901 if (fVerbose)
902 RTPrintf("Process execution aborted!\n");
903
904 pGuestSession->Close();
905 return EXITCODEEXEC_TERM_ABEND;
906 }
907
908 pGuestSession->Close();
909
910 return RT_FAILURE(vrc) || FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
911}
912
913/**
914 * Creates a copy context structure which then can be used with various
915 * guest control copy functions. Needs to be free'd with ctrlCopyContextFree().
916 *
917 * @return IPRT status code.
918 * @param pGuest Pointer to IGuest interface to use.
919 * @param fVerbose Flag indicating if we want to run in verbose mode.
920 * @param fDryRun Flag indicating if we want to run a dry run only.
921 * @param fHostToGuest Flag indicating if we want to copy from host to guest
922 * or vice versa.
923 * @param strUsername Username of account to use on the guest side.
924 * @param strPassword Password of account to use.
925 * @param strDomain Domain of account to use.
926 * @param strSessionName Session name (only for identification purposes).
927 * @param ppContext Pointer which receives the allocated copy context.
928 */
929static int ctrlCopyContextCreate(IGuest *pGuest, bool fVerbose, bool fDryRun,
930 bool fHostToGuest, const Utf8Str &strUsername,
931 const Utf8Str &strPassword, const Utf8Str &strDomain,
932 const Utf8Str &strSessionName,
933 PCOPYCONTEXT *ppContext)
934{
935 AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
936
937 PCOPYCONTEXT pContext = new COPYCONTEXT();
938 AssertPtrReturn(pContext, VERR_NO_MEMORY); /**< @todo r=klaus cannot happen with new */
939 ComPtr<IGuestSession> pGuestSession;
940 HRESULT rc = pGuest->CreateSession(Bstr(strUsername).raw(),
941 Bstr(strPassword).raw(),
942 Bstr(strDomain).raw(),
943 Bstr(strSessionName).raw(),
944 pGuestSession.asOutParam());
945 if (FAILED(rc))
946 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
947
948 pContext->fVerbose = fVerbose;
949 pContext->fDryRun = fDryRun;
950 pContext->fHostToGuest = fHostToGuest;
951 pContext->pGuestSession = pGuestSession;
952
953 *ppContext = pContext;
954
955 return VINF_SUCCESS;
956}
957
958/**
959 * Frees are previously allocated copy context structure.
960 *
961 * @param pContext Pointer to copy context to free.
962 */
963static void ctrlCopyContextFree(PCOPYCONTEXT pContext)
964{
965 if (pContext)
966 {
967 if (pContext->pGuestSession)
968 pContext->pGuestSession->Close();
969 delete pContext;
970 }
971}
972
973/**
974 * Translates a source path to a destination path (can be both sides,
975 * either host or guest). The source root is needed to determine the start
976 * of the relative source path which also needs to present in the destination
977 * path.
978 *
979 * @return IPRT status code.
980 * @param pszSourceRoot Source root path. No trailing directory slash!
981 * @param pszSource Actual source to transform. Must begin with
982 * the source root path!
983 * @param pszDest Destination path.
984 * @param ppszTranslated Pointer to the allocated, translated destination
985 * path. Must be free'd with RTStrFree().
986 */
987static int ctrlCopyTranslatePath(const char *pszSourceRoot, const char *pszSource,
988 const char *pszDest, char **ppszTranslated)
989{
990 AssertPtrReturn(pszSourceRoot, VERR_INVALID_POINTER);
991 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
992 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
993 AssertPtrReturn(ppszTranslated, VERR_INVALID_POINTER);
994#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} */
995 AssertReturn(RTPathStartsWith(pszSource, pszSourceRoot), VERR_INVALID_PARAMETER);
996#endif
997
998 /* Construct the relative dest destination path by "subtracting" the
999 * source from the source root, e.g.
1000 *
1001 * source root path = "e:\foo\", source = "e:\foo\bar"
1002 * dest = "d:\baz\"
1003 * translated = "d:\baz\bar\"
1004 */
1005 char szTranslated[RTPATH_MAX];
1006 size_t srcOff = strlen(pszSourceRoot);
1007 AssertReturn(srcOff, VERR_INVALID_PARAMETER);
1008
1009 char *pszDestPath = RTStrDup(pszDest);
1010 AssertPtrReturn(pszDestPath, VERR_NO_MEMORY);
1011
1012 int vrc;
1013 if (!RTPathFilename(pszDestPath))
1014 {
1015 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1016 pszDestPath, &pszSource[srcOff]);
1017 }
1018 else
1019 {
1020 char *pszDestFileName = RTStrDup(RTPathFilename(pszDestPath));
1021 if (pszDestFileName)
1022 {
1023 RTPathStripFilename(pszDestPath);
1024 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1025 pszDestPath, pszDestFileName);
1026 RTStrFree(pszDestFileName);
1027 }
1028 else
1029 vrc = VERR_NO_MEMORY;
1030 }
1031 RTStrFree(pszDestPath);
1032
1033 if (RT_SUCCESS(vrc))
1034 {
1035 *ppszTranslated = RTStrDup(szTranslated);
1036#if 0
1037 RTPrintf("Root: %s, Source: %s, Dest: %s, Translated: %s\n",
1038 pszSourceRoot, pszSource, pszDest, *ppszTranslated);
1039#endif
1040 }
1041 return vrc;
1042}
1043
1044#ifdef DEBUG_andy
1045static int tstTranslatePath()
1046{
1047 RTAssertSetMayPanic(false /* Do not freak out, please. */);
1048
1049 static struct
1050 {
1051 const char *pszSourceRoot;
1052 const char *pszSource;
1053 const char *pszDest;
1054 const char *pszTranslated;
1055 int iResult;
1056 } aTests[] =
1057 {
1058 /* Invalid stuff. */
1059 { NULL, NULL, NULL, NULL, VERR_INVALID_POINTER },
1060#ifdef RT_OS_WINDOWS
1061 /* Windows paths. */
1062 { "c:\\foo", "c:\\foo\\bar.txt", "c:\\test", "c:\\test\\bar.txt", VINF_SUCCESS },
1063 { "c:\\foo", "c:\\foo\\baz\\bar.txt", "c:\\test", "c:\\test\\baz\\bar.txt", VINF_SUCCESS },
1064#else /* RT_OS_WINDOWS */
1065 { "/home/test/foo", "/home/test/foo/bar.txt", "/opt/test", "/opt/test/bar.txt", VINF_SUCCESS },
1066 { "/home/test/foo", "/home/test/foo/baz/bar.txt", "/opt/test", "/opt/test/baz/bar.txt", VINF_SUCCESS },
1067#endif /* !RT_OS_WINDOWS */
1068 /* Mixed paths*/
1069 /** @todo */
1070 { NULL }
1071 };
1072
1073 size_t iTest = 0;
1074 for (iTest; iTest < RT_ELEMENTS(aTests); iTest++)
1075 {
1076 RTPrintf("=> Test %d\n", iTest);
1077 RTPrintf("\tSourceRoot=%s, Source=%s, Dest=%s\n",
1078 aTests[iTest].pszSourceRoot, aTests[iTest].pszSource, aTests[iTest].pszDest);
1079
1080 char *pszTranslated = NULL;
1081 int iResult = ctrlCopyTranslatePath(aTests[iTest].pszSourceRoot, aTests[iTest].pszSource,
1082 aTests[iTest].pszDest, &pszTranslated);
1083 if (iResult != aTests[iTest].iResult)
1084 {
1085 RTPrintf("\tReturned %Rrc, expected %Rrc\n",
1086 iResult, aTests[iTest].iResult);
1087 }
1088 else if ( pszTranslated
1089 && strcmp(pszTranslated, aTests[iTest].pszTranslated))
1090 {
1091 RTPrintf("\tReturned translated path %s, expected %s\n",
1092 pszTranslated, aTests[iTest].pszTranslated);
1093 }
1094
1095 if (pszTranslated)
1096 {
1097 RTPrintf("\tTranslated=%s\n", pszTranslated);
1098 RTStrFree(pszTranslated);
1099 }
1100 }
1101
1102 return VINF_SUCCESS; /* @todo */
1103}
1104#endif
1105
1106/**
1107 * Creates a directory on the destination, based on the current copy
1108 * context.
1109 *
1110 * @return IPRT status code.
1111 * @param pContext Pointer to current copy control context.
1112 * @param pszDir Directory to create.
1113 */
1114static int ctrlCopyDirCreate(PCOPYCONTEXT pContext, const char *pszDir)
1115{
1116 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1117 AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
1118
1119 bool fDirExists;
1120 int vrc = ctrlCopyDirExists(pContext, pContext->fHostToGuest, pszDir, &fDirExists);
1121 if ( RT_SUCCESS(vrc)
1122 && fDirExists)
1123 {
1124 if (pContext->fVerbose)
1125 RTPrintf("Directory \"%s\" already exists\n", pszDir);
1126 return VINF_SUCCESS;
1127 }
1128
1129 /* If querying for a directory existence fails there's no point of even trying
1130 * to create such a directory. */
1131 if (RT_FAILURE(vrc))
1132 return vrc;
1133
1134 if (pContext->fVerbose)
1135 RTPrintf("Creating directory \"%s\" ...\n", pszDir);
1136
1137 if (pContext->fDryRun)
1138 return VINF_SUCCESS;
1139
1140 if (pContext->fHostToGuest) /* We want to create directories on the guest. */
1141 {
1142 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
1143 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
1144 HRESULT rc = pContext->pGuestSession->DirectoryCreate(Bstr(pszDir).raw(),
1145 0700, ComSafeArrayAsInParam(dirCreateFlags));
1146 if (FAILED(rc))
1147 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1148 }
1149 else /* ... or on the host. */
1150 {
1151 vrc = RTDirCreateFullPath(pszDir, 0700);
1152 if (vrc == VERR_ALREADY_EXISTS)
1153 vrc = VINF_SUCCESS;
1154 }
1155 return vrc;
1156}
1157
1158/**
1159 * Checks whether a specific host/guest directory exists.
1160 *
1161 * @return IPRT status code.
1162 * @param pContext Pointer to current copy control context.
1163 * @param bGuest true if directory needs to be checked on the guest
1164 * or false if on the host.
1165 * @param pszDir Actual directory to check.
1166 * @param fExists Pointer which receives the result if the
1167 * given directory exists or not.
1168 */
1169static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest,
1170 const char *pszDir, bool *fExists)
1171{
1172 AssertPtrReturn(pContext, false);
1173 AssertPtrReturn(pszDir, false);
1174 AssertPtrReturn(fExists, false);
1175
1176 int vrc = VINF_SUCCESS;
1177 if (bGuest)
1178 {
1179 BOOL fDirExists = FALSE;
1180 HRESULT rc = pContext->pGuestSession->DirectoryExists(Bstr(pszDir).raw(), &fDirExists);
1181 if (FAILED(rc))
1182 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1183 else
1184 *fExists = fDirExists ? true : false;
1185 }
1186 else
1187 *fExists = RTDirExists(pszDir);
1188 return vrc;
1189}
1190
1191/**
1192 * Checks whether a specific directory exists on the destination, based
1193 * on the current copy context.
1194 *
1195 * @return IPRT status code.
1196 * @param pContext Pointer to current copy control context.
1197 * @param pszDir Actual directory to check.
1198 * @param fExists Pointer which receives the result if the
1199 * given directory exists or not.
1200 */
1201static int ctrlCopyDirExistsOnDest(PCOPYCONTEXT pContext, const char *pszDir,
1202 bool *fExists)
1203{
1204 return ctrlCopyDirExists(pContext, pContext->fHostToGuest,
1205 pszDir, fExists);
1206}
1207
1208/**
1209 * Checks whether a specific directory exists on the source, based
1210 * on the current copy context.
1211 *
1212 * @return IPRT status code.
1213 * @param pContext Pointer to current copy control context.
1214 * @param pszDir Actual directory to check.
1215 * @param fExists Pointer which receives the result if the
1216 * given directory exists or not.
1217 */
1218static int ctrlCopyDirExistsOnSource(PCOPYCONTEXT pContext, const char *pszDir,
1219 bool *fExists)
1220{
1221 return ctrlCopyDirExists(pContext, !pContext->fHostToGuest,
1222 pszDir, fExists);
1223}
1224
1225/**
1226 * Checks whether a specific host/guest file exists.
1227 *
1228 * @return IPRT status code.
1229 * @param pContext Pointer to current copy control context.
1230 * @param bGuest true if file needs to be checked on the guest
1231 * or false if on the host.
1232 * @param pszFile Actual file to check.
1233 * @param fExists Pointer which receives the result if the
1234 * given file exists or not.
1235 */
1236static int ctrlCopyFileExists(PCOPYCONTEXT pContext, bool bOnGuest,
1237 const char *pszFile, bool *fExists)
1238{
1239 AssertPtrReturn(pContext, false);
1240 AssertPtrReturn(pszFile, false);
1241 AssertPtrReturn(fExists, false);
1242
1243 int vrc = VINF_SUCCESS;
1244 if (bOnGuest)
1245 {
1246 BOOL fFileExists = FALSE;
1247 HRESULT rc = pContext->pGuestSession->FileExists(Bstr(pszFile).raw(), &fFileExists);
1248 if (FAILED(rc))
1249 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1250 else
1251 *fExists = fFileExists ? true : false;
1252 }
1253 else
1254 *fExists = RTFileExists(pszFile);
1255 return vrc;
1256}
1257
1258/**
1259 * Checks whether a specific file exists on the destination, based on the
1260 * current copy context.
1261 *
1262 * @return IPRT status code.
1263 * @param pContext Pointer to current copy control context.
1264 * @param pszFile Actual file to check.
1265 * @param fExists Pointer which receives the result if the
1266 * given file exists or not.
1267 */
1268static int ctrlCopyFileExistsOnDest(PCOPYCONTEXT pContext, const char *pszFile,
1269 bool *fExists)
1270{
1271 return ctrlCopyFileExists(pContext, pContext->fHostToGuest,
1272 pszFile, fExists);
1273}
1274
1275/**
1276 * Checks whether a specific file exists on the source, based on the
1277 * current copy context.
1278 *
1279 * @return IPRT status code.
1280 * @param pContext Pointer to current copy control context.
1281 * @param pszFile Actual file to check.
1282 * @param fExists Pointer which receives the result if the
1283 * given file exists or not.
1284 */
1285static int ctrlCopyFileExistsOnSource(PCOPYCONTEXT pContext, const char *pszFile,
1286 bool *fExists)
1287{
1288 return ctrlCopyFileExists(pContext, !pContext->fHostToGuest,
1289 pszFile, fExists);
1290}
1291
1292/**
1293 * Copies a source file to the destination.
1294 *
1295 * @return IPRT status code.
1296 * @param pContext Pointer to current copy control context.
1297 * @param pszFileSource Source file to copy to the destination.
1298 * @param pszFileDest Name of copied file on the destination.
1299 * @param fFlags Copy flags. No supported at the moment and needs
1300 * to be set to 0.
1301 */
1302static int ctrlCopyFileToDest(PCOPYCONTEXT pContext, const char *pszFileSource,
1303 const char *pszFileDest, uint32_t fFlags)
1304{
1305 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1306 AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
1307 AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
1308 AssertReturn(!fFlags, VERR_INVALID_POINTER); /* No flags supported yet. */
1309
1310 if (pContext->fVerbose)
1311 RTPrintf("Copying \"%s\" to \"%s\" ...\n",
1312 pszFileSource, pszFileDest);
1313
1314 if (pContext->fDryRun)
1315 return VINF_SUCCESS;
1316
1317 int vrc = VINF_SUCCESS;
1318 ComPtr<IProgress> pProgress;
1319 HRESULT rc;
1320 if (pContext->fHostToGuest)
1321 {
1322 SafeArray<CopyFileFlag_T> copyFlags;
1323 rc = pContext->pGuestSession->CopyTo(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1324 ComSafeArrayAsInParam(copyFlags),
1325
1326 pProgress.asOutParam());
1327 }
1328 else
1329 {
1330 SafeArray<CopyFileFlag_T> copyFlags;
1331 rc = pContext->pGuestSession->CopyFrom(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1332 ComSafeArrayAsInParam(copyFlags),
1333 pProgress.asOutParam());
1334 }
1335
1336 if (FAILED(rc))
1337 {
1338 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1339 }
1340 else
1341 {
1342 if (pContext->fVerbose)
1343 rc = showProgress(pProgress);
1344 else
1345 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
1346 if (SUCCEEDED(rc))
1347 CHECK_PROGRESS_ERROR(pProgress, ("File copy failed"));
1348 vrc = ctrlPrintProgressError(pProgress);
1349 }
1350
1351 return vrc;
1352}
1353
1354/**
1355 * Copys a directory (tree) from host to the guest.
1356 *
1357 * @return IPRT status code.
1358 * @param pContext Pointer to current copy control context.
1359 * @param pszSource Source directory on the host to copy to the guest.
1360 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1361 * @param pszDest Destination directory on the guest.
1362 * @param fFlags Copy flags, such as recursive copying.
1363 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1364 * is needed for recursion.
1365 */
1366static int ctrlCopyDirToGuest(PCOPYCONTEXT pContext,
1367 const char *pszSource, const char *pszFilter,
1368 const char *pszDest, uint32_t fFlags,
1369 const char *pszSubDir /* For recursion. */)
1370{
1371 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1372 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1373 /* Filter is optional. */
1374 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1375 /* Sub directory is optional. */
1376
1377 /*
1378 * Construct current path.
1379 */
1380 char szCurDir[RTPATH_MAX];
1381 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
1382 if (RT_SUCCESS(vrc) && pszSubDir)
1383 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
1384
1385 if (pContext->fVerbose)
1386 RTPrintf("Processing host directory: %s\n", szCurDir);
1387
1388 /* Flag indicating whether the current directory was created on the
1389 * target or not. */
1390 bool fDirCreated = false;
1391
1392 /*
1393 * Open directory without a filter - RTDirOpenFiltered unfortunately
1394 * cannot handle sub directories so we have to do the filtering ourselves.
1395 */
1396 PRTDIR pDir = NULL;
1397 if (RT_SUCCESS(vrc))
1398 {
1399 vrc = RTDirOpen(&pDir, szCurDir);
1400 if (RT_FAILURE(vrc))
1401 pDir = NULL;
1402 }
1403 if (RT_SUCCESS(vrc))
1404 {
1405 /*
1406 * Enumerate the directory tree.
1407 */
1408 while (RT_SUCCESS(vrc))
1409 {
1410 RTDIRENTRY DirEntry;
1411 vrc = RTDirRead(pDir, &DirEntry, NULL);
1412 if (RT_FAILURE(vrc))
1413 {
1414 if (vrc == VERR_NO_MORE_FILES)
1415 vrc = VINF_SUCCESS;
1416 break;
1417 }
1418 switch (DirEntry.enmType)
1419 {
1420 case RTDIRENTRYTYPE_DIRECTORY:
1421 {
1422 /* Skip "." and ".." entries. */
1423 if ( !strcmp(DirEntry.szName, ".")
1424 || !strcmp(DirEntry.szName, ".."))
1425 break;
1426
1427 if (pContext->fVerbose)
1428 RTPrintf("Directory: %s\n", DirEntry.szName);
1429
1430 if (fFlags & CopyFileFlag_Recursive)
1431 {
1432 char *pszNewSub = NULL;
1433 if (pszSubDir)
1434 pszNewSub = RTPathJoinA(pszSubDir, DirEntry.szName);
1435 else
1436 {
1437 pszNewSub = RTStrDup(DirEntry.szName);
1438 RTPathStripTrailingSlash(pszNewSub);
1439 }
1440
1441 if (pszNewSub)
1442 {
1443 vrc = ctrlCopyDirToGuest(pContext,
1444 pszSource, pszFilter,
1445 pszDest, fFlags, pszNewSub);
1446 RTStrFree(pszNewSub);
1447 }
1448 else
1449 vrc = VERR_NO_MEMORY;
1450 }
1451 break;
1452 }
1453
1454 case RTDIRENTRYTYPE_SYMLINK:
1455 if ( (fFlags & CopyFileFlag_Recursive)
1456 && (fFlags & CopyFileFlag_FollowLinks))
1457 {
1458 /* Fall through to next case is intentional. */
1459 }
1460 else
1461 break;
1462
1463 case RTDIRENTRYTYPE_FILE:
1464 {
1465 if ( pszFilter
1466 && !RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
1467 {
1468 break; /* Filter does not match. */
1469 }
1470
1471 if (pContext->fVerbose)
1472 RTPrintf("File: %s\n", DirEntry.szName);
1473
1474 if (!fDirCreated)
1475 {
1476 char *pszDestDir;
1477 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
1478 pszDest, &pszDestDir);
1479 if (RT_SUCCESS(vrc))
1480 {
1481 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1482 RTStrFree(pszDestDir);
1483
1484 fDirCreated = true;
1485 }
1486 }
1487
1488 if (RT_SUCCESS(vrc))
1489 {
1490 char *pszFileSource = RTPathJoinA(szCurDir, DirEntry.szName);
1491 if (pszFileSource)
1492 {
1493 char *pszFileDest;
1494 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
1495 pszDest, &pszFileDest);
1496 if (RT_SUCCESS(vrc))
1497 {
1498 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
1499 pszFileDest, 0 /* Flags */);
1500 RTStrFree(pszFileDest);
1501 }
1502 RTStrFree(pszFileSource);
1503 }
1504 }
1505 break;
1506 }
1507
1508 default:
1509 break;
1510 }
1511 if (RT_FAILURE(vrc))
1512 break;
1513 }
1514
1515 RTDirClose(pDir);
1516 }
1517 return vrc;
1518}
1519
1520/**
1521 * Copys a directory (tree) from guest to the host.
1522 *
1523 * @return IPRT status code.
1524 * @param pContext Pointer to current copy control context.
1525 * @param pszSource Source directory on the guest to copy to the host.
1526 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1527 * @param pszDest Destination directory on the host.
1528 * @param fFlags Copy flags, such as recursive copying.
1529 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1530 * is needed for recursion.
1531 */
1532static int ctrlCopyDirToHost(PCOPYCONTEXT pContext,
1533 const char *pszSource, const char *pszFilter,
1534 const char *pszDest, uint32_t fFlags,
1535 const char *pszSubDir /* For recursion. */)
1536{
1537 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1538 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1539 /* Filter is optional. */
1540 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1541 /* Sub directory is optional. */
1542
1543 /*
1544 * Construct current path.
1545 */
1546 char szCurDir[RTPATH_MAX];
1547 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
1548 if (RT_SUCCESS(vrc) && pszSubDir)
1549 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
1550
1551 if (RT_FAILURE(vrc))
1552 return vrc;
1553
1554 if (pContext->fVerbose)
1555 RTPrintf("Processing guest directory: %s\n", szCurDir);
1556
1557 /* Flag indicating whether the current directory was created on the
1558 * target or not. */
1559 bool fDirCreated = false;
1560 SafeArray<DirectoryOpenFlag_T> dirOpenFlags; /* No flags supported yet. */
1561 ComPtr<IGuestDirectory> pDirectory;
1562 HRESULT rc = pContext->pGuestSession->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(),
1563 ComSafeArrayAsInParam(dirOpenFlags),
1564 pDirectory.asOutParam());
1565 if (FAILED(rc))
1566 return ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1567 ComPtr<IFsObjInfo> dirEntry;
1568 while (true)
1569 {
1570 rc = pDirectory->Read(dirEntry.asOutParam());
1571 if (FAILED(rc))
1572 break;
1573
1574 FsObjType_T enmType;
1575 dirEntry->COMGETTER(Type)(&enmType);
1576
1577 Bstr strName;
1578 dirEntry->COMGETTER(Name)(strName.asOutParam());
1579
1580 switch (enmType)
1581 {
1582 case FsObjType_Directory:
1583 {
1584 Assert(!strName.isEmpty());
1585
1586 /* Skip "." and ".." entries. */
1587 if ( !strName.compare(Bstr("."))
1588 || !strName.compare(Bstr("..")))
1589 break;
1590
1591 if (pContext->fVerbose)
1592 {
1593 Utf8Str strDir(strName);
1594 RTPrintf("Directory: %s\n", strDir.c_str());
1595 }
1596
1597 if (fFlags & CopyFileFlag_Recursive)
1598 {
1599 Utf8Str strDir(strName);
1600 char *pszNewSub = NULL;
1601 if (pszSubDir)
1602 pszNewSub = RTPathJoinA(pszSubDir, strDir.c_str());
1603 else
1604 {
1605 pszNewSub = RTStrDup(strDir.c_str());
1606 RTPathStripTrailingSlash(pszNewSub);
1607 }
1608 if (pszNewSub)
1609 {
1610 vrc = ctrlCopyDirToHost(pContext,
1611 pszSource, pszFilter,
1612 pszDest, fFlags, pszNewSub);
1613 RTStrFree(pszNewSub);
1614 }
1615 else
1616 vrc = VERR_NO_MEMORY;
1617 }
1618 break;
1619 }
1620
1621 case FsObjType_Symlink:
1622 if ( (fFlags & CopyFileFlag_Recursive)
1623 && (fFlags & CopyFileFlag_FollowLinks))
1624 {
1625 /* Fall through to next case is intentional. */
1626 }
1627 else
1628 break;
1629
1630 case FsObjType_File:
1631 {
1632 Assert(!strName.isEmpty());
1633
1634 Utf8Str strFile(strName);
1635 if ( pszFilter
1636 && !RTStrSimplePatternMatch(pszFilter, strFile.c_str()))
1637 {
1638 break; /* Filter does not match. */
1639 }
1640
1641 if (pContext->fVerbose)
1642 RTPrintf("File: %s\n", strFile.c_str());
1643
1644 if (!fDirCreated)
1645 {
1646 char *pszDestDir;
1647 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
1648 pszDest, &pszDestDir);
1649 if (RT_SUCCESS(vrc))
1650 {
1651 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1652 RTStrFree(pszDestDir);
1653
1654 fDirCreated = true;
1655 }
1656 }
1657
1658 if (RT_SUCCESS(vrc))
1659 {
1660 char *pszFileSource = RTPathJoinA(szCurDir, strFile.c_str());
1661 if (pszFileSource)
1662 {
1663 char *pszFileDest;
1664 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
1665 pszDest, &pszFileDest);
1666 if (RT_SUCCESS(vrc))
1667 {
1668 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
1669 pszFileDest, 0 /* Flags */);
1670 RTStrFree(pszFileDest);
1671 }
1672 RTStrFree(pszFileSource);
1673 }
1674 else
1675 vrc = VERR_NO_MEMORY;
1676 }
1677 break;
1678 }
1679
1680 default:
1681 RTPrintf("Warning: Directory entry of type %ld not handled, skipping ...\n",
1682 enmType);
1683 break;
1684 }
1685
1686 if (RT_FAILURE(vrc))
1687 break;
1688 }
1689
1690 if (RT_UNLIKELY(FAILED(rc)))
1691 {
1692 switch (rc)
1693 {
1694 case E_ABORT: /* No more directory entries left to process. */
1695 break;
1696
1697 case VBOX_E_FILE_ERROR: /* Current entry cannot be accessed to
1698 to missing rights. */
1699 {
1700 RTPrintf("Warning: Cannot access \"%s\", skipping ...\n",
1701 szCurDir);
1702 break;
1703 }
1704
1705 default:
1706 vrc = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
1707 break;
1708 }
1709 }
1710
1711 HRESULT rc2 = pDirectory->Close();
1712 if (FAILED(rc2))
1713 {
1714 int vrc2 = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
1715 if (RT_SUCCESS(vrc))
1716 vrc = vrc2;
1717 }
1718 else if (SUCCEEDED(rc))
1719 rc = rc2;
1720
1721 return vrc;
1722}
1723
1724/**
1725 * Copys a directory (tree) to the destination, based on the current copy
1726 * context.
1727 *
1728 * @return IPRT status code.
1729 * @param pContext Pointer to current copy control context.
1730 * @param pszSource Source directory to copy to the destination.
1731 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1732 * @param pszDest Destination directory where to copy in the source
1733 * source directory.
1734 * @param fFlags Copy flags, such as recursive copying.
1735 */
1736static int ctrlCopyDirToDest(PCOPYCONTEXT pContext,
1737 const char *pszSource, const char *pszFilter,
1738 const char *pszDest, uint32_t fFlags)
1739{
1740 if (pContext->fHostToGuest)
1741 return ctrlCopyDirToGuest(pContext, pszSource, pszFilter,
1742 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
1743 return ctrlCopyDirToHost(pContext, pszSource, pszFilter,
1744 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
1745}
1746
1747/**
1748 * Creates a source root by stripping file names or filters of the specified source.
1749 *
1750 * @return IPRT status code.
1751 * @param pszSource Source to create source root for.
1752 * @param ppszSourceRoot Pointer that receives the allocated source root. Needs
1753 * to be free'd with ctrlCopyFreeSourceRoot().
1754 */
1755static int ctrlCopyCreateSourceRoot(const char *pszSource, char **ppszSourceRoot)
1756{
1757 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1758 AssertPtrReturn(ppszSourceRoot, VERR_INVALID_POINTER);
1759
1760 char *pszNewRoot = RTStrDup(pszSource);
1761 AssertPtrReturn(pszNewRoot, VERR_NO_MEMORY);
1762
1763 size_t lenRoot = strlen(pszNewRoot);
1764 if ( lenRoot
1765 && pszNewRoot[lenRoot - 1] == '/'
1766 && pszNewRoot[lenRoot - 1] == '\\'
1767 && lenRoot > 1
1768 && pszNewRoot[lenRoot - 2] == '/'
1769 && pszNewRoot[lenRoot - 2] == '\\')
1770 {
1771 *ppszSourceRoot = pszNewRoot;
1772 if (lenRoot > 1)
1773 *ppszSourceRoot[lenRoot - 2] = '\0';
1774 *ppszSourceRoot[lenRoot - 1] = '\0';
1775 }
1776 else
1777 {
1778 /* If there's anything (like a file name or a filter),
1779 * strip it! */
1780 RTPathStripFilename(pszNewRoot);
1781 *ppszSourceRoot = pszNewRoot;
1782 }
1783
1784 return VINF_SUCCESS;
1785}
1786
1787/**
1788 * Frees a previously allocated source root.
1789 *
1790 * @return IPRT status code.
1791 * @param pszSourceRoot Source root to free.
1792 */
1793static void ctrlCopyFreeSourceRoot(char *pszSourceRoot)
1794{
1795 RTStrFree(pszSourceRoot);
1796}
1797
1798static int handleCtrlCopy(ComPtr<IGuest> guest, HandlerArg *pArg,
1799 bool fHostToGuest)
1800{
1801 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1802
1803 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
1804 * is much better (partly because it is much simpler of course). The main
1805 * arguments against this is that (1) all but two options conflicts with
1806 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
1807 * done windows CMD style (though not in a 100% compatible way), and (3)
1808 * that only one source is allowed - efficiently sabotaging default
1809 * wildcard expansion by a unix shell. The best solution here would be
1810 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
1811
1812 /*
1813 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
1814 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
1815 * does in here.
1816 */
1817 static const RTGETOPTDEF s_aOptions[] =
1818 {
1819 { "--dryrun", GETOPTDEF_COPY_DRYRUN, RTGETOPT_REQ_NOTHING },
1820 { "--follow", GETOPTDEF_COPY_FOLLOW, RTGETOPT_REQ_NOTHING },
1821 { "--username", 'u', RTGETOPT_REQ_STRING },
1822 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
1823 { "--password", GETOPTDEF_COPY_PASSWORD, RTGETOPT_REQ_STRING },
1824 { "--domain", 'd', RTGETOPT_REQ_STRING },
1825 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1826 { "--target-directory", GETOPTDEF_COPY_TARGETDIR, RTGETOPT_REQ_STRING },
1827 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1828 };
1829
1830 int ch;
1831 RTGETOPTUNION ValueUnion;
1832 RTGETOPTSTATE GetState;
1833 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
1834 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1835
1836 Utf8Str strSource;
1837 Utf8Str strDest;
1838 Utf8Str strUsername;
1839 Utf8Str strPassword;
1840 Utf8Str strDomain;
1841 uint32_t fFlags = CopyFileFlag_None;
1842 bool fVerbose = false;
1843 bool fCopyRecursive = false;
1844 bool fDryRun = false;
1845
1846 SOURCEVEC vecSources;
1847
1848 int vrc = VINF_SUCCESS;
1849 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1850 {
1851 /* For options that require an argument, ValueUnion has received the value. */
1852 switch (ch)
1853 {
1854 case GETOPTDEF_COPY_DRYRUN:
1855 fDryRun = true;
1856 break;
1857
1858 case GETOPTDEF_COPY_FOLLOW:
1859 fFlags |= CopyFileFlag_FollowLinks;
1860 break;
1861
1862 case 'u': /* User name */
1863 strUsername = ValueUnion.psz;
1864 break;
1865
1866 case GETOPTDEF_COPY_PASSWORD: /* Password */
1867 strPassword = ValueUnion.psz;
1868 break;
1869
1870 case 'p': /* Password file */
1871 {
1872 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
1873 if (rcExit != RTEXITCODE_SUCCESS)
1874 return rcExit;
1875 break;
1876 }
1877
1878 case 'd': /* domain */
1879 strDomain = ValueUnion.psz;
1880 break;
1881
1882 case 'R': /* Recursive processing */
1883 fFlags |= CopyFileFlag_Recursive;
1884 break;
1885
1886 case GETOPTDEF_COPY_TARGETDIR:
1887 strDest = ValueUnion.psz;
1888 break;
1889
1890 case 'v': /* Verbose */
1891 fVerbose = true;
1892 break;
1893
1894 case VINF_GETOPT_NOT_OPTION:
1895 {
1896 /* Last argument and no destination specified with
1897 * --target-directory yet? Then use the current
1898 * (= last) argument as destination. */
1899 if ( pArg->argc == GetState.iNext
1900 && strDest.isEmpty())
1901 {
1902 strDest = ValueUnion.psz;
1903 }
1904 else
1905 {
1906 /* Save the source directory. */
1907 vecSources.push_back(SOURCEFILEENTRY(ValueUnion.psz));
1908 }
1909 break;
1910 }
1911
1912 default:
1913 return RTGetOptPrintError(ch, &ValueUnion);
1914 }
1915 }
1916
1917 if (!vecSources.size())
1918 return errorSyntax(USAGE_GUESTCONTROL,
1919 "No source(s) specified!");
1920
1921 if (strDest.isEmpty())
1922 return errorSyntax(USAGE_GUESTCONTROL,
1923 "No destination specified!");
1924
1925 if (strUsername.isEmpty())
1926 return errorSyntax(USAGE_GUESTCONTROL,
1927 "No user name specified!");
1928
1929 /*
1930 * Done parsing arguments, do some more preparations.
1931 */
1932 if (fVerbose)
1933 {
1934 if (fHostToGuest)
1935 RTPrintf("Copying from host to guest ...\n");
1936 else
1937 RTPrintf("Copying from guest to host ...\n");
1938 if (fDryRun)
1939 RTPrintf("Dry run - no files copied!\n");
1940 }
1941
1942 /* Create the copy context -- it contains all information
1943 * the routines need to know when handling the actual copying. */
1944 PCOPYCONTEXT pContext = NULL;
1945 vrc = ctrlCopyContextCreate(guest, fVerbose, fDryRun, fHostToGuest,
1946 strUsername, strPassword, strDomain,
1947 "VBoxManage Guest Control Copy", &pContext);
1948 if (RT_FAILURE(vrc))
1949 {
1950 RTMsgError("Unable to create copy context, rc=%Rrc\n", vrc);
1951 return RTEXITCODE_FAILURE;
1952 }
1953
1954 /* If the destination is a path, (try to) create it. */
1955 const char *pszDest = strDest.c_str();
1956/** @todo r=bird: RTPathFilename and RTPathStripFilename won't work
1957 * correctly on non-windows hosts when the guest is from the DOS world (Windows,
1958 * OS/2, DOS). The host doesn't know about DOS slashes, only UNIX slashes and
1959 * will get the wrong idea if some dilligent user does:
1960 *
1961 * copyto myfile.txt 'C:\guestfile.txt'
1962 * or
1963 * copyto myfile.txt 'D:guestfile.txt'
1964 *
1965 * @bugref{6344}
1966 */
1967 if (!RTPathFilename(pszDest))
1968 {
1969 vrc = ctrlCopyDirCreate(pContext, pszDest);
1970 }
1971 else
1972 {
1973 /* We assume we got a file name as destination -- so strip
1974 * the actual file name and make sure the appropriate
1975 * directories get created. */
1976 char *pszDestDir = RTStrDup(pszDest);
1977 AssertPtr(pszDestDir);
1978 RTPathStripFilename(pszDestDir);
1979 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1980 RTStrFree(pszDestDir);
1981 }
1982
1983 if (RT_SUCCESS(vrc))
1984 {
1985 /*
1986 * Here starts the actual fun!
1987 * Handle all given sources one by one.
1988 */
1989 for (unsigned long s = 0; s < vecSources.size(); s++)
1990 {
1991 char *pszSource = RTStrDup(vecSources[s].GetSource());
1992 AssertPtrBreakStmt(pszSource, vrc = VERR_NO_MEMORY);
1993 const char *pszFilter = vecSources[s].GetFilter();
1994 if (!strlen(pszFilter))
1995 pszFilter = NULL; /* If empty filter then there's no filter :-) */
1996
1997 char *pszSourceRoot;
1998 vrc = ctrlCopyCreateSourceRoot(pszSource, &pszSourceRoot);
1999 if (RT_FAILURE(vrc))
2000 {
2001 RTMsgError("Unable to create source root, rc=%Rrc\n", vrc);
2002 break;
2003 }
2004
2005 if (fVerbose)
2006 RTPrintf("Source: %s\n", pszSource);
2007
2008 /** @todo Files with filter?? */
2009 bool fSourceIsFile = false;
2010 bool fSourceExists;
2011
2012 size_t cchSource = strlen(pszSource);
2013 if ( cchSource > 1
2014 && RTPATH_IS_SLASH(pszSource[cchSource - 1]))
2015 {
2016 if (pszFilter) /* Directory with filter (so use source root w/o the actual filter). */
2017 vrc = ctrlCopyDirExistsOnSource(pContext, pszSourceRoot, &fSourceExists);
2018 else /* Regular directory without filter. */
2019 vrc = ctrlCopyDirExistsOnSource(pContext, pszSource, &fSourceExists);
2020
2021 if (fSourceExists)
2022 {
2023 /* Strip trailing slash from our source element so that other functions
2024 * can use this stuff properly (like RTPathStartsWith). */
2025 RTPathStripTrailingSlash(pszSource);
2026 }
2027 }
2028 else
2029 {
2030 vrc = ctrlCopyFileExistsOnSource(pContext, pszSource, &fSourceExists);
2031 if ( RT_SUCCESS(vrc)
2032 && fSourceExists)
2033 {
2034 fSourceIsFile = true;
2035 }
2036 }
2037
2038 if ( RT_SUCCESS(vrc)
2039 && fSourceExists)
2040 {
2041 if (fSourceIsFile)
2042 {
2043 /* Single file. */
2044 char *pszDestFile;
2045 vrc = ctrlCopyTranslatePath(pszSourceRoot, pszSource,
2046 strDest.c_str(), &pszDestFile);
2047 if (RT_SUCCESS(vrc))
2048 {
2049 vrc = ctrlCopyFileToDest(pContext, pszSource,
2050 pszDestFile, 0 /* Flags */);
2051 RTStrFree(pszDestFile);
2052 }
2053 else
2054 RTMsgError("Unable to translate path for \"%s\", rc=%Rrc\n",
2055 pszSource, vrc);
2056 }
2057 else
2058 {
2059 /* Directory (with filter?). */
2060 vrc = ctrlCopyDirToDest(pContext, pszSource, pszFilter,
2061 strDest.c_str(), fFlags);
2062 }
2063 }
2064
2065 ctrlCopyFreeSourceRoot(pszSourceRoot);
2066
2067 if ( RT_SUCCESS(vrc)
2068 && !fSourceExists)
2069 {
2070 RTMsgError("Warning: Source \"%s\" does not exist, skipping!\n",
2071 pszSource);
2072 RTStrFree(pszSource);
2073 continue;
2074 }
2075 else if (RT_FAILURE(vrc))
2076 {
2077 RTMsgError("Error processing \"%s\", rc=%Rrc\n",
2078 pszSource, vrc);
2079 RTStrFree(pszSource);
2080 break;
2081 }
2082
2083 RTStrFree(pszSource);
2084 }
2085 }
2086
2087 ctrlCopyContextFree(pContext);
2088
2089 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2090}
2091
2092static int handleCtrlCreateDirectory(ComPtr<IGuest> pGuest, HandlerArg *pArg)
2093{
2094 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2095
2096 /*
2097 * Parse arguments.
2098 *
2099 * Note! No direct returns here, everyone must go thru the cleanup at the
2100 * end of this function.
2101 */
2102 static const RTGETOPTDEF s_aOptions[] =
2103 {
2104 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2105 { "--parents", 'P', RTGETOPT_REQ_NOTHING },
2106 { "--username", 'u', RTGETOPT_REQ_STRING },
2107 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
2108 { "--password", GETOPTDEF_MKDIR_PASSWORD, RTGETOPT_REQ_STRING },
2109 { "--domain", 'd', RTGETOPT_REQ_STRING },
2110 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2111 };
2112
2113 int ch;
2114 RTGETOPTUNION ValueUnion;
2115 RTGETOPTSTATE GetState;
2116 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
2117 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2118
2119 Utf8Str strUsername;
2120 Utf8Str strPassword;
2121 Utf8Str strDomain;
2122 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
2123 uint32_t fDirMode = 0; /* Default mode. */
2124 bool fVerbose = false;
2125
2126 DESTDIRMAP mapDirs;
2127
2128 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2129 {
2130 /* For options that require an argument, ValueUnion has received the value. */
2131 switch (ch)
2132 {
2133 case 'm': /* Mode */
2134 fDirMode = ValueUnion.u32;
2135 break;
2136
2137 case 'P': /* Create parents */
2138 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
2139 break;
2140
2141 case 'u': /* User name */
2142 strUsername = ValueUnion.psz;
2143 break;
2144
2145 case GETOPTDEF_MKDIR_PASSWORD: /* Password */
2146 strPassword = ValueUnion.psz;
2147 break;
2148
2149 case 'p': /* Password file */
2150 {
2151 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2152 if (rcExit != RTEXITCODE_SUCCESS)
2153 return rcExit;
2154 break;
2155 }
2156
2157 case 'd': /* domain */
2158 strDomain = ValueUnion.psz;
2159 break;
2160
2161 case 'v': /* Verbose */
2162 fVerbose = true;
2163 break;
2164
2165 case VINF_GETOPT_NOT_OPTION:
2166 {
2167 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2168 break;
2169 }
2170
2171 default:
2172 return RTGetOptPrintError(ch, &ValueUnion);
2173 }
2174 }
2175
2176 uint32_t cDirs = mapDirs.size();
2177 if (!cDirs)
2178 return errorSyntax(USAGE_GUESTCONTROL, "No directory to create specified!");
2179
2180 if (strUsername.isEmpty())
2181 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
2182
2183 /*
2184 * Create the directories.
2185 */
2186 HRESULT hrc = S_OK;
2187 if (fVerbose && cDirs)
2188 RTPrintf("Creating %u directories ...\n", cDirs);
2189
2190 ComPtr<IGuestSession> pGuestSession;
2191 hrc = pGuest->CreateSession(Bstr(strUsername).raw(),
2192 Bstr(strPassword).raw(),
2193 Bstr(strDomain).raw(),
2194 Bstr("VBoxManage Guest Control MkDir").raw(),
2195 pGuestSession.asOutParam());
2196 if (FAILED(hrc))
2197 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
2198
2199 DESTDIRMAPITER it = mapDirs.begin();
2200 while (it != mapDirs.end())
2201 {
2202 if (fVerbose)
2203 RTPrintf("Creating directory \"%s\" ...\n", it->first.c_str());
2204
2205 hrc = pGuestSession->DirectoryCreate(Bstr(it->first).raw(), fDirMode, ComSafeArrayAsInParam(dirCreateFlags));
2206 if (FAILED(hrc))
2207 {
2208 ctrlPrintError(pGuest, COM_IIDOF(IGuestSession)); /* Return code ignored, save original rc. */
2209 break;
2210 }
2211
2212 it++;
2213 }
2214
2215 if (!pGuestSession.isNull())
2216 pGuestSession->Close();
2217
2218 return FAILED(hrc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2219}
2220
2221static int handleCtrlCreateTemp(ComPtr<IGuest> pGuest, HandlerArg *pArg)
2222{
2223 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2224
2225 /*
2226 * Parse arguments.
2227 *
2228 * Note! No direct returns here, everyone must go thru the cleanup at the
2229 * end of this function.
2230 */
2231 static const RTGETOPTDEF s_aOptions[] =
2232 {
2233 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2234 { "--directory", 'D', RTGETOPT_REQ_NOTHING },
2235 { "--secure", 's', RTGETOPT_REQ_NOTHING },
2236 { "--tmpdir", 't', RTGETOPT_REQ_STRING },
2237 { "--username", 'u', RTGETOPT_REQ_STRING },
2238 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
2239 { "--password", GETOPTDEF_MKDIR_PASSWORD, RTGETOPT_REQ_STRING },
2240 { "--domain", 'd', RTGETOPT_REQ_STRING },
2241 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2242 };
2243
2244 int ch;
2245 RTGETOPTUNION ValueUnion;
2246 RTGETOPTSTATE GetState;
2247 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
2248 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2249
2250 Utf8Str strUsername;
2251 Utf8Str strPassword;
2252 Utf8Str strDomain;
2253 Utf8Str strTemplate;
2254 uint32_t fMode = 0; /* Default mode. */
2255 bool fDirectory = false;
2256 bool fSecure = false;
2257 Utf8Str strTempDir;
2258 bool fVerbose = false;
2259
2260 DESTDIRMAP mapDirs;
2261
2262 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2263 {
2264 /* For options that require an argument, ValueUnion has received the value. */
2265 switch (ch)
2266 {
2267 case 'm': /* Mode */
2268 fMode = ValueUnion.u32;
2269 break;
2270
2271 case 'D': /* Create directory */
2272 fDirectory = true;
2273 break;
2274
2275 case 's': /* Secure */
2276 fSecure = true;
2277 break;
2278
2279 case 't': /* Temp directory */
2280 strTempDir = ValueUnion.psz;
2281 break;
2282
2283 case 'u': /* User name */
2284 strUsername = ValueUnion.psz;
2285 break;
2286
2287 case GETOPTDEF_MKDIR_PASSWORD: /* Password */
2288 strPassword = ValueUnion.psz;
2289 break;
2290
2291 case 'p': /* Password file */
2292 {
2293 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2294 if (rcExit != RTEXITCODE_SUCCESS)
2295 return rcExit;
2296 break;
2297 }
2298
2299 case 'd': /* domain */
2300 strDomain = ValueUnion.psz;
2301 break;
2302
2303 case 'v': /* Verbose */
2304 fVerbose = true;
2305 break;
2306
2307 case VINF_GETOPT_NOT_OPTION:
2308 {
2309 if (strTemplate.isEmpty())
2310 strTemplate = ValueUnion.psz;
2311 else
2312 return errorSyntax(USAGE_GUESTCONTROL,
2313 "More than one template specified!\n");
2314 break;
2315 }
2316
2317 default:
2318 return RTGetOptPrintError(ch, &ValueUnion);
2319 }
2320 }
2321
2322 if (strTemplate.isEmpty())
2323 return errorSyntax(USAGE_GUESTCONTROL, "No template specified!");
2324
2325 if (strUsername.isEmpty())
2326 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
2327
2328 if (!fDirectory)
2329 return errorSyntax(USAGE_GUESTCONTROL, "Creating temporary files is currently not supported!");
2330
2331 /*
2332 * Create the directories.
2333 */
2334 HRESULT hrc = S_OK;
2335 if (fVerbose)
2336 {
2337 if (fDirectory && !strTempDir.isEmpty())
2338 RTPrintf("Creating temporary directory from template '%s' in directory '%s' ...\n",
2339 strTemplate.c_str(), strTempDir.c_str());
2340 else if (fDirectory)
2341 RTPrintf("Creating temporary directory from template '%s' in default temporary directory ...\n",
2342 strTemplate.c_str());
2343 else if (!fDirectory && !strTempDir.isEmpty())
2344 RTPrintf("Creating temporary file from template '%s' in directory '%s' ...\n",
2345 strTemplate.c_str(), strTempDir.c_str());
2346 else if (!fDirectory)
2347 RTPrintf("Creating temporary file from template '%s' in default temporary directory ...\n",
2348 strTemplate.c_str());
2349 }
2350
2351 ComPtr<IGuestSession> pGuestSession;
2352 hrc = pGuest->CreateSession(Bstr(strUsername).raw(),
2353 Bstr(strPassword).raw(),
2354 Bstr(strDomain).raw(),
2355 Bstr("VBoxManage Guest Control MkTemp").raw(),
2356 pGuestSession.asOutParam());
2357 if (FAILED(hrc))
2358 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
2359
2360 if (fDirectory)
2361 {
2362 Bstr directory;
2363 hrc = pGuestSession->DirectoryCreateTemp(Bstr(strTemplate).raw(),
2364 fMode, Bstr(strTempDir).raw(),
2365 fSecure,
2366 directory.asOutParam());
2367 if (SUCCEEDED(hrc))
2368 RTPrintf("Directory name: %ls\n", directory.raw());
2369 }
2370 // else - temporary file not yet implemented
2371 if (FAILED(hrc))
2372 ctrlPrintError(pGuest, COM_IIDOF(IGuestSession)); /* Return code ignored, save original rc. */
2373
2374 if (!pGuestSession.isNull())
2375 pGuestSession->Close();
2376
2377 return FAILED(hrc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2378}
2379
2380static int handleCtrlStat(ComPtr<IGuest> pGuest, HandlerArg *pArg)
2381{
2382 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2383
2384 static const RTGETOPTDEF s_aOptions[] =
2385 {
2386 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
2387 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
2388 { "--format", 'c', RTGETOPT_REQ_STRING },
2389 { "--username", 'u', RTGETOPT_REQ_STRING },
2390 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
2391 { "--password", GETOPTDEF_STAT_PASSWORD, RTGETOPT_REQ_STRING },
2392 { "--domain", 'd', RTGETOPT_REQ_STRING },
2393 { "--terse", 't', RTGETOPT_REQ_NOTHING },
2394 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2395 };
2396
2397 int ch;
2398 RTGETOPTUNION ValueUnion;
2399 RTGETOPTSTATE GetState;
2400 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
2401 s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2402
2403 Utf8Str strUsername;
2404 Utf8Str strPassword;
2405 Utf8Str strDomain;
2406
2407 bool fVerbose = false;
2408 DESTDIRMAP mapObjs;
2409
2410 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2411 {
2412 /* For options that require an argument, ValueUnion has received the value. */
2413 switch (ch)
2414 {
2415 case 'u': /* User name */
2416 strUsername = ValueUnion.psz;
2417 break;
2418
2419 case GETOPTDEF_STAT_PASSWORD: /* Password */
2420 strPassword = ValueUnion.psz;
2421 break;
2422
2423 case 'p': /* Password file */
2424 {
2425 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2426 if (rcExit != RTEXITCODE_SUCCESS)
2427 return rcExit;
2428 break;
2429 }
2430
2431 case 'd': /* domain */
2432 strDomain = ValueUnion.psz;
2433 break;
2434
2435 case 'L': /* Dereference */
2436 case 'f': /* File-system */
2437 case 'c': /* Format */
2438 case 't': /* Terse */
2439 return errorSyntax(USAGE_GUESTCONTROL, "Command \"%s\" not implemented yet!",
2440 ValueUnion.psz);
2441 break; /* Never reached. */
2442
2443 case 'v': /* Verbose */
2444 fVerbose = true;
2445 break;
2446
2447 case VINF_GETOPT_NOT_OPTION:
2448 {
2449 mapObjs[ValueUnion.psz]; /* Add element to check to map. */
2450 break;
2451 }
2452
2453 default:
2454 return RTGetOptPrintError(ch, &ValueUnion);
2455 }
2456 }
2457
2458 uint32_t cObjs = mapObjs.size();
2459 if (!cObjs)
2460 return errorSyntax(USAGE_GUESTCONTROL, "No element(s) to check specified!");
2461
2462 if (strUsername.isEmpty())
2463 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
2464
2465 ComPtr<IGuestSession> pGuestSession;
2466 HRESULT hrc = pGuest->CreateSession(Bstr(strUsername).raw(),
2467 Bstr(strPassword).raw(),
2468 Bstr(strDomain).raw(),
2469 Bstr("VBoxManage Guest Control Stat").raw(),
2470 pGuestSession.asOutParam());
2471 if (FAILED(hrc))
2472 return ctrlPrintError(pGuest, COM_IIDOF(IGuest));
2473
2474 /*
2475 * Create the directories.
2476 */
2477 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2478 DESTDIRMAPITER it = mapObjs.begin();
2479 while (it != mapObjs.end())
2480 {
2481 if (fVerbose)
2482 RTPrintf("Checking for element \"%s\" ...\n", it->first.c_str());
2483
2484 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2485 hrc = pGuestSession->FileQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
2486 if (FAILED(hrc))
2487 hrc = pGuestSession->DirectoryQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
2488
2489 if (FAILED(hrc))
2490 {
2491 /* If there's at least one element which does not exist on the guest,
2492 * drop out with exitcode 1. */
2493 if (fVerbose)
2494 RTPrintf("Cannot stat for element \"%s\": No such element\n",
2495 it->first.c_str());
2496 rcExit = RTEXITCODE_FAILURE;
2497 }
2498 else
2499 {
2500 FsObjType_T objType;
2501 hrc = pFsObjInfo->COMGETTER(Type)(&objType);
2502 if (FAILED(hrc))
2503 return ctrlPrintError(pGuest, COM_IIDOF(IGuestFsObjInfo));
2504 switch (objType)
2505 {
2506 case FsObjType_File:
2507 RTPrintf("Element \"%s\" found: Is a file\n", it->first.c_str());
2508 break;
2509
2510 case FsObjType_Directory:
2511 RTPrintf("Element \"%s\" found: Is a directory\n", it->first.c_str());
2512 break;
2513
2514 case FsObjType_Symlink:
2515 RTPrintf("Element \"%s\" found: Is a symlink\n", it->first.c_str());
2516 break;
2517
2518 default:
2519 RTPrintf("Element \"%s\" found, type unknown (%ld)\n", it->first.c_str(), objType);
2520 break;
2521 }
2522
2523 /** @todo: Show more information about this element. */
2524 }
2525
2526 it++;
2527 }
2528
2529 if (!pGuestSession.isNull())
2530 pGuestSession->Close();
2531
2532 return rcExit;
2533}
2534
2535static int handleCtrlUpdateAdditions(ComPtr<IGuest> guest, HandlerArg *pArg)
2536{
2537 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2538
2539 /*
2540 * Check the syntax. We can deduce the correct syntax from the number of
2541 * arguments.
2542 */
2543 Utf8Str strSource;
2544 bool fVerbose = false;
2545 bool fWaitStartOnly = false;
2546
2547 static const RTGETOPTDEF s_aOptions[] =
2548 {
2549 { "--source", 's', RTGETOPT_REQ_STRING },
2550 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2551 { "--wait-start", 'w', RTGETOPT_REQ_NOTHING }
2552 };
2553
2554 int ch;
2555 RTGETOPTUNION ValueUnion;
2556 RTGETOPTSTATE GetState;
2557 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2558
2559 int vrc = VINF_SUCCESS;
2560 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2561 && RT_SUCCESS(vrc))
2562 {
2563 switch (ch)
2564 {
2565 case 's':
2566 strSource = ValueUnion.psz;
2567 break;
2568
2569 case 'v':
2570 fVerbose = true;
2571 break;
2572
2573 case 'w':
2574 fWaitStartOnly = true;
2575 break;
2576
2577 default:
2578 return RTGetOptPrintError(ch, &ValueUnion);
2579 }
2580 }
2581
2582 if (fVerbose)
2583 RTPrintf("Updating Guest Additions ...\n");
2584
2585 HRESULT rc = S_OK;
2586 while (strSource.isEmpty())
2587 {
2588 ComPtr<ISystemProperties> pProperties;
2589 CHECK_ERROR_BREAK(pArg->virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam()));
2590 Bstr strISO;
2591 CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam()));
2592 strSource = strISO;
2593 break;
2594 }
2595
2596 /* Determine source if not set yet. */
2597 if (strSource.isEmpty())
2598 {
2599 RTMsgError("No Guest Additions source found or specified, aborting\n");
2600 vrc = VERR_FILE_NOT_FOUND;
2601 }
2602 else if (!RTFileExists(strSource.c_str()))
2603 {
2604 RTMsgError("Source \"%s\" does not exist!\n", strSource.c_str());
2605 vrc = VERR_FILE_NOT_FOUND;
2606 }
2607
2608 if (RT_SUCCESS(vrc))
2609 {
2610 if (fVerbose)
2611 RTPrintf("Using source: %s\n", strSource.c_str());
2612
2613 com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags;
2614 if (fWaitStartOnly)
2615 {
2616 aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly);
2617 if (fVerbose)
2618 RTPrintf("Preparing and waiting for Guest Additions installer to start ...\n");
2619 }
2620
2621 ComPtr<IProgress> pProgress;
2622 CHECK_ERROR(guest, UpdateGuestAdditions(Bstr(strSource).raw(),
2623 /* Wait for whole update process to complete. */
2624 ComSafeArrayAsInParam(aUpdateFlags),
2625 pProgress.asOutParam()));
2626 if (FAILED(rc))
2627 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
2628 else
2629 {
2630 if (fVerbose)
2631 rc = showProgress(pProgress);
2632 else
2633 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
2634
2635 if (SUCCEEDED(rc))
2636 CHECK_PROGRESS_ERROR(pProgress, ("Guest additions update failed"));
2637 vrc = ctrlPrintProgressError(pProgress);
2638 if ( RT_SUCCESS(vrc)
2639 && fVerbose)
2640 {
2641 RTPrintf("Guest Additions update successful\n");
2642 }
2643 }
2644 }
2645
2646 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2647}
2648
2649/**
2650 * Access the guest control store.
2651 *
2652 * @returns program exit code.
2653 * @note see the command line API description for parameters
2654 */
2655int handleGuestControl(HandlerArg *pArg)
2656{
2657 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
2658
2659#ifdef DEBUG_andy_disabled
2660 if (RT_FAILURE(tstTranslatePath()))
2661 return RTEXITCODE_FAILURE;
2662#endif
2663
2664 HandlerArg arg = *pArg;
2665 arg.argc = pArg->argc - 2; /* Skip VM name and sub command. */
2666 arg.argv = pArg->argv + 2; /* Same here. */
2667
2668 ComPtr<IGuest> guest;
2669 int vrc = ctrlInitVM(pArg, pArg->argv[0] /* VM Name */, &guest);
2670 if (RT_SUCCESS(vrc))
2671 {
2672 int rcExit;
2673 if (pArg->argc < 2)
2674 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
2675 else if ( !strcmp(pArg->argv[1], "exec")
2676 || !strcmp(pArg->argv[1], "execute"))
2677 rcExit = handleCtrlExecProgram(guest, &arg);
2678 else if (!strcmp(pArg->argv[1], "copyfrom"))
2679 rcExit = handleCtrlCopy(guest, &arg, false /* Guest to host */);
2680 else if ( !strcmp(pArg->argv[1], "copyto")
2681 || !strcmp(pArg->argv[1], "cp"))
2682 rcExit = handleCtrlCopy(guest, &arg, true /* Host to guest */);
2683 else if ( !strcmp(pArg->argv[1], "createdirectory")
2684 || !strcmp(pArg->argv[1], "createdir")
2685 || !strcmp(pArg->argv[1], "mkdir")
2686 || !strcmp(pArg->argv[1], "md"))
2687 rcExit = handleCtrlCreateDirectory(guest, &arg);
2688 else if ( !strcmp(pArg->argv[1], "createtemporary")
2689 || !strcmp(pArg->argv[1], "createtemp")
2690 || !strcmp(pArg->argv[1], "mktemp"))
2691 rcExit = handleCtrlCreateTemp(guest, &arg);
2692 else if ( !strcmp(pArg->argv[1], "stat"))
2693 rcExit = handleCtrlStat(guest, &arg);
2694 else if ( !strcmp(pArg->argv[1], "updateadditions")
2695 || !strcmp(pArg->argv[1], "updateadds"))
2696 rcExit = handleCtrlUpdateAdditions(guest, &arg);
2697 /** @todo Implement a "sessions list" command to list all opened
2698 * guest sessions along with their (friendly) names. */
2699 else
2700 rcExit = errorSyntax(USAGE_GUESTCONTROL, "Unknown sub command '%s' specified!", pArg->argv[1]);
2701
2702 ctrlUninitVM(pArg);
2703 return rcExit;
2704 }
2705 return RTEXITCODE_FAILURE;
2706}
2707
2708#endif /* !VBOX_ONLY_DOCS */
2709
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