VirtualBox

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

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

VBoxManage/guestcontrol: fix

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

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