VirtualBox

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

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

FE/VBoxManage/GuestCtrl: Added "guestcontrol list" command (debug versions only), proper RTEXITCODEs for handler returns.

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