VirtualBox

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

Last change on this file since 83432 was 83303, checked in by vboxsync, 5 years ago

Guest Control/VBoxManage: Resolve absolute path of --source argument in gctlHandleUpdateAdditions().

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