VirtualBox

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

Last change on this file since 99775 was 99775, checked in by vboxsync, 2 years ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette