VirtualBox

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

Last change on this file since 69198 was 63567, checked in by vboxsync, 8 years ago

scm: cleaning up todos

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