VirtualBox

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

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

VBoxManage/GuestCtrl: Addressed some todos.

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

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