VirtualBox

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

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

VBoxManageGuestCtrl.cpp: TODOs related to #6344.

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