VirtualBox

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

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

FE/VBoxManage/GuestCtrl: Include guest session status when listing.

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