VirtualBox

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

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

VBoxManage/GuestCtrl: Progress handling additions, less verbose output.

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