VirtualBox

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

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

Frontends/VBoxManage: add options to specify passwords through a file

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