VirtualBox

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

Last change on this file since 22809 was 22809, checked in by vboxsync, 16 years ago

VBoxService: Small fixes + UTF-8 handling.

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