VirtualBox

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

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

VBoxManage/GuestCtrl: Check for completion.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette