VirtualBox

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

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

FE/VBoxManage/Guest Control: Removed dead code.

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