VirtualBox

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

Last change on this file since 84036 was 83874, checked in by vboxsync, 5 years ago

Guest Control/Main: Emphasize that a guest needs to be restarted in order to make use of the updated Guest Additions.

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

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