VirtualBox

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

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

Guest Control 2.0: Update.

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