VirtualBox

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

Last change on this file since 88079 was 85220, checked in by vboxsync, 4 years ago

VBoxManageGuestCtrl.cpp: Must not use RT_ZERO on pCtx in gctrCmdCtxInit as the struct contains 5 C++ objects. bugref:9790.

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