VirtualBox

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

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

VBoxManage/GuestCtrl: Obey --dryrun, verbose adjustments.

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