VirtualBox

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

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

Guest Control/Main: Return an error in IGuestProcess::terminate() if the guest process is not in a running state (anymore). Useful for testcases.

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