VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceExec.cpp@ 23607

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

VBoxService: Added additional status reporting for sequential host scripts/programs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.2 KB
Line 
1/* $Id: VBoxServiceExec.cpp 23607 2009-10-07 19:57:48Z vboxsync $ */
2/** @file
3 * VBoxServiceExec - Host-driven Command Execution.
4 */
5
6/*
7 * Copyright (C) 2009 Sun Microsystems, Inc.
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 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#include <iprt/assert.h>
27#include <iprt/ctype.h>
28#include <iprt/env.h>
29#include <iprt/file.h>
30#include <iprt/mem.h>
31#include <iprt/path.h>
32#include <iprt/param.h>
33#include <iprt/process.h>
34#include <iprt/string.h>
35#include <iprt/semaphore.h>
36#include <iprt/thread.h>
37#include <VBox/version.h>
38#include <VBox/VBoxGuestLib.h>
39#include "VBoxServiceInternal.h"
40#include "VBoxServiceUtils.h"
41
42
43/*******************************************************************************
44* Global Variables *
45*******************************************************************************/
46/** The vminfo interval (millseconds). */
47static uint32_t g_cMsExecInterval = 0;
48/** The semaphore we're blocking on. */
49static RTSEMEVENTMULTI g_hExecEvent = NIL_RTSEMEVENTMULTI;
50/** The guest property service client ID. */
51static uint32_t g_uExecGuestPropSvcClientID = 0;
52
53
54/** @copydoc VBOXSERVICE::pfnPreInit */
55static DECLCALLBACK(int) VBoxServiceExecPreInit(void)
56{
57 return VINF_SUCCESS;
58}
59
60
61/** @copydoc VBOXSERVICE::pfnOption */
62static DECLCALLBACK(int) VBoxServiceExecOption(const char **ppszShort, int argc, char **argv, int *pi)
63{
64 int rc = -1;
65 if (ppszShort)
66 /* no short options */;
67 else if (!strcmp(argv[*pi], "--exec-interval"))
68 rc = VBoxServiceArgUInt32(argc, argv, "", pi, &g_cMsExecInterval, 1, UINT32_MAX - 1);
69 return rc;
70}
71
72
73/** @copydoc VBOXSERVICE::pfnInit */
74static DECLCALLBACK(int) VBoxServiceExecInit(void)
75{
76 /*
77 * If not specified, find the right interval default.
78 * Then create the event sem to block on.
79 */
80 if (!g_cMsExecInterval)
81 g_cMsExecInterval = g_DefaultInterval * 1000;
82 if (!g_cMsExecInterval)
83 g_cMsExecInterval = 10 * 1000;
84
85 int rc = RTSemEventMultiCreate(&g_hExecEvent);
86 AssertRCReturn(rc, rc);
87
88 rc = VbglR3GuestPropConnect(&g_uExecGuestPropSvcClientID);
89 if (RT_SUCCESS(rc))
90 VBoxServiceVerbose(3, "Exec: Property Service Client ID: %#x\n", g_uExecGuestPropSvcClientID);
91 else
92 {
93 VBoxServiceError("Exec: Failed to connect to the guest property service! Error: %Rrc\n", rc);
94 RTSemEventMultiDestroy(g_hExecEvent);
95 g_hExecEvent = NIL_RTSEMEVENTMULTI;
96 }
97
98 return rc;
99}
100
101
102/**
103 * Validates flags for executable guest properties.
104 *
105 * @returns VBox status code. Success means they are valid.
106 *
107 * @param pszFlags Pointer to flags to be checked.
108 */
109static int VBoxServiceExecValidateFlags(const char *pszFlags)
110{
111 if (!pszFlags)
112 return VERR_ACCESS_DENIED;
113 if (!RTStrStr(pszFlags, "TRANSIENT"))
114 return VERR_ACCESS_DENIED;
115 if (!RTStrStr(pszFlags, "RDONLYGUEST"))
116 return VERR_ACCESS_DENIED;
117 return VINF_SUCCESS;
118}
119
120
121/**
122 * Reads a host transient property.
123 *
124 * This will validate the flags to make sure it is a transient property that can
125 * only be change by the host.
126 *
127 * @returns VBox status code, fully bitched.
128 * @param pszPropName The property name.
129 * @param ppszValue Where to return the value. This is always set
130 * to NULL. Free it using RTStrFree().
131 * @param puTimestamp Where to return the timestamp. This is only set
132 * on success. Optional.
133 */
134static int VBoxServiceExecReadHostProp(const char *pszPropName, char **ppszValue, uint64_t *puTimestamp)
135{
136 size_t cbBuf = _1K;
137 void *pvBuf = NULL;
138 int rc;
139 *ppszValue = NULL;
140
141 char *pszPropNameUTF8;
142 rc = RTStrCurrentCPToUtf8(&pszPropNameUTF8, pszPropName);
143 if (RT_FAILURE(rc))
144 {
145 VBoxServiceError("Exec: Failed to convert property name \"%s\" to UTF-8!\n", pszPropName);
146 return rc;
147 }
148
149 for (unsigned cTries = 0; cTries < 10; cTries++)
150 {
151 /*
152 * (Re-)Allocate the buffer and try read the property.
153 */
154 RTMemFree(pvBuf);
155 pvBuf = RTMemAlloc(cbBuf);
156 if (!pvBuf)
157 {
158 VBoxServiceError("Exec: Failed to allocate %zu bytes\n", cbBuf);
159 rc = VERR_NO_MEMORY;
160 break;
161 }
162 char *pszValue;
163 char *pszFlags;
164 uint64_t uTimestamp;
165 rc = VbglR3GuestPropRead(g_uExecGuestPropSvcClientID, pszPropNameUTF8,
166 pvBuf, cbBuf,
167 &pszValue, &uTimestamp, &pszFlags, NULL);
168 if (RT_FAILURE(rc))
169 {
170 if (rc == VERR_BUFFER_OVERFLOW)
171 {
172 /* try again with a bigger buffer. */
173 cbBuf *= 2;
174 continue;
175 }
176 if (rc == VERR_NOT_FOUND)
177 VBoxServiceVerbose(2, "Exec: %s not found\n", pszPropName);
178 else
179 VBoxServiceError("Exec: Failed to query \"%s\": %Rrc\n", pszPropName, rc);
180 break;
181 }
182
183 /*
184 * Validate it and set return values on success.
185 */
186 rc = VBoxServiceExecValidateFlags(pszFlags);
187 if (RT_FAILURE(rc))
188 {
189 static uint32_t s_cBitched = 0;
190 if (++s_cBitched < 10)
191 VBoxServiceError("Exec: Flag validation failed for \"%s\": %Rrc; flags=\"%s\"\n",
192 pszPropName, rc, pszFlags);
193 break;
194 }
195 VBoxServiceVerbose(2, "Exec: Read \"%s\" = \"%s\", timestamp %RU64n\n",
196 pszPropName, pszValue, uTimestamp);
197 *ppszValue = RTStrDup(pszValue);
198 if (!*ppszValue)
199 {
200 VBoxServiceError("Exec: RTStrDup failed for \"%s\"\n", pszValue);
201 rc = VERR_NO_MEMORY;
202 break;
203 }
204
205 if (puTimestamp)
206 *puTimestamp = uTimestamp;
207 break; /* done */
208 }
209 RTMemFree(pvBuf);
210 RTStrFree(pszPropNameUTF8);
211 return rc;
212}
213
214
215/**
216 * Frees an argument vector constructed by VBoxServiceExecCreateArgV.
217 *
218 * @param papszArgs The vector to free.
219 */
220static void VBoxServiceExecFreeArgV(char **papszArgs)
221{
222 for (size_t i = 0; papszArgs[i]; i++)
223 {
224 RTStrFree(papszArgs[i]);
225 papszArgs[i] = NULL;
226 }
227 RTMemFree(papszArgs);
228}
229
230
231/**
232 * Creates an argument vector out of an executable name and a string containing
233 * the arguments separated by spaces.
234 *
235 * @returns VBox status code. Not bitched.
236 * @param pszExec The executable name.
237 * @param pszArgs The string containging the arguments.
238 * @param ppapszArgs Where to return the argument vector. Not set on
239 * failure. Use VBoxServiceExecFreeArgV to free.
240 *
241 * @todo Quoted strings. Do it unix (bourne shell) fashion.
242 */
243static int VBoxServiceExecCreateArgV(const char *pszExec, const char *pszArgs, char ***ppapszArgs)
244{
245 size_t cAlloc = 1;
246 size_t cUsed = 1;
247 char **papszArgs = (char **)RTMemAlloc(sizeof(char *) * (cAlloc + 1));
248 if (!papszArgs)
249 return VERR_NO_MEMORY;
250
251 /*
252 * Start by adding the executable name first.
253 * Note! We keep the papszArgs fully terminated at all times to keep cleanup simple.
254 */
255 int rc = VERR_NO_MEMORY;
256 papszArgs[1] = NULL;
257 papszArgs[0] = RTStrDup(pszExec);
258 if (papszArgs[0])
259 {
260 /*
261 * Parse the argument string and add any arguments found in it.
262 */
263 for (;;)
264 {
265 /* skip leading spaces */
266 char ch;
267 while ((ch = *pszArgs) && RT_C_IS_SPACE(ch))
268 pszArgs++;
269 if (!*pszArgs)
270 {
271 *ppapszArgs = papszArgs;
272 return VINF_SUCCESS;
273 }
274
275 /* find the of the current word. Quoting is ignored atm. */
276 char const *pszEnd = pszArgs + 1;
277 while ((ch = *pszEnd) && !RT_C_IS_SPACE(ch))
278 pszEnd++;
279
280 /* resize the vector. */
281 if (cUsed == cAlloc)
282 {
283 cAlloc += 10;
284 void *pvNew = RTMemRealloc(papszArgs, sizeof(char *) * (cAlloc + 1));
285 if (!pvNew)
286 break;
287 papszArgs = (char **)pvNew;
288 for (size_t i = cUsed; i <= cAlloc; i++)
289 papszArgs[i] = NULL;
290 }
291
292 /* add it */
293 papszArgs[cUsed] = RTStrDupN(pszArgs, (uintptr_t)pszEnd - (uintptr_t)pszArgs);
294 if (!papszArgs[cUsed])
295 break;
296 cUsed++;
297
298 /* advance */
299 pszArgs = pszEnd;
300 }
301 }
302
303 VBoxServiceExecFreeArgV(papszArgs);
304 return rc;
305}
306
307
308/** @copydoc VBOXSERVICE::pfnWorker */
309DECLCALLBACK(int) VBoxServiceExecWorker(bool volatile *pfShutdown)
310{
311 int rcRet = VINF_SUCCESS;
312
313 /*
314 * Tell the control thread that it can continue
315 * spawning services.
316 */
317 RTThreadUserSignal(RTThreadSelf());
318 Assert(g_uExecGuestPropSvcClientID > 0);
319
320 /*
321 * Execution loop.
322 *
323 * The thread at the moment does nothing but checking for one specific guest property
324 * for triggering a hard coded sysprep command with parameters given by the host. This
325 * feature was required by the VDI guys.
326 *
327 * Later this thread could become a general host->guest executor.. there are some
328 * sketches for this in the code.
329 */
330#ifdef FULL_FEATURED_EXEC
331 uint64_t u64TimestampPrev = UINT64_MAX;
332#endif
333 bool fSysprepDone = false;
334 bool fBitchedAboutMissingSysPrepCmd = false;
335 for (;;)
336 {
337 if (!fSysprepDone)
338 {
339 /*
340 * Get the sysprep command and arguments.
341 *
342 * The sysprep executable location is either retrieved from the host
343 * or is in a hard coded location depending on the Windows version.
344 */
345 char *pszSysprepExec = NULL;
346#ifdef SYSPREP_WITH_CMD
347 int rc = VBoxServiceExecReadHostProp("/VirtualBox/HostGuest/SysprepExec", &pszSysprepExec, NULL);
348 if (RT_SUCCESS(rc) && !*pszSysprepExec)
349 rc = VERR_NOT_FOUND;
350#else
351 /* Predefined sysprep. */
352 int rc = VINF_SUCCESS;
353 char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe";
354 OSVERSIONINFOEX OSInfoEx;
355 RT_ZERO(OSInfoEx);
356 OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
357 if ( GetVersionEx((LPOSVERSIONINFO) &OSInfoEx)
358 && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT
359 && OSInfoEx.dwMajorVersion >= 6 /* Vista or later */)
360 {
361 rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL);
362 if (RT_SUCCESS(rc))
363 rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\sysprep\\sysprep.exe");
364 }
365 pszSysprepExec = szSysprepCmd;
366#endif
367 if (RT_SUCCESS(rc))
368 {
369 char *pszSysprepArgs;
370 rc = VBoxServiceExecReadHostProp("/VirtualBox/HostGuest/SysprepArgs", &pszSysprepArgs, NULL);
371 if (RT_SUCCESS(rc) && !*pszSysprepArgs)
372 rc = VERR_NOT_FOUND;
373 if (RT_SUCCESS(rc))
374 {
375 if (RTFileExists(pszSysprepExec))
376 {
377 char **papszArgs;
378 rc = VBoxServiceExecCreateArgV(pszSysprepExec, pszSysprepArgs, &papszArgs);
379 if (RT_SUCCESS(rc))
380 {
381 /*
382 * Execute it synchronously and store the result.
383 *
384 * Note that RTProcWait should never fail here and
385 * that (the host is screwed if it does though).
386 */
387 VBoxServiceVerbose(3, "Exec: Executing sysprep ...\n");
388 for (size_t i = 0; papszArgs[i]; i++)
389 VBoxServiceVerbose(3, "Exec: sysprep argv[%u]: \"%s\"\n", i, papszArgs[i]);
390
391 /*
392 * Status reporting via SysprepVBoxRC is required here in order to make a host program wait for it
393 * in any case (here, yet before the execution or down below if something failed). If no SysprepVBoxRC
394 * would be updated here the host wouldn't know if he now has to wait for SysprepRet (all succeded) or
395 * SysprepRC (something failed):
396 *
397 * 1. Host sets SysprepArgs
398 * 2. Host waits for a changing SysprepRC (this could be either here or in step 5)
399 * 3. If SysprepRC is successful go to 4, otherwise exit
400 * 4. Host waits for a changing SysprepRet (result of sysprep.exe execution)
401 * 5. Host waits for a changing SysprepRC (final result of operation)
402 */
403 rc = VBoxServiceWritePropF(g_uExecGuestPropSvcClientID, "/VirtualBox/HostGuest/SysprepVBoxRC", "%d", rc);
404 if (RT_FAILURE(rc))
405 VBoxServiceError("Exec: Failed to write SysprepVBoxRC while setting up: rc=%Rrc\n", rc);
406
407 RTPROCESS pid;
408 if (RT_SUCCESS(rc))
409 rc = RTProcCreate(pszSysprepExec, papszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
410 if (RT_SUCCESS(rc))
411 {
412 RTPROCSTATUS Status;
413 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
414 if (RT_SUCCESS(rc))
415 {
416 VBoxServiceVerbose(1, "Sysprep returned: %d (reason %d)\n",
417 Status.iStatus, Status.enmReason);
418/** @todo r=bird: Figure out whether you should try re-execute sysprep if it
419 * fails or not. This is not mentioned in the defect. */
420 fSysprepDone = true; /* paranoia */
421
422 /*
423 * Store the result in Set return value so the host knows what happend.
424 */
425 rc = VBoxServiceWritePropF(g_uExecGuestPropSvcClientID,
426 "/VirtualBox/HostGuest/SysprepRet",
427 "%d", Status.iStatus);
428 if (RT_FAILURE(rc))
429 VBoxServiceError("Exec: Failed to write SysprepRet: rc=%Rrc\n", rc);
430 }
431 else
432 VBoxServiceError("Exec: RTProcWait failed for sysprep: %Rrc\n", rc);
433 }
434 VBoxServiceExecFreeArgV(papszArgs);
435 }
436 else
437 VBoxServiceError("Exec: VBoxServiceExecCreateArgV: %Rrc\n", rc);
438 }
439 else
440 {
441 if (!fBitchedAboutMissingSysPrepCmd)
442 {
443 VBoxServiceError("Exec: Sysprep executable not found! Search path=%s\n", pszSysprepExec);
444 fBitchedAboutMissingSysPrepCmd = true;
445 }
446 rc = VERR_FILE_NOT_FOUND;
447 }
448 RTStrFree(pszSysprepArgs);
449 }
450#ifdef SYSPREP_WITH_CMD
451 RTStrFree(pszSysprepExec);
452#endif
453 }
454
455 /*
456 * Only continue polling if the guest property value is empty/missing
457 * or if the sysprep command is missing.
458 */
459 if ( rc != VERR_NOT_FOUND
460 && rc != VERR_FILE_NOT_FOUND)
461 {
462 VBoxServiceVerbose(1, "Exec: Stopping sysprep processing (rc=%Rrc)\n", rc);
463 fSysprepDone = true;
464 }
465
466 /*
467 * Always let the host know what happend, except when the guest property
468 * value is empty/missing.
469 */
470 if (rc != VERR_NOT_FOUND)
471 {
472 rc = VBoxServiceWritePropF(g_uExecGuestPropSvcClientID, "/VirtualBox/HostGuest/SysprepVBoxRC", "%d", rc);
473 if (RT_FAILURE(rc))
474 VBoxServiceError("Exec: Failed to write final SysprepVBoxRC: rc=%Rrc\n", rc);
475 }
476 }
477
478#ifdef FULL_FEATURED_EXEC
479 1. Read the command - value, timestamp and flags.
480 2. Check that the flags indicates that the guest cannot write to it and that it's transient.
481 3. Check if the timestamp changed.
482 4. Get the arguments and other stuff.
483 5. Execute it. This may involve grabbing the output (stderr and/or stdout) and pushing into
484 values afterwards. It may also entail redirecting input to a file containing text from a guest prop value.
485 6. Set the result values (there will be three, one IPRT style one for everything up to
486 and including RTProcWait and two that mirrors Status.iStatus and Status.enmReason (stringified)).
487#endif
488
489 /*
490 * Block for a while.
491 *
492 * The event semaphore takes care of ignoring interruptions and it
493 * allows us to implement service wakeup later.
494 */
495 if (*pfShutdown)
496 break;
497#ifdef FULL_FEATURED_EXEC
498 Wait for changes to the command value. If that fails for some reason other than timeout / interrupt, fall back on the semaphore.
499#else
500 int rc2 = RTSemEventMultiWait(g_hExecEvent, g_cMsExecInterval);
501#endif
502 if (*pfShutdown)
503 break;
504 if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
505 {
506 VBoxServiceError("Exec: Service terminating - RTSemEventMultiWait: %Rrc\n", rc2);
507 rcRet = rc2;
508 break;
509 }
510 }
511
512 RTSemEventMultiDestroy(g_hExecEvent);
513 g_hExecEvent = NIL_RTSEMEVENTMULTI;
514 return rcRet;
515}
516
517
518/** @copydoc VBOXSERVICE::pfnStop */
519static DECLCALLBACK(void) VBoxServiceExecStop(void)
520{
521 /** @todo Later, figure what to do if we're in RTProcWait(). it's a very
522 * annoying call since doesn't support timeouts in the posix world. */
523 RTSemEventMultiSignal(g_hExecEvent);
524#ifdef FULL_FEATURED_EXEC
525 Interrupts waits.
526#endif
527}
528
529
530/** @copydoc VBOXSERVICE::pfnTerm */
531static DECLCALLBACK(void) VBoxServiceExecTerm(void)
532{
533 /* Nothing here yet. */
534 VbglR3GuestPropDisconnect(g_uExecGuestPropSvcClientID);
535 g_uExecGuestPropSvcClientID = 0;
536
537 RTSemEventMultiDestroy(g_hExecEvent);
538 g_hExecEvent = NIL_RTSEMEVENTMULTI;
539}
540
541
542/**
543 * The 'vminfo' service description.
544 */
545VBOXSERVICE g_Exec =
546{
547 /* pszName. */
548 "exec",
549 /* pszDescription. */
550 "Host-driven Command Execution",
551 /* pszUsage. */
552 "[--exec-interval <ms>]"
553 ,
554 /* pszOptions. */
555 " --exec-interval Specifies the interval at which to check for new\n"
556 " remote execution commands. The default is 10000 ms.\n"
557 ,
558 /* methods */
559 VBoxServiceExecPreInit,
560 VBoxServiceExecOption,
561 VBoxServiceExecInit,
562 VBoxServiceExecWorker,
563 VBoxServiceExecStop,
564 VBoxServiceExecTerm
565};
566
Note: See TracBrowser for help on using the repository browser.

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