VirtualBox

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

Last change on this file since 47601 was 47560, checked in by vboxsync, 12 years ago

FE/VBoxManage: Be a bit more informative.

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

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