VirtualBox

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

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

VBoxService: Exec: Also let host know the status if successful.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.7 KB
Line 
1/* $Id: VBoxServiceExec.cpp 23247 2009-09-23 09:10:07Z 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
140 *ppszValue = NULL;
141
142 for (unsigned cTries = 0; cTries < 10; cTries++)
143 {
144 /*
145 * (Re-)Allocate the buffer and try read the property.
146 */
147 RTMemFree(pvBuf);
148 pvBuf = RTMemAlloc(cbBuf);
149 if (!pvBuf)
150 {
151 VBoxServiceError("Exec: Failed to allocate %zu bytes\n", cbBuf);
152 rc = VERR_NO_MEMORY;
153 break;
154 }
155 char *pszValue;
156 char *pszFlags;
157 uint64_t uTimestamp;
158 rc = VbglR3GuestPropRead(g_uExecGuestPropSvcClientID, pszPropName,
159 pvBuf, cbBuf,
160 &pszValue, &uTimestamp, &pszFlags, NULL);
161 if (RT_FAILURE(rc))
162 {
163 if (rc == VERR_BUFFER_OVERFLOW)
164 {
165 /* try again with a bigger buffer. */
166 cbBuf *= 2;
167 continue;
168 }
169 if (rc == VERR_NOT_FOUND)
170 VBoxServiceVerbose(2, "Exec: %s not found\n", pszPropName);
171 else
172 VBoxServiceError("Exec: Failed to query \"%s\": %Rrc\n", pszPropName, rc);
173 break;
174 }
175
176 /*
177 * Validate it and set return values on success.
178 */
179 rc = VBoxServiceExecValidateFlags(pszFlags);
180 if (RT_FAILURE(rc))
181 {
182 static uint32_t s_cBitched = 0;
183 if (++s_cBitched < 10)
184 VBoxServiceError("Exec: Flag validation failed for \"%s\": %Rrc; flags=\"%s\"\n",
185 pszPropName, rc, pszFlags);
186 break;
187 }
188 VBoxServiceVerbose(2, "Exec: Read \"%s\" = \"%s\", timestamp %RU64n\n",
189 pszPropName, pszValue, uTimestamp);
190 *ppszValue = RTStrDup(pszValue);
191 if (!*ppszValue)
192 {
193 VBoxServiceError("Exec: RTStrDup failed for \"%s\"\n", pszValue);
194 rc = VERR_NO_MEMORY;
195 break;
196 }
197
198 if (puTimestamp)
199 *puTimestamp = uTimestamp;
200 break; /* done */
201 }
202
203 RTMemFree(pvBuf);
204 return rc;
205}
206
207
208/**
209 * Frees an argument vector constructed by VBoxServiceExecCreateArgV.
210 *
211 * @param papszArgs The vector to free.
212 */
213static void VBoxServiceExecFreeArgV(char **papszArgs)
214{
215 for (size_t i = 0; papszArgs[i]; i++)
216 {
217 RTStrFree(papszArgs[i]);
218 papszArgs[i] = NULL;
219 }
220 RTMemFree(papszArgs);
221}
222
223
224/**
225 * Creates an argument vector out of an executable name and a string containing
226 * the arguments separated by spaces.
227 *
228 * @returns VBox status code. Not bitched.
229 * @param pszExec The executable name.
230 * @param pszArgs The string containging the arguments.
231 * @param ppapszArgs Where to return the argument vector. Not set on
232 * failure. Use VBoxServiceExecFreeArgV to free.
233 *
234 * @todo Quoted strings. Do it unix (bourne shell) fashion.
235 */
236static int VBoxServiceExecCreateArgV(const char *pszExec, const char *pszArgs, char ***ppapszArgs)
237{
238 size_t cAlloc = 1;
239 size_t cUsed = 1;
240 char **papszArgs = (char **)RTMemAlloc(sizeof(char *) * (cAlloc + 1));
241 if (!papszArgs)
242 return VERR_NO_MEMORY;
243
244 /*
245 * Start by adding the executable name first.
246 * Note! We keep the papszArgs fully terminated at all times to keep cleanup simple.
247 */
248 int rc = VERR_NO_MEMORY;
249 papszArgs[1] = NULL;
250 papszArgs[0] = RTStrDup(pszExec);
251 if (papszArgs[0])
252 {
253 /*
254 * Parse the argument string and add any arguments found in it.
255 */
256 for (;;)
257 {
258 /* skip leading spaces */
259 char ch;
260 while ((ch = *pszArgs) && RT_C_IS_SPACE(ch))
261 pszArgs++;
262 if (!*pszArgs)
263 {
264 *ppapszArgs = papszArgs;
265 return VINF_SUCCESS;
266 }
267
268 /* find the of the current word. Quoting is ignored atm. */
269 char const *pszEnd = pszArgs + 1;
270 while ((ch = *pszEnd) && !RT_C_IS_SPACE(ch))
271 pszEnd++;
272
273 /* resize the vector. */
274 if (cUsed == cAlloc)
275 {
276 cAlloc += 10;
277 void *pvNew = RTMemRealloc(papszArgs, sizeof(char *) * (cAlloc + 1));
278 if (!pvNew)
279 break;
280 papszArgs = (char **)pvNew;
281 for (size_t i = cUsed; i <= cAlloc; i++)
282 papszArgs[i] = NULL;
283 }
284
285 /* add it */
286 papszArgs[cUsed] = RTStrDupN(pszArgs, (uintptr_t)pszEnd - (uintptr_t)pszArgs);
287 if (!papszArgs[cUsed])
288 break;
289 cUsed++;
290
291 /* advance */
292 pszArgs = pszEnd;
293 }
294 }
295
296 VBoxServiceExecFreeArgV(papszArgs);
297 return rc;
298}
299
300
301/** @copydoc VBOXSERVICE::pfnWorker */
302DECLCALLBACK(int) VBoxServiceExecWorker(bool volatile *pfShutdown)
303{
304 int rcRet = VINF_SUCCESS;
305
306 /*
307 * Tell the control thread that it can continue
308 * spawning services.
309 */
310 RTThreadUserSignal(RTThreadSelf());
311 Assert(g_uExecGuestPropSvcClientID > 0);
312
313 /*
314 * Execution loop.
315 *
316 * The thread at the moment does nothing but checking for one specific guest property
317 * for triggering a hard coded sysprep command with parameters given by the host. This
318 * feature was required by the VDI guys.
319 *
320 * Later this thread could become a general host->guest executor.. there are some
321 * sketches for this in the code.
322 */
323#ifdef FULL_FEATURED_EXEC
324 uint64_t u64TimestampPrev = UINT64_MAX;
325#endif
326 bool fSysprepDone = false;
327 bool fBitchedAboutMissingSysPrepCmd = false;
328 for (;;)
329 {
330 if (!fSysprepDone)
331 {
332 /*
333 * Get the sysprep command and arguments.
334 *
335 * The sysprep executable location is either retrieved from the host
336 * or is in a hard coded location depending on the Windows version.
337 */
338 char *pszSysprepExec = NULL;
339#ifdef SYSPREP_WITH_CMD
340 int rc = VBoxServiceExecReadHostProp("/VirtualBox/HostGuest/SysprepExec", &pszSysprepExec, NULL);
341 if (RT_SUCCESS(rc) && !*pszSysprepExec)
342 rc = VERR_NOT_FOUND;
343#else
344 /* Predefined sysprep. */
345 int rc = VINF_SUCCESS;
346 char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe";
347 OSVERSIONINFOEX OSInfoEx;
348 RT_ZERO(OSInfoEx);
349 OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
350 if ( GetVersionEx((LPOSVERSIONINFO) &OSInfoEx)
351 && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT
352 && OSInfoEx.dwMajorVersion >= 6 /* Vista or later */)
353 {
354 rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL);
355 if (RT_SUCCESS(rc))
356 rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\sysprep\\sysprep.exe");
357 }
358 pszSysprepExec = szSysprepCmd;
359#endif
360 if (RT_SUCCESS(rc))
361 {
362 char *pszSysprepArgs;
363 rc = VBoxServiceExecReadHostProp("/VirtualBox/HostGuest/SysprepArgs", &pszSysprepArgs, NULL);
364 if (RT_SUCCESS(rc) && !*pszSysprepArgs)
365 rc = VERR_NOT_FOUND;
366 if (RT_SUCCESS(rc))
367 {
368 if (RTFileExists(pszSysprepExec))
369 {
370 char **papszArgs;
371 rc = VBoxServiceExecCreateArgV(pszSysprepExec, pszSysprepArgs, &papszArgs);
372 if (RT_SUCCESS(rc))
373 {
374 /*
375 * Execute it synchronously and store the result.
376 *
377 * Note that RTProcWait should never fail here and
378 * that (the host is screwed if it does though).
379 */
380 VBoxServiceVerbose(3, "Exec: Executing sysprep ...\n");
381 for (size_t i = 0; papszArgs[i]; i++)
382 VBoxServiceVerbose(3, "Exec: sysprep argv[%u]: \"%s\"\n", i, papszArgs[i]);
383
384 RTPROCESS pid;
385 rc = RTProcCreate(pszSysprepExec, papszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
386 if (RT_SUCCESS(rc))
387 {
388 RTPROCSTATUS Status;
389 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
390 if (RT_SUCCESS(rc))
391 {
392 VBoxServiceVerbose(1, "Sysprep returned: %d (reason %d)\n",
393 Status.iStatus, Status.enmReason);
394/** @todo r=bird: Figure out whether you should try re-execute sysprep if it
395 * fails or not. This is not mentioned in the defect. */
396 fSysprepDone = true; /* paranoia */
397
398 /*
399 * Store the result in Set return value so the host knows what happend.
400 */
401 rc = VbglR3GuestPropWriteValueF(g_uExecGuestPropSvcClientID,
402 "/VirtualBox/HostGuest/SysprepRet",
403 "%d", Status.iStatus);
404 if (RT_FAILURE(rc))
405 VBoxServiceError("Exec: Failed to write SysprepRet: rc=%Rrc\n", rc);
406 }
407 else
408 VBoxServiceError("Exec: RTProcWait failed for sysprep: %Rrc\n", rc);
409 }
410 VBoxServiceExecFreeArgV(papszArgs);
411 }
412 else
413 VBoxServiceError("Exec: VBoxServiceExecCreateArgV: %Rrc\n", rc);
414 }
415 else
416 {
417 if (!fBitchedAboutMissingSysPrepCmd)
418 {
419 VBoxServiceError("Exec: Sysprep executable not found! Search path=%s\n", pszSysprepExec);
420 fBitchedAboutMissingSysPrepCmd = true;
421 }
422 rc = VERR_FILE_NOT_FOUND;
423 }
424 RTStrFree(pszSysprepArgs);
425 }
426#ifdef SYSPREP_WITH_CMD
427 RTStrFree(pszSysprepExec);
428#endif
429 }
430
431 if (RT_FAILURE(rc))
432 {
433 /*
434 * Only continue polling if the guest property value is empty/missing
435 * or if the sysprep command is missing.
436 */
437 if ( rc != VERR_NOT_FOUND
438 && rc != VERR_FILE_NOT_FOUND)
439 {
440 VBoxServiceVerbose(1, "Exec: Stopping sysprep processing (rc=%Rrc)\n", rc);
441 fSysprepDone = true;
442 }
443 }
444
445 /* Let the host know what happend (but only if we got a guest property value) */
446 if (rc != VERR_NOT_FOUND)
447 {
448 rc = VbglR3GuestPropWriteValueF(g_uExecGuestPropSvcClientID, "/VirtualBox/HostGuest/SysprepVBoxRC", "%d", rc);
449 if (RT_FAILURE(rc))
450 VBoxServiceError("Exec: Failed to write SysprepVBoxRC: rc=%Rrc\n", rc);
451 }
452 }
453
454#ifdef FULL_FEATURED_EXEC
455 1. Read the command - value, timestamp and flags.
456 2. Check that the flags indicates that the guest cannot write to it and that it's transient.
457 3. Check if the timestamp changed.
458 4. Get the arguments and other stuff.
459 5. Execute it. This may involve grabbing the output (stderr and/or stdout) and pushing into
460 values afterwards. It may also entail redirecting input to a file containing text from a guest prop value.
461 6. Set the result values (there will be three, one IPRT style one for everything up to
462 and including RTProcWait and two that mirrors Status.iStatus and Status.enmReason (stringified)).
463#endif
464
465 /*
466 * Block for a while.
467 *
468 * The event semaphore takes care of ignoring interruptions and it
469 * allows us to implement service wakeup later.
470 */
471 if (*pfShutdown)
472 break;
473#ifdef FULL_FEATURED_EXEC
474 Wait for changes to the command value. If that fails for some reason other than timeout / interrupt, fall back on the semaphore.
475#else
476 int rc2 = RTSemEventMultiWait(g_hExecEvent, g_cMsExecInterval);
477#endif
478 if (*pfShutdown)
479 break;
480 if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
481 {
482 VBoxServiceError("Exec: Service terminating - RTSemEventMultiWait: %Rrc\n", rc2);
483 rcRet = rc2;
484 break;
485 }
486 }
487
488 RTSemEventMultiDestroy(g_hExecEvent);
489 g_hExecEvent = NIL_RTSEMEVENTMULTI;
490 return rcRet;
491}
492
493
494/** @copydoc VBOXSERVICE::pfnStop */
495static DECLCALLBACK(void) VBoxServiceExecStop(void)
496{
497 /** @todo Later, figure what to do if we're in RTProcWait(). it's a very
498 * annoying call since doesn't support timeouts in the posix world. */
499 RTSemEventMultiSignal(g_hExecEvent);
500#ifdef FULL_FEATURED_EXEC
501 Interrupts waits.
502#endif
503}
504
505
506/** @copydoc VBOXSERVICE::pfnTerm */
507static DECLCALLBACK(void) VBoxServiceExecTerm(void)
508{
509 /* Nothing here yet. */
510 VbglR3GuestPropDisconnect(g_uExecGuestPropSvcClientID);
511 g_uExecGuestPropSvcClientID = 0;
512
513 RTSemEventMultiDestroy(g_hExecEvent);
514 g_hExecEvent = NIL_RTSEMEVENTMULTI;
515}
516
517
518/**
519 * The 'vminfo' service description.
520 */
521VBOXSERVICE g_Exec =
522{
523 /* pszName. */
524 "exec",
525 /* pszDescription. */
526 "Host-driven Command Execution",
527 /* pszUsage. */
528 "[--exec-interval <ms>]"
529 ,
530 /* pszOptions. */
531 " --exec-interval Specifies the interval at which to check for new\n"
532 " remote execution commands. The default is 10000 ms.\n"
533 ,
534 /* methods */
535 VBoxServiceExecPreInit,
536 VBoxServiceExecOption,
537 VBoxServiceExecInit,
538 VBoxServiceExecWorker,
539 VBoxServiceExecStop,
540 VBoxServiceExecTerm
541};
542
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