summaryrefslogtreecommitdiff
path: root/src/VBox/Additions/common/VBoxService/VBoxServiceControlExecThread.cpp
blob: fc5879b8e265507cffd94329ffa5a63a5865bea0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/* $Id: VBoxServiceControlExecThread.cpp $ */
/** @file
 * VBoxServiceControlExecThread - Thread for an executed guest process.
 */

/*
 * Copyright (C) 2011 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/getopt.h>
#include <iprt/mem.h>
#include <iprt/pipe.h>
#include <iprt/semaphore.h>
#include <iprt/string.h>

#include "VBoxServicePipeBuf.h"
#include "VBoxServiceControlExecThread.h"

extern RTLISTNODE g_GuestControlExecThreads;
extern RTCRITSECT g_GuestControlExecThreadsCritSect;


/**
 *  Allocates and gives back a thread data struct which then can be used by the worker thread.
 *  Needs to be freed with VBoxServiceControlExecDestroyThreadData().
 *
 * @return  IPRT status code.
 * @param   pThread                     The thread's handle to allocate the data for.
 * @param   u32ContextID                The context ID bound to this request / command.
 * @param   pszCmd                      Full qualified path of process to start (without arguments).
 * @param   uFlags                      Process execution flags.
 * @param   pszArgs                     String of arguments to pass to the process to start.
 * @param   uNumArgs                    Number of arguments specified in pszArgs.
 * @param   pszEnv                      String of environment variables ("FOO=BAR") to pass to the process
 *                                      to start.
 * @param   cbEnv                       Size (in bytes) of environment variables.
 * @param   uNumEnvVars                 Number of environment variables specified in pszEnv.
 * @param   pszUser                     User name (account) to start the process under.
 * @param   pszPassword                 Password of specified user name (account).
 * @param   uTimeLimitMS                Time limit (in ms) of the process' life time.
 */
int VBoxServiceControlExecThreadAlloc(PVBOXSERVICECTRLTHREAD pThread,
                                      uint32_t u32ContextID,
                                      const char *pszCmd, uint32_t uFlags,
                                      const char *pszArgs, uint32_t uNumArgs,
                                      const char *pszEnv, uint32_t cbEnv, uint32_t uNumEnvVars,
                                      const char *pszUser, const char *pszPassword, uint32_t uTimeLimitMS)
{
    AssertPtr(pThread);

    /* General stuff. */
    pThread->Node.pPrev = NULL;
    pThread->Node.pNext = NULL;

    pThread->fShutdown = false;
    pThread->fStarted = false;
    pThread->fStopped = false;

    pThread->uContextID = u32ContextID;
    /* ClientID will be assigned when thread is started! */

    /* Specific stuff. */
    PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREADDATAEXEC));
    if (pData == NULL)
        return VERR_NO_MEMORY;

    pData->uPID = 0; /* Don't have a PID yet. */
    pData->pszCmd = RTStrDup(pszCmd);
    pData->uFlags = uFlags;
    pData->uNumEnvVars = 0;
    pData->uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */

    /* Prepare argument list. */
    int rc = RTGetOptArgvFromString(&pData->papszArgs, (int*)&pData->uNumArgs,
                                    (uNumArgs > 0) ? pszArgs : "", NULL);
    /* Did we get the same result? */
    Assert(uNumArgs == pData->uNumArgs);

    if (RT_SUCCESS(rc))
    {
        /* Prepare environment list. */
        if (uNumEnvVars)
        {
            pData->papszEnv = (char **)RTMemAlloc(uNumEnvVars * sizeof(char*));
            AssertPtr(pData->papszEnv);
            pData->uNumEnvVars = uNumEnvVars;

            const char *pszCur = pszEnv;
            uint32_t i = 0;
            uint32_t cbLen = 0;
            while (cbLen < cbEnv)
            {
                /* sanity check */
                if (i >= uNumEnvVars)
                {
                    rc = VERR_INVALID_PARAMETER;
                    break;
                }
                int cbStr = RTStrAPrintf(&pData->papszEnv[i++], "%s", pszCur);
                if (cbStr < 0)
                {
                    rc = VERR_NO_STR_MEMORY;
                    break;
                }
                pszCur += cbStr + 1; /* Skip terminating '\0' */
                cbLen  += cbStr + 1; /* Skip terminating '\0' */
            }
        }

        pData->pszUser = RTStrDup(pszUser);
        pData->pszPassword = RTStrDup(pszPassword);
        pData->uTimeLimitMS = uTimeLimitMS;

        /* Adjust time limit value. */
        pData->uTimeLimitMS = (   uTimeLimitMS == UINT32_MAX
                               || uTimeLimitMS == 0)
                            ? RT_INDEFINITE_WAIT : uTimeLimitMS;

        /* Init buffers. */
        rc = VBoxServicePipeBufInit(&pData->stdOut, false /*fNeedNotificationPipe*/);
        if (RT_SUCCESS(rc))
        {
            rc = VBoxServicePipeBufInit(&pData->stdErr, false /*fNeedNotificationPipe*/);
            if (RT_SUCCESS(rc))
                rc = VBoxServicePipeBufInit(&pData->stdIn, true /*fNeedNotificationPipe*/);
        }
    }

    if (RT_FAILURE(rc))
    {
        VBoxServiceControlExecThreadDestroy(pData);
    }
    else
    {
        pThread->enmType = kVBoxServiceCtrlThreadDataExec;
        pThread->pvData = pData;
    }
    return rc;
}


/**
 *  Frees an allocated thread data structure along with all its allocated parameters.
 *
 * @param   pData          Pointer to thread data to free.
 */
void VBoxServiceControlExecThreadDestroy(PVBOXSERVICECTRLTHREADDATAEXEC pData)
{
    if (pData)
    {
        RTStrFree(pData->pszCmd);
        if (pData->uNumEnvVars)
        {
            for (uint32_t i = 0; i < pData->uNumEnvVars; i++)
                RTStrFree(pData->papszEnv[i]);
            RTMemFree(pData->papszEnv);
        }
        RTGetOptArgvFree(pData->papszArgs);
        RTStrFree(pData->pszUser);
        RTStrFree(pData->pszPassword);

        VBoxServicePipeBufDestroy(&pData->stdOut);
        VBoxServicePipeBufDestroy(&pData->stdErr);
        VBoxServicePipeBufDestroy(&pData->stdIn);

        RTMemFree(pData);
        pData = NULL;
    }
}


/**
 * Finds a (formerly) started process given by its PID.
 * Internal function, does not do locking -- this must be done from the caller function!
 *
 * @return  PVBOXSERVICECTRLTHREAD      Process structure if found, otherwise NULL.
 * @param   uPID                        PID to search for.
 */
PVBOXSERVICECTRLTHREAD vboxServiceControlExecThreadGetByPID(uint32_t uPID)
{
    PVBOXSERVICECTRLTHREAD pNode = NULL;
    RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)
    {
        if (   pNode->fStarted
            && pNode->enmType == kVBoxServiceCtrlThreadDataExec)
        {
            PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;
            if (pData && pData->uPID == uPID)
                return pNode;
        }
    }
    return NULL;
}


/**
 * Injects input to a specified running process.
 *
 * @return  IPRT status code.
 * @param   uPID                    PID of process to set the input for.
 * @param   fPendingClose           Flag indicating whether this is the last input block sent to the process.
 * @param   pBuf                    Pointer to a buffer containing the actual input data.
 * @param   cbSize                  Size (in bytes) of the input buffer data.
 * @param   pcbWritten              Pointer to number of bytes written to the process.  Optional.
 */
int VBoxServiceControlExecThreadSetInput(uint32_t uPID, bool fPendingClose, uint8_t *pBuf,
                                         uint32_t cbSize, uint32_t *pcbWritten)
{
    AssertPtrReturn(pBuf, VERR_INVALID_PARAMETER);

    int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
    if (RT_SUCCESS(rc))
    {
        PVBOXSERVICECTRLTHREAD pNode = vboxServiceControlExecThreadGetByPID(uPID);
        if (pNode)
        {
            PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;
            AssertPtr(pData);

            if (VBoxServicePipeBufIsEnabled(&pData->stdIn))
            {
                uint32_t cbWritten;
                /*
                 * Feed the data to the pipe.
                 */
                rc = VBoxServicePipeBufWriteToBuf(&pData->stdIn, pBuf,
                                                  cbSize, fPendingClose, &cbWritten);
                if (pcbWritten)
                    *pcbWritten = cbWritten;
            }
            else
            {
                /* If input buffer is not enabled anymore we cannot handle that data ... */
                rc = VERR_BAD_PIPE;
            }
        }
        else
            rc = VERR_NOT_FOUND; /* PID not found! */
        RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
    }
    return rc;
}


/**
 * Gets output from stdout/stderr of a specified process.
 *
 * @return  IPRT status code.
 * @param   uPID                    PID of process to retrieve the output from.
 * @param   uHandleId               Stream ID (stdout = 0, stderr = 2) to get the output from.
 * @param   uTimeout                Timeout (in ms) to wait for output becoming available.
 * @param   pBuf                    Pointer to a pre-allocated buffer to store the output.
 * @param   cbSize                  Size (in bytes) of the pre-allocated buffer.
 * @param   pcbRead                 Pointer to number of bytes read.  Optional.
 */
int VBoxServiceControlExecThreadGetOutput(uint32_t uPID, uint32_t uHandleId, uint32_t uTimeout,
                                          uint8_t *pBuf, uint32_t cbSize, uint32_t *pcbRead)
{
    AssertPtrReturn(pBuf, VERR_INVALID_PARAMETER);

    int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
    if (RT_SUCCESS(rc))
    {
        PVBOXSERVICECTRLTHREAD pNode = vboxServiceControlExecThreadGetByPID(uPID);
        if (pNode)
        {
            PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData;
            AssertPtr(pData);

            PVBOXSERVICECTRLEXECPIPEBUF pPipeBuf;
            switch (uHandleId)
            {
                case 2: /* StdErr */
                    pPipeBuf = &pData->stdErr;
                    break;

                case 0: /* StdOut */
                default:
                    pPipeBuf = &pData->stdOut;
                    break;
            }
            AssertPtr(pPipeBuf);

            /* If the stdout pipe buffer is enabled (that is, still could be filled by a running
             * process) wait for the signal to arrive so that we don't return without any actual
             * data read. */
            if (VBoxServicePipeBufIsEnabled(pPipeBuf))
                rc = VBoxServicePipeBufWaitForEvent(pPipeBuf, uTimeout);

            if (RT_SUCCESS(rc))
            {
                uint32_t cbRead = cbSize;
                rc = VBoxServicePipeBufRead(pPipeBuf, pBuf, cbSize, &cbRead);
                if (RT_SUCCESS(rc))
                {
                    if (pcbRead)
                        *pcbRead = cbRead;
                }
            }
        }
        else
            rc = VERR_NOT_FOUND; /* PID not found! */

        int rc2 = RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
        if (RT_SUCCESS(rc))
            rc = rc2;
    }
    return rc;
}


/**
 * Gracefully shuts down all process execution threads.
 *
 */
void VBoxServiceControlExecThreadsShutdown(void)
{
    int rc = RTCritSectEnter(&g_GuestControlExecThreadsCritSect);
    if (RT_SUCCESS(rc))
    {
        /* Signal all threads that we want to shutdown. */
        PVBOXSERVICECTRLTHREAD pNode;
        RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)
            ASMAtomicXchgBool(&pNode->fShutdown, true);

        /* Wait for threads to shutdown. */
        RTListForEach(&g_GuestControlExecThreads, pNode, VBOXSERVICECTRLTHREAD, Node)
        {
            if (pNode->Thread != NIL_RTTHREAD)
            {
                /* Wait a bit ... */
                int rc2 = RTThreadWait(pNode->Thread, 30 * 1000 /* Wait 30 seconds max. */, NULL);
                if (RT_FAILURE(rc2))
                    VBoxServiceError("Control: Thread failed to stop; rc2=%Rrc\n", rc2);
            }

            /* Destroy thread specific data. */
            switch (pNode->enmType)
            {
                case kVBoxServiceCtrlThreadDataExec:
                    VBoxServiceControlExecThreadDestroy((PVBOXSERVICECTRLTHREADDATAEXEC)pNode->pvData);
                    break;

                default:
                    break;
            }
        }

        /* Finally destroy thread list. */
        pNode = RTListGetFirst(&g_GuestControlExecThreads, VBOXSERVICECTRLTHREAD, Node);
        while (pNode)
        {
            PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pNode->Node, VBOXSERVICECTRLTHREAD, Node);
            bool fLast = RTListNodeIsLast(&g_GuestControlExecThreads, &pNode->Node);

            RTListNodeRemove(&pNode->Node);
            RTMemFree(pNode);

            if (fLast)
                break;

            pNode = pNext;
        }

        int rc2 = RTCritSectLeave(&g_GuestControlExecThreadsCritSect);
        if (RT_SUCCESS(rc))
            rc = rc2;
    }
    RTCritSectDelete(&g_GuestControlExecThreadsCritSect);
}