VirtualBox

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

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

Build fix.

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