VirtualBox

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

Last change on this file since 55746 was 55632, checked in by vboxsync, 10 years ago

build fix

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