VirtualBox

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

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

VBoxManage/Help: unify help output regarding UUID and VM name

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette