VirtualBox

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

Last change on this file since 49165 was 49165, checked in by vboxsync, 11 years ago

FE/VBoxManage/GuestCtrl: Unified a lot of command line handling, added support for remove file and remove directory, separated event listeners.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 131.1 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 49165 2013-10-17 14:48:13Z 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#include "VBoxManageGuestCtrl.h"
24
25#ifndef VBOX_ONLY_DOCS
26
27#include <VBox/com/array.h>
28#include <VBox/com/com.h>
29#include <VBox/com/ErrorInfo.h>
30#include <VBox/com/errorprint.h>
31#include <VBox/com/listeners.h>
32#include <VBox/com/NativeEventQueue.h>
33#include <VBox/com/string.h>
34#include <VBox/com/VirtualBox.h>
35
36#include <VBox/err.h>
37#include <VBox/log.h>
38
39#include <iprt/asm.h>
40#include <iprt/dir.h>
41#include <iprt/file.h>
42#include <iprt/isofs.h>
43#include <iprt/getopt.h>
44#include <iprt/list.h>
45#include <iprt/path.h>
46#include <iprt/process.h> /* For RTProcSelf(). */
47#include <iprt/thread.h>
48
49#include <map>
50#include <vector>
51
52#ifdef USE_XPCOM_QUEUE
53# include <sys/select.h>
54# include <errno.h>
55#endif
56
57#include <signal.h>
58
59#ifdef RT_OS_DARWIN
60# include <CoreFoundation/CFRunLoop.h>
61#endif
62
63using namespace com;
64
65/** Set by the signal handler. */
66static volatile bool g_fGuestCtrlCanceled = false;
67/** Our global session object which is also used in the
68 * signal handler to abort operations properly. */
69static ComPtr<IGuestSession> g_pGuestSession;
70
71/**
72 * Command context flags.
73 */
74/** No flags set. */
75#define CTLCMDCTX_FLAGS_NONE 0
76/** Don't install a signal handler (CTRL+C trap). */
77#define CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER RT_BIT(0)
78/** No guest session needed. */
79#define CTLCMDCTX_FLAGS_SESSION_ANONYMOUS RT_BIT(1)
80/** Detach the guest session. That is, don't close the
81 * guest session automatically on exit. */
82#define CTLCMDCTX_FLAGS_SESSION_DETACH RT_BIT(2)
83
84/**
85 * Context for handling a specific command.
86 */
87typedef struct GCTLCMDCTX
88{
89 HandlerArg handlerArg;
90 /** Command-specific argument count. */
91 int iArgc;
92 /** Command-specific argument vector. */
93 char **ppaArgv;
94 /** First argv to start parsing with. */
95 int iFirstArgc;
96 /** Command context flags. */
97 uint32_t uFlags;
98 /** Verbose flag. */
99 bool fVerbose;
100 /** User name. */
101 Utf8Str strUsername;
102 /** Password. */
103 Utf8Str strPassword;
104 /** Domain. */
105 Utf8Str strDomain;
106 /** Pointer to the IGuest interface. */
107 ComPtr<IGuest> pGuest;
108 /** Pointer to the to be used guest session. */
109 ComPtr<IGuestSession> pGuestSession;
110
111} GCTLCMDCTX, *PGCTLCMDCTX;
112
113typedef struct GCTLCMD
114{
115 /**
116 * Actual command handler callback.
117 *
118 * @param pCtx Pointer to command context to use.
119 */
120 DECLR3CALLBACKMEMBER(RTEXITCODE, pfnHandler, (PGCTLCMDCTX pCtx));
121
122} GCTLCMD, *PGCTLCMD;
123
124typedef struct COPYCONTEXT
125{
126 COPYCONTEXT()
127 : fDryRun(false),
128 fHostToGuest(false)
129 {
130 }
131
132 PGCTLCMDCTX pCmdCtx;
133 ComPtr<IGuestSession> pGuestSession;
134 bool fDryRun;
135 bool fHostToGuest;
136
137} COPYCONTEXT, *PCOPYCONTEXT;
138
139/**
140 * An entry for a source element, including an optional DOS-like wildcard (*,?).
141 */
142class SOURCEFILEENTRY
143{
144 public:
145
146 SOURCEFILEENTRY(const char *pszSource, const char *pszFilter)
147 : mSource(pszSource),
148 mFilter(pszFilter) {}
149
150 SOURCEFILEENTRY(const char *pszSource)
151 : mSource(pszSource)
152 {
153 Parse(pszSource);
154 }
155
156 const char* GetSource() const
157 {
158 return mSource.c_str();
159 }
160
161 const char* GetFilter() const
162 {
163 return mFilter.c_str();
164 }
165
166 private:
167
168 int Parse(const char *pszPath)
169 {
170 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
171
172 if ( !RTFileExists(pszPath)
173 && !RTDirExists(pszPath))
174 {
175 /* No file and no directory -- maybe a filter? */
176 char *pszFilename = RTPathFilename(pszPath);
177 if ( pszFilename
178 && strpbrk(pszFilename, "*?"))
179 {
180 /* Yep, get the actual filter part. */
181 mFilter = RTPathFilename(pszPath);
182 /* Remove the filter from actual sourcec directory name. */
183 RTPathStripFilename(mSource.mutableRaw());
184 mSource.jolt();
185 }
186 }
187
188 return VINF_SUCCESS; /* @todo */
189 }
190
191 private:
192
193 Utf8Str mSource;
194 Utf8Str mFilter;
195};
196typedef std::vector<SOURCEFILEENTRY> SOURCEVEC, *PSOURCEVEC;
197
198/**
199 * An entry for an element which needs to be copied/created to/on the guest.
200 */
201typedef struct DESTFILEENTRY
202{
203 DESTFILEENTRY(Utf8Str strFileName) : mFileName(strFileName) {}
204 Utf8Str mFileName;
205} DESTFILEENTRY, *PDESTFILEENTRY;
206/*
207 * Map for holding destination entires, whereas the key is the destination
208 * directory and the mapped value is a vector holding all elements for this directoy.
209 */
210typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> > DESTDIRMAP, *PDESTDIRMAP;
211typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> >::iterator DESTDIRMAPITER, *PDESTDIRMAPITER;
212
213/**
214 * Special exit codes for returning errors/information of a
215 * started guest process to the command line VBoxManage was started from.
216 * Useful for e.g. scripting.
217 *
218 * @note These are frozen as of 4.1.0.
219 */
220enum EXITCODEEXEC
221{
222 EXITCODEEXEC_SUCCESS = RTEXITCODE_SUCCESS,
223 /* Process exited normally but with an exit code <> 0. */
224 EXITCODEEXEC_CODE = 16,
225 EXITCODEEXEC_FAILED = 17,
226 EXITCODEEXEC_TERM_SIGNAL = 18,
227 EXITCODEEXEC_TERM_ABEND = 19,
228 EXITCODEEXEC_TIMEOUT = 20,
229 EXITCODEEXEC_DOWN = 21,
230 EXITCODEEXEC_CANCELED = 22
231};
232
233/*
234 * Common getopt definitions, starting at 1000.
235 * Specific command definitions will start all at 2000.
236 */
237enum GETOPTDEF_COMMON
238{
239 GETOPTDEF_COMMON_PASSWORD = 1000
240};
241
242/**
243 * RTGetOpt-IDs for the guest execution control command line.
244 */
245enum GETOPTDEF_EXEC
246{
247 GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000,
248 GETOPTDEF_EXEC_NO_PROFILE,
249 GETOPTDEF_EXEC_OUTPUTFORMAT,
250 GETOPTDEF_EXEC_DOS2UNIX,
251 GETOPTDEF_EXEC_UNIX2DOS,
252 GETOPTDEF_EXEC_WAITFOREXIT,
253 GETOPTDEF_EXEC_WAITFORSTDOUT,
254 GETOPTDEF_EXEC_WAITFORSTDERR
255};
256
257enum GETOPTDEF_COPY
258{
259 GETOPTDEF_COPY_DRYRUN = 1000,
260 GETOPTDEF_COPY_FOLLOW,
261 GETOPTDEF_COPY_TARGETDIR
262};
263
264enum GETOPTDEF_MKDIR
265{
266};
267
268enum GETOPTDEF_RM
269{
270};
271
272enum GETOPTDEF_RMDIR
273{
274 GETOPTDEF_RMDIR_RECURSIVE = 2000
275};
276
277enum GETOPTDEF_SESSIONCLOSE
278{
279 GETOPTDEF_SESSIONCLOSE_ALL = 2000
280};
281
282enum GETOPTDEF_STAT
283{
284};
285
286enum OUTPUTTYPE
287{
288 OUTPUTTYPE_UNDEFINED = 0,
289 OUTPUTTYPE_DOS2UNIX = 10,
290 OUTPUTTYPE_UNIX2DOS = 20
291};
292
293static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest, const char *pszDir, bool *fExists);
294
295#endif /* VBOX_ONLY_DOCS */
296
297void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2)
298{
299 RTStrmPrintf(pStrm,
300 "%s guestcontrol %s <uuid|vmname>\n"
301 " exec[ute]\n"
302 " --image <path to program> --username <name>\n"
303 " [--passwordfile <file> | --password <password>]\n"
304 " [--domain <domain>] [--verbose] [--timeout <msec>]\n"
305 " [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
306 " [--wait-exit] [--wait-stdout] [--wait-stderr]\n"
307 " [--dos2unix] [--unix2dos]\n"
308 " [-- [<argument1>] ... [<argumentN>]]\n"
309 /** @todo Add a "--" parameter (has to be last parameter) to directly execute
310 * stuff, e.g. "VBoxManage guestcontrol execute <VMName> --username <> ... -- /bin/rm -Rf /foo". */
311 "\n"
312 " copyfrom\n"
313 " <guest source> <host dest> --username <name>\n"
314 " [--passwordfile <file> | --password <password>]\n"
315 " [--domain <domain>] [--verbose]\n"
316 " [--dryrun] [--follow] [--recursive]\n"
317 "\n"
318 " copyto|cp\n"
319 " <host source> <guest dest> --username <name>\n"
320 " [--passwordfile <file> | --password <password>]\n"
321 " [--domain <domain>] [--verbose]\n"
322 " [--dryrun] [--follow] [--recursive]\n"
323 "\n"
324 " createdir[ectory]|mkdir|md\n"
325 " <guest directory>... --username <name>\n"
326 " [--passwordfile <file> | --password <password>]\n"
327 " [--domain <domain>] [--verbose]\n"
328 " [--parents] [--mode <mode>]\n"
329 "\n"
330 " removedir[ectory]|rmdir|rm\n"
331 " <guest directory>... --username <name>\n"
332 " [--passwordfile <file> | --password <password>]\n"
333 " [--domain <domain>] [--verbose]\n"
334 " [--recursive|-R|-r]\n"
335 "\n"
336 " removefile|rm\n"
337 " <guest file>... --username <name>\n"
338 " [--passwordfile <file> | --password <password>]\n"
339 " [--domain <domain>] [--verbose]\n"
340 "\n"
341 " createtemp[orary]|mktemp\n"
342 " <template> --username <name>\n"
343 " [--passwordfile <file> | --password <password>]\n"
344 " [--directory] [--secure] [--tmpdir <directory>]\n"
345 " [--domain <domain>] [--mode <mode>] [--verbose]\n"
346 "\n"
347 " list <all|sessions|processes|files> [--verbose]\n"
348 "\n"
349 /** @todo Add an own help group for "session" and "process" sub commands. */
350 " process kill --session-id <ID>\n"
351 " | --session-name <name or pattern>\n"
352 " [--verbose]\n"
353 " <PID> ... <PID n>\n"
354 "\n"
355 " [p[s]]kill --session-id <ID>\n"
356 " | --session-name <name or pattern>\n"
357 " [--verbose]\n"
358 " <PID> ... <PID n>\n"
359 "\n"
360 " session close --session-id <ID>\n"
361 " | --session-name <name or pattern>\n"
362 " | --all\n"
363 " [--verbose]\n"
364 " stat\n"
365 " <file>... --username <name>\n"
366 " [--passwordfile <file> | --password <password>]\n"
367 " [--domain <domain>] [--verbose]\n"
368 "\n"
369 " updateadditions\n"
370 " [--source <guest additions .ISO>] [--verbose]\n"
371 " [--wait-start]\n"
372 " [-- [<argument1>] ... [<argumentN>]]\n"
373 "\n"
374 " watch [--verbose]\n"
375 "\n", pcszSep1, pcszSep2);
376}
377
378#ifndef VBOX_ONLY_DOCS
379
380#ifdef RT_OS_WINDOWS
381static BOOL WINAPI guestCtrlSignalHandler(DWORD dwCtrlType)
382{
383 bool fEventHandled = FALSE;
384 switch (dwCtrlType)
385 {
386 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
387 * via GenerateConsoleCtrlEvent(). */
388 case CTRL_BREAK_EVENT:
389 case CTRL_CLOSE_EVENT:
390 case CTRL_C_EVENT:
391 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
392 if (!g_pGuestSession.isNull())
393 g_pGuestSession->Close();
394 fEventHandled = TRUE;
395 break;
396 default:
397 break;
398 /** @todo Add other events here. */
399 }
400
401 return fEventHandled;
402}
403#else /* !RT_OS_WINDOWS */
404/**
405 * Signal handler that sets g_fGuestCtrlCanceled.
406 *
407 * This can be executed on any thread in the process, on Windows it may even be
408 * a thread dedicated to delivering this signal. Do not doing anything
409 * unnecessary here.
410 */
411static void guestCtrlSignalHandler(int iSignal)
412{
413 NOREF(iSignal);
414 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
415 if (!g_pGuestSession.isNull())
416 g_pGuestSession->Close();
417}
418#endif
419
420/**
421 * Installs a custom signal handler to get notified
422 * whenever the user wants to intercept the program.
423 *
424 ** @todo Make this handler available for all VBoxManage modules?
425 */
426static int ctrlSignalHandlerInstall(void)
427{
428 int rc = VINF_SUCCESS;
429#ifdef RT_OS_WINDOWS
430 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)guestCtrlSignalHandler, TRUE /* Add handler */))
431 {
432 rc = RTErrConvertFromWin32(GetLastError());
433 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
434 }
435#else
436 signal(SIGINT, guestCtrlSignalHandler);
437# ifdef SIGBREAK
438 signal(SIGBREAK, guestCtrlSignalHandler);
439# endif
440#endif
441 return rc;
442}
443
444/**
445 * Uninstalls a previously installed signal handler.
446 */
447static int ctrlSignalHandlerUninstall(void)
448{
449 int rc = VINF_SUCCESS;
450#ifdef RT_OS_WINDOWS
451 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
452 {
453 rc = RTErrConvertFromWin32(GetLastError());
454 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
455 }
456#else
457 signal(SIGINT, SIG_DFL);
458# ifdef SIGBREAK
459 signal(SIGBREAK, SIG_DFL);
460# endif
461#endif
462 return rc;
463}
464
465/**
466 * Translates a process status to a human readable
467 * string.
468 */
469const char *ctrlProcessStatusToText(ProcessStatus_T enmStatus)
470{
471 switch (enmStatus)
472 {
473 case ProcessStatus_Starting:
474 return "starting";
475 case ProcessStatus_Started:
476 return "started";
477 case ProcessStatus_Paused:
478 return "paused";
479 case ProcessStatus_Terminating:
480 return "terminating";
481 case ProcessStatus_TerminatedNormally:
482 return "successfully terminated";
483 case ProcessStatus_TerminatedSignal:
484 return "terminated by signal";
485 case ProcessStatus_TerminatedAbnormally:
486 return "abnormally aborted";
487 case ProcessStatus_TimedOutKilled:
488 return "timed out";
489 case ProcessStatus_TimedOutAbnormally:
490 return "timed out, hanging";
491 case ProcessStatus_Down:
492 return "killed";
493 case ProcessStatus_Error:
494 return "error";
495 default:
496 break;
497 }
498 return "unknown";
499}
500
501static int ctrlExecProcessStatusToExitCode(ProcessStatus_T enmStatus, ULONG uExitCode)
502{
503 int vrc = EXITCODEEXEC_SUCCESS;
504 switch (enmStatus)
505 {
506 case ProcessStatus_Starting:
507 vrc = EXITCODEEXEC_SUCCESS;
508 break;
509 case ProcessStatus_Started:
510 vrc = EXITCODEEXEC_SUCCESS;
511 break;
512 case ProcessStatus_Paused:
513 vrc = EXITCODEEXEC_SUCCESS;
514 break;
515 case ProcessStatus_Terminating:
516 vrc = EXITCODEEXEC_SUCCESS;
517 break;
518 case ProcessStatus_TerminatedNormally:
519 vrc = !uExitCode ? EXITCODEEXEC_SUCCESS : EXITCODEEXEC_CODE;
520 break;
521 case ProcessStatus_TerminatedSignal:
522 vrc = EXITCODEEXEC_TERM_SIGNAL;
523 break;
524 case ProcessStatus_TerminatedAbnormally:
525 vrc = EXITCODEEXEC_TERM_ABEND;
526 break;
527 case ProcessStatus_TimedOutKilled:
528 vrc = EXITCODEEXEC_TIMEOUT;
529 break;
530 case ProcessStatus_TimedOutAbnormally:
531 vrc = EXITCODEEXEC_TIMEOUT;
532 break;
533 case ProcessStatus_Down:
534 /* Service/OS is stopping, process was killed, so
535 * not exactly an error of the started process ... */
536 vrc = EXITCODEEXEC_DOWN;
537 break;
538 case ProcessStatus_Error:
539 vrc = EXITCODEEXEC_FAILED;
540 break;
541 default:
542 AssertMsgFailed(("Unknown exit code (%u) from guest process returned!\n", enmStatus));
543 break;
544 }
545 return vrc;
546}
547
548/**
549 * Translates a guest session status to a human readable
550 * string.
551 */
552const char *ctrlSessionStatusToText(GuestSessionStatus_T enmStatus)
553{
554 switch (enmStatus)
555 {
556 case GuestSessionStatus_Starting:
557 return "starting";
558 case GuestSessionStatus_Started:
559 return "started";
560 case GuestSessionStatus_Terminating:
561 return "terminating";
562 case GuestSessionStatus_Terminated:
563 return "terminated";
564 case GuestSessionStatus_TimedOutKilled:
565 return "timed out";
566 case GuestSessionStatus_TimedOutAbnormally:
567 return "timed out, hanging";
568 case GuestSessionStatus_Down:
569 return "killed";
570 case GuestSessionStatus_Error:
571 return "error";
572 default:
573 break;
574 }
575 return "unknown";
576}
577
578/**
579 * Translates a guest file status to a human readable
580 * string.
581 */
582const char *ctrlFileStatusToText(FileStatus_T enmStatus)
583{
584 switch (enmStatus)
585 {
586 case FileStatus_Opening:
587 return "opening";
588 case FileStatus_Open:
589 return "open";
590 case FileStatus_Closing:
591 return "closing";
592 case FileStatus_Closed:
593 return "closed";
594 case FileStatus_Down:
595 return "killed";
596 case FileStatus_Error:
597 return "error";
598 default:
599 break;
600 }
601 return "unknown";
602}
603
604static int ctrlPrintError(com::ErrorInfo &errorInfo)
605{
606 if ( errorInfo.isFullAvailable()
607 || errorInfo.isBasicAvailable())
608 {
609 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
610 * because it contains more accurate info about what went wrong. */
611 if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
612 RTMsgError("%ls.", errorInfo.getText().raw());
613 else
614 {
615 RTMsgError("Error details:");
616 GluePrintErrorInfo(errorInfo);
617 }
618 return VERR_GENERAL_FAILURE; /** @todo */
619 }
620 AssertMsgFailedReturn(("Object has indicated no error (%Rhrc)!?\n", errorInfo.getResultCode()),
621 VERR_INVALID_PARAMETER);
622}
623
624static int ctrlPrintError(IUnknown *pObj, const GUID &aIID)
625{
626 com::ErrorInfo ErrInfo(pObj, aIID);
627 return ctrlPrintError(ErrInfo);
628}
629
630static int ctrlPrintProgressError(ComPtr<IProgress> pProgress)
631{
632 int vrc = VINF_SUCCESS;
633 HRESULT rc;
634
635 do
636 {
637 BOOL fCanceled;
638 CHECK_ERROR_BREAK(pProgress, COMGETTER(Canceled)(&fCanceled));
639 if (!fCanceled)
640 {
641 LONG rcProc;
642 CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&rcProc));
643 if (FAILED(rcProc))
644 {
645 com::ProgressErrorInfo ErrInfo(pProgress);
646 vrc = ctrlPrintError(ErrInfo);
647 }
648 }
649
650 } while(0);
651
652 AssertMsgStmt(SUCCEEDED(rc), ("Could not lookup progress information\n"), vrc = VERR_COM_UNEXPECTED);
653
654 return vrc;
655}
656
657/**
658 * Un-initializes the VM after guest control usage.
659 * @param pCmdCtx Pointer to command context.
660 * @param uFlags Command context flags.
661 */
662static void ctrlUninitVM(PGCTLCMDCTX pCtx, uint32_t uFlags)
663{
664 AssertPtrReturnVoid(pCtx);
665
666 if (!(pCtx->uFlags & CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER))
667 ctrlSignalHandlerUninstall();
668
669 HRESULT rc;
670
671 do
672 {
673 if (!pCtx->pGuestSession.isNull())
674 {
675 if ( !(pCtx->uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS)
676 && !(pCtx->uFlags & CTLCMDCTX_FLAGS_SESSION_DETACH))
677 {
678 if (pCtx->fVerbose)
679 RTPrintf("Closing guest session ...\n");
680
681 CHECK_ERROR(pCtx->pGuestSession, Close());
682 /* Keep going - don't break here. Try to unlock the
683 * machine down below. */
684 }
685 else if (pCtx->fVerbose)
686 RTPrintf("Guest session detached\n");
687 }
688
689 if (pCtx->handlerArg.session)
690 CHECK_ERROR(pCtx->handlerArg.session, UnlockMachine());
691
692 } while (0);
693
694 for (int i = 0; i < pCtx->iArgc; i++)
695 RTStrFree(pCtx->ppaArgv[i]);
696 RTMemFree(pCtx->ppaArgv);
697}
698
699/**
700 * Initializes the VM for IGuest operations.
701 *
702 * That is, checks whether it's up and running, if it can be locked (shared
703 * only) and returns a valid IGuest pointer on success. Also, it does some
704 * basic command line processing and opens a guest session, if required.
705 *
706 * @return RTEXITCODE status code.
707 * @param pArg Pointer to command line argument structure.
708 * @param pCmdCtx Pointer to command context.
709 * @param uFlags Command context flags.
710 */
711static RTEXITCODE ctrlInitVM(HandlerArg *pArg,
712 PGCTLCMDCTX pCtx, uint32_t uFlags)
713{
714 AssertPtrReturn(pArg, RTEXITCODE_FAILURE);
715 AssertReturn(pArg->argc > 1, RTEXITCODE_FAILURE);
716 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
717
718 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
719
720 const char *pszNameOrId = pArg->argv[0];
721 const char *pszCmd = pArg->argv[1];
722
723 /* Lookup VM. */
724 ComPtr<IMachine> machine;
725 /* Assume it's an UUID. */
726 HRESULT rc;
727 CHECK_ERROR(pArg->virtualBox, FindMachine(Bstr(pszNameOrId).raw(),
728 machine.asOutParam()));
729 if (SUCCEEDED(rc))
730 {
731 /* Machine is running? */
732 MachineState_T machineState;
733 CHECK_ERROR(machine, COMGETTER(State)(&machineState));
734 if ( SUCCEEDED(rc)
735 && (machineState != MachineState_Running))
736 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Machine \"%s\" is not running (currently %s)!\n",
737 pszNameOrId, machineStateToName(machineState, false));
738 }
739 else
740 rcExit = RTEXITCODE_FAILURE;
741
742 if (rcExit == RTEXITCODE_SUCCESS)
743 {
744 /*
745 * Process standard options which are served by all commands.
746 */
747 static const RTGETOPTDEF s_aOptions[] =
748 {
749 { "--username", 'u', RTGETOPT_REQ_STRING },
750 { "--passwordfile", 'p', RTGETOPT_REQ_STRING },
751 { "--password", GETOPTDEF_COMMON_PASSWORD, RTGETOPT_REQ_STRING },
752 { "--domain", 'd', RTGETOPT_REQ_STRING },
753 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
754 };
755
756 /*
757 * Allocate per-command argv. This then only contains the specific arguments
758 * the command needs.
759 */
760 pCtx->ppaArgv = (char**)RTMemAlloc(pArg->argc * sizeof(char*) + 1);
761 if (!pCtx->ppaArgv)
762 {
763 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Not enough memory for per-command argv\n");
764 }
765 else
766 {
767 pCtx->iArgc = 0;
768
769 int ch;
770 RTGETOPTUNION ValueUnion;
771 RTGETOPTSTATE GetState;
772 RTGetOptInit(&GetState, pArg->argc, pArg->argv,
773 s_aOptions, RT_ELEMENTS(s_aOptions),
774 2, /* Skip VM name and guest control command */
775 RTGETOPTINIT_FLAGS_OPTS_FIRST);
776
777 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
778 && (rcExit == RTEXITCODE_SUCCESS))
779 {
780 /* For options that require an argument, ValueUnion has received the value. */
781 switch (ch)
782 {
783 case 'u': /* User name */
784 if (!(uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS))
785 pCtx->strUsername = ValueUnion.psz;
786 break;
787
788 case GETOPTDEF_COMMON_PASSWORD: /* Password */
789 if (!(uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS))
790 {
791 if (pCtx->strPassword.isEmpty())
792 pCtx->strPassword = ValueUnion.psz;
793 }
794 break;
795
796 case 'p': /* Password file */
797 {
798 if (!(uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS))
799 rcExit = readPasswordFile(ValueUnion.psz, &pCtx->strPassword);
800 break;
801 }
802
803 case 'd': /* domain */
804 if (!(uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS))
805 pCtx->strDomain = ValueUnion.psz;
806 break;
807
808 case 'v': /* Verbose */
809 pCtx->fVerbose = true;
810 break;
811
812 case VINF_GETOPT_NOT_OPTION:
813 /* Fall through is intentional. */
814 default:
815 {
816 Assert(GetState.iNext);
817 char *pszArg = RTStrDup(pArg->argv[GetState.iNext - 1]);
818 if (!pszArg)
819 {
820 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
821 "Not enough memory for command line handling\n");
822 break;
823 }
824 pCtx->ppaArgv[pCtx->iArgc] = pszArg;
825 pCtx->iArgc++;
826 break;
827 }
828
829 } /* switch */
830 } /* while RTGetOpt */
831 }
832 }
833
834 /*
835 * Check for mandatory stuff.
836 */
837 if (rcExit == RTEXITCODE_SUCCESS)
838 {
839#if 0
840 RTPrintf("argc=%d\n", pCtx->iArgc);
841 for (int i = 0; i < pCtx->iArgc; i++)
842 RTPrintf("argv[%d]=%s\n", i, pCtx->ppaArgv[i]);
843#endif
844 if (!(uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS))
845 {
846 if (pCtx->strUsername.isEmpty())
847 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
848 }
849 }
850
851 if (rcExit == RTEXITCODE_SUCCESS)
852 {
853 /*
854 * Build up a reasonable guest session name. Useful for identifying
855 * a specific session when listing / searching for them.
856 */
857 char *pszSessionName;
858 if (0 >= RTStrAPrintf(&pszSessionName,
859 "[%RU32] VBoxManage Guest Control [%s] - %s",
860 RTProcSelf(), pszNameOrId, pszCmd))
861 return RTMsgErrorExit(RTEXITCODE_FAILURE, "No enough memory for session name\n");
862
863 do
864 {
865 /* Open a session for the VM. */
866 CHECK_ERROR_BREAK(machine, LockMachine(pArg->session, LockType_Shared));
867 /* Get the associated console. */
868 ComPtr<IConsole> console;
869 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Console)(console.asOutParam()));
870 /* ... and session machine. */
871 ComPtr<IMachine> sessionMachine;
872 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
873 /* Get IGuest interface. */
874 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pCtx->pGuest.asOutParam()));
875 if (!(uFlags & CTLCMDCTX_FLAGS_SESSION_ANONYMOUS))
876 {
877 if (pCtx->fVerbose)
878 RTPrintf("Opening guest session as user '%s' ...\n", pCtx->strUsername.c_str());
879
880 /* Open a guest session. */
881 Assert(!pCtx->pGuest.isNull());
882 CHECK_ERROR_BREAK(pCtx->pGuest, CreateSession(Bstr(pCtx->strUsername).raw(),
883 Bstr(pCtx->strPassword).raw(),
884 Bstr(pCtx->strDomain).raw(),
885 Bstr(pszSessionName).raw(),
886 pCtx->pGuestSession.asOutParam()));
887 }
888
889 if (!(uFlags & CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER))
890 ctrlSignalHandlerInstall();
891
892 } while (0);
893
894 if (FAILED(rc))
895 rcExit = RTEXITCODE_FAILURE;
896
897 RTStrFree(pszSessionName);
898 }
899
900 if (rcExit == RTEXITCODE_SUCCESS)
901 {
902 pCtx->handlerArg = *pArg;
903 pCtx->uFlags = uFlags;
904 }
905 else /* Clean up on failure. */
906 ctrlUninitVM(pCtx, uFlags);
907
908 return rcExit;
909}
910
911/**
912 * Prints the desired guest output to a stream.
913 *
914 * @return IPRT status code.
915 * @param pProcess Pointer to appropriate process object.
916 * @param pStrmOutput Where to write the data.
917 * @param uHandle Handle where to read the data from.
918 * @param uTimeoutMS Timeout (in ms) to wait for the operation to complete.
919 */
920static int ctrlExecPrintOutput(IProcess *pProcess, PRTSTREAM pStrmOutput,
921 ULONG uHandle, ULONG uTimeoutMS)
922{
923 AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
924 AssertPtrReturn(pStrmOutput, VERR_INVALID_POINTER);
925
926 int vrc = VINF_SUCCESS;
927
928 SafeArray<BYTE> aOutputData;
929 HRESULT rc = pProcess->Read(uHandle, _64K, uTimeoutMS,
930 ComSafeArrayAsOutParam(aOutputData));
931 if (FAILED(rc))
932 vrc = ctrlPrintError(pProcess, COM_IIDOF(IProcess));
933 else
934 {
935 size_t cbOutputData = aOutputData.size();
936 if (cbOutputData > 0)
937 {
938 BYTE *pBuf = aOutputData.raw();
939 AssertPtr(pBuf);
940 pBuf[cbOutputData - 1] = 0; /* Properly terminate buffer. */
941
942 /** @todo implement the dos2unix/unix2dos conversions */
943
944 /*
945 * If aOutputData is text data from the guest process' stdout or stderr,
946 * it has a platform dependent line ending. So standardize on
947 * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
948 * Windows. Otherwise we end up with CR/CR/LF on Windows.
949 */
950
951 char *pszBufUTF8;
952 vrc = RTStrCurrentCPToUtf8(&pszBufUTF8, (const char*)aOutputData.raw());
953 if (RT_SUCCESS(vrc))
954 {
955 cbOutputData = strlen(pszBufUTF8);
956
957 ULONG cbOutputDataPrint = cbOutputData;
958 for (char *s = pszBufUTF8, *d = s;
959 s - pszBufUTF8 < (ssize_t)cbOutputData;
960 s++, d++)
961 {
962 if (*s == '\r')
963 {
964 /* skip over CR, adjust destination */
965 d--;
966 cbOutputDataPrint--;
967 }
968 else if (s != d)
969 *d = *s;
970 }
971
972 vrc = RTStrmWrite(pStrmOutput, pszBufUTF8, cbOutputDataPrint);
973 if (RT_FAILURE(vrc))
974 RTMsgError("Unable to write output, rc=%Rrc\n", vrc);
975
976 RTStrFree(pszBufUTF8);
977 }
978 else
979 RTMsgError("Unable to convert output, rc=%Rrc\n", vrc);
980 }
981 }
982
983 return vrc;
984}
985
986/**
987 * Returns the remaining time (in ms) based on the start time and a set
988 * timeout value. Returns RT_INDEFINITE_WAIT if no timeout was specified.
989 *
990 * @return RTMSINTERVAL Time left (in ms).
991 * @param u64StartMs Start time (in ms).
992 * @param cMsTimeout Timeout value (in ms).
993 */
994inline RTMSINTERVAL ctrlExecGetRemainingTime(uint64_t u64StartMs, RTMSINTERVAL cMsTimeout)
995{
996 if (!cMsTimeout || cMsTimeout == RT_INDEFINITE_WAIT) /* If no timeout specified, wait forever. */
997 return RT_INDEFINITE_WAIT;
998
999 uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
1000 if (u64ElapsedMs >= cMsTimeout)
1001 return 0;
1002
1003 return cMsTimeout - (RTMSINTERVAL)u64ElapsedMs;
1004}
1005
1006static DECLCALLBACK(RTEXITCODE) handleCtrlProcessExec(PGCTLCMDCTX pCtx)
1007{
1008 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
1009
1010 /*
1011 * Parse arguments.
1012 */
1013 static const RTGETOPTDEF s_aOptions[] =
1014 {
1015 { "--dos2unix", GETOPTDEF_EXEC_DOS2UNIX, RTGETOPT_REQ_NOTHING },
1016 { "--environment", 'e', RTGETOPT_REQ_STRING },
1017 { "--flags", 'f', RTGETOPT_REQ_STRING },
1018 { "--ignore-operhaned-processes", GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES, RTGETOPT_REQ_NOTHING },
1019 { "--image", 'i', RTGETOPT_REQ_STRING },
1020 { "--no-profile", GETOPTDEF_EXEC_NO_PROFILE, RTGETOPT_REQ_NOTHING },
1021 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
1022 { "--unix2dos", GETOPTDEF_EXEC_UNIX2DOS, RTGETOPT_REQ_NOTHING },
1023 { "--wait-exit", GETOPTDEF_EXEC_WAITFOREXIT, RTGETOPT_REQ_NOTHING },
1024 { "--wait-stdout", GETOPTDEF_EXEC_WAITFORSTDOUT, RTGETOPT_REQ_NOTHING },
1025 { "--wait-stderr", GETOPTDEF_EXEC_WAITFORSTDERR, RTGETOPT_REQ_NOTHING }
1026 };
1027
1028 int ch;
1029 RTGETOPTUNION ValueUnion;
1030 RTGETOPTSTATE GetState;
1031 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv, s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, 0);
1032
1033 Utf8Str strCmd;
1034 com::SafeArray<ProcessCreateFlag_T> aCreateFlags;
1035 com::SafeArray<ProcessWaitForFlag_T> aWaitFlags;
1036 com::SafeArray<IN_BSTR> aArgs;
1037 com::SafeArray<IN_BSTR> aEnv;
1038 RTMSINTERVAL cMsTimeout = 0;
1039 OUTPUTTYPE eOutputType = OUTPUTTYPE_UNDEFINED;
1040 bool fDetached = true;
1041 int vrc = VINF_SUCCESS;
1042
1043 try
1044 {
1045 /* Wait for process start in any case. This is useful for scripting VBoxManage
1046 * when relying on its overall exit code. */
1047 aWaitFlags.push_back(ProcessWaitForFlag_Start);
1048
1049 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1050 && RT_SUCCESS(vrc))
1051 {
1052 /* For options that require an argument, ValueUnion has received the value. */
1053 switch (ch)
1054 {
1055 case GETOPTDEF_EXEC_DOS2UNIX:
1056 if (eOutputType != OUTPUTTYPE_UNDEFINED)
1057 return errorSyntax(USAGE_GUESTCONTROL, "More than one output type (dos2unix/unix2dos) specified!");
1058 eOutputType = OUTPUTTYPE_DOS2UNIX;
1059 break;
1060
1061 case 'e': /* Environment */
1062 {
1063 char **papszArg;
1064 int cArgs;
1065
1066 vrc = RTGetOptArgvFromString(&papszArg, &cArgs, ValueUnion.psz, NULL);
1067 if (RT_FAILURE(vrc))
1068 return errorSyntax(USAGE_GUESTCONTROL, "Failed to parse environment value, rc=%Rrc", vrc);
1069 for (int j = 0; j < cArgs; j++)
1070 aEnv.push_back(Bstr(papszArg[j]).raw());
1071
1072 RTGetOptArgvFree(papszArg);
1073 break;
1074 }
1075
1076 case GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES:
1077 aCreateFlags.push_back(ProcessCreateFlag_IgnoreOrphanedProcesses);
1078 break;
1079
1080 case GETOPTDEF_EXEC_NO_PROFILE:
1081 aCreateFlags.push_back(ProcessCreateFlag_NoProfile);
1082 break;
1083
1084 case 'i':
1085 strCmd = ValueUnion.psz;
1086 break;
1087
1088 /** @todo Add a hidden flag. */
1089
1090 case 't': /* Timeout */
1091 cMsTimeout = ValueUnion.u32;
1092 break;
1093
1094 case GETOPTDEF_EXEC_UNIX2DOS:
1095 if (eOutputType != OUTPUTTYPE_UNDEFINED)
1096 return errorSyntax(USAGE_GUESTCONTROL, "More than one output type (dos2unix/unix2dos) specified!");
1097 eOutputType = OUTPUTTYPE_UNIX2DOS;
1098 break;
1099
1100 case GETOPTDEF_EXEC_WAITFOREXIT:
1101 aWaitFlags.push_back(ProcessWaitForFlag_Terminate);
1102 fDetached = false;
1103 break;
1104
1105 case GETOPTDEF_EXEC_WAITFORSTDOUT:
1106 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut);
1107 aWaitFlags.push_back(ProcessWaitForFlag_StdOut);
1108 fDetached = false;
1109 break;
1110
1111 case GETOPTDEF_EXEC_WAITFORSTDERR:
1112 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr);
1113 aWaitFlags.push_back(ProcessWaitForFlag_StdErr);
1114 fDetached = false;
1115 break;
1116
1117 case VINF_GETOPT_NOT_OPTION:
1118 if (aArgs.size() == 0 && strCmd.isEmpty())
1119 strCmd = ValueUnion.psz;
1120 else
1121 aArgs.push_back(Bstr(ValueUnion.psz).raw());
1122 break;
1123
1124 default:
1125 return RTGetOptPrintError(ch, &ValueUnion);
1126 break;
1127
1128 } /* switch */
1129 } /* while RTGetOpt */
1130 }
1131 catch (std::bad_alloc &)
1132 {
1133 vrc = VERR_NO_MEMORY;
1134 }
1135
1136 if (RT_FAILURE(vrc))
1137 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize, rc=%Rrc\n", vrc);
1138
1139 if (strCmd.isEmpty())
1140 return errorSyntax(USAGE_GUESTCONTROL, "No command to execute specified!");
1141
1142 /** @todo Any output conversion not supported yet! */
1143 if (eOutputType != OUTPUTTYPE_UNDEFINED)
1144 return errorSyntax(USAGE_GUESTCONTROL, "Output conversion not implemented yet!");
1145
1146 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1147 HRESULT rc;
1148
1149 try
1150 {
1151 do
1152 {
1153 /* Adjust process creation flags if we don't want to wait for process termination. */
1154 if (fDetached)
1155 aCreateFlags.push_back(ProcessCreateFlag_WaitForProcessStartOnly);
1156
1157 /* Get current time stamp to later calculate rest of timeout left. */
1158 uint64_t u64StartMS = RTTimeMilliTS();
1159
1160 /*
1161 * Wait for guest session to start.
1162 */
1163 if (pCtx->fVerbose)
1164 {
1165 if (cMsTimeout == 0)
1166 RTPrintf("Waiting for guest session to start ...\n");
1167 else
1168 RTPrintf("Waiting for guest session to start (within %ums)\n", cMsTimeout);
1169 }
1170
1171 com::SafeArray<GuestSessionWaitForFlag_T> aSessionWaitFlags;
1172 aSessionWaitFlags.push_back(GuestSessionWaitForFlag_Start);
1173 GuestSessionWaitResult_T sessionWaitResult;
1174 CHECK_ERROR_BREAK(g_pGuestSession, WaitForArray(ComSafeArrayAsInParam(aSessionWaitFlags), cMsTimeout, &sessionWaitResult));
1175 ULONG uSessionID;
1176 CHECK_ERROR_BREAK(g_pGuestSession, COMGETTER(Id)(&uSessionID));
1177
1178 if ( sessionWaitResult == GuestSessionWaitResult_Start
1179 /* Note: This might happen when Guest Additions < 4.3 are installed which don't
1180 * support dedicated guest sessions. */
1181 || sessionWaitResult == GuestSessionWaitResult_WaitFlagNotSupported)
1182 {
1183 if (pCtx->fVerbose)
1184 RTPrintf("Guest session (ID %RU32) has been started\n", uSessionID);
1185 }
1186 else
1187 {
1188 RTPrintf("Error starting guest session\n");
1189 break;
1190 }
1191
1192 if (pCtx->fVerbose)
1193 {
1194 if (cMsTimeout == 0)
1195 RTPrintf("Waiting for guest process to start ...\n");
1196 else
1197 RTPrintf("Waiting for guest process to start (within %ums)\n", cMsTimeout);
1198 }
1199
1200 /*
1201 * Execute the process.
1202 */
1203 ComPtr<IGuestProcess> pProcess;
1204 CHECK_ERROR_BREAK(g_pGuestSession, ProcessCreate(Bstr(strCmd).raw(),
1205 ComSafeArrayAsInParam(aArgs),
1206 ComSafeArrayAsInParam(aEnv),
1207 ComSafeArrayAsInParam(aCreateFlags),
1208 cMsTimeout,
1209 pProcess.asOutParam()));
1210
1211 /** @todo does this need signal handling? there's no progress object etc etc */
1212
1213 vrc = RTStrmSetMode(g_pStdOut, 1 /* Binary mode */, -1 /* Code set, unchanged */);
1214 if (RT_FAILURE(vrc))
1215 RTMsgError("Unable to set stdout's binary mode, rc=%Rrc\n", vrc);
1216 vrc = RTStrmSetMode(g_pStdErr, 1 /* Binary mode */, -1 /* Code set, unchanged */);
1217 if (RT_FAILURE(vrc))
1218 RTMsgError("Unable to set stderr's binary mode, rc=%Rrc\n", vrc);
1219
1220 /* Wait for process to exit ... */
1221 RTMSINTERVAL cMsTimeLeft = 1;
1222 bool fReadStdOut, fReadStdErr;
1223 fReadStdOut = fReadStdErr = false;
1224
1225 bool fCompleted = false;
1226 while (!fCompleted && cMsTimeLeft != 0)
1227 {
1228 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
1229 ProcessWaitResult_T waitResult;
1230 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitFlags),
1231 cMsTimeLeft, &waitResult));
1232 switch (waitResult)
1233 {
1234 case ProcessWaitResult_Start:
1235 {
1236 ULONG uPID = 0;
1237 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
1238 if (pCtx->fVerbose)
1239 {
1240 RTPrintf("Process '%s' (PID %RU32) started\n",
1241 strCmd.c_str(), uPID);
1242 }
1243 else /** @todo Introduce a --quiet option for not printing this. */
1244 {
1245 /* Just print plain PID to make it easier for scripts
1246 * invoking VBoxManage. */
1247 RTPrintf("%RU32, session ID %RU32\n", uPID, uSessionID);
1248 }
1249
1250 /* We're done here if we don't want to wait for termination. */
1251 if (fDetached)
1252 fCompleted = true;
1253
1254 break;
1255 }
1256 case ProcessWaitResult_StdOut:
1257 fReadStdOut = true;
1258 break;
1259 case ProcessWaitResult_StdErr:
1260 fReadStdErr = true;
1261 break;
1262 case ProcessWaitResult_Terminate:
1263 /* Process terminated, we're done */
1264 fCompleted = true;
1265 break;
1266 case ProcessWaitResult_WaitFlagNotSupported:
1267 {
1268 /* The guest does not support waiting for stdout/err, so
1269 * yield to reduce the CPU load due to busy waiting. */
1270 RTThreadYield(); /* Optional, don't check rc. */
1271
1272 /* Try both, stdout + stderr. */
1273 fReadStdOut = fReadStdErr = true;
1274 break;
1275 }
1276 default:
1277 /* Ignore all other results, let the timeout expire */
1278 break;
1279 }
1280
1281 if (g_fGuestCtrlCanceled)
1282 break;
1283
1284 if (fReadStdOut) /* Do we need to fetch stdout data? */
1285 {
1286 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
1287 vrc = ctrlExecPrintOutput(pProcess, g_pStdOut,
1288 1 /* StdOut */, cMsTimeLeft);
1289 fReadStdOut = false;
1290 }
1291
1292 if (fReadStdErr) /* Do we need to fetch stdout data? */
1293 {
1294 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
1295 vrc = ctrlExecPrintOutput(pProcess, g_pStdErr,
1296 2 /* StdErr */, cMsTimeLeft);
1297 fReadStdErr = false;
1298 }
1299
1300 if ( RT_FAILURE(vrc)
1301 || g_fGuestCtrlCanceled)
1302 break;
1303
1304 /* Did we run out of time? */
1305 if ( cMsTimeout
1306 && RTTimeMilliTS() - u64StartMS > cMsTimeout)
1307 break;
1308
1309 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
1310
1311 } /* while */
1312
1313 /* Report status back to the user. */
1314 if ( fCompleted
1315 && !g_fGuestCtrlCanceled)
1316 {
1317 ProcessStatus_T procStatus;
1318 CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&procStatus));
1319 if ( procStatus == ProcessStatus_TerminatedNormally
1320 || procStatus == ProcessStatus_TerminatedAbnormally
1321 || procStatus == ProcessStatus_TerminatedSignal)
1322 {
1323 LONG exitCode;
1324 CHECK_ERROR_BREAK(pProcess, COMGETTER(ExitCode)(&exitCode));
1325 if (pCtx->fVerbose)
1326 RTPrintf("Exit code=%u (Status=%u [%s])\n",
1327 exitCode, procStatus, ctrlProcessStatusToText(procStatus));
1328
1329 rcExit = (RTEXITCODE)ctrlExecProcessStatusToExitCode(procStatus, exitCode);
1330 }
1331 else if (pCtx->fVerbose)
1332 RTPrintf("Process now is in status [%s]\n", ctrlProcessStatusToText(procStatus));
1333 }
1334 else
1335 {
1336 if (pCtx->fVerbose)
1337 RTPrintf("Process execution aborted!\n");
1338
1339 rcExit = (RTEXITCODE)EXITCODEEXEC_TERM_ABEND;
1340 }
1341
1342 } while (0);
1343 }
1344 catch (std::bad_alloc)
1345 {
1346 rc = E_OUTOFMEMORY;
1347 }
1348
1349 bool fCloseSession = false;
1350 if (SUCCEEDED(rc))
1351 {
1352 /*
1353 * Only close the guest session if we waited for the guest
1354 * process to exit. Otherwise we wouldn't have any chance to
1355 * access and/or kill detached guest process lateron.
1356 */
1357 fCloseSession = !fDetached;
1358 }
1359 else /* Close session on error. */
1360 fCloseSession = true;
1361
1362 if (!fCloseSession)
1363 pCtx->uFlags |= CTLCMDCTX_FLAGS_SESSION_DETACH;
1364
1365 if ( rcExit == RTEXITCODE_SUCCESS
1366 && FAILED(rc))
1367 {
1368 /* Make sure an appropriate exit code is set on error. */
1369 rcExit = RTEXITCODE_FAILURE;
1370 }
1371
1372 return rcExit;
1373}
1374
1375/**
1376 * Creates a copy context structure which then can be used with various
1377 * guest control copy functions. Needs to be free'd with ctrlCopyContextFree().
1378 *
1379 * @return IPRT status code.
1380 * @param pCtx Pointer to command context.
1381 * @param fDryRun Flag indicating if we want to run a dry run only.
1382 * @param fHostToGuest Flag indicating if we want to copy from host to guest
1383 * or vice versa.
1384 * @param strSessionName Session name (only for identification purposes).
1385 * @param ppContext Pointer which receives the allocated copy context.
1386 */
1387static int ctrlCopyContextCreate(PGCTLCMDCTX pCtx, bool fDryRun, bool fHostToGuest,
1388 const Utf8Str &strSessionName,
1389 PCOPYCONTEXT *ppContext)
1390{
1391 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1392
1393 PCOPYCONTEXT pContext = new COPYCONTEXT();
1394 AssertPtrReturn(pContext, VERR_NO_MEMORY); /**< @todo r=klaus cannot happen with new */
1395 ComPtr<IGuestSession> pGuestSession;
1396 HRESULT rc = pCtx->pGuest->CreateSession(Bstr(pCtx->strUsername).raw(),
1397 Bstr(pCtx->strPassword).raw(),
1398 Bstr(pCtx->strDomain).raw(),
1399 Bstr(strSessionName).raw(),
1400 pGuestSession.asOutParam());
1401 if (FAILED(rc))
1402 return ctrlPrintError(pCtx->pGuest, COM_IIDOF(IGuest));
1403
1404 pContext->pCmdCtx = pCtx;
1405 pContext->fDryRun = fDryRun;
1406 pContext->fHostToGuest = fHostToGuest;
1407 pContext->pGuestSession = pGuestSession;
1408
1409 *ppContext = pContext;
1410
1411 return VINF_SUCCESS;
1412}
1413
1414/**
1415 * Frees are previously allocated copy context structure.
1416 *
1417 * @param pContext Pointer to copy context to free.
1418 */
1419static void ctrlCopyContextFree(PCOPYCONTEXT pContext)
1420{
1421 if (pContext)
1422 {
1423 if (pContext->pGuestSession)
1424 pContext->pGuestSession->Close();
1425 delete pContext;
1426 }
1427}
1428
1429/**
1430 * Translates a source path to a destination path (can be both sides,
1431 * either host or guest). The source root is needed to determine the start
1432 * of the relative source path which also needs to present in the destination
1433 * path.
1434 *
1435 * @return IPRT status code.
1436 * @param pszSourceRoot Source root path. No trailing directory slash!
1437 * @param pszSource Actual source to transform. Must begin with
1438 * the source root path!
1439 * @param pszDest Destination path.
1440 * @param ppszTranslated Pointer to the allocated, translated destination
1441 * path. Must be free'd with RTStrFree().
1442 */
1443static int ctrlCopyTranslatePath(const char *pszSourceRoot, const char *pszSource,
1444 const char *pszDest, char **ppszTranslated)
1445{
1446 AssertPtrReturn(pszSourceRoot, VERR_INVALID_POINTER);
1447 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1448 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1449 AssertPtrReturn(ppszTranslated, VERR_INVALID_POINTER);
1450#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} */
1451 AssertReturn(RTPathStartsWith(pszSource, pszSourceRoot), VERR_INVALID_PARAMETER);
1452#endif
1453
1454 /* Construct the relative dest destination path by "subtracting" the
1455 * source from the source root, e.g.
1456 *
1457 * source root path = "e:\foo\", source = "e:\foo\bar"
1458 * dest = "d:\baz\"
1459 * translated = "d:\baz\bar\"
1460 */
1461 char szTranslated[RTPATH_MAX];
1462 size_t srcOff = strlen(pszSourceRoot);
1463 AssertReturn(srcOff, VERR_INVALID_PARAMETER);
1464
1465 char *pszDestPath = RTStrDup(pszDest);
1466 AssertPtrReturn(pszDestPath, VERR_NO_MEMORY);
1467
1468 int vrc;
1469 if (!RTPathFilename(pszDestPath))
1470 {
1471 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1472 pszDestPath, &pszSource[srcOff]);
1473 }
1474 else
1475 {
1476 char *pszDestFileName = RTStrDup(RTPathFilename(pszDestPath));
1477 if (pszDestFileName)
1478 {
1479 RTPathStripFilename(pszDestPath);
1480 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1481 pszDestPath, pszDestFileName);
1482 RTStrFree(pszDestFileName);
1483 }
1484 else
1485 vrc = VERR_NO_MEMORY;
1486 }
1487 RTStrFree(pszDestPath);
1488
1489 if (RT_SUCCESS(vrc))
1490 {
1491 *ppszTranslated = RTStrDup(szTranslated);
1492#if 0
1493 RTPrintf("Root: %s, Source: %s, Dest: %s, Translated: %s\n",
1494 pszSourceRoot, pszSource, pszDest, *ppszTranslated);
1495#endif
1496 }
1497 return vrc;
1498}
1499
1500#ifdef DEBUG_andy
1501static int tstTranslatePath()
1502{
1503 RTAssertSetMayPanic(false /* Do not freak out, please. */);
1504
1505 static struct
1506 {
1507 const char *pszSourceRoot;
1508 const char *pszSource;
1509 const char *pszDest;
1510 const char *pszTranslated;
1511 int iResult;
1512 } aTests[] =
1513 {
1514 /* Invalid stuff. */
1515 { NULL, NULL, NULL, NULL, VERR_INVALID_POINTER },
1516#ifdef RT_OS_WINDOWS
1517 /* Windows paths. */
1518 { "c:\\foo", "c:\\foo\\bar.txt", "c:\\test", "c:\\test\\bar.txt", VINF_SUCCESS },
1519 { "c:\\foo", "c:\\foo\\baz\\bar.txt", "c:\\test", "c:\\test\\baz\\bar.txt", VINF_SUCCESS },
1520#else /* RT_OS_WINDOWS */
1521 { "/home/test/foo", "/home/test/foo/bar.txt", "/opt/test", "/opt/test/bar.txt", VINF_SUCCESS },
1522 { "/home/test/foo", "/home/test/foo/baz/bar.txt", "/opt/test", "/opt/test/baz/bar.txt", VINF_SUCCESS },
1523#endif /* !RT_OS_WINDOWS */
1524 /* Mixed paths*/
1525 /** @todo */
1526 { NULL }
1527 };
1528
1529 size_t iTest = 0;
1530 for (iTest; iTest < RT_ELEMENTS(aTests); iTest++)
1531 {
1532 RTPrintf("=> Test %d\n", iTest);
1533 RTPrintf("\tSourceRoot=%s, Source=%s, Dest=%s\n",
1534 aTests[iTest].pszSourceRoot, aTests[iTest].pszSource, aTests[iTest].pszDest);
1535
1536 char *pszTranslated = NULL;
1537 int iResult = ctrlCopyTranslatePath(aTests[iTest].pszSourceRoot, aTests[iTest].pszSource,
1538 aTests[iTest].pszDest, &pszTranslated);
1539 if (iResult != aTests[iTest].iResult)
1540 {
1541 RTPrintf("\tReturned %Rrc, expected %Rrc\n",
1542 iResult, aTests[iTest].iResult);
1543 }
1544 else if ( pszTranslated
1545 && strcmp(pszTranslated, aTests[iTest].pszTranslated))
1546 {
1547 RTPrintf("\tReturned translated path %s, expected %s\n",
1548 pszTranslated, aTests[iTest].pszTranslated);
1549 }
1550
1551 if (pszTranslated)
1552 {
1553 RTPrintf("\tTranslated=%s\n", pszTranslated);
1554 RTStrFree(pszTranslated);
1555 }
1556 }
1557
1558 return VINF_SUCCESS; /* @todo */
1559}
1560#endif
1561
1562/**
1563 * Creates a directory on the destination, based on the current copy
1564 * context.
1565 *
1566 * @return IPRT status code.
1567 * @param pContext Pointer to current copy control context.
1568 * @param pszDir Directory to create.
1569 */
1570static int ctrlCopyDirCreate(PCOPYCONTEXT pContext, const char *pszDir)
1571{
1572 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1573 AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
1574
1575 bool fDirExists;
1576 int vrc = ctrlCopyDirExists(pContext, pContext->fHostToGuest, pszDir, &fDirExists);
1577 if ( RT_SUCCESS(vrc)
1578 && fDirExists)
1579 {
1580 if (pContext->pCmdCtx->fVerbose)
1581 RTPrintf("Directory \"%s\" already exists\n", pszDir);
1582 return VINF_SUCCESS;
1583 }
1584
1585 /* If querying for a directory existence fails there's no point of even trying
1586 * to create such a directory. */
1587 if (RT_FAILURE(vrc))
1588 return vrc;
1589
1590 if (pContext->pCmdCtx->fVerbose)
1591 RTPrintf("Creating directory \"%s\" ...\n", pszDir);
1592
1593 if (pContext->fDryRun)
1594 return VINF_SUCCESS;
1595
1596 if (pContext->fHostToGuest) /* We want to create directories on the guest. */
1597 {
1598 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
1599 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
1600 HRESULT rc = pContext->pGuestSession->DirectoryCreate(Bstr(pszDir).raw(),
1601 0700, ComSafeArrayAsInParam(dirCreateFlags));
1602 if (FAILED(rc))
1603 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1604 }
1605 else /* ... or on the host. */
1606 {
1607 vrc = RTDirCreateFullPath(pszDir, 0700);
1608 if (vrc == VERR_ALREADY_EXISTS)
1609 vrc = VINF_SUCCESS;
1610 }
1611 return vrc;
1612}
1613
1614/**
1615 * Checks whether a specific host/guest directory exists.
1616 *
1617 * @return IPRT status code.
1618 * @param pContext Pointer to current copy control context.
1619 * @param bGuest true if directory needs to be checked on the guest
1620 * or false if on the host.
1621 * @param pszDir Actual directory to check.
1622 * @param fExists Pointer which receives the result if the
1623 * given directory exists or not.
1624 */
1625static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest,
1626 const char *pszDir, bool *fExists)
1627{
1628 AssertPtrReturn(pContext, false);
1629 AssertPtrReturn(pszDir, false);
1630 AssertPtrReturn(fExists, false);
1631
1632 int vrc = VINF_SUCCESS;
1633 if (bGuest)
1634 {
1635 BOOL fDirExists = FALSE;
1636 HRESULT rc = pContext->pGuestSession->DirectoryExists(Bstr(pszDir).raw(), &fDirExists);
1637 if (FAILED(rc))
1638 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1639 else
1640 *fExists = fDirExists ? true : false;
1641 }
1642 else
1643 *fExists = RTDirExists(pszDir);
1644 return vrc;
1645}
1646
1647/**
1648 * Checks whether a specific directory exists on the destination, based
1649 * on the current copy context.
1650 *
1651 * @return IPRT status code.
1652 * @param pContext Pointer to current copy control context.
1653 * @param pszDir Actual directory to check.
1654 * @param fExists Pointer which receives the result if the
1655 * given directory exists or not.
1656 */
1657static int ctrlCopyDirExistsOnDest(PCOPYCONTEXT pContext, const char *pszDir,
1658 bool *fExists)
1659{
1660 return ctrlCopyDirExists(pContext, pContext->fHostToGuest,
1661 pszDir, fExists);
1662}
1663
1664/**
1665 * Checks whether a specific directory exists on the source, based
1666 * on the current copy context.
1667 *
1668 * @return IPRT status code.
1669 * @param pContext Pointer to current copy control context.
1670 * @param pszDir Actual directory to check.
1671 * @param fExists Pointer which receives the result if the
1672 * given directory exists or not.
1673 */
1674static int ctrlCopyDirExistsOnSource(PCOPYCONTEXT pContext, const char *pszDir,
1675 bool *fExists)
1676{
1677 return ctrlCopyDirExists(pContext, !pContext->fHostToGuest,
1678 pszDir, fExists);
1679}
1680
1681/**
1682 * Checks whether a specific host/guest file exists.
1683 *
1684 * @return IPRT status code.
1685 * @param pContext Pointer to current copy control context.
1686 * @param bGuest true if file needs to be checked on the guest
1687 * or false if on the host.
1688 * @param pszFile Actual file to check.
1689 * @param fExists Pointer which receives the result if the
1690 * given file exists or not.
1691 */
1692static int ctrlCopyFileExists(PCOPYCONTEXT pContext, bool bOnGuest,
1693 const char *pszFile, bool *fExists)
1694{
1695 AssertPtrReturn(pContext, false);
1696 AssertPtrReturn(pszFile, false);
1697 AssertPtrReturn(fExists, false);
1698
1699 int vrc = VINF_SUCCESS;
1700 if (bOnGuest)
1701 {
1702 BOOL fFileExists = FALSE;
1703 HRESULT rc = pContext->pGuestSession->FileExists(Bstr(pszFile).raw(), &fFileExists);
1704 if (FAILED(rc))
1705 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1706 else
1707 *fExists = fFileExists ? true : false;
1708 }
1709 else
1710 *fExists = RTFileExists(pszFile);
1711 return vrc;
1712}
1713
1714/**
1715 * Checks whether a specific file exists on the destination, based on the
1716 * current copy context.
1717 *
1718 * @return IPRT status code.
1719 * @param pContext Pointer to current copy control context.
1720 * @param pszFile Actual file to check.
1721 * @param fExists Pointer which receives the result if the
1722 * given file exists or not.
1723 */
1724static int ctrlCopyFileExistsOnDest(PCOPYCONTEXT pContext, const char *pszFile,
1725 bool *fExists)
1726{
1727 return ctrlCopyFileExists(pContext, pContext->fHostToGuest,
1728 pszFile, fExists);
1729}
1730
1731/**
1732 * Checks whether a specific file exists on the source, based on the
1733 * current copy context.
1734 *
1735 * @return IPRT status code.
1736 * @param pContext Pointer to current copy control context.
1737 * @param pszFile Actual file to check.
1738 * @param fExists Pointer which receives the result if the
1739 * given file exists or not.
1740 */
1741static int ctrlCopyFileExistsOnSource(PCOPYCONTEXT pContext, const char *pszFile,
1742 bool *fExists)
1743{
1744 return ctrlCopyFileExists(pContext, !pContext->fHostToGuest,
1745 pszFile, fExists);
1746}
1747
1748/**
1749 * Copies a source file to the destination.
1750 *
1751 * @return IPRT status code.
1752 * @param pContext Pointer to current copy control context.
1753 * @param pszFileSource Source file to copy to the destination.
1754 * @param pszFileDest Name of copied file on the destination.
1755 * @param fFlags Copy flags. No supported at the moment and needs
1756 * to be set to 0.
1757 */
1758static int ctrlCopyFileToDest(PCOPYCONTEXT pContext, const char *pszFileSource,
1759 const char *pszFileDest, uint32_t fFlags)
1760{
1761 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1762 AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
1763 AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
1764 AssertReturn(!fFlags, VERR_INVALID_POINTER); /* No flags supported yet. */
1765
1766 if (pContext->pCmdCtx->fVerbose)
1767 RTPrintf("Copying \"%s\" to \"%s\" ...\n",
1768 pszFileSource, pszFileDest);
1769
1770 if (pContext->fDryRun)
1771 return VINF_SUCCESS;
1772
1773 int vrc = VINF_SUCCESS;
1774 ComPtr<IProgress> pProgress;
1775 HRESULT rc;
1776 if (pContext->fHostToGuest)
1777 {
1778 SafeArray<CopyFileFlag_T> copyFlags;
1779 rc = pContext->pGuestSession->CopyTo(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1780 ComSafeArrayAsInParam(copyFlags),
1781
1782 pProgress.asOutParam());
1783 }
1784 else
1785 {
1786 SafeArray<CopyFileFlag_T> copyFlags;
1787 rc = pContext->pGuestSession->CopyFrom(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1788 ComSafeArrayAsInParam(copyFlags),
1789 pProgress.asOutParam());
1790 }
1791
1792 if (FAILED(rc))
1793 {
1794 vrc = ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
1795 }
1796 else
1797 {
1798 if (pContext->pCmdCtx->fVerbose)
1799 rc = showProgress(pProgress);
1800 else
1801 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
1802 if (SUCCEEDED(rc))
1803 CHECK_PROGRESS_ERROR(pProgress, ("File copy failed"));
1804 vrc = ctrlPrintProgressError(pProgress);
1805 }
1806
1807 return vrc;
1808}
1809
1810/**
1811 * Copys a directory (tree) from host to the guest.
1812 *
1813 * @return IPRT status code.
1814 * @param pContext Pointer to current copy control context.
1815 * @param pszSource Source directory on the host to copy to the guest.
1816 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1817 * @param pszDest Destination directory on the guest.
1818 * @param fFlags Copy flags, such as recursive copying.
1819 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1820 * is needed for recursion.
1821 */
1822static int ctrlCopyDirToGuest(PCOPYCONTEXT pContext,
1823 const char *pszSource, const char *pszFilter,
1824 const char *pszDest, uint32_t fFlags,
1825 const char *pszSubDir /* For recursion. */)
1826{
1827 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1828 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1829 /* Filter is optional. */
1830 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1831 /* Sub directory is optional. */
1832
1833 /*
1834 * Construct current path.
1835 */
1836 char szCurDir[RTPATH_MAX];
1837 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
1838 if (RT_SUCCESS(vrc) && pszSubDir)
1839 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
1840
1841 if (pContext->pCmdCtx->fVerbose)
1842 RTPrintf("Processing host directory: %s\n", szCurDir);
1843
1844 /* Flag indicating whether the current directory was created on the
1845 * target or not. */
1846 bool fDirCreated = false;
1847
1848 /*
1849 * Open directory without a filter - RTDirOpenFiltered unfortunately
1850 * cannot handle sub directories so we have to do the filtering ourselves.
1851 */
1852 PRTDIR pDir = NULL;
1853 if (RT_SUCCESS(vrc))
1854 {
1855 vrc = RTDirOpen(&pDir, szCurDir);
1856 if (RT_FAILURE(vrc))
1857 pDir = NULL;
1858 }
1859 if (RT_SUCCESS(vrc))
1860 {
1861 /*
1862 * Enumerate the directory tree.
1863 */
1864 while (RT_SUCCESS(vrc))
1865 {
1866 RTDIRENTRY DirEntry;
1867 vrc = RTDirRead(pDir, &DirEntry, NULL);
1868 if (RT_FAILURE(vrc))
1869 {
1870 if (vrc == VERR_NO_MORE_FILES)
1871 vrc = VINF_SUCCESS;
1872 break;
1873 }
1874 /** @todo r=bird: This ain't gonna work on most UNIX file systems because
1875 * enmType is RTDIRENTRYTYPE_UNKNOWN. This is clearly documented in
1876 * RTDIRENTRY::enmType. For trunk, RTDirQueryUnknownType can be used. */
1877 switch (DirEntry.enmType)
1878 {
1879 case RTDIRENTRYTYPE_DIRECTORY:
1880 {
1881 /* Skip "." and ".." entries. */
1882 if ( !strcmp(DirEntry.szName, ".")
1883 || !strcmp(DirEntry.szName, ".."))
1884 break;
1885
1886 if (pContext->pCmdCtx->fVerbose)
1887 RTPrintf("Directory: %s\n", DirEntry.szName);
1888
1889 if (fFlags & CopyFileFlag_Recursive)
1890 {
1891 char *pszNewSub = NULL;
1892 if (pszSubDir)
1893 pszNewSub = RTPathJoinA(pszSubDir, DirEntry.szName);
1894 else
1895 {
1896 pszNewSub = RTStrDup(DirEntry.szName);
1897 RTPathStripTrailingSlash(pszNewSub);
1898 }
1899
1900 if (pszNewSub)
1901 {
1902 vrc = ctrlCopyDirToGuest(pContext,
1903 pszSource, pszFilter,
1904 pszDest, fFlags, pszNewSub);
1905 RTStrFree(pszNewSub);
1906 }
1907 else
1908 vrc = VERR_NO_MEMORY;
1909 }
1910 break;
1911 }
1912
1913 case RTDIRENTRYTYPE_SYMLINK:
1914 if ( (fFlags & CopyFileFlag_Recursive)
1915 && (fFlags & CopyFileFlag_FollowLinks))
1916 {
1917 /* Fall through to next case is intentional. */
1918 }
1919 else
1920 break;
1921
1922 case RTDIRENTRYTYPE_FILE:
1923 {
1924 if ( pszFilter
1925 && !RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
1926 {
1927 break; /* Filter does not match. */
1928 }
1929
1930 if (pContext->pCmdCtx->fVerbose)
1931 RTPrintf("File: %s\n", DirEntry.szName);
1932
1933 if (!fDirCreated)
1934 {
1935 char *pszDestDir;
1936 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
1937 pszDest, &pszDestDir);
1938 if (RT_SUCCESS(vrc))
1939 {
1940 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
1941 RTStrFree(pszDestDir);
1942
1943 fDirCreated = true;
1944 }
1945 }
1946
1947 if (RT_SUCCESS(vrc))
1948 {
1949 char *pszFileSource = RTPathJoinA(szCurDir, DirEntry.szName);
1950 if (pszFileSource)
1951 {
1952 char *pszFileDest;
1953 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
1954 pszDest, &pszFileDest);
1955 if (RT_SUCCESS(vrc))
1956 {
1957 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
1958 pszFileDest, 0 /* Flags */);
1959 RTStrFree(pszFileDest);
1960 }
1961 RTStrFree(pszFileSource);
1962 }
1963 }
1964 break;
1965 }
1966
1967 default:
1968 break;
1969 }
1970 if (RT_FAILURE(vrc))
1971 break;
1972 }
1973
1974 RTDirClose(pDir);
1975 }
1976 return vrc;
1977}
1978
1979/**
1980 * Copys a directory (tree) from guest to the host.
1981 *
1982 * @return IPRT status code.
1983 * @param pContext Pointer to current copy control context.
1984 * @param pszSource Source directory on the guest to copy to the host.
1985 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1986 * @param pszDest Destination directory on the host.
1987 * @param fFlags Copy flags, such as recursive copying.
1988 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1989 * is needed for recursion.
1990 */
1991static int ctrlCopyDirToHost(PCOPYCONTEXT pContext,
1992 const char *pszSource, const char *pszFilter,
1993 const char *pszDest, uint32_t fFlags,
1994 const char *pszSubDir /* For recursion. */)
1995{
1996 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1997 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1998 /* Filter is optional. */
1999 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
2000 /* Sub directory is optional. */
2001
2002 /*
2003 * Construct current path.
2004 */
2005 char szCurDir[RTPATH_MAX];
2006 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
2007 if (RT_SUCCESS(vrc) && pszSubDir)
2008 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
2009
2010 if (RT_FAILURE(vrc))
2011 return vrc;
2012
2013 if (pContext->pCmdCtx->fVerbose)
2014 RTPrintf("Processing guest directory: %s\n", szCurDir);
2015
2016 /* Flag indicating whether the current directory was created on the
2017 * target or not. */
2018 bool fDirCreated = false;
2019 SafeArray<DirectoryOpenFlag_T> dirOpenFlags; /* No flags supported yet. */
2020 ComPtr<IGuestDirectory> pDirectory;
2021 HRESULT rc = pContext->pGuestSession->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(),
2022 ComSafeArrayAsInParam(dirOpenFlags),
2023 pDirectory.asOutParam());
2024 if (FAILED(rc))
2025 return ctrlPrintError(pContext->pGuestSession, COM_IIDOF(IGuestSession));
2026 ComPtr<IFsObjInfo> dirEntry;
2027 while (true)
2028 {
2029 rc = pDirectory->Read(dirEntry.asOutParam());
2030 if (FAILED(rc))
2031 break;
2032
2033 FsObjType_T enmType;
2034 dirEntry->COMGETTER(Type)(&enmType);
2035
2036 Bstr strName;
2037 dirEntry->COMGETTER(Name)(strName.asOutParam());
2038
2039 switch (enmType)
2040 {
2041 case FsObjType_Directory:
2042 {
2043 Assert(!strName.isEmpty());
2044
2045 /* Skip "." and ".." entries. */
2046 if ( !strName.compare(Bstr("."))
2047 || !strName.compare(Bstr("..")))
2048 break;
2049
2050 if (pContext->pCmdCtx->fVerbose)
2051 {
2052 Utf8Str strDir(strName);
2053 RTPrintf("Directory: %s\n", strDir.c_str());
2054 }
2055
2056 if (fFlags & CopyFileFlag_Recursive)
2057 {
2058 Utf8Str strDir(strName);
2059 char *pszNewSub = NULL;
2060 if (pszSubDir)
2061 pszNewSub = RTPathJoinA(pszSubDir, strDir.c_str());
2062 else
2063 {
2064 pszNewSub = RTStrDup(strDir.c_str());
2065 RTPathStripTrailingSlash(pszNewSub);
2066 }
2067 if (pszNewSub)
2068 {
2069 vrc = ctrlCopyDirToHost(pContext,
2070 pszSource, pszFilter,
2071 pszDest, fFlags, pszNewSub);
2072 RTStrFree(pszNewSub);
2073 }
2074 else
2075 vrc = VERR_NO_MEMORY;
2076 }
2077 break;
2078 }
2079
2080 case FsObjType_Symlink:
2081 if ( (fFlags & CopyFileFlag_Recursive)
2082 && (fFlags & CopyFileFlag_FollowLinks))
2083 {
2084 /* Fall through to next case is intentional. */
2085 }
2086 else
2087 break;
2088
2089 case FsObjType_File:
2090 {
2091 Assert(!strName.isEmpty());
2092
2093 Utf8Str strFile(strName);
2094 if ( pszFilter
2095 && !RTStrSimplePatternMatch(pszFilter, strFile.c_str()))
2096 {
2097 break; /* Filter does not match. */
2098 }
2099
2100 if (pContext->pCmdCtx->fVerbose)
2101 RTPrintf("File: %s\n", strFile.c_str());
2102
2103 if (!fDirCreated)
2104 {
2105 char *pszDestDir;
2106 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
2107 pszDest, &pszDestDir);
2108 if (RT_SUCCESS(vrc))
2109 {
2110 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
2111 RTStrFree(pszDestDir);
2112
2113 fDirCreated = true;
2114 }
2115 }
2116
2117 if (RT_SUCCESS(vrc))
2118 {
2119 char *pszFileSource = RTPathJoinA(szCurDir, strFile.c_str());
2120 if (pszFileSource)
2121 {
2122 char *pszFileDest;
2123 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
2124 pszDest, &pszFileDest);
2125 if (RT_SUCCESS(vrc))
2126 {
2127 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
2128 pszFileDest, 0 /* Flags */);
2129 RTStrFree(pszFileDest);
2130 }
2131 RTStrFree(pszFileSource);
2132 }
2133 else
2134 vrc = VERR_NO_MEMORY;
2135 }
2136 break;
2137 }
2138
2139 default:
2140 RTPrintf("Warning: Directory entry of type %ld not handled, skipping ...\n",
2141 enmType);
2142 break;
2143 }
2144
2145 if (RT_FAILURE(vrc))
2146 break;
2147 }
2148
2149 if (RT_UNLIKELY(FAILED(rc)))
2150 {
2151 switch (rc)
2152 {
2153 case E_ABORT: /* No more directory entries left to process. */
2154 break;
2155
2156 case VBOX_E_FILE_ERROR: /* Current entry cannot be accessed to
2157 to missing rights. */
2158 {
2159 RTPrintf("Warning: Cannot access \"%s\", skipping ...\n",
2160 szCurDir);
2161 break;
2162 }
2163
2164 default:
2165 vrc = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
2166 break;
2167 }
2168 }
2169
2170 HRESULT rc2 = pDirectory->Close();
2171 if (FAILED(rc2))
2172 {
2173 int vrc2 = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
2174 if (RT_SUCCESS(vrc))
2175 vrc = vrc2;
2176 }
2177 else if (SUCCEEDED(rc))
2178 rc = rc2;
2179
2180 return vrc;
2181}
2182
2183/**
2184 * Copys a directory (tree) to the destination, based on the current copy
2185 * context.
2186 *
2187 * @return IPRT status code.
2188 * @param pContext Pointer to current copy control context.
2189 * @param pszSource Source directory to copy to the destination.
2190 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
2191 * @param pszDest Destination directory where to copy in the source
2192 * source directory.
2193 * @param fFlags Copy flags, such as recursive copying.
2194 */
2195static int ctrlCopyDirToDest(PCOPYCONTEXT pContext,
2196 const char *pszSource, const char *pszFilter,
2197 const char *pszDest, uint32_t fFlags)
2198{
2199 if (pContext->fHostToGuest)
2200 return ctrlCopyDirToGuest(pContext, pszSource, pszFilter,
2201 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
2202 return ctrlCopyDirToHost(pContext, pszSource, pszFilter,
2203 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
2204}
2205
2206/**
2207 * Creates a source root by stripping file names or filters of the specified source.
2208 *
2209 * @return IPRT status code.
2210 * @param pszSource Source to create source root for.
2211 * @param ppszSourceRoot Pointer that receives the allocated source root. Needs
2212 * to be free'd with ctrlCopyFreeSourceRoot().
2213 */
2214static int ctrlCopyCreateSourceRoot(const char *pszSource, char **ppszSourceRoot)
2215{
2216 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
2217 AssertPtrReturn(ppszSourceRoot, VERR_INVALID_POINTER);
2218
2219 char *pszNewRoot = RTStrDup(pszSource);
2220 AssertPtrReturn(pszNewRoot, VERR_NO_MEMORY);
2221
2222 size_t lenRoot = strlen(pszNewRoot);
2223 if ( lenRoot
2224 && pszNewRoot[lenRoot - 1] == '/'
2225 && pszNewRoot[lenRoot - 1] == '\\'
2226 && lenRoot > 1
2227 && pszNewRoot[lenRoot - 2] == '/'
2228 && pszNewRoot[lenRoot - 2] == '\\')
2229 {
2230 *ppszSourceRoot = pszNewRoot;
2231 if (lenRoot > 1)
2232 *ppszSourceRoot[lenRoot - 2] = '\0';
2233 *ppszSourceRoot[lenRoot - 1] = '\0';
2234 }
2235 else
2236 {
2237 /* If there's anything (like a file name or a filter),
2238 * strip it! */
2239 RTPathStripFilename(pszNewRoot);
2240 *ppszSourceRoot = pszNewRoot;
2241 }
2242
2243 return VINF_SUCCESS;
2244}
2245
2246/**
2247 * Frees a previously allocated source root.
2248 *
2249 * @return IPRT status code.
2250 * @param pszSourceRoot Source root to free.
2251 */
2252static void ctrlCopyFreeSourceRoot(char *pszSourceRoot)
2253{
2254 RTStrFree(pszSourceRoot);
2255}
2256
2257static RTEXITCODE handleCtrlCopy(PGCTLCMDCTX pCtx, bool fHostToGuest)
2258{
2259 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2260
2261 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
2262 * is much better (partly because it is much simpler of course). The main
2263 * arguments against this is that (1) all but two options conflicts with
2264 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
2265 * done windows CMD style (though not in a 100% compatible way), and (3)
2266 * that only one source is allowed - efficiently sabotaging default
2267 * wildcard expansion by a unix shell. The best solution here would be
2268 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
2269
2270 /*
2271 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
2272 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
2273 * does in here.
2274 */
2275 static const RTGETOPTDEF s_aOptions[] =
2276 {
2277 { "--dryrun", GETOPTDEF_COPY_DRYRUN, RTGETOPT_REQ_NOTHING },
2278 { "--follow", GETOPTDEF_COPY_FOLLOW, RTGETOPT_REQ_NOTHING },
2279 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
2280 { "--target-directory", GETOPTDEF_COPY_TARGETDIR, RTGETOPT_REQ_STRING }
2281 };
2282
2283 int ch;
2284 RTGETOPTUNION ValueUnion;
2285 RTGETOPTSTATE GetState;
2286 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2287 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2288
2289 Utf8Str strSource;
2290 Utf8Str strDest;
2291 uint32_t fFlags = CopyFileFlag_None;
2292 bool fCopyRecursive = false;
2293 bool fDryRun = false;
2294
2295 SOURCEVEC vecSources;
2296
2297 int vrc = VINF_SUCCESS;
2298 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2299 {
2300 /* For options that require an argument, ValueUnion has received the value. */
2301 switch (ch)
2302 {
2303 case GETOPTDEF_COPY_DRYRUN:
2304 fDryRun = true;
2305 break;
2306
2307 case GETOPTDEF_COPY_FOLLOW:
2308 fFlags |= CopyFileFlag_FollowLinks;
2309 break;
2310
2311 case 'R': /* Recursive processing */
2312 fFlags |= CopyFileFlag_Recursive;
2313 break;
2314
2315 case GETOPTDEF_COPY_TARGETDIR:
2316 strDest = ValueUnion.psz;
2317 break;
2318
2319 case VINF_GETOPT_NOT_OPTION:
2320 {
2321 /* Last argument and no destination specified with
2322 * --target-directory yet? Then use the current
2323 * (= last) argument as destination. */
2324 if ( pCtx->iArgc == GetState.iNext
2325 && strDest.isEmpty())
2326 {
2327 strDest = ValueUnion.psz;
2328 }
2329 else
2330 {
2331 /* Save the source directory. */
2332 vecSources.push_back(SOURCEFILEENTRY(ValueUnion.psz));
2333 }
2334 break;
2335 }
2336
2337 default:
2338 return RTGetOptPrintError(ch, &ValueUnion);
2339 break;
2340 }
2341 }
2342
2343 if (!vecSources.size())
2344 return errorSyntax(USAGE_GUESTCONTROL,
2345 "No source(s) specified!");
2346
2347 if (strDest.isEmpty())
2348 return errorSyntax(USAGE_GUESTCONTROL,
2349 "No destination specified!");
2350
2351 /*
2352 * Done parsing arguments, do some more preparations.
2353 */
2354 if (pCtx->fVerbose)
2355 {
2356 if (fHostToGuest)
2357 RTPrintf("Copying from host to guest ...\n");
2358 else
2359 RTPrintf("Copying from guest to host ...\n");
2360 if (fDryRun)
2361 RTPrintf("Dry run - no files copied!\n");
2362 }
2363
2364 /* Create the copy context -- it contains all information
2365 * the routines need to know when handling the actual copying. */
2366 PCOPYCONTEXT pContext = NULL;
2367 vrc = ctrlCopyContextCreate(pCtx, fDryRun, fHostToGuest,
2368 "VBoxManage Guest Control Copy", &pContext);
2369 if (RT_FAILURE(vrc))
2370 {
2371 RTMsgError("Unable to create copy context, rc=%Rrc\n", vrc);
2372 return RTEXITCODE_FAILURE;
2373 }
2374
2375 /* If the destination is a path, (try to) create it. */
2376 const char *pszDest = strDest.c_str();
2377/** @todo r=bird: RTPathFilename and RTPathStripFilename won't work
2378 * correctly on non-windows hosts when the guest is from the DOS world (Windows,
2379 * OS/2, DOS). The host doesn't know about DOS slashes, only UNIX slashes and
2380 * will get the wrong idea if some dilligent user does:
2381 *
2382 * copyto myfile.txt 'C:\guestfile.txt'
2383 * or
2384 * copyto myfile.txt 'D:guestfile.txt'
2385 *
2386 * @bugref{6344}
2387 */
2388 if (!RTPathFilename(pszDest))
2389 {
2390 vrc = ctrlCopyDirCreate(pContext, pszDest);
2391 }
2392 else
2393 {
2394 /* We assume we got a file name as destination -- so strip
2395 * the actual file name and make sure the appropriate
2396 * directories get created. */
2397 char *pszDestDir = RTStrDup(pszDest);
2398 AssertPtr(pszDestDir);
2399 RTPathStripFilename(pszDestDir);
2400 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
2401 RTStrFree(pszDestDir);
2402 }
2403
2404 if (RT_SUCCESS(vrc))
2405 {
2406 /*
2407 * Here starts the actual fun!
2408 * Handle all given sources one by one.
2409 */
2410 for (unsigned long s = 0; s < vecSources.size(); s++)
2411 {
2412 char *pszSource = RTStrDup(vecSources[s].GetSource());
2413 AssertPtrBreakStmt(pszSource, vrc = VERR_NO_MEMORY);
2414 const char *pszFilter = vecSources[s].GetFilter();
2415 if (!strlen(pszFilter))
2416 pszFilter = NULL; /* If empty filter then there's no filter :-) */
2417
2418 char *pszSourceRoot;
2419 vrc = ctrlCopyCreateSourceRoot(pszSource, &pszSourceRoot);
2420 if (RT_FAILURE(vrc))
2421 {
2422 RTMsgError("Unable to create source root, rc=%Rrc\n", vrc);
2423 break;
2424 }
2425
2426 if (pCtx->fVerbose)
2427 RTPrintf("Source: %s\n", pszSource);
2428
2429 /** @todo Files with filter?? */
2430 bool fSourceIsFile = false;
2431 bool fSourceExists;
2432
2433 size_t cchSource = strlen(pszSource);
2434 if ( cchSource > 1
2435 && RTPATH_IS_SLASH(pszSource[cchSource - 1]))
2436 {
2437 if (pszFilter) /* Directory with filter (so use source root w/o the actual filter). */
2438 vrc = ctrlCopyDirExistsOnSource(pContext, pszSourceRoot, &fSourceExists);
2439 else /* Regular directory without filter. */
2440 vrc = ctrlCopyDirExistsOnSource(pContext, pszSource, &fSourceExists);
2441
2442 if (fSourceExists)
2443 {
2444 /* Strip trailing slash from our source element so that other functions
2445 * can use this stuff properly (like RTPathStartsWith). */
2446 RTPathStripTrailingSlash(pszSource);
2447 }
2448 }
2449 else
2450 {
2451 vrc = ctrlCopyFileExistsOnSource(pContext, pszSource, &fSourceExists);
2452 if ( RT_SUCCESS(vrc)
2453 && fSourceExists)
2454 {
2455 fSourceIsFile = true;
2456 }
2457 }
2458
2459 if ( RT_SUCCESS(vrc)
2460 && fSourceExists)
2461 {
2462 if (fSourceIsFile)
2463 {
2464 /* Single file. */
2465 char *pszDestFile;
2466 vrc = ctrlCopyTranslatePath(pszSourceRoot, pszSource,
2467 strDest.c_str(), &pszDestFile);
2468 if (RT_SUCCESS(vrc))
2469 {
2470 vrc = ctrlCopyFileToDest(pContext, pszSource,
2471 pszDestFile, 0 /* Flags */);
2472 RTStrFree(pszDestFile);
2473 }
2474 else
2475 RTMsgError("Unable to translate path for \"%s\", rc=%Rrc\n",
2476 pszSource, vrc);
2477 }
2478 else
2479 {
2480 /* Directory (with filter?). */
2481 vrc = ctrlCopyDirToDest(pContext, pszSource, pszFilter,
2482 strDest.c_str(), fFlags);
2483 }
2484 }
2485
2486 ctrlCopyFreeSourceRoot(pszSourceRoot);
2487
2488 if ( RT_SUCCESS(vrc)
2489 && !fSourceExists)
2490 {
2491 RTMsgError("Warning: Source \"%s\" does not exist, skipping!\n",
2492 pszSource);
2493 RTStrFree(pszSource);
2494 continue;
2495 }
2496 else if (RT_FAILURE(vrc))
2497 {
2498 RTMsgError("Error processing \"%s\", rc=%Rrc\n",
2499 pszSource, vrc);
2500 RTStrFree(pszSource);
2501 break;
2502 }
2503
2504 RTStrFree(pszSource);
2505 }
2506 }
2507
2508 ctrlCopyContextFree(pContext);
2509
2510 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2511}
2512
2513static DECLCALLBACK(RTEXITCODE) handleCtrlCopyFrom(PGCTLCMDCTX pCtx)
2514{
2515 return handleCtrlCopy(pCtx, false /* Guest to host */);
2516}
2517
2518static DECLCALLBACK(RTEXITCODE) handleCtrlCopyTo(PGCTLCMDCTX pCtx)
2519{
2520 return handleCtrlCopy(pCtx, true /* Host to guest */);
2521}
2522
2523static DECLCALLBACK(RTEXITCODE) handleCtrlCreateDirectory(PGCTLCMDCTX pCtx)
2524{
2525 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2526
2527 /*
2528 * Parse arguments.
2529 *
2530 * Note! No direct returns here, everyone must go thru the cleanup at the
2531 * end of this function.
2532 */
2533 static const RTGETOPTDEF s_aOptions[] =
2534 {
2535 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2536 { "--parents", 'P', RTGETOPT_REQ_NOTHING }
2537 };
2538
2539 int ch;
2540 RTGETOPTUNION ValueUnion;
2541 RTGETOPTSTATE GetState;
2542 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2543 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2544
2545 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
2546 uint32_t fDirMode = 0; /* Default mode. */
2547 DESTDIRMAP mapDirs;
2548
2549 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2550 {
2551 /* For options that require an argument, ValueUnion has received the value. */
2552 switch (ch)
2553 {
2554 case 'm': /* Mode */
2555 fDirMode = ValueUnion.u32;
2556 break;
2557
2558 case 'P': /* Create parents */
2559 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
2560 break;
2561
2562 case VINF_GETOPT_NOT_OPTION:
2563 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2564 break;
2565
2566 default:
2567 return RTGetOptPrintError(ch, &ValueUnion);
2568 break;
2569 }
2570 }
2571
2572 uint32_t cDirs = mapDirs.size();
2573 if (!cDirs)
2574 return errorSyntax(USAGE_GUESTCONTROL, "No directory to create specified!");
2575
2576 /*
2577 * Create the directories.
2578 */
2579 HRESULT rc = S_OK;
2580 if (pCtx->fVerbose && cDirs)
2581 RTPrintf("Creating %RU32 directories ...\n", cDirs);
2582
2583 DESTDIRMAPITER it = mapDirs.begin();
2584 while ( (it != mapDirs.end())
2585 && !g_fGuestCtrlCanceled)
2586 {
2587 if (pCtx->fVerbose)
2588 RTPrintf("Creating directory \"%s\" ...\n", it->first.c_str());
2589
2590 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryCreate(Bstr(it->first).raw(),
2591 fDirMode, ComSafeArrayAsInParam(dirCreateFlags)));
2592 it++;
2593 }
2594
2595 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2596}
2597
2598static DECLCALLBACK(RTEXITCODE) handleCtrlRemoveDirectory(PGCTLCMDCTX pCtx)
2599{
2600 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2601
2602 /*
2603 * Parse arguments.
2604 *
2605 * Note! No direct returns here, everyone must go thru the cleanup at the
2606 * end of this function.
2607 */
2608 static const RTGETOPTDEF s_aOptions[] =
2609 {
2610 { "--recursive", GETOPTDEF_RMDIR_RECURSIVE, RTGETOPT_REQ_NOTHING }
2611 };
2612
2613 int ch;
2614 RTGETOPTUNION ValueUnion;
2615 RTGETOPTSTATE GetState;
2616 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2617 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2618
2619 bool fRecursive = false;
2620 DESTDIRMAP mapDirs;
2621
2622 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2623 {
2624 /* For options that require an argument, ValueUnion has received the value. */
2625 switch (ch)
2626 {
2627 case GETOPTDEF_RMDIR_RECURSIVE:
2628 fRecursive = true;
2629 break;
2630
2631 case VINF_GETOPT_NOT_OPTION:
2632 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2633 break;
2634
2635 default:
2636 return RTGetOptPrintError(ch, &ValueUnion);
2637 break;
2638 }
2639 }
2640
2641 uint32_t cDirs = mapDirs.size();
2642 if (!cDirs)
2643 return errorSyntax(USAGE_GUESTCONTROL, "No directory to remove specified!");
2644
2645 /*
2646 * Remove the directories.
2647 */
2648 HRESULT rc = S_OK;
2649 if (pCtx->fVerbose && cDirs)
2650 RTPrintf("Removing %RU32 directories ...\n", cDirs);
2651
2652 DESTDIRMAPITER it = mapDirs.begin();
2653 while ( (it != mapDirs.end())
2654 && !g_fGuestCtrlCanceled)
2655 {
2656 if (pCtx->fVerbose)
2657 RTPrintf("%s directory \"%s\" ...\n",
2658 fRecursive ? "Recursively removing" : "Removing",
2659 it->first.c_str());
2660 try
2661 {
2662 if (fRecursive)
2663 {
2664 com::SafeArray<DirectoryRemoveRecFlag_T> aRemRecFlags;
2665 /** @todo Make flags configurable. */
2666 aRemRecFlags.push_back(DirectoryRemoveRecFlag_ContentAndDir);
2667
2668 ComPtr<IProgress> pProgress;
2669 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryRemoveRecursive(Bstr(it->first).raw(),
2670 ComSafeArrayAsInParam(aRemRecFlags),
2671 pProgress.asOutParam()));
2672 if (pCtx->fVerbose)
2673 rc = showProgress(pProgress);
2674 else
2675 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
2676 if (SUCCEEDED(rc))
2677 CHECK_PROGRESS_ERROR(pProgress, ("Directory deletion failed"));
2678 }
2679 else
2680 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryRemove(Bstr(it->first).raw()));
2681 }
2682 catch (std::bad_alloc)
2683 {
2684 rc = E_OUTOFMEMORY;
2685 break;
2686 }
2687
2688 it++;
2689 }
2690
2691 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2692}
2693
2694static DECLCALLBACK(RTEXITCODE) handleCtrlRemoveFile(PGCTLCMDCTX pCtx)
2695{
2696 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2697
2698 /*
2699 * Parse arguments.
2700 *
2701 * Note! No direct returns here, everyone must go thru the cleanup at the
2702 * end of this function.
2703 */
2704 static const RTGETOPTDEF s_aOptions[] = { 0 };
2705
2706 int ch;
2707 RTGETOPTUNION ValueUnion;
2708 RTGETOPTSTATE GetState;
2709 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2710 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2711
2712 DESTDIRMAP mapDirs;
2713
2714 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2715 {
2716 /* For options that require an argument, ValueUnion has received the value. */
2717 switch (ch)
2718 {
2719 case VINF_GETOPT_NOT_OPTION:
2720 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2721 break;
2722
2723 default:
2724 return RTGetOptPrintError(ch, &ValueUnion);
2725 break;
2726 }
2727 }
2728
2729 uint32_t cFiles = mapDirs.size();
2730 if (!cFiles)
2731 return errorSyntax(USAGE_GUESTCONTROL, "No file to remove specified!");
2732
2733 /*
2734 * Create the directories.
2735 */
2736 HRESULT rc = S_OK;
2737 if (pCtx->fVerbose && cFiles)
2738 RTPrintf("Removing %RU32 file(s) ...\n", cFiles);
2739
2740 DESTDIRMAPITER it = mapDirs.begin();
2741 while ( (it != mapDirs.end())
2742 && !g_fGuestCtrlCanceled)
2743 {
2744 if (pCtx->fVerbose)
2745 RTPrintf("Removing file \"%s\" ...\n", it->first.c_str());
2746
2747 CHECK_ERROR_BREAK(pCtx->pGuestSession, FileRemove(Bstr(it->first).raw()));
2748
2749 it++;
2750 }
2751
2752 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2753}
2754
2755static DECLCALLBACK(RTEXITCODE) handleCtrlCreateTemp(PGCTLCMDCTX pCtx)
2756{
2757 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2758
2759 /*
2760 * Parse arguments.
2761 *
2762 * Note! No direct returns here, everyone must go thru the cleanup at the
2763 * end of this function.
2764 */
2765 static const RTGETOPTDEF s_aOptions[] =
2766 {
2767 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2768 { "--directory", 'D', RTGETOPT_REQ_NOTHING },
2769 { "--secure", 's', RTGETOPT_REQ_NOTHING },
2770 { "--tmpdir", 't', RTGETOPT_REQ_STRING }
2771 };
2772
2773 int ch;
2774 RTGETOPTUNION ValueUnion;
2775 RTGETOPTSTATE GetState;
2776 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2777 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2778
2779 Utf8Str strTemplate;
2780 uint32_t fMode = 0; /* Default mode. */
2781 bool fDirectory = false;
2782 bool fSecure = false;
2783 Utf8Str strTempDir;
2784
2785 DESTDIRMAP mapDirs;
2786
2787 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2788 {
2789 /* For options that require an argument, ValueUnion has received the value. */
2790 switch (ch)
2791 {
2792 case 'm': /* Mode */
2793 fMode = ValueUnion.u32;
2794 break;
2795
2796 case 'D': /* Create directory */
2797 fDirectory = true;
2798 break;
2799
2800 case 's': /* Secure */
2801 fSecure = true;
2802 break;
2803
2804 case 't': /* Temp directory */
2805 strTempDir = ValueUnion.psz;
2806 break;
2807
2808 case VINF_GETOPT_NOT_OPTION:
2809 {
2810 if (strTemplate.isEmpty())
2811 strTemplate = ValueUnion.psz;
2812 else
2813 return errorSyntax(USAGE_GUESTCONTROL,
2814 "More than one template specified!\n");
2815 break;
2816 }
2817
2818 default:
2819 return RTGetOptPrintError(ch, &ValueUnion);
2820 break;
2821 }
2822 }
2823
2824 if (strTemplate.isEmpty())
2825 return errorSyntax(USAGE_GUESTCONTROL, "No template specified!");
2826
2827 if (!fDirectory)
2828 return errorSyntax(USAGE_GUESTCONTROL, "Creating temporary files is currently not supported!");
2829
2830 /*
2831 * Create the directories.
2832 */
2833 if (pCtx->fVerbose)
2834 {
2835 if (fDirectory && !strTempDir.isEmpty())
2836 RTPrintf("Creating temporary directory from template '%s' in directory '%s' ...\n",
2837 strTemplate.c_str(), strTempDir.c_str());
2838 else if (fDirectory)
2839 RTPrintf("Creating temporary directory from template '%s' in default temporary directory ...\n",
2840 strTemplate.c_str());
2841 else if (!fDirectory && !strTempDir.isEmpty())
2842 RTPrintf("Creating temporary file from template '%s' in directory '%s' ...\n",
2843 strTemplate.c_str(), strTempDir.c_str());
2844 else if (!fDirectory)
2845 RTPrintf("Creating temporary file from template '%s' in default temporary directory ...\n",
2846 strTemplate.c_str());
2847 }
2848
2849 HRESULT rc = S_OK;
2850 if (fDirectory)
2851 {
2852 Bstr directory;
2853 CHECK_ERROR(pCtx->pGuestSession, DirectoryCreateTemp(Bstr(strTemplate).raw(),
2854 fMode, Bstr(strTempDir).raw(),
2855 fSecure,
2856 directory.asOutParam()));
2857 if (SUCCEEDED(rc))
2858 RTPrintf("Directory name: %ls\n", directory.raw());
2859 }
2860 // else - temporary file not yet implemented
2861
2862 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2863}
2864
2865static DECLCALLBACK(RTEXITCODE) handleCtrlStat(PGCTLCMDCTX pCtx)
2866{
2867 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2868
2869 static const RTGETOPTDEF s_aOptions[] =
2870 {
2871 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
2872 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
2873 { "--format", 'c', RTGETOPT_REQ_STRING },
2874 { "--terse", 't', RTGETOPT_REQ_NOTHING }
2875 };
2876
2877 int ch;
2878 RTGETOPTUNION ValueUnion;
2879 RTGETOPTSTATE GetState;
2880 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2881 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2882
2883 DESTDIRMAP mapObjs;
2884
2885 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2886 {
2887 RTPrintf("val: %s=%d\n", ValueUnion.psz, ch);
2888
2889 /* For options that require an argument, ValueUnion has received the value. */
2890 switch (ch)
2891 {
2892 case 'L': /* Dereference */
2893 case 'f': /* File-system */
2894 case 'c': /* Format */
2895 case 't': /* Terse */
2896 return errorSyntax(USAGE_GUESTCONTROL, "Command \"%s\" not implemented yet!",
2897 ValueUnion.psz);
2898 break; /* Never reached. */
2899
2900 case VINF_GETOPT_NOT_OPTION:
2901 mapObjs[ValueUnion.psz]; /* Add element to check to map. */
2902 break;
2903
2904 default:
2905 return RTGetOptPrintError(ch, &ValueUnion);
2906 break;
2907 }
2908 }
2909
2910 uint32_t cObjs = mapObjs.size();
2911 if (!cObjs)
2912 return errorSyntax(USAGE_GUESTCONTROL, "No element(s) to check specified!");
2913
2914 HRESULT rc;
2915
2916 /*
2917 * Doing the checks.
2918 */
2919 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2920 DESTDIRMAPITER it = mapObjs.begin();
2921 while (it != mapObjs.end())
2922 {
2923 if (pCtx->fVerbose)
2924 RTPrintf("Checking for element \"%s\" ...\n", it->first.c_str());
2925
2926 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2927 rc = pCtx->pGuestSession->FileQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
2928 if (FAILED(rc))
2929 rc = pCtx->pGuestSession->DirectoryQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
2930
2931 if (FAILED(rc))
2932 {
2933 /* If there's at least one element which does not exist on the guest,
2934 * drop out with exitcode 1. */
2935 if (pCtx->fVerbose)
2936 RTPrintf("Cannot stat for element \"%s\": No such element\n",
2937 it->first.c_str());
2938 rcExit = RTEXITCODE_FAILURE;
2939 }
2940 else
2941 {
2942 FsObjType_T objType;
2943 pFsObjInfo->COMGETTER(Type)(&objType);
2944 switch (objType)
2945 {
2946 case FsObjType_File:
2947 RTPrintf("Element \"%s\" found: Is a file\n", it->first.c_str());
2948 break;
2949
2950 case FsObjType_Directory:
2951 RTPrintf("Element \"%s\" found: Is a directory\n", it->first.c_str());
2952 break;
2953
2954 case FsObjType_Symlink:
2955 RTPrintf("Element \"%s\" found: Is a symlink\n", it->first.c_str());
2956 break;
2957
2958 default:
2959 RTPrintf("Element \"%s\" found, type unknown (%ld)\n", it->first.c_str(), objType);
2960 break;
2961 }
2962
2963 /** @todo: Show more information about this element. */
2964 }
2965
2966 it++;
2967 }
2968
2969 return rcExit;
2970}
2971
2972static DECLCALLBACK(RTEXITCODE) handleCtrlUpdateAdditions(PGCTLCMDCTX pCtx)
2973{
2974 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2975
2976 /*
2977 * Check the syntax. We can deduce the correct syntax from the number of
2978 * arguments.
2979 */
2980 Utf8Str strSource;
2981 com::SafeArray<IN_BSTR> aArgs;
2982 bool fWaitStartOnly = false;
2983
2984 static const RTGETOPTDEF s_aOptions[] =
2985 {
2986 { "--source", 's', RTGETOPT_REQ_STRING },
2987 { "--wait-start", 'w', RTGETOPT_REQ_NOTHING }
2988 };
2989
2990 int ch;
2991 RTGETOPTUNION ValueUnion;
2992 RTGETOPTSTATE GetState;
2993 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv, s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, 0);
2994
2995 int vrc = VINF_SUCCESS;
2996 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2997 && RT_SUCCESS(vrc))
2998 {
2999 switch (ch)
3000 {
3001 case 's':
3002 strSource = ValueUnion.psz;
3003 break;
3004
3005 case 'w':
3006 fWaitStartOnly = true;
3007 break;
3008
3009 case VINF_GETOPT_NOT_OPTION:
3010 if (aArgs.size() == 0 && strSource.isEmpty())
3011 strSource = ValueUnion.psz;
3012 else
3013 aArgs.push_back(Bstr(ValueUnion.psz).raw());
3014 break;
3015
3016 default:
3017 return RTGetOptPrintError(ch, &ValueUnion);
3018 break;
3019 }
3020 }
3021
3022 if (pCtx->fVerbose)
3023 RTPrintf("Updating Guest Additions ...\n");
3024
3025 HRESULT rc = S_OK;
3026 while (strSource.isEmpty())
3027 {
3028 ComPtr<ISystemProperties> pProperties;
3029 CHECK_ERROR_BREAK(pCtx->handlerArg.virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam()));
3030 Bstr strISO;
3031 CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam()));
3032 strSource = strISO;
3033 break;
3034 }
3035
3036 /* Determine source if not set yet. */
3037 if (strSource.isEmpty())
3038 {
3039 RTMsgError("No Guest Additions source found or specified, aborting\n");
3040 vrc = VERR_FILE_NOT_FOUND;
3041 }
3042 else if (!RTFileExists(strSource.c_str()))
3043 {
3044 RTMsgError("Source \"%s\" does not exist!\n", strSource.c_str());
3045 vrc = VERR_FILE_NOT_FOUND;
3046 }
3047
3048 if (RT_SUCCESS(vrc))
3049 {
3050 if (pCtx->fVerbose)
3051 RTPrintf("Using source: %s\n", strSource.c_str());
3052
3053 com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags;
3054 if (fWaitStartOnly)
3055 {
3056 aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly);
3057 if (pCtx->fVerbose)
3058 RTPrintf("Preparing and waiting for Guest Additions installer to start ...\n");
3059 }
3060
3061 ComPtr<IProgress> pProgress;
3062 CHECK_ERROR(pCtx->pGuest, UpdateGuestAdditions(Bstr(strSource).raw(),
3063 ComSafeArrayAsInParam(aArgs),
3064 /* Wait for whole update process to complete. */
3065 ComSafeArrayAsInParam(aUpdateFlags),
3066 pProgress.asOutParam()));
3067 if (FAILED(rc))
3068 vrc = ctrlPrintError(pCtx->pGuest, COM_IIDOF(IGuest));
3069 else
3070 {
3071 if (pCtx->fVerbose)
3072 rc = showProgress(pProgress);
3073 else
3074 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
3075
3076 if (SUCCEEDED(rc))
3077 CHECK_PROGRESS_ERROR(pProgress, ("Guest additions update failed"));
3078 vrc = ctrlPrintProgressError(pProgress);
3079 if ( RT_SUCCESS(vrc)
3080 && pCtx->fVerbose)
3081 {
3082 RTPrintf("Guest Additions update successful\n");
3083 }
3084 }
3085 }
3086
3087 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3088}
3089
3090static DECLCALLBACK(RTEXITCODE) handleCtrlList(PGCTLCMDCTX pCtx)
3091{
3092 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3093
3094 if (pCtx->iArgc < 1)
3095 return errorSyntax(USAGE_GUESTCONTROL, "Must specify a listing category");
3096
3097 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
3098
3099 /** Use RTGetOpt here when handling command line args gets more complex. */
3100
3101 bool fListAll = false;
3102 bool fListSessions = false;
3103 bool fListProcesses = false;
3104 bool fListFiles = false;
3105 if ( !RTStrICmp(pCtx->ppaArgv[0], "sessions")
3106 || !RTStrICmp(pCtx->ppaArgv[0], "sess"))
3107 fListSessions = true;
3108 else if ( !RTStrICmp(pCtx->ppaArgv[0], "processes")
3109 || !RTStrICmp(pCtx->ppaArgv[0], "procs"))
3110 fListSessions = fListProcesses = true; /* Showing processes implies showing sessions. */
3111 else if ( !RTStrICmp(pCtx->ppaArgv[0], "files"))
3112 fListSessions = fListFiles = true; /* Showing files implies showing sessions. */
3113 else if (!RTStrICmp(pCtx->ppaArgv[0], "all"))
3114 fListAll = true;
3115
3116 /** @todo Handle "--verbose" using RTGetOpt. */
3117 /** @todo Do we need a machine-readable output here as well? */
3118
3119 if ( fListAll
3120 || fListSessions)
3121 {
3122 HRESULT rc;
3123 do
3124 {
3125 size_t cTotalProcs = 0;
3126 size_t cTotalFiles = 0;
3127
3128 SafeIfaceArray <IGuestSession> collSessions;
3129 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3130 size_t cSessions = collSessions.size();
3131
3132 if (cSessions)
3133 {
3134 RTPrintf("Active guest sessions:\n");
3135
3136 /** @todo Make this output a bit prettier. No time now. */
3137
3138 for (size_t i = 0; i < cSessions; i++)
3139 {
3140 ComPtr<IGuestSession> pCurSession = collSessions[i];
3141 if (!pCurSession.isNull())
3142 {
3143 ULONG uID;
3144 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Id)(&uID));
3145 Bstr strName;
3146 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Name)(strName.asOutParam()));
3147 Bstr strUser;
3148 CHECK_ERROR_BREAK(pCurSession, COMGETTER(User)(strUser.asOutParam()));
3149 GuestSessionStatus_T sessionStatus;
3150 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Status)(&sessionStatus));
3151 RTPrintf("\n\tSession #%-3zu ID=%-3RU32 User=%-16ls Status=[%s] Name=%ls",
3152 i, uID, strUser.raw(), ctrlSessionStatusToText(sessionStatus), strName.raw());
3153
3154 if ( fListAll
3155 || fListProcesses)
3156 {
3157 SafeIfaceArray <IGuestProcess> collProcesses;
3158 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcesses)));
3159 for (size_t a = 0; a < collProcesses.size(); a++)
3160 {
3161 ComPtr<IGuestProcess> pCurProcess = collProcesses[a];
3162 if (!pCurProcess.isNull())
3163 {
3164 ULONG uPID;
3165 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(PID)(&uPID));
3166 Bstr strExecPath;
3167 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(ExecutablePath)(strExecPath.asOutParam()));
3168 ProcessStatus_T procStatus;
3169 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(Status)(&procStatus));
3170
3171 RTPrintf("\n\t\tProcess #%-03zu PID=%-6RU32 Status=[%s] Command=%ls",
3172 a, uPID, ctrlProcessStatusToText(procStatus), strExecPath.raw());
3173 }
3174 }
3175
3176 cTotalProcs += collProcesses.size();
3177 }
3178
3179 if ( fListAll
3180 || fListFiles)
3181 {
3182 SafeIfaceArray <IGuestFile> collFiles;
3183 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Files)(ComSafeArrayAsOutParam(collFiles)));
3184 for (size_t a = 0; a < collFiles.size(); a++)
3185 {
3186 ComPtr<IGuestFile> pCurFile = collFiles[a];
3187 if (!pCurFile.isNull())
3188 {
3189 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Id)(&uID));
3190 CHECK_ERROR_BREAK(pCurFile, COMGETTER(FileName)(strName.asOutParam()));
3191 FileStatus_T fileStatus;
3192 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Status)(&fileStatus));
3193
3194 RTPrintf("\n\t\tFile #%-03zu ID=%-6RU32 Status=[%s] Name=%ls",
3195 a, uID, ctrlFileStatusToText(fileStatus), strName.raw());
3196 }
3197 }
3198
3199 cTotalFiles += collFiles.size();
3200 }
3201 }
3202 }
3203
3204 RTPrintf("\n\nTotal guest sessions: %zu\n", collSessions.size());
3205 if (fListAll || fListProcesses)
3206 RTPrintf("Total guest processes: %zu\n", cTotalProcs);
3207 if (fListAll || fListFiles)
3208 RTPrintf("Total guest files: %zu\n", cTotalFiles);
3209 }
3210 else
3211 RTPrintf("No active guest sessions found\n");
3212
3213 } while (0);
3214
3215 if (FAILED(rc))
3216 rcExit = RTEXITCODE_FAILURE;
3217 }
3218 else
3219 return errorSyntax(USAGE_GUESTCONTROL, "Invalid listing category '%s", pCtx->ppaArgv[0]);
3220
3221 return rcExit;
3222}
3223
3224static DECLCALLBACK(RTEXITCODE) handleCtrlProcessClose(PGCTLCMDCTX pCtx)
3225{
3226 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3227
3228 if (pCtx->iArgc < 1)
3229 return errorSyntax(USAGE_GUESTCONTROL, "Must specify at least a PID to close");
3230
3231 /*
3232 * Parse arguments.
3233 *
3234 * Note! No direct returns here, everyone must go thru the cleanup at the
3235 * end of this function.
3236 */
3237 static const RTGETOPTDEF s_aOptions[] =
3238 {
3239 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3240 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3241 };
3242
3243 int ch;
3244 RTGETOPTUNION ValueUnion;
3245 RTGETOPTSTATE GetState;
3246 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3247 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3248
3249 std::vector < uint32_t > vecPID;
3250 ULONG ulSessionID = UINT32_MAX;
3251 Utf8Str strSessionName;
3252
3253 int vrc = VINF_SUCCESS;
3254
3255 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3256 && RT_SUCCESS(vrc))
3257 {
3258 /* For options that require an argument, ValueUnion has received the value. */
3259 switch (ch)
3260 {
3261 case 'n': /* Session name (or pattern) */
3262 strSessionName = ValueUnion.psz;
3263 break;
3264
3265 case 'i': /* Session ID */
3266 ulSessionID = ValueUnion.u32;
3267 break;
3268
3269 case VINF_GETOPT_NOT_OPTION:
3270 if (pCtx->iArgc == GetState.iNext)
3271 {
3272 /* Treat every else specified as a PID to kill. */
3273 try
3274 {
3275 uint32_t uPID = RTStrToUInt32(ValueUnion.psz);
3276 if (uPID) /** @todo Is this what we want? If specifying PID 0
3277 this is not going to work on most systems anyway. */
3278 vecPID.push_back(uPID);
3279 else
3280 vrc = VERR_INVALID_PARAMETER;
3281 }
3282 catch(std::bad_alloc &)
3283 {
3284 vrc = VERR_NO_MEMORY;
3285 }
3286 }
3287 break;
3288
3289 default:
3290 return RTGetOptPrintError(ch, &ValueUnion);
3291 break;
3292 }
3293 }
3294
3295 if (vecPID.empty())
3296 return errorSyntax(USAGE_GUESTCONTROL, "At least one PID must be specified to kill!");
3297 else if ( strSessionName.isEmpty()
3298 && ulSessionID == UINT32_MAX)
3299 {
3300 return errorSyntax(USAGE_GUESTCONTROL, "No session ID specified!");
3301 }
3302 else if ( !strSessionName.isEmpty()
3303 && ulSessionID != UINT32_MAX)
3304 {
3305 return errorSyntax(USAGE_GUESTCONTROL, "Either session ID or name (pattern) must be specified");
3306 }
3307
3308 if (RT_FAILURE(vrc))
3309 return errorSyntax(USAGE_GUESTCONTROL, "Invalid parameters specified");
3310
3311 HRESULT rc = S_OK;
3312
3313 ComPtr<IGuestSession> pSession;
3314 ComPtr<IGuestProcess> pProcess;
3315 do
3316 {
3317 uint32_t uProcsTerminated = 0;
3318 bool fSessionFound = false;
3319
3320 SafeIfaceArray <IGuestSession> collSessions;
3321 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3322 size_t cSessions = collSessions.size();
3323
3324 uint32_t uSessionsHandled = 0;
3325 for (size_t i = 0; i < cSessions; i++)
3326 {
3327 pSession = collSessions[i];
3328 Assert(!pSession.isNull());
3329
3330 ULONG uID; /* Session ID */
3331 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3332 Bstr strName;
3333 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3334 Utf8Str strNameUtf8(strName); /* Session name */
3335 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3336 {
3337 fSessionFound = uID == ulSessionID;
3338 }
3339 else /* ... or by naming pattern. */
3340 {
3341 if (RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()))
3342 fSessionFound = true;
3343 }
3344
3345 if (fSessionFound)
3346 {
3347 AssertStmt(!pSession.isNull(), break);
3348 uSessionsHandled++;
3349
3350 SafeIfaceArray <IGuestProcess> collProcs;
3351 CHECK_ERROR_BREAK(pSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcs)));
3352
3353 size_t cProcs = collProcs.size();
3354 for (size_t p = 0; p < cProcs; p++)
3355 {
3356 pProcess = collProcs[p];
3357 Assert(!pProcess.isNull());
3358
3359 ULONG uPID; /* Process ID */
3360 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
3361
3362 bool fProcFound = false;
3363 for (size_t a = 0; a < vecPID.size(); a++) /* Slow, but works. */
3364 {
3365 fProcFound = vecPID[a] == uPID;
3366 if (fProcFound)
3367 break;
3368 }
3369
3370 if (fProcFound)
3371 {
3372 if (pCtx->fVerbose)
3373 RTPrintf("Terminating process (PID %RU32) (session ID %RU32) ...\n",
3374 uPID, uID);
3375 CHECK_ERROR_BREAK(pProcess, Terminate());
3376 uProcsTerminated++;
3377 }
3378 else
3379 {
3380 if (ulSessionID != UINT32_MAX)
3381 RTPrintf("No matching process(es) for session ID %RU32 found\n",
3382 ulSessionID);
3383 }
3384
3385 pProcess.setNull();
3386 }
3387
3388 pSession.setNull();
3389 }
3390 }
3391
3392 if (!uSessionsHandled)
3393 RTPrintf("No matching session(s) found\n");
3394
3395 if (uProcsTerminated)
3396 RTPrintf("%RU32 %s terminated\n",
3397 uProcsTerminated, uProcsTerminated == 1 ? "process" : "processes");
3398
3399 } while (0);
3400
3401 pProcess.setNull();
3402 pSession.setNull();
3403
3404 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3405}
3406
3407static DECLCALLBACK(RTEXITCODE) handleCtrlProcess(PGCTLCMDCTX pCtx)
3408{
3409 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3410
3411 if (pCtx->iArgc < 1)
3412 return errorSyntax(USAGE_GUESTCONTROL, "Must specify an action");
3413
3414 /** Use RTGetOpt here when handling command line args gets more complex. */
3415
3416 if ( !RTStrICmp(pCtx->ppaArgv[0], "close")
3417 || !RTStrICmp(pCtx->ppaArgv[0], "kill")
3418 || !RTStrICmp(pCtx->ppaArgv[0], "terminate"))
3419 {
3420 pCtx->iFirstArgc++; /* Skip process action. */
3421 return handleCtrlProcessClose(pCtx);
3422 }
3423
3424 return errorSyntax(USAGE_GUESTCONTROL, "Invalid process action '%s'", pCtx->ppaArgv[0]);
3425}
3426
3427static DECLCALLBACK(RTEXITCODE) handleCtrlSessionClose(PGCTLCMDCTX pCtx)
3428{
3429 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3430
3431 if (pCtx->iArgc < 1)
3432 return errorSyntax(USAGE_GUESTCONTROL, "Must specify at least a session ID to close");
3433
3434 /*
3435 * Parse arguments.
3436 *
3437 * Note! No direct returns here, everyone must go thru the cleanup at the
3438 * end of this function.
3439 */
3440 static const RTGETOPTDEF s_aOptions[] =
3441 {
3442 { "--all", GETOPTDEF_SESSIONCLOSE_ALL, RTGETOPT_REQ_NOTHING },
3443 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3444 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3445 };
3446
3447 int ch;
3448 RTGETOPTUNION ValueUnion;
3449 RTGETOPTSTATE GetState;
3450 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3451 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3452
3453 ULONG ulSessionID = UINT32_MAX;
3454 Utf8Str strSessionName;
3455
3456 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
3457 {
3458 /* For options that require an argument, ValueUnion has received the value. */
3459 switch (ch)
3460 {
3461 case 'n': /* Session name pattern */
3462 strSessionName = ValueUnion.psz;
3463 break;
3464
3465 case 'i': /* Session ID */
3466 ulSessionID = ValueUnion.u32;
3467 break;
3468
3469 case GETOPTDEF_SESSIONCLOSE_ALL:
3470 strSessionName = "*";
3471 break;
3472
3473 case VINF_GETOPT_NOT_OPTION:
3474 /** @todo Supply a CSV list of IDs or patterns to close? */
3475 break;
3476
3477 default:
3478 return RTGetOptPrintError(ch, &ValueUnion);
3479 break;
3480 }
3481 }
3482
3483 if ( strSessionName.isEmpty()
3484 && ulSessionID == UINT32_MAX)
3485 {
3486 return errorSyntax(USAGE_GUESTCONTROL, "No session ID specified!");
3487 }
3488 else if ( !strSessionName.isEmpty()
3489 && ulSessionID != UINT32_MAX)
3490 {
3491 return errorSyntax(USAGE_GUESTCONTROL, "Either session ID or name (pattern) must be specified");
3492 }
3493
3494 HRESULT rc = S_OK;
3495
3496 ComPtr<IGuestSession> pSession;
3497 do
3498 {
3499 bool fSessionFound = false;
3500 size_t cSessionsHandled = 0;
3501
3502 SafeIfaceArray <IGuestSession> collSessions;
3503 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3504 size_t cSessions = collSessions.size();
3505
3506 for (size_t i = 0; i < cSessions; i++)
3507 {
3508 pSession = collSessions[i];
3509 Assert(!pSession.isNull());
3510
3511 ULONG uID; /* Session ID */
3512 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3513 Bstr strName;
3514 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3515 Utf8Str strNameUtf8(strName); /* Session name */
3516
3517 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3518 {
3519 fSessionFound = uID == ulSessionID;
3520 }
3521 else /* ... or by naming pattern. */
3522 {
3523 if (RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()))
3524 fSessionFound = true;
3525 }
3526
3527 if (fSessionFound)
3528 {
3529 cSessionsHandled++;
3530
3531 Assert(!pSession.isNull());
3532 if (pCtx->fVerbose)
3533 RTPrintf("Closing guest session ID=#%RU32 \"%s\" ...\n",
3534 uID, strNameUtf8.c_str());
3535 CHECK_ERROR_BREAK(pSession, Close());
3536 if (pCtx->fVerbose)
3537 RTPrintf("Guest session successfully closed\n");
3538
3539 pSession->Release();
3540 }
3541 }
3542
3543 if (!cSessionsHandled)
3544 {
3545 RTPrintf("No guest session(s) found\n");
3546 rc = E_ABORT; /* To set exit code accordingly. */
3547 }
3548
3549 } while (0);
3550
3551 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3552}
3553
3554static DECLCALLBACK(RTEXITCODE) handleCtrlSession(PGCTLCMDCTX pCtx)
3555{
3556 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3557
3558 if (pCtx->iArgc < 1)
3559 return errorSyntax(USAGE_GUESTCONTROL, "Must specify an action");
3560
3561 /** Use RTGetOpt here when handling command line args gets more complex. */
3562
3563 if ( !RTStrICmp(pCtx->ppaArgv[0], "close")
3564 || !RTStrICmp(pCtx->ppaArgv[0], "kill")
3565 || !RTStrICmp(pCtx->ppaArgv[0], "terminate"))
3566 {
3567 pCtx->iFirstArgc++; /* Skip session action. */
3568 return handleCtrlSessionClose(pCtx);
3569 }
3570
3571 return errorSyntax(USAGE_GUESTCONTROL, "Invalid session action '%s'", pCtx->ppaArgv[0]);
3572}
3573
3574static DECLCALLBACK(RTEXITCODE) handleCtrlWatch(PGCTLCMDCTX pCtx)
3575{
3576 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3577
3578 /*
3579 * Parse arguments.
3580 */
3581 static const RTGETOPTDEF s_aOptions[] = { 0 };
3582
3583 int ch;
3584 RTGETOPTUNION ValueUnion;
3585 RTGETOPTSTATE GetState;
3586 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3587 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3588
3589 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
3590 {
3591 /* For options that require an argument, ValueUnion has received the value. */
3592 switch (ch)
3593 {
3594 case VINF_GETOPT_NOT_OPTION:
3595 break;
3596
3597 default:
3598 return RTGetOptPrintError(ch, &ValueUnion);
3599 break;
3600 }
3601 }
3602
3603 /** @todo Specify categories to watch for. */
3604 /** @todo Specify a --timeout for waiting only for a certain amount of time? */
3605
3606 HRESULT rc;
3607
3608 try
3609 {
3610 ComObjPtr<GuestEventListenerImpl> pGuestListener;
3611 do
3612 {
3613 /* Listener creation. */
3614 pGuestListener.createObject();
3615 pGuestListener->init(new GuestEventListener());
3616
3617 /* Register for IGuest events. */
3618 ComPtr<IEventSource> es;
3619 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(EventSource)(es.asOutParam()));
3620 com::SafeArray<VBoxEventType_T> eventTypes;
3621 eventTypes.push_back(VBoxEventType_OnGuestSessionRegistered);
3622 /** @todo Also register for VBoxEventType_OnGuestUserStateChanged on demand? */
3623 CHECK_ERROR_BREAK(es, RegisterListener(pGuestListener, ComSafeArrayAsInParam(eventTypes),
3624 true /* Active listener */));
3625 /* Note: All other guest control events have to be registered
3626 * as their corresponding objects appear. */
3627
3628 } while (0);
3629
3630 if (pCtx->fVerbose)
3631 RTPrintf("Waiting for events ...\n");
3632
3633 while (!g_fGuestCtrlCanceled)
3634 {
3635 /** @todo Timeout handling (see above)? */
3636 RTThreadSleep(10);
3637 }
3638
3639 if (pCtx->fVerbose)
3640 RTPrintf("Signal caught, exiting ...\n");
3641
3642 if (!pGuestListener.isNull())
3643 {
3644 /* Guest callback unregistration. */
3645 ComPtr<IEventSource> pES;
3646 CHECK_ERROR(pCtx->pGuest, COMGETTER(EventSource)(pES.asOutParam()));
3647 if (!pES.isNull())
3648 CHECK_ERROR(pES, UnregisterListener(pGuestListener));
3649 pGuestListener.setNull();
3650 }
3651 }
3652 catch (std::bad_alloc &)
3653 {
3654 rc = E_OUTOFMEMORY;
3655 }
3656
3657 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3658}
3659
3660/**
3661 * Access the guest control store.
3662 *
3663 * @returns program exit code.
3664 * @note see the command line API description for parameters
3665 */
3666int handleGuestControl(HandlerArg *pArg)
3667{
3668 AssertPtrReturn(pArg, VERR_INVALID_POINTER);
3669
3670#ifdef DEBUG_andy_disabled
3671 if (RT_FAILURE(tstTranslatePath()))
3672 return RTEXITCODE_FAILURE;
3673#endif
3674
3675 /* pArg->argv[0] contains the VM name. */
3676 /* pArg->argv[1] contains the guest control command. */
3677 if (pArg->argc < 2)
3678 return errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
3679
3680 uint32_t uCmdCtxFlags = 0;
3681 GCTLCMD gctlCmd;
3682 if ( !RTStrICmp(pArg->argv[1], "exec")
3683 || !RTStrICmp(pArg->argv[1], "execute"))
3684 gctlCmd.pfnHandler = handleCtrlProcessExec;
3685 else if (!RTStrICmp(pArg->argv[1], "copyfrom"))
3686 gctlCmd.pfnHandler = handleCtrlCopyFrom;
3687 else if ( !RTStrICmp(pArg->argv[1], "copyto")
3688 || !RTStrICmp(pArg->argv[1], "cp"))
3689 gctlCmd.pfnHandler = handleCtrlCopyTo;
3690 else if ( !RTStrICmp(pArg->argv[1], "createdirectory")
3691 || !RTStrICmp(pArg->argv[1], "createdir")
3692 || !RTStrICmp(pArg->argv[1], "mkdir")
3693 || !RTStrICmp(pArg->argv[1], "md"))
3694 gctlCmd.pfnHandler = handleCtrlCreateDirectory;
3695 else if ( !RTStrICmp(pArg->argv[1], "removedirectory")
3696 || !RTStrICmp(pArg->argv[1], "removedir")
3697 || !RTStrICmp(pArg->argv[1], "rmdir"))
3698 gctlCmd.pfnHandler = handleCtrlRemoveDirectory;
3699 else if ( !RTStrICmp(pArg->argv[1], "rm")
3700 || !RTStrICmp(pArg->argv[1], "removefile"))
3701 gctlCmd.pfnHandler = handleCtrlRemoveFile;
3702 else if ( !RTStrICmp(pArg->argv[1], "createtemporary")
3703 || !RTStrICmp(pArg->argv[1], "createtemp")
3704 || !RTStrICmp(pArg->argv[1], "mktemp"))
3705 gctlCmd.pfnHandler = handleCtrlCreateTemp;
3706 else if ( !RTStrICmp(pArg->argv[1], "kill") /* Linux. */
3707 || !RTStrICmp(pArg->argv[1], "pkill") /* Solaris / *BSD. */
3708 || !RTStrICmp(pArg->argv[1], "pskill")) /* SysInternals version. */
3709 {
3710 /** @todo What about "taskkill" on Windows? */
3711 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
3712 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
3713 gctlCmd.pfnHandler = handleCtrlProcessClose;
3714 }
3715 /** @todo Implement "killall"? */
3716 else if ( !RTStrICmp(pArg->argv[1], "stat"))
3717 gctlCmd.pfnHandler = handleCtrlStat;
3718 else if ( !RTStrICmp(pArg->argv[1], "updateadditions")
3719 || !RTStrICmp(pArg->argv[1], "updateadds"))
3720 {
3721 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
3722 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
3723 gctlCmd.pfnHandler = handleCtrlUpdateAdditions;
3724 }
3725 else if ( !RTStrICmp(pArg->argv[1], "list"))
3726 {
3727 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
3728 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
3729 gctlCmd.pfnHandler = handleCtrlList;
3730 }
3731 else if ( !RTStrICmp(pArg->argv[1], "session"))
3732 {
3733 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
3734 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
3735 gctlCmd.pfnHandler = handleCtrlSession;
3736 }
3737 else if ( !RTStrICmp(pArg->argv[1], "process"))
3738 {
3739 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
3740 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
3741 gctlCmd.pfnHandler = handleCtrlProcess;
3742 }
3743 else if ( !RTStrICmp(pArg->argv[1], "watch"))
3744 {
3745 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
3746 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
3747 gctlCmd.pfnHandler = handleCtrlWatch;
3748 }
3749 else
3750 return errorSyntax(USAGE_GUESTCONTROL, "Unknown sub command '%s' specified!", pArg->argv[1]);
3751
3752 GCTLCMDCTX cmdCtx;
3753 RT_ZERO(cmdCtx);
3754
3755 RTEXITCODE rcExit = ctrlInitVM(pArg, &cmdCtx, uCmdCtxFlags);
3756 if (rcExit == RTEXITCODE_SUCCESS)
3757 {
3758 /* Kick off the actual command handler. */
3759 rcExit = gctlCmd.pfnHandler(&cmdCtx);
3760
3761 ctrlUninitVM(&cmdCtx, cmdCtx.uFlags);
3762 return rcExit;
3763 }
3764
3765 return rcExit;
3766}
3767#endif /* !VBOX_ONLY_DOCS */
3768
Note: See TracBrowser for help on using the repository browser.

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