VirtualBox

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

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

Guest Additions update: Don't wait when showing modal progress dialog in GUI.

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