VirtualBox

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

Last change on this file since 90739 was 89981, checked in by vboxsync, 3 years ago

VBoxManageGuestCtrl.cpp: G/c obsolete unused enum GETOPTDEF_EXEC.
bugref:10055.

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