VirtualBox

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

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

doc/manual,FE/VBoxManage: Convert guestcontrol command to refentry documentation, ​bugref:9186

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

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