VirtualBox

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

Last change on this file since 43421 was 43259, checked in by vboxsync, 12 years ago

fixed a few gcc false positive warnings and two shadowed declarations

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

© 2023 Oracle
ContactPrivacy policyTerms of Use