VirtualBox

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

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

Fe/VBoxManage: Close the session after execution usage.

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