VirtualBox

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

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

Forward ported r85941 and required build fixes (Main: Implemented new event queue to separate system's native event queue and our own. Also, XPCOM is not needed for handling our own events. On Windows this also fixes the system's queue quota limitation).

  • 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 46649 2013-06-19 11:47:32Z 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.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/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.

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