VirtualBox

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

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

Fe/VBoxManage: Close the session after execution usage. Todos.

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