VirtualBox

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

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

Forward-ported r84173 from 4.2: FE/VBoxManage/GuestCtrl: Do UTF-8 conversion on process execution output.

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