VirtualBox

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

Last change on this file since 71795 was 71319, checked in by vboxsync, 7 years ago

Guest Control/VBoxManage: Check the source element to copy from guest to know whether to use IGuestSession::fileCopyTo() or IGuestSession::directoryCopyTo().

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