VirtualBox

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

Last change on this file since 38586 was 38586, checked in by vboxsync, 14 years ago

VBoxManage: Error checks.

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