VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp@ 45697

Last change on this file since 45697 was 45697, checked in by vboxsync, 12 years ago

GuestCtrl: Various bugfixes required for test driver to pass.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 83.8 KB
Line 
1/* $Id: VBoxServiceControlSession.cpp 45697 2013-04-24 13:30:50Z vboxsync $ */
2/** @file
3 * VBoxServiceControlSession - Guest session handling. Also handles
4 * the forked session processes.
5 */
6
7/*
8 * Copyright (C) 2013 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19
20/*******************************************************************************
21* Header Files *
22*******************************************************************************/
23#include <iprt/asm.h>
24#include <iprt/assert.h>
25#include <iprt/env.h>
26#include <iprt/file.h>
27#include <iprt/getopt.h>
28#include <iprt/handle.h>
29#include <iprt/mem.h>
30#include <iprt/message.h>
31#include <iprt/path.h>
32#include <iprt/pipe.h>
33#include <iprt/poll.h>
34#include <iprt/process.h>
35
36#include "VBoxServiceInternal.h"
37#include "VBoxServiceUtils.h"
38#include "VBoxServiceControl.h"
39
40using namespace guestControl;
41
42/*******************************************************************************
43* Externals *
44*******************************************************************************/
45extern RTLISTANCHOR g_lstControlSessionThreads;
46extern VBOXSERVICECTRLSESSION g_Session;
47
48extern int VBoxServiceLogCreate(const char *pszLogFile);
49extern void VBoxServiceLogDestroy(void);
50
51/*******************************************************************************
52* Internal Functions *
53*******************************************************************************/
54static int gstcntlSessionFileDestroy(PVBOXSERVICECTRLFILE pFile);
55static PVBOXSERVICECTRLFILE gstcntlSessionGetFile(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle);
56static int gstcntlSessionGetOutput(const PVBOXSERVICECTRLSESSION pSession, uint32_t uPID, uint32_t uCID, uint32_t uHandleId, uint32_t cMsTimeout, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead);
57static int gstcntlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx);
58static int gstcntlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx);
59static int gstcntlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx);
60static int gstcntlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf);
61static int gstcntlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx);
62static int gstcntlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx);
63static int gstcntlSessionSetInput(const PVBOXSERVICECTRLSESSION pSession, uint32_t uPID, uint32_t uCID, bool fPendingClose, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten);
64static DECLCALLBACK(int) gstcntlSessionThread(RTTHREAD ThreadSelf, void *pvUser);
65
66/** Generic option indices for session fork arguments. */
67enum
68{
69 VBOXSERVICESESSIONOPT_LOG_FILE = 1000,
70 VBOXSERVICESESSIONOPT_USERNAME,
71 VBOXSERVICESESSIONOPT_SESSION_ID,
72 VBOXSERVICESESSIONOPT_SESSION_PROTO,
73 VBOXSERVICESESSIONOPT_THREAD_ID
74};
75
76
77static int gstcntlSessionFileDestroy(PVBOXSERVICECTRLFILE pFile)
78{
79 AssertPtrReturn(pFile, VERR_INVALID_POINTER);
80
81 int rc = RTFileClose(pFile->hFile);
82 if (RT_SUCCESS(rc))
83 {
84 /* Remove file entry in any case. */
85 RTListNodeRemove(&pFile->Node);
86 /* Destroy this object. */
87 RTMemFree(pFile);
88 }
89
90 return rc;
91}
92
93
94static PVBOXSERVICECTRLFILE gstcntlSessionGetFile(const PVBOXSERVICECTRLSESSION pSession,
95 uint32_t uHandle)
96{
97 AssertPtrReturn(pSession, NULL);
98
99 PVBOXSERVICECTRLFILE pFileCur = NULL;
100 /** @todo Use a map later! */
101 RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node)
102 {
103 if (pFileCur->uHandle == uHandle)
104 return pFileCur;
105 }
106
107 return NULL;
108}
109
110
111#ifdef DEBUG
112static int gstcntlSessionDumpToFile(const char *pszFileName, void *pvBuf, size_t cbBuf)
113{
114 AssertPtrReturn(pszFileName, VERR_INVALID_POINTER);
115 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
116
117 if (!cbBuf)
118 return VINF_SUCCESS;
119
120 char szFile[RTPATH_MAX];
121
122 int rc = RTPathTemp(szFile, sizeof(szFile));
123 if (RT_SUCCESS(rc))
124 rc = RTPathAppend(szFile, sizeof(szFile), pszFileName);
125
126 if (RT_SUCCESS(rc))
127 {
128 VBoxServiceVerbose(4, "Dumping %ld bytes to \"%s\"\n", cbBuf, szFile);
129
130 RTFILE fh;
131 rc = RTFileOpen(&fh, szFile, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
132 if (RT_SUCCESS(rc))
133 {
134 rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */);
135 RTFileClose(fh);
136 }
137 }
138
139 return rc;
140}
141#endif
142
143
144static int gstcntlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession,
145 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
146{
147 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
148 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
149
150 char szFile[RTPATH_MAX];
151 char szOpenMode[64];
152 char szDisposition[64];
153 uint32_t uCreationMode = 0;
154 uint64_t uOffset = 0;
155
156 uint32_t uHandle = 0;
157 int rc = VbglR3GuestCtrlFileGetOpen(pHostCtx,
158 /* File to open. */
159 szFile, sizeof(szFile),
160 /* Open mode. */
161 szOpenMode, sizeof(szOpenMode),
162 /* Disposition. */
163 szDisposition, sizeof(szDisposition),
164 /* Creation mode. */
165 &uCreationMode,
166 /* Offset. */
167 &uOffset);
168 if (RT_SUCCESS(rc))
169 {
170 PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAlloc(sizeof(VBOXSERVICECTRLFILE));
171 if (pFile)
172 {
173 if (!RTStrPrintf(pFile->szName, sizeof(pFile->szName), "%s", szFile))
174 rc = VERR_BUFFER_OVERFLOW;
175
176 if (RT_SUCCESS(rc))
177 {
178 uint64_t fFlags = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE; /** @todo Modes! */
179 rc = RTFileOpen(&pFile->hFile, pFile->szName, fFlags);
180 if ( RT_SUCCESS(rc)
181 && uOffset)
182 {
183 /* Seeking is optional. */
184 int rc2 = RTFileSeek(pFile->hFile, (int64_t)uOffset, RTFILE_SEEK_BEGIN, NULL /* Current offset */);
185 if (RT_FAILURE(rc2))
186 VBoxServiceVerbose(3, "[File %s]: Seeking to offset %RU64 failed; rc=%Rrc\n",
187 pFile->szName, uOffset, rc);
188 }
189 else
190 VBoxServiceVerbose(3, "[File %s]: Opening failed; rc=%Rrc\n",
191 pFile->szName, rc);
192 }
193
194 if (RT_SUCCESS(rc))
195 {
196 uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID);
197 pFile->uHandle = uHandle;
198 /* rc = */ RTListAppend(&pSession->lstFiles, &pFile->Node);
199
200 VBoxServiceVerbose(3, "[File %s]: Opened (ID=%RU32)\n",
201 pFile->szName, pFile->uHandle);
202 }
203
204 if (RT_FAILURE(rc))
205 RTMemFree(pFile);
206 }
207 else
208 rc = VERR_NO_MEMORY;
209
210 /* Report back in any case. */
211 int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle);
212 if (RT_FAILURE(rc2))
213 VBoxServiceError("[File %s]: Failed to report file open status, rc=%Rrc\n",
214 szFile, rc2);
215 if (RT_SUCCESS(rc))
216 rc = rc2;
217 }
218
219 return rc;
220}
221
222
223static int gstcntlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession,
224 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
225{
226 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
227 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
228
229 uint32_t uHandle;
230
231 int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */);
232 if (RT_SUCCESS(rc))
233 {
234 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
235 if (pFile)
236 {
237 rc = gstcntlSessionFileDestroy(pFile);
238 }
239 else
240 rc = VERR_NOT_FOUND;
241
242 /* Report back in any case. */
243 int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc);
244 if (RT_FAILURE(rc2))
245 VBoxServiceError("Failed to report file close status, rc=%Rrc\n", rc2);
246 if (RT_SUCCESS(rc))
247 rc = rc2;
248 }
249 return rc;
250}
251
252
253static int gstcntlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession,
254 PVBGLR3GUESTCTRLCMDCTX pHostCtx,
255 void *pvScratchBuf, size_t cbScratchBuf)
256{
257 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
258 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
259
260 uint32_t uHandle;
261 uint32_t cbToRead;
262
263 int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead);
264 if (RT_SUCCESS(rc))
265 {
266 void *pvDataRead = pvScratchBuf;
267 size_t cbRead = 0;
268
269 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
270 if (pFile)
271 {
272 if (cbToRead)
273 {
274 if (cbToRead > cbScratchBuf)
275 {
276 pvDataRead = RTMemAlloc(cbToRead);
277 if (!pvDataRead)
278 rc = VERR_NO_MEMORY;
279 }
280
281 if (RT_LIKELY(RT_SUCCESS(rc)))
282 rc = RTFileRead(pFile->hFile, pvDataRead, cbToRead, &cbRead);
283 }
284 else
285 rc = VERR_BUFFER_UNDERFLOW;
286 }
287 else
288 rc = VERR_NOT_FOUND;
289
290 /* Report back in any case. */
291 int rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, pvDataRead, (uint32_t)cbRead);
292 if ( cbToRead > cbScratchBuf
293 && pvDataRead)
294 RTMemFree(pvDataRead);
295
296 if (RT_FAILURE(rc2))
297 VBoxServiceError("Failed to report file read status, rc=%Rrc\n", rc2);
298 if (RT_SUCCESS(rc))
299 rc = rc2;
300 }
301 return rc;
302}
303
304
305static int gstcntlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession,
306 PVBGLR3GUESTCTRLCMDCTX pHostCtx,
307 void *pvScratchBuf, size_t cbScratchBuf)
308{
309 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
310 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
311
312 uint32_t uHandle;
313 uint32_t cbToRead; int64_t iOffset;
314
315 int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx,
316 &uHandle, &cbToRead, (uint64_t*)&iOffset);
317 if (RT_SUCCESS(rc))
318 {
319 void *pvDataRead = pvScratchBuf;
320 size_t cbRead = 0;
321
322 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
323 if (pFile)
324 {
325 if (cbToRead)
326 {
327 if (cbToRead > cbScratchBuf)
328 {
329 pvDataRead = RTMemAlloc(cbToRead);
330 if (!pvDataRead)
331 rc = VERR_NO_MEMORY;
332 }
333
334 if (RT_LIKELY(RT_SUCCESS(rc)))
335 rc = RTFileReadAt(pFile->hFile, iOffset, pvDataRead, cbToRead, &cbRead);
336 }
337 else
338 rc = VERR_BUFFER_UNDERFLOW;
339 }
340 else
341 rc = VERR_NOT_FOUND;
342
343 /* Report back in any case. */
344 int rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, pvDataRead, (uint32_t)cbRead);
345 if ( cbToRead > cbScratchBuf
346 && pvDataRead)
347 RTMemFree(pvDataRead);
348
349 if (RT_FAILURE(rc2))
350 VBoxServiceError("Failed to report file read status, rc=%Rrc\n", rc2);
351 if (RT_SUCCESS(rc))
352 rc = rc2;
353 }
354 return rc;
355}
356
357
358static int gstcntlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession,
359 PVBGLR3GUESTCTRLCMDCTX pHostCtx,
360 void *pvScratchBuf, size_t cbScratchBuf)
361{
362 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
363 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
364 AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER);
365 AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER);
366
367 uint32_t uHandle;
368 uint32_t cbToWrite;
369
370 int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle,
371 pvScratchBuf, cbScratchBuf,
372 &cbToWrite);
373 if (RT_SUCCESS(rc))
374 {
375 size_t cbWritten = 0;
376 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
377 if (pFile)
378 {
379 rc = RTFileWrite(pFile->hFile, pvScratchBuf, cbScratchBuf, &cbWritten);
380 }
381 else
382 rc = VERR_NOT_FOUND;
383
384 /* Report back in any case. */
385 int rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten);
386 if (RT_FAILURE(rc2))
387 VBoxServiceError("Failed to report file write status, rc=%Rrc\n", rc2);
388 if (RT_SUCCESS(rc))
389 rc = rc2;
390 }
391 return rc;
392}
393
394
395static int gstcntlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession,
396 PVBGLR3GUESTCTRLCMDCTX pHostCtx,
397 void *pvScratchBuf, size_t cbScratchBuf)
398{
399 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
400 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
401 AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER);
402 AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER);
403
404 uint32_t uHandle;
405 uint32_t cbToWrite; int64_t iOffset;
406
407 int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle,
408 pvScratchBuf, cbScratchBuf,
409 &cbToWrite, (uint64_t*)&iOffset);
410 if (RT_SUCCESS(rc))
411 {
412 size_t cbWritten = 0;
413 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
414 if (pFile)
415 {
416 rc = RTFileWriteAt(pFile->hFile, iOffset,
417 pvScratchBuf, cbScratchBuf, &cbWritten);
418 }
419 else
420 rc = VERR_NOT_FOUND;
421
422 /* Report back in any case. */
423 int rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten);
424 if (RT_FAILURE(rc2))
425 VBoxServiceError("Failed to report file write status, rc=%Rrc\n", rc2);
426 if (RT_SUCCESS(rc))
427 rc = rc2;
428 }
429 return rc;
430}
431
432
433static int gstcntlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession,
434 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
435{
436 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
437 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
438
439 uint32_t uHandle;
440 uint32_t uSeekMethod;
441 uint64_t uOffset; /* Will be converted to int64_t. */
442
443 uint64_t uOffsetActual = 0;
444
445 int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle,
446 &uSeekMethod, &uOffset);
447 if (RT_SUCCESS(rc))
448 {
449 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
450 if (pFile)
451 {
452 unsigned uSeekMethodIPRT;
453 switch (uSeekMethod)
454 {
455 case GUEST_FILE_SEEKTYPE_BEGIN:
456 uSeekMethodIPRT = RTFILE_SEEK_BEGIN;
457 break;
458
459 case GUEST_FILE_SEEKTYPE_CURRENT:
460 uSeekMethodIPRT = RTFILE_SEEK_CURRENT;
461 break;
462
463 case GUEST_FILE_SEEKTYPE_END:
464 uSeekMethodIPRT = RTFILE_SEEK_END;
465 break;
466
467 default:
468 rc = VERR_NOT_SUPPORTED;
469 break;
470 }
471
472 if (RT_SUCCESS(rc))
473 rc = RTFileSeek(pFile->hFile, (int64_t)uOffset,
474 uSeekMethodIPRT, &uOffsetActual);
475 }
476 else
477 rc = VERR_NOT_FOUND;
478
479 /* Report back in any case. */
480 int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, uOffsetActual);
481 if (RT_FAILURE(rc2))
482 VBoxServiceError("Failed to report file seek status, rc=%Rrc\n", rc2);
483 if (RT_SUCCESS(rc))
484 rc = rc2;
485 }
486 return rc;
487}
488
489
490static int gstcntlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession,
491 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
492{
493 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
494 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
495
496 uint32_t uHandle;
497 uint64_t uOffsetActual = 0;
498
499 int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle);
500 if (RT_SUCCESS(rc))
501 {
502 PVBOXSERVICECTRLFILE pFile = gstcntlSessionGetFile(pSession, uHandle);
503 if (pFile)
504 {
505 uOffsetActual = RTFileTell(pFile->hFile);
506 }
507 else
508 rc = VERR_NOT_FOUND;
509
510 /* Report back in any case. */
511 int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, uOffsetActual);
512 if (RT_FAILURE(rc2))
513 VBoxServiceError("Failed to report file tell status, rc=%Rrc\n", rc2);
514 if (RT_SUCCESS(rc))
515 rc = rc2;
516 }
517 return rc;
518}
519
520
521/**
522 * Handles starting a guest processes.
523 *
524 * @returns IPRT status code.
525 * @param pSession Guest session.
526 * @param pHostCtx Host context.
527 */
528int GstCntlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession,
529 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
530{
531 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
532 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
533
534 int rc;
535 bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */
536
537 if ( (pHostCtx->uProtocol < 2 && pHostCtx->uNumParms == 11)
538 || (pHostCtx->uProtocol >= 2 && pHostCtx->uNumParms == 12)
539 )
540 {
541 VBOXSERVICECTRLPROCSTARTUPINFO startupInfo;
542 RT_ZERO(startupInfo);
543
544 /* Initialize maximum environment block size -- needed as input
545 * parameter to retrieve the stuff from the host. On output this then
546 * will contain the actual block size. */
547 startupInfo.cbEnv = sizeof(startupInfo.szEnv);
548
549 rc = VbglR3GuestCtrlProcGetStart(pHostCtx,
550 /* Command */
551 startupInfo.szCmd, sizeof(startupInfo.szCmd),
552 /* Flags */
553 &startupInfo.uFlags,
554 /* Arguments */
555 startupInfo.szArgs, sizeof(startupInfo.szArgs), &startupInfo.uNumArgs,
556 /* Environment */
557 startupInfo.szEnv, &startupInfo.cbEnv, &startupInfo.uNumEnvVars,
558 /* Credentials; for hosts with VBox < 4.3 (protocol version 1).
559 * For protocl v2 and up the credentials are part of the session
560 * opening call. */
561 startupInfo.szUser, sizeof(startupInfo.szUser),
562 startupInfo.szPassword, sizeof(startupInfo.szPassword),
563 /* Timeout (in ms) */
564 &startupInfo.uTimeLimitMS,
565 /* Process priority */
566 &startupInfo.uPriority,
567 /* Process affinity */
568 startupInfo.uAffinity, sizeof(startupInfo.uAffinity), &startupInfo.uNumAffinity);
569 if (RT_SUCCESS(rc))
570 {
571 VBoxServiceVerbose(3, "Request to start process szCmd=%s, uFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n",
572 startupInfo.szCmd, startupInfo.uFlags,
573 startupInfo.uNumArgs ? startupInfo.szArgs : "<None>",
574 startupInfo.uNumEnvVars ? startupInfo.szEnv : "<None>",
575 startupInfo.uTimeLimitMS);
576
577 /*rc = GstCntlSessionReapProcesses(pSession);
578 if (RT_FAILURE(rc))
579 VBoxServiceError("Reaping stopped guest processes failed with rc=%Rrc\n", rc);*/
580 /* Keep going. */
581
582 rc = GstCntlSessionProcessStartAllowed(pSession, &fStartAllowed);
583 if (RT_SUCCESS(rc))
584 {
585 if (fStartAllowed)
586 {
587 rc = GstCntlProcessStart(pSession, &startupInfo, pHostCtx->uContextID);
588 }
589 else
590 rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */
591 }
592 }
593 }
594 else
595 rc = VERR_NOT_SUPPORTED; /* Unsupported number of parameters. */
596
597 /* In case of an error we need to notify the host to not wait forever for our response. */
598 if (RT_FAILURE(rc))
599 {
600 VBoxServiceError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n",
601 rc, pHostCtx->uProtocol, pHostCtx->uNumParms);
602
603 /* Don't report back if we didn't supply sufficient buffer for getting
604 * the actual command -- we don't have the matching context ID. */
605 if (rc != VERR_TOO_MUCH_DATA)
606 {
607 /*
608 * Note: The context ID can be 0 because we mabye weren't able to fetch the command
609 * from the host. The host in case has to deal with that!
610 */
611 int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /* PID, invalid */,
612 PROC_STS_ERROR, rc,
613 NULL /* pvData */, 0 /* cbData */);
614 if (RT_FAILURE(rc2))
615 VBoxServiceError("Error sending start process status to host, rc=%Rrc\n", rc2);
616 }
617 }
618
619 return rc;
620}
621
622
623/**
624 * Handles input for a started process by copying the received data into its
625 * stdin pipe.
626 *
627 * @returns IPRT status code.
628 * @param pvScratchBuf The scratch buffer.
629 * @param cbScratchBuf The scratch buffer size for retrieving the input data.
630 */
631int GstCntlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession,
632 PVBGLR3GUESTCTRLCMDCTX pHostCtx,
633 void *pvScratchBuf, size_t cbScratchBuf)
634{
635 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
636 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
637 AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER);
638 AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER);
639
640 uint32_t uPID;
641 uint32_t uFlags;
642 uint32_t cbSize;
643
644 uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */
645 uint32_t cbWritten = 0; /* Number of bytes written to the guest. */
646
647 /*
648 * Ask the host for the input data.
649 */
650 int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &uFlags,
651 pvScratchBuf, cbScratchBuf, &cbSize);
652 if (RT_FAILURE(rc))
653 {
654 VBoxServiceError("[PID %RU32]: Failed to retrieve exec input command! Error: %Rrc\n",
655 uPID, rc);
656 }
657 else if (cbSize > cbScratchBuf)
658 {
659 VBoxServiceError("[PID %RU32]: Too much input received! cbSize=%u, cbScratchBuf=%u\n",
660 uPID, cbSize, cbScratchBuf);
661 rc = VERR_INVALID_PARAMETER;
662 }
663 else
664 {
665 /*
666 * Is this the last input block we need to deliver? Then let the pipe know ...
667 */
668 bool fPendingClose = false;
669 if (uFlags & INPUT_FLAG_EOF)
670 {
671 fPendingClose = true;
672 VBoxServiceVerbose(4, "[PID %RU32]: Got last input block of size %u ...\n",
673 uPID, cbSize);
674 }
675
676 rc = gstcntlSessionSetInput(pSession, uPID,
677 pHostCtx->uContextID, fPendingClose, pvScratchBuf,
678 cbSize, &cbWritten);
679 VBoxServiceVerbose(4, "[PID %RU32]: Written input, CID=%u, rc=%Rrc, uFlags=0x%x, fPendingClose=%d, cbSize=%u, cbWritten=%u\n",
680 uPID, pHostCtx->uContextID, rc, uFlags, fPendingClose, cbSize, cbWritten);
681 if (RT_SUCCESS(rc))
682 {
683 uStatus = INPUT_STS_WRITTEN;
684 uFlags = 0; /* No flags at the moment. */
685 }
686 else
687 {
688 if (rc == VERR_BAD_PIPE)
689 uStatus = INPUT_STS_TERMINATED;
690 else if (rc == VERR_BUFFER_OVERFLOW)
691 uStatus = INPUT_STS_OVERFLOW;
692 }
693 }
694
695 /*
696 * If there was an error and we did not set the host status
697 * yet, then do it now.
698 */
699 if ( RT_FAILURE(rc)
700 && uStatus == INPUT_STS_UNDEFINED)
701 {
702 uStatus = INPUT_STS_ERROR;
703 uFlags = rc;
704 }
705 Assert(uStatus > INPUT_STS_UNDEFINED);
706
707 VBoxServiceVerbose(3, "[PID %RU32]: Input processed, CID=%u, uStatus=%u, uFlags=0x%x, cbWritten=%u\n",
708 uPID, pHostCtx->uContextID, uStatus, uFlags, cbWritten);
709
710 /* Note: Since the context ID is unique the request *has* to be completed here,
711 * regardless whether we got data or not! Otherwise the progress object
712 * on the host never will get completed! */
713 rc = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, uPID,
714 uStatus, uFlags, (uint32_t)cbWritten);
715
716 if (RT_FAILURE(rc))
717 VBoxServiceError("[PID %RU32]: Failed to report input status! Error: %Rrc\n",
718 uPID, rc);
719 return rc;
720}
721
722
723/**
724 * Handles the guest control output command.
725 *
726 * @return IPRT status code.
727 */
728int GstCntlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession,
729 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
730{
731 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
732 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
733
734 uint32_t uPID;
735 uint32_t uHandleID;
736 uint32_t uFlags;
737
738 int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &uFlags);
739#ifdef DEBUG_andy
740 VBoxServiceVerbose(4, "[PID %RU32]: Get output CID=%RU32, uHandleID=%RU32, uFlags=%RU32\n",
741 uPID, pHostCtx->uContextID, uHandleID, uFlags);
742#endif
743 if (RT_SUCCESS(rc))
744 {
745 uint8_t *pBuf = (uint8_t*)RTMemAlloc(_64K);
746 if (pBuf)
747 {
748 uint32_t cbRead = 0;
749 rc = gstcntlSessionGetOutput(pSession, uPID,
750 pHostCtx->uContextID, uHandleID, RT_INDEFINITE_WAIT /* Timeout */,
751 pBuf, _64K /* cbSize */, &cbRead);
752 VBoxServiceVerbose(3, "[PID %RU32]: Got output, rc=%Rrc, CID=%RU32, cbRead=%RU32, uHandle=%RU32, uFlags=%x\n",
753 uPID, rc, pHostCtx->uContextID, cbRead, uHandleID, uFlags);
754
755#ifdef DEBUG
756 if ( (pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT)
757 && (uHandleID == OUTPUT_HANDLE_ID_STDERR))
758 {
759 char szDumpFile[RTPATH_MAX];
760 if (!RTStrPrintf(szDumpFile, sizeof(szDumpFile), "VBoxService_Session%RU32_PID%RU32_StdOut.txt",
761 pSession->StartupInfo.uSessionID, uPID))
762 rc = VERR_BUFFER_UNDERFLOW;
763 if (RT_SUCCESS(rc))
764 rc = gstcntlSessionDumpToFile(szDumpFile, pBuf, cbRead);
765 }
766 else if ( (pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR)
767 && ( uHandleID == OUTPUT_HANDLE_ID_STDOUT
768 || uHandleID == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED))
769 {
770 char szDumpFile[RTPATH_MAX];
771 if (!RTStrPrintf(szDumpFile, sizeof(szDumpFile), "VBoxService_Session%RU32_PID%RU32_StdErr.txt",
772 pSession->StartupInfo.uSessionID, uPID))
773 rc = VERR_BUFFER_UNDERFLOW;
774 if (RT_SUCCESS(rc))
775 rc = gstcntlSessionDumpToFile(szDumpFile, pBuf, cbRead);
776 AssertRC(rc);
777 }
778#endif
779 /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary
780 * data which the host needs to work with -- so just pass through all data unfiltered! */
781
782 /* Note: Since the context ID is unique the request *has* to be completed here,
783 * regardless whether we got data or not! Otherwise the progress object
784 * on the host never will get completed! */
785 int rc2 = VbglR3GuestCtrlProcCbOutput(pHostCtx, uPID, uHandleID, uFlags,
786 pBuf, cbRead);
787 if (RT_SUCCESS(rc))
788 rc = rc2;
789 else if (rc == VERR_NOT_FOUND) /* It's not critical if guest process (PID) is not found. */
790 rc = VINF_SUCCESS;
791
792 RTMemFree(pBuf);
793 }
794 else
795 rc = VERR_NO_MEMORY;
796 }
797
798 if (RT_FAILURE(rc))
799 VBoxServiceError("[PID %RU32]: Error handling output command! Error: %Rrc\n",
800 uPID, rc);
801 return rc;
802}
803
804
805int GstCntlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession,
806 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
807{
808 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
809 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
810
811 uint32_t uPID;
812 int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID);
813 if (RT_SUCCESS(rc))
814 {
815 PVBOXSERVICECTRLREQUEST pRequest;
816 rc = GstCntlProcessRequestAllocEx(&pRequest, VBOXSERVICECTRLREQUEST_PROC_TERM,
817 NULL /* pvBuf */, 0 /* cbBuf */, pHostCtx->uContextID);
818 if (RT_SUCCESS(rc))
819 {
820 PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionAcquireProcess(pSession, uPID);
821 if (pProcess)
822 {
823 rc = GstCntlProcessPerform(pProcess, pRequest, false /* Async */);
824 GstCntlProcessRelease(pProcess);
825 }
826 else
827 rc = VERR_NOT_FOUND;
828
829 GstCntlProcessRequestFree(pRequest);
830 }
831 }
832
833 return rc;
834}
835
836
837int GstCntlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession,
838 PVBGLR3GUESTCTRLCMDCTX pHostCtx)
839{
840 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
841 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
842
843 uint32_t uPID;
844 uint32_t uWaitFlags; uint32_t uTimeoutMS;
845
846 int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS);
847 if (RT_SUCCESS(rc))
848 {
849 PVBOXSERVICECTRLREQUEST pRequest;
850 VBOXSERVICECTRLREQDATA_WAIT_FOR reqData = { uWaitFlags, uTimeoutMS };
851 rc = GstCntlProcessRequestAllocEx(&pRequest, VBOXSERVICECTRLREQUEST_WAIT_FOR,
852 &reqData, sizeof(reqData), pHostCtx->uContextID);
853 if (RT_SUCCESS(rc))
854 {
855 PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionAcquireProcess(pSession, uPID);
856 if (pProcess)
857 {
858 rc = GstCntlProcessPerform(pProcess, pRequest, false /* Async */);
859 GstCntlProcessRelease(pProcess);
860 }
861 else
862 rc = VERR_NOT_FOUND;
863
864 GstCntlProcessRequestFree(pRequest);
865 }
866 }
867
868 return rc;
869}
870
871
872int GstCntlSessionHandler(PVBOXSERVICECTRLSESSION pSession,
873 uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
874 void *pvScratchBuf, size_t cbScratchBuf,
875 volatile bool *pfShutdown)
876{
877 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
878 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
879 AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER);
880 AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER);
881
882 int rc = VINF_SUCCESS;
883
884 /** @todo Implement asynchronous handling of all commands to speed up overall
885 * performance for handling multiple guest processes at once. At the moment
886 * only one guest process at a time can and will be served. */
887
888 /**
889 * Only anonymous sessions (that is, sessions which run with local
890 * service privileges) or forked session processes can do certain
891 * operations.
892 */
893 bool fImpersonated = ( pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_FORK
894 || pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS);
895
896 switch (uMsg)
897 {
898 case HOST_CANCEL_PENDING_WAITS:
899 VBoxServiceVerbose(1, "We were asked to quit ...\n");
900 /* Fall thru is intentional. */
901 case HOST_SESSION_CLOSE:
902 /* Shutdown this fork. */
903 rc = GstCntlSessionClose(pSession);
904 *pfShutdown = true; /* Shutdown in any case. */
905 break;
906
907 case HOST_EXEC_CMD:
908 rc = GstCntlSessionHandleProcExec(pSession, pHostCtx);
909 break;
910
911 case HOST_EXEC_SET_INPUT:
912 rc = GstCntlSessionHandleProcInput(pSession, pHostCtx,
913 pvScratchBuf, cbScratchBuf);
914 break;
915
916 case HOST_EXEC_GET_OUTPUT:
917 rc = GstCntlSessionHandleProcOutput(pSession, pHostCtx);
918 break;
919
920 case HOST_EXEC_TERMINATE:
921 rc = GstCntlSessionHandleProcTerminate(pSession, pHostCtx);
922 break;
923
924 case HOST_EXEC_WAIT_FOR:
925 rc = GstCntlSessionHandleProcWaitFor(pSession, pHostCtx);
926 break;
927
928 case HOST_FILE_OPEN:
929 rc = fImpersonated
930 ? gstcntlSessionHandleFileOpen(pSession, pHostCtx)
931 : VERR_NOT_SUPPORTED;
932 break;
933
934 case HOST_FILE_CLOSE:
935 rc = fImpersonated
936 ? gstcntlSessionHandleFileClose(pSession, pHostCtx)
937 : VERR_NOT_SUPPORTED;
938 break;
939
940 case HOST_FILE_READ:
941 rc = fImpersonated
942 ? gstcntlSessionHandleFileRead(pSession, pHostCtx,
943 pvScratchBuf, cbScratchBuf)
944 : VERR_NOT_SUPPORTED;
945 break;
946
947 case HOST_FILE_READ_AT:
948 rc = fImpersonated
949 ? gstcntlSessionHandleFileReadAt(pSession, pHostCtx,
950 pvScratchBuf, cbScratchBuf)
951 : VERR_NOT_SUPPORTED;
952 break;
953
954 case HOST_FILE_WRITE:
955 rc = fImpersonated
956 ? gstcntlSessionHandleFileWrite(pSession, pHostCtx,
957 pvScratchBuf, cbScratchBuf)
958 : VERR_NOT_SUPPORTED;
959 break;
960
961 case HOST_FILE_WRITE_AT:
962 rc = fImpersonated
963 ? gstcntlSessionHandleFileWriteAt(pSession, pHostCtx,
964 pvScratchBuf, cbScratchBuf)
965 : VERR_NOT_SUPPORTED;
966 break;
967
968 case HOST_FILE_SEEK:
969 rc = fImpersonated
970 ? gstcntlSessionHandleFileSeek(pSession, pHostCtx)
971 : VERR_NOT_SUPPORTED;
972 break;
973
974 case HOST_FILE_TELL:
975 rc = fImpersonated
976 ? gstcntlSessionHandleFileTell(pSession, pHostCtx)
977 : VERR_NOT_SUPPORTED;
978 break;
979
980 default:
981 rc = VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID);
982 VBoxServiceVerbose(3, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n",
983 uMsg, pHostCtx->uNumParms);
984 break;
985 }
986
987 return rc;
988}
989
990
991/**
992 * Thread main routine for a forked guest session. This
993 * thread runs in the main executable to control the forked
994 * session process.
995 *
996 * @return IPRT status code.
997 * @param RTTHREAD Pointer to the thread's data.
998 * @param void* User-supplied argument pointer.
999 *
1000 */
1001static DECLCALLBACK(int) gstcntlSessionThread(RTTHREAD ThreadSelf, void *pvUser)
1002{
1003 PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser;
1004 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
1005
1006 uint32_t uSessionID = pThread->StartupInfo.uSessionID;
1007
1008 uint32_t uClientID;
1009 int rc = VbglR3GuestCtrlConnect(&uClientID);
1010 if (RT_SUCCESS(rc))
1011 {
1012 VBoxServiceVerbose(3, "Session ID=%RU32 thread running, client ID=%RU32\n",
1013 uSessionID, uClientID);
1014 }
1015 else
1016 {
1017 VBoxServiceError("Error connecting to guest control service, rc=%Rrc\n", rc);
1018 return rc;
1019 }
1020
1021 /* Let caller know that we're done initializing. */
1022 rc = RTThreadUserSignal(RTThreadSelf());
1023 if (RT_FAILURE(rc))
1024 return rc;
1025
1026 bool fProcessAlive = true;
1027 RTPROCSTATUS ProcessStatus;
1028 RT_ZERO(ProcessStatus);
1029
1030 int rcWait;
1031 if (RT_SUCCESS(rc))
1032 {
1033 uint32_t uTimeoutsMS = 5 * 60 * 1000; /** @todo Make this configurable. Later. */
1034 uint64_t u64TimeoutStart = 0;
1035
1036 for (;;)
1037 {
1038 rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK,
1039 &ProcessStatus);
1040 if (RT_UNLIKELY(rcWait == VERR_INTERRUPTED))
1041 continue;
1042 else if ( rcWait == VINF_SUCCESS
1043 || rcWait == VERR_PROCESS_NOT_FOUND)
1044 {
1045 fProcessAlive = false;
1046 break;
1047 }
1048 else
1049 AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING,
1050 ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait));
1051
1052 if (ASMAtomicReadBool(&pThread->fShutdown))
1053 {
1054 if (!u64TimeoutStart)
1055 {
1056 VBoxServiceVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit ...\n",
1057 uSessionID);
1058 u64TimeoutStart = RTTimeMilliTS();
1059 continue; /* Don't waste time on waiting. */
1060 }
1061 if (RTTimeMilliTS() - u64TimeoutStart > uTimeoutsMS)
1062 {
1063 VBoxServiceVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n",
1064 uSessionID);
1065 break;
1066 }
1067 }
1068
1069 RTThreadSleep(100); /* Wait a bit. */
1070 }
1071
1072 if (!fProcessAlive)
1073 {
1074 VBoxServiceVerbose(2, "Guest session ID=%RU32 process terminated with rc=%Rrc, reason=%ld, status=%d\n",
1075 uSessionID, rcWait,
1076 ProcessStatus.enmReason, ProcessStatus.iStatus);
1077 }
1078 }
1079
1080 uint32_t uSessionStatus = GUEST_SESSION_NOTIFYTYPE_UNDEFINED;
1081 uint32_t uSessionRc = VINF_SUCCESS; /** uint32_t vs. int. */
1082
1083 if (fProcessAlive)
1084 {
1085 for (int i = 0; i < 3; i++)
1086 {
1087 VBoxServiceVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n",
1088 uSessionID, i + 1);
1089
1090 rc = RTProcTerminate(pThread->hProcess);
1091 if (RT_SUCCESS(rc))
1092 break;
1093 RTThreadSleep(3000);
1094 }
1095
1096 VBoxServiceVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n",
1097 uSessionID, rc);
1098
1099 uSessionStatus = RT_SUCCESS(rc)
1100 ? GUEST_SESSION_NOTIFYTYPE_TOK : GUEST_SESSION_NOTIFYTYPE_TOA;
1101 }
1102 else
1103 {
1104 if (RT_SUCCESS(rcWait))
1105 {
1106 switch (ProcessStatus.enmReason)
1107 {
1108 case RTPROCEXITREASON_NORMAL:
1109 uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN;
1110 break;
1111
1112 case RTPROCEXITREASON_ABEND:
1113 uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA;
1114 break;
1115
1116 case RTPROCEXITREASON_SIGNAL:
1117 uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES;
1118 break;
1119
1120 default:
1121 AssertMsgFailed(("Unhandled process termination reason (%ld)\n",
1122 ProcessStatus.enmReason));
1123 uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA;
1124 break;
1125 }
1126 }
1127 else
1128 {
1129 /* If we didn't find the guest process anymore, just assume it
1130 * terminated normally. */
1131 uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN;
1132 }
1133 }
1134
1135 VBoxServiceVerbose(3, "Guest session ID=%RU32 thread ended with sessionStatus=%RU32, sessionRc=%Rrc\n",
1136 uSessionID, uSessionStatus, uSessionRc);
1137
1138 /* Report final status. */
1139 Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED);
1140 VBGLR3GUESTCTRLCMDCTX ctx = { uClientID, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(uSessionID) };
1141 int rc2 = VbglR3GuestCtrlSessionNotify(&ctx,
1142 uSessionStatus, uSessionRc);
1143 if (RT_FAILURE(rc2))
1144 VBoxServiceError("Reporting session ID=%RU32 final status failed with rc=%Rrc\n",
1145 uSessionID, rc2);
1146
1147 VbglR3GuestCtrlDisconnect(uClientID);
1148
1149 VBoxServiceVerbose(3, "Session ID=%RU32 thread ended with rc=%Rrc\n",
1150 uSessionID, rc);
1151 return rc;
1152}
1153
1154
1155RTEXITCODE gstcntlSessionForkWorker(PVBOXSERVICECTRLSESSION pSession)
1156{
1157 AssertPtrReturn(pSession, RTEXITCODE_FAILURE);
1158
1159 bool fSessionFilter = true;
1160
1161 VBoxServiceVerbose(0, "Hi, this is guest session ID=%RU32\n",
1162 pSession->StartupInfo.uSessionID);
1163
1164 uint32_t uClientID;
1165 int rc = VbglR3GuestCtrlConnect(&uClientID);
1166 if (RT_SUCCESS(rc))
1167 {
1168 /* Set session filter. */
1169 uint32_t uFilterAdd =
1170 VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID);
1171
1172 rc = VbglR3GuestCtrlMsgSetFilter(uClientID, uFilterAdd, 0 /* Filter remove */);
1173 VBoxServiceVerbose(3, "Setting message filterAdd=%RU32 returned %Rrc\n",
1174 uFilterAdd, rc);
1175
1176 if ( RT_FAILURE(rc)
1177 && rc == VERR_NOT_SUPPORTED)
1178 {
1179 /* No session filter available. Skip. */
1180 fSessionFilter = false;
1181
1182 rc = VINF_SUCCESS;
1183 }
1184
1185 VBoxServiceVerbose(1, "Using client ID=%RU32\n", uClientID);
1186 }
1187 else
1188 VBoxServiceError("Error connecting to guest control service, rc=%Rrc\n", rc);
1189
1190 /* Report started status. */
1191 VBGLR3GUESTCTRLCMDCTX ctx = { uClientID, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID) };
1192 int rc2 = VbglR3GuestCtrlSessionNotify(&ctx,
1193 GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS);
1194 if (RT_FAILURE(rc2))
1195 VBoxServiceError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n",
1196 pSession->StartupInfo.uSessionID, rc2);
1197
1198 /* Allocate a scratch buffer for commands which also send
1199 * payload data with them. */
1200 uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */
1201 AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), RTEXITCODE_FAILURE);
1202 uint8_t *pvScratchBuf = NULL;
1203
1204 if (RT_SUCCESS(rc))
1205 {
1206 pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf);
1207 if (!pvScratchBuf)
1208 rc = VERR_NO_MEMORY;
1209 }
1210
1211 if (RT_SUCCESS(rc))
1212 {
1213 bool fShutdown = false;
1214
1215 VBGLR3GUESTCTRLCMDCTX ctxHost = { uClientID, 0 /* Context ID, zeroed */,
1216 pSession->StartupInfo.uProtocol };
1217 for (;;)
1218 {
1219 VBoxServiceVerbose(3, "Waiting for host msg ...\n");
1220 uint32_t uMsg = 0;
1221 uint32_t cParms = 0;
1222 rc = VbglR3GuestCtrlMsgWaitFor(uClientID, &uMsg, &cParms);
1223 if (rc == VERR_TOO_MUCH_DATA)
1224 {
1225 VBoxServiceVerbose(4, "Message requires %RU32 parameters, but only 2 supplied -- retrying request (no error!)...\n", cParms);
1226 rc = VINF_SUCCESS; /* Try to get "real" message in next block below. */
1227 }
1228 else if (RT_FAILURE(rc))
1229 VBoxServiceVerbose(3, "Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
1230 if (RT_SUCCESS(rc))
1231 {
1232 VBoxServiceVerbose(3, "Msg=%RU32 (%RU32 parms) retrieved\n", uMsg, cParms);
1233
1234 /* Set number of parameters for current host context. */
1235 ctxHost.uNumParms = cParms;
1236
1237 /* ... and pass it on to the session handler. */
1238 rc = GstCntlSessionHandler(pSession, uMsg, &ctxHost,
1239 pvScratchBuf, cbScratchBuf, &fShutdown);
1240 }
1241
1242 if (fShutdown)
1243 break;
1244
1245 /* Let's sleep for a bit and let others run ... */
1246 RTThreadYield();
1247 }
1248 }
1249
1250 VBoxServiceVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID);
1251
1252 if (pvScratchBuf)
1253 RTMemFree(pvScratchBuf);
1254
1255 VBoxServiceVerbose(3, "Disconnecting client ID=%RU32 ...\n", uClientID);
1256 VbglR3GuestCtrlDisconnect(uClientID);
1257
1258 VBoxServiceVerbose(3, "Session worker returned with rc=%Rrc\n", rc);
1259 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1260}
1261
1262
1263/**
1264 * Finds a (formerly) started guest process given by its PID and increses
1265 * its reference count. Must be decreased by the caller with GstCntlProcessRelease().
1266 *
1267 * @return PVBOXSERVICECTRLTHREAD Locked guest process if found, otherwise NULL.
1268 * @param PVBOXSERVICECTRLSESSION Pointer to guest session where to search process in.
1269 * @param uPID PID to search for.
1270 */
1271PVBOXSERVICECTRLPROCESS GstCntlSessionAcquireProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID)
1272{
1273 AssertPtrReturn(pSession, NULL);
1274
1275 PVBOXSERVICECTRLPROCESS pProcess = NULL;
1276 int rc = RTCritSectEnter(&pSession->CritSect);
1277 if (RT_SUCCESS(rc))
1278 {
1279 PVBOXSERVICECTRLPROCESS pCurProcess;
1280 RTListForEach(&pSession->lstProcessesActive, pCurProcess, VBOXSERVICECTRLPROCESS, Node)
1281 {
1282 if (pCurProcess->uPID == uPID)
1283 {
1284 rc = RTCritSectEnter(&pCurProcess->CritSect);
1285 if (RT_SUCCESS(rc))
1286 {
1287 pCurProcess->cRefs++;
1288 rc = RTCritSectLeave(&pCurProcess->CritSect);
1289 AssertRC(rc);
1290 }
1291
1292 if (RT_SUCCESS(rc))
1293 pProcess = pCurProcess;
1294 break;
1295 }
1296 }
1297
1298 rc = RTCritSectLeave(&pSession->CritSect);
1299 AssertRC(rc);
1300 }
1301
1302 return pProcess;
1303}
1304
1305
1306int GstCntlSessionClose(PVBOXSERVICECTRLSESSION pSession)
1307{
1308 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1309
1310 VBoxServiceVerbose(0, "Session %RU32 is about to close ...\n",
1311 pSession->StartupInfo.uSessionID);
1312
1313 int rc = RTCritSectEnter(&pSession->CritSect);
1314 if (RT_SUCCESS(rc))
1315 {
1316 /*
1317 * Close all guest processes.
1318 */
1319 VBoxServiceVerbose(0, "Stopping all guest processes ...\n");
1320
1321 /* Signal all guest processes in the active list that we want to shutdown. */
1322 PVBOXSERVICECTRLPROCESS pProcess;
1323 RTListForEach(&pSession->lstProcessesActive, pProcess, VBOXSERVICECTRLPROCESS, Node)
1324 GstCntlProcessStop(pProcess);
1325
1326 VBoxServiceVerbose(1, "All guest processes signalled to stop\n");
1327
1328 /* Wait for all active threads to shutdown and destroy the active thread list. */
1329 pProcess = RTListGetFirst(&pSession->lstProcessesActive, VBOXSERVICECTRLPROCESS, Node);
1330 while (pProcess)
1331 {
1332 PVBOXSERVICECTRLPROCESS pNext = RTListNodeGetNext(&pProcess->Node, VBOXSERVICECTRLPROCESS, Node);
1333 bool fLast = RTListNodeIsLast(&pSession->lstProcessesActive, &pProcess->Node);
1334
1335 int rc2 = GstCntlProcessWait(pProcess,
1336 30 * 1000 /* Wait 30 seconds max. */,
1337 NULL /* rc */);
1338 if (RT_FAILURE(rc2))
1339 {
1340 VBoxServiceError("Guest process thread failed to stop; rc=%Rrc\n", rc2);
1341 if (RT_SUCCESS(rc))
1342 rc = rc2;
1343 /* Keep going. */
1344 }
1345
1346 RTListNodeRemove(&pProcess->Node);
1347
1348 rc2 = GstCntlProcessFree(pProcess);
1349 if (RT_FAILURE(rc2))
1350 {
1351 VBoxServiceError("Guest process thread failed to free; rc=%Rrc\n", rc2);
1352 if (RT_SUCCESS(rc))
1353 rc = rc2;
1354 /* Keep going. */
1355 }
1356
1357 if (fLast)
1358 break;
1359
1360 pProcess = pNext;
1361 }
1362
1363 /*rc = GstCntlSessionReapProcesses(pSession);
1364 if (RT_FAILURE(rc))
1365 VBoxServiceError("Reaping inactive threads failed with rc=%Rrc\n", rc);*/
1366
1367 AssertMsg(RTListIsEmpty(&pSession->lstProcessesActive),
1368 ("Guest process active thread list still contains entries when it should not\n"));
1369 /*AssertMsg(RTListIsEmpty(&pSession->lstProcessesInactive),
1370 ("Guest process inactive thread list still contains entries when it should not\n"));*/
1371
1372 /*
1373 * Close all left guest files.
1374 */
1375 VBoxServiceVerbose(0, "Closing all guest files ...\n");
1376
1377 PVBOXSERVICECTRLFILE pFile;
1378 pFile = RTListGetFirst(&pSession->lstFiles, VBOXSERVICECTRLFILE, Node);
1379 while (pFile)
1380 {
1381 PVBOXSERVICECTRLFILE pNext = RTListNodeGetNext(&pFile->Node, VBOXSERVICECTRLFILE, Node);
1382 bool fLast = RTListNodeIsLast(&pSession->lstFiles, &pFile->Node);
1383
1384 int rc2 = gstcntlSessionFileDestroy(pFile);
1385 if (RT_FAILURE(rc2))
1386 {
1387 VBoxServiceError("Unable to close file \"%s\"; rc=%Rrc\n",
1388 pFile->szName, rc2);
1389 if (RT_SUCCESS(rc))
1390 rc = rc2;
1391 /* Keep going. */
1392 }
1393
1394 if (fLast)
1395 break;
1396
1397 pFile = pNext;
1398 }
1399
1400 AssertMsg(RTListIsEmpty(&pSession->lstFiles),
1401 ("Guest file list still contains entries when it should not\n"));
1402
1403 int rc2 = RTCritSectLeave(&pSession->CritSect);
1404 if (RT_SUCCESS(rc))
1405 rc = rc2;
1406 }
1407
1408 return rc;
1409}
1410
1411
1412int GstCntlSessionDestroy(PVBOXSERVICECTRLSESSION pSession)
1413{
1414 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1415
1416 int rc = GstCntlSessionClose(pSession);
1417
1418 /* Destroy critical section. */
1419 RTCritSectDelete(&pSession->CritSect);
1420
1421 return rc;
1422}
1423
1424
1425/**
1426 * Gets output from stdout/stderr of a specified guest process.
1427 *
1428 * @return IPRT status code.
1429 * @param pSession Guest session.
1430 * @param uPID PID of process to retrieve the output from.
1431 * @param uCID Context ID.
1432 * @param uHandleId Stream ID (stdout = 0, stderr = 2) to get the output from.
1433 * @param cMsTimeout Timeout (in ms) to wait for output becoming
1434 * available.
1435 * @param pvBuf Pointer to a pre-allocated buffer to store the output.
1436 * @param cbBuf Size (in bytes) of the pre-allocated buffer.
1437 * @param pcbRead Pointer to number of bytes read. Optional.
1438 */
1439static int gstcntlSessionGetOutput(const PVBOXSERVICECTRLSESSION pSession,
1440 uint32_t uPID, uint32_t uCID,
1441 uint32_t uHandleId, uint32_t cMsTimeout,
1442 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1443{
1444 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1445 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1446 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1447 /* pcbRead is optional. */
1448
1449 int rc = VINF_SUCCESS;
1450 VBOXSERVICECTRLREQUESTTYPE reqType = VBOXSERVICECTRLREQUEST_UNKNOWN; /* (gcc maybe, well, wrong.) */
1451 switch (uHandleId)
1452 {
1453 case OUTPUT_HANDLE_ID_STDERR:
1454 reqType = VBOXSERVICECTRLREQUEST_PROC_STDERR;
1455 break;
1456
1457 case OUTPUT_HANDLE_ID_STDOUT:
1458 case OUTPUT_HANDLE_ID_STDOUT_DEPRECATED:
1459 reqType = VBOXSERVICECTRLREQUEST_PROC_STDOUT;
1460 break;
1461
1462 default:
1463 rc = VERR_INVALID_PARAMETER;
1464 break;
1465 }
1466
1467 if (RT_SUCCESS(rc))
1468 {
1469 PVBOXSERVICECTRLREQUEST pRequest;
1470 rc = GstCntlProcessRequestAllocEx(&pRequest, reqType, pvBuf, cbBuf, uCID);
1471 if (RT_SUCCESS(rc))
1472 {
1473 PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionAcquireProcess(pSession, uPID);
1474 if (pProcess)
1475 {
1476 rc = GstCntlProcessPerform(pProcess, pRequest, false /* Async */);
1477 GstCntlProcessRelease(pProcess);
1478 }
1479 else
1480 rc = VERR_NOT_FOUND;
1481
1482 if (RT_SUCCESS(rc) && pcbRead)
1483 *pcbRead = pRequest->cbData;
1484 GstCntlProcessRequestFree(pRequest);
1485 }
1486 }
1487
1488 return rc;
1489}
1490
1491
1492int GstCntlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags)
1493{
1494 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1495
1496 RTListInit(&pSession->lstProcessesActive);
1497 RTListInit(&pSession->lstProcessesInactive);
1498 RTListInit(&pSession->lstFiles);
1499
1500 pSession->uFlags = uFlags;
1501
1502 if (pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_FORK)
1503 {
1504 /* Protocol must be specified explicitly. */
1505 pSession->StartupInfo.uProtocol = UINT32_MAX;
1506
1507 /* Session ID must be specified explicitly. */
1508 pSession->StartupInfo.uSessionID = UINT32_MAX;
1509 }
1510
1511 /* Init critical section for protecting the thread lists. */
1512 int rc = RTCritSectInit(&pSession->CritSect);
1513 AssertRC(rc);
1514
1515 return rc;
1516}
1517
1518
1519/**
1520 * Sets the specified guest thread to a certain list.
1521 ** @todo Still needed?
1522 *
1523 * @return IPRT status code.
1524 * @param pSession Guest session.
1525 * @param enmList List to move thread to.
1526 * @param pProcess Guest process to set.
1527 */
1528int GstCntlSessionListSet(PVBOXSERVICECTRLSESSION pSession,
1529 PVBOXSERVICECTRLPROCESS pProcess,
1530 VBOXSERVICECTRLTHREADLISTTYPE enmList)
1531{
1532 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1533 AssertReturn(enmList > VBOXSERVICECTRLTHREADLIST_UNKNOWN, VERR_INVALID_PARAMETER);
1534 AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
1535
1536 int rc = RTCritSectEnter(&pSession->CritSect);
1537 if (RT_SUCCESS(rc))
1538 {
1539 VBoxServiceVerbose(3, "Setting thread (PID %RU32) to list %d\n",
1540 pProcess->uPID, enmList);
1541
1542 PRTLISTANCHOR pAnchor = NULL;
1543 switch (enmList)
1544 {
1545 case VBOXSERVICECTRLTHREADLIST_STOPPED:
1546 pAnchor = &pSession->lstProcessesInactive;
1547 break;
1548
1549 case VBOXSERVICECTRLTHREADLIST_RUNNING:
1550 pAnchor = &pSession->lstProcessesActive;
1551 break;
1552
1553 default:
1554 AssertMsgFailed(("Unknown list type: %u\n",
1555 enmList));
1556 break;
1557 }
1558
1559 if (!pAnchor)
1560 rc = VERR_INVALID_PARAMETER;
1561
1562 if (RT_SUCCESS(rc))
1563 {
1564 if (pProcess->pAnchor != NULL)
1565 {
1566 /* If thread was assigned to a list before,
1567 * remove the thread from the old list first. */
1568 /* rc = */ RTListNodeRemove(&pProcess->Node);
1569 }
1570
1571 /* Add thread to desired list. */
1572 /* rc = */ RTListAppend(pAnchor, &pProcess->Node);
1573 pProcess->pAnchor = pAnchor;
1574 }
1575
1576 int rc2 = RTCritSectLeave(&pSession->CritSect);
1577 if (RT_SUCCESS(rc))
1578 rc = rc2;
1579 }
1580
1581 return VINF_SUCCESS;
1582}
1583
1584
1585
1586
1587
1588/**
1589 * Determines whether starting a new guest process according to the
1590 * maximum number of concurrent guest processes defined is allowed or not.
1591 *
1592 * @return IPRT status code.
1593 * @param pbAllowed True if starting (another) guest process
1594 * is allowed, false if not.
1595 */
1596int GstCntlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession,
1597 bool *pbAllowed)
1598{
1599 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1600 AssertPtrReturn(pbAllowed, VERR_INVALID_POINTER);
1601
1602 int rc = RTCritSectEnter(&pSession->CritSect);
1603 if (RT_SUCCESS(rc))
1604 {
1605 /*
1606 * Check if we're respecting our memory policy by checking
1607 * how many guest processes are started and served already.
1608 */
1609 bool fLimitReached = false;
1610 if (pSession->uProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */
1611 {
1612 uint32_t uProcsRunning = 0;
1613 PVBOXSERVICECTRLPROCESS pProcess;
1614 RTListForEach(&pSession->lstProcessesActive, pProcess, VBOXSERVICECTRLPROCESS, Node)
1615 uProcsRunning++;
1616
1617 VBoxServiceVerbose(3, "Maximum served guest processes set to %u, running=%u\n",
1618 pSession->uProcsMaxKept, uProcsRunning);
1619
1620 int32_t iProcsLeft = (pSession->uProcsMaxKept - uProcsRunning - 1);
1621 if (iProcsLeft < 0)
1622 {
1623 VBoxServiceVerbose(3, "Maximum running guest processes reached (%u)\n",
1624 pSession->uProcsMaxKept);
1625 fLimitReached = true;
1626 }
1627 }
1628
1629 *pbAllowed = !fLimitReached;
1630
1631 int rc2 = RTCritSectLeave(&pSession->CritSect);
1632 if (RT_SUCCESS(rc))
1633 rc = rc2;
1634 }
1635
1636 return rc;
1637}
1638
1639#if 0
1640/**
1641 * Reaps all inactive guest process threads.
1642 * Does not do locking; this is the job of the caller.
1643 *
1644 * @return IPRT status code.
1645 */
1646int GstCntlSessionReapProcesses(PVBOXSERVICECTRLSESSION pSession)
1647{
1648 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1649
1650 PVBOXSERVICECTRLPROCESS pThread =
1651 RTListGetFirst(&pSession->lstProcessesInactive, VBOXSERVICECTRLPROCESS, Node);
1652 while (pThread)
1653 {
1654 PVBOXSERVICECTRLPROCESS pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLPROCESS, Node);
1655 bool fLast = RTListNodeIsLast(&pSession->lstProcessesInactive, &pThread->Node);
1656 int rc2 = GstCntlProcessWait(pThread, 30 * 1000 /* 30 seconds max. */,
1657 NULL /* rc */);
1658 if (RT_SUCCESS(rc2))
1659 {
1660 RTListNodeRemove(&pThread->Node);
1661
1662 rc2 = GstCntlProcessFree(pThread);
1663 if (RT_FAILURE(rc2))
1664 {
1665 VBoxServiceError("Freeing guest process thread failed with rc=%Rrc\n", rc2);
1666 if (RT_SUCCESS(rc)) /* Keep original failure. */
1667 rc = rc2;
1668 }
1669 }
1670 else
1671 VBoxServiceError("Waiting on guest process thread failed with rc=%Rrc\n", rc2);
1672 /* Keep going. */
1673
1674 if (fLast)
1675 break;
1676
1677 pThread = pNext;
1678 }
1679
1680 VBoxServiceVerbose(4, "Reaping threads returned with rc=%Rrc\n", rc);
1681 return rc;
1682}
1683#endif
1684
1685
1686/**
1687 * Injects input to a specified running guest process.
1688 *
1689 * @return IPRT status code.
1690 * @param pSession Guest session.
1691 * @param uPID PID of process to set the input for.
1692 * @param uCID Context ID to use for reporting back.
1693 * @param fPendingClose Flag indicating whether this is the last input block sent to the process.
1694 * @param pvBuf Pointer to a buffer containing the actual input data.
1695 * @param cbBuf Size (in bytes) of the input buffer data.
1696 * @param pcbWritten Pointer to number of bytes written to the process. Optional.
1697 */
1698int gstcntlSessionSetInput(const PVBOXSERVICECTRLSESSION pSession,
1699 uint32_t uPID, uint32_t uCID,
1700 bool fPendingClose,
1701 void *pvBuf, uint32_t cbBuf,
1702 uint32_t *pcbWritten)
1703{
1704 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1705 /* pvBuf is optional. */
1706 /* cbBuf is optional. */
1707 /* pcbWritten is optional. */
1708
1709 PVBOXSERVICECTRLREQUEST pRequest;
1710 int rc = GstCntlProcessRequestAllocEx(&pRequest,
1711 fPendingClose
1712 ? VBOXSERVICECTRLREQUEST_PROC_STDIN_EOF
1713 : VBOXSERVICECTRLREQUEST_PROC_STDIN,
1714 pvBuf, cbBuf, uCID);
1715 if (RT_SUCCESS(rc))
1716 {
1717 PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionAcquireProcess(pSession, uPID);
1718 if (pProcess)
1719 {
1720 rc = GstCntlProcessPerform(pProcess, pRequest, false /* Async */);
1721 GstCntlProcessRelease(pProcess);
1722 }
1723 else
1724 rc = VERR_NOT_FOUND;
1725
1726 if (RT_SUCCESS(rc))
1727 {
1728 if (pcbWritten)
1729 *pcbWritten = pRequest->cbData;
1730 }
1731
1732 GstCntlProcessRequestFree(pRequest);
1733 }
1734
1735 return rc;
1736}
1737
1738
1739/**
1740 * Creates a guest session. This will spawn a new VBoxService.exe instance under
1741 * behalf of the given user which then will act as a session host. On successful
1742 * open, the session will be added to the given session thread list.
1743 *
1744 * @return IPRT status code.
1745 * @param pList Which list to use to store the session thread in.
1746 * @param pSessionStartupInfo Session startup info.
1747 * @param ppSessionThread Returns newly created session thread on success.
1748 * Optional.
1749 */
1750int GstCntlSessionThreadCreate(PRTLISTANCHOR pList,
1751 const PVBOXSERVICECTRLSESSIONSTARTUPINFO pSessionStartupInfo,
1752 PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread)
1753{
1754 AssertPtrReturn(pList, VERR_INVALID_POINTER);
1755 AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER);
1756 /* ppSessionThread is optional. */
1757
1758#ifdef DEBUG
1759 PVBOXSERVICECTRLSESSIONTHREAD pSessionCur;
1760 /* Check for existing session in debug mode. Should never happen because of
1761 * Main consistency. */
1762 RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node)
1763 {
1764 if (pSessionCur->StartupInfo.uSessionID == pSessionStartupInfo->uSessionID)
1765 {
1766 AssertMsgFailed(("Guest session thread ID=%RU32 (%p) already exists when it should not\n",
1767 pSessionCur->StartupInfo.uSessionID, pSessionCur));
1768 return VERR_ALREADY_EXISTS;
1769 }
1770 }
1771#endif
1772 int rc = VINF_SUCCESS;
1773
1774 /* Static counter to help tracking session thread <-> process relations. */
1775 static uint32_t s_uCtrlSessionThread = 0;
1776 if (s_uCtrlSessionThread++ == UINT32_MAX)
1777 s_uCtrlSessionThread = 0; /* Wrap around to not let IPRT freak out. */
1778
1779 PVBOXSERVICECTRLSESSIONTHREAD pSessionThread =
1780 (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(VBOXSERVICECTRLSESSIONTHREAD));
1781 if (pSessionThread)
1782 {
1783 /* Copy over session startup info. */
1784 memcpy(&pSessionThread->StartupInfo, pSessionStartupInfo,
1785 sizeof(VBOXSERVICECTRLSESSIONSTARTUPINFO));
1786
1787 pSessionThread->fShutdown = false;
1788 pSessionThread->fStarted = false;
1789 pSessionThread->fStopped = false;
1790
1791 /* Is this an anonymous session? */
1792 /* Anonymous sessions run with the same privileges as the main VBoxService executable. */
1793 bool fAnonymous = !RT_BOOL(strlen(pSessionThread->StartupInfo.szUser));
1794 if (fAnonymous)
1795 {
1796 Assert(!strlen(pSessionThread->StartupInfo.szPassword));
1797 Assert(!strlen(pSessionThread->StartupInfo.szDomain));
1798
1799 VBoxServiceVerbose(3, "New anonymous guest session ID=%RU32 created, uFlags=%x, using protocol %RU32\n",
1800 pSessionStartupInfo->uSessionID,
1801 pSessionStartupInfo->uFlags,
1802 pSessionStartupInfo->uProtocol);
1803 }
1804 else
1805 {
1806 VBoxServiceVerbose(3, "Forking new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, uFlags=%x, using protocol %RU32\n",
1807 pSessionStartupInfo->uSessionID,
1808 pSessionStartupInfo->szUser,
1809#ifdef DEBUG
1810 pSessionStartupInfo->szPassword,
1811#else
1812 "XXX", /* Never show passwords in release mode. */
1813#endif
1814 pSessionStartupInfo->szDomain,
1815 pSessionStartupInfo->uFlags,
1816 pSessionStartupInfo->uProtocol);
1817 }
1818
1819 rc = RTCritSectInit(&pSessionThread->CritSect);
1820 AssertRC(rc);
1821
1822 /* Fork child doing the actual session handling. */
1823 char szExeName[RTPATH_MAX];
1824 char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName));
1825 if (pszExeName)
1826 {
1827 char szParmUserName[GUESTPROCESS_MAX_USER_LEN + 32];
1828 if (!fAnonymous)
1829 {
1830 if (!RTStrPrintf(szParmUserName, sizeof(szParmUserName), "--user=%s", pSessionThread->StartupInfo.szUser))
1831 rc = VERR_BUFFER_OVERFLOW;
1832 }
1833 char szParmSessionID[32];
1834 if (RT_SUCCESS(rc) && !RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32",
1835 pSessionThread->StartupInfo.uSessionID))
1836 {
1837 rc = VERR_BUFFER_OVERFLOW;
1838 }
1839 char szParmSessionProto[32];
1840 if (RT_SUCCESS(rc) && !RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32",
1841 pSessionThread->StartupInfo.uProtocol))
1842 {
1843 rc = VERR_BUFFER_OVERFLOW;
1844 }
1845#ifdef DEBUG
1846 char szParmThreadId[32];
1847 if (RT_SUCCESS(rc) && !RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32",
1848 s_uCtrlSessionThread))
1849 {
1850 rc = VERR_BUFFER_OVERFLOW;
1851 }
1852#endif /* DEBUG */
1853 if (RT_SUCCESS(rc))
1854 {
1855 int iOptIdx = 0; /* Current index in argument vector. */
1856
1857 char const *papszArgs[16];
1858 papszArgs[iOptIdx++] = pszExeName;
1859 papszArgs[iOptIdx++] = "guestsession";
1860 papszArgs[iOptIdx++] = szParmSessionID;
1861 papszArgs[iOptIdx++] = szParmSessionProto;
1862#ifdef DEBUG
1863 papszArgs[iOptIdx++] = szParmThreadId;
1864#endif /* DEBUG */
1865 if (!fAnonymous)
1866 papszArgs[iOptIdx++] = szParmUserName;
1867
1868 /* Add same verbose flags as parent process. */
1869 int rc2 = VINF_SUCCESS;
1870 char szParmVerbose[32] = { 0 };
1871 for (int i = 0; i < g_cVerbosity && RT_SUCCESS(rc2); i++)
1872 {
1873 if (i == 0)
1874 rc2 = RTStrCat(szParmVerbose, sizeof(szParmVerbose), "-");
1875 if (RT_FAILURE(rc2))
1876 break;
1877 rc2 = RTStrCat(szParmVerbose, sizeof(szParmVerbose), "v");
1878 }
1879 if (RT_SUCCESS(rc2))
1880 papszArgs[iOptIdx++] = szParmVerbose;
1881
1882 /* Add log file handling. Each session will have an own
1883 * log file, naming based on the parent log file. */
1884 char szParmLogFile[RTPATH_MAX];
1885 if ( RT_SUCCESS(rc2)
1886 && strlen(g_szLogFile))
1887 {
1888 char *pszLogFile = RTStrDup(g_szLogFile);
1889 if (pszLogFile)
1890 {
1891 char *pszLogExt = NULL;
1892 if (RTPathHasExt(pszLogFile))
1893 pszLogExt = RTStrDup(RTPathExt(pszLogFile));
1894 RTPathStripExt(pszLogFile);
1895 char *pszLogSuffix;
1896#ifndef DEBUG
1897 if (RTStrAPrintf(&pszLogSuffix, "-%RU32-%s",
1898 pSessionStartupInfo->uSessionID,
1899 pSessionStartupInfo->szUser) < 0)
1900 {
1901 rc2 = VERR_NO_MEMORY;
1902 }
1903#else
1904 if (RTStrAPrintf(&pszLogSuffix, "-%RU32-%RU32-%s",
1905 pSessionStartupInfo->uSessionID,
1906 s_uCtrlSessionThread,
1907 pSessionStartupInfo->szUser) < 0)
1908 {
1909 rc2 = VERR_NO_MEMORY;
1910 }
1911#endif /* DEBUG */
1912 else
1913 {
1914 rc2 = RTStrAAppend(&pszLogFile, pszLogSuffix);
1915 if (RT_SUCCESS(rc2) && pszLogExt)
1916 rc2 = RTStrAAppend(&pszLogFile, pszLogExt);
1917 if (RT_SUCCESS(rc2))
1918 {
1919 if (!RTStrPrintf(szParmLogFile, sizeof(szParmLogFile),
1920 "--logfile=%s", pszLogFile))
1921 {
1922 rc2 = VERR_BUFFER_OVERFLOW;
1923 }
1924 }
1925 RTStrFree(pszLogSuffix);
1926 }
1927 if (RT_FAILURE(rc2))
1928 VBoxServiceError("Error building session logfile string for session %RU32 (user %s), rc=%Rrc\n",
1929 pSessionStartupInfo->uSessionID, pSessionStartupInfo->szUser, rc2);
1930 if (pszLogExt)
1931 RTStrFree(pszLogExt);
1932 RTStrFree(pszLogFile);
1933 }
1934 if (RT_SUCCESS(rc2))
1935 papszArgs[iOptIdx++] = szParmLogFile;
1936 papszArgs[iOptIdx++] = NULL;
1937 }
1938 else
1939 papszArgs[iOptIdx++] = NULL;
1940
1941 if (g_cVerbosity > 3)
1942 {
1943 VBoxServiceVerbose(4, "Forking parameters:\n");
1944
1945 iOptIdx = 0;
1946 while (papszArgs[iOptIdx])
1947 VBoxServiceVerbose(4, "\t%s\n", papszArgs[iOptIdx++]);
1948 }
1949
1950 uint32_t uProcFlags = RTPROC_FLAGS_SERVICE
1951 | RTPROC_FLAGS_HIDDEN; /** @todo More flags from startup info? */
1952
1953#if 0 /* Pipe handling not needed (yet). */
1954 /* Setup pipes. */
1955 rc = GstcntlProcessSetupPipe("|", 0 /*STDIN_FILENO*/,
1956 &pSession->StdIn.hChild, &pSession->StdIn.phChild, &pSession->hStdInW);
1957 if (RT_SUCCESS(rc))
1958 {
1959 rc = GstcntlProcessSetupPipe("|", 1 /*STDOUT_FILENO*/,
1960 &pSession->StdOut.hChild, &pSession->StdOut.phChild, &pSession->hStdOutR);
1961 if (RT_SUCCESS(rc))
1962 {
1963 rc = GstcntlProcessSetupPipe("|", 2 /*STDERR_FILENO*/,
1964 &pSession->StdErr.hChild, &pSession->StdErr.phChild, &pSession->hStdErrR);
1965 if (RT_SUCCESS(rc))
1966 {
1967 rc = RTPollSetCreate(&pSession->hPollSet);
1968 if (RT_SUCCESS(rc))
1969 rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdInW, RTPOLL_EVT_ERROR,
1970 VBOXSERVICECTRLPIPEID_STDIN);
1971 if (RT_SUCCESS(rc))
1972 rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
1973 VBOXSERVICECTRLPIPEID_STDOUT);
1974 if (RT_SUCCESS(rc))
1975 rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
1976 VBOXSERVICECTRLPIPEID_STDERR);
1977 }
1978
1979 if (RT_SUCCESS(rc))
1980 {
1981 /* Fork the thing. */
1982 /** @todo Do we need a custom environment block? */
1983 rc = RTProcCreateEx(pszExeName, papszArgs, RTENV_DEFAULT, uProcFlags,
1984 pSession->StdIn.phChild, pSession->StdOut.phChild, pSession->StdErr.phChild,
1985 !fAnonymous ? pSession->StartupInfo.szUser : NULL,
1986 !fAnonymous ? pSession->StartupInfo.szPassword : NULL,
1987 &pSession->hProcess);
1988 }
1989
1990 if (RT_SUCCESS(rc))
1991 {
1992 /*
1993 * Close the child ends of any pipes and redirected files.
1994 */
1995 int rc2 = RTHandleClose(pSession->StdIn.phChild); AssertRC(rc2);
1996 pSession->StdIn.phChild = NULL;
1997 rc2 = RTHandleClose(pSession->StdOut.phChild); AssertRC(rc2);
1998 pSession->StdOut.phChild = NULL;
1999 rc2 = RTHandleClose(pSession->StdErr.phChild); AssertRC(rc2);
2000 pSession->StdErr.phChild = NULL;
2001 }
2002 }
2003 }
2004#else
2005 RTHANDLE hStdIn;
2006 rc = RTFileOpenBitBucket(&hStdIn.u.hFile, RTFILE_O_READ);
2007 if (RT_SUCCESS(rc))
2008 {
2009 hStdIn.enmType = RTHANDLETYPE_FILE;
2010
2011 RTHANDLE hStdOutAndErr;
2012 rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE);
2013 if (RT_SUCCESS(rc))
2014 {
2015 hStdOutAndErr.enmType = RTHANDLETYPE_FILE;
2016
2017 /** @todo Set custom/cloned guest session environment block. */
2018 rc = RTProcCreateEx(pszExeName, papszArgs, RTENV_DEFAULT, uProcFlags,
2019 &hStdIn, &hStdOutAndErr, &hStdOutAndErr,
2020 !fAnonymous ? pSessionThread->StartupInfo.szUser : NULL,
2021 !fAnonymous ? pSessionThread->StartupInfo.szPassword : NULL,
2022 &pSessionThread->hProcess);
2023
2024 RTFileClose(hStdOutAndErr.u.hFile);
2025 }
2026
2027 RTFileClose(hStdIn.u.hFile);
2028 }
2029#endif
2030 }
2031 }
2032 else
2033 rc = VERR_FILE_NOT_FOUND;
2034
2035 if (RT_SUCCESS(rc))
2036 {
2037 /* Start session thread. */
2038 rc = RTThreadCreateF(&pSessionThread->Thread, gstcntlSessionThread,
2039 pSessionThread /*pvUser*/, 0 /*cbStack*/,
2040 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "sess%u", s_uCtrlSessionThread);
2041 if (RT_FAILURE(rc))
2042 {
2043 VBoxServiceError("Creating session thread failed, rc=%Rrc\n", rc);
2044 }
2045 else
2046 {
2047 /* Wait for the thread to initialize. */
2048 rc = RTThreadUserWait(pSessionThread->Thread, 60 * 1000 /* 60s timeout */);
2049 if ( ASMAtomicReadBool(&pSessionThread->fShutdown)
2050 || RT_FAILURE(rc))
2051 {
2052 VBoxServiceError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n",
2053 pSessionThread->StartupInfo.uSessionID, rc);
2054 if (RT_SUCCESS(rc))
2055 rc = VERR_CANT_CREATE; /** @todo Find a better rc. */
2056 }
2057 else
2058 {
2059 VBoxServiceVerbose(2, "Thread for session ID=%RU32 started\n",
2060 pSessionThread->StartupInfo.uSessionID);
2061
2062 ASMAtomicXchgBool(&pSessionThread->fStarted, true);
2063
2064 /* Add session to list. */
2065 /* rc = */ RTListAppend(pList, &pSessionThread->Node);
2066 if (ppSessionThread) /* Return session if wanted. */
2067 *ppSessionThread = pSessionThread;
2068 }
2069 }
2070 }
2071
2072 if (RT_FAILURE(rc))
2073 {
2074 RTMemFree(pSessionThread);
2075 }
2076 }
2077 else
2078 rc = VERR_NO_MEMORY;
2079
2080 VBoxServiceVerbose(3, "Forking session thread returned returned rc=%Rrc\n", rc);
2081 return rc;
2082}
2083
2084
2085/**
2086 * Waits for a formerly opened guest session process to close.
2087 *
2088 * @return IPRT status code.
2089 * @param pThread Guest session thread to wait for.
2090 * @param uTimeoutMS Waiting timeout (in ms).
2091 * @param uFlags Closing flags.
2092 */
2093int GstCntlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread,
2094 uint32_t uTimeoutMS, uint32_t uFlags)
2095{
2096 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
2097 /** @todo Validate closing flags. */
2098
2099 if (pThread->Thread == NIL_RTTHREAD)
2100 {
2101 AssertMsgFailed(("Guest session thread of session %p does not exist when it should\n",
2102 pThread));
2103 return VERR_NOT_FOUND;
2104 }
2105
2106 int rc = VINF_SUCCESS;
2107
2108 /*
2109 * The fork should have received the same closing request,
2110 * so just wait for the process to close.
2111 */
2112 if (ASMAtomicReadBool(&pThread->fStarted))
2113 {
2114 /* Ask the thread to shutdown. */
2115 ASMAtomicXchgBool(&pThread->fShutdown, true);
2116
2117 VBoxServiceVerbose(3, "Waiting for session thread ID=%RU32 to close (%RU32ms) ...\n",
2118 pThread->StartupInfo.uSessionID, uTimeoutMS);
2119
2120 int rcThread;
2121 rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread);
2122 if (RT_FAILURE(rc))
2123 {
2124 VBoxServiceError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n",
2125 pThread->StartupInfo.uSessionID, rc);
2126 }
2127 else
2128 VBoxServiceVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n",
2129 pThread->StartupInfo.uSessionID, rcThread);
2130 }
2131
2132 return rc;
2133}
2134
2135/**
2136 * Waits for the specified session thread to end and remove
2137 * it from the session thread list.
2138 *
2139 * @return IPRT status code.
2140 * @param pThread Session thread to destroy.
2141 * @param uFlags Closing flags.
2142 */
2143int GstCntlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uFlags)
2144{
2145 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
2146
2147 int rc = GstCntlSessionThreadWait(pThread,
2148 5 * 60 * 1000 /* 5 minutes timeout */, uFlags);
2149 /** @todo Kill session process if still around? */
2150
2151 /* Remove session from list and destroy object. */
2152 RTListNodeRemove(&pThread->Node);
2153 RTMemFree(pThread);
2154
2155 return rc;
2156}
2157
2158/**
2159 * Close all formerly opened guest session threads.
2160 *
2161 * @return IPRT status code.
2162 * @param pList Which list to close the session threads for.
2163 * @param uFlags Closing flags.
2164 */
2165int GstCntlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags)
2166{
2167 AssertPtrReturn(pList, VERR_INVALID_POINTER);
2168
2169 int rc = VINF_SUCCESS;
2170
2171 PVBOXSERVICECTRLSESSIONTHREAD pSessionThread
2172 = RTListGetFirst(pList, VBOXSERVICECTRLSESSIONTHREAD, Node);
2173 while (pSessionThread)
2174 {
2175 PVBOXSERVICECTRLSESSIONTHREAD pSessionThreadNext =
2176 RTListGetNext(pList, pSessionThread, VBOXSERVICECTRLSESSIONTHREAD, Node);
2177 bool fLast = RTListNodeIsLast(pList, &pSessionThread->Node);
2178
2179 int rc2 = GstCntlSessionThreadDestroy(pSessionThread, uFlags);
2180 if (RT_FAILURE(rc2))
2181 {
2182 VBoxServiceError("Closing session thread failed with rc=%Rrc\n", rc2);
2183 if (RT_SUCCESS(rc))
2184 rc = rc2;
2185 /* Keep going. */
2186 }
2187
2188 if (fLast)
2189 break;
2190
2191 pSessionThread = pSessionThreadNext;
2192 }
2193
2194 return rc;
2195}
2196
2197RTEXITCODE VBoxServiceControlSessionForkInit(int argc, char **argv)
2198{
2199 static const RTGETOPTDEF s_aOptions[] =
2200 {
2201 { "--logfile", VBOXSERVICESESSIONOPT_LOG_FILE, RTGETOPT_REQ_STRING },
2202 { "--user", VBOXSERVICESESSIONOPT_USERNAME, RTGETOPT_REQ_STRING },
2203 { "--session-id", VBOXSERVICESESSIONOPT_SESSION_ID, RTGETOPT_REQ_UINT32 },
2204 { "--session-proto", VBOXSERVICESESSIONOPT_SESSION_PROTO, RTGETOPT_REQ_UINT32 },
2205#ifdef DEBUG
2206 { "--thread-id", VBOXSERVICESESSIONOPT_THREAD_ID, RTGETOPT_REQ_UINT32 },
2207#endif /* DEBUG */
2208 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
2209 };
2210
2211 int ch;
2212 RTGETOPTUNION ValueUnion;
2213 RTGETOPTSTATE GetState;
2214 RTGetOptInit(&GetState, argc, argv,
2215 s_aOptions, RT_ELEMENTS(s_aOptions),
2216 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2217
2218 uint32_t uSessionFlags = VBOXSERVICECTRLSESSION_FLAG_FORK;
2219
2220 /* Init the session object. */
2221 int rc = GstCntlSessionInit(&g_Session, uSessionFlags);
2222 if (RT_FAILURE(rc))
2223 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize session object, rc=%Rrc\n", rc);
2224
2225 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
2226 && RT_SUCCESS(rc))
2227 {
2228 /* For options that require an argument, ValueUnion has received the value. */
2229 switch (ch)
2230 {
2231 case VBOXSERVICESESSIONOPT_LOG_FILE:
2232 if (!RTStrPrintf(g_szLogFile, sizeof(g_szLogFile), "%s", ValueUnion.psz))
2233 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unable to set logfile name to '%s'",
2234 ValueUnion.psz);
2235 break;
2236
2237 case VBOXSERVICESESSIONOPT_USERNAME:
2238 /** @todo. */
2239 break;
2240
2241 case VBOXSERVICESESSIONOPT_SESSION_ID:
2242 g_Session.StartupInfo.uSessionID = ValueUnion.u32;
2243 break;
2244
2245 case VBOXSERVICESESSIONOPT_SESSION_PROTO:
2246 g_Session.StartupInfo.uProtocol = ValueUnion.u32;
2247 break;
2248
2249 case VBOXSERVICESESSIONOPT_THREAD_ID:
2250 /* Not handled. */
2251 break;
2252
2253 /** @todo Implement help? */
2254
2255 case 'v':
2256 g_cVerbosity++;
2257 break;
2258
2259 case VINF_GETOPT_NOT_OPTION:
2260 /* Ignore; might be "guestsession" main command. */
2261 break;
2262
2263 default:
2264 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown command '%s'", ValueUnion.psz);
2265 break; /* Never reached. */
2266 }
2267 }
2268
2269 if (RT_FAILURE(rc))
2270 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Initialization failed with rc=%Rrc", rc);
2271
2272 if (g_Session.StartupInfo.uProtocol == UINT32_MAX)
2273 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No protocol version specified");
2274
2275 if (g_Session.StartupInfo.uSessionID == UINT32_MAX)
2276 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No session ID specified");
2277
2278 rc = VBoxServiceLogCreate(strlen(g_szLogFile) ? g_szLogFile : NULL);
2279 if (RT_FAILURE(rc))
2280 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log (%s, %Rrc)",
2281 strlen(g_szLogFile) ? g_szLogFile : "<None>", rc);
2282
2283 RTEXITCODE rcExit = gstcntlSessionForkWorker(&g_Session);
2284
2285 VBoxServiceLogDestroy();
2286 return rcExit;
2287}
2288
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