VirtualBox

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

Last change on this file since 33597 was 33597, checked in by vboxsync, 15 years ago

VBoxManage/Guest Control: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.4 KB
Line 
1/* $Id: VBoxManageGuestCtrl.cpp 33597 2010-10-29 10:51:40Z 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 (FAILED(rc))
1072 return 1;
1073
1074 /* Machine is running? */
1075 MachineState_T machineState;
1076 CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
1077 if (machineState != MachineState_Running)
1078 {
1079 RTMsgError("Machine \"%s\" is not running!\n", a->argv[0]);
1080 return 1;
1081 }
1082
1083 /* Open a session for the VM. */
1084 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), 1);
1085
1086 do
1087 {
1088 /* Get the associated console. */
1089 ComPtr<IConsole> console;
1090 CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam()));
1091 /* ... and session machine */
1092 ComPtr<IMachine> sessionMachine;
1093 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
1094
1095 ComPtr<IGuest> guest;
1096 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(guest.asOutParam()));
1097
1098 if (fVerbose)
1099 {
1100 if (fDryRun)
1101 RTPrintf("Dry run - no files copied!\n");
1102 RTPrintf("Gathering file information ...\n");
1103 }
1104
1105 RTLISTNODE listToCopy;
1106 uint32_t cObjects = 0;
1107 int vrc = ctrlCopyInit(Utf8Source.c_str(), Utf8Dest.c_str(), uFlags,
1108 &cObjects, &listToCopy);
1109 if (RT_FAILURE(vrc))
1110 {
1111 switch (vrc)
1112 {
1113 case VERR_NOT_FOUND:
1114 RTMsgError("No files to copy found!\n");
1115 break;
1116
1117 case VERR_PATH_NOT_FOUND:
1118 RTMsgError("Source path \"%s\" not found!\n", Utf8Source.c_str());
1119 break;
1120
1121 default:
1122 RTMsgError("Failed to initialize, rc=%Rrc\n", vrc);
1123 break;
1124 }
1125 }
1126 else
1127 {
1128 if (RT_SUCCESS(vrc) && fVerbose)
1129 {
1130 if (fCopyRecursive)
1131 RTPrintf("Recursively copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1132 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1133 else
1134 RTPrintf("Copying \"%s\" to \"%s\" (%u file(s)) ...\n",
1135 Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
1136 }
1137
1138 if (RT_SUCCESS(vrc))
1139 {
1140 PDIRECTORYENTRY pNode;
1141 uint32_t uCurObject = 1;
1142 char szDest[RTPATH_MAX];
1143 RTListForEach(&listToCopy, pNode, DIRECTORYENTRY, Node)
1144 {
1145 /*
1146 * Build final destination path: Append the relative path
1147 * stored in the directory node to the destination directory
1148 * specified on the command line.
1149 */
1150 szDest[0] = '\0'; /* Terminate string, needed for RTPathAppend(). */
1151 vrc = RTPathAppend(szDest, sizeof(szDest), Utf8Dest.c_str());
1152 if (RT_SUCCESS(vrc))
1153 vrc = RTPathAppend(szDest, sizeof(szDest), pNode->pszDestPath);
1154
1155 if (RT_SUCCESS(vrc))
1156 {
1157 if (fVerbose)
1158 RTPrintf("Copying \"%s\" (%u/%u) ...\n",
1159 pNode->pszSourcePath, uCurObject, cObjects);
1160
1161 /* Finally copy the desired file (if no dry run selected). */
1162 if (!fDryRun)
1163 vrc = ctrlCopyFile(guest, pNode->pszSourcePath, szDest,
1164 Utf8UserName.c_str(), Utf8Password.c_str(), uFlags);
1165 }
1166 else
1167 RTMsgError("Error building destination file name, rc=%Rrc\n", vrc);
1168 if (RT_FAILURE(vrc))
1169 break;
1170 uCurObject++;
1171 }
1172 if (RT_SUCCESS(vrc) && fVerbose)
1173 RTPrintf("Copy operation successful!\n");
1174 }
1175 ctrlCopyDestroy(&listToCopy);
1176 }
1177 a->session->UnlockMachine();
1178 } while (0);
1179 return SUCCEEDED(rc) ? 0 : 1;
1180}
1181
1182static int handleCtrlUpdateAdditions(HandlerArg *a)
1183{
1184 /*
1185 * Check the syntax. We can deduce the correct syntax from the number of
1186 * arguments.
1187 */
1188 if (a->argc < 1) /* At least the VM name should be present :-). */
1189 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1190
1191 Utf8Str Utf8Source;
1192 bool fVerbose = false;
1193
1194 /* Iterate through all possible commands (if available). */
1195 bool usageOK = true;
1196 for (int i = 1; usageOK && i < a->argc; i++)
1197 {
1198 if (!strcmp(a->argv[i], "--source"))
1199 {
1200 if (i + 1 >= a->argc)
1201 usageOK = false;
1202 else
1203 {
1204 Utf8Source = a->argv[i + 1];
1205 ++i;
1206 }
1207 }
1208 else if (!strcmp(a->argv[i], "--verbose"))
1209 fVerbose = true;
1210 else
1211 return errorSyntax(USAGE_GUESTCONTROL,
1212 "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
1213 }
1214
1215 if (!usageOK)
1216 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1217
1218 /* Lookup VM. */
1219 ComPtr<IMachine> machine;
1220 /* Assume it's an UUID. */
1221 HRESULT rc;
1222 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1223 machine.asOutParam()));
1224 if (FAILED(rc))
1225 return 1;
1226
1227 /* Machine is running? */
1228 MachineState_T machineState;
1229 CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
1230 if (machineState != MachineState_Running)
1231 {
1232 RTMsgError("Machine \"%s\" is not running!\n", a->argv[0]);
1233 return 1;
1234 }
1235
1236 /* Open a session for the VM. */
1237 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), 1);
1238
1239 do
1240 {
1241 /* Get the associated console. */
1242 ComPtr<IConsole> console;
1243 CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam()));
1244 /* ... and session machine */
1245 ComPtr<IMachine> sessionMachine;
1246 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
1247
1248 ComPtr<IGuest> guest;
1249 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(guest.asOutParam()));
1250
1251 if (fVerbose)
1252 RTPrintf("Updating Guest Additions of machine \"%s\" ...\n", a->argv[0]);
1253
1254#ifdef DEBUG_andy
1255 if (Utf8Source.isEmpty())
1256 Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
1257#endif
1258 /* Determine source if not set yet. */
1259 if (Utf8Source.isEmpty())
1260 {
1261 char strTemp[RTPATH_MAX];
1262 int vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
1263 AssertRC(vrc);
1264 Utf8Str Utf8Src1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
1265
1266 vrc = RTPathExecDir(strTemp, sizeof(strTemp));
1267 AssertRC(vrc);
1268 Utf8Str Utf8Src2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
1269
1270 /* Check the standard image locations */
1271 if (RTFileExists(Utf8Src1.c_str()))
1272 Utf8Source = Utf8Src1;
1273 else if (RTFileExists(Utf8Src2.c_str()))
1274 Utf8Source = Utf8Src2;
1275 else
1276 {
1277 RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
1278 break;
1279 }
1280 }
1281 else if (!RTFileExists(Utf8Source.c_str()))
1282 {
1283 RTMsgError("Source \"%s\" does not exist!\n", Utf8Source.c_str());
1284 break;
1285 }
1286 if (fVerbose)
1287 RTPrintf("Using source: %s\n", Utf8Source.c_str());
1288
1289 ComPtr<IProgress> progress;
1290 CHECK_ERROR_BREAK(guest, UpdateGuestAdditions(Bstr(Utf8Source).raw(),
1291 progress.asOutParam()));
1292 rc = showProgress(progress);
1293 if (FAILED(rc))
1294 {
1295 com::ProgressErrorInfo info(progress);
1296 if (info.isBasicAvailable())
1297 RTMsgError("Failed to start Guest Additions update. Error message: %lS\n", info.getText().raw());
1298 else
1299 RTMsgError("Failed to start Guest Additions update. No error message available!\n");
1300 }
1301 else
1302 {
1303 if (fVerbose)
1304 RTPrintf("Guest Additions installer successfully copied and started.\n");
1305 }
1306 a->session->UnlockMachine();
1307 } while (0);
1308 return SUCCEEDED(rc) ? 0 : 1;
1309}
1310
1311/**
1312 * Access the guest control store.
1313 *
1314 * @returns 0 on success, 1 on failure
1315 * @note see the command line API description for parameters
1316 */
1317int handleGuestControl(HandlerArg *a)
1318{
1319 HandlerArg arg = *a;
1320 arg.argc = a->argc - 1;
1321 arg.argv = a->argv + 1;
1322
1323 if (a->argc == 0)
1324 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1325
1326 /* switch (cmd) */
1327 if ( !strcmp(a->argv[0], "exec")
1328 || !strcmp(a->argv[0], "execute"))
1329 {
1330 return handleCtrlExecProgram(&arg);
1331 }
1332 else if (!strcmp(a->argv[0], "copyto"))
1333 {
1334 return handleCtrlCopyTo(&arg);
1335 }
1336 else if ( !strcmp(a->argv[0], "updateadditions")
1337 || !strcmp(a->argv[0], "updateadds"))
1338 {
1339 return handleCtrlUpdateAdditions(&arg);
1340 }
1341
1342 /* default: */
1343 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
1344}
1345
1346#endif /* !VBOX_ONLY_DOCS */
Note: See TracBrowser for help on using the repository browser.

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