VirtualBox

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

最後變更 在這個檔案從32701是 32701,由 vboxsync 提交於 15 年 前

Frontends/VBoxManage: Error printing cleanup, use stderr and consistent formatting. Small cleanups (like using Keyboard::PutScancodes instead of the more clumsy Keyboard::PutScancode and fixing the incorrect comment which attracted my attention).

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 19.0 KB
 
1/* $Id: VBoxManageGuestCtrl.cpp 32701 2010-09-22 17:12:01Z 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.alldomusa.eu.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#include <VBox/com/com.h>
25#include <VBox/com/string.h>
26#include <VBox/com/array.h>
27#include <VBox/com/ErrorInfo.h>
28#include <VBox/com/errorprint.h>
29
30#include <VBox/com/VirtualBox.h>
31#include <VBox/com/EventQueue.h>
32
33#include <VBox/HostServices/GuestControlSvc.h> /* for PROC_STS_XXX */
34
35#include <VBox/log.h>
36#include <iprt/asm.h>
37#include <iprt/getopt.h>
38#include <iprt/stream.h>
39#include <iprt/string.h>
40#include <iprt/time.h>
41#include <iprt/thread.h>
42
43#ifdef USE_XPCOM_QUEUE
44# include <sys/select.h>
45# include <errno.h>
46#endif
47
48#include <signal.h>
49
50#ifdef RT_OS_DARWIN
51# include <CoreFoundation/CFRunLoop.h>
52#endif
53
54using namespace com;
55
56/**
57 * IVirtualBoxCallback implementation for handling the GuestControlCallback in
58 * relation to the "guestcontrol * wait" command.
59 */
60/** @todo */
61
62/** Set by the signal handler. */
63static volatile bool g_fExecCanceled = false;
64
65void usageGuestControl(void)
66{
67 RTStrmPrintf(g_pStdErr,
68 "VBoxManage guestcontrol execute <vmname>|<uuid>\n"
69 " <path to program>\n"
70 " --username <name> --password <password>\n"
71 " [--arguments \"<arguments>\"]\n"
72 " [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
73 " [--flags <flags>] [--timeout <msec>]\n"
74 " [--verbose] [--wait-for exit,stdout,stderr||]\n"
75 "\n");
76}
77
78/**
79 * Signal handler that sets g_fCanceled.
80 *
81 * This can be executed on any thread in the process, on Windows it may even be
82 * a thread dedicated to delivering this signal. Do not doing anything
83 * unnecessary here.
84 */
85static void execProcessSignalHandler(int iSignal)
86{
87 NOREF(iSignal);
88 ASMAtomicWriteBool(&g_fExecCanceled, true);
89}
90
91static const char *getStatus(ULONG uStatus)
92{
93 switch (uStatus)
94 {
95 case guestControl::PROC_STS_STARTED:
96 return "started";
97 case guestControl::PROC_STS_TEN:
98 return "successfully terminated";
99 case guestControl::PROC_STS_TES:
100 return "terminated by signal";
101 case guestControl::PROC_STS_TEA:
102 return "abnormally aborted";
103 case guestControl::PROC_STS_TOK:
104 return "timed out";
105 case guestControl::PROC_STS_TOA:
106 return "timed out, hanging";
107 case guestControl::PROC_STS_DWN:
108 return "killed";
109 case guestControl::PROC_STS_ERROR:
110 return "error";
111 default:
112 return "unknown";
113 }
114}
115
116static int handleExecProgram(HandlerArg *a)
117{
118 /*
119 * Check the syntax. We can deduce the correct syntax from the number of
120 * arguments.
121 */
122 if (a->argc < 2) /* At least the command we want to execute in the guest should be present :-). */
123 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
124
125 Utf8Str Utf8Cmd(a->argv[1]);
126 uint32_t uFlags = 0;
127 com::SafeArray <BSTR> args;
128 com::SafeArray <BSTR> env;
129 Utf8Str Utf8UserName;
130 Utf8Str Utf8Password;
131 uint32_t u32TimeoutMS = 0;
132 bool fWaitForExit = false;
133 bool fWaitForStdOut = false;
134 bool fWaitForStdErr = false;
135 bool fVerbose = false;
136 bool fTimeout = false;
137
138 /* Always use the actual command line as argv[0]. */
139 args.push_back(Bstr(Utf8Cmd));
140
141 /* Iterate through all possible commands (if available). */
142 bool usageOK = true;
143 for (int i = 2; usageOK && i < a->argc; i++)
144 {
145 if ( !strcmp(a->argv[i], "--arguments")
146 || !strcmp(a->argv[i], "--args")
147 || !strcmp(a->argv[i], "--arg"))
148 {
149 if (i + 1 >= a->argc)
150 usageOK = false;
151 else
152 {
153 char **papszArg;
154 int cArgs;
155
156 int vrc = RTGetOptArgvFromString(&papszArg, &cArgs, a->argv[i + 1], NULL);
157 if (RT_SUCCESS(vrc))
158 {
159 for (int j = 0; j < cArgs; j++)
160 args.push_back(Bstr(papszArg[j]));
161
162 RTGetOptArgvFree(papszArg);
163 }
164 ++i;
165 }
166 }
167 else if ( !strcmp(a->argv[i], "--environment")
168 || !strcmp(a->argv[i], "--env"))
169 {
170 if (i + 1 >= a->argc)
171 usageOK = false;
172 else
173 {
174 char **papszArg;
175 int cArgs;
176
177 int vrc = RTGetOptArgvFromString(&papszArg, &cArgs, a->argv[i + 1], NULL);
178 if (RT_SUCCESS(vrc))
179 {
180 for (int j = 0; j < cArgs; j++)
181 env.push_back(Bstr(papszArg[j]));
182
183 RTGetOptArgvFree(papszArg);
184 }
185 ++i;
186 }
187 }
188 else if (!strcmp(a->argv[i], "--flags"))
189 {
190 if ( i + 1 >= a->argc
191 || RTStrToUInt32Full(a->argv[i + 1], 10, &uFlags) != VINF_SUCCESS)
192 usageOK = false;
193 else
194 ++i;
195 }
196 else if ( !strcmp(a->argv[i], "--username")
197 || !strcmp(a->argv[i], "--user"))
198 {
199 if (i + 1 >= a->argc)
200 usageOK = false;
201 else
202 {
203 Utf8UserName = a->argv[i + 1];
204 ++i;
205 }
206 }
207 else if ( !strcmp(a->argv[i], "--password")
208 || !strcmp(a->argv[i], "--pwd"))
209 {
210 if (i + 1 >= a->argc)
211 usageOK = false;
212 else
213 {
214 Utf8Password = a->argv[i + 1];
215 ++i;
216 }
217 }
218 else if (!strcmp(a->argv[i], "--timeout"))
219 {
220 if ( i + 1 >= a->argc
221 || RTStrToUInt32Full(a->argv[i + 1], 10, &u32TimeoutMS) != VINF_SUCCESS
222 || u32TimeoutMS == 0)
223 {
224 usageOK = false;
225 }
226 else
227 {
228 fTimeout = true;
229 ++i;
230 }
231 }
232 else if (!strcmp(a->argv[i], "--wait-for"))
233 {
234 if (i + 1 >= a->argc)
235 usageOK = false;
236 else
237 {
238 if (!strcmp(a->argv[i + 1], "exit"))
239 fWaitForExit = true;
240 else if (!strcmp(a->argv[i + 1], "stdout"))
241 {
242 fWaitForExit = true;
243 fWaitForStdOut = true;
244 }
245 else if (!strcmp(a->argv[i + 1], "stderr"))
246 {
247 fWaitForExit = true;
248 fWaitForStdErr = true;
249 }
250 else
251 usageOK = false;
252 ++i;
253 }
254 }
255 else if (!strcmp(a->argv[i], "--verbose"))
256 fVerbose = true;
257 /** @todo Add fancy piping stuff here. */
258 else
259 return errorSyntax(USAGE_GUESTCONTROL,
260 "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
261 }
262
263 if (!usageOK)
264 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
265
266 if (Utf8UserName.isEmpty())
267 return errorSyntax(USAGE_GUESTCONTROL,
268 "No user name specified!");
269
270 /* lookup VM. */
271 ComPtr<IMachine> machine;
272 /* assume it's an UUID */
273 HRESULT rc = a->virtualBox->GetMachine(Bstr(a->argv[0]), machine.asOutParam());
274 if (FAILED(rc) || !machine)
275 {
276 /* must be a name */
277 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]), machine.asOutParam()));
278 }
279
280 if (machine)
281 {
282 do
283 {
284 /* open an existing session for VM */
285 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Shared));
286 // @todo r=dj assert that it's an existing session
287
288 /* get the mutable session machine */
289 a->session->COMGETTER(Machine)(machine.asOutParam());
290
291 /* get the associated console */
292 ComPtr<IConsole> console;
293 CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam()));
294
295 ComPtr<IGuest> guest;
296 CHECK_ERROR_BREAK(console, COMGETTER(Guest)(guest.asOutParam()));
297
298 ComPtr<IProgress> progress;
299 ULONG uPID = 0;
300
301 if (fVerbose)
302 {
303 if (u32TimeoutMS == 0)
304 RTPrintf("Waiting for guest to start process ...\n");
305 else
306 RTPrintf("Waiting for guest to start process (within %ums)\n", u32TimeoutMS);
307 }
308
309 /* Get current time stamp to later calculate rest of timeout left. */
310 uint64_t u64StartMS = RTTimeMilliTS();
311
312 /* Execute the process. */
313 rc = guest->ExecuteProcess(Bstr(Utf8Cmd), uFlags,
314 ComSafeArrayAsInParam(args), ComSafeArrayAsInParam(env),
315 Bstr(Utf8UserName), Bstr(Utf8Password), u32TimeoutMS,
316 &uPID, progress.asOutParam());
317 if (FAILED(rc))
318 {
319 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
320 * because it contains more accurate info about what went wrong. */
321 ErrorInfo info(guest, COM_IIDOF(IGuest));
322 if (info.isFullAvailable())
323 {
324 if (rc == VBOX_E_IPRT_ERROR)
325 RTMsgError("%ls.", info.getText().raw());
326 else
327 RTMsgError("%ls (%Rhrc).", info.getText().raw(), info.getResultCode());
328 }
329 break;
330 }
331 if (fVerbose)
332 RTPrintf("Process '%s' (PID: %u) started\n", Utf8Cmd.c_str(), uPID);
333 if (fWaitForExit)
334 {
335 if (fTimeout)
336 {
337 /* Calculate timeout value left after process has been started. */
338 uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
339 /* Is timeout still bigger than current difference? */
340 if (u32TimeoutMS > u64Elapsed)
341 {
342 u32TimeoutMS -= (uint32_t)u64Elapsed;
343 if (fVerbose)
344 RTPrintf("Waiting for process to exit (%ums left) ...\n", u32TimeoutMS);
345 }
346 else
347 {
348 if (fVerbose)
349 RTPrintf("No time left to wait for process!\n");
350 }
351 }
352 else if (fVerbose)
353 RTPrintf("Waiting for process to exit ...\n");
354
355 /* setup signal handling if cancelable */
356 ASSERT(progress);
357 bool fCanceledAlready = false;
358 BOOL fCancelable;
359 HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
360 if (FAILED(hrc))
361 fCancelable = FALSE;
362 if (fCancelable)
363 {
364 signal(SIGINT, execProcessSignalHandler);
365 #ifdef SIGBREAK
366 signal(SIGBREAK, execProcessSignalHandler);
367 #endif
368 }
369
370 /* Wait for process to exit ... */
371 BOOL fCompleted = FALSE;
372 BOOL fCanceled = FALSE;
373 while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted))))
374 {
375 SafeArray<BYTE> aOutputData;
376 ULONG cbOutputData = 0;
377
378 /*
379 * Some data left to output?
380 */
381 if ( fWaitForStdOut
382 || fWaitForStdErr)
383 {
384
385 rc = guest->GetProcessOutput(uPID, 0 /* aFlags */,
386 u32TimeoutMS, _64K, ComSafeArrayAsOutParam(aOutputData));
387 if (FAILED(rc))
388 {
389 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
390 * because it contains more accurate info about what went wrong. */
391 ErrorInfo info(guest, COM_IIDOF(IGuest));
392 if (info.isFullAvailable())
393 {
394 if (rc == VBOX_E_IPRT_ERROR)
395 RTMsgError("%ls.", info.getText().raw());
396 else
397 RTMsgError("%ls (%Rhrc).", info.getText().raw(), info.getResultCode());
398 }
399 cbOutputData = 0;
400 }
401 else
402 {
403 cbOutputData = aOutputData.size();
404 if (cbOutputData > 0)
405 {
406 /* aOutputData has a platform dependent line ending, standardize on
407 * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
408 * Windows. Otherwise we end up with CR/CR/LF on Windows. */
409 ULONG cbOutputDataPrint = cbOutputData;
410 for (BYTE *s = aOutputData.raw(), *d = s;
411 s - aOutputData.raw() < (ssize_t)cbOutputData;
412 s++, d++)
413 {
414 if (*s == '\r')
415 {
416 /* skip over CR, adjust destination */
417 d--;
418 cbOutputDataPrint--;
419 }
420 else if (s != d)
421 *d = *s;
422 }
423 RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
424 }
425 }
426 }
427
428 if (cbOutputData <= 0) /* No more output data left? */
429 {
430 if (fCompleted)
431 break;
432
433 if ( fTimeout
434 && RTTimeMilliTS() - u64StartMS > u32TimeoutMS + 5000)
435 {
436 progress->Cancel();
437 break;
438 }
439 }
440
441 /* Process async cancelation */
442 if (g_fExecCanceled && !fCanceledAlready)
443 {
444 hrc = progress->Cancel();
445 if (SUCCEEDED(hrc))
446 fCanceledAlready = TRUE;
447 else
448 g_fExecCanceled = false;
449 }
450
451 /* Progress canceled by Main API? */
452 if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
453 && fCanceled)
454 {
455 break;
456 }
457 }
458
459 /* Undo signal handling */
460 if (fCancelable)
461 {
462 signal(SIGINT, SIG_DFL);
463 #ifdef SIGBREAK
464 signal(SIGBREAK, SIG_DFL);
465 #endif
466 }
467
468 if (fCanceled)
469 {
470 if (fVerbose)
471 RTPrintf("Process execution canceled!\n");
472 }
473 else if ( fCompleted
474 && SUCCEEDED(rc))
475 {
476 LONG iRc = false;
477 CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
478 if (FAILED(iRc))
479 {
480 ComPtr<IVirtualBoxErrorInfo> execError;
481 rc = progress->COMGETTER(ErrorInfo)(execError.asOutParam());
482 com::ErrorInfo info(execError, COM_IIDOF(IVirtualBoxErrorInfo));
483 if (SUCCEEDED(rc) && info.isFullAvailable())
484 {
485 /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
486 * because it contains more accurate info about what went wrong. */
487 if (info.getResultCode() == VBOX_E_IPRT_ERROR)
488 RTMsgError("%ls.", info.getText().raw());
489 else
490 {
491 RTMsgError("Process error details:");
492 GluePrintErrorInfo(info);
493 }
494 }
495 else
496 AssertMsgFailed(("Full error description is missing!\n"));
497 }
498 else if (fVerbose)
499 {
500 ULONG uRetStatus, uRetExitCode, uRetFlags;
501 rc = guest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &uRetStatus);
502 if (SUCCEEDED(rc))
503 RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, uRetStatus, getStatus(uRetStatus), uRetFlags);
504 }
505 }
506 else
507 {
508 if (fVerbose)
509 RTPrintf("Process execution aborted!\n");
510 }
511 }
512 a->session->UnlockMachine();
513 } while (0);
514 }
515 return SUCCEEDED(rc) ? 0 : 1;
516}
517
518/**
519 * Access the guest control store.
520 *
521 * @returns 0 on success, 1 on failure
522 * @note see the command line API description for parameters
523 */
524int handleGuestControl(HandlerArg *a)
525{
526 HandlerArg arg = *a;
527 arg.argc = a->argc - 1;
528 arg.argv = a->argv + 1;
529
530 if (a->argc == 0)
531 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
532
533 /* switch (cmd) */
534 if ( strcmp(a->argv[0], "exec") == 0
535 || strcmp(a->argv[0], "execute") == 0)
536 return handleExecProgram(&arg);
537
538 /* default: */
539 return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
540}
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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