VirtualBox

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

Last change on this file since 92861 was 92861, checked in by vboxsync, 3 years ago

Guest Control/VBoxManage: Fixed signal handling regression within the watch sub command, caused by r138248.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 152.4 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 92861 2021-12-10 13:25:55Z vboxsync $ */
2/** @file
3 * VBoxManage - Implementation of guestcontrol command.
4 */
5
6/*
7 * Copyright (C) 2010-2020 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/getopt.h>
43#include <iprt/list.h>
44#include <iprt/path.h>
45#include <iprt/process.h> /* For RTProcSelf(). */
46#include <iprt/semaphore.h>
47#include <iprt/thread.h>
48#include <iprt/vfs.h>
49
50#include <iprt/cpp/path.h>
51
52#include <map>
53#include <vector>
54
55#ifdef USE_XPCOM_QUEUE
56# include <sys/select.h>
57# include <errno.h>
58#endif
59
60#include <signal.h>
61
62#ifdef RT_OS_DARWIN
63# include <CoreFoundation/CFRunLoop.h>
64#endif
65
66using namespace com;
67
68
69/*********************************************************************************************************************************
70 * Defined Constants And Macros *
71*********************************************************************************************************************************/
72
73#define GCTLCMD_COMMON_OPT_USER 999 /**< The --username option number. */
74#define GCTLCMD_COMMON_OPT_PASSWORD 998 /**< The --password option number. */
75#define GCTLCMD_COMMON_OPT_PASSWORD_FILE 997 /**< The --password-file option number. */
76#define GCTLCMD_COMMON_OPT_DOMAIN 996 /**< The --domain option number. */
77/** Common option definitions. */
78#define GCTLCMD_COMMON_OPTION_DEFS() \
79 { "--user", GCTLCMD_COMMON_OPT_USER, RTGETOPT_REQ_STRING }, \
80 { "--username", GCTLCMD_COMMON_OPT_USER, RTGETOPT_REQ_STRING }, \
81 { "--passwordfile", GCTLCMD_COMMON_OPT_PASSWORD_FILE, RTGETOPT_REQ_STRING }, \
82 { "--password", GCTLCMD_COMMON_OPT_PASSWORD, RTGETOPT_REQ_STRING }, \
83 { "--domain", GCTLCMD_COMMON_OPT_DOMAIN, RTGETOPT_REQ_STRING }, \
84 { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, \
85 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
86
87/** Handles common options in the typical option parsing switch. */
88#define GCTLCMD_COMMON_OPTION_CASES(a_pCtx, a_ch, a_pValueUnion) \
89 case 'v': \
90 case 'q': \
91 case GCTLCMD_COMMON_OPT_USER: \
92 case GCTLCMD_COMMON_OPT_DOMAIN: \
93 case GCTLCMD_COMMON_OPT_PASSWORD: \
94 case GCTLCMD_COMMON_OPT_PASSWORD_FILE: \
95 { \
96 RTEXITCODE rcExitCommon = gctlCtxSetOption(a_pCtx, a_ch, a_pValueUnion); \
97 if (RT_UNLIKELY(rcExitCommon != RTEXITCODE_SUCCESS)) \
98 return rcExitCommon; \
99 } break
100
101
102/*********************************************************************************************************************************
103* Global Variables *
104*********************************************************************************************************************************/
105/** Set by the signal handler when current guest control
106 * action shall be aborted. */
107static volatile bool g_fGuestCtrlCanceled = false;
108/** Event semaphore used for wait notifications.
109 * Also being used for the listener implementations in VBoxManageGuestCtrlListener.cpp. */
110 RTSEMEVENT g_SemEventGuestCtrlCanceled = NIL_RTSEMEVENT;
111
112
113/*********************************************************************************************************************************
114* Structures and Typedefs *
115*********************************************************************************************************************************/
116/**
117 * Listener declarations.
118 */
119VBOX_LISTENER_DECLARE(GuestFileEventListenerImpl)
120VBOX_LISTENER_DECLARE(GuestProcessEventListenerImpl)
121VBOX_LISTENER_DECLARE(GuestSessionEventListenerImpl)
122VBOX_LISTENER_DECLARE(GuestEventListenerImpl)
123VBOX_LISTENER_DECLARE(GuestAdditionsRunlevelListener)
124
125/**
126 * Definition of a guestcontrol command, with handler and various flags.
127 */
128typedef struct GCTLCMDDEF
129{
130 /** The command name. */
131 const char *pszName;
132
133 /**
134 * Actual command handler callback.
135 *
136 * @param pCtx Pointer to command context to use.
137 */
138 DECLR3CALLBACKMEMBER(RTEXITCODE, pfnHandler, (struct GCTLCMDCTX *pCtx, int argc, char **argv));
139
140 /** The sub-command scope flags. */
141 uint64_t fSubcommandScope;
142 /** Command context flags (GCTLCMDCTX_F_XXX). */
143 uint32_t fCmdCtx;
144} GCTLCMD;
145/** Pointer to a const guest control command definition. */
146typedef GCTLCMDDEF const *PCGCTLCMDDEF;
147
148/** @name GCTLCMDCTX_F_XXX - Command context flags.
149 * @{
150 */
151/** No flags set. */
152#define GCTLCMDCTX_F_NONE 0
153/** Don't install a signal handler (CTRL+C trap). */
154#define GCTLCMDCTX_F_NO_SIGNAL_HANDLER RT_BIT(0)
155/** No guest session needed. */
156#define GCTLCMDCTX_F_SESSION_ANONYMOUS RT_BIT(1)
157/** @} */
158
159/**
160 * Context for handling a specific command.
161 */
162typedef struct GCTLCMDCTX
163{
164 HandlerArg *pArg;
165
166 /** Pointer to the command definition. */
167 PCGCTLCMDDEF pCmdDef;
168 /** The VM name or UUID. */
169 const char *pszVmNameOrUuid;
170
171 /** Whether we've done the post option parsing init already. */
172 bool fPostOptionParsingInited;
173 /** Whether we've locked the VM session. */
174 bool fLockedVmSession;
175 /** Whether to detach (@c true) or close the session. */
176 bool fDetachGuestSession;
177 /** Set if we've installed the signal handler. */
178 bool fInstalledSignalHandler;
179 /** The verbosity level. */
180 uint32_t cVerbose;
181 /** User name. */
182 Utf8Str strUsername;
183 /** Password. */
184 Utf8Str strPassword;
185 /** Domain. */
186 Utf8Str strDomain;
187 /** Pointer to the IGuest interface. */
188 ComPtr<IGuest> pGuest;
189 /** Pointer to the to be used guest session. */
190 ComPtr<IGuestSession> pGuestSession;
191 /** The guest session ID. */
192 ULONG uSessionID;
193
194} GCTLCMDCTX, *PGCTLCMDCTX;
195
196
197/**
198 * An entry for an element which needs to be copied/created to/on the guest.
199 */
200typedef struct DESTFILEENTRY
201{
202 DESTFILEENTRY(Utf8Str strFilename) : mFilename(strFilename) {}
203 Utf8Str mFilename;
204} DESTFILEENTRY, *PDESTFILEENTRY;
205/*
206 * Map for holding destination entries, whereas the key is the destination
207 * directory and the mapped value is a vector holding all elements for this directory.
208 */
209typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> > DESTDIRMAP, *PDESTDIRMAP;
210typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> >::iterator DESTDIRMAPITER, *PDESTDIRMAPITER;
211
212
213enum kStreamTransform
214{
215 kStreamTransform_None = 0,
216 kStreamTransform_Dos2Unix,
217 kStreamTransform_Unix2Dos
218};
219#endif /* VBOX_ONLY_DOCS */
220
221DECLARE_TRANSLATION_CONTEXT(GuestCtrl);
222
223void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2, uint64_t fSubcommandScope)
224{
225 const uint64_t fAnonSubCmds = HELP_SCOPE_GSTCTRL_CLOSESESSION
226 | HELP_SCOPE_GSTCTRL_LIST
227 | HELP_SCOPE_GSTCTRL_CLOSEPROCESS
228 | HELP_SCOPE_GSTCTRL_CLOSESESSION
229 | HELP_SCOPE_GSTCTRL_UPDATEGA
230 | HELP_SCOPE_GSTCTRL_WATCH
231 | HELP_SCOPE_GSTCTRL_WAITRUNLEVEL;
232
233 /* 0 1 2 3 4 5 6 7 8XXXXXXXXXX */
234 /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
235 if (~fAnonSubCmds & fSubcommandScope)
236 RTStrmPrintf(pStrm,
237 "%s guestcontrol %s <uuid|vmname> [--verbose|-v] [--quiet|-q]\n"
238 " [--user[name] <name>] [--domain <domain>]\n"
239 " [--passwordfile <file> | --password <password>]\n%s",
240 pcszSep1, pcszSep2, (fSubcommandScope & RTMSGREFENTRYSTR_SCOPE_MASK) == RTMSGREFENTRYSTR_SCOPE_GLOBAL ? "\n" : "");
241 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_RUN)
242 RTStrmPrintf(pStrm,
243 " run [common-options]\n"
244 " [--exe <path to executable>] [--timeout <msec>]\n"
245 " [-E|--putenv <NAME>[=<VALUE>]] [--unquoted-args]\n"
246 " [--ignore-operhaned-processes] [--profile]\n"
247 " [--no-wait-stdout|--wait-stdout]\n"
248 " [--no-wait-stderr|--wait-stderr]\n"
249 " [--dos2unix] [--unix2dos]\n"
250 " -- <program/arg0> [argument1] ... [argumentN]]\n"
251 "\n");
252 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_START)
253 RTStrmPrintf(pStrm,
254 " start [common-options]\n"
255 " [--exe <path to executable>] [--timeout <msec>]\n"
256 " [-E|--putenv <NAME>[=<VALUE>]] [--unquoted-args]\n"
257 " [--ignore-operhaned-processes] [--profile]\n"
258 " -- <program/arg0> [argument1] ... [argumentN]]\n"
259 "\n");
260 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_COPYFROM)
261 RTStrmPrintf(pStrm,
262 " copyfrom [common-options]\n"
263 " [-L|--dereference] [-R|--recursive]\n"
264 " <guest-src0> [guest-src1 [...]] <host-dst>\n"
265 "\n"
266 " copyfrom [common-options]\n"
267 " [-L|--dereference] [-R|--recursive]\n"
268 " [-t|--target-directory <host-dst-dir>]\n"
269 " <guest-src0> [guest-src1 [...]]\n"
270 "\n");
271 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_COPYTO)
272 RTStrmPrintf(pStrm,
273 " copyto [common-options]\n"
274 " [-L|--dereference] [-R|--recursive]\n"
275 " <host-src0> [host-src1 [...]] <guest-dst>\n"
276 "\n"
277 " copyto [common-options]\n"
278 " [-L|--dereference] [-R|--recursive]\n"
279 " [-t|--target-directory <guest-dst>]\n"
280 " <host-src0> [host-src1 [...]]\n"
281 "\n");
282 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_MKDIR)
283 RTStrmPrintf(pStrm,
284 " mkdir|createdir[ectory] [common-options]\n"
285 " [--parents] [--mode <mode>]\n"
286 " <guest directory> [...]\n"
287 "\n");
288 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_RMDIR)
289 RTStrmPrintf(pStrm,
290 " rmdir|removedir[ectory] [common-options]\n"
291 " [-R|--recursive]\n"
292 " <guest directory> [...]\n"
293 "\n");
294 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_RM)
295 RTStrmPrintf(pStrm,
296 " removefile|rm [common-options] [-f|--force]\n"
297 " <guest file> [...]\n"
298 "\n");
299 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_MV)
300 RTStrmPrintf(pStrm,
301 " mv|move|ren[ame] [common-options]\n"
302 " <source> [source1 [...]] <dest>\n"
303 "\n");
304 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_MKTEMP)
305 RTStrmPrintf(pStrm,
306 " mktemp|createtemp[orary] [common-options]\n"
307 " [--secure] [--mode <mode>] [--tmpdir <directory>]\n"
308 " <template>\n"
309 "\n");
310 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_STAT)
311 RTStrmPrintf(pStrm,
312 " stat [common-options]\n"
313 " <file> [...]\n"
314 "\n");
315
316 /*
317 * Command not requiring authentication.
318 */
319 if (fAnonSubCmds & fSubcommandScope)
320 {
321 /* 0 1 2 3 4 5 6 7 8XXXXXXXXXX */
322 /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
323 RTStrmPrintf(pStrm,
324 "%s guestcontrol %s <uuid|vmname> [--verbose|-v] [--quiet|-q]\n%s",
325 pcszSep1, pcszSep2, (fSubcommandScope & RTMSGREFENTRYSTR_SCOPE_MASK) == RTMSGREFENTRYSTR_SCOPE_GLOBAL ? "\n" : "");
326 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_LIST)
327 RTStrmPrintf(pStrm,
328 " list <all|sessions|processes|files> [common-opts]\n"
329 "\n");
330 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_CLOSEPROCESS)
331 RTStrmPrintf(pStrm,
332 " closeprocess [common-options]\n"
333 " < --session-id <ID>\n"
334 " | --session-name <name or pattern>\n"
335 " <PID1> [PID1 [...]]\n"
336 "\n");
337 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_CLOSESESSION)
338 RTStrmPrintf(pStrm,
339 " closesession [common-options]\n"
340 " < --all | --session-id <ID>\n"
341 " | --session-name <name or pattern> >\n"
342 "\n");
343 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_UPDATEGA)
344 RTStrmPrintf(pStrm,
345 " updatega|updateguestadditions|updateadditions\n"
346 " [--source <guest additions .ISO>]\n"
347 " [--wait-start] [common-options]\n"
348 " [-- [<argument1>] ... [<argumentN>]]\n"
349 "\n");
350 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_WATCH)
351 RTStrmPrintf(pStrm,
352 " watch [--timeout <msec>] [common-options]\n"
353 "\n");
354 if (fSubcommandScope & HELP_SCOPE_GSTCTRL_WAITRUNLEVEL)
355 RTStrmPrintf(pStrm,
356 " waitrunlevel [--timeout <msec>] [common-options]\n"
357 " <system|userland|desktop>\n"
358 "\n");
359 }
360}
361
362#ifndef VBOX_ONLY_DOCS
363
364
365#ifdef RT_OS_WINDOWS
366static BOOL WINAPI gctlSignalHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
367{
368 bool fEventHandled = FALSE;
369 switch (dwCtrlType)
370 {
371 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
372 * via GenerateConsoleCtrlEvent(). */
373 case CTRL_BREAK_EVENT:
374 case CTRL_CLOSE_EVENT:
375 case CTRL_C_EVENT:
376 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
377 RTSemEventSignal(g_SemEventGuestCtrlCanceled);
378 fEventHandled = TRUE;
379 break;
380 default:
381 break;
382 /** @todo Add other events here. */
383 }
384
385 return fEventHandled;
386}
387#else /* !RT_OS_WINDOWS */
388/**
389 * Signal handler that sets g_fGuestCtrlCanceled.
390 *
391 * This can be executed on any thread in the process, on Windows it may even be
392 * a thread dedicated to delivering this signal. Don't do anything
393 * unnecessary here.
394 */
395static void gctlSignalHandler(int iSignal) RT_NOTHROW_DEF
396{
397 RT_NOREF(iSignal);
398 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
399 RTSemEventSignal(g_SemEventGuestCtrlCanceled);
400}
401#endif
402
403
404/**
405 * Installs a custom signal handler to get notified
406 * whenever the user wants to intercept the program.
407 *
408 * @todo Make this handler available for all VBoxManage modules?
409 */
410static int gctlSignalHandlerInstall(void)
411{
412 g_fGuestCtrlCanceled = false;
413
414 int rc = VINF_SUCCESS;
415#ifdef RT_OS_WINDOWS
416 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)gctlSignalHandler, TRUE /* Add handler */))
417 {
418 rc = RTErrConvertFromWin32(GetLastError());
419 RTMsgError(GuestCtrl::tr("Unable to install console control handler, rc=%Rrc\n"), rc);
420 }
421#else
422 signal(SIGINT, gctlSignalHandler);
423 signal(SIGTERM, gctlSignalHandler);
424# ifdef SIGBREAK
425 signal(SIGBREAK, gctlSignalHandler);
426# endif
427#endif
428
429 if (RT_SUCCESS(rc))
430 rc = RTSemEventCreate(&g_SemEventGuestCtrlCanceled);
431
432 return rc;
433}
434
435
436/**
437 * Uninstalls a previously installed signal handler.
438 */
439static int gctlSignalHandlerUninstall(void)
440{
441 int rc = VINF_SUCCESS;
442#ifdef RT_OS_WINDOWS
443 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
444 {
445 rc = RTErrConvertFromWin32(GetLastError());
446 RTMsgError(GuestCtrl::tr("Unable to uninstall console control handler, rc=%Rrc\n"), rc);
447 }
448#else
449 signal(SIGINT, SIG_DFL);
450 signal(SIGTERM, SIG_DFL);
451# ifdef SIGBREAK
452 signal(SIGBREAK, SIG_DFL);
453# endif
454#endif
455
456 if (g_SemEventGuestCtrlCanceled != NIL_RTSEMEVENT)
457 {
458 RTSemEventDestroy(g_SemEventGuestCtrlCanceled);
459 g_SemEventGuestCtrlCanceled = NIL_RTSEMEVENT;
460 }
461 return rc;
462}
463
464
465/**
466 * Translates a process status to a human readable string.
467 */
468const char *gctlProcessStatusToText(ProcessStatus_T enmStatus)
469{
470 switch (enmStatus)
471 {
472 case ProcessStatus_Starting:
473 return GuestCtrl::tr("starting");
474 case ProcessStatus_Started:
475 return GuestCtrl::tr("started");
476 case ProcessStatus_Paused:
477 return GuestCtrl::tr("paused");
478 case ProcessStatus_Terminating:
479 return GuestCtrl::tr("terminating");
480 case ProcessStatus_TerminatedNormally:
481 return GuestCtrl::tr("successfully terminated");
482 case ProcessStatus_TerminatedSignal:
483 return GuestCtrl::tr("terminated by signal");
484 case ProcessStatus_TerminatedAbnormally:
485 return GuestCtrl::tr("abnormally aborted");
486 case ProcessStatus_TimedOutKilled:
487 return GuestCtrl::tr("timed out");
488 case ProcessStatus_TimedOutAbnormally:
489 return GuestCtrl::tr("timed out, hanging");
490 case ProcessStatus_Down:
491 return GuestCtrl::tr("killed");
492 case ProcessStatus_Error:
493 return GuestCtrl::tr("error");
494 default:
495 break;
496 }
497 return GuestCtrl::tr("unknown");
498}
499
500/**
501 * Translates a guest process wait result to a human readable string.
502 */
503const char *gctlProcessWaitResultToText(ProcessWaitResult_T enmWaitResult)
504{
505 switch (enmWaitResult)
506 {
507 case ProcessWaitResult_Start:
508 return GuestCtrl::tr("started");
509 case ProcessWaitResult_Terminate:
510 return GuestCtrl::tr("terminated");
511 case ProcessWaitResult_Status:
512 return GuestCtrl::tr("status changed");
513 case ProcessWaitResult_Error:
514 return GuestCtrl::tr("error");
515 case ProcessWaitResult_Timeout:
516 return GuestCtrl::tr("timed out");
517 case ProcessWaitResult_StdIn:
518 return GuestCtrl::tr("stdin ready");
519 case ProcessWaitResult_StdOut:
520 return GuestCtrl::tr("data on stdout");
521 case ProcessWaitResult_StdErr:
522 return GuestCtrl::tr("data on stderr");
523 case ProcessWaitResult_WaitFlagNotSupported:
524 return GuestCtrl::tr("waiting flag not supported");
525 default:
526 break;
527 }
528 return GuestCtrl::tr("unknown");
529}
530
531/**
532 * Translates a guest session status to a human readable string.
533 */
534const char *gctlGuestSessionStatusToText(GuestSessionStatus_T enmStatus)
535{
536 switch (enmStatus)
537 {
538 case GuestSessionStatus_Starting:
539 return GuestCtrl::tr("starting");
540 case GuestSessionStatus_Started:
541 return GuestCtrl::tr("started");
542 case GuestSessionStatus_Terminating:
543 return GuestCtrl::tr("terminating");
544 case GuestSessionStatus_Terminated:
545 return GuestCtrl::tr("terminated");
546 case GuestSessionStatus_TimedOutKilled:
547 return GuestCtrl::tr("timed out");
548 case GuestSessionStatus_TimedOutAbnormally:
549 return GuestCtrl::tr("timed out, hanging");
550 case GuestSessionStatus_Down:
551 return GuestCtrl::tr("killed");
552 case GuestSessionStatus_Error:
553 return GuestCtrl::tr("error");
554 default:
555 break;
556 }
557 return GuestCtrl::tr("unknown");
558}
559
560/**
561 * Translates a guest file status to a human readable string.
562 */
563const char *gctlFileStatusToText(FileStatus_T enmStatus)
564{
565 switch (enmStatus)
566 {
567 case FileStatus_Opening:
568 return GuestCtrl::tr("opening");
569 case FileStatus_Open:
570 return GuestCtrl::tr("open");
571 case FileStatus_Closing:
572 return GuestCtrl::tr("closing");
573 case FileStatus_Closed:
574 return GuestCtrl::tr("closed");
575 case FileStatus_Down:
576 return GuestCtrl::tr("killed");
577 case FileStatus_Error:
578 return GuestCtrl::tr("error");
579 default:
580 break;
581 }
582 return GuestCtrl::tr("unknown");
583}
584
585/**
586 * Translates a file system objec type to a string.
587 */
588const char *gctlFsObjTypeToName(FsObjType_T enmType)
589{
590 switch (enmType)
591 {
592 case FsObjType_Unknown: return GuestCtrl::tr("unknown");
593 case FsObjType_Fifo: return GuestCtrl::tr("fifo");
594 case FsObjType_DevChar: return GuestCtrl::tr("char-device");
595 case FsObjType_Directory: return GuestCtrl::tr("directory");
596 case FsObjType_DevBlock: return GuestCtrl::tr("block-device");
597 case FsObjType_File: return GuestCtrl::tr("file");
598 case FsObjType_Symlink: return GuestCtrl::tr("symlink");
599 case FsObjType_Socket: return GuestCtrl::tr("socket");
600 case FsObjType_WhiteOut: return GuestCtrl::tr("white-out");
601#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
602 case FsObjType_32BitHack: break;
603#endif
604 }
605 return GuestCtrl::tr("unknown");
606}
607
608static int gctlPrintError(com::ErrorInfo &errorInfo)
609{
610 if ( errorInfo.isFullAvailable()
611 || errorInfo.isBasicAvailable())
612 {
613 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
614 * because it contains more accurate info about what went wrong. */
615 if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
616 RTMsgError("%ls.", errorInfo.getText().raw());
617 else
618 {
619 RTMsgError(GuestCtrl::tr("Error details:"));
620 GluePrintErrorInfo(errorInfo);
621 }
622 return VERR_GENERAL_FAILURE; /** @todo */
623 }
624 AssertMsgFailedReturn((GuestCtrl::tr("Object has indicated no error (%Rhrc)!?\n"), errorInfo.getResultCode()),
625 VERR_INVALID_PARAMETER);
626}
627
628static int gctlPrintError(IUnknown *pObj, const GUID &aIID)
629{
630 com::ErrorInfo ErrInfo(pObj, aIID);
631 return gctlPrintError(ErrInfo);
632}
633
634static int gctlPrintProgressError(ComPtr<IProgress> pProgress)
635{
636 int vrc = VINF_SUCCESS;
637 HRESULT rc;
638
639 do
640 {
641 BOOL fCanceled;
642 CHECK_ERROR_BREAK(pProgress, COMGETTER(Canceled)(&fCanceled));
643 if (!fCanceled)
644 {
645 LONG rcProc;
646 CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&rcProc));
647 if (FAILED(rcProc))
648 {
649 com::ProgressErrorInfo ErrInfo(pProgress);
650 vrc = gctlPrintError(ErrInfo);
651 }
652 }
653
654 } while(0);
655
656 AssertMsgStmt(SUCCEEDED(rc), (GuestCtrl::tr("Could not lookup progress information\n")), vrc = VERR_COM_UNEXPECTED);
657
658 return vrc;
659}
660
661
662
663/*
664 *
665 *
666 * Guest Control Command Context
667 * Guest Control Command Context
668 * Guest Control Command Context
669 * Guest Control Command Context
670 *
671 *
672 *
673 */
674
675
676/**
677 * Initializes a guest control command context structure.
678 *
679 * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE on failure (after
680 * informing the user of course).
681 * @param pCtx The command context to init.
682 * @param pArg The handle argument package.
683 */
684static RTEXITCODE gctrCmdCtxInit(PGCTLCMDCTX pCtx, HandlerArg *pArg)
685{
686 pCtx->pArg = pArg;
687 pCtx->pCmdDef = NULL;
688 pCtx->pszVmNameOrUuid = NULL;
689 pCtx->fPostOptionParsingInited = false;
690 pCtx->fLockedVmSession = false;
691 pCtx->fDetachGuestSession = false;
692 pCtx->fInstalledSignalHandler = false;
693 pCtx->cVerbose = 0;
694 pCtx->strUsername.setNull();
695 pCtx->strPassword.setNull();
696 pCtx->strDomain.setNull();
697 pCtx->pGuest.setNull();
698 pCtx->pGuestSession.setNull();
699 pCtx->uSessionID = 0;
700
701 /*
702 * The user name defaults to the host one, if we can get at it.
703 */
704 char szUser[1024];
705 int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL);
706 if ( RT_SUCCESS(rc)
707 && RTStrIsValidEncoding(szUser)) /* paranoia was required on posix at some point, not needed any more! */
708 {
709 try
710 {
711 pCtx->strUsername = szUser;
712 }
713 catch (std::bad_alloc &)
714 {
715 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Out of memory"));
716 }
717 }
718 /* else: ignore this failure. */
719
720 return RTEXITCODE_SUCCESS;
721}
722
723
724/**
725 * Worker for GCTLCMD_COMMON_OPTION_CASES.
726 *
727 * @returns RTEXITCODE_SUCCESS if the option was handled successfully. If not,
728 * an error message is printed and an appropriate failure exit code is
729 * returned.
730 * @param pCtx The guest control command context.
731 * @param ch The option char or ordinal.
732 * @param pValueUnion The option value union.
733 */
734static RTEXITCODE gctlCtxSetOption(PGCTLCMDCTX pCtx, int ch, PRTGETOPTUNION pValueUnion)
735{
736 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
737 switch (ch)
738 {
739 case GCTLCMD_COMMON_OPT_USER: /* User name */
740 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
741 pCtx->strUsername = pValueUnion->psz;
742 else
743 RTMsgWarning(GuestCtrl::tr("The --username|-u option is ignored by '%s'"), pCtx->pCmdDef->pszName);
744 break;
745
746 case GCTLCMD_COMMON_OPT_PASSWORD: /* Password */
747 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
748 {
749 if (pCtx->strPassword.isNotEmpty())
750 RTMsgWarning(GuestCtrl::tr("Password is given more than once."));
751 pCtx->strPassword = pValueUnion->psz;
752 }
753 else
754 RTMsgWarning(GuestCtrl::tr("The --password option is ignored by '%s'"), pCtx->pCmdDef->pszName);
755 break;
756
757 case GCTLCMD_COMMON_OPT_PASSWORD_FILE: /* Password file */
758 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
759 rcExit = readPasswordFile(pValueUnion->psz, &pCtx->strPassword);
760 else
761 RTMsgWarning(GuestCtrl::tr("The --password-file|-p option is ignored by '%s'"), pCtx->pCmdDef->pszName);
762 break;
763
764 case GCTLCMD_COMMON_OPT_DOMAIN: /* domain */
765 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
766 pCtx->strDomain = pValueUnion->psz;
767 else
768 RTMsgWarning(GuestCtrl::tr("The --domain option is ignored by '%s'"), pCtx->pCmdDef->pszName);
769 break;
770
771 case 'v': /* --verbose */
772 pCtx->cVerbose++;
773 break;
774
775 case 'q': /* --quiet */
776 if (pCtx->cVerbose)
777 pCtx->cVerbose--;
778 break;
779
780 default:
781 AssertFatalMsgFailed(("ch=%d (%c)\n", ch, ch));
782 }
783 return rcExit;
784}
785
786
787/**
788 * Initializes the VM for IGuest operation.
789 *
790 * This opens a shared session to a running VM and gets hold of IGuest.
791 *
792 * @returns RTEXITCODE_SUCCESS on success. RTEXITCODE_FAILURE and user message
793 * on failure.
794 * @param pCtx The guest control command context.
795 * GCTLCMDCTX::pGuest will be set on success.
796 */
797static RTEXITCODE gctlCtxInitVmSession(PGCTLCMDCTX pCtx)
798{
799 HRESULT rc;
800 AssertPtr(pCtx);
801 AssertPtr(pCtx->pArg);
802
803 /*
804 * Find the VM and check if it's running.
805 */
806 ComPtr<IMachine> machine;
807 CHECK_ERROR(pCtx->pArg->virtualBox, FindMachine(Bstr(pCtx->pszVmNameOrUuid).raw(), machine.asOutParam()));
808 if (SUCCEEDED(rc))
809 {
810 MachineState_T enmMachineState;
811 CHECK_ERROR(machine, COMGETTER(State)(&enmMachineState));
812 if ( SUCCEEDED(rc)
813 && enmMachineState == MachineState_Running)
814 {
815 /*
816 * It's running. So, open a session to it and get the IGuest interface.
817 */
818 CHECK_ERROR(machine, LockMachine(pCtx->pArg->session, LockType_Shared));
819 if (SUCCEEDED(rc))
820 {
821 pCtx->fLockedVmSession = true;
822 ComPtr<IConsole> ptrConsole;
823 CHECK_ERROR(pCtx->pArg->session, COMGETTER(Console)(ptrConsole.asOutParam()));
824 if (SUCCEEDED(rc))
825 {
826 if (ptrConsole.isNotNull())
827 {
828 CHECK_ERROR(ptrConsole, COMGETTER(Guest)(pCtx->pGuest.asOutParam()));
829 if (SUCCEEDED(rc))
830 return RTEXITCODE_SUCCESS;
831 }
832 else
833 RTMsgError(GuestCtrl::tr("Failed to get a IConsole pointer for the machine. Is it still running?\n"));
834 }
835 }
836 }
837 else if (SUCCEEDED(rc))
838 RTMsgError(GuestCtrl::tr("Machine \"%s\" is not running (currently %s)!\n"),
839 pCtx->pszVmNameOrUuid, machineStateToName(enmMachineState, false));
840 }
841 return RTEXITCODE_FAILURE;
842}
843
844
845/**
846 * Creates a guest session with the VM.
847 *
848 * @retval RTEXITCODE_SUCCESS on success.
849 * @retval RTEXITCODE_FAILURE and user message on failure.
850 * @param pCtx The guest control command context.
851 * GCTCMDCTX::pGuestSession and GCTLCMDCTX::uSessionID
852 * will be set.
853 */
854static RTEXITCODE gctlCtxInitGuestSession(PGCTLCMDCTX pCtx)
855{
856 HRESULT rc;
857 AssertPtr(pCtx);
858 Assert(!(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS));
859 Assert(pCtx->pGuest.isNotNull());
860
861 /*
862 * Build up a reasonable guest session name. Useful for identifying
863 * a specific session when listing / searching for them.
864 */
865 char *pszSessionName;
866 if (RTStrAPrintf(&pszSessionName,
867 GuestCtrl::tr("[%RU32] VBoxManage Guest Control [%s] - %s"),
868 RTProcSelf(), pCtx->pszVmNameOrUuid, pCtx->pCmdDef->pszName) < 0)
869 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("No enough memory for session name"));
870
871 /*
872 * Create a guest session.
873 */
874 if (pCtx->cVerbose)
875 RTPrintf(GuestCtrl::tr("Creating guest session as user '%s'...\n"), pCtx->strUsername.c_str());
876 try
877 {
878 CHECK_ERROR(pCtx->pGuest, CreateSession(Bstr(pCtx->strUsername).raw(),
879 Bstr(pCtx->strPassword).raw(),
880 Bstr(pCtx->strDomain).raw(),
881 Bstr(pszSessionName).raw(),
882 pCtx->pGuestSession.asOutParam()));
883 }
884 catch (std::bad_alloc &)
885 {
886 RTMsgError(GuestCtrl::tr("Out of memory setting up IGuest::CreateSession call"));
887 rc = E_OUTOFMEMORY;
888 }
889 if (SUCCEEDED(rc))
890 {
891 /*
892 * Wait for guest session to start.
893 */
894 if (pCtx->cVerbose)
895 RTPrintf(GuestCtrl::tr("Waiting for guest session to start...\n"));
896 GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None; /* Shut up MSC */
897 try
898 {
899 com::SafeArray<GuestSessionWaitForFlag_T> aSessionWaitFlags;
900 aSessionWaitFlags.push_back(GuestSessionWaitForFlag_Start);
901 CHECK_ERROR(pCtx->pGuestSession, WaitForArray(ComSafeArrayAsInParam(aSessionWaitFlags),
902 /** @todo Make session handling timeouts configurable. */
903 30 * 1000, &enmWaitResult));
904 }
905 catch (std::bad_alloc &)
906 {
907 RTMsgError(GuestCtrl::tr("Out of memory setting up IGuestSession::WaitForArray call"));
908 rc = E_OUTOFMEMORY;
909 }
910 if (SUCCEEDED(rc))
911 {
912 /* The WaitFlagNotSupported result may happen with GAs older than 4.3. */
913 if ( enmWaitResult == GuestSessionWaitResult_Start
914 || enmWaitResult == GuestSessionWaitResult_WaitFlagNotSupported)
915 {
916 /*
917 * Get the session ID and we're ready to rumble.
918 */
919 CHECK_ERROR(pCtx->pGuestSession, COMGETTER(Id)(&pCtx->uSessionID));
920 if (SUCCEEDED(rc))
921 {
922 if (pCtx->cVerbose)
923 RTPrintf(GuestCtrl::tr("Successfully started guest session (ID %RU32)\n"), pCtx->uSessionID);
924 RTStrFree(pszSessionName);
925 return RTEXITCODE_SUCCESS;
926 }
927 }
928 else
929 {
930 GuestSessionStatus_T enmSessionStatus;
931 CHECK_ERROR(pCtx->pGuestSession, COMGETTER(Status)(&enmSessionStatus));
932 RTMsgError(GuestCtrl::tr("Error starting guest session (current status is: %s)\n"),
933 SUCCEEDED(rc) ? gctlGuestSessionStatusToText(enmSessionStatus) : GuestCtrl::tr("<unknown>"));
934 }
935 }
936 }
937
938 RTStrFree(pszSessionName);
939 return RTEXITCODE_FAILURE;
940}
941
942
943/**
944 * Completes the guest control context initialization after parsing arguments.
945 *
946 * Will validate common arguments, open a VM session, and if requested open a
947 * guest session and install the CTRL-C signal handler.
948 *
949 * It is good to validate all the options and arguments you can before making
950 * this call. However, the VM session, IGuest and IGuestSession interfaces are
951 * not availabe till after this call, so take care.
952 *
953 * @retval RTEXITCODE_SUCCESS on success.
954 * @retval RTEXITCODE_FAILURE and user message on failure.
955 * @param pCtx The guest control command context.
956 * GCTCMDCTX::pGuestSession and GCTLCMDCTX::uSessionID
957 * will be set.
958 * @remarks Can safely be called multiple times, will only do work once.
959 */
960static RTEXITCODE gctlCtxPostOptionParsingInit(PGCTLCMDCTX pCtx)
961{
962 if (pCtx->fPostOptionParsingInited)
963 return RTEXITCODE_SUCCESS;
964
965 /*
966 * Check that the user name isn't empty when we need it.
967 */
968 RTEXITCODE rcExit;
969 if ( (pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)
970 || pCtx->strUsername.isNotEmpty())
971 {
972 /*
973 * Open the VM session and if required, a guest session.
974 */
975 rcExit = gctlCtxInitVmSession(pCtx);
976 if ( rcExit == RTEXITCODE_SUCCESS
977 && !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
978 rcExit = gctlCtxInitGuestSession(pCtx);
979 if (rcExit == RTEXITCODE_SUCCESS)
980 {
981 /*
982 * Install signal handler if requested (errors are ignored).
983 */
984 if (!(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_NO_SIGNAL_HANDLER))
985 {
986 int rc = gctlSignalHandlerInstall();
987 pCtx->fInstalledSignalHandler = RT_SUCCESS(rc);
988 }
989 }
990 }
991 else
992 rcExit = errorSyntaxEx(USAGE_GUESTCONTROL, pCtx->pCmdDef->fSubcommandScope, GuestCtrl::tr("No user name specified!"));
993
994 pCtx->fPostOptionParsingInited = rcExit == RTEXITCODE_SUCCESS;
995 return rcExit;
996}
997
998
999/**
1000 * Cleans up the context when the command returns.
1001 *
1002 * This will close any open guest session, unless the DETACH flag is set.
1003 * It will also close any VM session that may be been established. Any signal
1004 * handlers we've installed will also be removed.
1005 *
1006 * Un-initializes the VM after guest control usage.
1007 * @param pCmdCtx Pointer to command context.
1008 */
1009static void gctlCtxTerm(PGCTLCMDCTX pCtx)
1010{
1011 HRESULT rc;
1012 AssertPtr(pCtx);
1013
1014 /*
1015 * Uninstall signal handler.
1016 */
1017 if (pCtx->fInstalledSignalHandler)
1018 {
1019 gctlSignalHandlerUninstall();
1020 pCtx->fInstalledSignalHandler = false;
1021 }
1022
1023 /*
1024 * Close, or at least release, the guest session.
1025 */
1026 if (pCtx->pGuestSession.isNotNull())
1027 {
1028 if ( !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)
1029 && !pCtx->fDetachGuestSession)
1030 {
1031 if (pCtx->cVerbose)
1032 RTPrintf(GuestCtrl::tr("Closing guest session ...\n"));
1033
1034 CHECK_ERROR(pCtx->pGuestSession, Close());
1035 }
1036 else if ( pCtx->fDetachGuestSession
1037 && pCtx->cVerbose)
1038 RTPrintf(GuestCtrl::tr("Guest session detached\n"));
1039
1040 pCtx->pGuestSession.setNull();
1041 }
1042
1043 /*
1044 * Close the VM session.
1045 */
1046 if (pCtx->fLockedVmSession)
1047 {
1048 Assert(pCtx->pArg->session.isNotNull());
1049 CHECK_ERROR(pCtx->pArg->session, UnlockMachine());
1050 pCtx->fLockedVmSession = false;
1051 }
1052}
1053
1054
1055
1056
1057
1058/*
1059 *
1060 *
1061 * Guest Control Command Handling.
1062 * Guest Control Command Handling.
1063 * Guest Control Command Handling.
1064 * Guest Control Command Handling.
1065 * Guest Control Command Handling.
1066 *
1067 *
1068 */
1069
1070
1071/** @name EXITCODEEXEC_XXX - Special run exit codes.
1072 *
1073 * Special exit codes for returning errors/information of a started guest
1074 * process to the command line VBoxManage was started from. Useful for e.g.
1075 * scripting.
1076 *
1077 * ASSUMING that all platforms have at least 7-bits for the exit code we can do
1078 * the following mapping:
1079 * - Guest exit code 0 is mapped to 0 on the host.
1080 * - Guest exit codes 1 thru 93 (0x5d) are displaced by 32, so that 1
1081 * becomes 33 (0x21) on the host and 93 becomes 125 (0x7d) on the host.
1082 * - Guest exit codes 94 (0x5e) and above are mapped to 126 (0x5e).
1083 *
1084 * We ASSUME that all VBoxManage status codes are in the range 0 thru 32.
1085 *
1086 * @note These are frozen as of 4.1.0.
1087 * @note The guest exit code mappings was introduced with 5.0 and the 'run'
1088 * command, they are/was not supported by 'exec'.
1089 * @sa gctlRunCalculateExitCode
1090 */
1091/** Process exited normally but with an exit code <> 0. */
1092#define EXITCODEEXEC_CODE ((RTEXITCODE)16)
1093#define EXITCODEEXEC_FAILED ((RTEXITCODE)17)
1094#define EXITCODEEXEC_TERM_SIGNAL ((RTEXITCODE)18)
1095#define EXITCODEEXEC_TERM_ABEND ((RTEXITCODE)19)
1096#define EXITCODEEXEC_TIMEOUT ((RTEXITCODE)20)
1097#define EXITCODEEXEC_DOWN ((RTEXITCODE)21)
1098/** Execution was interrupt by user (ctrl-c). */
1099#define EXITCODEEXEC_CANCELED ((RTEXITCODE)22)
1100/** The first mapped guest (non-zero) exit code. */
1101#define EXITCODEEXEC_MAPPED_FIRST 33
1102/** The last mapped guest (non-zero) exit code value (inclusive). */
1103#define EXITCODEEXEC_MAPPED_LAST 125
1104/** The number of exit codes from EXITCODEEXEC_MAPPED_FIRST to
1105 * EXITCODEEXEC_MAPPED_LAST. This is also the highest guest exit code number
1106 * we're able to map. */
1107#define EXITCODEEXEC_MAPPED_RANGE (93)
1108/** The guest exit code displacement value. */
1109#define EXITCODEEXEC_MAPPED_DISPLACEMENT 32
1110/** The guest exit code was too big to be mapped. */
1111#define EXITCODEEXEC_MAPPED_BIG ((RTEXITCODE)126)
1112/** @} */
1113
1114/**
1115 * Calculates the exit code of VBoxManage.
1116 *
1117 * @returns The exit code to return.
1118 * @param enmStatus The guest process status.
1119 * @param uExitCode The associated guest process exit code (where
1120 * applicable).
1121 * @param fReturnExitCodes Set if we're to use the 32-126 range for guest
1122 * exit codes.
1123 */
1124static RTEXITCODE gctlRunCalculateExitCode(ProcessStatus_T enmStatus, ULONG uExitCode, bool fReturnExitCodes)
1125{
1126 switch (enmStatus)
1127 {
1128 case ProcessStatus_TerminatedNormally:
1129 if (uExitCode == 0)
1130 return RTEXITCODE_SUCCESS;
1131 if (!fReturnExitCodes)
1132 return EXITCODEEXEC_CODE;
1133 if (uExitCode <= EXITCODEEXEC_MAPPED_RANGE)
1134 return (RTEXITCODE) (uExitCode + EXITCODEEXEC_MAPPED_DISPLACEMENT);
1135 return EXITCODEEXEC_MAPPED_BIG;
1136
1137 case ProcessStatus_TerminatedAbnormally:
1138 return EXITCODEEXEC_TERM_ABEND;
1139 case ProcessStatus_TerminatedSignal:
1140 return EXITCODEEXEC_TERM_SIGNAL;
1141
1142#if 0 /* see caller! */
1143 case ProcessStatus_TimedOutKilled:
1144 return EXITCODEEXEC_TIMEOUT;
1145 case ProcessStatus_Down:
1146 return EXITCODEEXEC_DOWN; /* Service/OS is stopping, process was killed. */
1147 case ProcessStatus_Error:
1148 return EXITCODEEXEC_FAILED;
1149
1150 /* The following is probably for detached? */
1151 case ProcessStatus_Starting:
1152 return RTEXITCODE_SUCCESS;
1153 case ProcessStatus_Started:
1154 return RTEXITCODE_SUCCESS;
1155 case ProcessStatus_Paused:
1156 return RTEXITCODE_SUCCESS;
1157 case ProcessStatus_Terminating:
1158 return RTEXITCODE_SUCCESS; /** @todo ???? */
1159#endif
1160
1161 default:
1162 AssertMsgFailed(("Unknown exit status (%u/%u) from guest process returned!\n", enmStatus, uExitCode));
1163 return RTEXITCODE_FAILURE;
1164 }
1165}
1166
1167
1168/**
1169 * Pumps guest output to the host.
1170 *
1171 * @return IPRT status code.
1172 * @param pProcess Pointer to appropriate process object.
1173 * @param hVfsIosDst Where to write the data. Can be the bit bucket or a (valid [std]) handle.
1174 * @param uHandle Handle where to read the data from.
1175 * @param cMsTimeout Timeout (in ms) to wait for the operation to
1176 * complete.
1177 */
1178static int gctlRunPumpOutput(IProcess *pProcess, RTVFSIOSTREAM hVfsIosDst, ULONG uHandle, RTMSINTERVAL cMsTimeout)
1179{
1180 AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
1181 Assert(hVfsIosDst != NIL_RTVFSIOSTREAM);
1182
1183 int vrc;
1184
1185 SafeArray<BYTE> aOutputData;
1186 HRESULT hrc = pProcess->Read(uHandle, _64K, RT_MAX(cMsTimeout, 1), ComSafeArrayAsOutParam(aOutputData));
1187 if (SUCCEEDED(hrc))
1188 {
1189 size_t cbOutputData = aOutputData.size();
1190 if (cbOutputData == 0)
1191 vrc = VINF_SUCCESS;
1192 else
1193 {
1194 BYTE const *pbBuf = aOutputData.raw();
1195 AssertPtr(pbBuf);
1196
1197 vrc = RTVfsIoStrmWrite(hVfsIosDst, pbBuf, cbOutputData, true /*fBlocking*/, NULL);
1198 if (RT_FAILURE(vrc))
1199 RTMsgError(GuestCtrl::tr("Unable to write output, rc=%Rrc\n"), vrc);
1200 }
1201 }
1202 else
1203 vrc = gctlPrintError(pProcess, COM_IIDOF(IProcess));
1204 return vrc;
1205}
1206
1207
1208/**
1209 * Configures a host handle for pumping guest bits.
1210 *
1211 * @returns true if enabled and we successfully configured it.
1212 * @param fEnabled Whether pumping this pipe is configured to std handles,
1213 * or going to the bit bucket instead.
1214 * @param enmHandle The IPRT standard handle designation.
1215 * @param pszName The name for user messages.
1216 * @param enmTransformation The transformation to apply.
1217 * @param phVfsIos Where to return the resulting I/O stream handle.
1218 */
1219static bool gctlRunSetupHandle(bool fEnabled, RTHANDLESTD enmHandle, const char *pszName,
1220 kStreamTransform enmTransformation, PRTVFSIOSTREAM phVfsIos)
1221{
1222 if (fEnabled)
1223 {
1224 int vrc = RTVfsIoStrmFromStdHandle(enmHandle, 0, true /*fLeaveOpen*/, phVfsIos);
1225 if (RT_SUCCESS(vrc))
1226 {
1227 if (enmTransformation != kStreamTransform_None)
1228 {
1229 RTMsgWarning(GuestCtrl::tr("Unsupported %s line ending conversion"), pszName);
1230 /** @todo Implement dos2unix and unix2dos stream filters. */
1231 }
1232 return true;
1233 }
1234 RTMsgWarning(GuestCtrl::tr("Error getting %s handle: %Rrc"), pszName, vrc);
1235 }
1236 else /* If disabled, all goes to / gets fed to/from the bit bucket. */
1237 {
1238 RTFILE hFile;
1239 int vrc = RTFileOpenBitBucket(&hFile, enmHandle == RTHANDLESTD_INPUT ? RTFILE_O_READ : RTFILE_O_WRITE);
1240 if (RT_SUCCESS(vrc))
1241 {
1242 vrc = RTVfsIoStrmFromRTFile(hFile, 0 /* fOpen */, false /* fLeaveOpen */, phVfsIos);
1243 if (RT_SUCCESS(vrc))
1244 return true;
1245 }
1246 }
1247
1248 return false;
1249}
1250
1251
1252/**
1253 * Returns the remaining time (in ms) based on the start time and a set
1254 * timeout value. Returns RT_INDEFINITE_WAIT if no timeout was specified.
1255 *
1256 * @return RTMSINTERVAL Time left (in ms).
1257 * @param u64StartMs Start time (in ms).
1258 * @param cMsTimeout Timeout value (in ms).
1259 */
1260static RTMSINTERVAL gctlRunGetRemainingTime(uint64_t u64StartMs, RTMSINTERVAL cMsTimeout)
1261{
1262 if (!cMsTimeout || cMsTimeout == RT_INDEFINITE_WAIT) /* If no timeout specified, wait forever. */
1263 return RT_INDEFINITE_WAIT;
1264
1265 uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
1266 if (u64ElapsedMs >= cMsTimeout)
1267 return 0;
1268
1269 return cMsTimeout - (RTMSINTERVAL)u64ElapsedMs;
1270}
1271
1272/**
1273 * Common handler for the 'run' and 'start' commands.
1274 *
1275 * @returns Command exit code.
1276 * @param pCtx Guest session context.
1277 * @param argc The argument count.
1278 * @param argv The argument vector for this command.
1279 * @param fRunCmd Set if it's 'run' clear if 'start'.
1280 * @param fHelp The help flag for the command.
1281 */
1282static RTEXITCODE gctlHandleRunCommon(PGCTLCMDCTX pCtx, int argc, char **argv, bool fRunCmd, uint32_t fHelp)
1283{
1284 RT_NOREF(fHelp);
1285 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
1286
1287 /*
1288 * Parse arguments.
1289 */
1290 enum kGstCtrlRunOpt
1291 {
1292 kGstCtrlRunOpt_IgnoreOrphanedProcesses = 1000,
1293 kGstCtrlRunOpt_NoProfile, /** @todo Deprecated and will be removed soon; use kGstCtrlRunOpt_Profile instead, if needed. */
1294 kGstCtrlRunOpt_Profile,
1295 kGstCtrlRunOpt_Dos2Unix,
1296 kGstCtrlRunOpt_Unix2Dos,
1297 kGstCtrlRunOpt_WaitForStdOut,
1298 kGstCtrlRunOpt_NoWaitForStdOut,
1299 kGstCtrlRunOpt_WaitForStdErr,
1300 kGstCtrlRunOpt_NoWaitForStdErr
1301 };
1302 static const RTGETOPTDEF s_aOptions[] =
1303 {
1304 GCTLCMD_COMMON_OPTION_DEFS()
1305 { "--putenv", 'E', RTGETOPT_REQ_STRING },
1306 { "--exe", 'e', RTGETOPT_REQ_STRING },
1307 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
1308 { "--unquoted-args", 'u', RTGETOPT_REQ_NOTHING },
1309 { "--ignore-operhaned-processes", kGstCtrlRunOpt_IgnoreOrphanedProcesses, RTGETOPT_REQ_NOTHING },
1310 { "--no-profile", kGstCtrlRunOpt_NoProfile, RTGETOPT_REQ_NOTHING }, /** @todo Deprecated. */
1311 { "--profile", kGstCtrlRunOpt_Profile, RTGETOPT_REQ_NOTHING },
1312 /* run only: 6 - options */
1313 { "--dos2unix", kGstCtrlRunOpt_Dos2Unix, RTGETOPT_REQ_NOTHING },
1314 { "--unix2dos", kGstCtrlRunOpt_Unix2Dos, RTGETOPT_REQ_NOTHING },
1315 { "--no-wait-stdout", kGstCtrlRunOpt_NoWaitForStdOut, RTGETOPT_REQ_NOTHING },
1316 { "--wait-stdout", kGstCtrlRunOpt_WaitForStdOut, RTGETOPT_REQ_NOTHING },
1317 { "--no-wait-stderr", kGstCtrlRunOpt_NoWaitForStdErr, RTGETOPT_REQ_NOTHING },
1318 { "--wait-stderr", kGstCtrlRunOpt_WaitForStdErr, RTGETOPT_REQ_NOTHING },
1319 };
1320
1321 /** @todo stdin handling. */
1322
1323 int ch;
1324 RTGETOPTUNION ValueUnion;
1325 RTGETOPTSTATE GetState;
1326 int vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions) - (fRunCmd ? 0 : 6),
1327 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1328 AssertRC(vrc);
1329
1330 com::SafeArray<ProcessCreateFlag_T> aCreateFlags;
1331 com::SafeArray<ProcessWaitForFlag_T> aWaitFlags;
1332 com::SafeArray<IN_BSTR> aArgs;
1333 com::SafeArray<IN_BSTR> aEnv;
1334 const char * pszImage = NULL;
1335 bool fWaitForStdOut = fRunCmd;
1336 bool fWaitForStdErr = fRunCmd;
1337 RTVFSIOSTREAM hVfsStdOut = NIL_RTVFSIOSTREAM;
1338 RTVFSIOSTREAM hVfsStdErr = NIL_RTVFSIOSTREAM;
1339 enum kStreamTransform enmStdOutTransform = kStreamTransform_None;
1340 enum kStreamTransform enmStdErrTransform = kStreamTransform_None;
1341 RTMSINTERVAL cMsTimeout = 0;
1342
1343 try
1344 {
1345 /* Wait for process start in any case. This is useful for scripting VBoxManage
1346 * when relying on its overall exit code. */
1347 aWaitFlags.push_back(ProcessWaitForFlag_Start);
1348
1349 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
1350 {
1351 /* For options that require an argument, ValueUnion has received the value. */
1352 switch (ch)
1353 {
1354 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
1355
1356 case 'E':
1357 if ( ValueUnion.psz[0] == '\0'
1358 || ValueUnion.psz[0] == '=')
1359 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RUN,
1360 GuestCtrl::tr("Invalid argument variable[=value]: '%s'"), ValueUnion.psz);
1361 aEnv.push_back(Bstr(ValueUnion.psz).raw());
1362 break;
1363
1364 case kGstCtrlRunOpt_IgnoreOrphanedProcesses:
1365 aCreateFlags.push_back(ProcessCreateFlag_IgnoreOrphanedProcesses);
1366 break;
1367
1368 case kGstCtrlRunOpt_NoProfile:
1369 /** @todo Deprecated, will be removed. */
1370 RTPrintf(GuestCtrl::tr("Warning: Deprecated option \"--no-profile\" specified\n"));
1371 break;
1372
1373 case kGstCtrlRunOpt_Profile:
1374 aCreateFlags.push_back(ProcessCreateFlag_Profile);
1375 break;
1376
1377 case 'e':
1378 pszImage = ValueUnion.psz;
1379 break;
1380
1381 case 'u':
1382 aCreateFlags.push_back(ProcessCreateFlag_UnquotedArguments);
1383 break;
1384
1385 /** @todo Add a hidden flag. */
1386
1387 case 't': /* Timeout */
1388 cMsTimeout = ValueUnion.u32;
1389 break;
1390
1391 /* run only options: */
1392 case kGstCtrlRunOpt_Dos2Unix:
1393 Assert(fRunCmd);
1394 enmStdErrTransform = enmStdOutTransform = kStreamTransform_Dos2Unix;
1395 break;
1396 case kGstCtrlRunOpt_Unix2Dos:
1397 Assert(fRunCmd);
1398 enmStdErrTransform = enmStdOutTransform = kStreamTransform_Unix2Dos;
1399 break;
1400
1401 case kGstCtrlRunOpt_WaitForStdOut:
1402 Assert(fRunCmd);
1403 fWaitForStdOut = true;
1404 break;
1405 case kGstCtrlRunOpt_NoWaitForStdOut:
1406 Assert(fRunCmd);
1407 fWaitForStdOut = false;
1408 break;
1409
1410 case kGstCtrlRunOpt_WaitForStdErr:
1411 Assert(fRunCmd);
1412 fWaitForStdErr = true;
1413 break;
1414 case kGstCtrlRunOpt_NoWaitForStdErr:
1415 Assert(fRunCmd);
1416 fWaitForStdErr = false;
1417 break;
1418
1419 case VINF_GETOPT_NOT_OPTION:
1420 aArgs.push_back(Bstr(ValueUnion.psz).raw());
1421 if (!pszImage)
1422 {
1423 Assert(aArgs.size() == 1);
1424 pszImage = ValueUnion.psz;
1425 }
1426 break;
1427
1428 default:
1429 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RUN, ch, &ValueUnion);
1430
1431 } /* switch */
1432 } /* while RTGetOpt */
1433
1434 /* Must have something to execute. */
1435 if (!pszImage || !*pszImage)
1436 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RUN, GuestCtrl::tr("No executable specified!"));
1437
1438 /*
1439 * Finalize process creation and wait flags and input/output streams.
1440 */
1441 if (!fRunCmd)
1442 {
1443 aCreateFlags.push_back(ProcessCreateFlag_WaitForProcessStartOnly);
1444 Assert(!fWaitForStdOut);
1445 Assert(!fWaitForStdErr);
1446 }
1447 else
1448 {
1449 aWaitFlags.push_back(ProcessWaitForFlag_Terminate);
1450 fWaitForStdOut = gctlRunSetupHandle(fWaitForStdOut, RTHANDLESTD_OUTPUT, "stdout", enmStdOutTransform, &hVfsStdOut);
1451 if (fWaitForStdOut)
1452 {
1453 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut);
1454 aWaitFlags.push_back(ProcessWaitForFlag_StdOut);
1455 }
1456 fWaitForStdErr = gctlRunSetupHandle(fWaitForStdErr, RTHANDLESTD_ERROR, "stderr", enmStdErrTransform, &hVfsStdErr);
1457 if (fWaitForStdErr)
1458 {
1459 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr);
1460 aWaitFlags.push_back(ProcessWaitForFlag_StdErr);
1461 }
1462 }
1463 }
1464 catch (std::bad_alloc &)
1465 {
1466 return RTMsgErrorExit(RTEXITCODE_FAILURE, "VERR_NO_MEMORY\n");
1467 }
1468
1469 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
1470 if (rcExit != RTEXITCODE_SUCCESS)
1471 return rcExit;
1472
1473 HRESULT rc;
1474
1475 try
1476 {
1477 do
1478 {
1479 /* Get current time stamp to later calculate rest of timeout left. */
1480 uint64_t msStart = RTTimeMilliTS();
1481
1482 /*
1483 * Create the process.
1484 */
1485 if (pCtx->cVerbose)
1486 {
1487 if (cMsTimeout == 0)
1488 RTPrintf(GuestCtrl::tr("Starting guest process ...\n"));
1489 else
1490 RTPrintf(GuestCtrl::tr("Starting guest process (within %ums)\n"), cMsTimeout);
1491 }
1492 ComPtr<IGuestProcess> pProcess;
1493 CHECK_ERROR_BREAK(pCtx->pGuestSession, ProcessCreate(Bstr(pszImage).raw(),
1494 ComSafeArrayAsInParam(aArgs),
1495 ComSafeArrayAsInParam(aEnv),
1496 ComSafeArrayAsInParam(aCreateFlags),
1497 gctlRunGetRemainingTime(msStart, cMsTimeout),
1498 pProcess.asOutParam()));
1499
1500 /*
1501 * Explicitly wait for the guest process to be in a started state.
1502 */
1503 com::SafeArray<ProcessWaitForFlag_T> aWaitStartFlags;
1504 aWaitStartFlags.push_back(ProcessWaitForFlag_Start);
1505 ProcessWaitResult_T waitResult;
1506 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitStartFlags),
1507 gctlRunGetRemainingTime(msStart, cMsTimeout), &waitResult));
1508
1509 ULONG uPID = 0;
1510 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
1511 if (fRunCmd && pCtx->cVerbose)
1512 RTPrintf(GuestCtrl::tr("Process '%s' (PID %RU32) started\n"), pszImage, uPID);
1513 else if (!fRunCmd && pCtx->cVerbose)
1514 {
1515 /* Just print plain PID to make it easier for scripts
1516 * invoking VBoxManage. */
1517 RTPrintf(GuestCtrl::tr("[%RU32 - Session %RU32]\n"), uPID, pCtx->uSessionID);
1518 }
1519
1520 /*
1521 * Wait for process to exit/start...
1522 */
1523 RTMSINTERVAL cMsTimeLeft = 1; /* Will be calculated. */
1524 bool fReadStdOut = false;
1525 bool fReadStdErr = false;
1526 bool fCompleted = false;
1527 bool fCompletedStartCmd = false;
1528
1529 vrc = VINF_SUCCESS;
1530 while ( !fCompleted
1531 && cMsTimeLeft > 0)
1532 {
1533 cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout);
1534 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitFlags),
1535 RT_MIN(500 /*ms*/, RT_MAX(cMsTimeLeft, 1 /*ms*/)),
1536 &waitResult));
1537 if (pCtx->cVerbose)
1538 RTPrintf(GuestCtrl::tr("waitResult: %d\n"), waitResult);
1539 switch (waitResult)
1540 {
1541 case ProcessWaitResult_Start: /** @todo you always wait for 'start', */
1542 fCompletedStartCmd = fCompleted = !fRunCmd; /* Only wait for startup if the 'start' command. */
1543 if (!fCompleted && aWaitFlags[0] == ProcessWaitForFlag_Start)
1544 aWaitFlags[0] = ProcessWaitForFlag_Terminate;
1545 break;
1546 case ProcessWaitResult_StdOut:
1547 fReadStdOut = true;
1548 break;
1549 case ProcessWaitResult_StdErr:
1550 fReadStdErr = true;
1551 break;
1552 case ProcessWaitResult_Terminate:
1553 if (pCtx->cVerbose)
1554 RTPrintf(GuestCtrl::tr("Process terminated\n"));
1555 /* Process terminated, we're done. */
1556 fCompleted = true;
1557 break;
1558 case ProcessWaitResult_WaitFlagNotSupported:
1559 /* The guest does not support waiting for stdout/err, so
1560 * yield to reduce the CPU load due to busy waiting. */
1561 RTThreadYield();
1562 fReadStdOut = fReadStdErr = true;
1563 /* Note: In case the user specified explicitly not wanting to wait for stdout / stderr,
1564 * the configured VFS handle goes to / will be fed from the bit bucket. */
1565 break;
1566 case ProcessWaitResult_Timeout:
1567 {
1568 /** @todo It is really unclear whether we will get stuck with the timeout
1569 * result here if the guest side times out the process and fails to
1570 * kill the process... To be on the save side, double the IPC and
1571 * check the process status every time we time out. */
1572 ProcessStatus_T enmProcStatus;
1573 CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&enmProcStatus));
1574 if ( enmProcStatus == ProcessStatus_TimedOutKilled
1575 || enmProcStatus == ProcessStatus_TimedOutAbnormally)
1576 fCompleted = true;
1577 fReadStdOut = fReadStdErr = true;
1578 break;
1579 }
1580 case ProcessWaitResult_Status:
1581 /* ignore. */
1582 break;
1583 case ProcessWaitResult_Error:
1584 /* waitFor is dead in the water, I think, so better leave the loop. */
1585 vrc = VERR_CALLBACK_RETURN;
1586 break;
1587
1588 case ProcessWaitResult_StdIn: AssertFailed(); /* did ask for this! */ break;
1589 case ProcessWaitResult_None: AssertFailed(); /* used. */ break;
1590 default: AssertFailed(); /* huh? */ break;
1591 }
1592
1593 if (g_fGuestCtrlCanceled)
1594 break;
1595
1596 /*
1597 * Pump output as needed.
1598 */
1599 if (fReadStdOut)
1600 {
1601 cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout);
1602 int vrc2 = gctlRunPumpOutput(pProcess, hVfsStdOut, 1 /* StdOut */, cMsTimeLeft);
1603 if (RT_FAILURE(vrc2) && RT_SUCCESS(vrc))
1604 vrc = vrc2;
1605 fReadStdOut = false;
1606 }
1607 if (fReadStdErr)
1608 {
1609 cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout);
1610 int vrc2 = gctlRunPumpOutput(pProcess, hVfsStdErr, 2 /* StdErr */, cMsTimeLeft);
1611 if (RT_FAILURE(vrc2) && RT_SUCCESS(vrc))
1612 vrc = vrc2;
1613 fReadStdErr = false;
1614 }
1615 if ( RT_FAILURE(vrc)
1616 || g_fGuestCtrlCanceled)
1617 break;
1618
1619 /*
1620 * Process events before looping.
1621 */
1622 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
1623 } /* while */
1624
1625 /*
1626 * Report status back to the user.
1627 */
1628 if (g_fGuestCtrlCanceled)
1629 {
1630 if (pCtx->cVerbose)
1631 RTPrintf(GuestCtrl::tr("Process execution aborted!\n"));
1632 rcExit = EXITCODEEXEC_CANCELED;
1633 }
1634 else if (fCompletedStartCmd)
1635 {
1636 if (pCtx->cVerbose)
1637 RTPrintf(GuestCtrl::tr("Process successfully started!\n"));
1638 rcExit = RTEXITCODE_SUCCESS;
1639 }
1640 else if (fCompleted)
1641 {
1642 ProcessStatus_T procStatus;
1643 CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&procStatus));
1644 if ( procStatus == ProcessStatus_TerminatedNormally
1645 || procStatus == ProcessStatus_TerminatedAbnormally
1646 || procStatus == ProcessStatus_TerminatedSignal)
1647 {
1648 LONG lExitCode;
1649 CHECK_ERROR_BREAK(pProcess, COMGETTER(ExitCode)(&lExitCode));
1650 if (pCtx->cVerbose)
1651 RTPrintf(GuestCtrl::tr("Exit code=%u (Status=%u [%s])\n"),
1652 lExitCode, procStatus, gctlProcessStatusToText(procStatus));
1653
1654 rcExit = gctlRunCalculateExitCode(procStatus, lExitCode, true /*fReturnExitCodes*/);
1655 }
1656 else if ( procStatus == ProcessStatus_TimedOutKilled
1657 || procStatus == ProcessStatus_TimedOutAbnormally)
1658 {
1659 if (pCtx->cVerbose)
1660 RTPrintf(GuestCtrl::tr("Process timed out (guest side) and %s\n"),
1661 procStatus == ProcessStatus_TimedOutAbnormally
1662 ? GuestCtrl::tr("failed to terminate so far") : GuestCtrl::tr("was terminated"));
1663 rcExit = EXITCODEEXEC_TIMEOUT;
1664 }
1665 else
1666 {
1667 if (pCtx->cVerbose)
1668 RTPrintf(GuestCtrl::tr("Process now is in status [%s] (unexpected)\n"),
1669 gctlProcessStatusToText(procStatus));
1670 rcExit = RTEXITCODE_FAILURE;
1671 }
1672 }
1673 else if (RT_FAILURE_NP(vrc))
1674 {
1675 if (pCtx->cVerbose)
1676 RTPrintf(GuestCtrl::tr("Process monitor loop quit with vrc=%Rrc\n"), vrc);
1677 rcExit = RTEXITCODE_FAILURE;
1678 }
1679 else
1680 {
1681 if (pCtx->cVerbose)
1682 RTPrintf(GuestCtrl::tr("Process monitor loop timed out\n"));
1683 rcExit = EXITCODEEXEC_TIMEOUT;
1684 }
1685
1686 } while (0);
1687 }
1688 catch (std::bad_alloc &)
1689 {
1690 rc = E_OUTOFMEMORY;
1691 }
1692
1693 /*
1694 * Decide what to do with the guest session.
1695 *
1696 * If it's the 'start' command where detach the guest process after
1697 * starting, don't close the guest session it is part of, except on
1698 * failure or ctrl-c.
1699 *
1700 * For the 'run' command the guest process quits with us.
1701 */
1702 if (!fRunCmd && SUCCEEDED(rc) && !g_fGuestCtrlCanceled)
1703 pCtx->fDetachGuestSession = true;
1704
1705 /* Make sure we return failure on failure. */
1706 if (FAILED(rc) && rcExit == RTEXITCODE_SUCCESS)
1707 rcExit = RTEXITCODE_FAILURE;
1708 return rcExit;
1709}
1710
1711
1712static DECLCALLBACK(RTEXITCODE) gctlHandleRun(PGCTLCMDCTX pCtx, int argc, char **argv)
1713{
1714 return gctlHandleRunCommon(pCtx, argc, argv, true /*fRunCmd*/, HELP_SCOPE_GSTCTRL_RUN);
1715}
1716
1717
1718static DECLCALLBACK(RTEXITCODE) gctlHandleStart(PGCTLCMDCTX pCtx, int argc, char **argv)
1719{
1720 return gctlHandleRunCommon(pCtx, argc, argv, false /*fRunCmd*/, HELP_SCOPE_GSTCTRL_START);
1721}
1722
1723
1724static RTEXITCODE gctlHandleCopy(PGCTLCMDCTX pCtx, int argc, char **argv, bool fHostToGuest)
1725{
1726 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
1727
1728 /*
1729 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
1730 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
1731 * does in here.
1732 */
1733 static const RTGETOPTDEF s_aOptions[] =
1734 {
1735 GCTLCMD_COMMON_OPTION_DEFS()
1736 { "--follow", 'L', RTGETOPT_REQ_NOTHING }, /* Kept for backwards-compatibility (VBox < 7.0). */
1737 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
1738 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1739 { "--target-directory", 't', RTGETOPT_REQ_STRING }
1740 };
1741
1742 int ch;
1743 RTGETOPTUNION ValueUnion;
1744 RTGETOPTSTATE GetState;
1745 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1746
1747 bool fDstMustBeDir = false;
1748 const char *pszDst = NULL;
1749 bool fFollow = false;
1750 bool fRecursive = false;
1751 uint64_t uUsage = fHostToGuest ? HELP_SCOPE_GSTCTRL_COPYTO : HELP_SCOPE_GSTCTRL_COPYFROM;
1752
1753 int vrc = VINF_SUCCESS;
1754 while ( (ch = RTGetOpt(&GetState, &ValueUnion)) != 0
1755 && ch != VINF_GETOPT_NOT_OPTION)
1756 {
1757 /* For options that require an argument, ValueUnion has received the value. */
1758 switch (ch)
1759 {
1760 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
1761
1762 case 'L':
1763 if (!RTStrICmp(ValueUnion.pDef->pszLong, "--follow"))
1764 RTMsgWarning("--follow is deprecated; use --dereference instead.");
1765 fFollow = true;
1766 break;
1767
1768 case 'R':
1769 fRecursive = true;
1770 break;
1771
1772 case 't':
1773 pszDst = ValueUnion.psz;
1774 fDstMustBeDir = true;
1775 break;
1776
1777 default:
1778 return errorGetOptEx(USAGE_GUESTCONTROL, uUsage, ch, &ValueUnion);
1779 }
1780 }
1781
1782 char **papszSources = RTGetOptNonOptionArrayPtr(&GetState);
1783 size_t cSources = &argv[argc] - papszSources;
1784
1785 if (!cSources)
1786 return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage, GuestCtrl::tr("No sources specified!"));
1787
1788 /* Unless a --target-directory is given, the last argument is the destination, so
1789 bump it from the source list. */
1790 if (pszDst == NULL && cSources >= 2)
1791 pszDst = papszSources[--cSources];
1792
1793 if (pszDst == NULL)
1794 return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage, GuestCtrl::tr("No destination specified!"));
1795
1796 char szAbsDst[RTPATH_MAX];
1797 if (!fHostToGuest)
1798 {
1799 vrc = RTPathAbs(pszDst, szAbsDst, sizeof(szAbsDst));
1800 if (RT_SUCCESS(vrc))
1801 pszDst = szAbsDst;
1802 else
1803 return RTMsgErrorExitFailure(GuestCtrl::tr("RTPathAbs failed on '%s': %Rrc"), pszDst, vrc);
1804 }
1805
1806 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
1807 if (rcExit != RTEXITCODE_SUCCESS)
1808 return rcExit;
1809
1810 /*
1811 * Done parsing arguments, do some more preparations.
1812 */
1813 if (pCtx->cVerbose)
1814 {
1815 if (fHostToGuest)
1816 RTPrintf(GuestCtrl::tr("Copying from host to guest ...\n"));
1817 else
1818 RTPrintf(GuestCtrl::tr("Copying from guest to host ...\n"));
1819 }
1820
1821 HRESULT rc = S_OK;
1822
1823 bool fDstIsDir = false;
1824
1825 if (!fHostToGuest)
1826 {
1827 RTFSOBJINFO ObjInfo;
1828 vrc = RTPathQueryInfo(szAbsDst, &ObjInfo, RTFSOBJATTRADD_NOTHING);
1829 if (RT_FAILURE(vrc))
1830 return RTMsgErrorExitFailure(GuestCtrl::tr("RTPathQueryInfo failed on '%s': %Rrc"), szAbsDst, vrc);
1831 fDstIsDir = RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode);
1832 }
1833 else
1834 {
1835 BOOL fDirExists = FALSE;
1836 rc = pCtx->pGuestSession->DirectoryExists(Bstr(pszDst).raw(), TRUE /* fFollowSymlinks */, &fDirExists);
1837 if (SUCCEEDED(rc))
1838 fDstIsDir = RT_BOOL(fDirExists);
1839 }
1840
1841 /* If multiple sources are specified, the destination must be a directory. */
1842 fDstMustBeDir = cSources > 1;
1843
1844 /* If destination must be a directory, check this first before everything else. */
1845 if ( fDstMustBeDir
1846 && !fDstIsDir)
1847 {
1848 return RTMsgErrorExitFailure(GuestCtrl::tr("Destination must be a directory!"));
1849 }
1850
1851 for (size_t iSrc = 0; iSrc < cSources; iSrc++)
1852 {
1853 /*
1854 * Note: Having a dedicated progress object on every single source being copied can be very slow.
1855 * We implement IGuestSession::Copy[From|To]Guest() since quite a while which does most of the below
1856 * under the hood in Main using a multi-staged progress object. However, leave this like it is for now
1857 * to (also) have a different method of API testing.
1858 */
1859 ComPtr<IProgress> pProgress;
1860 const char *pszSource = papszSources[iSrc];
1861
1862 /* Note: Wildcards have to be processed by the calling process (i.e. via globbing, if available). */
1863
1864 if (fHostToGuest)
1865 {
1866 /*
1867 * Source is host, destination guest.
1868 */
1869 char szAbsSrc[RTPATH_MAX];
1870 vrc = RTPathAbs(pszSource, szAbsSrc, sizeof(szAbsSrc));
1871 if (RT_SUCCESS(vrc))
1872 {
1873 RTFSOBJINFO ObjInfo;
1874 vrc = RTPathQueryInfo(szAbsSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING);
1875 if (RT_SUCCESS(vrc))
1876 {
1877 if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
1878 {
1879 if (pCtx->cVerbose)
1880 RTPrintf(GuestCtrl::tr("File '%s' -> '%s'\n"), szAbsSrc, pszDst);
1881
1882 SafeArray<FileCopyFlag_T> aCopyFlags;
1883 rc = pCtx->pGuestSession->FileCopyToGuest(Bstr(szAbsSrc).raw(), Bstr(pszDst).raw(),
1884 ComSafeArrayAsInParam(aCopyFlags), pProgress.asOutParam());
1885 }
1886 else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
1887 {
1888 if (pCtx->cVerbose)
1889 RTPrintf(GuestCtrl::tr("Directory '%s' -> '%s'\n"), szAbsSrc, pszDst);
1890
1891 SafeArray<DirectoryCopyFlag_T> aCopyFlags;
1892 aCopyFlags.push_back(DirectoryCopyFlag_CopyIntoExisting);
1893 if (fRecursive)
1894 aCopyFlags.push_back(DirectoryCopyFlag_Recursive);
1895 if (fFollow)
1896 aCopyFlags.push_back(DirectoryCopyFlag_FollowLinks);
1897 rc = pCtx->pGuestSession->DirectoryCopyToGuest(Bstr(szAbsSrc).raw(), Bstr(pszDst).raw(),
1898 ComSafeArrayAsInParam(aCopyFlags), pProgress.asOutParam());
1899 }
1900 else
1901 rcExit = RTMsgErrorExitFailure(GuestCtrl::tr("Not a file or directory: %s\n"), szAbsSrc);
1902 }
1903 else
1904 rcExit = RTMsgErrorExitFailure(GuestCtrl::tr("RTPathQueryInfo failed on '%s': %Rrc"), szAbsSrc, vrc);
1905 }
1906 else
1907 rcExit = RTMsgErrorExitFailure(GuestCtrl::tr("RTPathAbs failed on '%s': %Rrc"), pszSource, vrc);
1908 }
1909 else
1910 {
1911 /*
1912 * Source guest, destination host.
1913 */
1914 /* We need to query the source type on the guest first in order to know which copy flavor we need. */
1915 ComPtr<IGuestFsObjInfo> pFsObjInfo;
1916 rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(pszSource).raw(), TRUE /* fFollowSymlinks */, pFsObjInfo.asOutParam());
1917 if (SUCCEEDED(rc))
1918 {
1919 FsObjType_T enmObjType;
1920 CHECK_ERROR(pFsObjInfo,COMGETTER(Type)(&enmObjType));
1921 if (SUCCEEDED(rc))
1922 {
1923 /* Take action according to source file. */
1924 if (enmObjType == FsObjType_Directory)
1925 {
1926 if (pCtx->cVerbose)
1927 RTPrintf(GuestCtrl::tr("Directory '%s' -> '%s'\n"), pszSource, pszDst);
1928
1929 SafeArray<DirectoryCopyFlag_T> aCopyFlags;
1930 aCopyFlags.push_back(DirectoryCopyFlag_CopyIntoExisting);
1931 if (fRecursive)
1932 aCopyFlags.push_back(DirectoryCopyFlag_Recursive);
1933 if (fFollow)
1934 aCopyFlags.push_back(DirectoryCopyFlag_FollowLinks);
1935 rc = pCtx->pGuestSession->DirectoryCopyFromGuest(Bstr(pszSource).raw(), Bstr(pszDst).raw(),
1936 ComSafeArrayAsInParam(aCopyFlags), pProgress.asOutParam());
1937 }
1938 else if (enmObjType == FsObjType_File)
1939 {
1940 if (pCtx->cVerbose)
1941 RTPrintf(GuestCtrl::tr("File '%s' -> '%s'\n"), pszSource, pszDst);
1942
1943 SafeArray<FileCopyFlag_T> aCopyFlags;
1944 if (fFollow)
1945 aCopyFlags.push_back(FileCopyFlag_FollowLinks);
1946 rc = pCtx->pGuestSession->FileCopyFromGuest(Bstr(pszSource).raw(), Bstr(pszDst).raw(),
1947 ComSafeArrayAsInParam(aCopyFlags), pProgress.asOutParam());
1948 }
1949 else
1950 rcExit = RTMsgErrorExitFailure(GuestCtrl::tr("Not a file or directory: %s\n"), pszSource);
1951 }
1952 else
1953 rcExit = RTEXITCODE_FAILURE;
1954 }
1955 else
1956 rcExit = RTMsgErrorExitFailure(GuestCtrl::tr("FsObjQueryInfo failed on '%s': %Rhrc"), pszSource, rc);
1957 }
1958
1959 if (FAILED(rc))
1960 {
1961 vrc = gctlPrintError(pCtx->pGuestSession, COM_IIDOF(IGuestSession));
1962 }
1963 else if (pProgress.isNotNull())
1964 {
1965 if (pCtx->cVerbose)
1966 rc = showProgress(pProgress);
1967 else
1968 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
1969 if (SUCCEEDED(rc))
1970 CHECK_PROGRESS_ERROR(pProgress, (GuestCtrl::tr("File copy failed")));
1971 vrc = gctlPrintProgressError(pProgress);
1972 }
1973
1974 /* Keep going. */
1975 }
1976
1977 if (RT_FAILURE(vrc))
1978 rcExit = RTEXITCODE_FAILURE;
1979
1980 return rcExit;
1981}
1982
1983static DECLCALLBACK(RTEXITCODE) gctlHandleCopyFrom(PGCTLCMDCTX pCtx, int argc, char **argv)
1984{
1985 return gctlHandleCopy(pCtx, argc, argv, false /* Guest to host */);
1986}
1987
1988static DECLCALLBACK(RTEXITCODE) gctlHandleCopyTo(PGCTLCMDCTX pCtx, int argc, char **argv)
1989{
1990 return gctlHandleCopy(pCtx, argc, argv, true /* Host to guest */);
1991}
1992
1993static DECLCALLBACK(RTEXITCODE) gctrlHandleMkDir(PGCTLCMDCTX pCtx, int argc, char **argv)
1994{
1995 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
1996
1997 static const RTGETOPTDEF s_aOptions[] =
1998 {
1999 GCTLCMD_COMMON_OPTION_DEFS()
2000 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2001 { "--parents", 'P', RTGETOPT_REQ_NOTHING }
2002 };
2003
2004 int ch;
2005 RTGETOPTUNION ValueUnion;
2006 RTGETOPTSTATE GetState;
2007 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2008
2009 SafeArray<DirectoryCreateFlag_T> aDirCreateFlags;
2010 uint32_t fDirMode = 0; /* Default mode. */
2011 uint32_t cDirsCreated = 0;
2012 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2013
2014 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
2015 {
2016 /* For options that require an argument, ValueUnion has received the value. */
2017 switch (ch)
2018 {
2019 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2020
2021 case 'm': /* Mode */
2022 fDirMode = ValueUnion.u32;
2023 break;
2024
2025 case 'P': /* Create parents */
2026 aDirCreateFlags.push_back(DirectoryCreateFlag_Parents);
2027 break;
2028
2029 case VINF_GETOPT_NOT_OPTION:
2030 if (cDirsCreated == 0)
2031 {
2032 /*
2033 * First non-option - no more options now.
2034 */
2035 rcExit = gctlCtxPostOptionParsingInit(pCtx);
2036 if (rcExit != RTEXITCODE_SUCCESS)
2037 return rcExit;
2038 if (pCtx->cVerbose)
2039 RTPrintf(GuestCtrl::tr("Creating %RU32 directories...\n", "", argc - GetState.iNext + 1),
2040 argc - GetState.iNext + 1);
2041 }
2042 if (g_fGuestCtrlCanceled)
2043 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("mkdir was interrupted by Ctrl-C (%u left)\n"),
2044 argc - GetState.iNext + 1);
2045
2046 /*
2047 * Create the specified directory.
2048 *
2049 * On failure we'll change the exit status to failure and
2050 * continue with the next directory that needs creating. We do
2051 * this because we only create new things, and because this is
2052 * how /bin/mkdir works on unix.
2053 */
2054 cDirsCreated++;
2055 if (pCtx->cVerbose)
2056 RTPrintf(GuestCtrl::tr("Creating directory \"%s\" ...\n"), ValueUnion.psz);
2057 try
2058 {
2059 HRESULT rc;
2060 CHECK_ERROR(pCtx->pGuestSession, DirectoryCreate(Bstr(ValueUnion.psz).raw(),
2061 fDirMode, ComSafeArrayAsInParam(aDirCreateFlags)));
2062 if (FAILED(rc))
2063 rcExit = RTEXITCODE_FAILURE;
2064 }
2065 catch (std::bad_alloc &)
2066 {
2067 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Out of memory\n"));
2068 }
2069 break;
2070
2071 default:
2072 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MKDIR, ch, &ValueUnion);
2073 }
2074 }
2075
2076 if (!cDirsCreated)
2077 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MKDIR, GuestCtrl::tr("No directory to create specified!"));
2078 return rcExit;
2079}
2080
2081
2082static DECLCALLBACK(RTEXITCODE) gctlHandleRmDir(PGCTLCMDCTX pCtx, int argc, char **argv)
2083{
2084 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2085
2086 static const RTGETOPTDEF s_aOptions[] =
2087 {
2088 GCTLCMD_COMMON_OPTION_DEFS()
2089 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
2090 };
2091
2092 int ch;
2093 RTGETOPTUNION ValueUnion;
2094 RTGETOPTSTATE GetState;
2095 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2096
2097 bool fRecursive = false;
2098 uint32_t cDirRemoved = 0;
2099 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2100
2101 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
2102 {
2103 /* For options that require an argument, ValueUnion has received the value. */
2104 switch (ch)
2105 {
2106 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2107
2108 case 'R':
2109 fRecursive = true;
2110 break;
2111
2112 case VINF_GETOPT_NOT_OPTION:
2113 {
2114 if (cDirRemoved == 0)
2115 {
2116 /*
2117 * First non-option - no more options now.
2118 */
2119 rcExit = gctlCtxPostOptionParsingInit(pCtx);
2120 if (rcExit != RTEXITCODE_SUCCESS)
2121 return rcExit;
2122 if (pCtx->cVerbose)
2123 {
2124 if (fRecursive)
2125 RTPrintf(GuestCtrl::tr("Removing %RU32 directory tree(s)...\n", "", argc - GetState.iNext + 1),
2126 argc - GetState.iNext + 1);
2127 else
2128 RTPrintf(GuestCtrl::tr("Removing %RU32 directorie(s)...\n", "", argc - GetState.iNext + 1),
2129 argc - GetState.iNext + 1);
2130 }
2131 }
2132 if (g_fGuestCtrlCanceled)
2133 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("rmdir was interrupted by Ctrl-C (%u left)\n"),
2134 argc - GetState.iNext + 1);
2135
2136 cDirRemoved++;
2137 HRESULT rc;
2138 if (!fRecursive)
2139 {
2140 /*
2141 * Remove exactly one directory.
2142 */
2143 if (pCtx->cVerbose)
2144 RTPrintf(GuestCtrl::tr("Removing directory \"%s\" ...\n"), ValueUnion.psz);
2145 try
2146 {
2147 CHECK_ERROR(pCtx->pGuestSession, DirectoryRemove(Bstr(ValueUnion.psz).raw()));
2148 }
2149 catch (std::bad_alloc &)
2150 {
2151 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Out of memory\n"));
2152 }
2153 }
2154 else
2155 {
2156 /*
2157 * Remove the directory and anything under it, that means files
2158 * and everything. This is in the tradition of the Windows NT
2159 * CMD.EXE "rmdir /s" operation, a tradition which jpsoft's TCC
2160 * strongly warns against (and half-ways questions the sense of).
2161 */
2162 if (pCtx->cVerbose)
2163 RTPrintf(GuestCtrl::tr("Recursively removing directory \"%s\" ...\n"), ValueUnion.psz);
2164 try
2165 {
2166 /** @todo Make flags configurable. */
2167 com::SafeArray<DirectoryRemoveRecFlag_T> aRemRecFlags;
2168 aRemRecFlags.push_back(DirectoryRemoveRecFlag_ContentAndDir);
2169
2170 ComPtr<IProgress> ptrProgress;
2171 CHECK_ERROR(pCtx->pGuestSession, DirectoryRemoveRecursive(Bstr(ValueUnion.psz).raw(),
2172 ComSafeArrayAsInParam(aRemRecFlags),
2173 ptrProgress.asOutParam()));
2174 if (SUCCEEDED(rc))
2175 {
2176 if (pCtx->cVerbose)
2177 rc = showProgress(ptrProgress);
2178 else
2179 rc = ptrProgress->WaitForCompletion(-1 /* indefinitely */);
2180 if (SUCCEEDED(rc))
2181 CHECK_PROGRESS_ERROR(ptrProgress, (GuestCtrl::tr("Directory deletion failed")));
2182 ptrProgress.setNull();
2183 }
2184 }
2185 catch (std::bad_alloc &)
2186 {
2187 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Out of memory during recursive rmdir\n"));
2188 }
2189 }
2190
2191 /*
2192 * This command returns immediately on failure since it's destructive in nature.
2193 */
2194 if (FAILED(rc))
2195 return RTEXITCODE_FAILURE;
2196 break;
2197 }
2198
2199 default:
2200 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RMDIR, ch, &ValueUnion);
2201 }
2202 }
2203
2204 if (!cDirRemoved)
2205 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RMDIR, GuestCtrl::tr("No directory to remove specified!"));
2206 return rcExit;
2207}
2208
2209static DECLCALLBACK(RTEXITCODE) gctlHandleRm(PGCTLCMDCTX pCtx, int argc, char **argv)
2210{
2211 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2212
2213 static const RTGETOPTDEF s_aOptions[] =
2214 {
2215 GCTLCMD_COMMON_OPTION_DEFS()
2216 { "--force", 'f', RTGETOPT_REQ_NOTHING, },
2217 };
2218
2219 int ch;
2220 RTGETOPTUNION ValueUnion;
2221 RTGETOPTSTATE GetState;
2222 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2223
2224 uint32_t cFilesDeleted = 0;
2225 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2226 bool fForce = true;
2227
2228 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
2229 {
2230 /* For options that require an argument, ValueUnion has received the value. */
2231 switch (ch)
2232 {
2233 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2234
2235 case VINF_GETOPT_NOT_OPTION:
2236 if (cFilesDeleted == 0)
2237 {
2238 /*
2239 * First non-option - no more options now.
2240 */
2241 rcExit = gctlCtxPostOptionParsingInit(pCtx);
2242 if (rcExit != RTEXITCODE_SUCCESS)
2243 return rcExit;
2244 if (pCtx->cVerbose)
2245 RTPrintf(GuestCtrl::tr("Removing %RU32 file(s)...\n", "", argc - GetState.iNext + 1),
2246 argc - GetState.iNext + 1);
2247 }
2248 if (g_fGuestCtrlCanceled)
2249 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("rm was interrupted by Ctrl-C (%u left)\n"),
2250 argc - GetState.iNext + 1);
2251
2252 /*
2253 * Remove the specified file.
2254 *
2255 * On failure we will by default stop, however, the force option will
2256 * by unix traditions force us to ignore errors and continue.
2257 */
2258 cFilesDeleted++;
2259 if (pCtx->cVerbose)
2260 RTPrintf(GuestCtrl::tr("Removing file \"%s\" ...\n", ValueUnion.psz));
2261 try
2262 {
2263 /** @todo How does IGuestSession::FsObjRemove work with read-only files? Do we
2264 * need to do some chmod or whatever to better emulate the --force flag? */
2265 HRESULT rc;
2266 CHECK_ERROR(pCtx->pGuestSession, FsObjRemove(Bstr(ValueUnion.psz).raw()));
2267 if (FAILED(rc) && !fForce)
2268 return RTEXITCODE_FAILURE;
2269 }
2270 catch (std::bad_alloc &)
2271 {
2272 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Out of memory\n"));
2273 }
2274 break;
2275
2276 default:
2277 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RM, ch, &ValueUnion);
2278 }
2279 }
2280
2281 if (!cFilesDeleted && !fForce)
2282 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_RM, GuestCtrl::tr("No file to remove specified!"));
2283 return rcExit;
2284}
2285
2286static DECLCALLBACK(RTEXITCODE) gctlHandleMv(PGCTLCMDCTX pCtx, int argc, char **argv)
2287{
2288 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2289
2290 static const RTGETOPTDEF s_aOptions[] =
2291 {
2292 GCTLCMD_COMMON_OPTION_DEFS()
2293/** @todo Missing --force/-f flag. */
2294 };
2295
2296 int ch;
2297 RTGETOPTUNION ValueUnion;
2298 RTGETOPTSTATE GetState;
2299 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2300
2301 int vrc = VINF_SUCCESS;
2302
2303 bool fDryrun = false;
2304 std::vector< Utf8Str > vecSources;
2305 const char *pszDst = NULL;
2306 com::SafeArray<FsObjRenameFlag_T> aRenameFlags;
2307
2308 try
2309 {
2310 /** @todo Make flags configurable. */
2311 aRenameFlags.push_back(FsObjRenameFlag_NoReplace);
2312
2313 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2314 && RT_SUCCESS(vrc))
2315 {
2316 /* For options that require an argument, ValueUnion has received the value. */
2317 switch (ch)
2318 {
2319 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2320
2321 /** @todo Implement a --dryrun command. */
2322 /** @todo Implement rename flags. */
2323
2324 case VINF_GETOPT_NOT_OPTION:
2325 vecSources.push_back(Utf8Str(ValueUnion.psz));
2326 pszDst = ValueUnion.psz;
2327 break;
2328
2329 default:
2330 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MV, ch, &ValueUnion);
2331 }
2332 }
2333 }
2334 catch (std::bad_alloc &)
2335 {
2336 vrc = VERR_NO_MEMORY;
2337 }
2338
2339 if (RT_FAILURE(vrc))
2340 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Failed to initialize, rc=%Rrc\n"), vrc);
2341
2342 size_t cSources = vecSources.size();
2343 if (!cSources)
2344 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MV,
2345 GuestCtrl::tr("No source(s) to move specified!"));
2346 if (cSources < 2)
2347 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MV,
2348 GuestCtrl::tr("No destination specified!"));
2349
2350 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
2351 if (rcExit != RTEXITCODE_SUCCESS)
2352 return rcExit;
2353
2354 /* Delete last element, which now is the destination. */
2355 vecSources.pop_back();
2356 cSources = vecSources.size();
2357
2358 HRESULT rc = S_OK;
2359
2360 /* Destination must be a directory when specifying multiple sources. */
2361 if (cSources > 1)
2362 {
2363 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2364 rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(pszDst).raw(), FALSE /*followSymlinks*/, pFsObjInfo.asOutParam());
2365 if (FAILED(rc))
2366 {
2367 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Destination does not exist\n"));
2368 }
2369 else
2370 {
2371 FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC */
2372 rc = pFsObjInfo->COMGETTER(Type)(&enmObjType);
2373 if (SUCCEEDED(rc))
2374 {
2375 if (enmObjType != FsObjType_Directory)
2376 return RTMsgErrorExit(RTEXITCODE_FAILURE,
2377 GuestCtrl::tr("Destination must be a directory when specifying multiple sources\n"));
2378 }
2379 else
2380 return RTMsgErrorExit(RTEXITCODE_FAILURE,
2381 GuestCtrl::tr("Unable to determine destination type: %Rhrc\n"),
2382 rc);
2383 }
2384 }
2385
2386 /*
2387 * Rename (move) the entries.
2388 */
2389 if (pCtx->cVerbose)
2390 RTPrintf(GuestCtrl::tr("Renaming %RU32 %s ...\n"), cSources,
2391 cSources > 1 ? GuestCtrl::tr("sources", "", cSources) : GuestCtrl::tr("source"));
2392
2393 std::vector< Utf8Str >::iterator it = vecSources.begin();
2394 while ( it != vecSources.end()
2395 && !g_fGuestCtrlCanceled)
2396 {
2397 Utf8Str strSrcCur = (*it);
2398
2399 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2400 FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC */
2401 rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(strSrcCur).raw(), FALSE /*followSymlinks*/, pFsObjInfo.asOutParam());
2402 if (SUCCEEDED(rc))
2403 rc = pFsObjInfo->COMGETTER(Type)(&enmObjType);
2404 if (FAILED(rc))
2405 {
2406 RTPrintf(GuestCtrl::tr("Cannot stat \"%s\": No such file or directory\n"), strSrcCur.c_str());
2407 ++it;
2408 continue; /* Skip. */
2409 }
2410
2411 char *pszDstCur = NULL;
2412
2413 if (cSources > 1)
2414 {
2415 pszDstCur = RTPathJoinA(pszDst, RTPathFilename(strSrcCur.c_str()));
2416 }
2417 else
2418 pszDstCur = RTStrDup(pszDst);
2419
2420 AssertPtrBreakStmt(pszDstCur, VERR_NO_MEMORY);
2421
2422 if (pCtx->cVerbose)
2423 RTPrintf(GuestCtrl::tr("Renaming %s \"%s\" to \"%s\" ...\n"),
2424 enmObjType == FsObjType_Directory ? GuestCtrl::tr("directory", "object") : GuestCtrl::tr("file","object"),
2425 strSrcCur.c_str(), pszDstCur);
2426
2427 if (!fDryrun)
2428 {
2429 CHECK_ERROR(pCtx->pGuestSession, FsObjRename(Bstr(strSrcCur).raw(),
2430 Bstr(pszDstCur).raw(),
2431 ComSafeArrayAsInParam(aRenameFlags)));
2432 /* Keep going with next item in case of errors. */
2433 }
2434
2435 RTStrFree(pszDstCur);
2436
2437 ++it;
2438 }
2439
2440 if ( (it != vecSources.end())
2441 && pCtx->cVerbose)
2442 {
2443 RTPrintf(GuestCtrl::tr("Warning: Not all sources were renamed\n"));
2444 }
2445
2446 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2447}
2448
2449static DECLCALLBACK(RTEXITCODE) gctlHandleMkTemp(PGCTLCMDCTX pCtx, int argc, char **argv)
2450{
2451 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2452
2453 static const RTGETOPTDEF s_aOptions[] =
2454 {
2455 GCTLCMD_COMMON_OPTION_DEFS()
2456 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2457 { "--directory", 'D', RTGETOPT_REQ_NOTHING },
2458 { "--secure", 's', RTGETOPT_REQ_NOTHING },
2459 { "--tmpdir", 't', RTGETOPT_REQ_STRING }
2460 };
2461
2462 int ch;
2463 RTGETOPTUNION ValueUnion;
2464 RTGETOPTSTATE GetState;
2465 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2466
2467 Utf8Str strTemplate;
2468 uint32_t fMode = 0; /* Default mode. */
2469 bool fDirectory = false;
2470 bool fSecure = false;
2471 Utf8Str strTempDir;
2472
2473 DESTDIRMAP mapDirs;
2474
2475 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
2476 {
2477 /* For options that require an argument, ValueUnion has received the value. */
2478 switch (ch)
2479 {
2480 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2481
2482 case 'm': /* Mode */
2483 fMode = ValueUnion.u32;
2484 break;
2485
2486 case 'D': /* Create directory */
2487 fDirectory = true;
2488 break;
2489
2490 case 's': /* Secure */
2491 fSecure = true;
2492 break;
2493
2494 case 't': /* Temp directory */
2495 strTempDir = ValueUnion.psz;
2496 break;
2497
2498 case VINF_GETOPT_NOT_OPTION:
2499 if (strTemplate.isEmpty())
2500 strTemplate = ValueUnion.psz;
2501 else
2502 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MKTEMP,
2503 GuestCtrl::tr("More than one template specified!\n"));
2504 break;
2505
2506 default:
2507 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MKTEMP, ch, &ValueUnion);
2508 }
2509 }
2510
2511 if (strTemplate.isEmpty())
2512 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MKTEMP,
2513 GuestCtrl::tr("No template specified!"));
2514
2515 if (!fDirectory)
2516 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_MKTEMP,
2517 GuestCtrl::tr("Creating temporary files is currently not supported!"));
2518
2519 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
2520 if (rcExit != RTEXITCODE_SUCCESS)
2521 return rcExit;
2522
2523 /*
2524 * Create the directories.
2525 */
2526 if (pCtx->cVerbose)
2527 {
2528 if (fDirectory && !strTempDir.isEmpty())
2529 RTPrintf(GuestCtrl::tr("Creating temporary directory from template '%s' in directory '%s' ...\n"),
2530 strTemplate.c_str(), strTempDir.c_str());
2531 else if (fDirectory)
2532 RTPrintf(GuestCtrl::tr("Creating temporary directory from template '%s' in default temporary directory ...\n"),
2533 strTemplate.c_str());
2534 else if (!fDirectory && !strTempDir.isEmpty())
2535 RTPrintf(GuestCtrl::tr("Creating temporary file from template '%s' in directory '%s' ...\n"),
2536 strTemplate.c_str(), strTempDir.c_str());
2537 else if (!fDirectory)
2538 RTPrintf(GuestCtrl::tr("Creating temporary file from template '%s' in default temporary directory ...\n"),
2539 strTemplate.c_str());
2540 }
2541
2542 HRESULT rc = S_OK;
2543 if (fDirectory)
2544 {
2545 Bstr bstrDirectory;
2546 CHECK_ERROR(pCtx->pGuestSession, DirectoryCreateTemp(Bstr(strTemplate).raw(),
2547 fMode, Bstr(strTempDir).raw(),
2548 fSecure,
2549 bstrDirectory.asOutParam()));
2550 if (SUCCEEDED(rc))
2551 RTPrintf(GuestCtrl::tr("Directory name: %ls\n"), bstrDirectory.raw());
2552 }
2553 else
2554 {
2555 // else - temporary file not yet implemented
2556 /** @todo implement temporary file creation (we fend it off above, no
2557 * worries). */
2558 rc = E_FAIL;
2559 }
2560
2561 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2562}
2563
2564static DECLCALLBACK(RTEXITCODE) gctlHandleStat(PGCTLCMDCTX pCtx, int argc, char **argv)
2565{
2566 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2567
2568 static const RTGETOPTDEF s_aOptions[] =
2569 {
2570 GCTLCMD_COMMON_OPTION_DEFS()
2571 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
2572 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
2573 { "--format", 'c', RTGETOPT_REQ_STRING },
2574 { "--terse", 't', RTGETOPT_REQ_NOTHING }
2575 };
2576
2577 int ch;
2578 RTGETOPTUNION ValueUnion;
2579 RTGETOPTSTATE GetState;
2580 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2581
2582 while ( (ch = RTGetOpt(&GetState, &ValueUnion)) != 0
2583 && ch != VINF_GETOPT_NOT_OPTION)
2584 {
2585 /* For options that require an argument, ValueUnion has received the value. */
2586 switch (ch)
2587 {
2588 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2589
2590 case 'L': /* Dereference */
2591 case 'f': /* File-system */
2592 case 'c': /* Format */
2593 case 't': /* Terse */
2594 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_STAT,
2595 GuestCtrl::tr("Command \"%s\" not implemented yet!"), ValueUnion.psz);
2596
2597 default:
2598 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_STAT, ch, &ValueUnion);
2599 }
2600 }
2601
2602 if (ch != VINF_GETOPT_NOT_OPTION)
2603 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_STAT, GuestCtrl::tr("Nothing to stat!"));
2604
2605 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
2606 if (rcExit != RTEXITCODE_SUCCESS)
2607 return rcExit;
2608
2609
2610 /*
2611 * Do the file stat'ing.
2612 */
2613 while (ch == VINF_GETOPT_NOT_OPTION)
2614 {
2615 if (pCtx->cVerbose)
2616 RTPrintf(GuestCtrl::tr("Checking for element \"%s\" ...\n"), ValueUnion.psz);
2617
2618 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2619 HRESULT hrc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(ValueUnion.psz).raw(), FALSE /*followSymlinks*/,
2620 pFsObjInfo.asOutParam());
2621 if (FAILED(hrc))
2622 {
2623 /** @todo r=bird: There might be other reasons why we end up here than
2624 * non-existing "element" (object or file, please, nobody calls it elements). */
2625 if (pCtx->cVerbose)
2626 RTPrintf(GuestCtrl::tr("Failed to stat '%s': No such file\n"), ValueUnion.psz);
2627 rcExit = RTEXITCODE_FAILURE;
2628 }
2629 else
2630 {
2631 RTPrintf(GuestCtrl::tr(" File: '%s'\n"), ValueUnion.psz); /** @todo escape this name. */
2632
2633 FsObjType_T enmType = FsObjType_Unknown;
2634 CHECK_ERROR2I(pFsObjInfo, COMGETTER(Type)(&enmType));
2635 LONG64 cbObject = 0;
2636 CHECK_ERROR2I(pFsObjInfo, COMGETTER(ObjectSize)(&cbObject));
2637 LONG64 cbAllocated = 0;
2638 CHECK_ERROR2I(pFsObjInfo, COMGETTER(AllocatedSize)(&cbAllocated));
2639 LONG uid = 0;
2640 CHECK_ERROR2I(pFsObjInfo, COMGETTER(UID)(&uid));
2641 LONG gid = 0;
2642 CHECK_ERROR2I(pFsObjInfo, COMGETTER(GID)(&gid));
2643 Bstr bstrUsername;
2644 CHECK_ERROR2I(pFsObjInfo, COMGETTER(UserName)(bstrUsername.asOutParam()));
2645 Bstr bstrGroupName;
2646 CHECK_ERROR2I(pFsObjInfo, COMGETTER(GroupName)(bstrGroupName.asOutParam()));
2647 Bstr bstrAttribs;
2648 CHECK_ERROR2I(pFsObjInfo, COMGETTER(FileAttributes)(bstrAttribs.asOutParam()));
2649 LONG64 idNode = 0;
2650 CHECK_ERROR2I(pFsObjInfo, COMGETTER(NodeId)(&idNode));
2651 ULONG uDevNode = 0;
2652 CHECK_ERROR2I(pFsObjInfo, COMGETTER(NodeIdDevice)(&uDevNode));
2653 ULONG uDeviceNo = 0;
2654 CHECK_ERROR2I(pFsObjInfo, COMGETTER(DeviceNumber)(&uDeviceNo));
2655 ULONG cHardLinks = 1;
2656 CHECK_ERROR2I(pFsObjInfo, COMGETTER(HardLinks)(&cHardLinks));
2657 LONG64 nsBirthTime = 0;
2658 CHECK_ERROR2I(pFsObjInfo, COMGETTER(BirthTime)(&nsBirthTime));
2659 LONG64 nsChangeTime = 0;
2660 CHECK_ERROR2I(pFsObjInfo, COMGETTER(ChangeTime)(&nsChangeTime));
2661 LONG64 nsModificationTime = 0;
2662 CHECK_ERROR2I(pFsObjInfo, COMGETTER(ModificationTime)(&nsModificationTime));
2663 LONG64 nsAccessTime = 0;
2664 CHECK_ERROR2I(pFsObjInfo, COMGETTER(AccessTime)(&nsAccessTime));
2665
2666 RTPrintf(GuestCtrl::tr(" Size: %-17RU64 Alloc: %-19RU64 Type: %s\n"),
2667 cbObject, cbAllocated, gctlFsObjTypeToName(enmType));
2668 RTPrintf(GuestCtrl::tr("Device: %#-17RX32 INode: %-18RU64 Links: %u\n"), uDevNode, idNode, cHardLinks);
2669
2670 Utf8Str strAttrib(bstrAttribs);
2671 char *pszMode = strAttrib.mutableRaw();
2672 char *pszAttribs = strchr(pszMode, ' ');
2673 if (pszAttribs)
2674 do *pszAttribs++ = '\0';
2675 while (*pszAttribs == ' ');
2676 else
2677 pszAttribs = strchr(pszMode, '\0');
2678 if (uDeviceNo != 0)
2679 RTPrintf(GuestCtrl::tr(" Mode: %-16s Attrib: %-17s Dev ID: %#RX32\n"), pszMode, pszAttribs, uDeviceNo);
2680 else
2681 RTPrintf(GuestCtrl::tr(" Mode: %-16s Attrib: %s\n"), pszMode, pszAttribs);
2682
2683 RTPrintf(GuestCtrl::tr(" Owner: %4d/%-12ls Group: %4d/%ls\n"), uid, bstrUsername.raw(), gid, bstrGroupName.raw());
2684
2685 RTTIMESPEC TimeSpec;
2686 char szTmp[RTTIME_STR_LEN];
2687 RTPrintf(GuestCtrl::tr(" Birth: %s\n"), RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsBirthTime),
2688 szTmp, sizeof(szTmp)));
2689 RTPrintf(GuestCtrl::tr("Change: %s\n"), RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsChangeTime),
2690 szTmp, sizeof(szTmp)));
2691 RTPrintf(GuestCtrl::tr("Modify: %s\n"), RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsModificationTime),
2692 szTmp, sizeof(szTmp)));
2693 RTPrintf(GuestCtrl::tr("Access: %s\n"), RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsAccessTime),
2694 szTmp, sizeof(szTmp)));
2695
2696 /* Skiping: Generation ID - only the ISO9660 VFS sets this. FreeBSD user flags. */
2697 }
2698
2699 /* Next file. */
2700 ch = RTGetOpt(&GetState, &ValueUnion);
2701 }
2702
2703 return rcExit;
2704}
2705
2706/**
2707 * Waits for a Guest Additions run level being reached.
2708 *
2709 * @returns VBox status code.
2710 * Returns VERR_CANCELLED if waiting for cancelled due to signal handling, e.g. when CTRL+C or some sort was pressed.
2711 * @param pCtx The guest control command context.
2712 * @param enmRunLevel Run level to wait for.
2713 * @param cMsTimeout Timeout (in ms) for waiting.
2714 */
2715static int gctlWaitForRunLevel(PGCTLCMDCTX pCtx, AdditionsRunLevelType_T enmRunLevel, RTMSINTERVAL cMsTimeout)
2716{
2717 int vrc = VINF_SUCCESS; /* Shut up MSVC. */
2718
2719 try
2720 {
2721 HRESULT rc = S_OK;
2722 /** Whether we need to actually wait for the run level or if we already reached it. */
2723 bool fWait = false;
2724
2725 /* Install an event handler first to catch any runlevel changes. */
2726 ComObjPtr<GuestAdditionsRunlevelListenerImpl> pGuestListener;
2727 do
2728 {
2729 /* Listener creation. */
2730 pGuestListener.createObject();
2731 pGuestListener->init(new GuestAdditionsRunlevelListener(enmRunLevel));
2732
2733 /* Register for IGuest events. */
2734 ComPtr<IEventSource> es;
2735 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(EventSource)(es.asOutParam()));
2736 com::SafeArray<VBoxEventType_T> eventTypes;
2737 eventTypes.push_back(VBoxEventType_OnGuestAdditionsStatusChanged);
2738 CHECK_ERROR_BREAK(es, RegisterListener(pGuestListener, ComSafeArrayAsInParam(eventTypes),
2739 true /* Active listener */));
2740
2741 AdditionsRunLevelType_T enmRunLevelCur = AdditionsRunLevelType_None;
2742 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(AdditionsRunLevel)(&enmRunLevelCur));
2743 fWait = enmRunLevelCur != enmRunLevel;
2744
2745 if (pCtx->cVerbose)
2746 RTPrintf(GuestCtrl::tr("Current run level is %RU32\n"), enmRunLevelCur);
2747
2748 } while (0);
2749
2750 if (fWait)
2751 {
2752 if (pCtx->cVerbose)
2753 RTPrintf(GuestCtrl::tr("Waiting for run level %RU32 ...\n"), enmRunLevel);
2754
2755 RTMSINTERVAL tsStart = RTTimeMilliTS();
2756 while (RTTimeMilliTS() - tsStart < cMsTimeout)
2757 {
2758 /* Wait for the global signal semaphore getting signalled. */
2759 vrc = RTSemEventWait(g_SemEventGuestCtrlCanceled, 100 /* ms */);
2760 if (RT_FAILURE(vrc))
2761 {
2762 if (vrc == VERR_TIMEOUT)
2763 continue;
2764 else
2765 {
2766 RTPrintf(GuestCtrl::tr("Waiting failed with %Rrc\n"), vrc);
2767 break;
2768 }
2769 }
2770 else if (pCtx->cVerbose)
2771 {
2772 RTPrintf(GuestCtrl::tr("Run level %RU32 reached\n"), enmRunLevel);
2773 break;
2774 }
2775
2776 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
2777 }
2778
2779 if ( vrc == VERR_TIMEOUT
2780 && pCtx->cVerbose)
2781 RTPrintf(GuestCtrl::tr("Run level %RU32 not reached within time\n"), enmRunLevel);
2782 }
2783
2784 if (!pGuestListener.isNull())
2785 {
2786 /* Guest callback unregistration. */
2787 ComPtr<IEventSource> pES;
2788 CHECK_ERROR(pCtx->pGuest, COMGETTER(EventSource)(pES.asOutParam()));
2789 if (!pES.isNull())
2790 CHECK_ERROR(pES, UnregisterListener(pGuestListener));
2791 pGuestListener.setNull();
2792 }
2793
2794 if (g_fGuestCtrlCanceled)
2795 vrc = VERR_CANCELLED;
2796 }
2797 catch (std::bad_alloc &)
2798 {
2799 vrc = VERR_NO_MEMORY;
2800 }
2801
2802 return vrc;
2803}
2804
2805static DECLCALLBACK(RTEXITCODE) gctlHandleUpdateAdditions(PGCTLCMDCTX pCtx, int argc, char **argv)
2806{
2807 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2808
2809 /** Timeout to wait for the whole updating procedure to complete. */
2810 uint32_t cMsTimeout = RT_INDEFINITE_WAIT;
2811 /** Source path to .ISO Guest Additions file to use. */
2812 Utf8Str strSource;
2813 com::SafeArray<IN_BSTR> aArgs;
2814 /** Whether to reboot the guest automatically when the update process has finished successfully. */
2815 bool fRebootOnFinish = false;
2816 /** Whether to only wait for getting the update process started instead of waiting until it finishes. */
2817 bool fWaitStartOnly = false;
2818 /** Whether to wait for the VM being ready to start the update. Needs Guest Additions facility reporting. */
2819 bool fWaitReady = false;
2820 /** Whether to verify if the Guest Additions were successfully updated on the guest. */
2821 bool fVerify = false;
2822
2823 /*
2824 * Parse arguments.
2825 */
2826 enum KGSTCTRLUPDATEADDITIONSOPT
2827 {
2828 KGSTCTRLUPDATEADDITIONSOPT_REBOOT = 1000,
2829 KGSTCTRLUPDATEADDITIONSOPT_SOURCE,
2830 KGSTCTRLUPDATEADDITIONSOPT_TIMEOUT,
2831 KGSTCTRLUPDATEADDITIONSOPT_VERIFY,
2832 KGSTCTRLUPDATEADDITIONSOPT_WAITREADY,
2833 KGSTCTRLUPDATEADDITIONSOPT_WAITSTART
2834 };
2835
2836 static const RTGETOPTDEF s_aOptions[] =
2837 {
2838 GCTLCMD_COMMON_OPTION_DEFS()
2839 { "--reboot", KGSTCTRLUPDATEADDITIONSOPT_REBOOT, RTGETOPT_REQ_NOTHING },
2840 { "--source", KGSTCTRLUPDATEADDITIONSOPT_SOURCE, RTGETOPT_REQ_STRING },
2841 { "--timeout", KGSTCTRLUPDATEADDITIONSOPT_TIMEOUT, RTGETOPT_REQ_UINT32 },
2842 { "--verify", KGSTCTRLUPDATEADDITIONSOPT_VERIFY, RTGETOPT_REQ_NOTHING },
2843 { "--wait-ready", KGSTCTRLUPDATEADDITIONSOPT_WAITREADY, RTGETOPT_REQ_NOTHING },
2844 { "--wait-start", KGSTCTRLUPDATEADDITIONSOPT_WAITSTART, RTGETOPT_REQ_NOTHING }
2845 };
2846
2847 int ch;
2848 RTGETOPTUNION ValueUnion;
2849 RTGETOPTSTATE GetState;
2850 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2851
2852 int vrc = VINF_SUCCESS;
2853 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2854 && RT_SUCCESS(vrc))
2855 {
2856 switch (ch)
2857 {
2858 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2859
2860 case KGSTCTRLUPDATEADDITIONSOPT_REBOOT:
2861 fRebootOnFinish = true;
2862 break;
2863
2864 case KGSTCTRLUPDATEADDITIONSOPT_SOURCE:
2865 vrc = RTPathAbsCxx(strSource, ValueUnion.psz);
2866 if (RT_FAILURE(vrc))
2867 return RTMsgErrorExitFailure(GuestCtrl::tr("RTPathAbsCxx failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2868 break;
2869
2870 case KGSTCTRLUPDATEADDITIONSOPT_WAITSTART:
2871 fWaitStartOnly = true;
2872 break;
2873
2874 case KGSTCTRLUPDATEADDITIONSOPT_WAITREADY:
2875 fWaitReady = true;
2876 break;
2877
2878 case KGSTCTRLUPDATEADDITIONSOPT_VERIFY:
2879 fVerify = true;
2880 fRebootOnFinish = true; /* Verification needs a mandatory reboot after successful update. */
2881 break;
2882
2883 case VINF_GETOPT_NOT_OPTION:
2884 if (aArgs.size() == 0 && strSource.isEmpty())
2885 strSource = ValueUnion.psz;
2886 else
2887 aArgs.push_back(Bstr(ValueUnion.psz).raw());
2888 break;
2889
2890 default:
2891 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_UPDATEGA, ch, &ValueUnion);
2892 }
2893 }
2894
2895 if (pCtx->cVerbose)
2896 RTPrintf(GuestCtrl::tr("Updating Guest Additions ...\n"));
2897
2898 HRESULT rc = S_OK;
2899 while (strSource.isEmpty())
2900 {
2901 ComPtr<ISystemProperties> pProperties;
2902 CHECK_ERROR_BREAK(pCtx->pArg->virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam()));
2903 Bstr strISO;
2904 CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam()));
2905 strSource = strISO;
2906 break;
2907 }
2908
2909 /* Determine source if not set yet. */
2910 if (strSource.isEmpty())
2911 {
2912 RTMsgError(GuestCtrl::tr("No Guest Additions source found or specified, aborting\n"));
2913 vrc = VERR_FILE_NOT_FOUND;
2914 }
2915 else if (!RTFileExists(strSource.c_str()))
2916 {
2917 RTMsgError(GuestCtrl::tr("Source \"%s\" does not exist!\n"), strSource.c_str());
2918 vrc = VERR_FILE_NOT_FOUND;
2919 }
2920
2921
2922
2923#if 0
2924 ComPtr<IGuest> guest;
2925 rc = pConsole->COMGETTER(Guest)(guest.asOutParam());
2926 if (SUCCEEDED(rc) && !guest.isNull())
2927 {
2928 SHOW_STRING_PROP_NOT_EMPTY(guest, OSTypeId, "GuestOSType", GuestCtrl::tr("OS type:"));
2929
2930 AdditionsRunLevelType_T guestRunLevel; /** @todo Add a runlevel-to-string (e.g. 0 = "None") method? */
2931 rc = guest->COMGETTER(AdditionsRunLevel)(&guestRunLevel);
2932 if (SUCCEEDED(rc))
2933 SHOW_ULONG_VALUE("GuestAdditionsRunLevel", GuestCtrl::tr("Additions run level:"), (ULONG)guestRunLevel, "");
2934
2935 Bstr guestString;
2936 rc = guest->COMGETTER(AdditionsVersion)(guestString.asOutParam());
2937 if ( SUCCEEDED(rc)
2938 && !guestString.isEmpty())
2939 {
2940 ULONG uRevision;
2941 rc = guest->COMGETTER(AdditionsRevision)(&uRevision);
2942 if (FAILED(rc))
2943 uRevision = 0;
2944 RTStrPrintf(szValue, sizeof(szValue), "%ls r%u", guestString.raw(), uRevision);
2945 SHOW_UTF8_STRING("GuestAdditionsVersion", GuestCtrl::tr("Additions version:"), szValue);
2946 }
2947 }
2948#endif
2949
2950 if (RT_SUCCESS(vrc))
2951 {
2952 if (pCtx->cVerbose)
2953 RTPrintf(GuestCtrl::tr("Using source: %s\n"), strSource.c_str());
2954
2955 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
2956 if (rcExit != RTEXITCODE_SUCCESS)
2957 return rcExit;
2958
2959 if (fWaitReady)
2960 {
2961 if (pCtx->cVerbose)
2962 RTPrintf(GuestCtrl::tr("Waiting for current Guest Additions inside VM getting ready for updating ...\n"));
2963
2964 const uint64_t uTsStart = RTTimeMilliTS();
2965 vrc = gctlWaitForRunLevel(pCtx, AdditionsRunLevelType_Userland, cMsTimeout);
2966 if (RT_SUCCESS(vrc))
2967 cMsTimeout = cMsTimeout != RT_INDEFINITE_WAIT ? cMsTimeout - (RTTimeMilliTS() - uTsStart) : cMsTimeout;
2968 }
2969
2970 if (RT_SUCCESS(vrc))
2971 {
2972 /* Get current Guest Additions version / revision. */
2973 Bstr strGstVerCur;
2974 ULONG uGstRevCur = 0;
2975 rc = pCtx->pGuest->COMGETTER(AdditionsVersion)(strGstVerCur.asOutParam());
2976 if ( SUCCEEDED(rc)
2977 && !strGstVerCur.isEmpty())
2978 {
2979 rc = pCtx->pGuest->COMGETTER(AdditionsRevision)(&uGstRevCur);
2980 if (SUCCEEDED(rc))
2981 {
2982 if (pCtx->cVerbose)
2983 RTPrintf(GuestCtrl::tr("Guest Additions %lsr%RU64 currently installed, waiting for Guest Additions installer to start ...\n"),
2984 strGstVerCur.raw(), uGstRevCur);
2985 }
2986 }
2987
2988 com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags;
2989 if (fWaitStartOnly)
2990 aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly);
2991
2992 ComPtr<IProgress> pProgress;
2993 CHECK_ERROR(pCtx->pGuest, UpdateGuestAdditions(Bstr(strSource).raw(),
2994 ComSafeArrayAsInParam(aArgs),
2995 ComSafeArrayAsInParam(aUpdateFlags),
2996 pProgress.asOutParam()));
2997 if (FAILED(rc))
2998 vrc = gctlPrintError(pCtx->pGuest, COM_IIDOF(IGuest));
2999 else
3000 {
3001 if (pCtx->cVerbose)
3002 rc = showProgress(pProgress);
3003 else
3004 rc = pProgress->WaitForCompletion((int32_t)cMsTimeout);
3005
3006 if (SUCCEEDED(rc))
3007 CHECK_PROGRESS_ERROR(pProgress, (GuestCtrl::tr("Guest Additions update failed")));
3008 vrc = gctlPrintProgressError(pProgress);
3009 if (RT_SUCCESS(vrc))
3010 {
3011 if (pCtx->cVerbose)
3012 RTPrintf(GuestCtrl::tr("Guest Additions update successful.\n"));
3013
3014 if (fRebootOnFinish)
3015 {
3016 if (pCtx->cVerbose)
3017 RTPrintf(GuestCtrl::tr("Rebooting guest ...\n"));
3018 com::SafeArray<GuestShutdownFlag_T> aShutdownFlags;
3019 aShutdownFlags.push_back(GuestShutdownFlag_Reboot);
3020 CHECK_ERROR(pCtx->pGuest, Shutdown(ComSafeArrayAsInParam(aShutdownFlags)));
3021 if (FAILED(rc))
3022 {
3023 if (rc == VBOX_E_NOT_SUPPORTED)
3024 {
3025 RTPrintf(GuestCtrl::tr("Current installed Guest Additions don't support automatic rebooting. "
3026 "Please reboot manually.\n"));
3027 vrc = VERR_NOT_SUPPORTED;
3028 }
3029 else
3030 vrc = gctlPrintError(pCtx->pGuest, COM_IIDOF(IGuest));
3031 }
3032 else
3033 {
3034 if (fWaitReady)
3035 {
3036 if (pCtx->cVerbose)
3037 RTPrintf(GuestCtrl::tr("Waiting for new Guest Additions inside VM getting ready ...\n"));
3038
3039 vrc = gctlWaitForRunLevel(pCtx, AdditionsRunLevelType_Userland, cMsTimeout);
3040 if (RT_SUCCESS(vrc))
3041 {
3042 if (fVerify)
3043 {
3044 if (pCtx->cVerbose)
3045 RTPrintf(GuestCtrl::tr("Verifying Guest Additions update ...\n"));
3046
3047 /* Get new Guest Additions version / revision. */
3048 Bstr strGstVerNew;
3049 ULONG uGstRevNew = 0;
3050 rc = pCtx->pGuest->COMGETTER(AdditionsVersion)(strGstVerNew.asOutParam());
3051 if ( SUCCEEDED(rc)
3052 && !strGstVerNew.isEmpty())
3053 {
3054 rc = pCtx->pGuest->COMGETTER(AdditionsRevision)(&uGstRevNew);
3055 if (FAILED(rc))
3056 uGstRevNew = 0;
3057 }
3058
3059 /** @todo Do more verification here. */
3060 vrc = uGstRevNew > uGstRevCur ? VINF_SUCCESS : VERR_NO_CHANGE;
3061
3062 if (pCtx->cVerbose)
3063 {
3064 RTPrintf(GuestCtrl::tr("Old Guest Additions: %ls%RU64\n"), strGstVerCur.raw(),
3065 uGstRevCur);
3066 RTPrintf(GuestCtrl::tr("New Guest Additions: %ls%RU64\n"), strGstVerNew.raw(),
3067 uGstRevNew);
3068
3069 if (RT_FAILURE(vrc))
3070 {
3071 RTPrintf(GuestCtrl::tr("\nError updating Guest Additions, please check guest installer log\n"));
3072 }
3073 else
3074 {
3075 if (uGstRevNew < uGstRevCur)
3076 RTPrintf(GuestCtrl::tr("\nWARNING: Guest Additions were downgraded\n"));
3077 }
3078 }
3079 }
3080 }
3081 }
3082 else if (pCtx->cVerbose)
3083 RTPrintf(GuestCtrl::tr("The guest needs to be restarted in order to make use of the updated Guest Additions.\n"));
3084 }
3085 }
3086 }
3087 }
3088 }
3089 }
3090
3091 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3092}
3093
3094/**
3095 * Returns a Guest Additions run level from a string.
3096 *
3097 * @returns Run level if found, or AdditionsRunLevelType_None if not found / invalid.
3098 * @param pcszStr String to return run level for.
3099 */
3100static AdditionsRunLevelType_T gctlGetRunLevelFromStr(const char *pcszStr)
3101{
3102 AssertPtrReturn(pcszStr, AdditionsRunLevelType_None);
3103
3104 if (RTStrICmp(pcszStr, "system") == 0) return AdditionsRunLevelType_System;
3105 else if (RTStrICmp(pcszStr, "userland") == 0) return AdditionsRunLevelType_Userland;
3106 else if (RTStrICmp(pcszStr, "desktop") == 0) return AdditionsRunLevelType_Desktop;
3107
3108 return AdditionsRunLevelType_None;
3109}
3110
3111static DECLCALLBACK(RTEXITCODE) gctlHandleWaitRunLevel(PGCTLCMDCTX pCtx, int argc, char **argv)
3112{
3113 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3114
3115 /** Timeout to wait for run level being reached.
3116 * By default we wait until it's reached. */
3117 uint32_t cMsTimeout = RT_INDEFINITE_WAIT;
3118
3119 /*
3120 * Parse arguments.
3121 */
3122 enum KGSTCTRLWAITRUNLEVELOPT
3123 {
3124 KGSTCTRLWAITRUNLEVELOPT_TIMEOUT = 1000
3125 };
3126
3127 static const RTGETOPTDEF s_aOptions[] =
3128 {
3129 GCTLCMD_COMMON_OPTION_DEFS()
3130 { "--timeout", KGSTCTRLWAITRUNLEVELOPT_TIMEOUT, RTGETOPT_REQ_UINT32 }
3131 };
3132
3133 int ch;
3134 RTGETOPTUNION ValueUnion;
3135 RTGETOPTSTATE GetState;
3136 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3137
3138 AdditionsRunLevelType_T enmRunLevel = AdditionsRunLevelType_None;
3139
3140 int vrc = VINF_SUCCESS;
3141 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3142 && RT_SUCCESS(vrc))
3143 {
3144 switch (ch)
3145 {
3146 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3147
3148 case KGSTCTRLWAITRUNLEVELOPT_TIMEOUT:
3149 cMsTimeout = ValueUnion.u32;
3150 break;
3151
3152 case VINF_GETOPT_NOT_OPTION:
3153 {
3154 enmRunLevel = gctlGetRunLevelFromStr(ValueUnion.psz);
3155 if (enmRunLevel == AdditionsRunLevelType_None)
3156 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_WAITRUNLEVEL,
3157 GuestCtrl::tr("Invalid run level specified. Valid values are: system, userland, desktop"));
3158 break;
3159 }
3160
3161 default:
3162 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_WAITRUNLEVEL, ch, &ValueUnion);
3163 }
3164 }
3165
3166 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3167 if (rcExit != RTEXITCODE_SUCCESS)
3168 return rcExit;
3169
3170 if (enmRunLevel == AdditionsRunLevelType_None)
3171 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_WAITRUNLEVEL, GuestCtrl::tr("Missing run level to wait for"));
3172
3173 vrc = gctlWaitForRunLevel(pCtx, enmRunLevel, cMsTimeout);
3174
3175 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3176}
3177
3178static DECLCALLBACK(RTEXITCODE) gctlHandleList(PGCTLCMDCTX pCtx, int argc, char **argv)
3179{
3180 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3181
3182 static const RTGETOPTDEF s_aOptions[] =
3183 {
3184 GCTLCMD_COMMON_OPTION_DEFS()
3185 };
3186
3187 int ch;
3188 RTGETOPTUNION ValueUnion;
3189 RTGETOPTSTATE GetState;
3190 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3191
3192 bool fSeenListArg = false;
3193 bool fListAll = false;
3194 bool fListSessions = false;
3195 bool fListProcesses = false;
3196 bool fListFiles = false;
3197
3198 int vrc = VINF_SUCCESS;
3199 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3200 && RT_SUCCESS(vrc))
3201 {
3202 switch (ch)
3203 {
3204 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3205
3206 case VINF_GETOPT_NOT_OPTION:
3207 if ( !RTStrICmp(ValueUnion.psz, "sessions")
3208 || !RTStrICmp(ValueUnion.psz, "sess"))
3209 fListSessions = true;
3210 else if ( !RTStrICmp(ValueUnion.psz, "processes")
3211 || !RTStrICmp(ValueUnion.psz, "procs"))
3212 fListSessions = fListProcesses = true; /* Showing processes implies showing sessions. */
3213 else if (!RTStrICmp(ValueUnion.psz, "files"))
3214 fListSessions = fListFiles = true; /* Showing files implies showing sessions. */
3215 else if (!RTStrICmp(ValueUnion.psz, "all"))
3216 fListAll = true;
3217 else
3218 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_LIST,
3219 GuestCtrl::tr("Unknown list: '%s'"), ValueUnion.psz);
3220 fSeenListArg = true;
3221 break;
3222
3223 default:
3224 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_UPDATEGA, ch, &ValueUnion);
3225 }
3226 }
3227
3228 if (!fSeenListArg)
3229 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_LIST, GuestCtrl::tr("Missing list name"));
3230 Assert(fListAll || fListSessions);
3231
3232 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3233 if (rcExit != RTEXITCODE_SUCCESS)
3234 return rcExit;
3235
3236
3237 /** @todo Do we need a machine-readable output here as well? */
3238
3239 HRESULT rc;
3240 size_t cTotalProcs = 0;
3241 size_t cTotalFiles = 0;
3242
3243 SafeIfaceArray <IGuestSession> collSessions;
3244 CHECK_ERROR(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3245 if (SUCCEEDED(rc))
3246 {
3247 size_t const cSessions = collSessions.size();
3248 if (cSessions)
3249 {
3250 RTPrintf(GuestCtrl::tr("Active guest sessions:\n"));
3251
3252 /** @todo Make this output a bit prettier. No time now. */
3253
3254 for (size_t i = 0; i < cSessions; i++)
3255 {
3256 ComPtr<IGuestSession> pCurSession = collSessions[i];
3257 if (!pCurSession.isNull())
3258 {
3259 do
3260 {
3261 ULONG uID;
3262 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Id)(&uID));
3263 Bstr strName;
3264 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Name)(strName.asOutParam()));
3265 Bstr strUser;
3266 CHECK_ERROR_BREAK(pCurSession, COMGETTER(User)(strUser.asOutParam()));
3267 GuestSessionStatus_T sessionStatus;
3268 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Status)(&sessionStatus));
3269 RTPrintf(GuestCtrl::tr("\n\tSession #%-3zu ID=%-3RU32 User=%-16ls Status=[%s] Name=%ls"),
3270 i, uID, strUser.raw(), gctlGuestSessionStatusToText(sessionStatus), strName.raw());
3271 } while (0);
3272
3273 if ( fListAll
3274 || fListProcesses)
3275 {
3276 SafeIfaceArray <IGuestProcess> collProcesses;
3277 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcesses)));
3278 for (size_t a = 0; a < collProcesses.size(); a++)
3279 {
3280 ComPtr<IGuestProcess> pCurProcess = collProcesses[a];
3281 if (!pCurProcess.isNull())
3282 {
3283 do
3284 {
3285 ULONG uPID;
3286 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(PID)(&uPID));
3287 Bstr strExecPath;
3288 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(ExecutablePath)(strExecPath.asOutParam()));
3289 ProcessStatus_T procStatus;
3290 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(Status)(&procStatus));
3291
3292 RTPrintf(GuestCtrl::tr("\n\t\tProcess #%-03zu PID=%-6RU32 Status=[%s] Command=%ls"),
3293 a, uPID, gctlProcessStatusToText(procStatus), strExecPath.raw());
3294 } while (0);
3295 }
3296 }
3297
3298 cTotalProcs += collProcesses.size();
3299 }
3300
3301 if ( fListAll
3302 || fListFiles)
3303 {
3304 SafeIfaceArray <IGuestFile> collFiles;
3305 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Files)(ComSafeArrayAsOutParam(collFiles)));
3306 for (size_t a = 0; a < collFiles.size(); a++)
3307 {
3308 ComPtr<IGuestFile> pCurFile = collFiles[a];
3309 if (!pCurFile.isNull())
3310 {
3311 do
3312 {
3313 ULONG idFile;
3314 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Id)(&idFile));
3315 Bstr strName;
3316 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Filename)(strName.asOutParam()));
3317 FileStatus_T fileStatus;
3318 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Status)(&fileStatus));
3319
3320 RTPrintf(GuestCtrl::tr("\n\t\tFile #%-03zu ID=%-6RU32 Status=[%s] Name=%ls"),
3321 a, idFile, gctlFileStatusToText(fileStatus), strName.raw());
3322 } while (0);
3323 }
3324 }
3325
3326 cTotalFiles += collFiles.size();
3327 }
3328 }
3329 }
3330
3331 RTPrintf(GuestCtrl::tr("\n\nTotal guest sessions: %zu\n"), collSessions.size());
3332 if (fListAll || fListProcesses)
3333 RTPrintf(GuestCtrl::tr("Total guest processes: %zu\n"), cTotalProcs);
3334 if (fListAll || fListFiles)
3335 RTPrintf(GuestCtrl::tr("Total guest files: %zu\n"), cTotalFiles);
3336 }
3337 else
3338 RTPrintf(GuestCtrl::tr("No active guest sessions found\n"));
3339 }
3340
3341 if (FAILED(rc)) /** @todo yeah, right... Only the last error? */
3342 rcExit = RTEXITCODE_FAILURE;
3343
3344 return rcExit;
3345}
3346
3347static DECLCALLBACK(RTEXITCODE) gctlHandleCloseProcess(PGCTLCMDCTX pCtx, int argc, char **argv)
3348{
3349 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3350
3351 static const RTGETOPTDEF s_aOptions[] =
3352 {
3353 GCTLCMD_COMMON_OPTION_DEFS()
3354 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3355 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3356 };
3357
3358 int ch;
3359 RTGETOPTUNION ValueUnion;
3360 RTGETOPTSTATE GetState;
3361 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3362
3363 std::vector < uint32_t > vecPID;
3364 ULONG idSession = UINT32_MAX;
3365 Utf8Str strSessionName;
3366
3367 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3368 {
3369 /* For options that require an argument, ValueUnion has received the value. */
3370 switch (ch)
3371 {
3372 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3373
3374 case 'n': /* Session name (or pattern) */
3375 strSessionName = ValueUnion.psz;
3376 break;
3377
3378 case 'i': /* Session ID */
3379 idSession = ValueUnion.u32;
3380 break;
3381
3382 case VINF_GETOPT_NOT_OPTION:
3383 {
3384 /* Treat every else specified as a PID to kill. */
3385 uint32_t uPid;
3386 int rc = RTStrToUInt32Ex(ValueUnion.psz, NULL, 0, &uPid);
3387 if ( RT_SUCCESS(rc)
3388 && rc != VWRN_TRAILING_CHARS
3389 && rc != VWRN_NUMBER_TOO_BIG
3390 && rc != VWRN_NEGATIVE_UNSIGNED)
3391 {
3392 if (uPid != 0)
3393 {
3394 try
3395 {
3396 vecPID.push_back(uPid);
3397 }
3398 catch (std::bad_alloc &)
3399 {
3400 return RTMsgErrorExit(RTEXITCODE_FAILURE, GuestCtrl::tr("Out of memory"));
3401 }
3402 }
3403 else
3404 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSEPROCESS,
3405 GuestCtrl::tr("Invalid PID value: 0"));
3406 }
3407 else
3408 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSEPROCESS,
3409 GuestCtrl::tr("Error parsing PID value: %Rrc"), rc);
3410 break;
3411 }
3412
3413 default:
3414 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSEPROCESS, ch, &ValueUnion);
3415 }
3416 }
3417
3418 if (vecPID.empty())
3419 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSEPROCESS,
3420 GuestCtrl::tr("At least one PID must be specified to kill!"));
3421
3422 if ( strSessionName.isEmpty()
3423 && idSession == UINT32_MAX)
3424 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSEPROCESS, GuestCtrl::tr("No session ID specified!"));
3425
3426 if ( strSessionName.isNotEmpty()
3427 && idSession != UINT32_MAX)
3428 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSEPROCESS,
3429 GuestCtrl::tr("Either session ID or name (pattern) must be specified"));
3430
3431 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3432 if (rcExit != RTEXITCODE_SUCCESS)
3433 return rcExit;
3434
3435 HRESULT rc = S_OK;
3436
3437 ComPtr<IGuestSession> pSession;
3438 ComPtr<IGuestProcess> pProcess;
3439 do
3440 {
3441 uint32_t uProcsTerminated = 0;
3442
3443 SafeIfaceArray <IGuestSession> collSessions;
3444 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3445 size_t cSessions = collSessions.size();
3446
3447 uint32_t cSessionsHandled = 0;
3448 for (size_t i = 0; i < cSessions; i++)
3449 {
3450 pSession = collSessions[i];
3451 Assert(!pSession.isNull());
3452
3453 ULONG uID; /* Session ID */
3454 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3455 Bstr strName;
3456 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3457 Utf8Str strNameUtf8(strName); /* Session name */
3458
3459 bool fSessionFound;
3460 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3461 fSessionFound = uID == idSession;
3462 else /* ... or by naming pattern. */
3463 fSessionFound = RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str());
3464 if (fSessionFound)
3465 {
3466 AssertStmt(!pSession.isNull(), break);
3467 cSessionsHandled++;
3468
3469 SafeIfaceArray <IGuestProcess> collProcs;
3470 CHECK_ERROR_BREAK(pSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcs)));
3471
3472 size_t cProcs = collProcs.size();
3473 for (size_t p = 0; p < cProcs; p++)
3474 {
3475 pProcess = collProcs[p];
3476 Assert(!pProcess.isNull());
3477
3478 ULONG uPID; /* Process ID */
3479 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
3480
3481 bool fProcFound = false;
3482 for (size_t a = 0; a < vecPID.size(); a++) /* Slow, but works. */
3483 {
3484 fProcFound = vecPID[a] == uPID;
3485 if (fProcFound)
3486 break;
3487 }
3488
3489 if (fProcFound)
3490 {
3491 if (pCtx->cVerbose)
3492 RTPrintf(GuestCtrl::tr("Terminating process (PID %RU32) (session ID %RU32) ...\n"),
3493 uPID, uID);
3494 CHECK_ERROR_BREAK(pProcess, Terminate());
3495 uProcsTerminated++;
3496 }
3497 else
3498 {
3499 if (idSession != UINT32_MAX)
3500 RTPrintf(GuestCtrl::tr("No matching process(es) for session ID %RU32 found\n"),
3501 idSession);
3502 }
3503
3504 pProcess.setNull();
3505 }
3506
3507 pSession.setNull();
3508 }
3509 }
3510
3511 if (!cSessionsHandled)
3512 RTPrintf(GuestCtrl::tr("No matching session(s) found\n"));
3513
3514 if (uProcsTerminated)
3515 RTPrintf(GuestCtrl::tr("%RU32 process(es) terminated\n", "", uProcsTerminated), uProcsTerminated);
3516
3517 } while (0);
3518
3519 pProcess.setNull();
3520 pSession.setNull();
3521
3522 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3523}
3524
3525
3526static DECLCALLBACK(RTEXITCODE) gctlHandleCloseSession(PGCTLCMDCTX pCtx, int argc, char **argv)
3527{
3528 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3529
3530 enum GETOPTDEF_SESSIONCLOSE
3531 {
3532 GETOPTDEF_SESSIONCLOSE_ALL = 2000
3533 };
3534 static const RTGETOPTDEF s_aOptions[] =
3535 {
3536 GCTLCMD_COMMON_OPTION_DEFS()
3537 { "--all", GETOPTDEF_SESSIONCLOSE_ALL, RTGETOPT_REQ_NOTHING },
3538 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3539 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3540 };
3541
3542 int ch;
3543 RTGETOPTUNION ValueUnion;
3544 RTGETOPTSTATE GetState;
3545 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3546
3547 ULONG idSession = UINT32_MAX;
3548 Utf8Str strSessionName;
3549
3550 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3551 {
3552 /* For options that require an argument, ValueUnion has received the value. */
3553 switch (ch)
3554 {
3555 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3556
3557 case 'n': /* Session name pattern */
3558 strSessionName = ValueUnion.psz;
3559 break;
3560
3561 case 'i': /* Session ID */
3562 idSession = ValueUnion.u32;
3563 break;
3564
3565 case GETOPTDEF_SESSIONCLOSE_ALL:
3566 strSessionName = "*";
3567 break;
3568
3569 case VINF_GETOPT_NOT_OPTION:
3570 /** @todo Supply a CSV list of IDs or patterns to close?
3571 * break; */
3572 default:
3573 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSESESSION, ch, &ValueUnion);
3574 }
3575 }
3576
3577 if ( strSessionName.isEmpty()
3578 && idSession == UINT32_MAX)
3579 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSESESSION,
3580 GuestCtrl::tr("No session ID specified!"));
3581
3582 if ( !strSessionName.isEmpty()
3583 && idSession != UINT32_MAX)
3584 return errorSyntaxEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_CLOSESESSION,
3585 GuestCtrl::tr("Either session ID or name (pattern) must be specified"));
3586
3587 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3588 if (rcExit != RTEXITCODE_SUCCESS)
3589 return rcExit;
3590
3591 HRESULT rc = S_OK;
3592
3593 do
3594 {
3595 size_t cSessionsHandled = 0;
3596
3597 SafeIfaceArray <IGuestSession> collSessions;
3598 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3599 size_t cSessions = collSessions.size();
3600
3601 for (size_t i = 0; i < cSessions; i++)
3602 {
3603 ComPtr<IGuestSession> pSession = collSessions[i];
3604 Assert(!pSession.isNull());
3605
3606 ULONG uID; /* Session ID */
3607 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3608 Bstr strName;
3609 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3610 Utf8Str strNameUtf8(strName); /* Session name */
3611
3612 bool fSessionFound;
3613 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3614 fSessionFound = uID == idSession;
3615 else /* ... or by naming pattern. */
3616 fSessionFound = RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str());
3617 if (fSessionFound)
3618 {
3619 cSessionsHandled++;
3620
3621 Assert(!pSession.isNull());
3622 if (pCtx->cVerbose)
3623 RTPrintf(GuestCtrl::tr("Closing guest session ID=#%RU32 \"%s\" ...\n"),
3624 uID, strNameUtf8.c_str());
3625 CHECK_ERROR_BREAK(pSession, Close());
3626 if (pCtx->cVerbose)
3627 RTPrintf(GuestCtrl::tr("Guest session successfully closed\n"));
3628
3629 pSession.setNull();
3630 }
3631 }
3632
3633 if (!cSessionsHandled)
3634 {
3635 RTPrintf(GuestCtrl::tr("No guest session(s) found\n"));
3636 rc = E_ABORT; /* To set exit code accordingly. */
3637 }
3638
3639 } while (0);
3640
3641 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3642}
3643
3644
3645static DECLCALLBACK(RTEXITCODE) gctlHandleWatch(PGCTLCMDCTX pCtx, int argc, char **argv)
3646{
3647 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3648
3649 /*
3650 * Parse arguments.
3651 */
3652 static const RTGETOPTDEF s_aOptions[] =
3653 {
3654 GCTLCMD_COMMON_OPTION_DEFS()
3655 { "--timeout", 't', RTGETOPT_REQ_UINT32 }
3656 };
3657
3658 uint32_t cMsTimeout = RT_INDEFINITE_WAIT;
3659
3660 int ch;
3661 RTGETOPTUNION ValueUnion;
3662 RTGETOPTSTATE GetState;
3663 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3664
3665 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3666 {
3667 /* For options that require an argument, ValueUnion has received the value. */
3668 switch (ch)
3669 {
3670 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3671
3672 case 't': /* Timeout */
3673 cMsTimeout = ValueUnion.u32;
3674 break;
3675
3676 case VINF_GETOPT_NOT_OPTION:
3677 default:
3678 return errorGetOptEx(USAGE_GUESTCONTROL, HELP_SCOPE_GSTCTRL_WATCH, ch, &ValueUnion);
3679 }
3680 }
3681
3682 /** @todo Specify categories to watch for. */
3683
3684 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3685 if (rcExit != RTEXITCODE_SUCCESS)
3686 return rcExit;
3687
3688 HRESULT rc;
3689
3690 try
3691 {
3692 ComObjPtr<GuestEventListenerImpl> pGuestListener;
3693 do
3694 {
3695 /* Listener creation. */
3696 pGuestListener.createObject();
3697 pGuestListener->init(new GuestEventListener());
3698
3699 /* Register for IGuest events. */
3700 ComPtr<IEventSource> es;
3701 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(EventSource)(es.asOutParam()));
3702 com::SafeArray<VBoxEventType_T> eventTypes;
3703 eventTypes.push_back(VBoxEventType_OnGuestSessionRegistered);
3704 /** @todo Also register for VBoxEventType_OnGuestUserStateChanged on demand? */
3705 CHECK_ERROR_BREAK(es, RegisterListener(pGuestListener, ComSafeArrayAsInParam(eventTypes),
3706 true /* Active listener */));
3707 /* Note: All other guest control events have to be registered
3708 * as their corresponding objects appear. */
3709
3710 } while (0);
3711
3712 if (pCtx->cVerbose)
3713 RTPrintf(GuestCtrl::tr("Waiting for events ...\n"));
3714
3715 /* Wait for the global signal semaphore getting signalled. */
3716 int vrc = RTSemEventWait(g_SemEventGuestCtrlCanceled, cMsTimeout);
3717 if (vrc == VERR_TIMEOUT)
3718 {
3719 if (pCtx->cVerbose)
3720 RTPrintf(GuestCtrl::tr("Waiting done\n"));
3721 }
3722 else if (RT_FAILURE(vrc))
3723 RTPrintf(GuestCtrl::tr("Waiting failed with %Rrc\n"), vrc);
3724
3725 if (!pGuestListener.isNull())
3726 {
3727 /* Guest callback unregistration. */
3728 ComPtr<IEventSource> pES;
3729 CHECK_ERROR(pCtx->pGuest, COMGETTER(EventSource)(pES.asOutParam()));
3730 if (!pES.isNull())
3731 CHECK_ERROR(pES, UnregisterListener(pGuestListener));
3732 pGuestListener.setNull();
3733 }
3734 }
3735 catch (std::bad_alloc &)
3736 {
3737 rc = E_OUTOFMEMORY;
3738 }
3739
3740 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3741}
3742
3743/**
3744 * Access the guest control store.
3745 *
3746 * @returns program exit code.
3747 * @note see the command line API description for parameters
3748 */
3749RTEXITCODE handleGuestControl(HandlerArg *pArg)
3750{
3751 AssertPtr(pArg);
3752
3753#ifdef DEBUG_andy_disabled
3754 if (RT_FAILURE(tstTranslatePath()))
3755 return RTEXITCODE_FAILURE;
3756#endif
3757
3758 /*
3759 * Command definitions.
3760 */
3761 static const GCTLCMDDEF s_aCmdDefs[] =
3762 {
3763 { "run", gctlHandleRun, HELP_SCOPE_GSTCTRL_RUN, 0 },
3764 { "start", gctlHandleStart, HELP_SCOPE_GSTCTRL_START, 0 },
3765 { "copyfrom", gctlHandleCopyFrom, HELP_SCOPE_GSTCTRL_COPYFROM, 0 },
3766 { "copyto", gctlHandleCopyTo, HELP_SCOPE_GSTCTRL_COPYTO, 0 },
3767
3768 { "mkdir", gctrlHandleMkDir, HELP_SCOPE_GSTCTRL_MKDIR, 0 },
3769 { "md", gctrlHandleMkDir, HELP_SCOPE_GSTCTRL_MKDIR, 0 },
3770 { "createdirectory", gctrlHandleMkDir, HELP_SCOPE_GSTCTRL_MKDIR, 0 },
3771 { "createdir", gctrlHandleMkDir, HELP_SCOPE_GSTCTRL_MKDIR, 0 },
3772
3773 { "rmdir", gctlHandleRmDir, HELP_SCOPE_GSTCTRL_RMDIR, 0 },
3774 { "removedir", gctlHandleRmDir, HELP_SCOPE_GSTCTRL_RMDIR, 0 },
3775 { "removedirectory", gctlHandleRmDir, HELP_SCOPE_GSTCTRL_RMDIR, 0 },
3776
3777 { "rm", gctlHandleRm, HELP_SCOPE_GSTCTRL_RM, 0 },
3778 { "removefile", gctlHandleRm, HELP_SCOPE_GSTCTRL_RM, 0 },
3779 { "erase", gctlHandleRm, HELP_SCOPE_GSTCTRL_RM, 0 },
3780 { "del", gctlHandleRm, HELP_SCOPE_GSTCTRL_RM, 0 },
3781 { "delete", gctlHandleRm, HELP_SCOPE_GSTCTRL_RM, 0 },
3782
3783 { "mv", gctlHandleMv, HELP_SCOPE_GSTCTRL_MV, 0 },
3784 { "move", gctlHandleMv, HELP_SCOPE_GSTCTRL_MV, 0 },
3785 { "ren", gctlHandleMv, HELP_SCOPE_GSTCTRL_MV, 0 },
3786 { "rename", gctlHandleMv, HELP_SCOPE_GSTCTRL_MV, 0 },
3787
3788 { "mktemp", gctlHandleMkTemp, HELP_SCOPE_GSTCTRL_MKTEMP, 0 },
3789 { "createtemp", gctlHandleMkTemp, HELP_SCOPE_GSTCTRL_MKTEMP, 0 },
3790 { "createtemporary", gctlHandleMkTemp, HELP_SCOPE_GSTCTRL_MKTEMP, 0 },
3791
3792 { "stat", gctlHandleStat, HELP_SCOPE_GSTCTRL_STAT, 0 },
3793
3794 { "closeprocess", gctlHandleCloseProcess, HELP_SCOPE_GSTCTRL_CLOSEPROCESS, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER },
3795 { "closesession", gctlHandleCloseSession, HELP_SCOPE_GSTCTRL_CLOSESESSION, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER },
3796 { "list", gctlHandleList, HELP_SCOPE_GSTCTRL_LIST, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER },
3797 { "watch", gctlHandleWatch, HELP_SCOPE_GSTCTRL_WATCH, GCTLCMDCTX_F_SESSION_ANONYMOUS },
3798
3799 {"updateguestadditions",gctlHandleUpdateAdditions, HELP_SCOPE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS },
3800 { "updateadditions", gctlHandleUpdateAdditions, HELP_SCOPE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS },
3801 { "updatega", gctlHandleUpdateAdditions, HELP_SCOPE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS },
3802
3803 { "waitrunlevel", gctlHandleWaitRunLevel, HELP_SCOPE_GSTCTRL_WAITRUNLEVEL, GCTLCMDCTX_F_SESSION_ANONYMOUS },
3804 { "waitforrunlevel", gctlHandleWaitRunLevel, HELP_SCOPE_GSTCTRL_WAITRUNLEVEL, GCTLCMDCTX_F_SESSION_ANONYMOUS },
3805 };
3806
3807 /*
3808 * VBoxManage guestcontrol [common-options] <VM> [common-options] <sub-command> ...
3809 *
3810 * Parse common options and VM name until we find a sub-command. Allowing
3811 * the user to put the user and password related options before the
3812 * sub-command makes it easier to edit the command line when doing several
3813 * operations with the same guest user account. (Accidentally, it also
3814 * makes the syntax diagram shorter and easier to read.)
3815 */
3816 GCTLCMDCTX CmdCtx;
3817 RTEXITCODE rcExit = gctrCmdCtxInit(&CmdCtx, pArg);
3818 if (rcExit == RTEXITCODE_SUCCESS)
3819 {
3820 static const RTGETOPTDEF s_CommonOptions[] = { GCTLCMD_COMMON_OPTION_DEFS() };
3821
3822 int ch;
3823 RTGETOPTUNION ValueUnion;
3824 RTGETOPTSTATE GetState;
3825 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_CommonOptions, RT_ELEMENTS(s_CommonOptions), 0, 0 /* No sorting! */);
3826
3827 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3828 {
3829 switch (ch)
3830 {
3831 GCTLCMD_COMMON_OPTION_CASES(&CmdCtx, ch, &ValueUnion);
3832
3833 case VINF_GETOPT_NOT_OPTION:
3834 /* First comes the VM name or UUID. */
3835 if (!CmdCtx.pszVmNameOrUuid)
3836 CmdCtx.pszVmNameOrUuid = ValueUnion.psz;
3837 /*
3838 * The sub-command is next. Look it up and invoke it.
3839 * Note! Currently no warnings about user/password options (like we'll do later on)
3840 * for GCTLCMDCTX_F_SESSION_ANONYMOUS commands. No reason to be too pedantic.
3841 */
3842 else
3843 {
3844 const char *pszCmd = ValueUnion.psz;
3845 uint32_t iCmd;
3846 for (iCmd = 0; iCmd < RT_ELEMENTS(s_aCmdDefs); iCmd++)
3847 if (strcmp(s_aCmdDefs[iCmd].pszName, pszCmd) == 0)
3848 {
3849 CmdCtx.pCmdDef = &s_aCmdDefs[iCmd];
3850
3851 rcExit = s_aCmdDefs[iCmd].pfnHandler(&CmdCtx, pArg->argc - GetState.iNext + 1,
3852 &pArg->argv[GetState.iNext - 1]);
3853
3854 gctlCtxTerm(&CmdCtx);
3855 return rcExit;
3856 }
3857 return errorSyntax(USAGE_GUESTCONTROL, GuestCtrl::tr("Unknown sub-command: '%s'"), pszCmd);
3858 }
3859 break;
3860
3861 default:
3862 return errorGetOpt(USAGE_GUESTCONTROL, ch, &ValueUnion);
3863 }
3864 }
3865 if (CmdCtx.pszVmNameOrUuid)
3866 rcExit = errorSyntax(USAGE_GUESTCONTROL, GuestCtrl::tr("Missing sub-command"));
3867 else
3868 rcExit = errorSyntax(USAGE_GUESTCONTROL, GuestCtrl::tr("Missing VM name and sub-command"));
3869 }
3870 return rcExit;
3871}
3872#endif /* !VBOX_ONLY_DOCS */
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