VirtualBox

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

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

GuestCtrl: Added support for explicitly waiting on stdout/stderr, bugfixes, logging adjustments.

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