VirtualBox

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

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

FE/VBoxManage/GuestCtrl: Implemented handling guest file, process and session (un)registration and status changes when using "watch".

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