VirtualBox

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

Last change on this file since 84589 was 84570, checked in by vboxsync, 5 years ago

Guest Control/VBoxManage: Make use of the newly IGuest::Shutdown() API when automatically rebooting the guest on Guest Additions update. bugref:9320

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