VirtualBox

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

Last change on this file since 56927 was 56739, checked in by vboxsync, 9 years ago

FE/VBoxManage: Format string error found by Parfait

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