VirtualBox

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

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

Main: bugref:1909: Fixed scm issues

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