VirtualBox

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

Last change on this file since 42568 was 42551, checked in by vboxsync, 13 years ago

Main: big API naming cleanup, use all caps acronyms everywhere, including SDK docs
Frontends/VBoxManage: implement guestcontrol execute for new API, disabled by default

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

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