VirtualBox

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

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

*: Fixes for incorrect RTStrAPrintf usage (it does NOT return an IPRT status code) and the odd bugs nearby.

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