VirtualBox

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

Last change on this file since 53404 was 52842, checked in by vboxsync, 10 years ago

VBoxManageGuestCtrl.cpp: Fixed ctrlCopyCreateSourceRoot().

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 142.5 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 52842 2014-09-24 13:44:42Z 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.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "VBoxManage.h"
23#include "VBoxManageGuestCtrl.h"
24
25#ifndef VBOX_ONLY_DOCS
26
27#include <VBox/com/array.h>
28#include <VBox/com/com.h>
29#include <VBox/com/ErrorInfo.h>
30#include <VBox/com/errorprint.h>
31#include <VBox/com/listeners.h>
32#include <VBox/com/NativeEventQueue.h>
33#include <VBox/com/string.h>
34#include <VBox/com/VirtualBox.h>
35
36#include <VBox/err.h>
37#include <VBox/log.h>
38
39#include <iprt/asm.h>
40#include <iprt/dir.h>
41#include <iprt/file.h>
42#include <iprt/isofs.h>
43#include <iprt/getopt.h>
44#include <iprt/list.h>
45#include <iprt/path.h>
46#include <iprt/process.h> /* For RTProcSelf(). */
47#include <iprt/thread.h>
48
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] [--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 { "--wait-exit", GETOPTDEF_EXEC_WAITFOREXIT, RTGETOPT_REQ_NOTHING },
1157 { "--wait-stdout", GETOPTDEF_EXEC_WAITFORSTDOUT, RTGETOPT_REQ_NOTHING },
1158 { "--wait-stderr", GETOPTDEF_EXEC_WAITFORSTDERR, RTGETOPT_REQ_NOTHING }
1159 };
1160
1161#ifdef DEBUG_andy
1162 RTPrintf("first=%d\n", pCtx->iFirstArgc);
1163 for (int i=0; i<pCtx->iArgc;i++)
1164 RTPrintf("\targv[%d]=%s\n", i, pCtx->ppaArgv[i]);
1165#endif
1166
1167 int ch;
1168 RTGETOPTUNION ValueUnion;
1169 RTGETOPTSTATE GetState;
1170 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv, s_aOptions, RT_ELEMENTS(s_aOptions),
1171 pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1172
1173 Utf8Str strCmd;
1174 com::SafeArray<ProcessCreateFlag_T> aCreateFlags;
1175 com::SafeArray<ProcessWaitForFlag_T> aWaitFlags;
1176 com::SafeArray<IN_BSTR> aArgs;
1177 com::SafeArray<IN_BSTR> aEnv;
1178 RTMSINTERVAL cMsTimeout = 0;
1179 OUTPUTTYPE eOutputType = OUTPUTTYPE_UNDEFINED;
1180 bool fDetached = true;
1181 int vrc = VINF_SUCCESS;
1182
1183 try
1184 {
1185 /* Wait for process start in any case. This is useful for scripting VBoxManage
1186 * when relying on its overall exit code. */
1187 aWaitFlags.push_back(ProcessWaitForFlag_Start);
1188
1189 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1190 && RT_SUCCESS(vrc))
1191 {
1192 /* For options that require an argument, ValueUnion has received the value. */
1193 switch (ch)
1194 {
1195 case GETOPTDEF_EXEC_DOS2UNIX:
1196 if (eOutputType != OUTPUTTYPE_UNDEFINED)
1197 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_EXEC,
1198 "More than one output type (dos2unix/unix2dos) specified!");
1199 eOutputType = OUTPUTTYPE_DOS2UNIX;
1200 break;
1201
1202 case 'e': /* Environment */
1203 {
1204 char **papszArg;
1205 int cArgs;
1206
1207 vrc = RTGetOptArgvFromString(&papszArg, &cArgs, ValueUnion.psz, NULL);
1208 if (RT_FAILURE(vrc))
1209 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_EXEC,
1210 "Failed to parse environment value, rc=%Rrc", vrc);
1211 for (int j = 0; j < cArgs; j++)
1212 aEnv.push_back(Bstr(papszArg[j]).raw());
1213
1214 RTGetOptArgvFree(papszArg);
1215 break;
1216 }
1217
1218 case GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES:
1219 aCreateFlags.push_back(ProcessCreateFlag_IgnoreOrphanedProcesses);
1220 break;
1221
1222 case GETOPTDEF_EXEC_NO_PROFILE:
1223 aCreateFlags.push_back(ProcessCreateFlag_NoProfile);
1224 break;
1225
1226 case 'i':
1227 strCmd = ValueUnion.psz;
1228 break;
1229
1230 /** @todo Add a hidden flag. */
1231
1232 case 't': /* Timeout */
1233 cMsTimeout = ValueUnion.u32;
1234 break;
1235
1236 case GETOPTDEF_EXEC_UNIX2DOS:
1237 if (eOutputType != OUTPUTTYPE_UNDEFINED)
1238 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_EXEC,
1239 "More than one output type (dos2unix/unix2dos) specified!");
1240 eOutputType = OUTPUTTYPE_UNIX2DOS;
1241 break;
1242
1243 case GETOPTDEF_EXEC_WAITFOREXIT:
1244 aWaitFlags.push_back(ProcessWaitForFlag_Terminate);
1245 fDetached = false;
1246 break;
1247
1248 case GETOPTDEF_EXEC_WAITFORSTDOUT:
1249 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut);
1250 aWaitFlags.push_back(ProcessWaitForFlag_StdOut);
1251 fDetached = false;
1252 break;
1253
1254 case GETOPTDEF_EXEC_WAITFORSTDERR:
1255 aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr);
1256 aWaitFlags.push_back(ProcessWaitForFlag_StdErr);
1257 fDetached = false;
1258 break;
1259
1260 case VINF_GETOPT_NOT_OPTION:
1261 if (aArgs.size() == 0 && strCmd.isEmpty())
1262 strCmd = ValueUnion.psz;
1263 else
1264 aArgs.push_back(Bstr(ValueUnion.psz).raw());
1265 break;
1266
1267 default:
1268 /* Note: Necessary for handling non-options (after --) which
1269 * contain a single dash, e.g. "-- foo.exe -s". */
1270 if (GetState.argc == GetState.iNext)
1271 aArgs.push_back(Bstr(ValueUnion.psz).raw());
1272 else
1273 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_EXEC, ch, &ValueUnion);
1274 break;
1275
1276 } /* switch */
1277 } /* while RTGetOpt */
1278 }
1279 catch (std::bad_alloc &)
1280 {
1281 vrc = VERR_NO_MEMORY;
1282 }
1283
1284 if (RT_FAILURE(vrc))
1285 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize, rc=%Rrc\n", vrc);
1286
1287 if (strCmd.isEmpty())
1288 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_EXEC,
1289 "No command to execute specified!");
1290
1291 /** @todo Any output conversion not supported yet! */
1292 if (eOutputType != OUTPUTTYPE_UNDEFINED)
1293 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_EXEC,
1294 "Output conversion not implemented yet!");
1295
1296 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1297 HRESULT rc;
1298
1299 try
1300 {
1301 do
1302 {
1303 /* Adjust process creation flags if we don't want to wait for process termination. */
1304 if (fDetached)
1305 aCreateFlags.push_back(ProcessCreateFlag_WaitForProcessStartOnly);
1306
1307 /* Get current time stamp to later calculate rest of timeout left. */
1308 uint64_t u64StartMS = RTTimeMilliTS();
1309
1310 if (pCtx->fVerbose)
1311 {
1312 if (cMsTimeout == 0)
1313 RTPrintf("Starting guest process ...\n");
1314 else
1315 RTPrintf("Starting guest process (within %ums)\n", cMsTimeout);
1316 }
1317
1318 /*
1319 * Execute the process.
1320 */
1321 ComPtr<IGuestProcess> pProcess;
1322 CHECK_ERROR_BREAK(pCtx->pGuestSession, ProcessCreate(Bstr(strCmd).raw(),
1323 ComSafeArrayAsInParam(aArgs),
1324 ComSafeArrayAsInParam(aEnv),
1325 ComSafeArrayAsInParam(aCreateFlags),
1326 ctrlExecGetRemainingTime(u64StartMS, cMsTimeout),
1327 pProcess.asOutParam()));
1328
1329 /*
1330 * Explicitly wait for the guest process to be in a started
1331 * state.
1332 */
1333 com::SafeArray<ProcessWaitForFlag_T> aWaitStartFlags;
1334 aWaitStartFlags.push_back(ProcessWaitForFlag_Start);
1335 ProcessWaitResult_T waitResult;
1336 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitStartFlags),
1337 ctrlExecGetRemainingTime(u64StartMS, cMsTimeout), &waitResult));
1338 bool fCompleted = false;
1339
1340 ULONG uPID = 0;
1341 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
1342 if (!fDetached && pCtx->fVerbose)
1343 {
1344 RTPrintf("Process '%s' (PID %RU32) started\n",
1345 strCmd.c_str(), uPID);
1346 }
1347 else if (fDetached) /** @todo Introduce a --quiet option for not printing this. */
1348 {
1349 /* Just print plain PID to make it easier for scripts
1350 * invoking VBoxManage. */
1351 RTPrintf("[%RU32 - Session %RU32]\n", uPID, pCtx->uSessionID);
1352 }
1353
1354 vrc = RTStrmSetMode(g_pStdOut, 1 /* Binary mode */, -1 /* Code set, unchanged */);
1355 if (RT_FAILURE(vrc))
1356 RTMsgError("Unable to set stdout's binary mode, rc=%Rrc\n", vrc);
1357 vrc = RTStrmSetMode(g_pStdErr, 1 /* Binary mode */, -1 /* Code set, unchanged */);
1358 if (RT_FAILURE(vrc))
1359 RTMsgError("Unable to set stderr's binary mode, rc=%Rrc\n", vrc);
1360
1361 /* Wait for process to exit ... */
1362 RTMSINTERVAL cMsTimeLeft = 1; /* Will be calculated. */
1363 bool fReadStdOut, fReadStdErr;
1364 fReadStdOut = fReadStdErr = false;
1365
1366 while ( !fCompleted
1367 && !fDetached
1368 && cMsTimeLeft != 0)
1369 {
1370 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
1371 CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitFlags),
1372 500 /* ms */, &waitResult));
1373 switch (waitResult)
1374 {
1375 case ProcessWaitResult_Start:
1376 {
1377 /* We're done here if we don't want to wait for termination. */
1378 if (fDetached)
1379 fCompleted = true;
1380
1381 break;
1382 }
1383 case ProcessWaitResult_StdOut:
1384 fReadStdOut = true;
1385 break;
1386 case ProcessWaitResult_StdErr:
1387 fReadStdErr = true;
1388 break;
1389 case ProcessWaitResult_Terminate:
1390 if (pCtx->fVerbose)
1391 RTPrintf("Process terminated\n");
1392 /* Process terminated, we're done. */
1393 fCompleted = true;
1394 break;
1395 case ProcessWaitResult_WaitFlagNotSupported:
1396 {
1397 /* The guest does not support waiting for stdout/err, so
1398 * yield to reduce the CPU load due to busy waiting. */
1399 RTThreadYield(); /* Optional, don't check rc. */
1400
1401 /* Try both, stdout + stderr. */
1402 fReadStdOut = fReadStdErr = true;
1403 break;
1404 }
1405 case ProcessWaitResult_Timeout:
1406 /* Fall through is intentional. */
1407 default:
1408 /* Ignore all other results, let the timeout expire */
1409 break;
1410 }
1411
1412 if (g_fGuestCtrlCanceled)
1413 break;
1414
1415 if (fReadStdOut) /* Do we need to fetch stdout data? */
1416 {
1417 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
1418 vrc = ctrlExecPrintOutput(pProcess, g_pStdOut,
1419 1 /* StdOut */, cMsTimeLeft);
1420 fReadStdOut = false;
1421 }
1422
1423 if (fReadStdErr) /* Do we need to fetch stdout data? */
1424 {
1425 cMsTimeLeft = ctrlExecGetRemainingTime(u64StartMS, cMsTimeout);
1426 vrc = ctrlExecPrintOutput(pProcess, g_pStdErr,
1427 2 /* StdErr */, cMsTimeLeft);
1428 fReadStdErr = false;
1429 }
1430
1431 if ( RT_FAILURE(vrc)
1432 || g_fGuestCtrlCanceled)
1433 break;
1434
1435 /* Did we run out of time? */
1436 if ( cMsTimeout
1437 && RTTimeMilliTS() - u64StartMS > cMsTimeout)
1438 break;
1439
1440 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
1441
1442 } /* while */
1443
1444 if (!fDetached)
1445 {
1446 /* Report status back to the user. */
1447 if ( fCompleted
1448 && !g_fGuestCtrlCanceled)
1449 {
1450
1451 {
1452 ProcessStatus_T procStatus;
1453 CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&procStatus));
1454 if ( procStatus == ProcessStatus_TerminatedNormally
1455 || procStatus == ProcessStatus_TerminatedAbnormally
1456 || procStatus == ProcessStatus_TerminatedSignal)
1457 {
1458 LONG exitCode;
1459 CHECK_ERROR_BREAK(pProcess, COMGETTER(ExitCode)(&exitCode));
1460 if (pCtx->fVerbose)
1461 RTPrintf("Exit code=%u (Status=%u [%s])\n",
1462 exitCode, procStatus, ctrlProcessStatusToText(procStatus));
1463
1464 rcExit = (RTEXITCODE)ctrlExecProcessStatusToExitCode(procStatus, exitCode);
1465 }
1466 else if (pCtx->fVerbose)
1467 RTPrintf("Process now is in status [%s]\n", ctrlProcessStatusToText(procStatus));
1468 }
1469 }
1470 else
1471 {
1472 if (pCtx->fVerbose)
1473 RTPrintf("Process execution aborted!\n");
1474
1475 rcExit = (RTEXITCODE)EXITCODEEXEC_TERM_ABEND;
1476 }
1477 }
1478
1479 } while (0);
1480 }
1481 catch (std::bad_alloc)
1482 {
1483 rc = E_OUTOFMEMORY;
1484 }
1485
1486 /*
1487 * Decide what to do with the guest session. If we started a
1488 * detached guest process (that is, without waiting for it to exit),
1489 * don't close the guest session it is part of.
1490 */
1491 bool fCloseSession = false;
1492 if (SUCCEEDED(rc))
1493 {
1494 /*
1495 * Only close the guest session if we waited for the guest
1496 * process to exit. Otherwise we wouldn't have any chance to
1497 * access and/or kill detached guest process lateron.
1498 */
1499 fCloseSession = !fDetached;
1500
1501 /*
1502 * If execution was aborted from the host side (signal handler),
1503 * close the guest session in any case.
1504 */
1505 if (g_fGuestCtrlCanceled)
1506 fCloseSession = true;
1507 }
1508 else /* Close session on error. */
1509 fCloseSession = true;
1510
1511 if (!fCloseSession)
1512 pCtx->uFlags |= CTLCMDCTX_FLAGS_SESSION_DETACH;
1513
1514 if ( rcExit == RTEXITCODE_SUCCESS
1515 && FAILED(rc))
1516 {
1517 /* Make sure an appropriate exit code is set on error. */
1518 rcExit = RTEXITCODE_FAILURE;
1519 }
1520
1521 return rcExit;
1522}
1523
1524/**
1525 * Creates a copy context structure which then can be used with various
1526 * guest control copy functions. Needs to be free'd with ctrlCopyContextFree().
1527 *
1528 * @return IPRT status code.
1529 * @param pCtx Pointer to command context.
1530 * @param fDryRun Flag indicating if we want to run a dry run only.
1531 * @param fHostToGuest Flag indicating if we want to copy from host to guest
1532 * or vice versa.
1533 * @param strSessionName Session name (only for identification purposes).
1534 * @param ppContext Pointer which receives the allocated copy context.
1535 */
1536static int ctrlCopyContextCreate(PGCTLCMDCTX pCtx, bool fDryRun, bool fHostToGuest,
1537 const Utf8Str &strSessionName,
1538 PCOPYCONTEXT *ppContext)
1539{
1540 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1541
1542 int vrc = VINF_SUCCESS;
1543 try
1544 {
1545 PCOPYCONTEXT pContext = new COPYCONTEXT();
1546
1547 pContext->pCmdCtx = pCtx;
1548 pContext->fDryRun = fDryRun;
1549 pContext->fHostToGuest = fHostToGuest;
1550
1551 *ppContext = pContext;
1552 }
1553 catch (std::bad_alloc)
1554 {
1555 vrc = VERR_NO_MEMORY;
1556 }
1557
1558 return vrc;
1559}
1560
1561/**
1562 * Frees are previously allocated copy context structure.
1563 *
1564 * @param pContext Pointer to copy context to free.
1565 */
1566static void ctrlCopyContextFree(PCOPYCONTEXT pContext)
1567{
1568 if (pContext)
1569 delete pContext;
1570}
1571
1572/**
1573 * Translates a source path to a destination path (can be both sides,
1574 * either host or guest). The source root is needed to determine the start
1575 * of the relative source path which also needs to present in the destination
1576 * path.
1577 *
1578 * @return IPRT status code.
1579 * @param pszSourceRoot Source root path. No trailing directory slash!
1580 * @param pszSource Actual source to transform. Must begin with
1581 * the source root path!
1582 * @param pszDest Destination path.
1583 * @param ppszTranslated Pointer to the allocated, translated destination
1584 * path. Must be free'd with RTStrFree().
1585 */
1586static int ctrlCopyTranslatePath(const char *pszSourceRoot, const char *pszSource,
1587 const char *pszDest, char **ppszTranslated)
1588{
1589 AssertPtrReturn(pszSourceRoot, VERR_INVALID_POINTER);
1590 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1591 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1592 AssertPtrReturn(ppszTranslated, VERR_INVALID_POINTER);
1593#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} */
1594 AssertReturn(RTPathStartsWith(pszSource, pszSourceRoot), VERR_INVALID_PARAMETER);
1595#endif
1596
1597 /* Construct the relative dest destination path by "subtracting" the
1598 * source from the source root, e.g.
1599 *
1600 * source root path = "e:\foo\", source = "e:\foo\bar"
1601 * dest = "d:\baz\"
1602 * translated = "d:\baz\bar\"
1603 */
1604 char szTranslated[RTPATH_MAX];
1605 size_t srcOff = strlen(pszSourceRoot);
1606 AssertReturn(srcOff, VERR_INVALID_PARAMETER);
1607
1608 char *pszDestPath = RTStrDup(pszDest);
1609 AssertPtrReturn(pszDestPath, VERR_NO_MEMORY);
1610
1611 int vrc;
1612 if (!RTPathFilename(pszDestPath))
1613 {
1614 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1615 pszDestPath, &pszSource[srcOff]);
1616 }
1617 else
1618 {
1619 char *pszDestFileName = RTStrDup(RTPathFilename(pszDestPath));
1620 if (pszDestFileName)
1621 {
1622 RTPathStripFilename(pszDestPath);
1623 vrc = RTPathJoin(szTranslated, sizeof(szTranslated),
1624 pszDestPath, pszDestFileName);
1625 RTStrFree(pszDestFileName);
1626 }
1627 else
1628 vrc = VERR_NO_MEMORY;
1629 }
1630 RTStrFree(pszDestPath);
1631
1632 if (RT_SUCCESS(vrc))
1633 {
1634 *ppszTranslated = RTStrDup(szTranslated);
1635#if 0
1636 RTPrintf("Root: %s, Source: %s, Dest: %s, Translated: %s\n",
1637 pszSourceRoot, pszSource, pszDest, *ppszTranslated);
1638#endif
1639 }
1640 return vrc;
1641}
1642
1643#ifdef DEBUG_andy
1644static int tstTranslatePath()
1645{
1646 RTAssertSetMayPanic(false /* Do not freak out, please. */);
1647
1648 static struct
1649 {
1650 const char *pszSourceRoot;
1651 const char *pszSource;
1652 const char *pszDest;
1653 const char *pszTranslated;
1654 int iResult;
1655 } aTests[] =
1656 {
1657 /* Invalid stuff. */
1658 { NULL, NULL, NULL, NULL, VERR_INVALID_POINTER },
1659#ifdef RT_OS_WINDOWS
1660 /* Windows paths. */
1661 { "c:\\foo", "c:\\foo\\bar.txt", "c:\\test", "c:\\test\\bar.txt", VINF_SUCCESS },
1662 { "c:\\foo", "c:\\foo\\baz\\bar.txt", "c:\\test", "c:\\test\\baz\\bar.txt", VINF_SUCCESS },
1663#else /* RT_OS_WINDOWS */
1664 { "/home/test/foo", "/home/test/foo/bar.txt", "/opt/test", "/opt/test/bar.txt", VINF_SUCCESS },
1665 { "/home/test/foo", "/home/test/foo/baz/bar.txt", "/opt/test", "/opt/test/baz/bar.txt", VINF_SUCCESS },
1666#endif /* !RT_OS_WINDOWS */
1667 /* Mixed paths*/
1668 /** @todo */
1669 { NULL }
1670 };
1671
1672 size_t iTest = 0;
1673 for (iTest; iTest < RT_ELEMENTS(aTests); iTest++)
1674 {
1675 RTPrintf("=> Test %d\n", iTest);
1676 RTPrintf("\tSourceRoot=%s, Source=%s, Dest=%s\n",
1677 aTests[iTest].pszSourceRoot, aTests[iTest].pszSource, aTests[iTest].pszDest);
1678
1679 char *pszTranslated = NULL;
1680 int iResult = ctrlCopyTranslatePath(aTests[iTest].pszSourceRoot, aTests[iTest].pszSource,
1681 aTests[iTest].pszDest, &pszTranslated);
1682 if (iResult != aTests[iTest].iResult)
1683 {
1684 RTPrintf("\tReturned %Rrc, expected %Rrc\n",
1685 iResult, aTests[iTest].iResult);
1686 }
1687 else if ( pszTranslated
1688 && strcmp(pszTranslated, aTests[iTest].pszTranslated))
1689 {
1690 RTPrintf("\tReturned translated path %s, expected %s\n",
1691 pszTranslated, aTests[iTest].pszTranslated);
1692 }
1693
1694 if (pszTranslated)
1695 {
1696 RTPrintf("\tTranslated=%s\n", pszTranslated);
1697 RTStrFree(pszTranslated);
1698 }
1699 }
1700
1701 return VINF_SUCCESS; /* @todo */
1702}
1703#endif
1704
1705/**
1706 * Creates a directory on the destination, based on the current copy
1707 * context.
1708 *
1709 * @return IPRT status code.
1710 * @param pContext Pointer to current copy control context.
1711 * @param pszDir Directory to create.
1712 */
1713static int ctrlCopyDirCreate(PCOPYCONTEXT pContext, const char *pszDir)
1714{
1715 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1716 AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
1717
1718 bool fDirExists;
1719 int vrc = ctrlCopyDirExists(pContext, pContext->fHostToGuest, pszDir, &fDirExists);
1720 if ( RT_SUCCESS(vrc)
1721 && fDirExists)
1722 {
1723 if (pContext->pCmdCtx->fVerbose)
1724 RTPrintf("Directory \"%s\" already exists\n", pszDir);
1725 return VINF_SUCCESS;
1726 }
1727
1728 /* If querying for a directory existence fails there's no point of even trying
1729 * to create such a directory. */
1730 if (RT_FAILURE(vrc))
1731 return vrc;
1732
1733 if (pContext->pCmdCtx->fVerbose)
1734 RTPrintf("Creating directory \"%s\" ...\n", pszDir);
1735
1736 if (pContext->fDryRun)
1737 return VINF_SUCCESS;
1738
1739 if (pContext->fHostToGuest) /* We want to create directories on the guest. */
1740 {
1741 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
1742 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
1743 HRESULT rc = pContext->pCmdCtx->pGuestSession->DirectoryCreate(Bstr(pszDir).raw(),
1744 0700, ComSafeArrayAsInParam(dirCreateFlags));
1745 if (FAILED(rc))
1746 vrc = ctrlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
1747 }
1748 else /* ... or on the host. */
1749 {
1750 vrc = RTDirCreateFullPath(pszDir, 0700);
1751 if (vrc == VERR_ALREADY_EXISTS)
1752 vrc = VINF_SUCCESS;
1753 }
1754 return vrc;
1755}
1756
1757/**
1758 * Checks whether a specific host/guest directory exists.
1759 *
1760 * @return IPRT status code.
1761 * @param pContext Pointer to current copy control context.
1762 * @param fOnGuest true if directory needs to be checked on the guest
1763 * or false if on the host.
1764 * @param pszDir Actual directory to check.
1765 * @param fExists Pointer which receives the result if the
1766 * given directory exists or not.
1767 */
1768static int ctrlCopyDirExists(PCOPYCONTEXT pContext, bool fOnGuest,
1769 const char *pszDir, bool *fExists)
1770{
1771 AssertPtrReturn(pContext, false);
1772 AssertPtrReturn(pszDir, false);
1773 AssertPtrReturn(fExists, false);
1774
1775 int vrc = VINF_SUCCESS;
1776 if (fOnGuest)
1777 {
1778 BOOL fDirExists = FALSE;
1779 HRESULT rc = pContext->pCmdCtx->pGuestSession->DirectoryExists(Bstr(pszDir).raw(), &fDirExists);
1780 if (FAILED(rc))
1781 vrc = ctrlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
1782 else
1783 *fExists = fDirExists ? true : false;
1784 }
1785 else
1786 *fExists = RTDirExists(pszDir);
1787 return vrc;
1788}
1789
1790/**
1791 * Checks whether a specific directory exists on the destination, based
1792 * on the current copy context.
1793 *
1794 * @return IPRT status code.
1795 * @param pContext Pointer to current copy control context.
1796 * @param pszDir Actual directory to check.
1797 * @param fExists Pointer which receives the result if the
1798 * given directory exists or not.
1799 */
1800static int ctrlCopyDirExistsOnDest(PCOPYCONTEXT pContext, const char *pszDir,
1801 bool *fExists)
1802{
1803 return ctrlCopyDirExists(pContext, pContext->fHostToGuest,
1804 pszDir, fExists);
1805}
1806
1807/**
1808 * Checks whether a specific directory exists on the source, based
1809 * on the current copy context.
1810 *
1811 * @return IPRT status code.
1812 * @param pContext Pointer to current copy control context.
1813 * @param pszDir Actual directory to check.
1814 * @param fExists Pointer which receives the result if the
1815 * given directory exists or not.
1816 */
1817static int ctrlCopyDirExistsOnSource(PCOPYCONTEXT pContext, const char *pszDir,
1818 bool *fExists)
1819{
1820 return ctrlCopyDirExists(pContext, !pContext->fHostToGuest,
1821 pszDir, fExists);
1822}
1823
1824/**
1825 * Checks whether a specific host/guest file exists.
1826 *
1827 * @return IPRT status code.
1828 * @param pContext Pointer to current copy control context.
1829 * @param bGuest true if file needs to be checked on the guest
1830 * or false if on the host.
1831 * @param pszFile Actual file to check.
1832 * @param fExists Pointer which receives the result if the
1833 * given file exists or not.
1834 */
1835static int ctrlCopyFileExists(PCOPYCONTEXT pContext, bool bOnGuest,
1836 const char *pszFile, bool *fExists)
1837{
1838 AssertPtrReturn(pContext, false);
1839 AssertPtrReturn(pszFile, false);
1840 AssertPtrReturn(fExists, false);
1841
1842 int vrc = VINF_SUCCESS;
1843 if (bOnGuest)
1844 {
1845 BOOL fFileExists = FALSE;
1846 HRESULT rc = pContext->pCmdCtx->pGuestSession->FileExists(Bstr(pszFile).raw(), &fFileExists);
1847 if (FAILED(rc))
1848 vrc = ctrlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
1849 else
1850 *fExists = fFileExists ? true : false;
1851 }
1852 else
1853 *fExists = RTFileExists(pszFile);
1854 return vrc;
1855}
1856
1857/**
1858 * Checks whether a specific file exists on the destination, based on the
1859 * current copy context.
1860 *
1861 * @return IPRT status code.
1862 * @param pContext Pointer to current copy control context.
1863 * @param pszFile Actual file to check.
1864 * @param fExists Pointer which receives the result if the
1865 * given file exists or not.
1866 */
1867static int ctrlCopyFileExistsOnDest(PCOPYCONTEXT pContext, const char *pszFile,
1868 bool *fExists)
1869{
1870 return ctrlCopyFileExists(pContext, pContext->fHostToGuest,
1871 pszFile, fExists);
1872}
1873
1874/**
1875 * Checks whether a specific file exists on the source, based on the
1876 * current copy context.
1877 *
1878 * @return IPRT status code.
1879 * @param pContext Pointer to current copy control context.
1880 * @param pszFile Actual file to check.
1881 * @param fExists Pointer which receives the result if the
1882 * given file exists or not.
1883 */
1884static int ctrlCopyFileExistsOnSource(PCOPYCONTEXT pContext, const char *pszFile,
1885 bool *fExists)
1886{
1887 return ctrlCopyFileExists(pContext, !pContext->fHostToGuest,
1888 pszFile, fExists);
1889}
1890
1891/**
1892 * Copies a source file to the destination.
1893 *
1894 * @return IPRT status code.
1895 * @param pContext Pointer to current copy control context.
1896 * @param pszFileSource Source file to copy to the destination.
1897 * @param pszFileDest Name of copied file on the destination.
1898 * @param fFlags Copy flags. No supported at the moment and needs
1899 * to be set to 0.
1900 */
1901static int ctrlCopyFileToDest(PCOPYCONTEXT pContext, const char *pszFileSource,
1902 const char *pszFileDest, uint32_t fFlags)
1903{
1904 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1905 AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
1906 AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
1907 AssertReturn(!fFlags, VERR_INVALID_POINTER); /* No flags supported yet. */
1908
1909 if (pContext->pCmdCtx->fVerbose)
1910 RTPrintf("Copying \"%s\" to \"%s\" ...\n",
1911 pszFileSource, pszFileDest);
1912
1913 if (pContext->fDryRun)
1914 return VINF_SUCCESS;
1915
1916 int vrc = VINF_SUCCESS;
1917 ComPtr<IProgress> pProgress;
1918 HRESULT rc;
1919 if (pContext->fHostToGuest)
1920 {
1921 SafeArray<CopyFileFlag_T> copyFlags;
1922 rc = pContext->pCmdCtx->pGuestSession->CopyTo(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1923 ComSafeArrayAsInParam(copyFlags),
1924 pProgress.asOutParam());
1925 }
1926 else
1927 {
1928 SafeArray<CopyFileFlag_T> copyFlags;
1929 rc = pContext->pCmdCtx->pGuestSession->CopyFrom(Bstr(pszFileSource).raw(), Bstr(pszFileDest).raw(),
1930 ComSafeArrayAsInParam(copyFlags),
1931 pProgress.asOutParam());
1932 }
1933
1934 if (FAILED(rc))
1935 {
1936 vrc = ctrlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
1937 }
1938 else
1939 {
1940 if (pContext->pCmdCtx->fVerbose)
1941 rc = showProgress(pProgress);
1942 else
1943 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
1944 if (SUCCEEDED(rc))
1945 CHECK_PROGRESS_ERROR(pProgress, ("File copy failed"));
1946 vrc = ctrlPrintProgressError(pProgress);
1947 }
1948
1949 return vrc;
1950}
1951
1952/**
1953 * Copys a directory (tree) from host to the guest.
1954 *
1955 * @return IPRT status code.
1956 * @param pContext Pointer to current copy control context.
1957 * @param pszSource Source directory on the host to copy to the guest.
1958 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
1959 * @param pszDest Destination directory on the guest.
1960 * @param fFlags Copy flags, such as recursive copying.
1961 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
1962 * is needed for recursion.
1963 */
1964static int ctrlCopyDirToGuest(PCOPYCONTEXT pContext,
1965 const char *pszSource, const char *pszFilter,
1966 const char *pszDest, uint32_t fFlags,
1967 const char *pszSubDir /* For recursion. */)
1968{
1969 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
1970 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
1971 /* Filter is optional. */
1972 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
1973 /* Sub directory is optional. */
1974
1975 /*
1976 * Construct current path.
1977 */
1978 char szCurDir[RTPATH_MAX];
1979 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
1980 if (RT_SUCCESS(vrc) && pszSubDir)
1981 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
1982
1983 if (pContext->pCmdCtx->fVerbose)
1984 RTPrintf("Processing host directory: %s\n", szCurDir);
1985
1986 /* Flag indicating whether the current directory was created on the
1987 * target or not. */
1988 bool fDirCreated = false;
1989
1990 /*
1991 * Open directory without a filter - RTDirOpenFiltered unfortunately
1992 * cannot handle sub directories so we have to do the filtering ourselves.
1993 */
1994 PRTDIR pDir = NULL;
1995 if (RT_SUCCESS(vrc))
1996 {
1997 vrc = RTDirOpen(&pDir, szCurDir);
1998 if (RT_FAILURE(vrc))
1999 pDir = NULL;
2000 }
2001 if (RT_SUCCESS(vrc))
2002 {
2003 /*
2004 * Enumerate the directory tree.
2005 */
2006 while (RT_SUCCESS(vrc))
2007 {
2008 RTDIRENTRY DirEntry;
2009 vrc = RTDirRead(pDir, &DirEntry, NULL);
2010 if (RT_FAILURE(vrc))
2011 {
2012 if (vrc == VERR_NO_MORE_FILES)
2013 vrc = VINF_SUCCESS;
2014 break;
2015 }
2016 /** @todo r=bird: This ain't gonna work on most UNIX file systems because
2017 * enmType is RTDIRENTRYTYPE_UNKNOWN. This is clearly documented in
2018 * RTDIRENTRY::enmType. For trunk, RTDirQueryUnknownType can be used. */
2019 switch (DirEntry.enmType)
2020 {
2021 case RTDIRENTRYTYPE_DIRECTORY:
2022 {
2023 /* Skip "." and ".." entries. */
2024 if ( !strcmp(DirEntry.szName, ".")
2025 || !strcmp(DirEntry.szName, ".."))
2026 break;
2027
2028 if (pContext->pCmdCtx->fVerbose)
2029 RTPrintf("Directory: %s\n", DirEntry.szName);
2030
2031 if (fFlags & CopyFileFlag_Recursive)
2032 {
2033 char *pszNewSub = NULL;
2034 if (pszSubDir)
2035 pszNewSub = RTPathJoinA(pszSubDir, DirEntry.szName);
2036 else
2037 {
2038 pszNewSub = RTStrDup(DirEntry.szName);
2039 RTPathStripTrailingSlash(pszNewSub);
2040 }
2041
2042 if (pszNewSub)
2043 {
2044 vrc = ctrlCopyDirToGuest(pContext,
2045 pszSource, pszFilter,
2046 pszDest, fFlags, pszNewSub);
2047 RTStrFree(pszNewSub);
2048 }
2049 else
2050 vrc = VERR_NO_MEMORY;
2051 }
2052 break;
2053 }
2054
2055 case RTDIRENTRYTYPE_SYMLINK:
2056 if ( (fFlags & CopyFileFlag_Recursive)
2057 && (fFlags & CopyFileFlag_FollowLinks))
2058 {
2059 /* Fall through to next case is intentional. */
2060 }
2061 else
2062 break;
2063
2064 case RTDIRENTRYTYPE_FILE:
2065 {
2066 if ( pszFilter
2067 && !RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
2068 {
2069 break; /* Filter does not match. */
2070 }
2071
2072 if (pContext->pCmdCtx->fVerbose)
2073 RTPrintf("File: %s\n", DirEntry.szName);
2074
2075 if (!fDirCreated)
2076 {
2077 char *pszDestDir;
2078 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
2079 pszDest, &pszDestDir);
2080 if (RT_SUCCESS(vrc))
2081 {
2082 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
2083 RTStrFree(pszDestDir);
2084
2085 fDirCreated = true;
2086 }
2087 }
2088
2089 if (RT_SUCCESS(vrc))
2090 {
2091 char *pszFileSource = RTPathJoinA(szCurDir, DirEntry.szName);
2092 if (pszFileSource)
2093 {
2094 char *pszFileDest;
2095 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
2096 pszDest, &pszFileDest);
2097 if (RT_SUCCESS(vrc))
2098 {
2099 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
2100 pszFileDest, 0 /* Flags */);
2101 RTStrFree(pszFileDest);
2102 }
2103 RTStrFree(pszFileSource);
2104 }
2105 }
2106 break;
2107 }
2108
2109 default:
2110 break;
2111 }
2112 if (RT_FAILURE(vrc))
2113 break;
2114 }
2115
2116 RTDirClose(pDir);
2117 }
2118 return vrc;
2119}
2120
2121/**
2122 * Copys a directory (tree) from guest to the host.
2123 *
2124 * @return IPRT status code.
2125 * @param pContext Pointer to current copy control context.
2126 * @param pszSource Source directory on the guest to copy to the host.
2127 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
2128 * @param pszDest Destination directory on the host.
2129 * @param fFlags Copy flags, such as recursive copying.
2130 * @param pszSubDir Current sub directory to handle. Needs to NULL and only
2131 * is needed for recursion.
2132 */
2133static int ctrlCopyDirToHost(PCOPYCONTEXT pContext,
2134 const char *pszSource, const char *pszFilter,
2135 const char *pszDest, uint32_t fFlags,
2136 const char *pszSubDir /* For recursion. */)
2137{
2138 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
2139 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
2140 /* Filter is optional. */
2141 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
2142 /* Sub directory is optional. */
2143
2144 /*
2145 * Construct current path.
2146 */
2147 char szCurDir[RTPATH_MAX];
2148 int vrc = RTStrCopy(szCurDir, sizeof(szCurDir), pszSource);
2149 if (RT_SUCCESS(vrc) && pszSubDir)
2150 vrc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
2151
2152 if (RT_FAILURE(vrc))
2153 return vrc;
2154
2155 if (pContext->pCmdCtx->fVerbose)
2156 RTPrintf("Processing guest directory: %s\n", szCurDir);
2157
2158 /* Flag indicating whether the current directory was created on the
2159 * target or not. */
2160 bool fDirCreated = false;
2161 SafeArray<DirectoryOpenFlag_T> dirOpenFlags; /* No flags supported yet. */
2162 ComPtr<IGuestDirectory> pDirectory;
2163 HRESULT rc = pContext->pCmdCtx->pGuestSession->DirectoryOpen(Bstr(szCurDir).raw(), Bstr(pszFilter).raw(),
2164 ComSafeArrayAsInParam(dirOpenFlags),
2165 pDirectory.asOutParam());
2166 if (FAILED(rc))
2167 return ctrlPrintError(pContext->pCmdCtx->pGuestSession, COM_IIDOF(IGuestSession));
2168 ComPtr<IFsObjInfo> dirEntry;
2169 while (true)
2170 {
2171 rc = pDirectory->Read(dirEntry.asOutParam());
2172 if (FAILED(rc))
2173 break;
2174
2175 FsObjType_T enmType;
2176 dirEntry->COMGETTER(Type)(&enmType);
2177
2178 Bstr strName;
2179 dirEntry->COMGETTER(Name)(strName.asOutParam());
2180
2181 switch (enmType)
2182 {
2183 case FsObjType_Directory:
2184 {
2185 Assert(!strName.isEmpty());
2186
2187 /* Skip "." and ".." entries. */
2188 if ( !strName.compare(Bstr("."))
2189 || !strName.compare(Bstr("..")))
2190 break;
2191
2192 if (pContext->pCmdCtx->fVerbose)
2193 {
2194 Utf8Str strDir(strName);
2195 RTPrintf("Directory: %s\n", strDir.c_str());
2196 }
2197
2198 if (fFlags & CopyFileFlag_Recursive)
2199 {
2200 Utf8Str strDir(strName);
2201 char *pszNewSub = NULL;
2202 if (pszSubDir)
2203 pszNewSub = RTPathJoinA(pszSubDir, strDir.c_str());
2204 else
2205 {
2206 pszNewSub = RTStrDup(strDir.c_str());
2207 RTPathStripTrailingSlash(pszNewSub);
2208 }
2209 if (pszNewSub)
2210 {
2211 vrc = ctrlCopyDirToHost(pContext,
2212 pszSource, pszFilter,
2213 pszDest, fFlags, pszNewSub);
2214 RTStrFree(pszNewSub);
2215 }
2216 else
2217 vrc = VERR_NO_MEMORY;
2218 }
2219 break;
2220 }
2221
2222 case FsObjType_Symlink:
2223 if ( (fFlags & CopyFileFlag_Recursive)
2224 && (fFlags & CopyFileFlag_FollowLinks))
2225 {
2226 /* Fall through to next case is intentional. */
2227 }
2228 else
2229 break;
2230
2231 case FsObjType_File:
2232 {
2233 Assert(!strName.isEmpty());
2234
2235 Utf8Str strFile(strName);
2236 if ( pszFilter
2237 && !RTStrSimplePatternMatch(pszFilter, strFile.c_str()))
2238 {
2239 break; /* Filter does not match. */
2240 }
2241
2242 if (pContext->pCmdCtx->fVerbose)
2243 RTPrintf("File: %s\n", strFile.c_str());
2244
2245 if (!fDirCreated)
2246 {
2247 char *pszDestDir;
2248 vrc = ctrlCopyTranslatePath(pszSource, szCurDir,
2249 pszDest, &pszDestDir);
2250 if (RT_SUCCESS(vrc))
2251 {
2252 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
2253 RTStrFree(pszDestDir);
2254
2255 fDirCreated = true;
2256 }
2257 }
2258
2259 if (RT_SUCCESS(vrc))
2260 {
2261 char *pszFileSource = RTPathJoinA(szCurDir, strFile.c_str());
2262 if (pszFileSource)
2263 {
2264 char *pszFileDest;
2265 vrc = ctrlCopyTranslatePath(pszSource, pszFileSource,
2266 pszDest, &pszFileDest);
2267 if (RT_SUCCESS(vrc))
2268 {
2269 vrc = ctrlCopyFileToDest(pContext, pszFileSource,
2270 pszFileDest, 0 /* Flags */);
2271 RTStrFree(pszFileDest);
2272 }
2273 RTStrFree(pszFileSource);
2274 }
2275 else
2276 vrc = VERR_NO_MEMORY;
2277 }
2278 break;
2279 }
2280
2281 default:
2282 RTPrintf("Warning: Directory entry of type %ld not handled, skipping ...\n",
2283 enmType);
2284 break;
2285 }
2286
2287 if (RT_FAILURE(vrc))
2288 break;
2289 }
2290
2291 if (RT_UNLIKELY(FAILED(rc)))
2292 {
2293 switch (rc)
2294 {
2295 case E_ABORT: /* No more directory entries left to process. */
2296 break;
2297
2298 case VBOX_E_FILE_ERROR: /* Current entry cannot be accessed to
2299 to missing rights. */
2300 {
2301 RTPrintf("Warning: Cannot access \"%s\", skipping ...\n",
2302 szCurDir);
2303 break;
2304 }
2305
2306 default:
2307 vrc = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
2308 break;
2309 }
2310 }
2311
2312 HRESULT rc2 = pDirectory->Close();
2313 if (FAILED(rc2))
2314 {
2315 int vrc2 = ctrlPrintError(pDirectory, COM_IIDOF(IGuestDirectory));
2316 if (RT_SUCCESS(vrc))
2317 vrc = vrc2;
2318 }
2319 else if (SUCCEEDED(rc))
2320 rc = rc2;
2321
2322 return vrc;
2323}
2324
2325/**
2326 * Copys a directory (tree) to the destination, based on the current copy
2327 * context.
2328 *
2329 * @return IPRT status code.
2330 * @param pContext Pointer to current copy control context.
2331 * @param pszSource Source directory to copy to the destination.
2332 * @param pszFilter DOS-style wildcard filter (?, *). Optional.
2333 * @param pszDest Destination directory where to copy in the source
2334 * source directory.
2335 * @param fFlags Copy flags, such as recursive copying.
2336 */
2337static int ctrlCopyDirToDest(PCOPYCONTEXT pContext,
2338 const char *pszSource, const char *pszFilter,
2339 const char *pszDest, uint32_t fFlags)
2340{
2341 if (pContext->fHostToGuest)
2342 return ctrlCopyDirToGuest(pContext, pszSource, pszFilter,
2343 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
2344 return ctrlCopyDirToHost(pContext, pszSource, pszFilter,
2345 pszDest, fFlags, NULL /* Sub directory, only for recursion. */);
2346}
2347
2348/**
2349 * Creates a source root by stripping file names or filters of the specified source.
2350 *
2351 * @return IPRT status code.
2352 * @param pszSource Source to create source root for.
2353 * @param ppszSourceRoot Pointer that receives the allocated source root. Needs
2354 * to be free'd with ctrlCopyFreeSourceRoot().
2355 */
2356static int ctrlCopyCreateSourceRoot(const char *pszSource, char **ppszSourceRoot)
2357{
2358 AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
2359 AssertPtrReturn(ppszSourceRoot, VERR_INVALID_POINTER);
2360
2361 char *pszNewRoot = RTStrDup(pszSource);
2362 if (!pszNewRoot)
2363 return VERR_NO_MEMORY;
2364
2365 size_t lenRoot = strlen(pszNewRoot);
2366 if ( lenRoot
2367 && ( pszNewRoot[lenRoot - 1] == '/'
2368 || pszNewRoot[lenRoot - 1] == '\\')
2369 )
2370 {
2371 pszNewRoot[lenRoot - 1] = '\0';
2372 }
2373
2374 if ( lenRoot > 1
2375 && ( pszNewRoot[lenRoot - 2] == '/'
2376 || pszNewRoot[lenRoot - 2] == '\\')
2377 )
2378 {
2379 pszNewRoot[lenRoot - 2] = '\0';
2380 }
2381
2382 if (!lenRoot)
2383 {
2384 /* If there's anything (like a file name or a filter),
2385 * strip it! */
2386 RTPathStripFilename(pszNewRoot);
2387 }
2388
2389 *ppszSourceRoot = pszNewRoot;
2390
2391 return VINF_SUCCESS;
2392}
2393
2394/**
2395 * Frees a previously allocated source root.
2396 *
2397 * @return IPRT status code.
2398 * @param pszSourceRoot Source root to free.
2399 */
2400static void ctrlCopyFreeSourceRoot(char *pszSourceRoot)
2401{
2402 RTStrFree(pszSourceRoot);
2403}
2404
2405static RTEXITCODE handleCtrlCopy(PGCTLCMDCTX pCtx, bool fHostToGuest)
2406{
2407 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2408
2409 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
2410 * is much better (partly because it is much simpler of course). The main
2411 * arguments against this is that (1) all but two options conflicts with
2412 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
2413 * done windows CMD style (though not in a 100% compatible way), and (3)
2414 * that only one source is allowed - efficiently sabotaging default
2415 * wildcard expansion by a unix shell. The best solution here would be
2416 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
2417
2418 /*
2419 * IGuest::CopyToGuest is kept as simple as possible to let the developer choose
2420 * what and how to implement the file enumeration/recursive lookup, like VBoxManage
2421 * does in here.
2422 */
2423 static const RTGETOPTDEF s_aOptions[] =
2424 {
2425 { "--dryrun", GETOPTDEF_COPY_DRYRUN, RTGETOPT_REQ_NOTHING },
2426 { "--follow", GETOPTDEF_COPY_FOLLOW, RTGETOPT_REQ_NOTHING },
2427 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
2428 { "--target-directory", GETOPTDEF_COPY_TARGETDIR, RTGETOPT_REQ_STRING }
2429 };
2430
2431 int ch;
2432 RTGETOPTUNION ValueUnion;
2433 RTGETOPTSTATE GetState;
2434 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2435 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2436
2437 Utf8Str strSource;
2438 Utf8Str strDest;
2439 uint32_t fFlags = CopyFileFlag_None;
2440 bool fCopyRecursive = false;
2441 bool fDryRun = false;
2442 uint32_t uUsage = fHostToGuest ? USAGE_GSTCTRL_COPYTO : USAGE_GSTCTRL_COPYFROM;
2443
2444 SOURCEVEC vecSources;
2445
2446 int vrc = VINF_SUCCESS;
2447 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2448 {
2449 /* For options that require an argument, ValueUnion has received the value. */
2450 switch (ch)
2451 {
2452 case GETOPTDEF_COPY_DRYRUN:
2453 fDryRun = true;
2454 break;
2455
2456 case GETOPTDEF_COPY_FOLLOW:
2457 fFlags |= CopyFileFlag_FollowLinks;
2458 break;
2459
2460 case 'R': /* Recursive processing */
2461 fFlags |= CopyFileFlag_Recursive;
2462 break;
2463
2464 case GETOPTDEF_COPY_TARGETDIR:
2465 strDest = ValueUnion.psz;
2466 break;
2467
2468 case VINF_GETOPT_NOT_OPTION:
2469 {
2470 /* Last argument and no destination specified with
2471 * --target-directory yet? Then use the current
2472 * (= last) argument as destination. */
2473 if ( pCtx->iArgc == GetState.iNext
2474 && strDest.isEmpty())
2475 {
2476 strDest = ValueUnion.psz;
2477 }
2478 else
2479 {
2480 /* Save the source directory. */
2481 vecSources.push_back(SOURCEFILEENTRY(ValueUnion.psz));
2482 }
2483 break;
2484 }
2485
2486 default:
2487 return errorGetOptEx(USAGE_GUESTCONTROL, uUsage, ch, &ValueUnion);
2488 }
2489 }
2490
2491 if (!vecSources.size())
2492 return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage,
2493 "No source(s) specified!");
2494
2495 if (strDest.isEmpty())
2496 return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage,
2497 "No destination specified!");
2498
2499 /*
2500 * Done parsing arguments, do some more preparations.
2501 */
2502 if (pCtx->fVerbose)
2503 {
2504 if (fHostToGuest)
2505 RTPrintf("Copying from host to guest ...\n");
2506 else
2507 RTPrintf("Copying from guest to host ...\n");
2508 if (fDryRun)
2509 RTPrintf("Dry run - no files copied!\n");
2510 }
2511
2512 /* Create the copy context -- it contains all information
2513 * the routines need to know when handling the actual copying. */
2514 PCOPYCONTEXT pContext = NULL;
2515 vrc = ctrlCopyContextCreate(pCtx, fDryRun, fHostToGuest,
2516 fHostToGuest
2517 ? "VBoxManage Guest Control - Copy to guest"
2518 : "VBoxManage Guest Control - Copy from guest", &pContext);
2519 if (RT_FAILURE(vrc))
2520 {
2521 RTMsgError("Unable to create copy context, rc=%Rrc\n", vrc);
2522 return RTEXITCODE_FAILURE;
2523 }
2524
2525 /* If the destination is a path, (try to) create it. */
2526 const char *pszDest = strDest.c_str();
2527/** @todo r=bird: RTPathFilename and RTPathStripFilename won't work
2528 * correctly on non-windows hosts when the guest is from the DOS world (Windows,
2529 * OS/2, DOS). The host doesn't know about DOS slashes, only UNIX slashes and
2530 * will get the wrong idea if some dilligent user does:
2531 *
2532 * copyto myfile.txt 'C:\guestfile.txt'
2533 * or
2534 * copyto myfile.txt 'D:guestfile.txt'
2535 *
2536 * @bugref{6344}
2537 */
2538 if (!RTPathFilename(pszDest))
2539 {
2540 vrc = ctrlCopyDirCreate(pContext, pszDest);
2541 }
2542 else
2543 {
2544 /* We assume we got a file name as destination -- so strip
2545 * the actual file name and make sure the appropriate
2546 * directories get created. */
2547 char *pszDestDir = RTStrDup(pszDest);
2548 AssertPtr(pszDestDir);
2549 RTPathStripFilename(pszDestDir);
2550 vrc = ctrlCopyDirCreate(pContext, pszDestDir);
2551 RTStrFree(pszDestDir);
2552 }
2553
2554 if (RT_SUCCESS(vrc))
2555 {
2556 /*
2557 * Here starts the actual fun!
2558 * Handle all given sources one by one.
2559 */
2560 for (unsigned long s = 0; s < vecSources.size(); s++)
2561 {
2562 char *pszSource = RTStrDup(vecSources[s].GetSource());
2563 AssertPtrBreakStmt(pszSource, vrc = VERR_NO_MEMORY);
2564 const char *pszFilter = vecSources[s].GetFilter();
2565 if (!strlen(pszFilter))
2566 pszFilter = NULL; /* If empty filter then there's no filter :-) */
2567
2568 char *pszSourceRoot;
2569 vrc = ctrlCopyCreateSourceRoot(pszSource, &pszSourceRoot);
2570 if (RT_FAILURE(vrc))
2571 {
2572 RTMsgError("Unable to create source root, rc=%Rrc\n", vrc);
2573 break;
2574 }
2575
2576 if (pCtx->fVerbose)
2577 RTPrintf("Source: %s\n", pszSource);
2578
2579 /** @todo Files with filter?? */
2580 bool fSourceIsFile = false;
2581 bool fSourceExists;
2582
2583 size_t cchSource = strlen(pszSource);
2584 if ( cchSource > 1
2585 && RTPATH_IS_SLASH(pszSource[cchSource - 1]))
2586 {
2587 if (pszFilter) /* Directory with filter (so use source root w/o the actual filter). */
2588 vrc = ctrlCopyDirExistsOnSource(pContext, pszSourceRoot, &fSourceExists);
2589 else /* Regular directory without filter. */
2590 vrc = ctrlCopyDirExistsOnSource(pContext, pszSource, &fSourceExists);
2591
2592 if (fSourceExists)
2593 {
2594 /* Strip trailing slash from our source element so that other functions
2595 * can use this stuff properly (like RTPathStartsWith). */
2596 RTPathStripTrailingSlash(pszSource);
2597 }
2598 }
2599 else
2600 {
2601 vrc = ctrlCopyFileExistsOnSource(pContext, pszSource, &fSourceExists);
2602 if ( RT_SUCCESS(vrc)
2603 && fSourceExists)
2604 {
2605 fSourceIsFile = true;
2606 }
2607 }
2608
2609 if ( RT_SUCCESS(vrc)
2610 && fSourceExists)
2611 {
2612 if (fSourceIsFile)
2613 {
2614 /* Single file. */
2615 char *pszDestFile;
2616 vrc = ctrlCopyTranslatePath(pszSourceRoot, pszSource,
2617 strDest.c_str(), &pszDestFile);
2618 if (RT_SUCCESS(vrc))
2619 {
2620 vrc = ctrlCopyFileToDest(pContext, pszSource,
2621 pszDestFile, 0 /* Flags */);
2622 RTStrFree(pszDestFile);
2623 }
2624 else
2625 RTMsgError("Unable to translate path for \"%s\", rc=%Rrc\n",
2626 pszSource, vrc);
2627 }
2628 else
2629 {
2630 /* Directory (with filter?). */
2631 vrc = ctrlCopyDirToDest(pContext, pszSource, pszFilter,
2632 strDest.c_str(), fFlags);
2633 }
2634 }
2635
2636 ctrlCopyFreeSourceRoot(pszSourceRoot);
2637
2638 if ( RT_SUCCESS(vrc)
2639 && !fSourceExists)
2640 {
2641 RTMsgError("Warning: Source \"%s\" does not exist, skipping!\n",
2642 pszSource);
2643 RTStrFree(pszSource);
2644 continue;
2645 }
2646 else if (RT_FAILURE(vrc))
2647 {
2648 RTMsgError("Error processing \"%s\", rc=%Rrc\n",
2649 pszSource, vrc);
2650 RTStrFree(pszSource);
2651 break;
2652 }
2653
2654 RTStrFree(pszSource);
2655 }
2656 }
2657
2658 ctrlCopyContextFree(pContext);
2659
2660 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2661}
2662
2663static DECLCALLBACK(RTEXITCODE) handleCtrlCopyFrom(PGCTLCMDCTX pCtx)
2664{
2665 return handleCtrlCopy(pCtx, false /* Guest to host */);
2666}
2667
2668static DECLCALLBACK(RTEXITCODE) handleCtrlCopyTo(PGCTLCMDCTX pCtx)
2669{
2670 return handleCtrlCopy(pCtx, true /* Host to guest */);
2671}
2672
2673static DECLCALLBACK(RTEXITCODE) handleCtrlCreateDirectory(PGCTLCMDCTX pCtx)
2674{
2675 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2676
2677 static const RTGETOPTDEF s_aOptions[] =
2678 {
2679 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
2680 { "--parents", 'P', RTGETOPT_REQ_NOTHING }
2681 };
2682
2683 int ch;
2684 RTGETOPTUNION ValueUnion;
2685 RTGETOPTSTATE GetState;
2686 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2687 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2688
2689 SafeArray<DirectoryCreateFlag_T> dirCreateFlags;
2690 uint32_t fDirMode = 0; /* Default mode. */
2691 DESTDIRMAP mapDirs;
2692
2693 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2694 {
2695 /* For options that require an argument, ValueUnion has received the value. */
2696 switch (ch)
2697 {
2698 case 'm': /* Mode */
2699 fDirMode = ValueUnion.u32;
2700 break;
2701
2702 case 'P': /* Create parents */
2703 dirCreateFlags.push_back(DirectoryCreateFlag_Parents);
2704 break;
2705
2706 case VINF_GETOPT_NOT_OPTION:
2707 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2708 break;
2709
2710 default:
2711 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CREATEDIR, ch, &ValueUnion);
2712 }
2713 }
2714
2715 uint32_t cDirs = mapDirs.size();
2716 if (!cDirs)
2717 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CREATEDIR,
2718 "No directory to create specified!");
2719
2720 /*
2721 * Create the directories.
2722 */
2723 HRESULT rc = S_OK;
2724 if (pCtx->fVerbose && cDirs)
2725 RTPrintf("Creating %RU32 directories ...\n", cDirs);
2726
2727 DESTDIRMAPITER it = mapDirs.begin();
2728 while ( (it != mapDirs.end())
2729 && !g_fGuestCtrlCanceled)
2730 {
2731 if (pCtx->fVerbose)
2732 RTPrintf("Creating directory \"%s\" ...\n", it->first.c_str());
2733
2734 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryCreate(Bstr(it->first).raw(),
2735 fDirMode, ComSafeArrayAsInParam(dirCreateFlags)));
2736 it++;
2737 }
2738
2739 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2740}
2741
2742static DECLCALLBACK(RTEXITCODE) handleCtrlRemoveDirectory(PGCTLCMDCTX pCtx)
2743{
2744 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2745
2746 static const RTGETOPTDEF s_aOptions[] =
2747 {
2748 { "--recursive", 'R', RTGETOPT_REQ_NOTHING }
2749 };
2750
2751 int ch;
2752 RTGETOPTUNION ValueUnion;
2753 RTGETOPTSTATE GetState;
2754 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2755 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2756
2757 bool fRecursive = false;
2758 DESTDIRMAP mapDirs;
2759
2760 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2761 {
2762 /* For options that require an argument, ValueUnion has received the value. */
2763 switch (ch)
2764 {
2765 case 'R':
2766 fRecursive = true;
2767 break;
2768
2769 case VINF_GETOPT_NOT_OPTION:
2770 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2771 break;
2772
2773 default:
2774 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_REMOVEDIR, ch, &ValueUnion);
2775 }
2776 }
2777
2778 uint32_t cDirs = mapDirs.size();
2779 if (!cDirs)
2780 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_REMOVEDIR,
2781 "No directory to remove specified!");
2782
2783 /*
2784 * Remove the directories.
2785 */
2786 HRESULT rc = S_OK;
2787 if (pCtx->fVerbose && cDirs)
2788 RTPrintf("Removing %RU32 directories ...\n", cDirs);
2789
2790 DESTDIRMAPITER it = mapDirs.begin();
2791 while ( (it != mapDirs.end())
2792 && !g_fGuestCtrlCanceled)
2793 {
2794 if (pCtx->fVerbose)
2795 RTPrintf("%s directory \"%s\" ...\n",
2796 fRecursive ? "Recursively removing" : "Removing",
2797 it->first.c_str());
2798 try
2799 {
2800 if (fRecursive)
2801 {
2802 com::SafeArray<DirectoryRemoveRecFlag_T> aRemRecFlags;
2803 /** @todo Make flags configurable. */
2804 aRemRecFlags.push_back(DirectoryRemoveRecFlag_ContentAndDir);
2805
2806 ComPtr<IProgress> pProgress;
2807 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryRemoveRecursive(Bstr(it->first).raw(),
2808 ComSafeArrayAsInParam(aRemRecFlags),
2809 pProgress.asOutParam()));
2810 if (pCtx->fVerbose)
2811 rc = showProgress(pProgress);
2812 else
2813 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
2814 if (SUCCEEDED(rc))
2815 CHECK_PROGRESS_ERROR(pProgress, ("Directory deletion failed"));
2816
2817 pProgress.setNull();
2818 }
2819 else
2820 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryRemove(Bstr(it->first).raw()));
2821 }
2822 catch (std::bad_alloc)
2823 {
2824 rc = E_OUTOFMEMORY;
2825 break;
2826 }
2827
2828 it++;
2829 }
2830
2831 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2832}
2833
2834static DECLCALLBACK(RTEXITCODE) handleCtrlRemoveFile(PGCTLCMDCTX pCtx)
2835{
2836 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2837
2838 int ch;
2839 RTGETOPTUNION ValueUnion;
2840 RTGETOPTSTATE GetState;
2841 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2842 NULL /* s_aOptions */, 0 /* RT_ELEMENTS(s_aOptions) */,
2843 pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2844
2845 DESTDIRMAP mapDirs;
2846
2847 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
2848 {
2849 /* For options that require an argument, ValueUnion has received the value. */
2850 switch (ch)
2851 {
2852 case VINF_GETOPT_NOT_OPTION:
2853 mapDirs[ValueUnion.psz]; /* Add destination directory to map. */
2854 break;
2855
2856 default:
2857 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_REMOVEFILE, ch, &ValueUnion);
2858 }
2859 }
2860
2861 uint32_t cFiles = mapDirs.size();
2862 if (!cFiles)
2863 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_REMOVEFILE,
2864 "No file to remove specified!");
2865
2866 /*
2867 * Create the directories.
2868 */
2869 HRESULT rc = S_OK;
2870 if (pCtx->fVerbose && cFiles)
2871 RTPrintf("Removing %RU32 file(s) ...\n", cFiles);
2872
2873 DESTDIRMAPITER it = mapDirs.begin();
2874 while ( (it != mapDirs.end())
2875 && !g_fGuestCtrlCanceled)
2876 {
2877 if (pCtx->fVerbose)
2878 RTPrintf("Removing file \"%s\" ...\n", it->first.c_str());
2879
2880 CHECK_ERROR_BREAK(pCtx->pGuestSession, FileRemove(Bstr(it->first).raw()));
2881
2882 it++;
2883 }
2884
2885 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
2886}
2887
2888static DECLCALLBACK(RTEXITCODE) handleCtrlRename(PGCTLCMDCTX pCtx)
2889{
2890 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
2891
2892 static const RTGETOPTDEF s_aOptions[] = { { 0 } };
2893
2894 int ch;
2895 RTGETOPTUNION ValueUnion;
2896 RTGETOPTSTATE GetState;
2897 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
2898 NULL /*s_aOptions*/, 0 /*RT_ELEMENTS(s_aOptions)*/, pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2899
2900 int vrc = VINF_SUCCESS;
2901
2902 bool fDryrun = false;
2903 std::vector< Utf8Str > vecSources;
2904 Utf8Str strDest;
2905 com::SafeArray<PathRenameFlag_T> aRenameFlags;
2906
2907 try
2908 {
2909 /** @todo Make flags configurable. */
2910 aRenameFlags.push_back(PathRenameFlag_NoReplace);
2911
2912 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2913 && RT_SUCCESS(vrc))
2914 {
2915 /* For options that require an argument, ValueUnion has received the value. */
2916 switch (ch)
2917 {
2918 /** @todo Implement a --dryrun command. */
2919 /** @todo Implement rename flags. */
2920
2921 case VINF_GETOPT_NOT_OPTION:
2922 vecSources.push_back(Utf8Str(ValueUnion.psz));
2923 strDest = ValueUnion.psz;
2924 break;
2925
2926 default:
2927 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RENAME, ch, &ValueUnion);
2928 }
2929 }
2930 }
2931 catch (std::bad_alloc)
2932 {
2933 vrc = VERR_NO_MEMORY;
2934 }
2935
2936 if (RT_FAILURE(vrc))
2937 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize, rc=%Rrc\n", vrc);
2938
2939 uint32_t cSources = vecSources.size();
2940 if (!cSources)
2941 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RENAME,
2942 "No source(s) to move specified!");
2943 if (cSources < 2)
2944 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RENAME,
2945 "No destination specified!");
2946
2947 /* Delete last element, which now is the destination. */
2948 vecSources.pop_back();
2949 cSources = vecSources.size();
2950
2951 HRESULT rc = S_OK;
2952
2953 if (cSources > 1)
2954 {
2955 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2956 rc = pCtx->pGuestSession->DirectoryQueryInfo(Bstr(strDest).raw(), pFsObjInfo.asOutParam());
2957 if (FAILED(rc))
2958 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Destination must be a directory when specifying multiple sources\n");
2959 }
2960
2961 /*
2962 * Rename (move) the entries.
2963 */
2964 if (pCtx->fVerbose && cSources)
2965 RTPrintf("Renaming %RU32 %s ...\n", cSources,
2966 cSources > 1
2967 ? "entries" : "entry");
2968
2969 std::vector< Utf8Str >::iterator it = vecSources.begin();
2970 while ( (it != vecSources.end())
2971 && !g_fGuestCtrlCanceled)
2972 {
2973 bool fSourceIsDirectory = false;
2974 Utf8Str strCurSource = (*it);
2975 Utf8Str strCurDest = strDest;
2976
2977 /** @todo Slooooow, but works for now. */
2978 ComPtr<IGuestFsObjInfo> pFsObjInfo;
2979 rc = pCtx->pGuestSession->FileQueryInfo(Bstr(strCurSource).raw(), pFsObjInfo.asOutParam());
2980 if (FAILED(rc))
2981 {
2982 rc = pCtx->pGuestSession->DirectoryQueryInfo(Bstr(strCurSource).raw(), pFsObjInfo.asOutParam());
2983 fSourceIsDirectory = SUCCEEDED(rc);
2984 }
2985 if (FAILED(rc))
2986 {
2987 if (pCtx->fVerbose)
2988 RTPrintf("Warning: Cannot stat for element \"%s\": No such element\n",
2989 strCurSource.c_str());
2990 it++;
2991 continue; /* Skip. */
2992 }
2993
2994 if (pCtx->fVerbose)
2995 RTPrintf("Renaming %s \"%s\" to \"%s\" ...\n",
2996 fSourceIsDirectory ? "directory" : "file",
2997 strCurSource.c_str(), strCurDest.c_str());
2998
2999 if (!fDryrun)
3000 {
3001 if (fSourceIsDirectory)
3002 {
3003 CHECK_ERROR_BREAK(pCtx->pGuestSession, DirectoryRename(Bstr(strCurSource).raw(),
3004 Bstr(strCurDest).raw(),
3005 ComSafeArrayAsInParam(aRenameFlags)));
3006
3007 /* Break here, since it makes no sense to rename mroe than one source to
3008 * the same directory. */
3009 it = vecSources.end();
3010 break;
3011 }
3012 else
3013 CHECK_ERROR_BREAK(pCtx->pGuestSession, FileRename(Bstr(strCurSource).raw(),
3014 Bstr(strCurDest).raw(),
3015 ComSafeArrayAsInParam(aRenameFlags)));
3016 }
3017
3018 it++;
3019 }
3020
3021 if ( (it != vecSources.end())
3022 && pCtx->fVerbose)
3023 {
3024 RTPrintf("Warning: Not all sources were renamed\n");
3025 }
3026
3027 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
3028}
3029
3030static DECLCALLBACK(RTEXITCODE) handleCtrlCreateTemp(PGCTLCMDCTX pCtx)
3031{
3032 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3033
3034 static const RTGETOPTDEF s_aOptions[] =
3035 {
3036 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
3037 { "--directory", 'D', RTGETOPT_REQ_NOTHING },
3038 { "--secure", 's', RTGETOPT_REQ_NOTHING },
3039 { "--tmpdir", 't', RTGETOPT_REQ_STRING }
3040 };
3041
3042 int ch;
3043 RTGETOPTUNION ValueUnion;
3044 RTGETOPTSTATE GetState;
3045 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3046 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3047
3048 Utf8Str strTemplate;
3049 uint32_t fMode = 0; /* Default mode. */
3050 bool fDirectory = false;
3051 bool fSecure = false;
3052 Utf8Str strTempDir;
3053
3054 DESTDIRMAP mapDirs;
3055
3056 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
3057 {
3058 /* For options that require an argument, ValueUnion has received the value. */
3059 switch (ch)
3060 {
3061 case 'm': /* Mode */
3062 fMode = ValueUnion.u32;
3063 break;
3064
3065 case 'D': /* Create directory */
3066 fDirectory = true;
3067 break;
3068
3069 case 's': /* Secure */
3070 fSecure = true;
3071 break;
3072
3073 case 't': /* Temp directory */
3074 strTempDir = ValueUnion.psz;
3075 break;
3076
3077 case VINF_GETOPT_NOT_OPTION:
3078 {
3079 if (strTemplate.isEmpty())
3080 strTemplate = ValueUnion.psz;
3081 else
3082 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CREATETEMP,
3083 "More than one template specified!\n");
3084 break;
3085 }
3086
3087 default:
3088 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CREATETEMP, ch, &ValueUnion);
3089 }
3090 }
3091
3092 if (strTemplate.isEmpty())
3093 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CREATETEMP,
3094 "No template specified!");
3095
3096 if (!fDirectory)
3097 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CREATETEMP,
3098 "Creating temporary files is currently not supported!");
3099
3100 /*
3101 * Create the directories.
3102 */
3103 if (pCtx->fVerbose)
3104 {
3105 if (fDirectory && !strTempDir.isEmpty())
3106 RTPrintf("Creating temporary directory from template '%s' in directory '%s' ...\n",
3107 strTemplate.c_str(), strTempDir.c_str());
3108 else if (fDirectory)
3109 RTPrintf("Creating temporary directory from template '%s' in default temporary directory ...\n",
3110 strTemplate.c_str());
3111 else if (!fDirectory && !strTempDir.isEmpty())
3112 RTPrintf("Creating temporary file from template '%s' in directory '%s' ...\n",
3113 strTemplate.c_str(), strTempDir.c_str());
3114 else if (!fDirectory)
3115 RTPrintf("Creating temporary file from template '%s' in default temporary directory ...\n",
3116 strTemplate.c_str());
3117 }
3118
3119 HRESULT rc = S_OK;
3120 if (fDirectory)
3121 {
3122 Bstr directory;
3123 CHECK_ERROR(pCtx->pGuestSession, DirectoryCreateTemp(Bstr(strTemplate).raw(),
3124 fMode, Bstr(strTempDir).raw(),
3125 fSecure,
3126 directory.asOutParam()));
3127 if (SUCCEEDED(rc))
3128 RTPrintf("Directory name: %ls\n", directory.raw());
3129 }
3130 // else - temporary file not yet implemented
3131
3132 return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS;
3133}
3134
3135static DECLCALLBACK(RTEXITCODE) handleCtrlStat(PGCTLCMDCTX pCtx)
3136{
3137 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3138
3139 static const RTGETOPTDEF s_aOptions[] =
3140 {
3141 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
3142 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
3143 { "--format", 'c', RTGETOPT_REQ_STRING },
3144 { "--terse", 't', RTGETOPT_REQ_NOTHING }
3145 };
3146
3147 int ch;
3148 RTGETOPTUNION ValueUnion;
3149 RTGETOPTSTATE GetState;
3150 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3151 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3152
3153 DESTDIRMAP mapObjs;
3154
3155 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
3156 {
3157 /* For options that require an argument, ValueUnion has received the value. */
3158 switch (ch)
3159 {
3160 case 'L': /* Dereference */
3161 case 'f': /* File-system */
3162 case 'c': /* Format */
3163 case 't': /* Terse */
3164 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT,
3165 "Command \"%s\" not implemented yet!", ValueUnion.psz);
3166
3167 case VINF_GETOPT_NOT_OPTION:
3168 mapObjs[ValueUnion.psz]; /* Add element to check to map. */
3169 break;
3170
3171 default:
3172 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT, ch, &ValueUnion);
3173 }
3174 }
3175
3176 uint32_t cObjs = mapObjs.size();
3177 if (!cObjs)
3178 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT,
3179 "No element(s) to check specified!");
3180
3181 HRESULT rc;
3182
3183 /*
3184 * Doing the checks.
3185 */
3186 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
3187 DESTDIRMAPITER it = mapObjs.begin();
3188 while (it != mapObjs.end())
3189 {
3190 if (pCtx->fVerbose)
3191 RTPrintf("Checking for element \"%s\" ...\n", it->first.c_str());
3192
3193 ComPtr<IGuestFsObjInfo> pFsObjInfo;
3194 rc = pCtx->pGuestSession->FileQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
3195 if (FAILED(rc))
3196 rc = pCtx->pGuestSession->DirectoryQueryInfo(Bstr(it->first).raw(), pFsObjInfo.asOutParam());
3197
3198 if (FAILED(rc))
3199 {
3200 /* If there's at least one element which does not exist on the guest,
3201 * drop out with exitcode 1. */
3202 if (pCtx->fVerbose)
3203 RTPrintf("Cannot stat for element \"%s\": No such element\n",
3204 it->first.c_str());
3205 rcExit = RTEXITCODE_FAILURE;
3206 }
3207 else
3208 {
3209 FsObjType_T objType;
3210 pFsObjInfo->COMGETTER(Type)(&objType);
3211 switch (objType)
3212 {
3213 case FsObjType_File:
3214 RTPrintf("Element \"%s\" found: Is a file\n", it->first.c_str());
3215 break;
3216
3217 case FsObjType_Directory:
3218 RTPrintf("Element \"%s\" found: Is a directory\n", it->first.c_str());
3219 break;
3220
3221 case FsObjType_Symlink:
3222 RTPrintf("Element \"%s\" found: Is a symlink\n", it->first.c_str());
3223 break;
3224
3225 default:
3226 RTPrintf("Element \"%s\" found, type unknown (%ld)\n", it->first.c_str(), objType);
3227 break;
3228 }
3229
3230 /** @todo: Show more information about this element. */
3231 }
3232
3233 it++;
3234 }
3235
3236 return rcExit;
3237}
3238
3239static DECLCALLBACK(RTEXITCODE) handleCtrlUpdateAdditions(PGCTLCMDCTX pCtx)
3240{
3241 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3242
3243 /*
3244 * Check the syntax. We can deduce the correct syntax from the number of
3245 * arguments.
3246 */
3247 Utf8Str strSource;
3248 com::SafeArray<IN_BSTR> aArgs;
3249 bool fWaitStartOnly = false;
3250
3251 static const RTGETOPTDEF s_aOptions[] =
3252 {
3253 { "--source", 's', RTGETOPT_REQ_STRING },
3254 { "--wait-start", 'w', RTGETOPT_REQ_NOTHING }
3255 };
3256
3257 int ch;
3258 RTGETOPTUNION ValueUnion;
3259 RTGETOPTSTATE GetState;
3260 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv, s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, 0);
3261
3262 int vrc = VINF_SUCCESS;
3263 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3264 && RT_SUCCESS(vrc))
3265 {
3266 switch (ch)
3267 {
3268 case 's':
3269 strSource = ValueUnion.psz;
3270 break;
3271
3272 case 'w':
3273 fWaitStartOnly = true;
3274 break;
3275
3276 case VINF_GETOPT_NOT_OPTION:
3277 if (aArgs.size() == 0 && strSource.isEmpty())
3278 strSource = ValueUnion.psz;
3279 else
3280 aArgs.push_back(Bstr(ValueUnion.psz).raw());
3281 break;
3282
3283 default:
3284 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_UPDATEADDS, ch, &ValueUnion);
3285 }
3286 }
3287
3288 if (pCtx->fVerbose)
3289 RTPrintf("Updating Guest Additions ...\n");
3290
3291 HRESULT rc = S_OK;
3292 while (strSource.isEmpty())
3293 {
3294 ComPtr<ISystemProperties> pProperties;
3295 CHECK_ERROR_BREAK(pCtx->handlerArg.virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam()));
3296 Bstr strISO;
3297 CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam()));
3298 strSource = strISO;
3299 break;
3300 }
3301
3302 /* Determine source if not set yet. */
3303 if (strSource.isEmpty())
3304 {
3305 RTMsgError("No Guest Additions source found or specified, aborting\n");
3306 vrc = VERR_FILE_NOT_FOUND;
3307 }
3308 else if (!RTFileExists(strSource.c_str()))
3309 {
3310 RTMsgError("Source \"%s\" does not exist!\n", strSource.c_str());
3311 vrc = VERR_FILE_NOT_FOUND;
3312 }
3313
3314 if (RT_SUCCESS(vrc))
3315 {
3316 if (pCtx->fVerbose)
3317 RTPrintf("Using source: %s\n", strSource.c_str());
3318
3319 com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags;
3320 if (fWaitStartOnly)
3321 {
3322 aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly);
3323 if (pCtx->fVerbose)
3324 RTPrintf("Preparing and waiting for Guest Additions installer to start ...\n");
3325 }
3326
3327 ComPtr<IProgress> pProgress;
3328 CHECK_ERROR(pCtx->pGuest, UpdateGuestAdditions(Bstr(strSource).raw(),
3329 ComSafeArrayAsInParam(aArgs),
3330 /* Wait for whole update process to complete. */
3331 ComSafeArrayAsInParam(aUpdateFlags),
3332 pProgress.asOutParam()));
3333 if (FAILED(rc))
3334 vrc = ctrlPrintError(pCtx->pGuest, COM_IIDOF(IGuest));
3335 else
3336 {
3337 if (pCtx->fVerbose)
3338 rc = showProgress(pProgress);
3339 else
3340 rc = pProgress->WaitForCompletion(-1 /* No timeout */);
3341
3342 if (SUCCEEDED(rc))
3343 CHECK_PROGRESS_ERROR(pProgress, ("Guest additions update failed"));
3344 vrc = ctrlPrintProgressError(pProgress);
3345 if ( RT_SUCCESS(vrc)
3346 && pCtx->fVerbose)
3347 {
3348 RTPrintf("Guest Additions update successful\n");
3349 }
3350 }
3351 }
3352
3353 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3354}
3355
3356static DECLCALLBACK(RTEXITCODE) handleCtrlList(PGCTLCMDCTX pCtx)
3357{
3358 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3359
3360 if (pCtx->iArgc < 1)
3361 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_LIST,
3362 "Must specify a listing category");
3363
3364 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
3365
3366 /** Use RTGetOpt here when handling command line args gets more complex. */
3367
3368 bool fListAll = false;
3369 bool fListSessions = false;
3370 bool fListProcesses = false;
3371 bool fListFiles = false;
3372 if ( !RTStrICmp(pCtx->ppaArgv[0], "sessions")
3373 || !RTStrICmp(pCtx->ppaArgv[0], "sess"))
3374 fListSessions = true;
3375 else if ( !RTStrICmp(pCtx->ppaArgv[0], "processes")
3376 || !RTStrICmp(pCtx->ppaArgv[0], "procs"))
3377 fListSessions = fListProcesses = true; /* Showing processes implies showing sessions. */
3378 else if ( !RTStrICmp(pCtx->ppaArgv[0], "files"))
3379 fListSessions = fListFiles = true; /* Showing files implies showing sessions. */
3380 else if (!RTStrICmp(pCtx->ppaArgv[0], "all"))
3381 fListAll = true;
3382
3383 /** @todo Handle "--verbose" using RTGetOpt. */
3384 /** @todo Do we need a machine-readable output here as well? */
3385
3386 if ( fListAll
3387 || fListSessions)
3388 {
3389 HRESULT rc;
3390 do
3391 {
3392 size_t cTotalProcs = 0;
3393 size_t cTotalFiles = 0;
3394
3395 SafeIfaceArray <IGuestSession> collSessions;
3396 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3397 size_t cSessions = collSessions.size();
3398
3399 if (cSessions)
3400 {
3401 RTPrintf("Active guest sessions:\n");
3402
3403 /** @todo Make this output a bit prettier. No time now. */
3404
3405 for (size_t i = 0; i < cSessions; i++)
3406 {
3407 ComPtr<IGuestSession> pCurSession = collSessions[i];
3408 if (!pCurSession.isNull())
3409 {
3410 ULONG uID;
3411 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Id)(&uID));
3412 Bstr strName;
3413 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Name)(strName.asOutParam()));
3414 Bstr strUser;
3415 CHECK_ERROR_BREAK(pCurSession, COMGETTER(User)(strUser.asOutParam()));
3416 GuestSessionStatus_T sessionStatus;
3417 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Status)(&sessionStatus));
3418 RTPrintf("\n\tSession #%-3zu ID=%-3RU32 User=%-16ls Status=[%s] Name=%ls",
3419 i, uID, strUser.raw(), ctrlSessionStatusToText(sessionStatus), strName.raw());
3420
3421 if ( fListAll
3422 || fListProcesses)
3423 {
3424 SafeIfaceArray <IGuestProcess> collProcesses;
3425 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcesses)));
3426 for (size_t a = 0; a < collProcesses.size(); a++)
3427 {
3428 ComPtr<IGuestProcess> pCurProcess = collProcesses[a];
3429 if (!pCurProcess.isNull())
3430 {
3431 ULONG uPID;
3432 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(PID)(&uPID));
3433 Bstr strExecPath;
3434 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(ExecutablePath)(strExecPath.asOutParam()));
3435 ProcessStatus_T procStatus;
3436 CHECK_ERROR_BREAK(pCurProcess, COMGETTER(Status)(&procStatus));
3437
3438 RTPrintf("\n\t\tProcess #%-03zu PID=%-6RU32 Status=[%s] Command=%ls",
3439 a, uPID, ctrlProcessStatusToText(procStatus), strExecPath.raw());
3440 }
3441 }
3442
3443 cTotalProcs += collProcesses.size();
3444 }
3445
3446 if ( fListAll
3447 || fListFiles)
3448 {
3449 SafeIfaceArray <IGuestFile> collFiles;
3450 CHECK_ERROR_BREAK(pCurSession, COMGETTER(Files)(ComSafeArrayAsOutParam(collFiles)));
3451 for (size_t a = 0; a < collFiles.size(); a++)
3452 {
3453 ComPtr<IGuestFile> pCurFile = collFiles[a];
3454 if (!pCurFile.isNull())
3455 {
3456 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Id)(&uID));
3457 CHECK_ERROR_BREAK(pCurFile, COMGETTER(FileName)(strName.asOutParam()));
3458 FileStatus_T fileStatus;
3459 CHECK_ERROR_BREAK(pCurFile, COMGETTER(Status)(&fileStatus));
3460
3461 RTPrintf("\n\t\tFile #%-03zu ID=%-6RU32 Status=[%s] Name=%ls",
3462 a, uID, ctrlFileStatusToText(fileStatus), strName.raw());
3463 }
3464 }
3465
3466 cTotalFiles += collFiles.size();
3467 }
3468 }
3469 }
3470
3471 RTPrintf("\n\nTotal guest sessions: %zu\n", collSessions.size());
3472 if (fListAll || fListProcesses)
3473 RTPrintf("Total guest processes: %zu\n", cTotalProcs);
3474 if (fListAll || fListFiles)
3475 RTPrintf("Total guest files: %zu\n", cTotalFiles);
3476 }
3477 else
3478 RTPrintf("No active guest sessions found\n");
3479
3480 } while (0);
3481
3482 if (FAILED(rc))
3483 rcExit = RTEXITCODE_FAILURE;
3484 }
3485 else
3486 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_LIST,
3487 "Invalid listing category '%s", pCtx->ppaArgv[0]);
3488
3489 return rcExit;
3490}
3491
3492static DECLCALLBACK(RTEXITCODE) handleCtrlProcessClose(PGCTLCMDCTX pCtx)
3493{
3494 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3495
3496 if (pCtx->iArgc < 1)
3497 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3498 "Must specify at least a PID to close");
3499
3500 static const RTGETOPTDEF s_aOptions[] =
3501 {
3502 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3503 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3504 };
3505
3506 int ch;
3507 RTGETOPTUNION ValueUnion;
3508 RTGETOPTSTATE GetState;
3509 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3510 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3511
3512 std::vector < uint32_t > vecPID;
3513 ULONG ulSessionID = UINT32_MAX;
3514 Utf8Str strSessionName;
3515
3516 int vrc = VINF_SUCCESS;
3517
3518 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
3519 && RT_SUCCESS(vrc))
3520 {
3521 /* For options that require an argument, ValueUnion has received the value. */
3522 switch (ch)
3523 {
3524 case 'n': /* Session name (or pattern) */
3525 strSessionName = ValueUnion.psz;
3526 break;
3527
3528 case 'i': /* Session ID */
3529 ulSessionID = ValueUnion.u32;
3530 break;
3531
3532 case VINF_GETOPT_NOT_OPTION:
3533 if (pCtx->iArgc == GetState.iNext)
3534 {
3535 /* Treat every else specified as a PID to kill. */
3536 try
3537 {
3538 uint32_t uPID = RTStrToUInt32(ValueUnion.psz);
3539 if (uPID) /** @todo Is this what we want? If specifying PID 0
3540 this is not going to work on most systems anyway. */
3541 vecPID.push_back(uPID);
3542 else
3543 vrc = VERR_INVALID_PARAMETER;
3544 }
3545 catch(std::bad_alloc &)
3546 {
3547 vrc = VERR_NO_MEMORY;
3548 }
3549 }
3550 break;
3551
3552 default:
3553 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS, ch, &ValueUnion);
3554 }
3555 }
3556
3557 if (vecPID.empty())
3558 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3559 "At least one PID must be specified to kill!");
3560
3561 if ( strSessionName.isEmpty()
3562 && ulSessionID == UINT32_MAX)
3563 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3564 "No session ID specified!");
3565
3566 if ( !strSessionName.isEmpty()
3567 && ulSessionID != UINT32_MAX)
3568 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3569 "Either session ID or name (pattern) must be specified");
3570
3571 if (RT_FAILURE(vrc))
3572 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3573 "Invalid parameters specified");
3574
3575 HRESULT rc = S_OK;
3576
3577 ComPtr<IGuestSession> pSession;
3578 ComPtr<IGuestProcess> pProcess;
3579 do
3580 {
3581 uint32_t uProcsTerminated = 0;
3582 bool fSessionFound = false;
3583
3584 SafeIfaceArray <IGuestSession> collSessions;
3585 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3586 size_t cSessions = collSessions.size();
3587
3588 uint32_t uSessionsHandled = 0;
3589 for (size_t i = 0; i < cSessions; i++)
3590 {
3591 pSession = collSessions[i];
3592 Assert(!pSession.isNull());
3593
3594 ULONG uID; /* Session ID */
3595 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3596 Bstr strName;
3597 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3598 Utf8Str strNameUtf8(strName); /* Session name */
3599 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3600 {
3601 fSessionFound = uID == ulSessionID;
3602 }
3603 else /* ... or by naming pattern. */
3604 {
3605 if (RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()))
3606 fSessionFound = true;
3607 }
3608
3609 if (fSessionFound)
3610 {
3611 AssertStmt(!pSession.isNull(), break);
3612 uSessionsHandled++;
3613
3614 SafeIfaceArray <IGuestProcess> collProcs;
3615 CHECK_ERROR_BREAK(pSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcs)));
3616
3617 size_t cProcs = collProcs.size();
3618 for (size_t p = 0; p < cProcs; p++)
3619 {
3620 pProcess = collProcs[p];
3621 Assert(!pProcess.isNull());
3622
3623 ULONG uPID; /* Process ID */
3624 CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID));
3625
3626 bool fProcFound = false;
3627 for (size_t a = 0; a < vecPID.size(); a++) /* Slow, but works. */
3628 {
3629 fProcFound = vecPID[a] == uPID;
3630 if (fProcFound)
3631 break;
3632 }
3633
3634 if (fProcFound)
3635 {
3636 if (pCtx->fVerbose)
3637 RTPrintf("Terminating process (PID %RU32) (session ID %RU32) ...\n",
3638 uPID, uID);
3639 CHECK_ERROR_BREAK(pProcess, Terminate());
3640 uProcsTerminated++;
3641 }
3642 else
3643 {
3644 if (ulSessionID != UINT32_MAX)
3645 RTPrintf("No matching process(es) for session ID %RU32 found\n",
3646 ulSessionID);
3647 }
3648
3649 pProcess.setNull();
3650 }
3651
3652 pSession.setNull();
3653 }
3654 }
3655
3656 if (!uSessionsHandled)
3657 RTPrintf("No matching session(s) found\n");
3658
3659 if (uProcsTerminated)
3660 RTPrintf("%RU32 %s terminated\n",
3661 uProcsTerminated, uProcsTerminated == 1 ? "process" : "processes");
3662
3663 } while (0);
3664
3665 pProcess.setNull();
3666 pSession.setNull();
3667
3668 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3669}
3670
3671static DECLCALLBACK(RTEXITCODE) handleCtrlProcess(PGCTLCMDCTX pCtx)
3672{
3673 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3674
3675 if (pCtx->iArgc < 1)
3676 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3677 "Must specify an action");
3678
3679 /** Use RTGetOpt here when handling command line args gets more complex. */
3680
3681 if ( !RTStrICmp(pCtx->ppaArgv[0], "close")
3682 || !RTStrICmp(pCtx->ppaArgv[0], "kill")
3683 || !RTStrICmp(pCtx->ppaArgv[0], "terminate"))
3684 {
3685 pCtx->iFirstArgc++; /* Skip process action. */
3686 return handleCtrlProcessClose(pCtx);
3687 }
3688
3689 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_PROCESS,
3690 "Invalid process action '%s'", pCtx->ppaArgv[0]);
3691}
3692
3693static DECLCALLBACK(RTEXITCODE) handleCtrlSessionClose(PGCTLCMDCTX pCtx)
3694{
3695 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3696
3697 if (pCtx->iArgc < 1)
3698 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_SESSION,
3699 "Must specify at least a session to close");
3700
3701 static const RTGETOPTDEF s_aOptions[] =
3702 {
3703 { "--all", GETOPTDEF_SESSIONCLOSE_ALL, RTGETOPT_REQ_NOTHING },
3704 { "--session-id", 'i', RTGETOPT_REQ_UINT32 },
3705 { "--session-name", 'n', RTGETOPT_REQ_STRING }
3706 };
3707
3708 int ch;
3709 RTGETOPTUNION ValueUnion;
3710 RTGETOPTSTATE GetState;
3711 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3712 s_aOptions, RT_ELEMENTS(s_aOptions), pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3713
3714 ULONG ulSessionID = UINT32_MAX;
3715 Utf8Str strSessionName;
3716
3717 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
3718 {
3719 /* For options that require an argument, ValueUnion has received the value. */
3720 switch (ch)
3721 {
3722 case 'n': /* Session name pattern */
3723 strSessionName = ValueUnion.psz;
3724 break;
3725
3726 case 'i': /* Session ID */
3727 ulSessionID = ValueUnion.u32;
3728 break;
3729
3730 case GETOPTDEF_SESSIONCLOSE_ALL:
3731 strSessionName = "*";
3732 break;
3733
3734 case VINF_GETOPT_NOT_OPTION:
3735 /** @todo Supply a CSV list of IDs or patterns to close? */
3736 break;
3737
3738 default:
3739 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_SESSION, ch, &ValueUnion);
3740 }
3741 }
3742
3743 if ( strSessionName.isEmpty()
3744 && ulSessionID == UINT32_MAX)
3745 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_SESSION,
3746 "No session ID specified!");
3747
3748 if ( !strSessionName.isEmpty()
3749 && ulSessionID != UINT32_MAX)
3750 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_SESSION,
3751 "Either session ID or name (pattern) must be specified");
3752
3753 HRESULT rc = S_OK;
3754
3755 do
3756 {
3757 bool fSessionFound = false;
3758 size_t cSessionsHandled = 0;
3759
3760 SafeIfaceArray <IGuestSession> collSessions;
3761 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions)));
3762 size_t cSessions = collSessions.size();
3763
3764 for (size_t i = 0; i < cSessions; i++)
3765 {
3766 ComPtr<IGuestSession> pSession = collSessions[i];
3767 Assert(!pSession.isNull());
3768
3769 ULONG uID; /* Session ID */
3770 CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID));
3771 Bstr strName;
3772 CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam()));
3773 Utf8Str strNameUtf8(strName); /* Session name */
3774
3775 if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */
3776 {
3777 fSessionFound = uID == ulSessionID;
3778 }
3779 else /* ... or by naming pattern. */
3780 {
3781 if (RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()))
3782 fSessionFound = true;
3783 }
3784
3785 if (fSessionFound)
3786 {
3787 cSessionsHandled++;
3788
3789 Assert(!pSession.isNull());
3790 if (pCtx->fVerbose)
3791 RTPrintf("Closing guest session ID=#%RU32 \"%s\" ...\n",
3792 uID, strNameUtf8.c_str());
3793 CHECK_ERROR_BREAK(pSession, Close());
3794 if (pCtx->fVerbose)
3795 RTPrintf("Guest session successfully closed\n");
3796
3797 pSession.setNull();
3798 }
3799 }
3800
3801 if (!cSessionsHandled)
3802 {
3803 RTPrintf("No guest session(s) found\n");
3804 rc = E_ABORT; /* To set exit code accordingly. */
3805 }
3806
3807 } while (0);
3808
3809 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3810}
3811
3812static DECLCALLBACK(RTEXITCODE) handleCtrlSession(PGCTLCMDCTX pCtx)
3813{
3814 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3815
3816 if (pCtx->iArgc < 1)
3817 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_SESSION,
3818 "Must specify an action");
3819
3820 /** Use RTGetOpt here when handling command line args gets more complex. */
3821
3822 if ( !RTStrICmp(pCtx->ppaArgv[0], "close")
3823 || !RTStrICmp(pCtx->ppaArgv[0], "kill")
3824 || !RTStrICmp(pCtx->ppaArgv[0], "terminate"))
3825 {
3826 pCtx->iFirstArgc++; /* Skip session action. */
3827 return handleCtrlSessionClose(pCtx);
3828 }
3829
3830 return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_SESSION,
3831 "Invalid session action '%s'", pCtx->ppaArgv[0]);
3832}
3833
3834static DECLCALLBACK(RTEXITCODE) handleCtrlWatch(PGCTLCMDCTX pCtx)
3835{
3836 AssertPtrReturn(pCtx, RTEXITCODE_FAILURE);
3837
3838 /*
3839 * Parse arguments.
3840 */
3841 static const RTGETOPTDEF s_aOptions[] = { { 0 } };
3842
3843 int ch;
3844 RTGETOPTUNION ValueUnion;
3845 RTGETOPTSTATE GetState;
3846 RTGetOptInit(&GetState, pCtx->iArgc, pCtx->ppaArgv,
3847 NULL /*s_aOptions*/, 0 /*RT_ELEMENTS(s_aOptions)*/, pCtx->iFirstArgc, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3848
3849 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
3850 {
3851 /* For options that require an argument, ValueUnion has received the value. */
3852 switch (ch)
3853 {
3854 case VINF_GETOPT_NOT_OPTION:
3855 break;
3856
3857 default:
3858 return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_WATCH, ch, &ValueUnion);
3859 }
3860 }
3861
3862 /** @todo Specify categories to watch for. */
3863 /** @todo Specify a --timeout for waiting only for a certain amount of time? */
3864
3865 HRESULT rc;
3866
3867 try
3868 {
3869 ComObjPtr<GuestEventListenerImpl> pGuestListener;
3870 do
3871 {
3872 /* Listener creation. */
3873 pGuestListener.createObject();
3874 pGuestListener->init(new GuestEventListener());
3875
3876 /* Register for IGuest events. */
3877 ComPtr<IEventSource> es;
3878 CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(EventSource)(es.asOutParam()));
3879 com::SafeArray<VBoxEventType_T> eventTypes;
3880 eventTypes.push_back(VBoxEventType_OnGuestSessionRegistered);
3881 /** @todo Also register for VBoxEventType_OnGuestUserStateChanged on demand? */
3882 CHECK_ERROR_BREAK(es, RegisterListener(pGuestListener, ComSafeArrayAsInParam(eventTypes),
3883 true /* Active listener */));
3884 /* Note: All other guest control events have to be registered
3885 * as their corresponding objects appear. */
3886
3887 } while (0);
3888
3889 if (pCtx->fVerbose)
3890 RTPrintf("Waiting for events ...\n");
3891
3892 while (!g_fGuestCtrlCanceled)
3893 {
3894 /** @todo Timeout handling (see above)? */
3895 RTThreadSleep(10);
3896 }
3897
3898 if (pCtx->fVerbose)
3899 RTPrintf("Signal caught, exiting ...\n");
3900
3901 if (!pGuestListener.isNull())
3902 {
3903 /* Guest callback unregistration. */
3904 ComPtr<IEventSource> pES;
3905 CHECK_ERROR(pCtx->pGuest, COMGETTER(EventSource)(pES.asOutParam()));
3906 if (!pES.isNull())
3907 CHECK_ERROR(pES, UnregisterListener(pGuestListener));
3908 pGuestListener.setNull();
3909 }
3910 }
3911 catch (std::bad_alloc &)
3912 {
3913 rc = E_OUTOFMEMORY;
3914 }
3915
3916 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
3917}
3918
3919/**
3920 * Access the guest control store.
3921 *
3922 * @returns program exit code.
3923 * @note see the command line API description for parameters
3924 */
3925int handleGuestControl(HandlerArg *pArg)
3926{
3927 AssertPtrReturn(pArg, VERR_INVALID_POINTER);
3928
3929#ifdef DEBUG_andy_disabled
3930 if (RT_FAILURE(tstTranslatePath()))
3931 return RTEXITCODE_FAILURE;
3932#endif
3933
3934 /* pArg->argv[0] contains the VM name. */
3935 /* pArg->argv[1] contains the guest control command. */
3936 if (pArg->argc < 2)
3937 return errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
3938
3939 uint32_t uCmdCtxFlags = 0;
3940 uint32_t uUsage;
3941 GCTLCMD gctlCmd;
3942 if ( !RTStrICmp(pArg->argv[1], "exec")
3943 || !RTStrICmp(pArg->argv[1], "execute"))
3944 {
3945 gctlCmd.pfnHandler = handleCtrlProcessExec;
3946 uUsage = USAGE_GSTCTRL_EXEC;
3947 }
3948 else if (!RTStrICmp(pArg->argv[1], "copyfrom"))
3949 {
3950 gctlCmd.pfnHandler = handleCtrlCopyFrom;
3951 uUsage = USAGE_GSTCTRL_COPYFROM;
3952 }
3953 else if ( !RTStrICmp(pArg->argv[1], "copyto")
3954 || !RTStrICmp(pArg->argv[1], "cp"))
3955 {
3956 gctlCmd.pfnHandler = handleCtrlCopyTo;
3957 uUsage = USAGE_GSTCTRL_COPYTO;
3958 }
3959 else if ( !RTStrICmp(pArg->argv[1], "createdirectory")
3960 || !RTStrICmp(pArg->argv[1], "createdir")
3961 || !RTStrICmp(pArg->argv[1], "mkdir")
3962 || !RTStrICmp(pArg->argv[1], "md"))
3963 {
3964 gctlCmd.pfnHandler = handleCtrlCreateDirectory;
3965 uUsage = USAGE_GSTCTRL_CREATEDIR;
3966 }
3967 else if ( !RTStrICmp(pArg->argv[1], "removedirectory")
3968 || !RTStrICmp(pArg->argv[1], "removedir")
3969 || !RTStrICmp(pArg->argv[1], "rmdir"))
3970 {
3971 gctlCmd.pfnHandler = handleCtrlRemoveDirectory;
3972 uUsage = USAGE_GSTCTRL_REMOVEDIR;
3973 }
3974 else if ( !RTStrICmp(pArg->argv[1], "rm")
3975 || !RTStrICmp(pArg->argv[1], "removefile"))
3976 {
3977 gctlCmd.pfnHandler = handleCtrlRemoveFile;
3978 uUsage = USAGE_GSTCTRL_REMOVEFILE;
3979 }
3980 else if ( !RTStrICmp(pArg->argv[1], "ren")
3981 || !RTStrICmp(pArg->argv[1], "rename")
3982 || !RTStrICmp(pArg->argv[1], "mv"))
3983 {
3984 gctlCmd.pfnHandler = handleCtrlRename;
3985 uUsage = USAGE_GSTCTRL_RENAME;
3986 }
3987 else if ( !RTStrICmp(pArg->argv[1], "createtemporary")
3988 || !RTStrICmp(pArg->argv[1], "createtemp")
3989 || !RTStrICmp(pArg->argv[1], "mktemp"))
3990 {
3991 gctlCmd.pfnHandler = handleCtrlCreateTemp;
3992 uUsage = USAGE_GSTCTRL_CREATETEMP;
3993 }
3994 else if ( !RTStrICmp(pArg->argv[1], "kill") /* Linux. */
3995 || !RTStrICmp(pArg->argv[1], "pkill") /* Solaris / *BSD. */
3996 || !RTStrICmp(pArg->argv[1], "pskill")) /* SysInternals version. */
3997 {
3998 /** @todo What about "taskkill" on Windows? */
3999 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
4000 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
4001 gctlCmd.pfnHandler = handleCtrlProcessClose;
4002 uUsage = USAGE_GSTCTRL_KILL;
4003 }
4004 /** @todo Implement "killall"? */
4005 else if ( !RTStrICmp(pArg->argv[1], "stat"))
4006 {
4007 gctlCmd.pfnHandler = handleCtrlStat;
4008 uUsage = USAGE_GSTCTRL_STAT;
4009 }
4010 else if ( !RTStrICmp(pArg->argv[1], "updateadditions")
4011 || !RTStrICmp(pArg->argv[1], "updateadds"))
4012 {
4013 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
4014 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
4015 gctlCmd.pfnHandler = handleCtrlUpdateAdditions;
4016 uUsage = USAGE_GSTCTRL_UPDATEADDS;
4017 }
4018 else if ( !RTStrICmp(pArg->argv[1], "list"))
4019 {
4020 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
4021 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
4022 gctlCmd.pfnHandler = handleCtrlList;
4023 uUsage = USAGE_GSTCTRL_LIST;
4024 }
4025 else if ( !RTStrICmp(pArg->argv[1], "session"))
4026 {
4027 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
4028 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
4029 gctlCmd.pfnHandler = handleCtrlSession;
4030 uUsage = USAGE_GSTCTRL_SESSION;
4031 }
4032 else if ( !RTStrICmp(pArg->argv[1], "process"))
4033 {
4034 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
4035 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
4036 gctlCmd.pfnHandler = handleCtrlProcess;
4037 uUsage = USAGE_GSTCTRL_PROCESS;
4038 }
4039 else if ( !RTStrICmp(pArg->argv[1], "watch"))
4040 {
4041 uCmdCtxFlags = CTLCMDCTX_FLAGS_SESSION_ANONYMOUS
4042 | CTLCMDCTX_FLAGS_NO_SIGNAL_HANDLER;
4043 gctlCmd.pfnHandler = handleCtrlWatch;
4044 uUsage = USAGE_GSTCTRL_WATCH;
4045 }
4046 else
4047 return errorSyntax(USAGE_GUESTCONTROL, "Unknown sub command '%s' specified!", pArg->argv[1]);
4048
4049 GCTLCMDCTX cmdCtx;
4050 RT_ZERO(cmdCtx);
4051
4052 RTEXITCODE rcExit = ctrlInitVM(pArg, &cmdCtx, uCmdCtxFlags, uUsage);
4053 if (rcExit == RTEXITCODE_SUCCESS)
4054 {
4055 /* Kick off the actual command handler. */
4056 rcExit = gctlCmd.pfnHandler(&cmdCtx);
4057
4058 ctrlUninitVM(&cmdCtx, cmdCtx.uFlags);
4059 return rcExit;
4060 }
4061
4062 return rcExit;
4063}
4064#endif /* !VBOX_ONLY_DOCS */
4065
Note: See TracBrowser for help on using the repository browser.

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