VirtualBox

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

Last change on this file since 40673 was 40398, checked in by vboxsync, 13 years ago

VBoxManage: don't crash if no subcommand was specified in 'VBoxManage guestcontrol'

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