VirtualBox

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

Last change on this file since 82781 was 81143, checked in by vboxsync, 5 years ago

Guest Control/VBoxManage: Added support for renaming (mv) multiple sources to a (directory) destination, cleaned up that code area and print errors by default.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use