VirtualBox

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

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

VBoxManageGuestCtrl: todo

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 56.8 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 36227 2011-03-09 14:04:25Z 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#ifdef USE_XPCOM_QUEUE
47# include <sys/select.h>
48# include <errno.h>
49#endif
50
51#include <signal.h>
52
53#ifdef RT_OS_DARWIN
54# include <CoreFoundation/CFRunLoop.h>
55#endif
56
57using namespace com;
58
59/**
60 * IVirtualBoxCallback implementation for handling the GuestControlCallback in
61 * relation to the "guestcontrol * wait" command.
62 */
63/** @todo */
64
65/** Set by the signal handler. */
66static volatile bool g_fGuestCtrlCanceled = false;
67
68/*
69 * Structure holding a directory entry.
70 */
71typedef struct DIRECTORYENTRY
72{
73 char *pszSourcePath;
74 char *pszDestPath;
75 RTLISTNODE Node;
76} DIRECTORYENTRY, *PDIRECTORYENTRY;
77
78/**
79 * Special exit codes for returning errors/information of a
80 * started guest process to the command line VBoxManage was started from.
81 * Useful for e.g. scripting.
82 *
83 * @note These are frozen as of 4.1.0.
84 */
85enum EXITCODEEXEC
86{
87 EXITCODEEXEC_SUCCESS = RTEXITCODE_SUCCESS,
88 /* Process exited normally but with an exit code <> 0. */
89 EXITCODEEXEC_CODE = 16,
90 EXITCODEEXEC_FAILED = 17,
91 EXITCODEEXEC_TERM_SIGNAL = 18,
92 EXITCODEEXEC_TERM_ABEND = 19,
93 EXITCODEEXEC_TIMEOUT = 20,
94 EXITCODEEXEC_DOWN = 21,
95 EXITCODEEXEC_CANCELED = 22
96};
97
98/**
99 * RTGetOpt-IDs for the guest execution control command line.
100 */
101enum GETOPTDEF_EXEC
102{
103 GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000,
104 GETOPTDEF_EXEC_OUTPUTFORMAT,
105 GETOPTDEF_EXEC_DOS2UNIX,
106 GETOPTDEF_EXEC_UNIX2DOS,
107 GETOPTDEF_EXEC_WAITFOREXIT,
108 GETOPTDEF_EXEC_WAITFORSTDOUT,
109 GETOPTDEF_EXEC_WAITFORSTDERR
110};
111
112enum OUTPUTTYPE
113{
114 OUTPUTTYPE_UNDEFINED = 0,
115 OUTPUTTYPE_DOS2UNIX = 10,
116 OUTPUTTYPE_UNIX2DOS = 20
117};
118
119#endif /* VBOX_ONLY_DOCS */
120
121void usageGuestControl(PRTSTREAM pStrm)
122{
123 RTStrmPrintf(pStrm,
124 "VBoxManage guestcontrol <vmname>|<uuid>\n"
125 " exec[ute]\n"
126 " --image <path to program>\n"
127 " --username <name> --password <password>\n"
128 " [--dos2unix]\n"
129 " [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
130 " [--timeout <msec>] [--unix2dos] [--verbose]\n"
131 " [--wait-exit] [--wait-stdout] [--wait-stdout]\n"
132 //" [--output-format=<dos>|<unix>]\n"
133 " [--output-type=<binary>|<text>]\n"
134 " [-- [<argument1>] ... [<argumentN>]\n"
135 /** @todo Add a "--" parameter (has to be last parameter) to directly execute
136 * stuff, e.g. "VBoxManage guestcontrol execute <VMName> --username <> ... -- /bin/rm -Rf /foo". */
137 "\n"
138 " copyto|cp\n"
139 " <source on host> <destination on guest>\n"
140 " --username <name> --password <password>\n"
141 " [--dryrun] [--follow] [--recursive] [--verbose]\n"
142 "\n"
143 " createdir[ectory]|mkdir|md\n"
144 " <directory to create on guest>\n"
145 " --username <name> --password <password>\n"
146 " [--parents] [--mode <mode>] [--verbose]\n"
147 "\n"
148 " updateadditions\n"
149 " [--source <guest additions .ISO>] [--verbose]\n"
150 "\n");
151}
152
153#ifndef VBOX_ONLY_DOCS
154
155/**
156 * Signal handler that sets g_fGuestCtrlCanceled.
157 *
158 * This can be executed on any thread in the process, on Windows it may even be
159 * a thread dedicated to delivering this signal. Do not doing anything
160 * unnecessary here.
161 */
162static void guestCtrlSignalHandler(int iSignal)
163{
164 NOREF(iSignal);
165 ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
166}
167
168/**
169 * Installs a custom signal handler to get notified
170 * whenever the user wants to intercept the program.
171 */
172static void ctrlSignalHandlerInstall()
173{
174 signal(SIGINT, guestCtrlSignalHandler);
175#ifdef SIGBREAK
176 signal(SIGBREAK, guestCtrlSignalHandler);
177#endif
178}
179
180/**
181 * Uninstalls a previously installed signal handler.
182 */
183static void ctrlSignalHandlerUninstall()
184{
185 signal(SIGINT, SIG_DFL);
186#ifdef SIGBREAK
187 signal(SIGBREAK, SIG_DFL);
188#endif
189}
190
191/**
192 * Translates a process status to a human readable
193 * string.
194 */
195static const char *ctrlExecProcessStatusToText(ExecuteProcessStatus_T enmStatus)
196{
197 switch (enmStatus)
198 {
199 case ExecuteProcessStatus_Started:
200 return "started";
201 case ExecuteProcessStatus_TerminatedNormally:
202 return "successfully terminated";
203 case ExecuteProcessStatus_TerminatedSignal:
204 return "terminated by signal";
205 case ExecuteProcessStatus_TerminatedAbnormally:
206 return "abnormally aborted";
207 case ExecuteProcessStatus_TimedOutKilled:
208 return "timed out";
209 case ExecuteProcessStatus_TimedOutAbnormally:
210 return "timed out, hanging";
211 case ExecuteProcessStatus_Down:
212 return "killed";
213 case ExecuteProcessStatus_Error:
214 return "error";
215 default:
216 break;
217 }
218 return "unknown";
219}
220
221static int ctrlExecProcessStatusToExitCode(ExecuteProcessStatus_T enmStatus, ULONG uExitCode)
222{
223 int rc = EXITCODEEXEC_SUCCESS;
224 switch (enmStatus)
225 {
226 case ExecuteProcessStatus_Started:
227 rc = EXITCODEEXEC_SUCCESS;
228 break;
229 case ExecuteProcessStatus_TerminatedNormally:
230 rc = !uExitCode ? EXITCODEEXEC_SUCCESS : EXITCODEEXEC_CODE;
231 break;
232 case ExecuteProcessStatus_TerminatedSignal:
233 rc = EXITCODEEXEC_TERM_SIGNAL;
234 break;
235 case ExecuteProcessStatus_TerminatedAbnormally:
236 rc = EXITCODEEXEC_TERM_ABEND;
237 break;
238 case ExecuteProcessStatus_TimedOutKilled:
239 rc = EXITCODEEXEC_TIMEOUT;
240 break;
241 case ExecuteProcessStatus_TimedOutAbnormally:
242 rc = EXITCODEEXEC_TIMEOUT;
243 break;
244 case ExecuteProcessStatus_Down:
245 /* Service/OS is stopping, process was killed, so
246 * not exactly an error of the started process ... */
247 rc = EXITCODEEXEC_DOWN;
248 break;
249 case ExecuteProcessStatus_Error:
250 rc = EXITCODEEXEC_FAILED;
251 break;
252 default:
253 AssertMsgFailed(("Unknown exit code (%u) from guest process returned!\n", enmStatus));
254 break;
255 }
256 return rc;
257}
258
259static int ctrlPrintError(com::ErrorInfo &errorInfo)
260{
261 if ( errorInfo.isFullAvailable()
262 || errorInfo.isBasicAvailable())
263 {
264 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
265 * because it contains more accurate info about what went wrong. */
266 if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
267 RTMsgError("%ls.", errorInfo.getText().raw());
268 else
269 {
270 RTMsgError("Error details:");
271 GluePrintErrorInfo(errorInfo);
272 }
273 return VERR_GENERAL_FAILURE; /** @todo */
274 }
275 AssertMsgFailedReturn(("Object has indicated no error!?\n"),
276 VERR_INVALID_PARAMETER);
277}
278
279static int ctrlPrintError(IUnknown *pObj, const GUID &aIID)
280{
281 com::ErrorInfo ErrInfo(pObj, aIID);
282 return ctrlPrintError(ErrInfo);
283}
284
285
286static int ctrlPrintProgressError(ComPtr<IProgress> progress)
287{
288 int rc;
289 BOOL fCanceled;
290 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
291 && fCanceled)
292 {
293 rc = VERR_CANCELLED;
294 }
295 else
296 {
297 com::ProgressErrorInfo ErrInfo(progress);
298 rc = ctrlPrintError(ErrInfo);
299 }
300 return rc;
301}
302
303/**
304 * Un-initializes the VM after guest control usage.
305 */
306static void ctrlUninitVM(HandlerArg *pArg)
307{
308 AssertPtrReturnVoid(pArg);
309 if (pArg->session)
310 pArg->session->UnlockMachine();
311}
312
313/**
314 * Initializes the VM for IGuest operations.
315 *
316 * That is, checks whether it's up and running, if it can be locked (shared
317 * only) and returns a valid IGuest pointer on success.
318 *
319 * @return IPRT status code.
320 * @param pArg Our command line argument structure.
321 * @param pszNameOrId The VM's name or UUID.
322 * @param pGuest Where to return the IGuest interface pointer.
323 */
324static int ctrlInitVM(HandlerArg *pArg, const char *pszNameOrId, ComPtr<IGuest> *pGuest)
325{
326 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
327 AssertPtrReturn(pszNameOrId, VERR_INVALID_PARAMETER);
328
329 /* Lookup VM. */
330 ComPtr<IMachine> machine;
331 /* Assume it's an UUID. */
332 HRESULT rc;
333 CHECK_ERROR(pArg->virtualBox, FindMachine(Bstr(pszNameOrId).raw(),
334 machine.asOutParam()));
335 if (FAILED(rc))
336 return VERR_NOT_FOUND;
337
338 /* Machine is running? */
339 MachineState_T machineState;
340 CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
341 if (machineState != MachineState_Running)
342 {
343 RTMsgError("Machine \"%s\" is not running (currently %s)!\n",
344 pszNameOrId, machineStateToName(machineState, false));
345 return VERR_VM_INVALID_VM_STATE;
346 }
347
348 do
349 {
350 /* Open a session for the VM. */
351 CHECK_ERROR_BREAK(machine, LockMachine(pArg->session, LockType_Shared));
352 /* Get the associated console. */
353 ComPtr<IConsole> console;
354 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Console)(console.asOutParam()));
355 /* ... and session machine. */
356 ComPtr<IMachine> sessionMachine;
357 CHECK_ERROR_BREAK(pArg->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
358 /* Get IGuest interface. */
359 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pGuest->asOutParam()));
360 } while (0);
361
362 if (FAILED(rc))
363 ctrlUninitVM(pArg);
364 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
365}
366
367/* <Missing docuemntation> */
368static int handleCtrlExecProgram(ComPtr<IGuest> guest, HandlerArg *pArg)
369{
370 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
371
372 /*
373 * Parse arguments.
374 */
375 if (pArg->argc < 2) /* At least the command we want to execute in the guest should be present :-). */
376 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
377
378 static const RTGETOPTDEF s_aOptions[] =
379 {
380 { "--dos2unix", GETOPTDEF_EXEC_DOS2UNIX, RTGETOPT_REQ_NOTHING },
381 { "--environment", 'e', RTGETOPT_REQ_STRING },
382 { "--flags", 'f', RTGETOPT_REQ_STRING },
383 { "--ignore-operhaned-processes", GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES, RTGETOPT_REQ_NOTHING },
384 { "--image", 'i', RTGETOPT_REQ_STRING },
385 { "--password", 'p', RTGETOPT_REQ_STRING },
386 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
387 { "--unix2dos", GETOPTDEF_EXEC_UNIX2DOS, RTGETOPT_REQ_NOTHING },
388 { "--username", 'u', RTGETOPT_REQ_STRING },
389 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
390 { "--wait-exit", GETOPTDEF_EXEC_WAITFOREXIT, RTGETOPT_REQ_NOTHING },
391 { "--wait-stdout", GETOPTDEF_EXEC_WAITFORSTDOUT, RTGETOPT_REQ_NOTHING },
392 { "--wait-stderr", GETOPTDEF_EXEC_WAITFORSTDERR, RTGETOPT_REQ_NOTHING }
393 };
394
395 int ch;
396 RTGETOPTUNION ValueUnion;
397 RTGETOPTSTATE GetState;
398 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
399
400 Utf8Str Utf8Cmd;
401 uint32_t fFlags = 0;
402 com::SafeArray<IN_BSTR> args;
403 com::SafeArray<IN_BSTR> env;
404 Utf8Str Utf8UserName;
405 Utf8Str Utf8Password;
406 uint32_t cMsTimeout = 0;
407 OUTPUTTYPE eOutputType = OUTPUTTYPE_UNDEFINED;
408 bool fOutputBinary = false;
409 bool fWaitForExit = false;
410 bool fWaitForStdOut = false;
411 bool fWaitForStdErr = false;
412 bool fVerbose = false;
413
414 int vrc = VINF_SUCCESS;
415 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
416 && RT_SUCCESS(vrc))
417 {
418 /* For options that require an argument, ValueUnion has received the value. */
419 switch (ch)
420 {
421 case GETOPTDEF_EXEC_DOS2UNIX:
422 if (eOutputType != OUTPUTTYPE_UNDEFINED)
423 return errorSyntax(USAGE_GUESTCONTROL, "More than one output type (dos2unix/unix2dos) specified!");
424 eOutputType = OUTPUTTYPE_DOS2UNIX;
425 break;
426
427 case 'e': /* Environment */
428 {
429 char **papszArg;
430 int cArgs;
431
432 vrc = RTGetOptArgvFromString(&papszArg, &cArgs, ValueUnion.psz, NULL);
433 if (RT_FAILURE(vrc))
434 return errorSyntax(USAGE_GUESTCONTROL, "Failed to parse environment value, rc=%Rrc", vrc);
435 for (int j = 0; j < cArgs; j++)
436 env.push_back(Bstr(papszArg[j]).raw());
437
438 RTGetOptArgvFree(papszArg);
439 break;
440 }
441
442 case GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES:
443 fFlags |= ExecuteProcessFlag_IgnoreOrphanedProcesses;
444 break;
445
446 case 'i':
447 Utf8Cmd = ValueUnion.psz;
448 break;
449
450 /** @todo Add a hidden flag. */
451
452 case 'p': /* Password */
453 Utf8Password = ValueUnion.psz;
454 break;
455
456 case 't': /* Timeout */
457 cMsTimeout = ValueUnion.u32;
458 break;
459
460 case GETOPTDEF_EXEC_UNIX2DOS:
461 if (eOutputType != OUTPUTTYPE_UNDEFINED)
462 return errorSyntax(USAGE_GUESTCONTROL, "More than one output type (dos2unix/unix2dos) specified!");
463 eOutputType = OUTPUTTYPE_UNIX2DOS;
464 break;
465
466 case 'u': /* User name */
467 Utf8UserName = ValueUnion.psz;
468 break;
469
470 case 'v': /* Verbose */
471 fVerbose = true;
472 break;
473
474 case GETOPTDEF_EXEC_WAITFOREXIT:
475 fWaitForExit = true;
476 break;
477
478 case GETOPTDEF_EXEC_WAITFORSTDOUT:
479 fWaitForExit = true;
480 fWaitForStdOut = true;
481 break;
482
483 case GETOPTDEF_EXEC_WAITFORSTDERR:
484 fWaitForExit = true;
485 fWaitForStdErr = true;
486 break;
487
488 case VINF_GETOPT_NOT_OPTION:
489 {
490#if 0 /** @todo r=bird: enable this when the argv[0] issue has been addressed. */
491 if (args.size() == 0 && Utf8Cmd.isEmpty())
492 Utf8Cmd = ValueUnion.psz;
493 args.push_back(Bstr(ValueUnion.psz).raw());
494#else
495 if (Utf8Cmd.isEmpty())
496 {
497 Utf8Cmd = ValueUnion.psz;
498 if (Utf8Cmd.isEmpty())
499 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The first argument cannot be empty yet, wait for 4.1.0");
500 }
501 else
502 args.push_back(Bstr(ValueUnion.psz).raw());
503#endif
504 break;
505 }
506
507 default:
508 return RTGetOptPrintError(ch, &ValueUnion);
509 }
510 }
511
512 if (Utf8Cmd.isEmpty())
513 return errorSyntax(USAGE_GUESTCONTROL, "No command to execute specified!");
514
515 if (Utf8UserName.isEmpty())
516 return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
517
518 /*
519 * <missing comment indicating that we're done parsing args and started doing something else>
520 */
521 HRESULT rc = S_OK;
522 if (fVerbose)
523 {
524 if (cMsTimeout == 0)
525 RTPrintf("Waiting for guest to start process ...\n");
526 else
527 RTPrintf("Waiting for guest to start process (within %ums)\n", cMsTimeout);
528 }
529
530 /* Get current time stamp to later calculate rest of timeout left. */
531 uint64_t u64StartMS = RTTimeMilliTS();
532
533 /* Execute the process. */
534 int rcProc; /** @todo: Not always initialized! */
535 ComPtr<IProgress> progress;
536 ULONG uPID = 0;
537 rc = guest->ExecuteProcess(Bstr(Utf8Cmd).raw(),
538 fFlags,
539 ComSafeArrayAsInParam(args),
540 ComSafeArrayAsInParam(env),
541 Bstr(Utf8UserName).raw(),
542 Bstr(Utf8Password).raw(),
543 cMsTimeout,
544 &uPID,
545 progress.asOutParam());
546 if (FAILED(rc))
547 return ctrlPrintError(guest, COM_IIDOF(IGuest));
548
549 if (fVerbose)
550 RTPrintf("Process '%s' (PID: %u) started\n", Utf8Cmd.c_str(), uPID);
551 if (fWaitForExit)
552 {
553 if (fVerbose)
554 {
555 if (cMsTimeout) /* Wait with a certain timeout. */
556 {
557 /* Calculate timeout value left after process has been started. */
558 uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
559 /* Is timeout still bigger than current difference? */
560 if (cMsTimeout > u64Elapsed)
561 RTPrintf("Waiting for process to exit (%ums left) ...\n", cMsTimeout - u64Elapsed);
562 else
563 RTPrintf("No time left to wait for process!\n"); /** @todo a bit misleading ... */
564 }
565 else /* Wait forever. */
566 RTPrintf("Waiting for process to exit ...\n");
567 }
568
569 /* Setup signal handling if cancelable. */
570 ASSERT(progress);
571 bool fCanceledAlready = false;
572 BOOL fCancelable;
573 HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
574 if (FAILED(hrc))
575 fCancelable = FALSE;
576 if (fCancelable)
577 ctrlSignalHandlerInstall();
578
579 /* Wait for process to exit ... */
580 BOOL fCompleted = FALSE;
581 BOOL fCanceled = FALSE;
582 int cMilliesSleep = 0;
583 while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted))))
584 {
585 SafeArray<BYTE> aOutputData;
586 ULONG cbOutputData = 0;
587
588 /*
589 * Some data left to output?
590 */
591 if ( fWaitForStdOut
592 || fWaitForStdErr)
593 {
594 /** @todo r=bird: The timeout argument is bogus in several
595 * ways:
596 * 1. RT_MAX will evaluate the arguments twice, which may
597 * result in different values because RTTimeMilliTS()
598 * returns a higher value the 2nd time. Worst case:
599 * Imagine when RT_MAX calculates the remaining time
600 * out (first expansion) there is say 60 ms left. Then
601 * we're preempted and rescheduled after, say, 120 ms.
602 * We call RTTimeMilliTS() again and ends up with a
603 * value -60 ms, which translate to a UINT32_MAX - 59
604 * ms timeout.
605 *
606 * 2. When the period expires, we will wait forever since
607 * both 0 and -1 mean indefinite timeout with this API,
608 * at least that's one way of reading the main code.
609 *
610 * 3. There is a signed/unsigned ambiguity in the
611 * RT_MAX expression. The left hand side is signed
612 * integer (0), the right side is unsigned 64-bit. From
613 * what I can tell, the compiler will treat this as
614 * unsigned 64-bit and never return 0.
615 */
616 /** @todo r=bird: We must separate stderr and stdout
617 * output, seems bunched together here which
618 * won't do the trick for unix BOFHs. */
619 rc = guest->GetProcessOutput(uPID, 0 /* aFlags */,
620 RT_MAX(0, cMsTimeout - (RTTimeMilliTS() - u64StartMS)) /* Timeout in ms */,
621 _64K, ComSafeArrayAsOutParam(aOutputData));
622 if (FAILED(rc))
623 {
624 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
625 cbOutputData = 0;
626 }
627 else
628 {
629 cbOutputData = aOutputData.size();
630 if (cbOutputData > 0)
631 {
632 /** @todo r=bird: Use a VFS I/O stream filter for doing this, it's a
633 * generic problem and the new VFS APIs will handle it more
634 * transparently. (requires writing dos2unix/unix2dos filters ofc) */
635 if (eOutputType != OUTPUTTYPE_UNDEFINED)
636 {
637 /*
638 * If aOutputData is text data from the guest process' stdout or stderr,
639 * it has a platform dependent line ending. So standardize on
640 * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
641 * Windows. Otherwise we end up with CR/CR/LF on Windows.
642 */
643 ULONG cbOutputDataPrint = cbOutputData;
644 for (BYTE *s = aOutputData.raw(), *d = s;
645 s - aOutputData.raw() < (ssize_t)cbOutputData;
646 s++, d++)
647 {
648 if (*s == '\r')
649 {
650 /* skip over CR, adjust destination */
651 d--;
652 cbOutputDataPrint--;
653 }
654 else if (s != d)
655 *d = *s;
656 }
657 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
658 }
659 else /* Just dump all data as we got it ... */
660 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputData);
661 }
662 }
663 }
664
665 /* No more output data left? */
666 if (cbOutputData <= 0)
667 {
668 /* Only break out from process handling loop if we processed (displayed)
669 * all output data or if there simply never was output data and the process
670 * has been marked as complete. */
671 if (fCompleted)
672 break;
673 }
674
675 /* Process async cancelation */
676 if (g_fGuestCtrlCanceled && !fCanceledAlready)
677 {
678 hrc = progress->Cancel();
679 if (SUCCEEDED(hrc))
680 fCanceledAlready = TRUE;
681 else
682 g_fGuestCtrlCanceled = false;
683 }
684
685 /* Progress canceled by Main API? */
686 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
687 && fCanceled)
688 break;
689
690 /* Did we run out of time? */
691 if ( cMsTimeout
692 && RTTimeMilliTS() - u64StartMS > cMsTimeout)
693 {
694 progress->Cancel();
695 break;
696 }
697 } /* while */
698
699 /* Undo signal handling */
700 if (fCancelable)
701 ctrlSignalHandlerUninstall();
702
703 /* Report status back to the user. */
704 if (fCanceled)
705 {
706 if (fVerbose)
707 RTPrintf("Process execution canceled!\n");
708 rcProc = EXITCODEEXEC_CANCELED;
709 }
710 else if ( fCompleted
711 && SUCCEEDED(rc)) /* The GetProcessOutput rc. */
712 {
713 LONG iRc;
714 CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
715 if (FAILED(iRc))
716 vrc = ctrlPrintProgressError(progress);
717 else
718 {
719 ExecuteProcessStatus_T retStatus;
720 ULONG uRetExitCode, uRetFlags;
721 rc = guest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &retStatus);
722 if (SUCCEEDED(rc) && fVerbose)
723 RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, retStatus, ctrlExecProcessStatusToText(retStatus), uRetFlags);
724 rcProc = ctrlExecProcessStatusToExitCode(retStatus, uRetExitCode);
725 }
726 }
727 else
728 {
729 if (fVerbose)
730 RTPrintf("Process execution aborted!\n");
731 rcProc = EXITCODEEXEC_TERM_ABEND;
732 }
733 }
734
735 if (RT_FAILURE(vrc) || FAILED(rc))
736 return RTEXITCODE_FAILURE;
737 return rcProc;
738}
739
740/**
741 * Appends a new file/directory entry to a given list.
742 *
743 * @return IPRT status code.
744 * @param pszFileSource Full qualified source path of file to copy (optional).
745 * @param pszFileDest Full qualified destination path (optional).
746 * @param pList Copy list used for insertion.
747 *
748 * @todo r=bird: Since everyone is maintaining an entry count, it would make
749 * sense to abstract the list. To simplify cleanup work, it would be
750 * preferable to use a standard C++ container template class.
751 */
752static int ctrlDirectoryEntryAppend(const char *pszFileSource, const char *pszFileDest,
753 PRTLISTNODE pList)
754{
755 AssertPtrReturn(pList, VERR_INVALID_POINTER);
756 AssertReturn(pszFileSource || pszFileDest, VERR_INVALID_PARAMETER);
757
758 PDIRECTORYENTRY pNode = (PDIRECTORYENTRY)RTMemAlloc(sizeof(DIRECTORYENTRY));
759 if (pNode == NULL)
760 return VERR_NO_MEMORY;
761
762 pNode->pszSourcePath = NULL;
763 pNode->pszDestPath = NULL;
764
765 if (pszFileSource)
766 {
767 pNode->pszSourcePath = RTStrDup(pszFileSource);
768 AssertPtrReturn(pNode->pszSourcePath, VERR_NO_MEMORY);
769 }
770 if (pszFileDest)
771 {
772 pNode->pszDestPath = RTStrDup(pszFileDest);
773 AssertPtrReturn(pNode->pszDestPath, VERR_NO_MEMORY);
774 }
775
776 pNode->Node.pPrev = NULL;
777 pNode->Node.pNext = NULL;
778 RTListAppend(pList, &pNode->Node);
779 return VINF_SUCCESS;
780}
781
782/**
783 * Destroys a directory list.
784 *
785 * @param pList Pointer to list to destroy.
786 */
787static void ctrlDirectoryListDestroy(PRTLISTNODE pList)
788{
789 AssertPtr(pList);
790
791 /* Destroy file list. */
792 PDIRECTORYENTRY pNode = RTListGetFirst(pList, DIRECTORYENTRY, Node);
793 while (pNode)
794 {
795 PDIRECTORYENTRY pNext = RTListNodeGetNext(&pNode->Node, DIRECTORYENTRY, Node);
796 bool fLast = RTListNodeIsLast(pList, &pNode->Node);
797
798 if (pNode->pszSourcePath)
799 RTStrFree(pNode->pszSourcePath);
800 if (pNode->pszDestPath)
801 RTStrFree(pNode->pszDestPath);
802 RTListNodeRemove(&pNode->Node);
803 RTMemFree(pNode);
804
805 if (fLast)
806 break;
807
808 pNode = pNext;
809 }
810}
811
812
813/**
814 * Reads a specified directory (recursively) based on the copy flags
815 * and appends all matching entries to the supplied list.
816 *
817 * @return IPRT status code.
818 * @param pszRootDir Directory to start with. Must end with
819 * a trailing slash and must be absolute.
820 * @param pszSubDir Sub directory part relative to the root
821 * directory; needed for recursion.
822 * @param pszFilter Search filter (e.g. *.pdf).
823 * @param pszDest Destination directory.
824 * @param fFlags Copy flags.
825 * @param pcObjects Where to store the overall objects to
826 * copy found.
827 * @param pList Pointer to the object list to use.
828 */
829static int ctrlCopyDirectoryRead(const char *pszRootDir, const char *pszSubDir,
830 const char *pszFilter, const char *pszDest,
831 uint32_t fFlags, uint32_t *pcObjects, PRTLISTNODE pList)
832{
833 AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
834 /* Sub directory is optional. */
835 /* Filter directory is optional. */
836 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
837 AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
838 AssertPtrReturn(pList, VERR_INVALID_POINTER);
839
840 /*
841 * Construct current path.
842 */
843 char szCurDir[RTPATH_MAX];
844 int rc = RTStrCopy(szCurDir, sizeof(szCurDir), pszRootDir);
845 if (RT_SUCCESS(rc) && pszSubDir != NULL)
846 rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
847
848 /*
849 * Open directory without a filter - RTDirOpenFiltered unfortunately
850 * cannot handle sub directories so we have to do the filtering ourselves.
851 */
852 PRTDIR pDir = NULL;
853 if (RT_SUCCESS(rc))
854 {
855 rc = RTDirOpen(&pDir, szCurDir);
856 if (RT_FAILURE(rc))
857 pDir = NULL;
858 }
859 if (RT_SUCCESS(rc))
860 {
861 /*
862 * Enumerate the directory tree.
863 */
864 while (RT_SUCCESS(rc))
865 {
866 RTDIRENTRY DirEntry;
867 rc = RTDirRead(pDir, &DirEntry, NULL);
868 if (RT_FAILURE(rc))
869 {
870 if (rc == VERR_NO_MORE_FILES)
871 rc = VINF_SUCCESS;
872 break;
873 }
874 switch (DirEntry.enmType)
875 {
876 case RTDIRENTRYTYPE_DIRECTORY:
877 /* Skip "." and ".." entries. */
878 if ( !strcmp(DirEntry.szName, ".")
879 || !strcmp(DirEntry.szName, ".."))
880 break;
881
882 if (fFlags & CopyFileFlag_Recursive)
883 {
884 char *pszNewSub = NULL;
885 if (pszSubDir)
886 RTStrAPrintf(&pszNewSub, "%s%s/", pszSubDir, DirEntry.szName);
887 else
888 RTStrAPrintf(&pszNewSub, "%s/", DirEntry.szName);
889
890 if (pszNewSub)
891 {
892 rc = ctrlCopyDirectoryRead(pszRootDir, pszNewSub,
893 pszFilter, pszDest,
894 fFlags, pcObjects, pList);
895 RTStrFree(pszNewSub);
896 }
897 else
898 rc = VERR_NO_MEMORY;
899 }
900 break;
901
902 case RTDIRENTRYTYPE_SYMLINK:
903 if ( (fFlags & CopyFileFlag_Recursive)
904 && (fFlags & CopyFileFlag_FollowLinks))
905 {
906 /* Fall through to next case is intentional. */
907 }
908 else
909 break;
910
911 case RTDIRENTRYTYPE_FILE:
912 {
913 if ( !pszFilter
914 || RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
915 {
916 char *pszFileSource = NULL;
917 char *pszFileDest = NULL;
918 if (RTStrAPrintf(&pszFileSource, "%s%s%s",
919 pszRootDir, pszSubDir ? pszSubDir : "",
920 DirEntry.szName) >= 0)
921 {
922 if (RTStrAPrintf(&pszFileDest, "%s%s%s",
923 pszDest, pszSubDir ? pszSubDir : "",
924 DirEntry.szName) <= 0)
925 {
926 rc = VERR_NO_MEMORY;
927 }
928 }
929 else
930 rc = VERR_NO_MEMORY;
931
932 if (RT_SUCCESS(rc))
933 {
934 rc = ctrlDirectoryEntryAppend(pszFileSource, pszFileDest, pList);
935 if (RT_SUCCESS(rc))
936 *pcObjects += 1;
937 }
938
939 if (pszFileSource)
940 RTStrFree(pszFileSource);
941 if (pszFileDest)
942 RTStrFree(pszFileDest);
943 }
944 }
945 break;
946
947 default:
948 break;
949 }
950 if (RT_FAILURE(rc))
951 break;
952 }
953
954 RTDirClose(pDir);
955 }
956 return rc;
957}
958
959/**
960 * Initializes the copy process and builds up an object list
961 * with all required information to start the actual copy process.
962 *
963 * @return IPRT status code.
964 * @param pszSource Source path on host to use.
965 * @param pszDest Destination path on guest to use.
966 * @param fFlags Copy flags.
967 * @param pcObjects Where to store the count of objects to be copied.
968 * @param pList Where to store the object list.
969 */
970static int ctrlCopyInit(const char *pszSource, const char *pszDest, uint32_t fFlags,
971 uint32_t *pcObjects, PRTLISTNODE pList)
972{
973 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
974 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
975 AssertPtrReturn(pcObjects, VERR_INVALID_PARAMETER);
976 AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
977
978 int rc = VINF_SUCCESS;
979 char *pszSourceAbs = RTPathAbsDup(pszSource);
980 if (pszSourceAbs)
981 {
982 if ( RTPathFilename(pszSourceAbs)
983 && RTFileExists(pszSourceAbs)) /* We have a single file ... */
984 {
985 char *pszDestAbs = RTStrDup(pszDest);
986 if (pszDestAbs)
987 {
988 /* Do we have a trailing slash for the destination?
989 * Then this is a directory ... */
990 size_t cch = strlen(pszDestAbs);
991 if ( cch > 1
992 && ( RTPATH_IS_SLASH(pszDestAbs[cch - 1])
993 || RTPATH_IS_SLASH(pszDestAbs[cch - 2])
994 )
995 )
996 {
997 rc = RTStrAAppend(&pszDestAbs, RTPathFilename(pszSourceAbs));
998 }
999 else
1000 {
1001 /* Since the desetination seems not to be a directory,
1002 * we assume that this is the absolute path to the destination
1003 * file -> nothing to do here ... */
1004 }
1005
1006 if (RT_SUCCESS(rc))
1007 {
1008 RTListInit(pList);
1009 rc = ctrlDirectoryEntryAppend(pszSourceAbs, pszDestAbs, pList);
1010 *pcObjects = 1;
1011 }
1012 RTStrFree(pszDestAbs);
1013 }
1014 else
1015 rc = VERR_NO_MEMORY;
1016 }
1017 else /* ... or a directory. */
1018 {
1019 /* Append trailing slash to absolute directory. */
1020 if (RTDirExists(pszSourceAbs))
1021 RTStrAAppend(&pszSourceAbs, RTPATH_SLASH_STR);
1022
1023 /* Extract directory filter (e.g. "*.exe"). */
1024 char *pszFilter = RTPathFilename(pszSourceAbs);
1025 char *pszSourceAbsRoot = RTStrDup(pszSourceAbs);
1026 char *pszDestAbs = RTStrDup(pszDest);
1027 if ( pszSourceAbsRoot
1028 && pszDestAbs)
1029 {
1030 if (pszFilter)
1031 {
1032 RTPathStripFilename(pszSourceAbsRoot);
1033 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
1034 }
1035 else
1036 {
1037 /*
1038 * If we have more than one file to copy, make sure that we have
1039 * a trailing slash so that we can construct a full path name
1040 * (e.g. "foo.txt" -> "c:/foo/temp.txt") as destination.
1041 */
1042 size_t cch = strlen(pszSourceAbsRoot);
1043 if ( cch > 1
1044 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 1])
1045 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 2]))
1046 {
1047 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
1048 }
1049 }
1050
1051 if (RT_SUCCESS(rc))
1052 {
1053 /*
1054 * Make sure we have a valid destination path. All we can do
1055 * here is to check whether we have a trailing slash -- the rest
1056 * (i.e. path creation, rights etc.) needs to be done inside the guest.
1057 */
1058 size_t cch = strlen(pszDestAbs);
1059 if ( cch > 1
1060 && !RTPATH_IS_SLASH(pszDestAbs[cch - 1])
1061 && !RTPATH_IS_SLASH(pszDestAbs[cch - 2]))
1062 {
1063 rc = RTStrAAppend(&pszDestAbs, RTPATH_SLASH_STR);
1064 }
1065 }
1066
1067 if (RT_SUCCESS(rc))
1068 {
1069 RTListInit(pList);
1070 rc = ctrlCopyDirectoryRead(pszSourceAbsRoot, NULL /* Sub directory */,
1071 pszFilter, pszDestAbs,
1072 fFlags, pcObjects, pList);
1073 if (RT_SUCCESS(rc) && *pcObjects == 0)
1074 rc = VERR_NOT_FOUND;
1075 }
1076
1077 if (pszDestAbs)
1078 RTStrFree(pszDestAbs);
1079 if (pszSourceAbsRoot)
1080 RTStrFree(pszSourceAbsRoot);
1081 }
1082 else
1083 rc = VERR_NO_MEMORY;
1084 }
1085 RTStrFree(pszSourceAbs);
1086 }
1087 else
1088 rc = VERR_NO_MEMORY;
1089 return rc;
1090}
1091
1092/**
1093 * Copys a file from host to the guest.
1094 *
1095 * @return IPRT status code.
1096 * @param pGuest IGuest interface pointer.
1097 * @param fVerbose Verbose flag.
1098 * @param pszSource Source path of existing host file to copy.
1099 * @param pszDest Destination path on guest to copy the file to.
1100 * @param pszUserName User name on guest to use for the copy operation.
1101 * @param pszPassword Password of user account.
1102 * @param fFlags Copy flags.
1103 */
1104static int ctrlCopyFileToGuest(IGuest *pGuest, bool fVerbose, const char *pszSource, const char *pszDest,
1105 const char *pszUserName, const char *pszPassword,
1106 uint32_t fFlags)
1107{
1108 AssertPtrReturn(pGuest, VERR_INVALID_PARAMETER);
1109 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
1110 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
1111 AssertPtrReturn(pszUserName, VERR_INVALID_PARAMETER);
1112 AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER);
1113
1114 int vrc = VINF_SUCCESS;
1115 ComPtr<IProgress> progress;
1116 HRESULT rc = pGuest->CopyToGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
1117 Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
1118 fFlags, progress.asOutParam());
1119 if (FAILED(rc))
1120 vrc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
1121 else
1122 {
1123 rc = showProgress(progress);
1124 if (FAILED(rc))
1125 vrc = ctrlPrintProgressError(progress);
1126 }
1127 return vrc;
1128}
1129
1130static int handleCtrlCopyTo(ComPtr<IGuest> guest, HandlerArg *pArg)
1131{
1132 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1133
1134 /** @todo r=bird: This command isn't very unix friendly in general. mkdir
1135 * is much better (partly because it is much simpler of course). The main
1136 * arguments against this is that (1) all but two options conflicts with
1137 * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is
1138 * done windows CMD style (though not in a 100% compatible way), and (3)
1139 * that only one source is allowed - efficiently sabotaging default
1140 * wildcard expansion by a unix shell. The best solution here would be
1141 * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */
1142 /** @todo r=bird: Why isn't IGuest::CopyToGuest powerful enough to do
1143 * all this recursive copying? */
1144
1145 static const RTGETOPTDEF s_aOptions[] =
1146 {
1147 { "--dryrun", 'd', RTGETOPT_REQ_NOTHING }, /**< @todo r=bird: '-d' incompatible with GNU cp. */
1148 { "--follow", 'F', RTGETOPT_REQ_NOTHING }, /**< @todo r=bird: This isn't a GNU cp option. This is instead spread over several options. */
1149 { "--password", 'p', RTGETOPT_REQ_STRING }, /**< @todo r=bird: Just drop these short options since (BSD, GNU, ++) cp uses '-p' to indicate that ownership, timestamp and other stuff should be preserved. Other file commands probably use '-p' as well. */
1150 { "--recursive", 'R', RTGETOPT_REQ_NOTHING }, /**< @todo r=bird: Most cp implementations treats '-r' as an alias for '-R'. */
1151 { "--username", 'u', RTGETOPT_REQ_STRING }, /**< @todo r=bird: Just drop these short options since GNU cp uses '-u' to indicate update-only (as does 4nt). Other file commands probably uses '-u' as well. */
1152 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1153 };
1154
1155 int ch;
1156 RTGETOPTUNION ValueUnion;
1157 RTGETOPTSTATE GetState;
1158 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1159
1160 Utf8Str Utf8Source;
1161 Utf8Str Utf8Dest;
1162 Utf8Str Utf8UserName;
1163 Utf8Str Utf8Password;
1164 uint32_t fFlags = CopyFileFlag_None;
1165 bool fVerbose = false;
1166 bool fCopyRecursive = false;
1167 bool fDryRun = false;
1168
1169 int vrc = VINF_SUCCESS;
1170 uint32_t idxNonOption = 0;
1171 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1172 {
1173 /* For options that require an argument, ValueUnion has received the value. */
1174 switch (ch)
1175 {
1176 case 'd': /* Dry run */
1177 fDryRun = true;
1178 break;
1179
1180 case 'F': /* Follow symlinks */
1181 fFlags |= CopyFileFlag_FollowLinks;
1182 break;
1183
1184 case 'p': /* Password */
1185 Utf8Password = ValueUnion.psz;
1186 break;
1187
1188 case 'R': /* Recursive processing */
1189 fFlags |= CopyFileFlag_Recursive;
1190 break;
1191
1192 case 'u': /* User name */
1193 Utf8UserName = ValueUnion.psz;
1194 break;
1195
1196 case 'v': /* Verbose */
1197 fVerbose = true;
1198 break;
1199
1200 case VINF_GETOPT_NOT_OPTION:
1201 {
1202 /* Get the actual source + destination. */
1203 switch (idxNonOption)
1204 {
1205 case 0:
1206 Utf8Source = ValueUnion.psz;
1207 break;
1208
1209 case 1:
1210 Utf8Dest = ValueUnion.psz;
1211 break;
1212
1213 /* @todo r=bird: VBoxManage guestcontrol execute <VMName> copyto hostfile.1 hostfile.2 hostfile.3 guestdir/
1214 * A better way to implement this would be to tell RTGetOptInit to sort the argument, so
1215 * that when VINF_GETOPT_NOT_OPTION is returned, the remainder of pArg->argv are all arguments. */
1216
1217 default:
1218 return errorSyntax(USAGE_GUESTCONTROL, "Too many parameters specified, only source and destination allowed!");
1219 }
1220 idxNonOption++;
1221 if (idxNonOption == UINT32_MAX)
1222 return errorSyntax(USAGE_GUESTCONTROL, "Too many files specified!");
1223 break;
1224 }
1225
1226 default:
1227 return RTGetOptPrintError(ch, &ValueUnion);
1228 }
1229 }
1230
1231 if (Utf8Source.isEmpty())
1232 return errorSyntax(USAGE_GUESTCONTROL,
1233 "No source specified!");
1234
1235 if (Utf8Dest.isEmpty())
1236 return errorSyntax(USAGE_GUESTCONTROL,
1237 "No destination specified!");
1238
1239 if (Utf8UserName.isEmpty())
1240 return errorSyntax(USAGE_GUESTCONTROL,
1241 "No user name specified!");
1242
1243 /*
1244 * Done parsing arguments, do stuff.
1245 */
1246 HRESULT rc = S_OK;
1247 if (fVerbose)
1248 {
1249 if (fDryRun)
1250 RTPrintf("Dry run - no files copied!\n");
1251 RTPrintf("Gathering file information ...\n");
1252 }
1253
1254 RTLISTNODE listToCopy;
1255 uint32_t cObjects = 0;
1256 vrc = ctrlCopyInit(Utf8Source.c_str(), Utf8Dest.c_str(), fFlags,
1257 &cObjects, &listToCopy);
1258 if (RT_FAILURE(vrc))
1259 {
1260 switch (vrc)
1261 {
1262 case VERR_NOT_FOUND:
1263 RTMsgError("No files to copy found!\n");
1264 break;
1265
1266 case VERR_FILE_NOT_FOUND:
1267 RTMsgError("Source path \"%s\" not found!\n", Utf8Source.c_str());
1268 break;
1269
1270 default:
1271 RTMsgError("Failed to initialize, rc=%Rrc\n", vrc);
1272 break;
1273 }
1274 }
1275 else
1276 {
1277 PDIRECTORYENTRY pNode;
1278 if (RT_SUCCESS(vrc))
1279 {
1280 if (fVerbose)
1281 {
1282 if (fCopyRecursive)
1283 RTPrintf("Recursively copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1284 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1285 else
1286 RTPrintf("Copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1287 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1288 }
1289
1290 uint32_t iObject = 1;
1291 RTListForEach(&listToCopy, pNode, DIRECTORYENTRY, Node)
1292 {
1293 if (!fDryRun)
1294 {
1295 if (fVerbose)
1296 RTPrintf("Copying \"%s\" to \"%s\" (%u/%u) ...\n",
1297 pNode->pszSourcePath, pNode->pszDestPath, iObject, cObjects);
1298 /* Finally copy the desired file (if no dry run selected). */
1299 if (!fDryRun)
1300 vrc = ctrlCopyFileToGuest(guest, fVerbose, pNode->pszSourcePath, pNode->pszDestPath,
1301 Utf8UserName.c_str(), Utf8Password.c_str(), fFlags);
1302 }
1303 if (RT_FAILURE(vrc))
1304 break;
1305 iObject++;
1306 }
1307 if (RT_SUCCESS(vrc) && fVerbose)
1308 RTPrintf("Copy operation successful!\n");
1309 }
1310 ctrlDirectoryListDestroy(&listToCopy);
1311 }
1312
1313 if (RT_FAILURE(vrc))
1314 rc = VBOX_E_IPRT_ERROR;
1315 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1316}
1317
1318static int handleCtrlCreateDirectory(ComPtr<IGuest> guest, HandlerArg *pArg)
1319{
1320 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1321
1322 /*
1323 * Parse arguments.
1324 *
1325 * Note! No direct returns here, everyone must go thru the cleanup at the
1326 * end of this function.
1327 */
1328 static const RTGETOPTDEF s_aOptions[] =
1329 {
1330 { "--mode", 'm', RTGETOPT_REQ_UINT32 },
1331 { "--parents", 'P', RTGETOPT_REQ_NOTHING },
1332 { "--password", 'p', RTGETOPT_REQ_STRING },
1333 { "--username", 'u', RTGETOPT_REQ_STRING },
1334 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1335 };
1336
1337 int ch;
1338 RTGETOPTUNION ValueUnion;
1339 RTGETOPTSTATE GetState;
1340 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1341
1342 Utf8Str Utf8UserName;
1343 Utf8Str Utf8Password;
1344 uint32_t fFlags = CreateDirectoryFlag_None;
1345 uint32_t fDirMode = 0; /* 0 == default mode, right? */
1346 bool fVerbose = false;
1347
1348 RTLISTNODE listDirs;
1349 uint32_t cDirs = 0;
1350 RTListInit(&listDirs);
1351
1352 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1353 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1354 && rcExit == RTEXITCODE_SUCCESS)
1355 {
1356 /* For options that require an argument, ValueUnion has received the value. */
1357 switch (ch)
1358 {
1359 case 'm': /* Mode */
1360 fDirMode = ValueUnion.u32;
1361 break;
1362
1363 case 'P': /* Create parents */
1364 fFlags |= CreateDirectoryFlag_Parents;
1365 break;
1366
1367 case 'p': /* Password */
1368 Utf8Password = ValueUnion.psz;
1369 break;
1370
1371 case 'u': /* User name */
1372 Utf8UserName = ValueUnion.psz;
1373 break;
1374
1375 case 'v': /* Verbose */
1376 fVerbose = true;
1377 break;
1378
1379 case VINF_GETOPT_NOT_OPTION:
1380 {
1381/** @todo r=bird: Simplify by letting RTGetOptInit sort the argv, just like
1382 * for cp. */
1383 int vrc = ctrlDirectoryEntryAppend(NULL, /* No source given */
1384 ValueUnion.psz, /* Destination */
1385 &listDirs);
1386 if (RT_SUCCESS(vrc))
1387 {
1388 cDirs++;
1389 if (cDirs == UINT32_MAX)
1390 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many directories specified! Aborting.");
1391 }
1392 else
1393 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to append directory: %Rrc", vrc);
1394 break;
1395 }
1396
1397 default:
1398 rcExit = RTGetOptPrintError(ch, &ValueUnion);
1399 break;
1400 }
1401 }
1402
1403 if (rcExit == RTEXITCODE_SUCCESS && !cDirs)
1404 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No directory to create specified!");
1405
1406 if (rcExit == RTEXITCODE_SUCCESS && Utf8UserName.isEmpty())
1407 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
1408
1409 if (rcExit == RTEXITCODE_SUCCESS)
1410 {
1411 /*
1412 * Create the directories.
1413 */
1414 HRESULT hrc = S_OK;
1415 if (fVerbose && cDirs > 1)
1416 RTPrintf("Creating %u directories ...\n", cDirs);
1417
1418 PDIRECTORYENTRY pNode;
1419 RTListForEach(&listDirs, pNode, DIRECTORYENTRY, Node)
1420 {
1421 if (fVerbose)
1422 RTPrintf("Creating directory \"%s\" ...\n", pNode->pszDestPath);
1423
1424 ComPtr<IProgress> progress;
1425 hrc = guest->CreateDirectory(Bstr(pNode->pszDestPath).raw(),
1426 Bstr(Utf8UserName).raw(), Bstr(Utf8Password).raw(),
1427 fDirMode, fFlags, progress.asOutParam());
1428 if (FAILED(hrc))
1429 {
1430 ctrlPrintError(guest, COM_IIDOF(IGuest)); /* (return code ignored, save original rc) */
1431 break;
1432 }
1433 }
1434 if (FAILED(hrc))
1435 rcExit = RTEXITCODE_FAILURE;
1436 }
1437
1438 /*
1439 * Clean up and return.
1440 */
1441 ctrlDirectoryListDestroy(&listDirs);
1442
1443 return rcExit;
1444}
1445
1446static int handleCtrlUpdateAdditions(ComPtr<IGuest> guest, HandlerArg *pArg)
1447{
1448 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1449
1450 /*
1451 * Check the syntax. We can deduce the correct syntax from the number of
1452 * arguments.
1453 */
1454 Utf8Str Utf8Source;
1455 bool fVerbose = false;
1456
1457 static const RTGETOPTDEF s_aOptions[] =
1458 {
1459 { "--source", 's', RTGETOPT_REQ_STRING },
1460 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1461 };
1462
1463 int ch;
1464 RTGETOPTUNION ValueUnion;
1465 RTGETOPTSTATE GetState;
1466 RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1467
1468 int vrc = VINF_SUCCESS;
1469 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1470 && RT_SUCCESS(vrc))
1471 {
1472 switch (ch)
1473 {
1474 case 's':
1475 Utf8Source = ValueUnion.psz;
1476 break;
1477
1478 case 'v':
1479 fVerbose = true;
1480 break;
1481
1482 default:
1483 return RTGetOptPrintError(ch, &ValueUnion);
1484 }
1485 }
1486
1487 if (fVerbose)
1488 RTPrintf("Updating Guest Additions ...\n");
1489
1490#ifdef DEBUG_andy
1491 if (Utf8Source.isEmpty())
1492 Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
1493#endif
1494
1495 /* Determine source if not set yet. */
1496 if (Utf8Source.isEmpty())
1497 {
1498 char strTemp[RTPATH_MAX];
1499 vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
1500 AssertRC(vrc);
1501 Utf8Str Utf8Src1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
1502
1503 vrc = RTPathExecDir(strTemp, sizeof(strTemp));
1504 AssertRC(vrc);
1505 Utf8Str Utf8Src2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
1506
1507 /* Check the standard image locations */
1508 if (RTFileExists(Utf8Src1.c_str()))
1509 Utf8Source = Utf8Src1;
1510 else if (RTFileExists(Utf8Src2.c_str()))
1511 Utf8Source = Utf8Src2;
1512 else
1513 {
1514 RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
1515 vrc = VERR_FILE_NOT_FOUND;
1516 }
1517 }
1518 else if (!RTFileExists(Utf8Source.c_str()))
1519 {
1520 RTMsgError("Source \"%s\" does not exist!\n", Utf8Source.c_str());
1521 vrc = VERR_FILE_NOT_FOUND;
1522 }
1523
1524 if (RT_SUCCESS(vrc))
1525 {
1526 if (fVerbose)
1527 RTPrintf("Using source: %s\n", Utf8Source.c_str());
1528
1529 HRESULT rc = S_OK;
1530 ComPtr<IProgress> progress;
1531 CHECK_ERROR(guest, UpdateGuestAdditions(Bstr(Utf8Source).raw(),
1532 /* Wait for whole update process to complete. */
1533 AdditionsUpdateFlag_None,
1534 progress.asOutParam()));
1535 if (FAILED(rc))
1536 vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
1537 else
1538 {
1539 rc = showProgress(progress);
1540 if (FAILED(rc))
1541 vrc = ctrlPrintProgressError(progress);
1542 else if (fVerbose)
1543 RTPrintf("Guest Additions update successful.\n");
1544 }
1545 }
1546
1547 return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1548}
1549
1550/**
1551 * Access the guest control store.
1552 *
1553 * @returns program exit code.
1554 * @note see the command line API description for parameters
1555 */
1556int handleGuestControl(HandlerArg *pArg)
1557{
1558 AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
1559
1560 HandlerArg arg = *pArg;
1561 arg.argc = pArg->argc - 2; /* Skip VM name and sub command. */
1562 arg.argv = pArg->argv + 2; /* Same here. */
1563
1564 ComPtr<IGuest> guest;
1565 int vrc = ctrlInitVM(pArg, pArg->argv[0] /* VM Name */, &guest);
1566 if (RT_SUCCESS(vrc))
1567 {
1568 int rcExit;
1569 if ( !strcmp(pArg->argv[1], "exec")
1570 || !strcmp(pArg->argv[1], "execute"))
1571 {
1572 rcExit = handleCtrlExecProgram(guest, &arg);
1573 }
1574 else if ( !strcmp(pArg->argv[1], "copyto")
1575 || !strcmp(pArg->argv[1], "cp"))
1576 {
1577 rcExit = handleCtrlCopyTo(guest, &arg);
1578 }
1579 else if ( !strcmp(pArg->argv[1], "createdirectory")
1580 || !strcmp(pArg->argv[1], "createdir")
1581 || !strcmp(pArg->argv[1], "mkdir")
1582 || !strcmp(pArg->argv[1], "md"))
1583 {
1584 rcExit = handleCtrlCreateDirectory(guest, &arg);
1585 }
1586 else if ( !strcmp(pArg->argv[1], "updateadditions")
1587 || !strcmp(pArg->argv[1], "updateadds"))
1588 {
1589 rcExit = handleCtrlUpdateAdditions(guest, &arg);
1590 }
1591 else
1592 rcExit = errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
1593
1594 ctrlUninitVM(pArg);
1595 return rcExit;
1596 }
1597 return RTEXITCODE_FAILURE;
1598}
1599
1600#endif /* !VBOX_ONLY_DOCS */
1601
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