VirtualBox

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

最後變更 在這個檔案從75832是 75832,由 vboxsync 提交於 6 年 前

VBoxManage/guestcontrol: r=bird comments

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

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