VirtualBox

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

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

not required anymore

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