VirtualBox

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

Last change on this file since 47678 was 47631, checked in by vboxsync, 11 years ago

VBoxManage/VBoxManageGuestCtrl.cpp: Added "watch" command for printing current guest control activity. Currently limited to session (un)registration. More to come.

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