VirtualBox

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

Last change on this file since 74942 was 73506, checked in by vboxsync, 6 years ago

VBoxManage: GCC 8.2.0 fixes

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

© 2023 Oracle
ContactPrivacy policyTerms of Use