VirtualBox

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

Last change on this file since 102749 was 102749, checked in by vboxsync, 11 months ago

Guest Control/VBoxManage: Implemented a "mount" sub command for the new GetMountPoints API. bugref:10415

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

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