VirtualBox

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

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

VBoxManage: improved assertion (warning fix)

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