VirtualBox

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

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

VBoxServiceExec.cpp: fixed memory leak when getting an empty /VirtualBox/HostGuest/SysprepArgs and another one when the flags are wrong.

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