VirtualBox

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

Last change on this file was 103415, checked in by vboxsync, 3 months ago

Additions,Main,VMM,Runtime: Fix some unused expression warnings, bugref:3409

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

© 2023 Oracle
ContactPrivacy policyTerms of Use