VirtualBox

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

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette