VirtualBox

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

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

com/string: this is tricky to get right on COM and on XPCOM

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