VirtualBox

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

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

FE/VBoxManage/GuestCtrl: Integrated waiting for guest sessions to start in common startup code, save the session ID in command context.

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