VirtualBox

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

Last change on this file since 57671 was 57593, checked in by vboxsync, 9 years ago

Fe/VBoxManage: Fixed broken "guestcontrol <VM-Name> list" command. Regression introduced by r99977.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 159.8 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 57593 2015-09-01 14:58:04Z vboxsync $ */
2/** @file
3 * VBoxManage - Implementation of guestcontrol command.
4 */
5
6/*
7 * Copyright (C) 2010-2015 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 const char* GetSource() const
222 {
223 return mSource.c_str();
224 }
225
226 const char* GetFilter() const
227 {
228 return mFilter.c_str();
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 entires, whereas the key is the destination
273 * directory and the mapped value is a vector holding all elements for this directoy.
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
301
302/*********************************************************************************************************************************
303* Internal Functions *
304*********************************************************************************************************************************/
305static int gctlCopyDirExists(PCOPYCONTEXT pContext, bool bGuest, const char *pszDir, bool *fExists);
306
307#endif /* VBOX_ONLY_DOCS */
308
309
310
311void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2, uint32_t uSubCmd)
312{
313 const uint32_t fAnonSubCmds = USAGE_GSTCTRL_CLOSESESSION
314 | USAGE_GSTCTRL_LIST
315 | USAGE_GSTCTRL_CLOSEPROCESS
316 | USAGE_GSTCTRL_CLOSESESSION
317 | USAGE_GSTCTRL_UPDATEGA
318 | USAGE_GSTCTRL_WATCH;
319
320 /* 0 1 2 3 4 5 6 7 8XXXXXXXXXX */
321 /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
322 if (~fAnonSubCmds & uSubCmd)
323 RTStrmPrintf(pStrm,
324 "%s guestcontrol %s <uuid|vmname> [--verbose|-v] [--quiet|-q]\n"
325 " [--username <name>] [--domain <domain>]\n"
326 " [--passwordfile <file> | --password <password>]\n%s",
327 pcszSep1, pcszSep2, uSubCmd == ~0U ? "\n" : "");
328 if (uSubCmd & USAGE_GSTCTRL_RUN)
329 RTStrmPrintf(pStrm,
330 " run [common-options]\n"
331 " [--exe <path to executable>] [--timeout <msec>]\n"
332 " [-E|--putenv <NAME>[=<VALUE>]] [--unquoted-args]\n"
333 " [--ignore-operhaned-processes] [--no-profile]\n"
334 " [--no-wait-stdout|--wait-stdout]\n"
335 " [--no-wait-stderr|--wait-stderr]\n"
336 " [--dos2unix] [--unix2dos]\n"
337 " -- <program/arg0> [argument1] ... [argumentN]]\n"
338 "\n");
339 if (uSubCmd & USAGE_GSTCTRL_START)
340 RTStrmPrintf(pStrm,
341 " start [common-options]\n"
342 " [--exe <path to executable>] [--timeout <msec>]\n"
343 " [-E|--putenv <NAME>[=<VALUE>]] [--unquoted-args]\n"
344 " [--ignore-operhaned-processes] [--no-profile]\n"
345 " -- <program/arg0> [argument1] ... [argumentN]]\n"
346 "\n");
347 if (uSubCmd & USAGE_GSTCTRL_COPYFROM)
348 RTStrmPrintf(pStrm,
349 " copyfrom [common-options]\n"
350 " [--dryrun] [--follow] [-R|--recursive]\n"
351 " <guest-src0> [guest-src1 [...]] <host-dst>\n"
352 "\n"
353 " copyfrom [common-options]\n"
354 " [--dryrun] [--follow] [-R|--recursive]\n"
355 " [--target-directory <host-dst-dir>]\n"
356 " <guest-src0> [guest-src1 [...]]\n"
357 "\n");
358 if (uSubCmd & USAGE_GSTCTRL_COPYTO)
359 RTStrmPrintf(pStrm,
360 " copyto [common-options]\n"
361 " [--dryrun] [--follow] [-R|--recursive]\n"
362 " <host-src0> [host-src1 [...]] <guest-dst>\n"
363 "\n"
364 " copyto [common-options]\n"
365 " [--dryrun] [--follow] [-R|--recursive]\n"
366 " [--target-directory <guest-dst>]\n"
367 " <host-src0> [host-src1 [...]]\n"
368 "\n");
369 if (uSubCmd & USAGE_GSTCTRL_MKDIR)
370 RTStrmPrintf(pStrm,
371 " mkdir|createdir[ectory] [common-options]\n"
372 " [--parents] [--mode <mode>]\n"
373 " <guest directory> [...]\n"
374 "\n");
375 if (uSubCmd & USAGE_GSTCTRL_RMDIR)
376 RTStrmPrintf(pStrm,
377 " rmdir|removedir[ectory] [common-options]\n"
378 " [-R|--recursive]\n"
379 " <guest directory> [...]\n"
380 "\n");
381 if (uSubCmd & USAGE_GSTCTRL_RM)
382 RTStrmPrintf(pStrm,
383 " removefile|rm [common-options] [-f|--force]\n"
384 " <guest file> [...]\n"
385 "\n");
386 if (uSubCmd & USAGE_GSTCTRL_MV)
387 RTStrmPrintf(pStrm,
388 " mv|move|ren[ame] [common-options]\n"
389 " <source> [source1 [...]] <dest>\n"
390 "\n");
391 if (uSubCmd & USAGE_GSTCTRL_MKTEMP)
392 RTStrmPrintf(pStrm,
393 " mktemp|createtemp[orary] [common-options]\n"
394 " [--secure] [--mode <mode>] [--tmpdir <directory>]\n"
395 " <template>\n"
396 "\n");
397 if (uSubCmd & USAGE_GSTCTRL_STAT)
398 RTStrmPrintf(pStrm,
399 " stat [common-options]\n"
400 " <file> [...]\n"
401 "\n");
402
403 /*
404 * Command not requiring authentication.
405 */
406 if (fAnonSubCmds & uSubCmd)
407 {
408 /* 0 1 2 3 4 5 6 7 8XXXXXXXXXX */
409 /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
410 RTStrmPrintf(pStrm,
411 "%s guestcontrol %s <uuid|vmname> [--verbose|-v] [--quiet|-q]\n%s",
412 pcszSep1, pcszSep2, uSubCmd == ~0U ? "\n" : "");
413 if (uSubCmd & USAGE_GSTCTRL_LIST)
414 RTStrmPrintf(pStrm,
415 " list <all|sessions|processes|files> [common-opts]\n"
416 "\n");
417 if (uSubCmd & USAGE_GSTCTRL_CLOSEPROCESS)
418 RTStrmPrintf(pStrm,
419 " closeprocess [common-options]\n"
420 " < --session-id <ID>\n"
421 " | --session-name <name or pattern>\n"
422 " <PID1> [PID1 [...]]\n"
423 "\n");
424 if (uSubCmd & USAGE_GSTCTRL_CLOSESESSION)
425 RTStrmPrintf(pStrm,
426 " closesession [common-options]\n"
427 " < --all | --session-id <ID>\n"
428 " | --session-name <name or pattern> >\n"
429 "\n");
430 if (uSubCmd & USAGE_GSTCTRL_UPDATEGA)
431 RTStrmPrintf(pStrm,
432 " updatega|updateguestadditions|updateadditions\n"
433 " [--source <guest additions .ISO>]\n"
434 " [--wait-start] [common-options]\n"
435 " [-- [<argument1>] ... [<argumentN>]]\n"
436 "\n");
437 if (uSubCmd & USAGE_GSTCTRL_WATCH)
438 RTStrmPrintf(pStrm,
439 " watch [common-options]\n"
440 "\n");
441 }
442}
443
444#ifndef VBOX_ONLY_DOCS
445
446
447#ifdef RT_OS_WINDOWS
448static BOOL WINAPI gctlSignalHandler(DWORD dwCtrlType)
449{
450 bool fEventHandled = FALSE;
451 switch (dwCtrlType)
452 {
453 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
454 * via GenerateConsoleCtrlEvent(). */
455 case CTRL_BREAK_EVENT:
456 case CTRL_CLOSE_EVENT:
457 case CTRL_C_EVENT:
458 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
459 fEventHandled = TRUE;
460 break;
461 default:
462 break;
463 /** @todo Add other events here. */
464 }
465
466 return fEventHandled;
467}
468#else /* !RT_OS_WINDOWS */
469/**
470 * Signal handler that sets g_fGuestCtrlCanceled.
471 *
472 * This can be executed on any thread in the process, on Windows it may even be
473 * a thread dedicated to delivering this signal. Don't do anything
474 * unnecessary here.
475 */
476static void gctlSignalHandler(int iSignal)
477{
478 NOREF(iSignal);
479 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
480}
481#endif
482
483
484/**
485 * Installs a custom signal handler to get notified
486 * whenever the user wants to intercept the program.
487 *
488 * @todo Make this handler available for all VBoxManage modules?
489 */
490static int gctlSignalHandlerInstall(void)
491{
492 g_fGuestCtrlCanceled = false;
493
494 int rc = VINF_SUCCESS;
495#ifdef RT_OS_WINDOWS
496 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)gctlSignalHandler, TRUE /* Add handler */))
497 {
498 rc = RTErrConvertFromWin32(GetLastError());
499 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
500 }
501#else
502 signal(SIGINT, gctlSignalHandler);
503# ifdef SIGBREAK
504 signal(SIGBREAK, gctlSignalHandler);
505# endif
506#endif
507 return rc;
508}
509
510
511/**
512 * Uninstalls a previously installed signal handler.
513 */
514static int gctlSignalHandlerUninstall(void)
515{
516 int rc = VINF_SUCCESS;
517#ifdef RT_OS_WINDOWS
518 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
519 {
520 rc = RTErrConvertFromWin32(GetLastError());
521 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
522 }
523#else
524 signal(SIGINT, SIG_DFL);
525# ifdef SIGBREAK
526 signal(SIGBREAK, SIG_DFL);
527# endif
528#endif
529 return rc;
530}
531
532
533/**
534 * Translates a process status to a human readable string.
535 */
536const char *gctlProcessStatusToText(ProcessStatus_T enmStatus)
537{
538 switch (enmStatus)
539 {
540 case ProcessStatus_Starting:
541 return "starting";
542 case ProcessStatus_Started:
543 return "started";
544 case ProcessStatus_Paused:
545 return "paused";
546 case ProcessStatus_Terminating:
547 return "terminating";
548 case ProcessStatus_TerminatedNormally:
549 return "successfully terminated";
550 case ProcessStatus_TerminatedSignal:
551 return "terminated by signal";
552 case ProcessStatus_TerminatedAbnormally:
553 return "abnormally aborted";
554 case ProcessStatus_TimedOutKilled:
555 return "timed out";
556 case ProcessStatus_TimedOutAbnormally:
557 return "timed out, hanging";
558 case ProcessStatus_Down:
559 return "killed";
560 case ProcessStatus_Error:
561 return "error";
562 default:
563 break;
564 }
565 return "unknown";
566}
567
568/**
569 * Translates a guest process wait result to a human readable string.
570 */
571const char *gctlProcessWaitResultToText(ProcessWaitResult_T enmWaitResult)
572{
573 switch (enmWaitResult)
574 {
575 case ProcessWaitResult_Start:
576 return "started";
577 case ProcessWaitResult_Terminate:
578 return "terminated";
579 case ProcessWaitResult_Status:
580 return "status changed";
581 case ProcessWaitResult_Error:
582 return "error";
583 case ProcessWaitResult_Timeout:
584 return "timed out";
585 case ProcessWaitResult_StdIn:
586 return "stdin ready";
587 case ProcessWaitResult_StdOut:
588 return "data on stdout";
589 case ProcessWaitResult_StdErr:
590 return "data on stderr";
591 case ProcessWaitResult_WaitFlagNotSupported:
592 return "waiting flag not supported";
593 default:
594 break;
595 }
596 return "unknown";
597}
598
599/**
600 * Translates a guest session status to a human readable string.
601 */
602const char *gctlGuestSessionStatusToText(GuestSessionStatus_T enmStatus)
603{
604 switch (enmStatus)
605 {
606 case GuestSessionStatus_Starting:
607 return "starting";
608 case GuestSessionStatus_Started:
609 return "started";
610 case GuestSessionStatus_Terminating:
611 return "terminating";
612 case GuestSessionStatus_Terminated:
613 return "terminated";
614 case GuestSessionStatus_TimedOutKilled:
615 return "timed out";
616 case GuestSessionStatus_TimedOutAbnormally:
617 return "timed out, hanging";
618 case GuestSessionStatus_Down:
619 return "killed";
620 case GuestSessionStatus_Error:
621 return "error";
622 default:
623 break;
624 }
625 return "unknown";
626}
627
628/**
629 * Translates a guest file status to a human readable string.
630 */
631const char *gctlFileStatusToText(FileStatus_T enmStatus)
632{
633 switch (enmStatus)
634 {
635 case FileStatus_Opening:
636 return "opening";
637 case FileStatus_Open:
638 return "open";
639 case FileStatus_Closing:
640 return "closing";
641 case FileStatus_Closed:
642 return "closed";
643 case FileStatus_Down:
644 return "killed";
645 case FileStatus_Error:
646 return "error";
647 default:
648 break;
649 }
650 return "unknown";
651}
652
653static int gctlPrintError(com::ErrorInfo &errorInfo)
654{
655 if ( errorInfo.isFullAvailable()
656 || errorInfo.isBasicAvailable())
657 {
658 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
659 * because it contains more accurate info about what went wrong. */
660 if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
661 RTMsgError("%ls.", errorInfo.getText().raw());
662 else
663 {
664 RTMsgError("Error details:");
665 GluePrintErrorInfo(errorInfo);
666 }
667 return VERR_GENERAL_FAILURE; /** @todo */
668 }
669 AssertMsgFailedReturn(("Object has indicated no error (%Rhrc)!?\n", errorInfo.getResultCode()),
670 VERR_INVALID_PARAMETER);
671}
672
673static int gctlPrintError(IUnknown *pObj, const GUID &aIID)
674{
675 com::ErrorInfo ErrInfo(pObj, aIID);
676 return gctlPrintError(ErrInfo);
677}
678
679static int gctlPrintProgressError(ComPtr<IProgress> pProgress)
680{
681 int vrc = VINF_SUCCESS;
682 HRESULT rc;
683
684 do
685 {
686 BOOL fCanceled;
687 CHECK_ERROR_BREAK(pProgress, COMGETTER(Canceled)(&fCanceled));
688 if (!fCanceled)
689 {
690 LONG rcProc;
691 CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&rcProc));
692 if (FAILED(rcProc))
693 {
694 com::ProgressErrorInfo ErrInfo(pProgress);
695 vrc = gctlPrintError(ErrInfo);
696 }
697 }
698
699 } while(0);
700
701 AssertMsgStmt(SUCCEEDED(rc), ("Could not lookup progress information\n"), vrc = VERR_COM_UNEXPECTED);
702
703 return vrc;
704}
705
706
707
708/*
709 *
710 *
711 * Guest Control Command Context
712 * Guest Control Command Context
713 * Guest Control Command Context
714 * Guest Control Command Context
715 *
716 *
717 *
718 */
719
720
721/**
722 * Initializes a guest control command context structure.
723 *
724 * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE on failure (after
725 * informing the user of course).
726 * @param pCtx The command context to init.
727 * @param pArg The handle argument package.
728 */
729static RTEXITCODE gctrCmdCtxInit(PGCTLCMDCTX pCtx, HandlerArg *pArg)
730{
731 RT_ZERO(*pCtx);
732 pCtx->pArg = pArg;
733
734 /*
735 * The user name defaults to the host one, if we can get at it.
736 */
737 char szUser[1024];
738 int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL);
739 if ( RT_SUCCESS(rc)
740 && RTStrIsValidEncoding(szUser)) /* paranoia required on posix */
741 {
742 try
743 {
744 pCtx->strUsername = szUser;
745 }
746 catch (std::bad_alloc &)
747 {
748 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory");
749 }
750 }
751 /* else: ignore this failure. */
752
753 return RTEXITCODE_SUCCESS;
754}
755
756
757/**
758 * Worker for GCTLCMD_COMMON_OPTION_CASES.
759 *
760 * @returns RTEXITCODE_SUCCESS if the option was handled successfully. If not,
761 * an error message is printed and an appropriate failure exit code is
762 * returned.
763 * @param pCtx The guest control command context.
764 * @param ch The option char or ordinal.
765 * @param pValueUnion The option value union.
766 */
767static RTEXITCODE gctlCtxSetOption(PGCTLCMDCTX pCtx, int ch, PRTGETOPTUNION pValueUnion)
768{
769 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
770 switch (ch)
771 {
772 case GCTLCMD_COMMON_OPT_USER: /* User name */
773 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
774 pCtx->strUsername = pValueUnion->psz;
775 else
776 RTMsgWarning("The --username|-u option is ignored by '%s'", pCtx->pCmdDef->pszName);
777 break;
778
779 case GCTLCMD_COMMON_OPT_PASSWORD: /* Password */
780 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
781 {
782 if (pCtx->strPassword.isNotEmpty())
783 RTMsgWarning("Password is given more than once.");
784 pCtx->strPassword = pValueUnion->psz;
785 }
786 else
787 RTMsgWarning("The --password option is ignored by '%s'", pCtx->pCmdDef->pszName);
788 break;
789
790 case GCTLCMD_COMMON_OPT_PASSWORD_FILE: /* Password file */
791 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
792 rcExit = readPasswordFile(pValueUnion->psz, &pCtx->strPassword);
793 else
794 RTMsgWarning("The --password-file|-p option is ignored by '%s'", pCtx->pCmdDef->pszName);
795 break;
796
797 case GCTLCMD_COMMON_OPT_DOMAIN: /* domain */
798 if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
799 pCtx->strDomain = pValueUnion->psz;
800 else
801 RTMsgWarning("The --domain option is ignored by '%s'", pCtx->pCmdDef->pszName);
802 break;
803
804 case 'v': /* --verbose */
805 pCtx->cVerbose++;
806 break;
807
808 case 'q': /* --quiet */
809 if (pCtx->cVerbose)
810 pCtx->cVerbose--;
811 break;
812
813 default:
814 AssertFatalMsgFailed(("ch=%d (%c)\n", ch, ch));
815 }
816 return rcExit;
817}
818
819
820/**
821 * Initializes the VM for IGuest operation.
822 *
823 * This opens a shared session to a running VM and gets hold of IGuest.
824 *
825 * @returns RTEXITCODE_SUCCESS on success. RTEXITCODE_FAILURE and user message
826 * on failure.
827 * @param pCtx The guest control command context.
828 * GCTLCMDCTX::pGuest will be set on success.
829 */
830static RTEXITCODE gctlCtxInitVmSession(PGCTLCMDCTX pCtx)
831{
832 HRESULT rc;
833 AssertPtr(pCtx);
834 AssertPtr(pCtx->pArg);
835
836 /*
837 * Find the VM and check if it's running.
838 */
839 ComPtr<IMachine> machine;
840 CHECK_ERROR(pCtx->pArg->virtualBox, FindMachine(Bstr(pCtx->pszVmNameOrUuid).raw(), machine.asOutParam()));
841 if (SUCCEEDED(rc))
842 {
843 MachineState_T enmMachineState;
844 CHECK_ERROR(machine, COMGETTER(State)(&enmMachineState));
845 if ( SUCCEEDED(rc)
846 && enmMachineState == MachineState_Running)
847 {
848 /*
849 * It's running. So, open a session to it and get the IGuest interface.
850 */
851 CHECK_ERROR(machine, LockMachine(pCtx->pArg->session, LockType_Shared));
852 if (SUCCEEDED(rc))
853 {
854 pCtx->fLockedVmSession = true;
855 ComPtr<IConsole> ptrConsole;
856 CHECK_ERROR(pCtx->pArg->session, COMGETTER(Console)(ptrConsole.asOutParam()));
857 if (SUCCEEDED(rc))
858 {
859 if (ptrConsole.isNotNull())
860 {
861 CHECK_ERROR(ptrConsole, COMGETTER(Guest)(pCtx->pGuest.asOutParam()));
862 if (SUCCEEDED(rc))
863 return RTEXITCODE_SUCCESS;
864 }
865 else
866 RTMsgError("Failed to get a IConsole pointer for the machine. Is it still running?\n");
867 }
868 }
869 }
870 else if (SUCCEEDED(rc))
871 RTMsgError("Machine \"%s\" is not running (currently %s)!\n",
872 pCtx->pszVmNameOrUuid, machineStateToName(enmMachineState, false));
873 }
874 return RTEXITCODE_FAILURE;
875}
876
877
878/**
879 * Creates a guest session with the VM.
880 *
881 * @retval RTEXITCODE_SUCCESS on success.
882 * @retval RTEXITCODE_FAILURE and user message on failure.
883 * @param pCtx The guest control command context.
884 * GCTCMDCTX::pGuestSession and GCTLCMDCTX::uSessionID
885 * will be set.
886 */
887static RTEXITCODE gctlCtxInitGuestSession(PGCTLCMDCTX pCtx)
888{
889 HRESULT rc;
890 AssertPtr(pCtx);
891 Assert(!(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS));
892 Assert(pCtx->pGuest.isNotNull());
893
894 /*
895 * Build up a reasonable guest session name. Useful for identifying
896 * a specific session when listing / searching for them.
897 */
898 char *pszSessionName;
899 if (RTStrAPrintf(&pszSessionName,
900 "[%RU32] VBoxManage Guest Control [%s] - %s",
901 RTProcSelf(), pCtx->pszVmNameOrUuid, pCtx->pCmdDef->pszName) < 0)
902 return RTMsgErrorExit(RTEXITCODE_FAILURE, "No enough memory for session name");
903
904 /*
905 * Create a guest session.
906 */
907 if (pCtx->cVerbose > 1)
908 RTPrintf("Creating guest session as user '%s'...\n", pCtx->strUsername.c_str());
909 try
910 {
911 CHECK_ERROR(pCtx->pGuest, CreateSession(Bstr(pCtx->strUsername).raw(),
912 Bstr(pCtx->strPassword).raw(),
913 Bstr(pCtx->strDomain).raw(),
914 Bstr(pszSessionName).raw(),
915 pCtx->pGuestSession.asOutParam()));
916 }
917 catch (std::bad_alloc &)
918 {
919 RTMsgError("Out of memory setting up IGuest::CreateSession call");
920 rc = E_OUTOFMEMORY;
921 }
922 if (SUCCEEDED(rc))
923 {
924 /*
925 * Wait for guest session to start.
926 */
927 if (pCtx->cVerbose > 1)
928 RTPrintf("Waiting for guest session to start...\n");
929 GuestSessionWaitResult_T enmWaitResult;
930 try
931 {
932 com::SafeArray<GuestSessionWaitForFlag_T> aSessionWaitFlags;
933 aSessionWaitFlags.push_back(GuestSessionWaitForFlag_Start);
934 CHECK_ERROR(pCtx->pGuestSession, WaitForArray(ComSafeArrayAsInParam(aSessionWaitFlags),
935 /** @todo Make session handling timeouts configurable. */
936 30 * 1000, &enmWaitResult));
937 }
938 catch (std::bad_alloc &)
939 {
940 RTMsgError("Out of memory setting up IGuestSession::WaitForArray call");
941 rc = E_OUTOFMEMORY;
942 }
943 if (SUCCEEDED(rc))
944 {
945 /* The WaitFlagNotSupported result may happen with GAs older than 4.3. */
946 if ( enmWaitResult == GuestSessionWaitResult_Start
947 || enmWaitResult == GuestSessionWaitResult_WaitFlagNotSupported)
948 {
949 /*
950 * Get the session ID and we're ready to rumble.
951 */
952 CHECK_ERROR(pCtx->pGuestSession, COMGETTER(Id)(&pCtx->uSessionID));
953 if (SUCCEEDED(rc))
954 {
955 if (pCtx->cVerbose > 1)
956 RTPrintf("Successfully started guest session (ID %RU32)\n", pCtx->uSessionID);
957 RTStrFree(pszSessionName);
958 return RTEXITCODE_SUCCESS;
959 }
960 }
961 else
962 {
963 GuestSessionStatus_T enmSessionStatus;
964 CHECK_ERROR(pCtx->pGuestSession, COMGETTER(Status)(&enmSessionStatus));
965 RTMsgError("Error starting guest session (current status is: %s)\n",
966 SUCCEEDED(rc) ? gctlGuestSessionStatusToText(enmSessionStatus) : "<unknown>");
967 }
968 }
969 }
970
971 RTStrFree(pszSessionName);
972 return RTEXITCODE_FAILURE;
973}
974
975
976/**
977 * Completes the guest control context initialization after parsing arguments.
978 *
979 * Will validate common arguments, open a VM session, and if requested open a
980 * guest session and install the CTRL-C signal handler.
981 *
982 * It is good to validate all the options and arguments you can before making
983 * this call. However, the VM session, IGuest and IGuestSession interfaces are
984 * not availabe till after this call, so take care.
985 *
986 * @retval RTEXITCODE_SUCCESS on success.
987 * @retval RTEXITCODE_FAILURE and user message on failure.
988 * @param pCtx The guest control command context.
989 * GCTCMDCTX::pGuestSession and GCTLCMDCTX::uSessionID
990 * will be set.
991 * @remarks Can safely be called multiple times, will only do work once.
992 */
993static RTEXITCODE gctlCtxPostOptionParsingInit(PGCTLCMDCTX pCtx)
994{
995 if (pCtx->fPostOptionParsingInited)
996 return RTEXITCODE_SUCCESS;
997
998 /*
999 * Check that the user name isn't empty when we need it.
1000 */
1001 RTEXITCODE rcExit;
1002 if ( (pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)
1003 || pCtx->strUsername.isNotEmpty())
1004 {
1005 /*
1006 * Open the VM session and if required, a guest session.
1007 */
1008 rcExit = gctlCtxInitVmSession(pCtx);
1009 if ( rcExit == RTEXITCODE_SUCCESS
1010 && !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS))
1011 rcExit = gctlCtxInitGuestSession(pCtx);
1012 if (rcExit == RTEXITCODE_SUCCESS)
1013 {
1014 /*
1015 * Install signal handler if requested (errors are ignored).
1016 */
1017 if (!(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_NO_SIGNAL_HANDLER))
1018 {
1019 int rc = gctlSignalHandlerInstall();
1020 pCtx->fInstalledSignalHandler = RT_SUCCESS(rc);
1021 }
1022 }
1023 }
1024 else
1025 rcExit = errorSyntaxEx(USAGE_GUESTCONTROL, pCtx->pCmdDef->fCmdUsage, "No user name specified!");
1026
1027 pCtx->fPostOptionParsingInited = rcExit == RTEXITCODE_SUCCESS;
1028 return rcExit;
1029}
1030
1031
1032/**
1033 * Cleans up the context when the command returns.
1034 *
1035 * This will close any open guest session, unless the DETACH flag is set.
1036 * It will also close any VM session that may be been established. Any signal
1037 * handlers we've installed will also be removed.
1038 *
1039 * Un-initializes the VM after guest control usage.
1040 * @param pCmdCtx Pointer to command context.
1041 */
1042static void gctlCtxTerm(PGCTLCMDCTX pCtx)
1043{
1044 HRESULT rc;
1045 AssertPtr(pCtx);
1046
1047 /*
1048 * Uninstall signal handler.
1049 */
1050 if (pCtx->fInstalledSignalHandler)
1051 {
1052 gctlSignalHandlerUninstall();
1053 pCtx->fInstalledSignalHandler = false;
1054 }
1055
1056 /*
1057 * Close, or at least release, the guest session.
1058 */
1059 if (pCtx->pGuestSession.isNotNull())
1060 {
1061 if ( !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)
1062 && !pCtx->fDetachGuestSession)
1063 {
1064 if (pCtx->cVerbose > 1)
1065 RTPrintf("Closing guest session ...\n");
1066
1067 CHECK_ERROR(pCtx->pGuestSession, Close());
1068 }
1069 else if ( pCtx->fDetachGuestSession
1070 && pCtx->cVerbose > 1)
1071 RTPrintf("Guest session detached\n");
1072
1073 pCtx->pGuestSession.setNull();
1074 }
1075
1076 /*
1077 * Close the VM session.
1078 */
1079 if (pCtx->fLockedVmSession)
1080 {
1081 Assert(pCtx->pArg->session.isNotNull());
1082 CHECK_ERROR(pCtx->pArg->session, UnlockMachine());
1083 pCtx->fLockedVmSession = false;
1084 }
1085}
1086
1087
1088
1089
1090
1091/*
1092 *
1093 *
1094 * Guest Control Command Handling.
1095 * Guest Control Command Handling.
1096 * Guest Control Command Handling.
1097 * Guest Control Command Handling.
1098 * Guest Control Command Handling.
1099 *
1100 *
1101 */
1102
1103
1104/** @name EXITCODEEXEC_XXX - Special run exit codes.
1105 *
1106 * Special exit codes for returning errors/information of a started guest
1107 * process to the command line VBoxManage was started from. Useful for e.g.
1108 * scripting.
1109 *
1110 * ASSUMING that all platforms have at least 7-bits for the exit code we can do
1111 * the following mapping:
1112 * - Guest exit code 0 is mapped to 0 on the host.
1113 * - Guest exit codes 1 thru 93 (0x5d) are displaced by 32, so that 1
1114 * becomes 33 (0x21) on the host and 93 becomes 125 (0x7d) on the host.
1115 * - Guest exit codes 94 (0x5e) and above are mapped to 126 (0x5e).
1116 *
1117 * We ASSUME that all VBoxManage status codes are in the range 0 thru 32.
1118 *
1119 * @note These are frozen as of 4.1.0.
1120 * @note The guest exit code mappings was introduced with 5.0 and the 'run'
1121 * command, they are/was not supported by 'exec'.
1122 * @sa gctlRunCalculateExitCode
1123 */
1124/** Process exited normally but with an exit code <> 0. */
1125#define EXITCODEEXEC_CODE ((RTEXITCODE)16)
1126#define EXITCODEEXEC_FAILED ((RTEXITCODE)17)
1127#define EXITCODEEXEC_TERM_SIGNAL ((RTEXITCODE)18)
1128#define EXITCODEEXEC_TERM_ABEND ((RTEXITCODE)19)
1129#define EXITCODEEXEC_TIMEOUT ((RTEXITCODE)20)
1130#define EXITCODEEXEC_DOWN ((RTEXITCODE)21)
1131/** Execution was interrupt by user (ctrl-c). */
1132#define EXITCODEEXEC_CANCELED ((RTEXITCODE)22)
1133/** The first mapped guest (non-zero) exit code. */
1134#define EXITCODEEXEC_MAPPED_FIRST 33
1135/** The last mapped guest (non-zero) exit code value (inclusive). */
1136#define EXITCODEEXEC_MAPPED_LAST 125
1137/** The number of exit codes from EXITCODEEXEC_MAPPED_FIRST to
1138 * EXITCODEEXEC_MAPPED_LAST. This is also the highest guest exit code number
1139 * we're able to map. */
1140#define EXITCODEEXEC_MAPPED_RANGE (93)
1141/** The guest exit code displacement value. */
1142#define EXITCODEEXEC_MAPPED_DISPLACEMENT 32
1143/** The guest exit code was too big to be mapped. */
1144#define EXITCODEEXEC_MAPPED_BIG ((RTEXITCODE)126)
1145/** @} */
1146
1147/**
1148 * Calculates the exit code of VBoxManage.
1149 *
1150 * @returns The exit code to return.
1151 * @param enmStatus The guest process status.
1152 * @param uExitCode The associated guest process exit code (where
1153 * applicable).
1154 * @param fReturnExitCodes Set if we're to use the 32-126 range for guest
1155 * exit codes.
1156 */
1157static RTEXITCODE gctlRunCalculateExitCode(ProcessStatus_T enmStatus, ULONG uExitCode, bool fReturnExitCodes)
1158{
1159 int vrc = RTEXITCODE_SUCCESS;
1160 switch (enmStatus)
1161 {
1162 case ProcessStatus_TerminatedNormally:
1163 if (uExitCode == 0)
1164 return RTEXITCODE_SUCCESS;
1165 if (!fReturnExitCodes)
1166 return EXITCODEEXEC_CODE;
1167 if (uExitCode <= EXITCODEEXEC_MAPPED_RANGE)
1168 return (RTEXITCODE) (uExitCode + EXITCODEEXEC_MAPPED_DISPLACEMENT);
1169 return EXITCODEEXEC_MAPPED_BIG;
1170
1171 case ProcessStatus_TerminatedAbnormally:
1172 return EXITCODEEXEC_TERM_ABEND;
1173 case ProcessStatus_TerminatedSignal:
1174 return EXITCODEEXEC_TERM_SIGNAL;
1175
1176#if 0 /* see caller! */
1177 case ProcessStatus_TimedOutKilled:
1178 return EXITCODEEXEC_TIMEOUT;
1179 case ProcessStatus_Down:
1180 return EXITCODEEXEC_DOWN; /* Service/OS is stopping, process was killed. */
1181 case ProcessStatus_Error:
1182 return EXITCODEEXEC_FAILED;
1183
1184 /* The following is probably for detached? */
1185 case ProcessStatus_Starting:
1186 return RTEXITCODE_SUCCESS;
1187 case ProcessStatus_Started:
1188 return RTEXITCODE_SUCCESS;
1189 case ProcessStatus_Paused:
1190 return RTEXITCODE_SUCCESS;
1191 case ProcessStatus_Terminating:
1192 return RTEXITCODE_SUCCESS; /** @todo ???? */
1193#endif
1194
1195 default:
1196 AssertMsgFailed(("Unknown exit status (%u/%u) from guest process returned!\n", enmStatus, uExitCode));
1197 return RTEXITCODE_FAILURE;
1198 }
1199}
1200
1201
1202/**
1203 * Pumps guest output to the host.
1204 *
1205 * @return IPRT status code.
1206 * @param pProcess Pointer to appropriate process object.
1207 * @param hVfsIosDst Where to write the data.
1208 * @param uHandle Handle where to read the data from.
1209 * @param cMsTimeout Timeout (in ms) to wait for the operation to
1210 * complete.
1211 */
1212static int gctlRunPumpOutput(IProcess *pProcess, RTVFSIOSTREAM hVfsIosDst, ULONG uHandle, RTMSINTERVAL cMsTimeout)
1213{
1214 AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
1215 Assert(hVfsIosDst != NIL_RTVFSIOSTREAM);
1216
1217 int vrc;
1218
1219 SafeArray<BYTE> aOutputData;
1220 HRESULT hrc = pProcess->Read(uHandle, _64K, RT_MAX(cMsTimeout, 1), ComSafeArrayAsOutParam(aOutputData));
1221 if (SUCCEEDED(hrc))
1222 {
1223 size_t cbOutputData = aOutputData.size();
1224 if (cbOutputData == 0)
1225 vrc = VINF_SUCCESS;
1226 else
1227 {
1228 BYTE const *pbBuf = aOutputData.raw();
1229 AssertPtr(pbBuf);
1230
1231 vrc = RTVfsIoStrmWrite(hVfsIosDst, pbBuf, cbOutputData, true /*fBlocking*/, NULL);
1232 if (RT_FAILURE(vrc))
1233 RTMsgError("Unable to write output, rc=%Rrc\n", vrc);
1234 }
1235 }
1236 else
1237 vrc = gctlPrintError(pProcess, COM_IIDOF(IProcess));
1238 return vrc;
1239}
1240
1241
1242/**
1243 * Configures a host handle for pumping guest bits.
1244 *
1245 * @returns true if enabled and we successfully configured it.
1246 * @param fEnabled Whether pumping this pipe is configured.
1247 * @param enmHandle The IPRT standard handle designation.
1248 * @param pszName The name for user messages.
1249 * @param enmTransformation The transformation to apply.
1250 * @param phVfsIos Where to return the resulting I/O stream handle.
1251 */
1252static bool gctlRunSetupHandle(bool fEnabled, RTHANDLESTD enmHandle, const char *pszName,
1253 kStreamTransform enmTransformation, PRTVFSIOSTREAM phVfsIos)
1254{
1255 if (fEnabled)
1256 {
1257 int vrc = RTVfsIoStrmFromStdHandle(enmHandle, 0, true /*fLeaveOpen*/, phVfsIos);
1258 if (RT_SUCCESS(vrc))
1259 {
1260 if (enmTransformation != kStreamTransform_None)
1261 {
1262 RTMsgWarning("Unsupported %s line ending conversion", pszName);
1263 /** @todo Implement dos2unix and unix2dos stream filters. */
1264 }
1265 return true;
1266 }
1267 RTMsgWarning("Error getting %s handle: %Rrc", pszName, vrc);
1268 }
1269 return false;
1270}
1271
1272
1273/**
1274 * Returns the remaining time (in ms) based on the start time and a set
1275 * timeout value. Returns RT_INDEFINITE_WAIT if no timeout was specified.
1276 *
1277 * @return RTMSINTERVAL Time left (in ms).
1278 * @param u64StartMs Start time (in ms).
1279 * @param cMsTimeout Timeout value (in ms).
1280 */
1281static RTMSINTERVAL gctlRunGetRemainingTime(uint64_t u64StartMs, RTMSINTERVAL cMsTimeout)
1282{
1283 if (!cMsTimeout || cMsTimeout == RT_INDEFINITE_WAIT) /* If no timeout specified, wait forever. */
1284 return RT_INDEFINITE_WAIT;
1285
1286 uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
1287 if (u64ElapsedMs >= cMsTimeout)
1288 return 0;
1289
1290 return cMsTimeout - (RTMSINTERVAL)u64ElapsedMs;
1291}
1292
1293/**
1294 * Common handler for the 'run' and 'start' commands.
1295 *
1296 * @returns Command exit code.
1297 * @param pCtx Guest session context.
1298 * @param argc The argument count.
1299 * @param argv The argument vector for this command.
1300 * @param fRunCmd Set if it's 'run' clear if 'start'.
1301 * @param fHelp The help flag for the command.
1302 */
1303static RTEXITCODE gctlHandleRunCommon(PGCTLCMDCTX pCtx, int argc, char **argv, bool fRunCmd, uint32_t fHelp)
1304{
1305 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
1306
1307 /*
1308 * Parse arguments.
1309 */
1310 enum kGstCtrlRunOpt
1311 {
1312 kGstCtrlRunOpt_IgnoreOrphanedProcesses = 1000,
1313 kGstCtrlRunOpt_NoProfile,
1314 kGstCtrlRunOpt_Dos2Unix,
1315 kGstCtrlRunOpt_Unix2Dos,
1316 kGstCtrlRunOpt_WaitForStdOut,
1317 kGstCtrlRunOpt_NoWaitForStdOut,
1318 kGstCtrlRunOpt_WaitForStdErr,
1319 kGstCtrlRunOpt_NoWaitForStdErr
1320 };
1321 static const RTGETOPTDEF s_aOptions[] =
1322 {
1323 GCTLCMD_COMMON_OPTION_DEFS()
1324 { "--putenv", 'E', RTGETOPT_REQ_STRING },
1325 { "--exe", 'e', RTGETOPT_REQ_STRING },
1326 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
1327 { "--unquoted-args", 'u', RTGETOPT_REQ_NOTHING },
1328 { "--ignore-operhaned-processes", kGstCtrlRunOpt_IgnoreOrphanedProcesses, RTGETOPT_REQ_NOTHING },
1329 { "--no-profile", kGstCtrlRunOpt_NoProfile, RTGETOPT_REQ_NOTHING },
1330 /* run only: 6 - options */
1331 { "--dos2unix", kGstCtrlRunOpt_Dos2Unix, RTGETOPT_REQ_NOTHING },
1332 { "--unix2dos", kGstCtrlRunOpt_Unix2Dos, RTGETOPT_REQ_NOTHING },
1333 { "--no-wait-stdout", kGstCtrlRunOpt_NoWaitForStdOut, RTGETOPT_REQ_NOTHING },
1334 { "--wait-stdout", kGstCtrlRunOpt_WaitForStdOut, RTGETOPT_REQ_NOTHING },
1335 { "--no-wait-stderr", kGstCtrlRunOpt_NoWaitForStdErr, RTGETOPT_REQ_NOTHING },
1336 { "--wait-stderr", kGstCtrlRunOpt_WaitForStdErr, RTGETOPT_REQ_NOTHING },
1337 };
1338
1339 /** @todo stdin handling. */
1340
1341 int ch;
1342 RTGETOPTUNION ValueUnion;
1343 RTGETOPTSTATE GetState;
1344 size_t cOptions =
1345 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions) - (fRunCmd ? 0 : 6),
1346 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1347
1348 com::SafeArray<ProcessCreateFlag_T> aCreateFlags;
1349 com::SafeArray<ProcessWaitForFlag_T> aWaitFlags;
1350 com::SafeArray<IN_BSTR> aArgs;
1351 com::SafeArray<IN_BSTR> aEnv;
1352 const char * pszImage = NULL;
1353 bool fWaitForStdOut = fRunCmd;
1354 bool fWaitForStdErr = fRunCmd;
1355 RTVFSIOSTREAM hVfsStdOut = NIL_RTVFSIOSTREAM;
1356 RTVFSIOSTREAM hVfsStdErr = NIL_RTVFSIOSTREAM;
1357 enum kStreamTransform enmStdOutTransform = kStreamTransform_None;
1358 enum kStreamTransform enmStdErrTransform = kStreamTransform_None;
1359 RTMSINTERVAL cMsTimeout = 0;
1360
1361 try
1362 {
1363 /* Wait for process start in any case. This is useful for scripting VBoxManage
1364 * when relying on its overall exit code. */
1365 aWaitFlags.push_back(ProcessWaitForFlag_Start);
1366
1367 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
1368 {
1369 /* For options that require an argument, ValueUnion has received the value. */
1370 switch (ch)
1371 {
1372 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
1373
1374 case 'E':
1375 if ( ValueUnion.psz[0] == '\0'
1376 || ValueUnion.psz[0] == '=')
1377 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RUN,
1378 "Invalid argument variable[=value]: '%s'", ValueUnion.psz);
1379 aEnv.push_back(Bstr(ValueUnion.psz).raw());
1380 break;
1381
1382 case kGstCtrlRunOpt_IgnoreOrphanedProcesses:
1383 aCreateFlags.push_back(ProcessCreateFlag_IgnoreOrphanedProcesses);
1384 break;
1385
1386 case kGstCtrlRunOpt_NoProfile:
1387 aCreateFlags.push_back(ProcessCreateFlag_NoProfile);
1388 break;
1389
1390 case 'e':
1391 pszImage = ValueUnion.psz;
1392 break;
1393
1394 case 'u':
1395 aCreateFlags.push_back(ProcessCreateFlag_UnquotedArguments);
1396 break;
1397
1398 /** @todo Add a hidden flag. */
1399
1400 case 't': /* Timeout */
1401 cMsTimeout = ValueUnion.u32;
1402 break;
1403
1404 /* run only options: */
1405 case kGstCtrlRunOpt_Dos2Unix:
1406 Assert(fRunCmd);
1407 enmStdErrTransform = enmStdOutTransform = kStreamTransform_Dos2Unix;
1408 break;
1409 case kGstCtrlRunOpt_Unix2Dos:
1410 Assert(fRunCmd);
1411 enmStdErrTransform = enmStdOutTransform = kStreamTransform_Unix2Dos;
1412 break;
1413
1414 case kGstCtrlRunOpt_WaitForStdOut:
1415 Assert(fRunCmd);
1416 fWaitForStdOut = true;
1417 break;
1418 case kGstCtrlRunOpt_NoWaitForStdOut:
1419 Assert(fRunCmd);
1420 fWaitForStdOut = false;
1421 break;
1422
1423 case kGstCtrlRunOpt_WaitForStdErr:
1424 Assert(fRunCmd);
1425 fWaitForStdErr = true;
1426 break;
1427 case kGstCtrlRunOpt_NoWaitForStdErr:
1428 Assert(fRunCmd);
1429 fWaitForStdErr = false;
1430 break;
1431
1432 case VINF_GETOPT_NOT_OPTION:
1433 aArgs.push_back(Bstr(ValueUnion.psz).raw());
1434 if (!pszImage)
1435 {
1436 Assert(aArgs.size() == 1);
1437 pszImage = ValueUnion.psz;
1438 }
1439 break;
1440
1441 default:
1442 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RUN, ch, &ValueUnion);
1443
1444 } /* switch */
1445 } /* while RTGetOpt */
1446
1447 /* Must have something to execute. */
1448 if (!pszImage || !*pszImage)
1449 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RUN, "No executable specified!");
1450
1451 /*
1452 * Finalize process creation and wait flags and input/output streams.
1453 */
1454 if (!fRunCmd)
1455 {
1456 aCreateFlags.push_back(ProcessCreateFlag_WaitForProcessStartOnly);
1457 Assert(!fWaitForStdOut);
1458 Assert(!fWaitForStdErr);
1459 }
1460 else
1461 {
1462 aWaitFlags.push_back(ProcessWaitForFlag_Terminate);
1463 fWaitForStdOut = gctlRunSetupHandle(fWaitForStdOut, RTHANDLESTD_OUTPUT, "stdout", enmStdOutTransform, &hVfsStdOut);
1464 if (fWaitForStdOut)
1465 {
1466 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut);
1467 aWaitFlags.push_back(ProcessWaitForFlag_StdOut);
1468 }
1469 fWaitForStdErr = gctlRunSetupHandle(fWaitForStdErr, RTHANDLESTD_ERROR, "stderr", enmStdErrTransform, &hVfsStdErr);
1470 if (fWaitForStdErr)
1471 {
1472 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr);
1473 aWaitFlags.push_back(ProcessWaitForFlag_StdErr);
1474 }
1475 }
1476 }
1477 catch (std::bad_alloc &)
1478 {
1479 return RTMsgErrorExit(RTEXITCODE_FAILURE, "VERR_NO_MEMORY\n");
1480 }
1481
1482 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
1483 if (rcExit != RTEXITCODE_SUCCESS)
1484 return rcExit;
1485
1486 HRESULT rc;
1487
1488 try
1489 {
1490 do
1491 {
1492 /* Get current time stamp to later calculate rest of timeout left. */
1493 uint64_t msStart = RTTimeMilliTS();
1494
1495 /*
1496 * Create the process.
1497 */
1498 if (pCtx->cVerbose > 1)
1499 {
1500 if (cMsTimeout == 0)
1501 RTPrintf("Starting guest process ...\n");
1502 else
1503 RTPrintf("Starting guest process (within %ums)\n", cMsTimeout);
1504 }
1505 ComPtr<IGuestProcess> pProcess;
1506 CHECK_ERROR_BREAK(pCtx->pGuestSession, ProcessCreate(Bstr(pszImage).raw(),
1507 ComSafeArrayAsInParam(aArgs),
1508 ComSafeArrayAsInParam(aEnv),
1509 ComSafeArrayAsInParam(aCreateFlags),
1510 gctlRunGetRemainingTime(msStart, cMsTimeout),
1511 pProcess.asOutParam()));
1512
1513 /*
1514 * Explicitly wait for the guest process to be in a started state.
1515 */
1516 com::SafeArray<ProcessWaitForFlag_T> aWaitStartFlags;
1517 aWaitStartFlags.push_back(ProcessWaitForFlag_Start);
1518 ProcessWaitResult_T waitResult;
1519 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitStartFlags),
1520 gctlRunGetRemainingTime(msStart, cMsTimeout), &waitResult));
1521
1522 ULONG uPID = 0;
1523 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
1524 if (fRunCmd && pCtx->cVerbose > 1)
1525 RTPrintf("Process '%s' (PID %RU32) started\n", pszImage, uPID);
1526 else if (!fRunCmd && pCtx->cVerbose)
1527 {
1528 /* Just print plain PID to make it easier for scripts
1529 * invoking VBoxManage. */
1530 RTPrintf("[%RU32 - Session %RU32]\n", uPID, pCtx->uSessionID);
1531 }
1532
1533 /*
1534 * Wait for process to exit/start...
1535 */
1536 RTMSINTERVAL cMsTimeLeft = 1; /* Will be calculated. */
1537 bool fReadStdOut = false;
1538 bool fReadStdErr = false;
1539 bool fCompleted = false;
1540 bool fCompletedStartCmd = false;
1541 int vrc = VINF_SUCCESS;
1542
1543 while ( !fCompleted
1544 && cMsTimeLeft > 0)
1545 {
1546 cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout);
1547 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitFlags),
1548 RT_MIN(500 /*ms*/, RT_MAX(cMsTimeLeft, 1 /*ms*/)),
1549 &waitResult));
1550 switch (waitResult)
1551 {
1552 case ProcessWaitResult_Start:
1553 fCompletedStartCmd = fCompleted = !fRunCmd; /* Only wait for startup if the 'start' command. */
1554 break;
1555 case ProcessWaitResult_StdOut:
1556 fReadStdOut = true;
1557 break;
1558 case ProcessWaitResult_StdErr:
1559 fReadStdErr = true;
1560 break;
1561 case ProcessWaitResult_Terminate:
1562 if (pCtx->cVerbose > 1)
1563 RTPrintf("Process terminated\n");
1564 /* Process terminated, we're done. */
1565 fCompleted = true;
1566 break;
1567 case ProcessWaitResult_WaitFlagNotSupported:
1568 /* The guest does not support waiting for stdout/err, so
1569 * yield to reduce the CPU load due to busy waiting. */
1570 RTThreadYield();
1571 fReadStdOut = fReadStdErr = true;
1572 break;
1573 case ProcessWaitResult_Timeout:
1574 {
1575 /** @todo It is really unclear whether we will get stuck with the timeout
1576 * result here if the guest side times out the process and fails to
1577 * kill the process... To be on the save side, double the IPC and
1578 * check the process status every time we time out. */
1579 ProcessStatus_T enmProcStatus;
1580 CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&enmProcStatus));
1581 if ( enmProcStatus == ProcessStatus_TimedOutKilled
1582 || enmProcStatus == ProcessStatus_TimedOutAbnormally)
1583 fCompleted = true;
1584 fReadStdOut = fReadStdErr = true;
1585 break;
1586 }
1587 case ProcessWaitResult_Status:
1588 /* ignore. */
1589 break;
1590 case ProcessWaitResult_Error:
1591 /* waitFor is dead in the water, I think, so better leave the loop. */
1592 vrc = VERR_CALLBACK_RETURN;
1593 break;
1594
1595 case ProcessWaitResult_StdIn: AssertFailed(); /* did ask for this! */ break;
1596 case ProcessWaitResult_None: AssertFailed(); /* used. */ break;
1597 default: AssertFailed(); /* huh? */ break;
1598 }
1599
1600 if (g_fGuestCtrlCanceled)
1601 break;
1602
1603 /*
1604 * Pump output as needed.
1605 */
1606 if (fReadStdOut)
1607 {
1608 cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout);
1609 int vrc2 = gctlRunPumpOutput(pProcess, hVfsStdOut, 1 /* StdOut */, cMsTimeLeft);
1610 if (RT_FAILURE(vrc2) && RT_SUCCESS(vrc))
1611 vrc = vrc2;
1612 fReadStdOut = false;
1613 }
1614 if (fReadStdErr)
1615 {
1616 cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout);
1617 int vrc2 = gctlRunPumpOutput(pProcess, hVfsStdErr, 2 /* StdErr */, cMsTimeLeft);
1618 if (RT_FAILURE(vrc2) && RT_SUCCESS(vrc))
1619 vrc = vrc2;
1620 fReadStdErr = false;
1621 }
1622 if ( RT_FAILURE(vrc)
1623 || g_fGuestCtrlCanceled)
1624 break;
1625
1626 /*
1627 * Process events before looping.
1628 */
1629 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
1630 } /* while */
1631
1632 /*
1633 * Report status back to the user.
1634 */
1635 if (g_fGuestCtrlCanceled)
1636 {
1637 if (pCtx->cVerbose > 1)
1638 RTPrintf("Process execution aborted!\n");
1639 rcExit = EXITCODEEXEC_CANCELED;
1640 }
1641 else if (fCompletedStartCmd)
1642 {
1643 if (pCtx->cVerbose > 1)
1644 RTPrintf("Process successfully started!\n");
1645 rcExit = RTEXITCODE_SUCCESS;
1646 }
1647 else if (fCompleted)
1648 {
1649 ProcessStatus_T procStatus;
1650 CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&procStatus));
1651 if ( procStatus == ProcessStatus_TerminatedNormally
1652 || procStatus == ProcessStatus_TerminatedAbnormally
1653 || procStatus == ProcessStatus_TerminatedSignal)
1654 {
1655 LONG lExitCode;
1656 CHECK_ERROR_BREAK(pProcess, COMGETTER(ExitCode)(&lExitCode));
1657 if (pCtx->cVerbose > 1)
1658 RTPrintf("Exit code=%u (Status=%u [%s])\n",
1659 lExitCode, procStatus, gctlProcessStatusToText(procStatus));
1660
1661 rcExit = gctlRunCalculateExitCode(procStatus, lExitCode, true /*fReturnExitCodes*/);
1662 }
1663 else if ( procStatus == ProcessStatus_TimedOutKilled
1664 || procStatus == ProcessStatus_TimedOutAbnormally)
1665 {
1666 if (pCtx->cVerbose > 1)
1667 RTPrintf("Process timed out (guest side) and %s\n",
1668 procStatus == ProcessStatus_TimedOutAbnormally
1669 ? "failed to terminate so far" : "was terminated");
1670 rcExit = EXITCODEEXEC_TIMEOUT;
1671 }
1672 else
1673 {
1674 if (pCtx->cVerbose > 1)
1675 RTPrintf("Process now is in status [%s] (unexpected)\n", gctlProcessStatusToText(procStatus));
1676 rcExit = RTEXITCODE_FAILURE;
1677 }
1678 }
1679 else if (RT_FAILURE_NP(vrc))
1680 {
1681 if (pCtx->cVerbose > 1)
1682 RTPrintf("Process monitor loop quit with vrc=%Rrc\n", vrc);
1683 rcExit = RTEXITCODE_FAILURE;
1684 }
1685 else
1686 {
1687 if (pCtx->cVerbose > 1)
1688 RTPrintf("Process monitor loop timed out\n");
1689 rcExit = EXITCODEEXEC_TIMEOUT;
1690 }
1691
1692 } while (0);
1693 }
1694 catch (std::bad_alloc)
1695 {
1696 rc = E_OUTOFMEMORY;
1697 }
1698
1699 /*
1700 * Decide what to do with the guest session.
1701 *
1702 * If it's the 'start' command where detach the guest process after
1703 * starting, don't close the guest session it is part of, except on
1704 * failure or ctrl-c.
1705 *
1706 * For the 'run' command the guest process quits with us.
1707 */
1708 if (!fRunCmd && SUCCEEDED(rc) && !g_fGuestCtrlCanceled)
1709 pCtx->fDetachGuestSession = true;
1710
1711 /* Make sure we return failure on failure. */
1712 if (FAILED(rc) && rcExit == RTEXITCODE_SUCCESS)
1713 rcExit = RTEXITCODE_FAILURE;
1714 return rcExit;
1715}
1716
1717
1718static DECLCALLBACK(RTEXITCODE) gctlHandleRun(PGCTLCMDCTX pCtx, int argc, char **argv)
1719{
1720 return gctlHandleRunCommon(pCtx, argc, argv, true /*fRunCmd*/, USAGE_GSTCTRL_RUN);
1721}
1722
1723
1724static DECLCALLBACK(RTEXITCODE) gctlHandleStart(PGCTLCMDCTX pCtx, int argc, char **argv)
1725{
1726 return gctlHandleRunCommon(pCtx, argc, argv, false /*fRunCmd*/, USAGE_GSTCTRL_START);
1727}
1728
1729
1730/** bird: This is just a code conversion tool, flags are better defined by
1731 * the preprocessor, in general. But the code was using obsoleted
1732 * main flags for internal purposes (in a uint32_t) without passing them
1733 * along, or it seemed that way. Enum means compiler checks types. */
1734enum gctlCopyFlags
1735{
1736 kGctlCopyFlags_None = 0,
1737 kGctlCopyFlags_Recursive = RT_BIT(1),
1738 kGctlCopyFlags_FollowLinks = RT_BIT(2)
1739};
1740
1741
1742/**
1743 * Creates a copy context structure which then can be used with various
1744 * guest control copy functions. Needs to be free'd with gctlCopyContextFree().
1745 *
1746 * @return IPRT status code.
1747 * @param pCtx Pointer to command context.
1748 * @param fDryRun Flag indicating if we want to run a dry run only.
1749 * @param fHostToGuest Flag indicating if we want to copy from host to guest
1750 * or vice versa.
1751 * @param strSessionName Session name (only for identification purposes).
1752 * @param ppContext Pointer which receives the allocated copy context.
1753 */
1754static int gctlCopyContextCreate(PGCTLCMDCTX pCtx, bool fDryRun, bool fHostToGuest,
1755 const Utf8Str &strSessionName,
1756 PCOPYCONTEXT *ppContext)
1757{
1758 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1759
1760 int vrc = VINF_SUCCESS;
1761 try
1762 {
1763 PCOPYCONTEXT pContext = new COPYCONTEXT();
1764
1765 pContext->pCmdCtx = pCtx;
1766 pContext->fDryRun = fDryRun;
1767 pContext->fHostToGuest = fHostToGuest;
1768
1769 *ppContext = pContext;
1770 }
1771 catch (std::bad_alloc)
1772 {
1773 vrc = VERR_NO_MEMORY;
1774 }
1775
1776 return vrc;
1777}
1778
1779/**
1780 * Frees are previously allocated copy context structure.
1781 *
1782 * @param pContext Pointer to copy context to free.
1783 */
1784static void gctlCopyContextFree(PCOPYCONTEXT pContext)
1785{
1786 if (pContext)
1787 delete pContext;
1788}
1789
1790/**
1791 * Translates a source path to a destination path (can be both sides,
1792 * either host or guest). The source root is needed to determine the start
1793 * of the relative source path which also needs to present in the destination
1794 * path.
1795 *
1796 * @return IPRT status code.
1797 * @param pszSourceRoot Source root path. No trailing directory slash!
1798 * @param pszSource Actual source to transform. Must begin with
1799 * the source root path!
1800 * @param pszDest Destination path.
1801 * @param ppszTranslated Pointer to the allocated, translated destination
1802 * path. Must be free'd with RTStrFree().
1803 */
1804static int gctlCopyTranslatePath(const char *pszSourceRoot, const char *pszSource,
1805 const char *pszDest, char **ppszTranslated)
1806{
1807 AssertPtrReturn(pszSourceRoot, VERR_INVALID_POINTER);
1808 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1809 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1810 AssertPtrReturn(ppszTranslated, VERR_INVALID_POINTER);
1811#if 0 /** @todo r=bird: It does not make sense to apply host path parsing semantics onto guest paths. I hope this code isn't mixing host/guest paths in the same way anywhere else... @bugref{6344} */
1812 AssertReturn(RTPathStartsWith(pszSource, pszSourceRoot), VERR_INVALID_PARAMETER);
1813#endif
1814
1815 /* Construct the relative dest destination path by "subtracting" the
1816 * source from the source root, e.g.
1817 *
1818 * source root path = "e:\foo\", source = "e:\foo\bar"
1819 * dest = "d:\baz\"
1820 * translated = "d:\baz\bar\"
1821 */
1822 char szTranslated[RTPATH_MAX];
1823 size_t srcOff = strlen(pszSourceRoot);
1824 AssertReturn(srcOff, VERR_INVALID_PARAMETER);
1825
1826 char *pszDestPath = RTStrDup(pszDest);
1827 AssertPtrReturn(pszDestPath, VERR_NO_MEMORY);
1828
1829 int vrc;
1830 if (!RTPathFilename(pszDestPath))
1831 {
1832 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1833 pszDestPath, &pszSource[srcOff]);
1834 }
1835 else
1836 {
1837 char *pszDestFileName = RTStrDup(RTPathFilename(pszDestPath));
1838 if (pszDestFileName)
1839 {
1840 RTPathStripFilename(pszDestPath);
1841 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1842 pszDestPath, pszDestFileName);
1843 RTStrFree(pszDestFileName);
1844 }
1845 else
1846 vrc = VERR_NO_MEMORY;
1847 }
1848 RTStrFree(pszDestPath);
1849
1850 if (RT_SUCCESS(vrc))
1851 {
1852 *ppszTranslated = RTStrDup(szTranslated);
1853#if 0
1854 RTPrintf("Root: %s, Source: %s, Dest: %s, Translated: %s\n",
1855 pszSourceRoot, pszSource, pszDest, *ppszTranslated);
1856#endif
1857 }
1858 return vrc;
1859}
1860
1861#ifdef DEBUG_andy
1862static int tstTranslatePath()
1863{
1864 RTAssertSetMayPanic(false /* Do not freak out, please. */);
1865
1866 static struct
1867 {
1868 const char *pszSourceRoot;
1869 const char *pszSource;
1870 const char *pszDest;
1871 const char *pszTranslated;
1872 int iResult;
1873 } aTests[] =
1874 {
1875 /* Invalid stuff. */
1876 { NULL, NULL, NULL, NULL, VERR_INVALID_POINTER },
1877#ifdef RT_OS_WINDOWS
1878 /* Windows paths. */
1879 { "c:\\foo", "c:\\foo\\bar.txt", "c:\\test", "c:\\test\\bar.txt", VINF_SUCCESS },
1880 { "c:\\foo", "c:\\foo\\baz\\bar.txt", "c:\\test", "c:\\test\\baz\\bar.txt", VINF_SUCCESS },
1881#else /* RT_OS_WINDOWS */
1882 { "/home/test/foo", "/home/test/foo/bar.txt", "/opt/test", "/opt/test/bar.txt", VINF_SUCCESS },
1883 { "/home/test/foo", "/home/test/foo/baz/bar.txt", "/opt/test", "/opt/test/baz/bar.txt", VINF_SUCCESS },
1884#endif /* !RT_OS_WINDOWS */
1885 /* Mixed paths*/
1886 /** @todo */
1887 { NULL }
1888 };
1889
1890 size_t iTest = 0;
1891 for (iTest; iTest < RT_ELEMENTS(aTests); iTest++)
1892 {
1893 RTPrintf("=> Test %d\n", iTest);
1894 RTPrintf("\tSourceRoot=%s, Source=%s, Dest=%s\n",
1895 aTests[iTest].pszSourceRoot, aTests[iTest].pszSource, aTests[iTest].pszDest);
1896
1897 char *pszTranslated = NULL;
1898 int iResult = gctlCopyTranslatePath(aTests[iTest].pszSourceRoot, aTests[iTest].pszSource,
1899 aTests[iTest].pszDest, &pszTranslated);
1900 if (iResult != aTests[iTest].iResult)
1901 {
1902 RTPrintf("\tReturned %Rrc, expected %Rrc\n",
1903 iResult, aTests[iTest].iResult);
1904 }
1905 else if ( pszTranslated
1906 && strcmp(pszTranslated, aTests[iTest].pszTranslated))
1907 {
1908 RTPrintf("\tReturned translated path %s, expected %s\n",
1909 pszTranslated, aTests[iTest].pszTranslated);
1910 }
1911
1912 if (pszTranslated)
1913 {
1914 RTPrintf("\tTranslated=%s\n", pszTranslated);
1915 RTStrFree(pszTranslated);
1916 }
1917 }
1918
1919 return VINF_SUCCESS; /* @todo */
1920}
1921#endif
1922
1923/**
1924 * Creates a directory on the destination, based on the current copy
1925 * context.
1926 *
1927 * @return IPRT status code.
1928 * @param pContext Pointer to current copy control context.
1929 * @param pszDir Directory to create.
1930 */
1931static int gctlCopyDirCreate(PCOPYCONTEXT pContext, const char *pszDir)
1932{
1933 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1934 AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
1935
1936 bool fDirExists;
1937 int vrc = gctlCopyDirExists(pContext, pContext->fHostToGuest, pszDir, &fDirExists);
1938 if ( RT_SUCCESS(vrc)
1939 && fDirExists)
1940 {
1941 if (pContext->pCmdCtx->cVerbose > 1)
1942 RTPrintf("Directory \"%s\" already exists\n", pszDir);
1943 return VINF_SUCCESS;
1944 }
1945
1946 /* If querying for a directory existence fails there's no point of even trying
1947 * to create such a directory. */
1948 if (RT_FAILURE(vrc))
1949 return vrc;
1950
1951 if (pContext->pCmdCtx->cVerbose > 1)
1952 RTPrintf("Creating directory \"%s\" ...\n", pszDir);
1953
1954 if (pContext->fDryRun)
1955 return VINF_SUCCESS;
1956
1957 if (pContext->fHostToGuest) /* We want to create directories on the guest. */
1958 {
1959 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
1960 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
1961 HRESULT rc = pContext->pCmdCtx->pGuestSession->DirectoryCreate(Bstr(pszDir).raw(),
1962 0700, ComSafeArrayAsInParam(dirCreateFlags));
1963 if (FAILED(rc))
1964 vrc = gctlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
1965 }
1966 else /* ... or on the host. */
1967 {
1968 vrc = RTDirCreateFullPath(pszDir, 0700);
1969 if (vrc == VERR_ALREADY_EXISTS)
1970 vrc = VINF_SUCCESS;
1971 }
1972 return vrc;
1973}
1974
1975/**
1976 * Checks whether a specific host/guest directory exists.
1977 *
1978 * @return IPRT status code.
1979 * @param pContext Pointer to current copy control context.
1980 * @param fOnGuest true if directory needs to be checked on the guest
1981 * or false if on the host.
1982 * @param pszDir Actual directory to check.
1983 * @param fExists Pointer which receives the result if the
1984 * given directory exists or not.
1985 */
1986static int gctlCopyDirExists(PCOPYCONTEXT pContext, bool fOnGuest,
1987 const char *pszDir, bool *fExists)
1988{
1989 AssertPtrReturn(pContext, false);
1990 AssertPtrReturn(pszDir, false);
1991 AssertPtrReturn(fExists, false);
1992
1993 int vrc = VINF_SUCCESS;
1994 if (fOnGuest)
1995 {
1996 BOOL fDirExists = FALSE;
1997 HRESULT rc = pContext->pCmdCtx->pGuestSession->DirectoryExists(Bstr(pszDir).raw(), FALSE /*followSymlinks*/, &fDirExists);
1998 if (SUCCEEDED(rc))
1999 *fExists = fDirExists != FALSE;
2000 else
2001 vrc = gctlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
2002 }
2003 else
2004 *fExists = RTDirExists(pszDir);
2005 return vrc;
2006}
2007
2008/**
2009 * Checks whether a specific directory exists on the destination, based
2010 * on the current copy context.
2011 *
2012 * @return IPRT status code.
2013 * @param pContext Pointer to current copy control context.
2014 * @param pszDir Actual directory to check.
2015 * @param fExists Pointer which receives the result if the
2016 * given directory exists or not.
2017 */
2018static int gctlCopyDirExistsOnDest(PCOPYCONTEXT pContext, const char *pszDir,
2019 bool *fExists)
2020{
2021 return gctlCopyDirExists(pContext, pContext->fHostToGuest,
2022 pszDir, fExists);
2023}
2024
2025/**
2026 * Checks whether a specific directory exists on the source, based
2027 * on the current copy context.
2028 *
2029 * @return IPRT status code.
2030 * @param pContext Pointer to current copy control context.
2031 * @param pszDir Actual directory to check.
2032 * @param fExists Pointer which receives the result if the
2033 * given directory exists or not.
2034 */
2035static int gctlCopyDirExistsOnSource(PCOPYCONTEXT pContext, const char *pszDir,
2036 bool *fExists)
2037{
2038 return gctlCopyDirExists(pContext, !pContext->fHostToGuest,
2039 pszDir, fExists);
2040}
2041
2042/**
2043 * Checks whether a specific host/guest file exists.
2044 *
2045 * @return IPRT status code.
2046 * @param pContext Pointer to current copy control context.
2047 * @param bGuest true if file needs to be checked on the guest
2048 * or false if on the host.
2049 * @param pszFile Actual file to check.
2050 * @param fExists Pointer which receives the result if the
2051 * given file exists or not.
2052 */
2053static int gctlCopyFileExists(PCOPYCONTEXT pContext, bool bOnGuest,
2054 const char *pszFile, bool *fExists)
2055{
2056 AssertPtrReturn(pContext, false);
2057 AssertPtrReturn(pszFile, false);
2058 AssertPtrReturn(fExists, false);
2059
2060 int vrc = VINF_SUCCESS;
2061 if (bOnGuest)
2062 {
2063 BOOL fFileExists = FALSE;
2064 HRESULT rc = pContext->pCmdCtx->pGuestSession->FileExists(Bstr(pszFile).raw(), FALSE /*followSymlinks*/, &fFileExists);
2065 if (SUCCEEDED(rc))
2066 *fExists = fFileExists != FALSE;
2067 else
2068 vrc = gctlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
2069 }
2070 else
2071 *fExists = RTFileExists(pszFile);
2072 return vrc;
2073}
2074
2075/**
2076 * Checks whether a specific file exists on the destination, based on the
2077 * current copy context.
2078 *
2079 * @return IPRT status code.
2080 * @param pContext Pointer to current copy control context.
2081 * @param pszFile Actual file to check.
2082 * @param fExists Pointer which receives the result if the
2083 * given file exists or not.
2084 */
2085static int gctlCopyFileExistsOnDest(PCOPYCONTEXT pContext, const char *pszFile,
2086 bool *fExists)
2087{
2088 return gctlCopyFileExists(pContext, pContext->fHostToGuest,
2089 pszFile, fExists);
2090}
2091
2092/**
2093 * Checks whether a specific file exists on the source, based on the
2094 * current copy context.
2095 *
2096 * @return IPRT status code.
2097 * @param pContext Pointer to current copy control context.
2098 * @param pszFile Actual file to check.
2099 * @param fExists Pointer which receives the result if the
2100 * given file exists or not.
2101 */
2102static int gctlCopyFileExistsOnSource(PCOPYCONTEXT pContext, const char *pszFile,
2103 bool *fExists)
2104{
2105 return gctlCopyFileExists(pContext, !pContext->fHostToGuest,
2106 pszFile, fExists);
2107}
2108
2109/**
2110 * Copies a source file to the destination.
2111 *
2112 * @return IPRT status code.
2113 * @param pContext Pointer to current copy control context.
2114 * @param pszFileSource Source file to copy to the destination.
2115 * @param pszFileDest Name of copied file on the destination.
2116 * @param enmFlags Copy flags. No supported at the moment and
2117 * needs to be set to 0.
2118 */
2119static int gctlCopyFileToDest(PCOPYCONTEXT pContext, const char *pszFileSource,
2120 const char *pszFileDest, gctlCopyFlags enmFlags)
2121{
2122 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
2123 AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
2124 AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
2125 AssertReturn(enmFlags == kGctlCopyFlags_None, VERR_INVALID_PARAMETER); /* No flags supported yet. */
2126
2127 if (pContext->pCmdCtx->cVerbose > 1)
2128 RTPrintf("Copying \"%s\" to \"%s\" ...\n",
2129 pszFileSource, pszFileDest);
2130
2131 if (pContext->fDryRun)
2132 return VINF_SUCCESS;
2133
2134 int vrc = VINF_SUCCESS;
2135 ComPtr<IProgress> pProgress;
2136 HRESULT rc;
2137 if (pContext->fHostToGuest)
2138 {
2139 SafeArray<FileCopyFlag_T> copyFlags;
2140 rc = pContext->pCmdCtx->pGuestSession->FileCopyToGuest(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
2141 ComSafeArrayAsInParam(copyFlags),
2142 pProgress.asOutParam());
2143 }
2144 else
2145 {
2146 SafeArray<FileCopyFlag_T> copyFlags;
2147 rc = pContext->pCmdCtx->pGuestSession->FileCopyFromGuest(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
2148 ComSafeArrayAsInParam(copyFlags),
2149 pProgress.asOutParam());
2150 }
2151
2152 if (FAILED(rc))
2153 {
2154 vrc = gctlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
2155 }
2156 else
2157 {
2158 if (pContext->pCmdCtx->cVerbose > 1)
2159 rc = showProgress(pProgress);
2160 else
2161 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
2162 if (SUCCEEDED(rc))
2163 CHECK_PROGRESS_ERROR(pProgress, ("File copy failed"));
2164 vrc = gctlPrintProgressError(pProgress);
2165 }
2166
2167 return vrc;
2168}
2169
2170/**
2171 * Copys a directory (tree) from host to the guest.
2172 *
2173 * @return IPRT status code.
2174 * @param pContext Pointer to current copy control context.
2175 * @param pszSource Source directory on the host to copy to the guest.
2176 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
2177 * @param pszDest Destination directory on the guest.
2178 * @param enmFlags Copy flags, such as recursive copying.
2179 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
2180 * is needed for recursion.
2181 */
2182static int gctlCopyDirToGuest(PCOPYCONTEXT pContext,
2183 const char *pszSource, const char *pszFilter,
2184 const char *pszDest, enum gctlCopyFlags enmFlags,
2185 const char *pszSubDir /* For recursion. */)
2186{
2187 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
2188 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
2189 /* Filter is optional. */
2190 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
2191 /* Sub directory is optional. */
2192
2193 /*
2194 * Construct current path.
2195 */
2196 char szCurDir[RTPATH_MAX];
2197 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
2198 if (RT_SUCCESS(vrc) && pszSubDir)
2199 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
2200
2201 if (pContext->pCmdCtx->cVerbose > 1)
2202 RTPrintf("Processing host directory: %s\n", szCurDir);
2203
2204 /* Flag indicating whether the current directory was created on the
2205 * target or not. */
2206 bool fDirCreated = false;
2207
2208 /*
2209 * Open directory without a filter - RTDirOpenFiltered unfortunately
2210 * cannot handle sub directories so we have to do the filtering ourselves.
2211 */
2212 PRTDIR pDir = NULL;
2213 if (RT_SUCCESS(vrc))
2214 {
2215 vrc = RTDirOpen(&pDir, szCurDir);
2216 if (RT_FAILURE(vrc))
2217 pDir = NULL;
2218 }
2219 if (RT_SUCCESS(vrc))
2220 {
2221 /*
2222 * Enumerate the directory tree.
2223 */
2224 while (RT_SUCCESS(vrc))
2225 {
2226 RTDIRENTRY DirEntry;
2227 vrc = RTDirRead(pDir, &DirEntry, NULL);
2228 if (RT_FAILURE(vrc))
2229 {
2230 if (vrc == VERR_NO_MORE_FILES)
2231 vrc = VINF_SUCCESS;
2232 break;
2233 }
2234 /** @todo r=bird: This ain't gonna work on most UNIX file systems because
2235 * enmType is RTDIRENTRYTYPE_UNKNOWN. This is clearly documented in
2236 * RTDIRENTRY::enmType. For trunk, RTDirQueryUnknownType can be used. */
2237 switch (DirEntry.enmType)
2238 {
2239 case RTDIRENTRYTYPE_DIRECTORY:
2240 {
2241 /* Skip "." and ".." entries. */
2242 if ( !strcmp(DirEntry.szName, ".")
2243 || !strcmp(DirEntry.szName, ".."))
2244 break;
2245
2246 if (pContext->pCmdCtx->cVerbose > 1)
2247 RTPrintf("Directory: %s\n", DirEntry.szName);
2248
2249 if (enmFlags & kGctlCopyFlags_Recursive)
2250 {
2251 char *pszNewSub = NULL;
2252 if (pszSubDir)
2253 pszNewSub = RTPathJoinA(pszSubDir, DirEntry.szName);
2254 else
2255 {
2256 pszNewSub = RTStrDup(DirEntry.szName);
2257 RTPathStripTrailingSlash(pszNewSub);
2258 }
2259
2260 if (pszNewSub)
2261 {
2262 vrc = gctlCopyDirToGuest(pContext,
2263 pszSource, pszFilter,
2264 pszDest, enmFlags, pszNewSub);
2265 RTStrFree(pszNewSub);
2266 }
2267 else
2268 vrc = VERR_NO_MEMORY;
2269 }
2270 break;
2271 }
2272
2273 case RTDIRENTRYTYPE_SYMLINK:
2274 if ( (enmFlags & kGctlCopyFlags_Recursive)
2275 && (enmFlags & kGctlCopyFlags_FollowLinks))
2276 {
2277 /* Fall through to next case is intentional. */
2278 }
2279 else
2280 break;
2281
2282 case RTDIRENTRYTYPE_FILE:
2283 {
2284 if ( pszFilter
2285 && !RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
2286 {
2287 break; /* Filter does not match. */
2288 }
2289
2290 if (pContext->pCmdCtx->cVerbose > 1)
2291 RTPrintf("File: %s\n", DirEntry.szName);
2292
2293 if (!fDirCreated)
2294 {
2295 char *pszDestDir;
2296 vrc = gctlCopyTranslatePath(pszSource, szCurDir,
2297 pszDest, &pszDestDir);
2298 if (RT_SUCCESS(vrc))
2299 {
2300 vrc = gctlCopyDirCreate(pContext, pszDestDir);
2301 RTStrFree(pszDestDir);
2302
2303 fDirCreated = true;
2304 }
2305 }
2306
2307 if (RT_SUCCESS(vrc))
2308 {
2309 char *pszFileSource = RTPathJoinA(szCurDir, DirEntry.szName);
2310 if (pszFileSource)
2311 {
2312 char *pszFileDest;
2313 vrc = gctlCopyTranslatePath(pszSource, pszFileSource,
2314 pszDest, &pszFileDest);
2315 if (RT_SUCCESS(vrc))
2316 {
2317 vrc = gctlCopyFileToDest(pContext, pszFileSource,
2318 pszFileDest, kGctlCopyFlags_None);
2319 RTStrFree(pszFileDest);
2320 }
2321 RTStrFree(pszFileSource);
2322 }
2323 }
2324 break;
2325 }
2326
2327 default:
2328 break;
2329 }
2330 if (RT_FAILURE(vrc))
2331 break;
2332 }
2333
2334 RTDirClose(pDir);
2335 }
2336 return vrc;
2337}
2338
2339/**
2340 * Copys a directory (tree) from guest to the host.
2341 *
2342 * @return IPRT status code.
2343 * @param pContext Pointer to current copy control context.
2344 * @param pszSource Source directory on the guest to copy to the host.
2345 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
2346 * @param pszDest Destination directory on the host.
2347 * @param enmFlags Copy flags, such as recursive copying.
2348 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
2349 * is needed for recursion.
2350 */
2351static int gctlCopyDirToHost(PCOPYCONTEXT pContext,
2352 const char *pszSource, const char *pszFilter,
2353 const char *pszDest, gctlCopyFlags enmFlags,
2354 const char *pszSubDir /* For recursion. */)
2355{
2356 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
2357 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
2358 /* Filter is optional. */
2359 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
2360 /* Sub directory is optional. */
2361
2362 /*
2363 * Construct current path.
2364 */
2365 char szCurDir[RTPATH_MAX];
2366 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
2367 if (RT_SUCCESS(vrc) && pszSubDir)
2368 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
2369
2370 if (RT_FAILURE(vrc))
2371 return vrc;
2372
2373 if (pContext->pCmdCtx->cVerbose > 1)
2374 RTPrintf("Processing guest directory: %s\n", szCurDir);
2375
2376 /* Flag indicating whether the current directory was created on the
2377 * target or not. */
2378 bool fDirCreated = false;
2379 SafeArray<DirectoryOpenFlag_T> dirOpenFlags; /* No flags supported yet. */
2380 ComPtr<IGuestDirectory> pDirectory;
2381 HRESULT rc = pContext->pCmdCtx->pGuestSession->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(),
2382 ComSafeArrayAsInParam(dirOpenFlags),
2383 pDirectory.asOutParam());
2384 if (FAILED(rc))
2385 return gctlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
2386 ComPtr<IFsObjInfo> dirEntry;
2387 while (true)
2388 {
2389 rc = pDirectory->Read(dirEntry.asOutParam());
2390 if (FAILED(rc))
2391 break;
2392
2393 FsObjType_T enmType;
2394 dirEntry->COMGETTER(Type)(&enmType);
2395
2396 Bstr strName;
2397 dirEntry->COMGETTER(Name)(strName.asOutParam());
2398
2399 switch (enmType)
2400 {
2401 case FsObjType_Directory:
2402 {
2403 Assert(!strName.isEmpty());
2404
2405 /* Skip "." and ".." entries. */
2406 if ( !strName.compare(Bstr("."))
2407 || !strName.compare(Bstr("..")))
2408 break;
2409
2410 if (pContext->pCmdCtx->cVerbose > 1)
2411 {
2412 Utf8Str strDir(strName);
2413 RTPrintf("Directory: %s\n", strDir.c_str());
2414 }
2415
2416 if (enmFlags & kGctlCopyFlags_Recursive)
2417 {
2418 Utf8Str strDir(strName);
2419 char *pszNewSub = NULL;
2420 if (pszSubDir)
2421 pszNewSub = RTPathJoinA(pszSubDir, strDir.c_str());
2422 else
2423 {
2424 pszNewSub = RTStrDup(strDir.c_str());
2425 RTPathStripTrailingSlash(pszNewSub);
2426 }
2427 if (pszNewSub)
2428 {
2429 vrc = gctlCopyDirToHost(pContext,
2430 pszSource, pszFilter,
2431 pszDest, enmFlags, pszNewSub);
2432 RTStrFree(pszNewSub);
2433 }
2434 else
2435 vrc = VERR_NO_MEMORY;
2436 }
2437 break;
2438 }
2439
2440 case FsObjType_Symlink:
2441 if ( (enmFlags & kGctlCopyFlags_Recursive)
2442 && (enmFlags & kGctlCopyFlags_FollowLinks))
2443 {
2444 /* Fall through to next case is intentional. */
2445 }
2446 else
2447 break;
2448
2449 case FsObjType_File:
2450 {
2451 Assert(!strName.isEmpty());
2452
2453 Utf8Str strFile(strName);
2454 if ( pszFilter
2455 && !RTStrSimplePatternMatch(pszFilter, strFile.c_str()))
2456 {
2457 break; /* Filter does not match. */
2458 }
2459
2460 if (pContext->pCmdCtx->cVerbose > 1)
2461 RTPrintf("File: %s\n", strFile.c_str());
2462
2463 if (!fDirCreated)
2464 {
2465 char *pszDestDir;
2466 vrc = gctlCopyTranslatePath(pszSource, szCurDir,
2467 pszDest, &pszDestDir);
2468 if (RT_SUCCESS(vrc))
2469 {
2470 vrc = gctlCopyDirCreate(pContext, pszDestDir);
2471 RTStrFree(pszDestDir);
2472
2473 fDirCreated = true;
2474 }
2475 }
2476
2477 if (RT_SUCCESS(vrc))
2478 {
2479 char *pszFileSource = RTPathJoinA(szCurDir, strFile.c_str());
2480 if (pszFileSource)
2481 {
2482 char *pszFileDest;
2483 vrc = gctlCopyTranslatePath(pszSource, pszFileSource,
2484 pszDest, &pszFileDest);
2485 if (RT_SUCCESS(vrc))
2486 {
2487 vrc = gctlCopyFileToDest(pContext, pszFileSource,
2488 pszFileDest, kGctlCopyFlags_None);
2489 RTStrFree(pszFileDest);
2490 }
2491 RTStrFree(pszFileSource);
2492 }
2493 else
2494 vrc = VERR_NO_MEMORY;
2495 }
2496 break;
2497 }
2498
2499 default:
2500 RTPrintf("Warning: Directory entry of type %ld not handled, skipping ...\n",
2501 enmType);
2502 break;
2503 }
2504
2505 if (RT_FAILURE(vrc))
2506 break;
2507 }
2508
2509 if (RT_UNLIKELY(FAILED(rc)))
2510 {
2511 switch (rc)
2512 {
2513 case E_ABORT: /* No more directory entries left to process. */
2514 break;
2515
2516 case VBOX_E_FILE_ERROR: /* Current entry cannot be accessed to
2517 to missing rights. */
2518 {
2519 RTPrintf("Warning: Cannot access \"%s\", skipping ...\n",
2520 szCurDir);
2521 break;
2522 }
2523
2524 default:
2525 vrc = gctlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
2526 break;
2527 }
2528 }
2529
2530 HRESULT rc2 = pDirectory->Close();
2531 if (FAILED(rc2))
2532 {
2533 int vrc2 = gctlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
2534 if (RT_SUCCESS(vrc))
2535 vrc = vrc2;
2536 }
2537 else if (SUCCEEDED(rc))
2538 rc = rc2;
2539
2540 return vrc;
2541}
2542
2543/**
2544 * Copys a directory (tree) to the destination, based on the current copy
2545 * context.
2546 *
2547 * @return IPRT status code.
2548 * @param pContext Pointer to current copy control context.
2549 * @param pszSource Source directory to copy to the destination.
2550 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
2551 * @param pszDest Destination directory where to copy in the source
2552 * source directory.
2553 * @param enmFlags Copy flags, such as recursive copying.
2554 */
2555static int gctlCopyDirToDest(PCOPYCONTEXT pContext,
2556 const char *pszSource, const char *pszFilter,
2557 const char *pszDest, enum gctlCopyFlags enmFlags)
2558{
2559 if (pContext->fHostToGuest)
2560 return gctlCopyDirToGuest(pContext, pszSource, pszFilter,
2561 pszDest, enmFlags, NULL /* Sub directory, only for recursion. */);
2562 return gctlCopyDirToHost(pContext, pszSource, pszFilter,
2563 pszDest, enmFlags, NULL /* Sub directory, only for recursion. */);
2564}
2565
2566/**
2567 * Creates a source root by stripping file names or filters of the specified source.
2568 *
2569 * @return IPRT status code.
2570 * @param pszSource Source to create source root for.
2571 * @param ppszSourceRoot Pointer that receives the allocated source root. Needs
2572 * to be free'd with gctlCopyFreeSourceRoot().
2573 */
2574static int gctlCopyCreateSourceRoot(const char *pszSource, char **ppszSourceRoot)
2575{
2576 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
2577 AssertPtrReturn(ppszSourceRoot, VERR_INVALID_POINTER);
2578
2579 char *pszNewRoot = RTStrDup(pszSource);
2580 if (!pszNewRoot)
2581 return VERR_NO_MEMORY;
2582
2583 size_t lenRoot = strlen(pszNewRoot);
2584 if ( lenRoot
2585 && ( pszNewRoot[lenRoot - 1] == '/'
2586 || pszNewRoot[lenRoot - 1] == '\\')
2587 )
2588 {
2589 pszNewRoot[lenRoot - 1] = '\0';
2590 }
2591
2592 if ( lenRoot > 1
2593 && ( pszNewRoot[lenRoot - 2] == '/'
2594 || pszNewRoot[lenRoot - 2] == '\\')
2595 )
2596 {
2597 pszNewRoot[lenRoot - 2] = '\0';
2598 }
2599
2600 if (!lenRoot)
2601 {
2602 /* If there's anything (like a file name or a filter),
2603 * strip it! */
2604 RTPathStripFilename(pszNewRoot);
2605 }
2606
2607 *ppszSourceRoot = pszNewRoot;
2608
2609 return VINF_SUCCESS;
2610}
2611
2612/**
2613 * Frees a previously allocated source root.
2614 *
2615 * @return IPRT status code.
2616 * @param pszSourceRoot Source root to free.
2617 */
2618static void gctlCopyFreeSourceRoot(char *pszSourceRoot)
2619{
2620 RTStrFree(pszSourceRoot);
2621}
2622
2623static RTEXITCODE gctlHandleCopy(PGCTLCMDCTX pCtx, int argc, char **argv, bool fHostToGuest)
2624{
2625 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2626
2627 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
2628 * is much better (partly because it is much simpler of course). The main
2629 * arguments against this is that (1) all but two options conflicts with
2630 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
2631 * done windows CMD style (though not in a 100% compatible way), and (3)
2632 * that only one source is allowed - efficiently sabotaging default
2633 * wildcard expansion by a unix shell. The best solution here would be
2634 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
2635
2636 /*
2637 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
2638 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
2639 * does in here.
2640 */
2641 enum GETOPTDEF_COPY
2642 {
2643 GETOPTDEF_COPY_DRYRUN = 1000,
2644 GETOPTDEF_COPY_FOLLOW,
2645 GETOPTDEF_COPY_TARGETDIR
2646 };
2647 static const RTGETOPTDEF s_aOptions[] =
2648 {
2649 GCTLCMD_COMMON_OPTION_DEFS()
2650 { "--dryrun", GETOPTDEF_COPY_DRYRUN, RTGETOPT_REQ_NOTHING },
2651 { "--follow", GETOPTDEF_COPY_FOLLOW, RTGETOPT_REQ_NOTHING },
2652 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
2653 { "--target-directory", GETOPTDEF_COPY_TARGETDIR, RTGETOPT_REQ_STRING }
2654 };
2655
2656 int ch;
2657 RTGETOPTUNION ValueUnion;
2658 RTGETOPTSTATE GetState;
2659 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2660
2661 Utf8Str strSource;
2662 const char *pszDst = NULL;
2663 enum gctlCopyFlags enmFlags = kGctlCopyFlags_None;
2664 bool fCopyRecursive = false;
2665 bool fDryRun = false;
2666 uint32_t uUsage = fHostToGuest ? USAGE_GSTCTRL_COPYTO : USAGE_GSTCTRL_COPYFROM;
2667
2668 SOURCEVEC vecSources;
2669
2670 int vrc = VINF_SUCCESS;
2671 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
2672 {
2673 /* For options that require an argument, ValueUnion has received the value. */
2674 switch (ch)
2675 {
2676 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2677
2678 case GETOPTDEF_COPY_DRYRUN:
2679 fDryRun = true;
2680 break;
2681
2682 case GETOPTDEF_COPY_FOLLOW:
2683 enmFlags = (enum gctlCopyFlags)((uint32_t)enmFlags | kGctlCopyFlags_FollowLinks);
2684 break;
2685
2686 case 'R': /* Recursive processing */
2687 enmFlags = (enum gctlCopyFlags)((uint32_t)enmFlags | kGctlCopyFlags_Recursive);
2688 break;
2689
2690 case GETOPTDEF_COPY_TARGETDIR:
2691 pszDst = ValueUnion.psz;
2692 break;
2693
2694 case VINF_GETOPT_NOT_OPTION:
2695 /* Last argument and no destination specified with
2696 * --target-directory yet? Then use the current
2697 * (= last) argument as destination. */
2698 if ( pCtx->pArg->argc == GetState.iNext
2699 && pszDst == NULL)
2700 pszDst = ValueUnion.psz;
2701 else
2702 {
2703 try
2704 { /* Save the source directory. */
2705 vecSources.push_back(SOURCEFILEENTRY(ValueUnion.psz));
2706 }
2707 catch (std::bad_alloc &)
2708 {
2709 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory");
2710 }
2711 }
2712 break;
2713
2714 default:
2715 return errorGetOptEx(USAGE_GUESTCONTROL, uUsage, ch, &ValueUnion);
2716 }
2717 }
2718
2719 if (!vecSources.size())
2720 return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage, "No source(s) specified!");
2721
2722 if (pszDst == NULL)
2723 return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage, "No destination specified!");
2724
2725 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
2726 if (rcExit != RTEXITCODE_SUCCESS)
2727 return rcExit;
2728
2729 /*
2730 * Done parsing arguments, do some more preparations.
2731 */
2732 if (pCtx->cVerbose > 1)
2733 {
2734 if (fHostToGuest)
2735 RTPrintf("Copying from host to guest ...\n");
2736 else
2737 RTPrintf("Copying from guest to host ...\n");
2738 if (fDryRun)
2739 RTPrintf("Dry run - no files copied!\n");
2740 }
2741
2742 /* Create the copy context -- it contains all information
2743 * the routines need to know when handling the actual copying. */
2744 PCOPYCONTEXT pContext = NULL;
2745 vrc = gctlCopyContextCreate(pCtx, fDryRun, fHostToGuest,
2746 fHostToGuest
2747 ? "VBoxManage Guest Control - Copy to guest"
2748 : "VBoxManage Guest Control - Copy from guest", &pContext);
2749 if (RT_FAILURE(vrc))
2750 {
2751 RTMsgError("Unable to create copy context, rc=%Rrc\n", vrc);
2752 return RTEXITCODE_FAILURE;
2753 }
2754
2755/** @todo r=bird: RTPathFilename and RTPathStripFilename won't work
2756 * correctly on non-windows hosts when the guest is from the DOS world (Windows,
2757 * OS/2, DOS). The host doesn't know about DOS slashes, only UNIX slashes and
2758 * will get the wrong idea if some dilligent user does:
2759 *
2760 * copyto myfile.txt 'C:\guestfile.txt'
2761 * or
2762 * copyto myfile.txt 'D:guestfile.txt'
2763 *
2764 * @bugref{6344}
2765 */
2766 if (!RTPathFilename(pszDst))
2767 {
2768 vrc = gctlCopyDirCreate(pContext, pszDst);
2769 }
2770 else
2771 {
2772 /* We assume we got a file name as destination -- so strip
2773 * the actual file name and make sure the appropriate
2774 * directories get created. */
2775 char *pszDstDir = RTStrDup(pszDst);
2776 AssertPtr(pszDstDir);
2777 RTPathStripFilename(pszDstDir);
2778 vrc = gctlCopyDirCreate(pContext, pszDstDir);
2779 RTStrFree(pszDstDir);
2780 }
2781
2782 if (RT_SUCCESS(vrc))
2783 {
2784 /*
2785 * Here starts the actual fun!
2786 * Handle all given sources one by one.
2787 */
2788 for (unsigned long s = 0; s < vecSources.size(); s++)
2789 {
2790 char *pszSource = RTStrDup(vecSources[s].GetSource());
2791 AssertPtrBreakStmt(pszSource, vrc = VERR_NO_MEMORY);
2792 const char *pszFilter = vecSources[s].GetFilter();
2793 if (!strlen(pszFilter))
2794 pszFilter = NULL; /* If empty filter then there's no filter :-) */
2795
2796 char *pszSourceRoot;
2797 vrc = gctlCopyCreateSourceRoot(pszSource, &pszSourceRoot);
2798 if (RT_FAILURE(vrc))
2799 {
2800 RTMsgError("Unable to create source root, rc=%Rrc\n", vrc);
2801 break;
2802 }
2803
2804 if (pCtx->cVerbose > 1)
2805 RTPrintf("Source: %s\n", pszSource);
2806
2807 /** @todo Files with filter?? */
2808 bool fSourceIsFile = false;
2809 bool fSourceExists;
2810
2811 size_t cchSource = strlen(pszSource);
2812 if ( cchSource > 1
2813 && RTPATH_IS_SLASH(pszSource[cchSource - 1]))
2814 {
2815 if (pszFilter) /* Directory with filter (so use source root w/o the actual filter). */
2816 vrc = gctlCopyDirExistsOnSource(pContext, pszSourceRoot, &fSourceExists);
2817 else /* Regular directory without filter. */
2818 vrc = gctlCopyDirExistsOnSource(pContext, pszSource, &fSourceExists);
2819
2820 if (fSourceExists)
2821 {
2822 /* Strip trailing slash from our source element so that other functions
2823 * can use this stuff properly (like RTPathStartsWith). */
2824 RTPathStripTrailingSlash(pszSource);
2825 }
2826 }
2827 else
2828 {
2829 vrc = gctlCopyFileExistsOnSource(pContext, pszSource, &fSourceExists);
2830 if ( RT_SUCCESS(vrc)
2831 && fSourceExists)
2832 {
2833 fSourceIsFile = true;
2834 }
2835 }
2836
2837 if ( RT_SUCCESS(vrc)
2838 && fSourceExists)
2839 {
2840 if (fSourceIsFile)
2841 {
2842 /* Single file. */
2843 char *pszDstFile;
2844 vrc = gctlCopyTranslatePath(pszSourceRoot, pszSource, pszDst, &pszDstFile);
2845 if (RT_SUCCESS(vrc))
2846 {
2847 vrc = gctlCopyFileToDest(pContext, pszSource, pszDstFile, kGctlCopyFlags_None);
2848 RTStrFree(pszDstFile);
2849 }
2850 else
2851 RTMsgError("Unable to translate path for \"%s\", rc=%Rrc\n", pszSource, vrc);
2852 }
2853 else
2854 {
2855 /* Directory (with filter?). */
2856 vrc = gctlCopyDirToDest(pContext, pszSource, pszFilter, pszDst, enmFlags);
2857 }
2858 }
2859
2860 gctlCopyFreeSourceRoot(pszSourceRoot);
2861
2862 if ( RT_SUCCESS(vrc)
2863 && !fSourceExists)
2864 {
2865 RTMsgError("Warning: Source \"%s\" does not exist, skipping!\n",
2866 pszSource);
2867 RTStrFree(pszSource);
2868 continue;
2869 }
2870 else if (RT_FAILURE(vrc))
2871 {
2872 RTMsgError("Error processing \"%s\", rc=%Rrc\n",
2873 pszSource, vrc);
2874 RTStrFree(pszSource);
2875 break;
2876 }
2877
2878 RTStrFree(pszSource);
2879 }
2880 }
2881
2882 gctlCopyContextFree(pContext);
2883
2884 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2885}
2886
2887static DECLCALLBACK(RTEXITCODE) gctlHandleCopyFrom(PGCTLCMDCTX pCtx, int argc, char **argv)
2888{
2889 return gctlHandleCopy(pCtx, argc, argv, false /* Guest to host */);
2890}
2891
2892static DECLCALLBACK(RTEXITCODE) gctlHandleCopyTo(PGCTLCMDCTX pCtx, int argc, char **argv)
2893{
2894 return gctlHandleCopy(pCtx, argc, argv, true /* Host to guest */);
2895}
2896
2897static DECLCALLBACK(RTEXITCODE) handleCtrtMkDir(PGCTLCMDCTX pCtx, int argc, char **argv)
2898{
2899 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2900
2901 static const RTGETOPTDEF s_aOptions[] =
2902 {
2903 GCTLCMD_COMMON_OPTION_DEFS()
2904 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2905 { "--parents", 'P', RTGETOPT_REQ_NOTHING }
2906 };
2907
2908 int ch;
2909 RTGETOPTUNION ValueUnion;
2910 RTGETOPTSTATE GetState;
2911 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2912
2913 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
2914 uint32_t fDirMode = 0; /* Default mode. */
2915 uint32_t cDirsCreated = 0;
2916 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2917
2918 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
2919 {
2920 /* For options that require an argument, ValueUnion has received the value. */
2921 switch (ch)
2922 {
2923 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
2924
2925 case 'm': /* Mode */
2926 fDirMode = ValueUnion.u32;
2927 break;
2928
2929 case 'P': /* Create parents */
2930 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
2931 break;
2932
2933 case VINF_GETOPT_NOT_OPTION:
2934 if (cDirsCreated == 0)
2935 {
2936 /*
2937 * First non-option - no more options now.
2938 */
2939 rcExit = gctlCtxPostOptionParsingInit(pCtx);
2940 if (rcExit != RTEXITCODE_SUCCESS)
2941 return rcExit;
2942 if (pCtx->cVerbose > 1)
2943 RTPrintf("Creating %RU32 directories...\n", argc - GetState.iNext + 1);
2944 }
2945 if (g_fGuestCtrlCanceled)
2946 return RTMsgErrorExit(RTEXITCODE_FAILURE, "mkdir was interrupted by Ctrl-C (%u left)\n",
2947 argc - GetState.iNext + 1);
2948
2949 /*
2950 * Create the specified directory.
2951 *
2952 * On failure we'll change the exit status to failure and
2953 * continue with the next directory that needs creating. We do
2954 * this because we only create new things, and because this is
2955 * how /bin/mkdir works on unix.
2956 */
2957 cDirsCreated++;
2958 if (pCtx->cVerbose > 1)
2959 RTPrintf("Creating directory \"%s\" ...\n", ValueUnion.psz);
2960 try
2961 {
2962 HRESULT rc;
2963 CHECK_ERROR(pCtx->pGuestSession, DirectoryCreate(Bstr(ValueUnion.psz).raw(),
2964 fDirMode, ComSafeArrayAsInParam(dirCreateFlags)));
2965 if (FAILED(rc))
2966 rcExit = RTEXITCODE_FAILURE;
2967 }
2968 catch (std::bad_alloc &)
2969 {
2970 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory\n");
2971 }
2972 break;
2973
2974 default:
2975 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKDIR, ch, &ValueUnion);
2976 }
2977 }
2978
2979 if (!cDirsCreated)
2980 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKDIR, "No directory to create specified!");
2981 return rcExit;
2982}
2983
2984
2985static DECLCALLBACK(RTEXITCODE) gctlHandleRmDir(PGCTLCMDCTX pCtx, int argc, char **argv)
2986{
2987 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2988
2989 static const RTGETOPTDEF s_aOptions[] =
2990 {
2991 GCTLCMD_COMMON_OPTION_DEFS()
2992 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
2993 };
2994
2995 int ch;
2996 RTGETOPTUNION ValueUnion;
2997 RTGETOPTSTATE GetState;
2998 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2999
3000 bool fRecursive = false;
3001 uint32_t cDirRemoved = 0;
3002 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
3003
3004 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3005 {
3006 /* For options that require an argument, ValueUnion has received the value. */
3007 switch (ch)
3008 {
3009 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3010
3011 case 'R':
3012 fRecursive = true;
3013 break;
3014
3015 case VINF_GETOPT_NOT_OPTION:
3016 {
3017 if (cDirRemoved == 0)
3018 {
3019 /*
3020 * First non-option - no more options now.
3021 */
3022 rcExit = gctlCtxPostOptionParsingInit(pCtx);
3023 if (rcExit != RTEXITCODE_SUCCESS)
3024 return rcExit;
3025 if (pCtx->cVerbose > 1)
3026 RTPrintf("Removing %RU32 directorie%ss...\n", argc - GetState.iNext + 1, fRecursive ? "trees" : "");
3027 }
3028 if (g_fGuestCtrlCanceled)
3029 return RTMsgErrorExit(RTEXITCODE_FAILURE, "rmdir was interrupted by Ctrl-C (%u left)\n",
3030 argc - GetState.iNext + 1);
3031
3032 cDirRemoved++;
3033 HRESULT rc;
3034 if (!fRecursive)
3035 {
3036 /*
3037 * Remove exactly one directory.
3038 */
3039 if (pCtx->cVerbose > 1)
3040 RTPrintf("Removing directory \"%s\" ...\n", ValueUnion.psz);
3041 try
3042 {
3043 CHECK_ERROR(pCtx->pGuestSession, DirectoryRemove(Bstr(ValueUnion.psz).raw()));
3044 }
3045 catch (std::bad_alloc &)
3046 {
3047 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory\n");
3048 }
3049 }
3050 else
3051 {
3052 /*
3053 * Remove the directory and anything under it, that means files
3054 * and everything. This is in the tradition of the Windows NT
3055 * CMD.EXE "rmdir /s" operation, a tradition which jpsoft's TCC
3056 * strongly warns against (and half-ways questions the sense of).
3057 */
3058 if (pCtx->cVerbose > 1)
3059 RTPrintf("Recursively removing directory \"%s\" ...\n", ValueUnion.psz);
3060 try
3061 {
3062 /** @todo Make flags configurable. */
3063 com::SafeArray<DirectoryRemoveRecFlag_T> aRemRecFlags;
3064 aRemRecFlags.push_back(DirectoryRemoveRecFlag_ContentAndDir);
3065
3066 ComPtr<IProgress> ptrProgress;
3067 CHECK_ERROR(pCtx->pGuestSession, DirectoryRemoveRecursive(Bstr(ValueUnion.psz).raw(),
3068 ComSafeArrayAsInParam(aRemRecFlags),
3069 ptrProgress.asOutParam()));
3070 if (SUCCEEDED(rc))
3071 {
3072 if (pCtx->cVerbose > 1)
3073 rc = showProgress(ptrProgress);
3074 else
3075 rc = ptrProgress->WaitForCompletion(-1 /* indefinitely */);
3076 if (SUCCEEDED(rc))
3077 CHECK_PROGRESS_ERROR(ptrProgress, ("Directory deletion failed"));
3078 ptrProgress.setNull();
3079 }
3080 }
3081 catch (std::bad_alloc &)
3082 {
3083 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory during recursive rmdir\n");
3084 }
3085 }
3086
3087 /*
3088 * This command returns immediately on failure since it's destructive in nature.
3089 */
3090 if (FAILED(rc))
3091 return RTEXITCODE_FAILURE;
3092 break;
3093 }
3094
3095 default:
3096 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RMDIR, ch, &ValueUnion);
3097 }
3098 }
3099
3100 if (!cDirRemoved)
3101 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RMDIR, "No directory to remove specified!");
3102 return rcExit;
3103}
3104
3105static DECLCALLBACK(RTEXITCODE) gctlHandleRm(PGCTLCMDCTX pCtx, int argc, char **argv)
3106{
3107 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3108
3109 static const RTGETOPTDEF s_aOptions[] =
3110 {
3111 GCTLCMD_COMMON_OPTION_DEFS()
3112 { "--force", 'f', RTGETOPT_REQ_NOTHING, },
3113 };
3114
3115 int ch;
3116 RTGETOPTUNION ValueUnion;
3117 RTGETOPTSTATE GetState;
3118 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3119
3120 uint32_t cFilesDeleted = 0;
3121 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
3122 bool fForce = true;
3123
3124 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3125 {
3126 /* For options that require an argument, ValueUnion has received the value. */
3127 switch (ch)
3128 {
3129 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3130
3131 case VINF_GETOPT_NOT_OPTION:
3132 if (cFilesDeleted == 0)
3133 {
3134 /*
3135 * First non-option - no more options now.
3136 */
3137 rcExit = gctlCtxPostOptionParsingInit(pCtx);
3138 if (rcExit != RTEXITCODE_SUCCESS)
3139 return rcExit;
3140 if (pCtx->cVerbose > 1)
3141 RTPrintf("Removing %RU32 file(s)...\n", argc - GetState.iNext + 1);
3142 }
3143 if (g_fGuestCtrlCanceled)
3144 return RTMsgErrorExit(RTEXITCODE_FAILURE, "rm was interrupted by Ctrl-C (%u left)\n",
3145 argc - GetState.iNext + 1);
3146
3147 /*
3148 * Remove the specified file.
3149 *
3150 * On failure we will by default stop, however, the force option will
3151 * by unix traditions force us to ignore errors and continue.
3152 */
3153 cFilesDeleted++;
3154 if (pCtx->cVerbose > 1)
3155 RTPrintf("Removing file \"%s\" ...\n", ValueUnion.psz);
3156 try
3157 {
3158 /** @todo How does IGuestSession::FsObjRemove work with read-only files? Do we
3159 * need to do some chmod or whatever to better emulate the --force flag? */
3160 HRESULT rc;
3161 CHECK_ERROR(pCtx->pGuestSession, FsObjRemove(Bstr(ValueUnion.psz).raw()));
3162 if (FAILED(rc) && !fForce)
3163 return RTEXITCODE_FAILURE;
3164 }
3165 catch (std::bad_alloc &)
3166 {
3167 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory\n");
3168 }
3169 break;
3170
3171 default:
3172 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RM, ch, &ValueUnion);
3173 }
3174 }
3175
3176 if (!cFilesDeleted && !fForce)
3177 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RM, "No file to remove specified!");
3178 return rcExit;
3179}
3180
3181static DECLCALLBACK(RTEXITCODE) gctlHandleMv(PGCTLCMDCTX pCtx, int argc, char **argv)
3182{
3183 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3184
3185 static const RTGETOPTDEF s_aOptions[] =
3186 {
3187 GCTLCMD_COMMON_OPTION_DEFS()
3188 };
3189
3190 int ch;
3191 RTGETOPTUNION ValueUnion;
3192 RTGETOPTSTATE GetState;
3193 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3194
3195 int vrc = VINF_SUCCESS;
3196
3197 bool fDryrun = false;
3198 std::vector< Utf8Str > vecSources;
3199 const char *pszDst = NULL;
3200 com::SafeArray<FsObjRenameFlag_T> aRenameFlags;
3201
3202 try
3203 {
3204 /** @todo Make flags configurable. */
3205 aRenameFlags.push_back(FsObjRenameFlag_NoReplace);
3206
3207 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3208 && RT_SUCCESS(vrc))
3209 {
3210 /* For options that require an argument, ValueUnion has received the value. */
3211 switch (ch)
3212 {
3213 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3214
3215 /** @todo Implement a --dryrun command. */
3216 /** @todo Implement rename flags. */
3217
3218 case VINF_GETOPT_NOT_OPTION:
3219 vecSources.push_back(Utf8Str(ValueUnion.psz));
3220 pszDst = ValueUnion.psz;
3221 break;
3222
3223 default:
3224 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MV, ch, &ValueUnion);
3225 }
3226 }
3227 }
3228 catch (std::bad_alloc)
3229 {
3230 vrc = VERR_NO_MEMORY;
3231 }
3232
3233 if (RT_FAILURE(vrc))
3234 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize, rc=%Rrc\n", vrc);
3235
3236 size_t cSources = vecSources.size();
3237 if (!cSources)
3238 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MV,
3239 "No source(s) to move specified!");
3240 if (cSources < 2)
3241 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MV,
3242 "No destination specified!");
3243
3244 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3245 if (rcExit != RTEXITCODE_SUCCESS)
3246 return rcExit;
3247
3248 /* Delete last element, which now is the destination. */
3249 vecSources.pop_back();
3250 cSources = vecSources.size();
3251
3252 HRESULT rc = S_OK;
3253
3254 if (cSources > 1)
3255 {
3256 BOOL fExists = FALSE;
3257 rc = pCtx->pGuestSession->DirectoryExists(Bstr(pszDst).raw(), FALSE /*followSymlinks*/, &fExists);
3258 if (FAILED(rc) || !fExists)
3259 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Destination must be a directory when specifying multiple sources\n");
3260 }
3261
3262 /*
3263 * Rename (move) the entries.
3264 */
3265 if (pCtx->cVerbose > 1)
3266 RTPrintf("Renaming %RU32 %s ...\n", cSources, cSources > 1 ? "entries" : "entry");
3267
3268 std::vector< Utf8Str >::iterator it = vecSources.begin();
3269 while ( (it != vecSources.end())
3270 && !g_fGuestCtrlCanceled)
3271 {
3272 Utf8Str strCurSource = (*it);
3273
3274 ComPtr<IGuestFsObjInfo> pFsObjInfo;
3275 FsObjType_T enmObjType;
3276 rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(strCurSource).raw(), FALSE /*followSymlinks*/, pFsObjInfo.asOutParam());
3277 if (SUCCEEDED(rc))
3278 rc = pFsObjInfo->COMGETTER(Type)(&enmObjType);
3279 if (FAILED(rc))
3280 {
3281 if (pCtx->cVerbose > 1)
3282 RTPrintf("Warning: Cannot stat for element \"%s\": No such element\n",
3283 strCurSource.c_str());
3284 ++it;
3285 continue; /* Skip. */
3286 }
3287
3288 if (pCtx->cVerbose > 1)
3289 RTPrintf("Renaming %s \"%s\" to \"%s\" ...\n",
3290 enmObjType == FsObjType_Directory ? "directory" : "file",
3291 strCurSource.c_str(), pszDst);
3292
3293 if (!fDryrun)
3294 {
3295 if (enmObjType == FsObjType_Directory)
3296 {
3297 CHECK_ERROR_BREAK(pCtx->pGuestSession, FsObjRename(Bstr(strCurSource).raw(),
3298 Bstr(pszDst).raw(),
3299 ComSafeArrayAsInParam(aRenameFlags)));
3300
3301 /* Break here, since it makes no sense to rename mroe than one source to
3302 * the same directory. */
3303/** @todo r=bird: You are being kind of windowsy (or just DOSish) about the 'sense' part here,
3304 * while being totaly buggy about the behavior. 'VBoxGuest guestcontrol ren dir1 dir2 dstdir' will
3305 * stop after 'dir1' and SILENTLY ignore dir2. If you tried this on Windows, you'd see an error
3306 * being displayed. If you 'man mv' on a nearby unixy system, you'd see that they've made perfect
3307 * sense out of any situation with more than one source. */
3308 it = vecSources.end();
3309 break;
3310 }
3311 else
3312 CHECK_ERROR_BREAK(pCtx->pGuestSession, FsObjRename(Bstr(strCurSource).raw(),
3313 Bstr(pszDst).raw(),
3314 ComSafeArrayAsInParam(aRenameFlags)));
3315 }
3316
3317 ++it;
3318 }
3319
3320 if ( (it != vecSources.end())
3321 && pCtx->cVerbose > 1)
3322 {
3323 RTPrintf("Warning: Not all sources were renamed\n");
3324 }
3325
3326 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
3327}
3328
3329static DECLCALLBACK(RTEXITCODE) gctlHandleMkTemp(PGCTLCMDCTX pCtx, int argc, char **argv)
3330{
3331 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3332
3333 static const RTGETOPTDEF s_aOptions[] =
3334 {
3335 GCTLCMD_COMMON_OPTION_DEFS()
3336 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
3337 { "--directory", 'D', RTGETOPT_REQ_NOTHING },
3338 { "--secure", 's', RTGETOPT_REQ_NOTHING },
3339 { "--tmpdir", 't', RTGETOPT_REQ_STRING }
3340 };
3341
3342 int ch;
3343 RTGETOPTUNION ValueUnion;
3344 RTGETOPTSTATE GetState;
3345 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3346
3347 Utf8Str strTemplate;
3348 uint32_t fMode = 0; /* Default mode. */
3349 bool fDirectory = false;
3350 bool fSecure = false;
3351 Utf8Str strTempDir;
3352
3353 DESTDIRMAP mapDirs;
3354
3355 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3356 {
3357 /* For options that require an argument, ValueUnion has received the value. */
3358 switch (ch)
3359 {
3360 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3361
3362 case 'm': /* Mode */
3363 fMode = ValueUnion.u32;
3364 break;
3365
3366 case 'D': /* Create directory */
3367 fDirectory = true;
3368 break;
3369
3370 case 's': /* Secure */
3371 fSecure = true;
3372 break;
3373
3374 case 't': /* Temp directory */
3375 strTempDir = ValueUnion.psz;
3376 break;
3377
3378 case VINF_GETOPT_NOT_OPTION:
3379 {
3380 if (strTemplate.isEmpty())
3381 strTemplate = ValueUnion.psz;
3382 else
3383 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP,
3384 "More than one template specified!\n");
3385 break;
3386 }
3387
3388 default:
3389 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP, ch, &ValueUnion);
3390 }
3391 }
3392
3393 if (strTemplate.isEmpty())
3394 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP,
3395 "No template specified!");
3396
3397 if (!fDirectory)
3398 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP,
3399 "Creating temporary files is currently not supported!");
3400
3401 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3402 if (rcExit != RTEXITCODE_SUCCESS)
3403 return rcExit;
3404
3405 /*
3406 * Create the directories.
3407 */
3408 if (pCtx->cVerbose > 1)
3409 {
3410 if (fDirectory && !strTempDir.isEmpty())
3411 RTPrintf("Creating temporary directory from template '%s' in directory '%s' ...\n",
3412 strTemplate.c_str(), strTempDir.c_str());
3413 else if (fDirectory)
3414 RTPrintf("Creating temporary directory from template '%s' in default temporary directory ...\n",
3415 strTemplate.c_str());
3416 else if (!fDirectory && !strTempDir.isEmpty())
3417 RTPrintf("Creating temporary file from template '%s' in directory '%s' ...\n",
3418 strTemplate.c_str(), strTempDir.c_str());
3419 else if (!fDirectory)
3420 RTPrintf("Creating temporary file from template '%s' in default temporary directory ...\n",
3421 strTemplate.c_str());
3422 }
3423
3424 HRESULT rc = S_OK;
3425 if (fDirectory)
3426 {
3427 Bstr directory;
3428 CHECK_ERROR(pCtx->pGuestSession, DirectoryCreateTemp(Bstr(strTemplate).raw(),
3429 fMode, Bstr(strTempDir).raw(),
3430 fSecure,
3431 directory.asOutParam()));
3432 if (SUCCEEDED(rc))
3433 RTPrintf("Directory name: %ls\n", directory.raw());
3434 }
3435 else
3436 {
3437 // else - temporary file not yet implemented
3438 /** @todo implement temporary file creation (we fend it off above, no
3439 * worries). */
3440 rc = E_FAIL;
3441 }
3442
3443 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
3444}
3445
3446static DECLCALLBACK(RTEXITCODE) gctlHandleStat(PGCTLCMDCTX pCtx, int argc, char **argv)
3447{
3448 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3449
3450 static const RTGETOPTDEF s_aOptions[] =
3451 {
3452 GCTLCMD_COMMON_OPTION_DEFS()
3453 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
3454 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
3455 { "--format", 'c', RTGETOPT_REQ_STRING },
3456 { "--terse", 't', RTGETOPT_REQ_NOTHING }
3457 };
3458
3459 int ch;
3460 RTGETOPTUNION ValueUnion;
3461 RTGETOPTSTATE GetState;
3462 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3463
3464 DESTDIRMAP mapObjs;
3465
3466 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3467 {
3468 /* For options that require an argument, ValueUnion has received the value. */
3469 switch (ch)
3470 {
3471 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3472
3473 case 'L': /* Dereference */
3474 case 'f': /* File-system */
3475 case 'c': /* Format */
3476 case 't': /* Terse */
3477 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT,
3478 "Command \"%s\" not implemented yet!", ValueUnion.psz);
3479
3480 case VINF_GETOPT_NOT_OPTION:
3481 mapObjs[ValueUnion.psz]; /* Add element to check to map. */
3482 break;
3483
3484 default:
3485 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT, ch, &ValueUnion);
3486 }
3487 }
3488
3489 size_t cObjs = mapObjs.size();
3490 if (!cObjs)
3491 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT,
3492 "No element(s) to check specified!");
3493
3494 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3495 if (rcExit != RTEXITCODE_SUCCESS)
3496 return rcExit;
3497
3498 HRESULT rc;
3499
3500 /*
3501 * Doing the checks.
3502 */
3503 DESTDIRMAPITER it = mapObjs.begin();
3504 while (it != mapObjs.end())
3505 {
3506 if (pCtx->cVerbose > 1)
3507 RTPrintf("Checking for element \"%s\" ...\n", it->first.c_str());
3508
3509 ComPtr<IGuestFsObjInfo> pFsObjInfo;
3510 rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(it->first).raw(), FALSE /*followSymlinks*/, pFsObjInfo.asOutParam());
3511 if (FAILED(rc))
3512 {
3513 /* If there's at least one element which does not exist on the guest,
3514 * drop out with exitcode 1. */
3515 if (pCtx->cVerbose > 1)
3516 RTPrintf("Cannot stat for element \"%s\": No such element\n",
3517 it->first.c_str());
3518 rcExit = RTEXITCODE_FAILURE;
3519 }
3520 else
3521 {
3522 FsObjType_T objType;
3523 pFsObjInfo->COMGETTER(Type)(&objType); /** @todo What about error checking? */
3524 switch (objType)
3525 {
3526 case FsObjType_File:
3527 RTPrintf("Element \"%s\" found: Is a file\n", it->first.c_str());
3528 break;
3529
3530 case FsObjType_Directory:
3531 RTPrintf("Element \"%s\" found: Is a directory\n", it->first.c_str());
3532 break;
3533
3534 case FsObjType_Symlink:
3535 RTPrintf("Element \"%s\" found: Is a symlink\n", it->first.c_str());
3536 break;
3537
3538 default:
3539 RTPrintf("Element \"%s\" found, type unknown (%ld)\n", it->first.c_str(), objType);
3540 break;
3541 }
3542
3543 /** @todo: Show more information about this element. */
3544 }
3545
3546 ++it;
3547 }
3548
3549 return rcExit;
3550}
3551
3552static DECLCALLBACK(RTEXITCODE) gctlHandleUpdateAdditions(PGCTLCMDCTX pCtx, int argc, char **argv)
3553{
3554 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3555
3556 /*
3557 * Check the syntax. We can deduce the correct syntax from the number of
3558 * arguments.
3559 */
3560 Utf8Str strSource;
3561 com::SafeArray<IN_BSTR> aArgs;
3562 bool fWaitStartOnly = false;
3563
3564 static const RTGETOPTDEF s_aOptions[] =
3565 {
3566 GCTLCMD_COMMON_OPTION_DEFS()
3567 { "--source", 's', RTGETOPT_REQ_STRING },
3568 { "--wait-start", 'w', RTGETOPT_REQ_NOTHING }
3569 };
3570
3571 int ch;
3572 RTGETOPTUNION ValueUnion;
3573 RTGETOPTSTATE GetState;
3574 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3575
3576 int vrc = VINF_SUCCESS;
3577 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3578 && RT_SUCCESS(vrc))
3579 {
3580 switch (ch)
3581 {
3582 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3583
3584 case 's':
3585 strSource = ValueUnion.psz;
3586 break;
3587
3588 case 'w':
3589 fWaitStartOnly = true;
3590 break;
3591
3592 case VINF_GETOPT_NOT_OPTION:
3593 if (aArgs.size() == 0 && strSource.isEmpty())
3594 strSource = ValueUnion.psz;
3595 else
3596 aArgs.push_back(Bstr(ValueUnion.psz).raw());
3597 break;
3598
3599 default:
3600 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_UPDATEGA, ch, &ValueUnion);
3601 }
3602 }
3603
3604 if (pCtx->cVerbose > 1)
3605 RTPrintf("Updating Guest Additions ...\n");
3606
3607 HRESULT rc = S_OK;
3608 while (strSource.isEmpty())
3609 {
3610 ComPtr<ISystemProperties> pProperties;
3611 CHECK_ERROR_BREAK(pCtx->pArg->virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam()));
3612 Bstr strISO;
3613 CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam()));
3614 strSource = strISO;
3615 break;
3616 }
3617
3618 /* Determine source if not set yet. */
3619 if (strSource.isEmpty())
3620 {
3621 RTMsgError("No Guest Additions source found or specified, aborting\n");
3622 vrc = VERR_FILE_NOT_FOUND;
3623 }
3624 else if (!RTFileExists(strSource.c_str()))
3625 {
3626 RTMsgError("Source \"%s\" does not exist!\n", strSource.c_str());
3627 vrc = VERR_FILE_NOT_FOUND;
3628 }
3629
3630 if (RT_SUCCESS(vrc))
3631 {
3632 if (pCtx->cVerbose > 1)
3633 RTPrintf("Using source: %s\n", strSource.c_str());
3634
3635
3636 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3637 if (rcExit != RTEXITCODE_SUCCESS)
3638 return rcExit;
3639
3640
3641 com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags;
3642 if (fWaitStartOnly)
3643 {
3644 aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly);
3645 if (pCtx->cVerbose > 1)
3646 RTPrintf("Preparing and waiting for Guest Additions installer to start ...\n");
3647 }
3648
3649 ComPtr<IProgress> pProgress;
3650 CHECK_ERROR(pCtx->pGuest, UpdateGuestAdditions(Bstr(strSource).raw(),
3651 ComSafeArrayAsInParam(aArgs),
3652 /* Wait for whole update process to complete. */
3653 ComSafeArrayAsInParam(aUpdateFlags),
3654 pProgress.asOutParam()));
3655 if (FAILED(rc))
3656 vrc = gctlPrintError(pCtx->pGuest, COM_IIDOF(IGuest));
3657 else
3658 {
3659 if (pCtx->cVerbose > 1)
3660 rc = showProgress(pProgress);
3661 else
3662 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
3663
3664 if (SUCCEEDED(rc))
3665 CHECK_PROGRESS_ERROR(pProgress, ("Guest additions update failed"));
3666 vrc = gctlPrintProgressError(pProgress);
3667 if ( RT_SUCCESS(vrc)
3668 && pCtx->cVerbose > 1)
3669 {
3670 RTPrintf("Guest Additions update successful\n");
3671 }
3672 }
3673 }
3674
3675 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3676}
3677
3678static DECLCALLBACK(RTEXITCODE) gctlHandleList(PGCTLCMDCTX pCtx, int argc, char **argv)
3679{
3680 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3681
3682 static const RTGETOPTDEF s_aOptions[] =
3683 {
3684 GCTLCMD_COMMON_OPTION_DEFS()
3685 };
3686
3687 int ch;
3688 RTGETOPTUNION ValueUnion;
3689 RTGETOPTSTATE GetState;
3690 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3691
3692 bool fSeenListArg = false;
3693 bool fListAll = false;
3694 bool fListSessions = false;
3695 bool fListProcesses = false;
3696 bool fListFiles = false;
3697
3698 int vrc = VINF_SUCCESS;
3699 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3700 && RT_SUCCESS(vrc))
3701 {
3702 switch (ch)
3703 {
3704 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3705
3706 case VINF_GETOPT_NOT_OPTION:
3707 if ( !RTStrICmp(ValueUnion.psz, "sessions")
3708 || !RTStrICmp(ValueUnion.psz, "sess"))
3709 fListSessions = true;
3710 else if ( !RTStrICmp(ValueUnion.psz, "processes")
3711 || !RTStrICmp(ValueUnion.psz, "procs"))
3712 fListSessions = fListProcesses = true; /* Showing processes implies showing sessions. */
3713 else if (!RTStrICmp(ValueUnion.psz, "files"))
3714 fListSessions = fListFiles = true; /* Showing files implies showing sessions. */
3715 else if (!RTStrICmp(ValueUnion.psz, "all"))
3716 fListAll = true;
3717 else
3718 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_LIST,
3719 "Unknown list: '%s'", ValueUnion.psz);
3720 fSeenListArg = true;
3721 break;
3722
3723 default:
3724 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_UPDATEGA, ch, &ValueUnion);
3725 }
3726 }
3727
3728 if (!fSeenListArg)
3729 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_LIST, "Missing list name");
3730 Assert(fListAll || fListSessions);
3731
3732 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3733 if (rcExit != RTEXITCODE_SUCCESS)
3734 return rcExit;
3735
3736
3737 /** @todo Do we need a machine-readable output here as well? */
3738
3739 HRESULT rc;
3740 size_t cTotalProcs = 0;
3741 size_t cTotalFiles = 0;
3742
3743 SafeIfaceArray <IGuestSession> collSessions;
3744 CHECK_ERROR(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3745 if (SUCCEEDED(rc))
3746 {
3747 size_t const cSessions = collSessions.size();
3748 if (cSessions)
3749 {
3750 RTPrintf("Active guest sessions:\n");
3751
3752 /** @todo Make this output a bit prettier. No time now. */
3753
3754 for (size_t i = 0; i < cSessions; i++)
3755 {
3756 ComPtr<IGuestSession> pCurSession = collSessions[i];
3757 if (!pCurSession.isNull())
3758 {
3759 do
3760 {
3761 ULONG uID;
3762 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Id)(&uID));
3763 Bstr strName;
3764 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Name)(strName.asOutParam()));
3765 Bstr strUser;
3766 CHECK_ERROR_BREAK(pCurSession, COMGETTER(User)(strUser.asOutParam()));
3767 GuestSessionStatus_T sessionStatus;
3768 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Status)(&sessionStatus));
3769 RTPrintf("\n\tSession #%-3zu ID=%-3RU32 User=%-16ls Status=[%s] Name=%ls",
3770 i, uID, strUser.raw(), gctlGuestSessionStatusToText(sessionStatus), strName.raw());
3771 } while (0);
3772
3773 if ( fListAll
3774 || fListProcesses)
3775 {
3776 SafeIfaceArray <IGuestProcess> collProcesses;
3777 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcesses)));
3778 for (size_t a = 0; a < collProcesses.size(); a++)
3779 {
3780 ComPtr<IGuestProcess> pCurProcess = collProcesses[a];
3781 if (!pCurProcess.isNull())
3782 {
3783 do
3784 {
3785 ULONG uPID;
3786 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(PID)(&uPID));
3787 Bstr strExecPath;
3788 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(ExecutablePath)(strExecPath.asOutParam()));
3789 ProcessStatus_T procStatus;
3790 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(Status)(&procStatus));
3791
3792 RTPrintf("\n\t\tProcess #%-03zu PID=%-6RU32 Status=[%s] Command=%ls",
3793 a, uPID, gctlProcessStatusToText(procStatus), strExecPath.raw());
3794 } while (0);
3795 }
3796 }
3797
3798 cTotalProcs += collProcesses.size();
3799 }
3800
3801 if ( fListAll
3802 || fListFiles)
3803 {
3804 SafeIfaceArray <IGuestFile> collFiles;
3805 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Files)(ComSafeArrayAsOutParam(collFiles)));
3806 for (size_t a = 0; a < collFiles.size(); a++)
3807 {
3808 ComPtr<IGuestFile> pCurFile = collFiles[a];
3809 if (!pCurFile.isNull())
3810 {
3811 do
3812 {
3813 ULONG idFile;
3814 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Id)(&idFile));
3815 Bstr strName;
3816 CHECK_ERROR_BREAK(pCurFile, COMGETTER(FileName)(strName.asOutParam()));
3817 FileStatus_T fileStatus;
3818 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Status)(&fileStatus));
3819
3820 RTPrintf("\n\t\tFile #%-03zu ID=%-6RU32 Status=[%s] Name=%ls",
3821 a, idFile, gctlFileStatusToText(fileStatus), strName.raw());
3822 } while (0);
3823 }
3824 }
3825
3826 cTotalFiles += collFiles.size();
3827 }
3828 }
3829 }
3830
3831 RTPrintf("\n\nTotal guest sessions: %zu\n", collSessions.size());
3832 if (fListAll || fListProcesses)
3833 RTPrintf("Total guest processes: %zu\n", cTotalProcs);
3834 if (fListAll || fListFiles)
3835 RTPrintf("Total guest files: %zu\n", cTotalFiles);
3836 }
3837 else
3838 RTPrintf("No active guest sessions found\n");
3839 }
3840
3841 if (FAILED(rc))
3842 rcExit = RTEXITCODE_FAILURE;
3843
3844 return rcExit;
3845}
3846
3847static DECLCALLBACK(RTEXITCODE) gctlHandleCloseProcess(PGCTLCMDCTX pCtx, int argc, char **argv)
3848{
3849 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3850
3851 static const RTGETOPTDEF s_aOptions[] =
3852 {
3853 GCTLCMD_COMMON_OPTION_DEFS()
3854 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3855 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3856 };
3857
3858 int ch;
3859 RTGETOPTUNION ValueUnion;
3860 RTGETOPTSTATE GetState;
3861 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3862
3863 std::vector < uint32_t > vecPID;
3864 ULONG ulSessionID = UINT32_MAX;
3865 Utf8Str strSessionName;
3866
3867 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
3868 {
3869 /* For options that require an argument, ValueUnion has received the value. */
3870 switch (ch)
3871 {
3872 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
3873
3874 case 'n': /* Session name (or pattern) */
3875 strSessionName = ValueUnion.psz;
3876 break;
3877
3878 case 'i': /* Session ID */
3879 ulSessionID = ValueUnion.u32;
3880 break;
3881
3882 case VINF_GETOPT_NOT_OPTION:
3883 {
3884 /* Treat every else specified as a PID to kill. */
3885 uint32_t uPid;
3886 int rc = RTStrToUInt32Ex(ValueUnion.psz, NULL, 0, &uPid);
3887 if ( RT_SUCCESS(rc)
3888 && rc != VWRN_TRAILING_CHARS
3889 && rc != VWRN_NUMBER_TOO_BIG
3890 && rc != VWRN_NEGATIVE_UNSIGNED)
3891 {
3892 if (uPid != 0)
3893 {
3894 try
3895 {
3896 vecPID.push_back(uPid);
3897 }
3898 catch (std::bad_alloc &)
3899 {
3900 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory");
3901 }
3902 }
3903 else
3904 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, "Invalid PID value: 0");
3905 }
3906 else
3907 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS,
3908 "Error parsing PID value: %Rrc", rc);
3909 break;
3910 }
3911
3912 default:
3913 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, ch, &ValueUnion);
3914 }
3915 }
3916
3917 if (vecPID.empty())
3918 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS,
3919 "At least one PID must be specified to kill!");
3920
3921 if ( strSessionName.isEmpty()
3922 && ulSessionID == UINT32_MAX)
3923 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, "No session ID specified!");
3924
3925 if ( strSessionName.isNotEmpty()
3926 && ulSessionID != UINT32_MAX)
3927 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS,
3928 "Either session ID or name (pattern) must be specified");
3929
3930 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
3931 if (rcExit != RTEXITCODE_SUCCESS)
3932 return rcExit;
3933
3934 HRESULT rc = S_OK;
3935
3936 ComPtr<IGuestSession> pSession;
3937 ComPtr<IGuestProcess> pProcess;
3938 do
3939 {
3940 uint32_t uProcsTerminated = 0;
3941 bool fSessionFound = false;
3942
3943 SafeIfaceArray <IGuestSession> collSessions;
3944 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3945 size_t cSessions = collSessions.size();
3946
3947 uint32_t uSessionsHandled = 0;
3948 for (size_t i = 0; i < cSessions; i++)
3949 {
3950 pSession = collSessions[i];
3951 Assert(!pSession.isNull());
3952
3953 ULONG uID; /* Session ID */
3954 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3955 Bstr strName;
3956 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3957 Utf8Str strNameUtf8(strName); /* Session name */
3958 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3959 {
3960 fSessionFound = uID == ulSessionID;
3961 }
3962 else /* ... or by naming pattern. */
3963 {
3964 if (RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()))
3965 fSessionFound = true;
3966 }
3967
3968 if (fSessionFound)
3969 {
3970 AssertStmt(!pSession.isNull(), break);
3971 uSessionsHandled++;
3972
3973 SafeIfaceArray <IGuestProcess> collProcs;
3974 CHECK_ERROR_BREAK(pSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcs)));
3975
3976 size_t cProcs = collProcs.size();
3977 for (size_t p = 0; p < cProcs; p++)
3978 {
3979 pProcess = collProcs[p];
3980 Assert(!pProcess.isNull());
3981
3982 ULONG uPID; /* Process ID */
3983 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
3984
3985 bool fProcFound = false;
3986 for (size_t a = 0; a < vecPID.size(); a++) /* Slow, but works. */
3987 {
3988 fProcFound = vecPID[a] == uPID;
3989 if (fProcFound)
3990 break;
3991 }
3992
3993 if (fProcFound)
3994 {
3995 if (pCtx->cVerbose > 1)
3996 RTPrintf("Terminating process (PID %RU32) (session ID %RU32) ...\n",
3997 uPID, uID);
3998 CHECK_ERROR_BREAK(pProcess, Terminate());
3999 uProcsTerminated++;
4000 }
4001 else
4002 {
4003 if (ulSessionID != UINT32_MAX)
4004 RTPrintf("No matching process(es) for session ID %RU32 found\n",
4005 ulSessionID);
4006 }
4007
4008 pProcess.setNull();
4009 }
4010
4011 pSession.setNull();
4012 }
4013 }
4014
4015 if (!uSessionsHandled)
4016 RTPrintf("No matching session(s) found\n");
4017
4018 if (uProcsTerminated)
4019 RTPrintf("%RU32 %s terminated\n",
4020 uProcsTerminated, uProcsTerminated == 1 ? "process" : "processes");
4021
4022 } while (0);
4023
4024 pProcess.setNull();
4025 pSession.setNull();
4026
4027 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
4028}
4029
4030
4031static DECLCALLBACK(RTEXITCODE) gctlHandleCloseSession(PGCTLCMDCTX pCtx, int argc, char **argv)
4032{
4033 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
4034
4035 enum GETOPTDEF_SESSIONCLOSE
4036 {
4037 GETOPTDEF_SESSIONCLOSE_ALL = 2000
4038 };
4039 static const RTGETOPTDEF s_aOptions[] =
4040 {
4041 GCTLCMD_COMMON_OPTION_DEFS()
4042 { "--all", GETOPTDEF_SESSIONCLOSE_ALL, RTGETOPT_REQ_NOTHING },
4043 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
4044 { "--session-name", 'n', RTGETOPT_REQ_STRING }
4045 };
4046
4047 int ch;
4048 RTGETOPTUNION ValueUnion;
4049 RTGETOPTSTATE GetState;
4050 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
4051
4052 ULONG ulSessionID = UINT32_MAX;
4053 Utf8Str strSessionName;
4054
4055 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
4056 {
4057 /* For options that require an argument, ValueUnion has received the value. */
4058 switch (ch)
4059 {
4060 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
4061
4062 case 'n': /* Session name pattern */
4063 strSessionName = ValueUnion.psz;
4064 break;
4065
4066 case 'i': /* Session ID */
4067 ulSessionID = ValueUnion.u32;
4068 break;
4069
4070 case GETOPTDEF_SESSIONCLOSE_ALL:
4071 strSessionName = "*";
4072 break;
4073
4074 case VINF_GETOPT_NOT_OPTION:
4075 /** @todo Supply a CSV list of IDs or patterns to close?
4076 * break; */
4077 default:
4078 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSESESSION, ch, &ValueUnion);
4079 }
4080 }
4081
4082 if ( strSessionName.isEmpty()
4083 && ulSessionID == UINT32_MAX)
4084 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSESESSION,
4085 "No session ID specified!");
4086
4087 if ( !strSessionName.isEmpty()
4088 && ulSessionID != UINT32_MAX)
4089 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSESESSION,
4090 "Either session ID or name (pattern) must be specified");
4091
4092 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
4093 if (rcExit != RTEXITCODE_SUCCESS)
4094 return rcExit;
4095
4096 HRESULT rc = S_OK;
4097
4098 do
4099 {
4100 bool fSessionFound = false;
4101 size_t cSessionsHandled = 0;
4102
4103 SafeIfaceArray <IGuestSession> collSessions;
4104 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
4105 size_t cSessions = collSessions.size();
4106
4107 for (size_t i = 0; i < cSessions; i++)
4108 {
4109 ComPtr<IGuestSession> pSession = collSessions[i];
4110 Assert(!pSession.isNull());
4111
4112 ULONG uID; /* Session ID */
4113 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
4114 Bstr strName;
4115 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
4116 Utf8Str strNameUtf8(strName); /* Session name */
4117
4118 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
4119 {
4120 fSessionFound = uID == ulSessionID;
4121 }
4122 else /* ... or by naming pattern. */
4123 {
4124 if (RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()))
4125 fSessionFound = true;
4126 }
4127
4128 if (fSessionFound)
4129 {
4130 cSessionsHandled++;
4131
4132 Assert(!pSession.isNull());
4133 if (pCtx->cVerbose > 1)
4134 RTPrintf("Closing guest session ID=#%RU32 \"%s\" ...\n",
4135 uID, strNameUtf8.c_str());
4136 CHECK_ERROR_BREAK(pSession, Close());
4137 if (pCtx->cVerbose > 1)
4138 RTPrintf("Guest session successfully closed\n");
4139
4140 pSession.setNull();
4141 }
4142 }
4143
4144 if (!cSessionsHandled)
4145 {
4146 RTPrintf("No guest session(s) found\n");
4147 rc = E_ABORT; /* To set exit code accordingly. */
4148 }
4149
4150 } while (0);
4151
4152 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
4153}
4154
4155
4156static DECLCALLBACK(RTEXITCODE) gctlHandleWatch(PGCTLCMDCTX pCtx, int argc, char **argv)
4157{
4158 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
4159
4160 /*
4161 * Parse arguments.
4162 */
4163 static const RTGETOPTDEF s_aOptions[] =
4164 {
4165 GCTLCMD_COMMON_OPTION_DEFS()
4166 };
4167
4168 int ch;
4169 RTGETOPTUNION ValueUnion;
4170 RTGETOPTSTATE GetState;
4171 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
4172
4173 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
4174 {
4175 /* For options that require an argument, ValueUnion has received the value. */
4176 switch (ch)
4177 {
4178 GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion);
4179
4180 case VINF_GETOPT_NOT_OPTION:
4181 default:
4182 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_WATCH, ch, &ValueUnion);
4183 }
4184 }
4185
4186 /** @todo Specify categories to watch for. */
4187 /** @todo Specify a --timeout for waiting only for a certain amount of time? */
4188
4189 RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx);
4190 if (rcExit != RTEXITCODE_SUCCESS)
4191 return rcExit;
4192
4193 HRESULT rc;
4194
4195 try
4196 {
4197 ComObjPtr<GuestEventListenerImpl> pGuestListener;
4198 do
4199 {
4200 /* Listener creation. */
4201 pGuestListener.createObject();
4202 pGuestListener->init(new GuestEventListener());
4203
4204 /* Register for IGuest events. */
4205 ComPtr<IEventSource> es;
4206 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(EventSource)(es.asOutParam()));
4207 com::SafeArray<VBoxEventType_T> eventTypes;
4208 eventTypes.push_back(VBoxEventType_OnGuestSessionRegistered);
4209 /** @todo Also register for VBoxEventType_OnGuestUserStateChanged on demand? */
4210 CHECK_ERROR_BREAK(es, RegisterListener(pGuestListener, ComSafeArrayAsInParam(eventTypes),
4211 true /* Active listener */));
4212 /* Note: All other guest control events have to be registered
4213 * as their corresponding objects appear. */
4214
4215 } while (0);
4216
4217 if (pCtx->cVerbose > 1)
4218 RTPrintf("Waiting for events ...\n");
4219
4220 while (!g_fGuestCtrlCanceled)
4221 {
4222 /** @todo Timeout handling (see above)? */
4223 RTThreadSleep(10);
4224 }
4225
4226 if (pCtx->cVerbose > 1)
4227 RTPrintf("Signal caught, exiting ...\n");
4228
4229 if (!pGuestListener.isNull())
4230 {
4231 /* Guest callback unregistration. */
4232 ComPtr<IEventSource> pES;
4233 CHECK_ERROR(pCtx->pGuest, COMGETTER(EventSource)(pES.asOutParam()));
4234 if (!pES.isNull())
4235 CHECK_ERROR(pES, UnregisterListener(pGuestListener));
4236 pGuestListener.setNull();
4237 }
4238 }
4239 catch (std::bad_alloc &)
4240 {
4241 rc = E_OUTOFMEMORY;
4242 }
4243
4244 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
4245}
4246
4247/**
4248 * Access the guest control store.
4249 *
4250 * @returns program exit code.
4251 * @note see the command line API description for parameters
4252 */
4253RTEXITCODE handleGuestControl(HandlerArg *pArg)
4254{
4255 AssertPtr(pArg);
4256
4257#ifdef DEBUG_andy_disabled
4258 if (RT_FAILURE(tstTranslatePath()))
4259 return RTEXITCODE_FAILURE;
4260#endif
4261
4262 /*
4263 * Command definitions.
4264 */
4265 static const GCTLCMDDEF s_aCmdDefs[] =
4266 {
4267 { "run", gctlHandleRun, USAGE_GSTCTRL_RUN, 0, },
4268 { "start", gctlHandleStart, USAGE_GSTCTRL_START, 0, },
4269 { "copyfrom", gctlHandleCopyFrom, USAGE_GSTCTRL_COPYFROM, 0, },
4270 { "copyto", gctlHandleCopyTo, USAGE_GSTCTRL_COPYTO, 0, },
4271
4272 { "mkdir", handleCtrtMkDir, USAGE_GSTCTRL_MKDIR, 0, },
4273 { "md", handleCtrtMkDir, USAGE_GSTCTRL_MKDIR, 0, },
4274 { "createdirectory", handleCtrtMkDir, USAGE_GSTCTRL_MKDIR, 0, },
4275 { "createdir", handleCtrtMkDir, USAGE_GSTCTRL_MKDIR, 0, },
4276
4277 { "rmdir", gctlHandleRmDir, USAGE_GSTCTRL_RMDIR, 0, },
4278 { "removedir", gctlHandleRmDir, USAGE_GSTCTRL_RMDIR, 0, },
4279 { "removedirectory", gctlHandleRmDir, USAGE_GSTCTRL_RMDIR, 0, },
4280
4281 { "rm", gctlHandleRm, USAGE_GSTCTRL_RM, 0, },
4282 { "removefile", gctlHandleRm, USAGE_GSTCTRL_RM, 0, },
4283 { "erase", gctlHandleRm, USAGE_GSTCTRL_RM, 0, },
4284 { "del", gctlHandleRm, USAGE_GSTCTRL_RM, 0, },
4285 { "delete", gctlHandleRm, USAGE_GSTCTRL_RM, 0, },
4286
4287 { "mv", gctlHandleMv, USAGE_GSTCTRL_MV, 0, },
4288 { "move", gctlHandleMv, USAGE_GSTCTRL_MV, 0, },
4289 { "ren", gctlHandleMv, USAGE_GSTCTRL_MV, 0, },
4290 { "rename", gctlHandleMv, USAGE_GSTCTRL_MV, 0, },
4291
4292 { "mktemp", gctlHandleMkTemp, USAGE_GSTCTRL_MKTEMP, 0, },
4293 { "createtemp", gctlHandleMkTemp, USAGE_GSTCTRL_MKTEMP, 0, },
4294 { "createtemporary", gctlHandleMkTemp, USAGE_GSTCTRL_MKTEMP, 0, },
4295
4296 { "stat", gctlHandleStat, USAGE_GSTCTRL_STAT, 0, },
4297
4298 { "closeprocess", gctlHandleCloseProcess, USAGE_GSTCTRL_CLOSEPROCESS, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4299 { "closesession", gctlHandleCloseSession, USAGE_GSTCTRL_CLOSESESSION, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4300 { "list", gctlHandleList, USAGE_GSTCTRL_LIST, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4301 { "watch", gctlHandleWatch, USAGE_GSTCTRL_WATCH, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4302
4303 {"updateguestadditions",gctlHandleUpdateAdditions, USAGE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4304 { "updateadditions", gctlHandleUpdateAdditions, USAGE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4305 { "updatega", gctlHandleUpdateAdditions, USAGE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, },
4306 };
4307
4308 /*
4309 * VBoxManage guestcontrol [common-options] <VM> [common-options] <sub-command> ...
4310 *
4311 * Parse common options and VM name until we find a sub-command. Allowing
4312 * the user to put the user and password related options before the
4313 * sub-command makes it easier to edit the command line when doing several
4314 * operations with the same guest user account. (Accidentally, it also
4315 * makes the syntax diagram shorter and easier to read.)
4316 */
4317 GCTLCMDCTX CmdCtx;
4318 RTEXITCODE rcExit = gctrCmdCtxInit(&CmdCtx, pArg);
4319 if (rcExit == RTEXITCODE_SUCCESS)
4320 {
4321 static const RTGETOPTDEF s_CommonOptions[] = { GCTLCMD_COMMON_OPTION_DEFS() };
4322
4323 int ch;
4324 RTGETOPTUNION ValueUnion;
4325 RTGETOPTSTATE GetState;
4326 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_CommonOptions, RT_ELEMENTS(s_CommonOptions), 0, 0 /* No sorting! */);
4327
4328 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
4329 {
4330 switch (ch)
4331 {
4332 GCTLCMD_COMMON_OPTION_CASES(&CmdCtx, ch, &ValueUnion);
4333
4334 case VINF_GETOPT_NOT_OPTION:
4335 /* First comes the VM name or UUID. */
4336 if (!CmdCtx.pszVmNameOrUuid)
4337 CmdCtx.pszVmNameOrUuid = ValueUnion.psz;
4338 /*
4339 * The sub-command is next. Look it up and invoke it.
4340 * Note! Currently no warnings about user/password options (like we'll do later on)
4341 * for GCTLCMDCTX_F_SESSION_ANONYMOUS commands. No reason to be too pedantic.
4342 */
4343 else
4344 {
4345 const char *pszCmd = ValueUnion.psz;
4346 uint32_t iCmd;
4347 for (iCmd = 0; iCmd < RT_ELEMENTS(s_aCmdDefs); iCmd++)
4348 if (strcmp(s_aCmdDefs[iCmd].pszName, pszCmd) == 0)
4349 {
4350 CmdCtx.pCmdDef = &s_aCmdDefs[iCmd];
4351
4352 rcExit = s_aCmdDefs[iCmd].pfnHandler(&CmdCtx, pArg->argc - GetState.iNext + 1,
4353 &pArg->argv[GetState.iNext - 1]);
4354
4355 gctlCtxTerm(&CmdCtx);
4356 return rcExit;
4357 }
4358 return errorSyntax(USAGE_GUESTCONTROL, "Unknown sub-command: '%s'", pszCmd);
4359 }
4360 break;
4361
4362 default:
4363 return errorGetOpt(USAGE_GUESTCONTROL, ch, &ValueUnion);
4364 }
4365 }
4366 if (CmdCtx.pszVmNameOrUuid)
4367 rcExit = errorSyntax(USAGE_GUESTCONTROL, "Missing sub-command");
4368 else
4369 rcExit = errorSyntax(USAGE_GUESTCONTROL, "Missing VM name and sub-command");
4370 }
4371 return rcExit;
4372}
4373#endif /* !VBOX_ONLY_DOCS */
4374
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