VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceControlExecThread.cpp@ 38113

Last change on this file since 38113 was 38113, checked in by vboxsync, 14 years ago

VBoxService/GuestCtrl: Fixed stability issues due to PID recycling, enhanced and unified debug logging along with thread names to stdout.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.5 KB
Line 
1/* $Id: VBoxServiceControlExecThread.cpp 38113 2011-07-22 13:57:35Z vboxsync $ */
2/** @file
3 * VBoxServiceControlExecThread - Thread for an executed guest process.
4 */
5
6/*
7 * Copyright (C) 2011 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 <iprt/asm.h>
23#include <iprt/assert.h>
24#include <iprt/getopt.h>
25#include <iprt/mem.h>
26#include <iprt/pipe.h>
27#include <iprt/semaphore.h>
28#include <iprt/string.h>
29
30#include <VBox/HostServices/GuestControlSvc.h>
31
32#include "VBoxServicePipeBuf.h"
33#include "VBoxServiceControlExecThread.h"
34
35extern RTLISTNODE g_GuestControlExecThreads;
36extern RTCRITSECT g_GuestControlExecThreadsCritSect;
37
38const PVBOXSERVICECTRLTHREAD vboxServiceControlExecThreadGetByPID(uint32_t uPID);
39
40/**
41 * Allocates and gives back a thread data struct which then can be used by the worker thread.
42 * Needs to be freed with VBoxServiceControlExecDestroyThreadData().
43 *
44 * @return IPRT status code.
45 * @param pThread The thread's handle to allocate the data for.
46 * @param u32ContextID The context ID bound to this request / command.
47 * @param pszCmd Full qualified path of process to start (without arguments).
48 * @param uFlags Process execution flags.
49 * @param pszArgs String of arguments to pass to the process to start.
50 * @param uNumArgs Number of arguments specified in pszArgs.
51 * @param pszEnv String of environment variables ("FOO=BAR") to pass to the process
52 * to start.
53 * @param cbEnv Size (in bytes) of environment variables.
54 * @param uNumEnvVars Number of environment variables specified in pszEnv.
55 * @param pszUser User name (account) to start the process under.
56 * @param pszPassword Password of specified user name (account).
57 * @param uTimeLimitMS Time limit (in ms) of the process' life time.
58 */
59int VBoxServiceControlExecThreadAlloc(PVBOXSERVICECTRLTHREAD pThread,
60 uint32_t u32ContextID,
61 const char *pszCmd, uint32_t uFlags,
62 const char *pszArgs, uint32_t uNumArgs,
63 const char *pszEnv, uint32_t cbEnv, uint32_t uNumEnvVars,
64 const char *pszUser, const char *pszPassword, uint32_t uTimeLimitMS)
65{
66 AssertPtr(pThread);
67
68 /* General stuff. */
69 pThread->Node.pPrev = NULL;
70 pThread->Node.pNext = NULL;
71
72 pThread->fShutdown = false;
73 pThread->fStarted = false;
74 pThread->fStopped = false;
75
76 pThread->uContextID = u32ContextID;
77 /* ClientID will be assigned when thread is started! */
78
79 /* Specific stuff. */
80 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREADDATAEXEC));
81 if (pData == NULL)
82 return VERR_NO_MEMORY;
83
84 pData->uPID = 0; /* Don't have a PID yet. */
85 pData->pszCmd = RTStrDup(pszCmd);
86 pData->uFlags = uFlags;
87 pData->uNumEnvVars = 0;
88 pData->uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */
89
90 /* Prepare argument list. */
91 int rc = RTGetOptArgvFromString(&pData->papszArgs, (int*)&pData->uNumArgs,
92 (uNumArgs > 0) ? pszArgs : "", NULL);
93 /* Did we get the same result? */
94 Assert(uNumArgs == pData->uNumArgs);
95
96 if (RT_SUCCESS(rc))
97 {
98 /* Prepare environment list. */
99 if (uNumEnvVars)
100 {
101 pData->papszEnv = (char **)RTMemAlloc(uNumEnvVars * sizeof(char*));
102 AssertPtr(pData->papszEnv);
103 pData->uNumEnvVars = uNumEnvVars;
104
105 const char *pszCur = pszEnv;
106 uint32_t i = 0;
107 uint32_t cbLen = 0;
108 while (cbLen < cbEnv)
109 {
110 /* sanity check */
111 if (i >= uNumEnvVars)
112 {
113 rc = VERR_INVALID_PARAMETER;
114 break;
115 }
116 int cbStr = RTStrAPrintf(&pData->papszEnv[i++], "%s", pszCur);
117 if (cbStr < 0)
118 {
119 rc = VERR_NO_STR_MEMORY;
120 break;
121 }
122 pszCur += cbStr + 1; /* Skip terminating '\0' */
123 cbLen += cbStr + 1; /* Skip terminating '\0' */
124 }
125 }
126
127 pData->pszUser = RTStrDup(pszUser);
128 pData->pszPassword = RTStrDup(pszPassword);
129 pData->uTimeLimitMS = uTimeLimitMS;
130
131 /* Adjust time limit value. */
132 pData->uTimeLimitMS = ( uTimeLimitMS == UINT32_MAX
133 || uTimeLimitMS == 0)
134 ? RT_INDEFINITE_WAIT : uTimeLimitMS;
135
136 /* Init buffers. */
137 rc = VBoxServicePipeBufInit(&pData->stdOut, VBOXSERVICECTRLPIPEID_STDOUT,
138 false /*fNeedNotificationPipe*/);
139 if (RT_SUCCESS(rc))
140 {
141 rc = VBoxServicePipeBufInit(&pData->stdErr, VBOXSERVICECTRLPIPEID_STDERR,
142 false /*fNeedNotificationPipe*/);
143 if (RT_SUCCESS(rc))
144 rc = VBoxServicePipeBufInit(&pData->stdIn, VBOXSERVICECTRLPIPEID_STDIN,
145 true /*fNeedNotificationPipe*/);
146 }
147
148 if (RT_SUCCESS(rc))
149 {
150 pThread->enmType = kVBoxServiceCtrlThreadDataExec;
151 pThread->pvData = pData;
152 }
153 }
154
155 if (RT_FAILURE(rc))
156 VBoxServiceControlExecThreadDestroy(pData);
157 return rc;
158}
159
160
161/**
162 * Frees an allocated thread data structure along with all its allocated parameters.
163 *
164 * @param pData Pointer to thread data to free.
165 */
166void VBoxServiceControlExecThreadDestroy(PVBOXSERVICECTRLTHREADDATAEXEC pData)
167{
168 if (pData)
169 {
170 VBoxServiceVerbose(3, "ControlExec: [PID %u]: Destroying thread ...\n",
171 pData->uPID);
172
173 RTStrFree(pData->pszCmd);
174 if (pData->uNumEnvVars)
175 {
176 for (uint32_t i = 0; i < pData->uNumEnvVars; i++)
177 RTStrFree(pData->papszEnv[i]);
178 RTMemFree(pData->papszEnv);
179 }
180 RTGetOptArgvFree(pData->papszArgs);
181 RTStrFree(pData->pszUser);
182 RTStrFree(pData->pszPassword);
183
184 VBoxServicePipeBufDestroy(&pData->stdOut);
185 VBoxServicePipeBufDestroy(&pData->stdErr);
186 VBoxServicePipeBufDestroy(&pData->stdIn);
187
188 RTMemFree(pData);
189 pData = NULL;
190 }
191}
192
193
194int VBoxServiceControlExecThreadAssignPID(PVBOXSERVICECTRLTHREADDATAEXEC pData, uint32_t uPID)
195{
196 AssertPtrReturn(pData, VERR_INVALID_POINTER);
197 AssertReturn(uPID, VERR_INVALID_PARAMETER);
198
199 int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
200 if (RT_SUCCESS(rc))
201 {
202 /* Search an old thread using the desired PID and shut it down completely -- it's
203 * not used anymore. */
204 const PVBOXSERVICECTRLTHREAD pOldThread = vboxServiceControlExecThreadGetByPID(uPID);
205 if ( pOldThread
206 && pOldThread->pvData != pData)
207 {
208 VBoxServiceVerbose(3, "ControlExec: PID %u was used before, shutting down stale exec thread ...\n",
209 uPID);
210 AssertPtr(pOldThread->pvData);
211 VBoxServiceControlExecThreadDestroy((PVBOXSERVICECTRLTHREADDATAEXEC)pOldThread->pvData);
212 }
213 /** @todo Remove node from thread list! */
214
215 /* Assign PID to current thread. */
216 pData->uPID = uPID;
217 VBoxServicePipeBufSetPID(&pData->stdIn, pData->uPID);
218 VBoxServicePipeBufSetPID(&pData->stdOut, pData->uPID);
219 VBoxServicePipeBufSetPID(&pData->stdErr, pData->uPID);
220
221 int rc2 = RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
222 if (RT_SUCCESS(rc))
223 rc = rc2;
224 }
225
226 return rc;
227}
228
229
230/**
231 * Finds a (formerly) started process given by its PID.
232 * Internal function, does not do locking -- this must be done from the caller function!
233 *
234 * @return PVBOXSERVICECTRLTHREAD Process structure if found, otherwise NULL.
235 * @param uPID PID to search for.
236 */
237const PVBOXSERVICECTRLTHREAD vboxServiceControlExecThreadGetByPID(uint32_t uPID)
238{
239 PVBOXSERVICECTRLTHREAD pNode = NULL;
240 RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)
241 {
242 if ( pNode->fStarted
243 && pNode->enmType == kVBoxServiceCtrlThreadDataExec)
244 {
245 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;
246 if (pData && pData->uPID == uPID)
247 return pNode;
248 }
249 }
250 return NULL;
251}
252
253
254/**
255 * Injects input to a specified running process.
256 *
257 * @return IPRT status code.
258 * @param uPID PID of process to set the input for.
259 * @param fPendingClose Flag indicating whether this is the last input block sent to the process.
260 * @param pBuf Pointer to a buffer containing the actual input data.
261 * @param cbSize Size (in bytes) of the input buffer data.
262 * @param pcbWritten Pointer to number of bytes written to the process. Optional.
263 */
264int VBoxServiceControlExecThreadSetInput(uint32_t uPID, bool fPendingClose, uint8_t *pBuf,
265 uint32_t cbSize, uint32_t *pcbWritten)
266{
267 AssertPtrReturn(pBuf, VERR_INVALID_PARAMETER);
268
269 int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
270 if (RT_SUCCESS(rc))
271 {
272 PVBOXSERVICECTRLTHREAD pNode = vboxServiceControlExecThreadGetByPID(uPID);
273 if (pNode)
274 {
275 PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;
276 AssertPtr(pData);
277
278 if (VBoxServicePipeBufIsEnabled(&pData->stdIn))
279 {
280 /*
281 * Feed the data to the pipe.
282 */
283 uint32_t cbWritten;
284 rc = VBoxServicePipeBufWriteToBuf(&pData->stdIn, pBuf,
285 cbSize, fPendingClose, &cbWritten);
286 if (pcbWritten)
287 *pcbWritten = cbWritten;
288 }
289 else
290 {
291 /* If input buffer is not enabled anymore we cannot handle that data ... */
292 rc = VERR_BAD_PIPE;
293 }
294 }
295 else
296 rc = VERR_NOT_FOUND; /* PID not found! */
297 RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
298 }
299 return rc;
300}
301
302
303/**
304 * Gets output from stdout/stderr of a specified process.
305 *
306 * @return IPRT status code.
307 * @param uPID PID of process to retrieve the output from.
308 * @param uHandleId Stream ID (stdout = 0, stderr = 2) to get the output from.
309 * @param uTimeout Timeout (in ms) to wait for output becoming available.
310 * @param pBuf Pointer to a pre-allocated buffer to store the output.
311 * @param cbSize Size (in bytes) of the pre-allocated buffer.
312 * @param pcbRead Pointer to number of bytes read. Optional.
313 */
314int VBoxServiceControlExecThreadGetOutput(uint32_t uPID, uint32_t uHandleId, uint32_t uTimeout,
315 uint8_t *pBuf, uint32_t cbSize, uint32_t *pcbRead)
316{
317 AssertPtrReturn(pBuf, VERR_INVALID_POINTER);
318 AssertReturn(cbSize, VERR_INVALID_PARAMETER);
319
320 int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
321 if (RT_SUCCESS(rc))
322 {
323 const PVBOXSERVICECTRLTHREAD pNode = vboxServiceControlExecThreadGetByPID(uPID);
324 if (pNode)
325 {
326 const PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;
327 AssertPtr(pData);
328
329 PVBOXSERVICECTRLEXECPIPEBUF pPipeBuf = NULL;
330 switch (uHandleId)
331 {
332 case OUTPUT_HANDLE_ID_STDERR: /* StdErr */
333 pPipeBuf = &pData->stdErr;
334 break;
335
336 case OUTPUT_HANDLE_ID_STDOUT: /* StdOut */
337 pPipeBuf = &pData->stdOut;
338 break;
339
340 default:
341 AssertReleaseMsgFailed(("Unknown output handle ID (%u)\n", uHandleId));
342 break;
343 }
344 if (!pPipeBuf)
345 return VERR_INVALID_PARAMETER;
346
347#ifdef DEBUG_andy
348 VBoxServiceVerbose(4, "ControlExec: [PID %u]: Getting output from pipe buffer %u ...\n",
349 uPID, pPipeBuf->uPipeId);
350#endif
351 /* If the stdout pipe buffer is enabled (that is, still could be filled by a running
352 * process) wait for the signal to arrive so that we don't return without any actual
353 * data read. */
354 bool fEnabled = VBoxServicePipeBufIsEnabled(pPipeBuf);
355 if (fEnabled)
356 {
357#ifdef DEBUG_andy
358 VBoxServiceVerbose(4, "ControlExec: [PID %u]: Waiting for pipe buffer %u\n",
359 uPID, pPipeBuf->uPipeId);
360#endif
361 rc = VBoxServicePipeBufWaitForEvent(pPipeBuf, uTimeout);
362 }
363 if (RT_SUCCESS(rc))
364 {
365 uint32_t cbRead = cbSize;
366 rc = VBoxServicePipeBufRead(pPipeBuf, pBuf, cbSize, &cbRead);
367 if (RT_SUCCESS(rc))
368 {
369 if (fEnabled && !cbRead)
370 AssertMsgFailed(("Waited for pipe buffer %u, but nothing read!\n",
371 pPipeBuf->uPipeId));
372 if (pcbRead)
373 *pcbRead = cbRead;
374 }
375 else
376 VBoxServiceError("ControlExec: [PID %u]: Unable to read from pipe buffer %u, rc=%Rrc\n",
377 uPID, pPipeBuf->uPipeId, rc);
378 }
379 }
380 else
381 rc = VERR_NOT_FOUND; /* PID not found! */
382
383 int rc2 = RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
384 if (RT_SUCCESS(rc))
385 rc = rc2;
386 }
387 return rc;
388}
389
390
391/**
392 * Gracefully shuts down all process execution threads.
393 *
394 */
395void VBoxServiceControlExecThreadsShutdown(void)
396{
397 int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
398 if (RT_SUCCESS(rc))
399 {
400 /* Signal all threads that we want to shutdown. */
401 PVBOXSERVICECTRLTHREAD pNode;
402 RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)
403 ASMAtomicXchgBool(&pNode->fShutdown, true);
404
405 /* Wait for threads to shutdown. */
406 RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)
407 {
408 if (pNode->Thread != NIL_RTTHREAD)
409 {
410 /* Wait a bit ... */
411 int rc2 = RTThreadWait(pNode->Thread, 30 * 1000 /* Wait 30 seconds max. */, NULL);
412 if (RT_FAILURE(rc2))
413 VBoxServiceError("Control: Thread failed to stop; rc2=%Rrc\n", rc2);
414 }
415
416 /* Destroy thread specific data. */
417 switch (pNode->enmType)
418 {
419 case kVBoxServiceCtrlThreadDataExec:
420 VBoxServiceControlExecThreadDestroy((PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData);
421 break;
422
423 default:
424 break;
425 }
426 }
427
428 /* Finally destroy thread list. */
429 pNode = RTListGetFirst(&g_GuestControlExecThreads, VBOXSERVICECTRLTHREAD, Node);
430 while (pNode)
431 {
432 PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pNode->Node, VBOXSERVICECTRLTHREAD, Node);
433 bool fLast = RTListNodeIsLast(&g_GuestControlExecThreads, &pNode->Node);
434
435 RTListNodeRemove(&pNode->Node);
436 RTMemFree(pNode);
437
438 if (fLast)
439 break;
440
441 pNode = pNext;
442 }
443
444 int rc2 = RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
445 if (RT_SUCCESS(rc))
446 rc = rc2;
447 }
448 RTCritSectDelete(&g_GuestControlExecThreadsCritSect);
449}
450
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