VirtualBox

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

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

VBoxManage/VBoxManageGuestCtrl: Optional shorter list command options.

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

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