VirtualBox

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

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

VBoxManage: warnings

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.6 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 33589 2010-10-29 06:58:22Z 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 " updateadditions <vmname>|<uuid>\n"
99 " [--source <guest additions .ISO file to use>] [--verbose]\n"
100 "\n");
101}
102
103#ifndef VBOX_ONLY_DOCS
104
105/**
106 * Signal handler that sets g_fCanceled.
107 *
108 * This can be executed on any thread in the process, on Windows it may even be
109 * a thread dedicated to delivering this signal. Do not doing anything
110 * unnecessary here.
111 */
112static void ctrlExecProcessSignalHandler(int iSignal)
113{
114 NOREF(iSignal);
115 ASMAtomicWriteBool(&g_fExecCanceled, true);
116}
117
118static const char *ctrlExecGetStatus(ULONG uStatus)
119{
120 switch (uStatus)
121 {
122 case guestControl::PROC_STS_STARTED:
123 return "started";
124 case guestControl::PROC_STS_TEN:
125 return "successfully terminated";
126 case guestControl::PROC_STS_TES:
127 return "terminated by signal";
128 case guestControl::PROC_STS_TEA:
129 return "abnormally aborted";
130 case guestControl::PROC_STS_TOK:
131 return "timed out";
132 case guestControl::PROC_STS_TOA:
133 return "timed out, hanging";
134 case guestControl::PROC_STS_DWN:
135 return "killed";
136 case guestControl::PROC_STS_ERROR:
137 return "error";
138 default:
139 return "unknown";
140 }
141}
142
143static int handleCtrlExecProgram(HandlerArg *a)
144{
145 /*
146 * Check the syntax. We can deduce the correct syntax from the number of
147 * arguments.
148 */
149 if (a->argc < 2) /* At least the command we want to execute in the guest should be present :-). */
150 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
151
152 Utf8Str Utf8Cmd(a->argv[1]);
153 uint32_t uFlags = 0;
154 /* Note: this uses IN_BSTR as it must be BSTR on COM and CBSTR on XPCOM */
155 com::SafeArray<IN_BSTR> args;
156 com::SafeArray<IN_BSTR> env;
157 Utf8Str Utf8UserName;
158 Utf8Str Utf8Password;
159 uint32_t u32TimeoutMS = 0;
160 bool fWaitForExit = false;
161 bool fWaitForStdOut = false;
162 bool fWaitForStdErr = false;
163 bool fVerbose = false;
164 bool fTimeout = false;
165
166 /* Always use the actual command line as argv[0]. */
167 args.push_back(Bstr(Utf8Cmd).raw());
168
169 /* Iterate through all possible commands (if available). */
170 bool usageOK = true;
171 for (int i = 2; usageOK && i < a->argc; i++)
172 {
173 if ( !strcmp(a->argv[i], "--arguments")
174 || !strcmp(a->argv[i], "--args")
175 || !strcmp(a->argv[i], "--arg"))
176 {
177 if (i + 1 >= a->argc)
178 usageOK = false;
179 else
180 {
181 char **papszArg;
182 int cArgs;
183
184 int vrc = RTGetOptArgvFromString(&papszArg, &cArgs, a->argv[i + 1], NULL);
185 if (RT_SUCCESS(vrc))
186 {
187 for (int j = 0; j < cArgs; j++)
188 args.push_back(Bstr(papszArg[j]).raw());
189
190 RTGetOptArgvFree(papszArg);
191 }
192 ++i;
193 }
194 }
195 else if ( !strcmp(a->argv[i], "--environment")
196 || !strcmp(a->argv[i], "--env"))
197 {
198 if (i + 1 >= a->argc)
199 usageOK = false;
200 else
201 {
202 char **papszArg;
203 int cArgs;
204
205 int vrc = RTGetOptArgvFromString(&papszArg, &cArgs, a->argv[i + 1], NULL);
206 if (RT_SUCCESS(vrc))
207 {
208 for (int j = 0; j < cArgs; j++)
209 env.push_back(Bstr(papszArg[j]).raw());
210
211 RTGetOptArgvFree(papszArg);
212 }
213 ++i;
214 }
215 }
216 else if (!strcmp(a->argv[i], "--flags"))
217 {
218 if (i + 1 >= a->argc)
219 usageOK = false;
220 else
221 {
222 /** @todo Needs a bit better processing as soon as we have more flags. */
223 if (!strcmp(a->argv[i + 1], "ignoreorphanedprocesses"))
224 uFlags |= ExecuteProcessFlag_IgnoreOrphanedProcesses;
225 else
226 usageOK = false;
227 ++i;
228 }
229 }
230 else if ( !strcmp(a->argv[i], "--username")
231 || !strcmp(a->argv[i], "--user"))
232 {
233 if (i + 1 >= a->argc)
234 usageOK = false;
235 else
236 {
237 Utf8UserName = a->argv[i + 1];
238 ++i;
239 }
240 }
241 else if ( !strcmp(a->argv[i], "--password")
242 || !strcmp(a->argv[i], "--pwd"))
243 {
244 if (i + 1 >= a->argc)
245 usageOK = false;
246 else
247 {
248 Utf8Password = a->argv[i + 1];
249 ++i;
250 }
251 }
252 else if (!strcmp(a->argv[i], "--timeout"))
253 {
254 if ( i + 1 >= a->argc
255 || RTStrToUInt32Full(a->argv[i + 1], 10, &u32TimeoutMS) != VINF_SUCCESS
256 || u32TimeoutMS == 0)
257 {
258 usageOK = false;
259 }
260 else
261 {
262 fTimeout = true;
263 ++i;
264 }
265 }
266 else if (!strcmp(a->argv[i], "--wait-for"))
267 {
268 if (i + 1 >= a->argc)
269 usageOK = false;
270 else
271 {
272 if (!strcmp(a->argv[i + 1], "exit"))
273 fWaitForExit = true;
274 else if (!strcmp(a->argv[i + 1], "stdout"))
275 {
276 fWaitForExit = true;
277 fWaitForStdOut = true;
278 }
279 else if (!strcmp(a->argv[i + 1], "stderr"))
280 {
281 fWaitForExit = true;
282 fWaitForStdErr = true;
283 }
284 else
285 usageOK = false;
286 ++i;
287 }
288 }
289 else if (!strcmp(a->argv[i], "--verbose"))
290 fVerbose = true;
291 /** @todo Add fancy piping stuff here. */
292 else
293 return errorSyntax(USAGE_GUESTCONTROL,
294 "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
295 }
296
297 if (!usageOK)
298 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
299
300 if (Utf8UserName.isEmpty())
301 return errorSyntax(USAGE_GUESTCONTROL,
302 "No user name specified!");
303
304 /* lookup VM. */
305 ComPtr<IMachine> machine;
306 HRESULT rc;
307 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
308 machine.asOutParam()));
309 if (machine)
310 {
311 do
312 {
313 /* open an existing session for VM */
314 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Shared));
315 // @todo r=dj assert that it's an existing session
316
317 /* get the mutable session machine */
318 a->session->COMGETTER(Machine)(machine.asOutParam());
319
320 /* get the associated console */
321 ComPtr<IConsole> console;
322 CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam()));
323
324 ComPtr<IGuest> guest;
325 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(guest.asOutParam()));
326
327 ComPtr<IProgress> progress;
328 ULONG uPID = 0;
329
330 if (fVerbose)
331 {
332 if (u32TimeoutMS == 0)
333 RTPrintf("Waiting for guest to start process ...\n");
334 else
335 RTPrintf("Waiting for guest to start process (within %ums)\n", u32TimeoutMS);
336 }
337
338 /* Get current time stamp to later calculate rest of timeout left. */
339 uint64_t u64StartMS = RTTimeMilliTS();
340
341 /* Execute the process. */
342 rc = guest->ExecuteProcess(Bstr(Utf8Cmd).raw(), uFlags,
343 ComSafeArrayAsInParam(args),
344 ComSafeArrayAsInParam(env),
345 Bstr(Utf8UserName).raw(),
346 Bstr(Utf8Password).raw(), u32TimeoutMS,
347 &uPID, progress.asOutParam());
348 if (FAILED(rc))
349 {
350 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
351 * because it contains more accurate info about what went wrong. */
352 ErrorInfo info(guest, COM_IIDOF(IGuest));
353 if (info.isFullAvailable())
354 {
355 if (rc == VBOX_E_IPRT_ERROR)
356 RTMsgError("%ls.", info.getText().raw());
357 else
358 RTMsgError("%ls (%Rhrc).", info.getText().raw(), info.getResultCode());
359 }
360 break;
361 }
362 if (fVerbose)
363 RTPrintf("Process '%s' (PID: %u) started\n", Utf8Cmd.c_str(), uPID);
364 if (fWaitForExit)
365 {
366 if (fTimeout)
367 {
368 /* Calculate timeout value left after process has been started. */
369 uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
370 /* Is timeout still bigger than current difference? */
371 if (u32TimeoutMS > u64Elapsed)
372 {
373 u32TimeoutMS -= (uint32_t)u64Elapsed;
374 if (fVerbose)
375 RTPrintf("Waiting for process to exit (%ums left) ...\n", u32TimeoutMS);
376 }
377 else
378 {
379 if (fVerbose)
380 RTPrintf("No time left to wait for process!\n");
381 }
382 }
383 else if (fVerbose)
384 RTPrintf("Waiting for process to exit ...\n");
385
386 /* Setup signal handling if cancelable. */
387 ASSERT(progress);
388 bool fCanceledAlready = false;
389 BOOL fCancelable;
390 HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
391 if (FAILED(hrc))
392 fCancelable = FALSE;
393 if (fCancelable)
394 {
395 signal(SIGINT, ctrlExecProcessSignalHandler);
396 #ifdef SIGBREAK
397 signal(SIGBREAK, ctrlExecProcessSignalHandler);
398 #endif
399 }
400
401 /* Wait for process to exit ... */
402 BOOL fCompleted = FALSE;
403 BOOL fCanceled = FALSE;
404 while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted))))
405 {
406 SafeArray<BYTE> aOutputData;
407 ULONG cbOutputData = 0;
408
409 /*
410 * Some data left to output?
411 */
412 if ( fWaitForStdOut
413 || fWaitForStdErr)
414 {
415 rc = guest->GetProcessOutput(uPID, 0 /* aFlags */,
416 u32TimeoutMS, _64K, ComSafeArrayAsOutParam(aOutputData));
417 if (FAILED(rc))
418 {
419 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
420 * because it contains more accurate info about what went wrong. */
421 ErrorInfo info(guest, COM_IIDOF(IGuest));
422 if (info.isFullAvailable())
423 {
424 if (rc == VBOX_E_IPRT_ERROR)
425 RTMsgError("%ls.", info.getText().raw());
426 else
427 RTMsgError("%ls (%Rhrc).", info.getText().raw(), info.getResultCode());
428 }
429 cbOutputData = 0;
430 fCompleted = true; /* rc contains a failure, so we'll go into aborted state down below. */
431 }
432 else
433 {
434 cbOutputData = aOutputData.size();
435 if (cbOutputData > 0)
436 {
437 /* aOutputData has a platform dependent line ending, standardize on
438 * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
439 * Windows. Otherwise we end up with CR/CR/LF on Windows. */
440 ULONG cbOutputDataPrint = cbOutputData;
441 for (BYTE *s = aOutputData.raw(), *d = s;
442 s - aOutputData.raw() < (ssize_t)cbOutputData;
443 s++, d++)
444 {
445 if (*s == '\r')
446 {
447 /* skip over CR, adjust destination */
448 d--;
449 cbOutputDataPrint--;
450 }
451 else if (s != d)
452 *d = *s;
453 }
454 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
455 }
456 }
457 }
458 if (cbOutputData <= 0) /* No more output data left? */
459 {
460 if (fCompleted)
461 break;
462
463 if ( fTimeout
464 && RTTimeMilliTS() - u64StartMS > u32TimeoutMS + 5000)
465 {
466 progress->Cancel();
467 break;
468 }
469 }
470
471 /* Process async cancelation */
472 if (g_fExecCanceled && !fCanceledAlready)
473 {
474 hrc = progress->Cancel();
475 if (SUCCEEDED(hrc))
476 fCanceledAlready = TRUE;
477 else
478 g_fExecCanceled = false;
479 }
480
481 /* Progress canceled by Main API? */
482 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
483 && fCanceled)
484 {
485 break;
486 }
487 }
488
489 /* Undo signal handling */
490 if (fCancelable)
491 {
492 signal(SIGINT, SIG_DFL);
493 #ifdef SIGBREAK
494 signal(SIGBREAK, SIG_DFL);
495 #endif
496 }
497
498 if (fCanceled)
499 {
500 if (fVerbose)
501 RTPrintf("Process execution canceled!\n");
502 }
503 else if ( fCompleted
504 && SUCCEEDED(rc))
505 {
506 LONG iRc = false;
507 CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
508 if (FAILED(iRc))
509 {
510 com::ProgressErrorInfo info(progress);
511 if ( info.isFullAvailable()
512 || info.isBasicAvailable())
513 {
514 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
515 * because it contains more accurate info about what went wrong. */
516 if (info.getResultCode() == VBOX_E_IPRT_ERROR)
517 RTMsgError("%ls.", info.getText().raw());
518 else
519 {
520 RTMsgError("Process error details:");
521 GluePrintErrorInfo(info);
522 }
523 }
524 else
525 com::GluePrintRCMessage(iRc);
526 }
527 else if (fVerbose)
528 {
529 ULONG uRetStatus, uRetExitCode, uRetFlags;
530 rc = guest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &uRetStatus);
531 if (SUCCEEDED(rc))
532 RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, uRetStatus, ctrlExecGetStatus(uRetStatus), uRetFlags);
533 }
534 }
535 else
536 {
537 if (fVerbose)
538 RTPrintf("Process execution aborted!\n");
539 }
540 }
541 a->session->UnlockMachine();
542 } while (0);
543 }
544 return SUCCEEDED(rc) ? 0 : 1;
545}
546
547/**
548 * Signal handler that sets g_fCopyCanceled.
549 *
550 * This can be executed on any thread in the process, on Windows it may even be
551 * a thread dedicated to delivering this signal. Do not doing anything
552 * unnecessary here.
553 */
554static void ctrlCopySignalHandler(int iSignal)
555{
556 NOREF(iSignal);
557 ASMAtomicWriteBool(&g_fCopyCanceled, true);
558}
559
560
561int ctrlCopyDirectoryEntryAppend(const char *pszFileSource, const char *pszFileDest,
562 PRTLISTNODE pList)
563{
564 AssertPtrReturn(pszFileSource, VERR_INVALID_POINTER);
565 AssertPtrReturn(pszFileDest, VERR_INVALID_POINTER);
566 AssertPtrReturn(pList, VERR_INVALID_POINTER);
567
568 PDIRECTORYENTRY pNode = (PDIRECTORYENTRY)RTMemAlloc(sizeof(DIRECTORYENTRY));
569 if (pNode == NULL)
570 return VERR_NO_MEMORY;
571
572 pNode->pszSourcePath = NULL;
573 pNode->pszDestPath = NULL;
574 if (RT_SUCCESS(RTStrAAppend(&pNode->pszSourcePath, pszFileSource)))
575 {
576 if (RT_SUCCESS(RTStrAAppend(&pNode->pszDestPath, pszFileDest)))
577 {
578 pNode->Node.pPrev = NULL;
579 pNode->Node.pNext = NULL;
580 RTListAppend(pList, &pNode->Node);
581 return VINF_SUCCESS;
582 }
583 return VERR_NO_MEMORY;
584 }
585 return VERR_NO_MEMORY;
586}
587
588/**
589 * TODO
590 *
591 * @return IPRT status code.
592 * @return int
593 * @param pszRootDir
594 * @param pszSubDir
595 * @param pszFilter
596 * @param uFlags
597 * @param pcObjects
598 * @param pList
599 */
600int ctrlCopyDirectoryRead(const char *pszRootDir, const char *pszSubDir, const char *pszFilter,
601 uint32_t uFlags, uint32_t *pcObjects, PRTLISTNODE pList)
602{
603 AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
604 /* Sub directory is optional. */
605 /* Filter directory is optional. */
606 AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
607 AssertPtrReturn(pList, VERR_INVALID_POINTER);
608
609 PRTDIR pDir = NULL;
610
611 int rc = VINF_SUCCESS;
612 char szCurDir[RTPATH_MAX];
613 if (RTStrPrintf(szCurDir, sizeof(szCurDir), pszRootDir))
614 {
615 if (pszSubDir != NULL)
616 rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
617 if (RT_SUCCESS(rc) && pszFilter != NULL)
618 rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszFilter);
619 }
620 else
621 rc = VERR_NO_MEMORY;
622
623 if (pszFilter)
624 rc = RTDirOpenFiltered(&pDir, szCurDir,
625#ifdef RT_OS_WINDOWS
626 RTDIRFILTER_WINNT);
627#else
628 RTDIRFILTER_UNIX);
629#endif
630 else
631 rc = RTDirOpen(&pDir, szCurDir);
632
633 if (RT_SUCCESS(rc))
634 {
635 for (;;)
636 {
637 RTDIRENTRY DirEntry;
638 rc = RTDirRead(pDir, &DirEntry, NULL);
639 if (RT_FAILURE(rc))
640 {
641 if (rc == VERR_NO_MORE_FILES)
642 rc = VINF_SUCCESS;
643 break;
644 }
645 switch (DirEntry.enmType)
646 {
647 case RTDIRENTRYTYPE_DIRECTORY:
648 /* Skip "." and ".." entrires. */
649 if ( !strcmp(DirEntry.szName, ".")
650 || !strcmp(DirEntry.szName, ".."))
651 {
652 break;
653 }
654 if (uFlags & CopyFileFlag_Recursive)
655 {
656 char *pszNewSub = NULL;
657 if (pszSubDir)
658 RTStrAPrintf(&pszNewSub, "%s%s/", pszSubDir, DirEntry.szName);
659 else
660 RTStrAPrintf(&pszNewSub, "%s/", DirEntry.szName);
661
662 if (pszNewSub)
663 {
664 rc = ctrlCopyDirectoryRead(pszRootDir, pszNewSub, pszFilter,
665 uFlags, pcObjects, pList);
666 RTStrFree(pszNewSub);
667 }
668 else
669 rc = VERR_NO_MEMORY;
670 }
671 break;
672
673 case RTDIRENTRYTYPE_FILE:
674 {
675 char *pszFileSource = NULL;
676 char *pszFileDest = NULL;
677 if (RTStrAPrintf(&pszFileSource, "%s%s%s",
678 pszRootDir, pszSubDir ? pszSubDir : "",
679 DirEntry.szName) >= 0)
680 {
681 if (!RTStrAPrintf(&pszFileDest, "%s%s",
682 pszSubDir ? pszSubDir : "",
683 DirEntry.szName) >= 0)
684 {
685 rc = VERR_NO_MEMORY;
686 }
687 }
688 else
689 rc = VERR_NO_MEMORY;
690
691 if (RT_SUCCESS(rc))
692 {
693 rc = ctrlCopyDirectoryEntryAppend(pszFileSource, pszFileDest, pList);
694 if (RT_SUCCESS(rc))
695 *pcObjects = *pcObjects + 1;
696 }
697
698 if (pszFileSource)
699 RTStrFree(pszFileSource);
700 if (pszFileDest)
701 RTStrFree(pszFileDest);
702 break;
703 }
704
705 case RTDIRENTRYTYPE_SYMLINK:
706 if ( (uFlags & CopyFileFlag_Recursive)
707 && (uFlags & CopyFileFlag_FollowLinks))
708 {
709 /* TODO */
710 }
711 break;
712
713 default:
714 break;
715 }
716 if (RT_FAILURE(rc))
717 break;
718 }
719 }
720
721 if (pDir)
722 RTDirClose(pDir);
723 return rc;
724}
725
726/**
727 * TODO
728 *
729 * @return IPRT status code.
730 * @return int
731 * @param pszSource
732 * @param pszDest
733 * @param uFlags
734 * @param pcObjects
735 * @param pList
736 */
737int ctrlCopyInit(const char *pszSource, const char *pszDest, uint32_t uFlags,
738 uint32_t *pcObjects, PRTLISTNODE pList)
739{
740 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
741 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
742 AssertPtrReturn(pcObjects, VERR_INVALID_PARAMETER);
743 AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
744
745 int rc = VINF_SUCCESS;
746 char *pszSourceAbs = RTPathAbsDup(pszSource);
747 if (pszSourceAbs)
748 {
749 if ( RTPathFilename(pszSourceAbs)
750 && RTFileExists(pszSourceAbs)) /* We have a single file ... */
751 {
752 RTListInit(pList);
753 rc = ctrlCopyDirectoryEntryAppend(pszSourceAbs, pszDest, pList);
754 *pcObjects = 1;
755 }
756 else /* ... or a directory. */
757 {
758 /* Append trailing slash to absolute directory. */
759 if (RTDirExists(pszSourceAbs))
760 RTStrAAppend(&pszSourceAbs, RTPATH_SLASH_STR);
761
762 /* Extract directory filter (e.g. "*.exe"). */
763 char *pszFilter = RTPathFilename(pszSourceAbs);
764 char *pszSourceAbsRoot = RTStrDup(pszSourceAbs);
765 if (pszSourceAbsRoot)
766 {
767 if (pszFilter)
768 {
769 RTPathStripFilename(pszSourceAbsRoot);
770 RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
771 }
772 else
773 {
774 /*
775 * If we have more than one file to copy, make sure that we have
776 * a trailing slash so that we can construct a full path name
777 * (e.g. "foo.txt" -> "c:/foo/temp.txt") as destination.
778 */
779 size_t cch = strlen(pszSourceAbsRoot);
780 if ( cch > 1
781 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 1])
782 && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 2]))
783 {
784 rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
785 }
786 }
787
788 if (RT_SUCCESS(rc))
789 {
790 RTListInit(pList);
791 rc = ctrlCopyDirectoryRead(pszSourceAbsRoot, NULL /* Sub directory */,
792 pszFilter,
793 uFlags, pcObjects, pList);
794 if (*pcObjects == 0)
795 rc = VERR_NOT_FOUND;
796 }
797
798 RTStrFree(pszSourceAbsRoot);
799 }
800 else
801 rc = VERR_NO_MEMORY;
802 }
803 RTStrFree(pszSourceAbs);
804 }
805 else
806 rc = VERR_NO_MEMORY;
807 return rc;
808}
809
810/**
811 * TODO
812 *
813 * @return IPRT status code.
814 */
815void ctrlCopyDestroy(PRTLISTNODE pList)
816{
817 AssertPtr(pList);
818
819 /* Destroy file list. */
820 PDIRECTORYENTRY pNode = RTListNodeGetFirst(pList, DIRECTORYENTRY, Node);
821 while (pNode)
822 {
823 PDIRECTORYENTRY pNext = RTListNodeGetNext(&pNode->Node, DIRECTORYENTRY, Node);
824 bool fLast = RTListNodeIsLast(pList, &pNode->Node);
825
826 if (pNode->pszSourcePath)
827 RTStrFree(pNode->pszSourcePath);
828 if (pNode->pszDestPath)
829 RTStrFree(pNode->pszDestPath);
830 RTListNodeRemove(&pNode->Node);
831 RTMemFree(pNode);
832
833 if (fLast)
834 break;
835
836 pNode = pNext;
837 }
838}
839
840/**
841 * TODO
842 *
843 * @return IPRT status code.
844 * @return int
845 * @param pGuest
846 * @param pszSource
847 * @param pszDest
848 * @param pszUserName
849 * @param pszPassword
850 * @param uFlags
851 */
852int ctrlCopyFile(IGuest *pGuest, const char *pszSource, const char *pszDest,
853 const char *pszUserName, const char *pszPassword,
854 uint32_t uFlags)
855{
856 AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
857 AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
858 AssertPtrReturn(pszUserName, VERR_INVALID_PARAMETER);
859 AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER);
860
861 int vrc = VINF_SUCCESS;
862 ComPtr<IProgress> progress;
863 HRESULT rc = pGuest->CopyToGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
864 Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
865 uFlags, progress.asOutParam());
866 if (FAILED(rc))
867 {
868 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
869 * because it contains more accurate info about what went wrong. */
870 ErrorInfo info(pGuest, COM_IIDOF(IGuest));
871 if (info.isFullAvailable())
872 {
873 if (rc == VBOX_E_IPRT_ERROR)
874 RTMsgError("%ls.", info.getText().raw());
875 else
876 RTMsgError("%ls (%Rhrc).", info.getText().raw(), info.getResultCode());
877 }
878 vrc = VERR_GENERAL_FAILURE;
879 }
880 else
881 {
882 /* Setup signal handling if cancelable. */
883 ASSERT(progress);
884 bool fCanceledAlready = false;
885 BOOL fCancelable;
886 HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
887 if (FAILED(hrc))
888 fCancelable = FALSE;
889 if (fCancelable)
890 {
891 signal(SIGINT, ctrlCopySignalHandler);
892 #ifdef SIGBREAK
893 signal(SIGBREAK, ctrlCopySignalHandler);
894 #endif
895 }
896
897 /* Wait for process to exit ... */
898 BOOL fCompleted = FALSE;
899 BOOL fCanceled = FALSE;
900 while ( SUCCEEDED(progress->COMGETTER(Completed(&fCompleted)))
901 && !fCompleted)
902 {
903 /* Process async cancelation */
904 if (g_fCopyCanceled && !fCanceledAlready)
905 {
906 hrc = progress->Cancel();
907 if (SUCCEEDED(hrc))
908 fCanceledAlready = TRUE;
909 else
910 g_fCopyCanceled = false;
911 }
912
913 /* Progress canceled by Main API? */
914 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
915 && fCanceled)
916 {
917 break;
918 }
919 }
920
921 /* Undo signal handling */
922 if (fCancelable)
923 {
924 signal(SIGINT, SIG_DFL);
925 #ifdef SIGBREAK
926 signal(SIGBREAK, SIG_DFL);
927 #endif
928 }
929
930 if (fCanceled)
931 {
932 //RTPrintf("Copy operation canceled!\n");
933 }
934 else if ( fCompleted
935 && SUCCEEDED(rc))
936 {
937 LONG iRc = false;
938 CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
939 if (FAILED(iRc))
940 {
941 com::ProgressErrorInfo info(progress);
942 if ( info.isFullAvailable()
943 || info.isBasicAvailable())
944 {
945 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
946 * because it contains more accurate info about what went wrong. */
947 if (info.getResultCode() == VBOX_E_IPRT_ERROR)
948 RTMsgError("%ls.", info.getText().raw());
949 else
950 {
951 RTMsgError("Copy operation error details:");
952 GluePrintErrorInfo(info);
953 }
954 }
955 else
956 {
957 if (RT_FAILURE(vrc))
958 RTMsgError("Error while looking up error code, rc=%Rrc\n", vrc);
959 else
960 com::GluePrintRCMessage(iRc);
961 }
962 vrc = VERR_GENERAL_FAILURE;
963 }
964 }
965 }
966 return vrc;
967}
968
969static int handleCtrlCopyTo(HandlerArg *a)
970{
971 /*
972 * Check the syntax. We can deduce the correct syntax from the number of
973 * arguments.
974 */
975 if (a->argc < 3) /* At least the source + destination should be present :-). */
976 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
977
978 Utf8Str Utf8Source(a->argv[1]);
979 Utf8Str Utf8Dest(a->argv[2]);
980 Utf8Str Utf8UserName;
981 Utf8Str Utf8Password;
982 uint32_t uFlags = CopyFileFlag_None;
983 bool fVerbose = false;
984 bool fCopyRecursive = false;
985 bool fDryRun = false;
986
987 /* Iterate through all possible commands (if available). */
988 bool usageOK = true;
989 for (int i = 3; usageOK && i < a->argc; i++)
990 {
991 if ( !strcmp(a->argv[i], "--username")
992 || !strcmp(a->argv[i], "--user"))
993 {
994 if (i + 1 >= a->argc)
995 usageOK = false;
996 else
997 {
998 Utf8UserName = a->argv[i + 1];
999 ++i;
1000 }
1001 }
1002 else if ( !strcmp(a->argv[i], "--password")
1003 || !strcmp(a->argv[i], "--pwd"))
1004 {
1005 if (i + 1 >= a->argc)
1006 usageOK = false;
1007 else
1008 {
1009 Utf8Password = a->argv[i + 1];
1010 ++i;
1011 }
1012 }
1013 else if (!strcmp(a->argv[i], "--dryrun"))
1014 {
1015 fDryRun = true;
1016 }
1017 else if (!strcmp(a->argv[i], "--flags"))
1018 {
1019 if (i + 1 >= a->argc)
1020 usageOK = false;
1021 else
1022 {
1023 /* Nothing to do here yet. */
1024 ++i;
1025 }
1026 }
1027 else if ( !strcmp(a->argv[i], "--recursive")
1028 || !strcmp(a->argv[i], "--r"))
1029 {
1030 uFlags |= CopyFileFlag_Recursive;
1031 }
1032 else if ( !strcmp(a->argv[i], "--update")
1033 || !strcmp(a->argv[i], "--u"))
1034 {
1035 uFlags |= CopyFileFlag_Update;
1036 }
1037 else if ( !strcmp(a->argv[i], "--follow")
1038 || !strcmp(a->argv[i], "--f"))
1039 {
1040 uFlags |= CopyFileFlag_FollowLinks;
1041 }
1042 /** @todo Add force flag for overwriting existing stuff. */
1043 else if (!strcmp(a->argv[i], "--verbose"))
1044 fVerbose = true;
1045 else
1046 return errorSyntax(USAGE_GUESTCONTROL,
1047 "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
1048 }
1049
1050 if (!usageOK)
1051 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1052
1053 if (Utf8Source.isEmpty())
1054 return errorSyntax(USAGE_GUESTCONTROL,
1055 "No source specified!");
1056
1057 if (Utf8Dest.isEmpty())
1058 return errorSyntax(USAGE_GUESTCONTROL,
1059 "No destination specified!");
1060
1061 if (Utf8UserName.isEmpty())
1062 return errorSyntax(USAGE_GUESTCONTROL,
1063 "No user name specified!");
1064
1065 /* Lookup VM. */
1066 ComPtr<IMachine> machine;
1067 /* Assume it's an UUID. */
1068 HRESULT rc;
1069 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1070 machine.asOutParam()));
1071 if (machine)
1072 {
1073 do
1074 {
1075 /* Open an existing session for VM. */
1076 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Shared));
1077 // @todo r=dj assert that it's an existing session
1078
1079 /* Get the mutable session machine. */
1080 a->session->COMGETTER(Machine)(machine.asOutParam());
1081
1082 /* get the associated console */
1083 ComPtr<IConsole> console;
1084 CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam()));
1085
1086 ComPtr<IGuest> guest;
1087 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(guest.asOutParam()));
1088
1089 ComPtr<IProgress> progress;
1090 ULONG uPID = 0;
1091
1092 if (fVerbose)
1093 {
1094 if (fDryRun)
1095 RTPrintf("Dry run - no files copied!\n");
1096 RTPrintf("Gathering file information ...\n");
1097 }
1098
1099 RTLISTNODE listToCopy;
1100 uint32_t cObjects = 0;
1101 int vrc = ctrlCopyInit(Utf8Source.c_str(), Utf8Dest.c_str(), uFlags,
1102 &cObjects, &listToCopy);
1103 if (RT_FAILURE(vrc))
1104 {
1105 switch (vrc)
1106 {
1107 case VERR_NOT_FOUND:
1108 RTMsgError("No files to copy found!\n");
1109 break;
1110
1111 case VERR_PATH_NOT_FOUND:
1112 RTMsgError("Source path \"%s\" not found!\n", Utf8Source.c_str());
1113 break;
1114
1115 default:
1116 RTMsgError("Failed to initialize, rc=%Rrc\n", vrc);
1117 break;
1118 }
1119 }
1120 else
1121 {
1122 if (RT_SUCCESS(vrc) && fVerbose)
1123 {
1124 if (fCopyRecursive)
1125 RTPrintf("Recursively copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1126 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1127 else
1128 RTPrintf("Copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1129 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1130 }
1131
1132 if (RT_SUCCESS(vrc))
1133 {
1134 PDIRECTORYENTRY pNode;
1135 uint32_t uCurObject = 1;
1136 char szDest[RTPATH_MAX];
1137 RTListForEach(&listToCopy, pNode, DIRECTORYENTRY, Node)
1138 {
1139 /*
1140 * Build final destination path: Append the relative path
1141 * stored in the directory node to the destination directory
1142 * specified on the command line.
1143 */
1144 szDest[0] = '\0'; /* Terminate string, needed for RTPathAppend(). */
1145 vrc = RTPathAppend(szDest, sizeof(szDest), Utf8Dest.c_str());
1146 if (RT_SUCCESS(vrc))
1147 vrc = RTPathAppend(szDest, sizeof(szDest), pNode->pszDestPath);
1148
1149 if (RT_SUCCESS(vrc))
1150 {
1151 if (fVerbose)
1152 RTPrintf("Copying \"%s\" (%u/%u) ...\n",
1153 pNode->pszSourcePath, uCurObject, cObjects);
1154
1155 /* Finally copy the desired file (if no dry run selected). */
1156 if (!fDryRun)
1157 vrc = ctrlCopyFile(guest, pNode->pszSourcePath, szDest,
1158 Utf8UserName.c_str(), Utf8Password.c_str(), uFlags);
1159 }
1160 else
1161 RTMsgError("Error building destination file name, rc=%Rrc\n", vrc);
1162 if (RT_FAILURE(vrc))
1163 break;
1164 uCurObject++;
1165 }
1166 if (RT_SUCCESS(vrc) && fVerbose)
1167 RTPrintf("Copy operation successful!\n");
1168 }
1169 ctrlCopyDestroy(&listToCopy);
1170 }
1171 a->session->UnlockMachine();
1172 } while (0);
1173 }
1174 return SUCCEEDED(rc) ? 0 : 1;
1175}
1176
1177static int handleCtrlUpdateAdditions(HandlerArg *a)
1178{
1179 /*
1180 * Check the syntax. We can deduce the correct syntax from the number of
1181 * arguments.
1182 */
1183 if (a->argc < 1) /* At least the VM name should be present :-). */
1184 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1185
1186 Utf8Str Utf8Source;
1187 bool fVerbose = false;
1188
1189 /* Iterate through all possible commands (if available). */
1190 bool usageOK = true;
1191 for (int i = 1; usageOK && i < a->argc; i++)
1192 {
1193 if (!strcmp(a->argv[i], "--source"))
1194 {
1195 if (i + 1 >= a->argc)
1196 usageOK = false;
1197 else
1198 {
1199 Utf8Source = a->argv[i + 1];
1200 ++i;
1201 }
1202 }
1203 else if (!strcmp(a->argv[i], "--verbose"))
1204 fVerbose = true;
1205 else
1206 return errorSyntax(USAGE_GUESTCONTROL,
1207 "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
1208 }
1209
1210 if (!usageOK)
1211 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1212
1213 /* Lookup VM. */
1214 ComPtr<IMachine> machine;
1215 /* Assume it's an UUID. */
1216 HRESULT rc;
1217 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1218 machine.asOutParam()));
1219 if (machine)
1220 {
1221 do
1222 {
1223 /* Open an existing session for VM. */
1224 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Shared));
1225 // @todo r=dj assert that it's an existing session
1226
1227 /* Get the mutable session machine. */
1228 a->session->COMGETTER(Machine)(machine.asOutParam());
1229
1230 /* Get the associated console. */
1231 ComPtr<IConsole> console;
1232 CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam()));
1233
1234 ComPtr<IGuest> guest;
1235 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(guest.asOutParam()));
1236
1237 if (fVerbose)
1238 RTPrintf("Updating Guest Additions on \"%s\" ...\n", a->argv[0]);
1239
1240#ifdef DEBUG_andy
1241 if (Utf8Source.isEmpty())
1242 Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
1243#endif
1244 /* Determine source if not set yet. */
1245 if (Utf8Source.isEmpty())
1246 {
1247 char strTemp[RTPATH_MAX];
1248 int vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
1249 AssertRC(vrc);
1250 Utf8Str Utf8Src1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
1251
1252 vrc = RTPathExecDir(strTemp, sizeof(strTemp));
1253 AssertRC(vrc);
1254 Utf8Str Utf8Src2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
1255
1256 /* Check the standard image locations */
1257 if (RTFileExists(Utf8Src1.c_str()))
1258 Utf8Source = Utf8Src1;
1259 else if (RTFileExists(Utf8Src2.c_str()))
1260 Utf8Source = Utf8Src2;
1261 else
1262 {
1263 RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
1264 break;
1265 }
1266 }
1267 else if (!RTFileExists(Utf8Source.c_str()))
1268 {
1269 RTMsgError("Source \"%s\" does not exist!\n", Utf8Source.c_str());
1270 break;
1271 }
1272 if (fVerbose)
1273 RTPrintf("Using source: %s\n", Utf8Source.c_str());
1274
1275 ComPtr<IProgress> progress;
1276 rc = guest->UpdateGuestAdditions(Bstr(Utf8Source).raw(), progress.asOutParam());
1277 if (SUCCEEDED(rc) && progress)
1278 {
1279 rc = showProgress(progress);
1280 if (FAILED(rc))
1281 {
1282 com::ProgressErrorInfo info(progress);
1283 if (info.isBasicAvailable())
1284 RTMsgError("Failed to start Guest Additions update. Error message: %lS\n", info.getText().raw());
1285 else
1286 RTMsgError("Failed to start Guest Additions update. No error message available!\n");
1287 }
1288 else
1289 {
1290 if (fVerbose)
1291 RTPrintf("Guest Additions installer successfully copied and started.\n");
1292 }
1293 }
1294 a->session->UnlockMachine();
1295 } while (0);
1296 }
1297 return SUCCEEDED(rc) ? 0 : 1;
1298}
1299
1300/**
1301 * Access the guest control store.
1302 *
1303 * @returns 0 on success, 1 on failure
1304 * @note see the command line API description for parameters
1305 */
1306int handleGuestControl(HandlerArg *a)
1307{
1308 HandlerArg arg = *a;
1309 arg.argc = a->argc - 1;
1310 arg.argv = a->argv + 1;
1311
1312 if (a->argc == 0)
1313 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1314
1315 /* switch (cmd) */
1316 if ( !strcmp(a->argv[0], "exec")
1317 || !strcmp(a->argv[0], "execute"))
1318 {
1319 return handleCtrlExecProgram(&arg);
1320 }
1321 else if (!strcmp(a->argv[0], "copyto"))
1322 {
1323 return handleCtrlCopyTo(&arg);
1324 }
1325 else if ( !strcmp(a->argv[0], "updateadditions")
1326 || !strcmp(a->argv[0], "updateadds"))
1327 {
1328 return handleCtrlUpdateAdditions(&arg);
1329 }
1330
1331 /* default: */
1332 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1333}
1334
1335#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