VirtualBox

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

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

Guest Control: Resolved a @todo: Implemented DirectoryCopyFlag_Recursive + DirectoryCopyFlag_FollowLinks and no longer do this implicitly internally.

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