VirtualBox

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

Last change on this file since 99286 was 99276, checked in by vboxsync, 23 months ago

Guest Control/VBoxManage: Added display percentage used for a guest filesystem; made the layout a bit more compact [formatting nit]. bugref:10414

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