VirtualBox

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

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

Build fix.

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