VirtualBox

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

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

VBoxManage: type mismatch

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