VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp@ 44863

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

GuestCtrl: Infrastructure changes for handling and executing dedicated guest sessions and protocol versioning (untested, work in progress).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 42.4 KB
Line 
1/* $Id: VBoxServiceControl.cpp 44863 2013-02-28 12:18:17Z vboxsync $ */
2/** @file
3 * VBoxServiceControl - Host-driven Guest Control.
4 */
5
6/*
7 * Copyright (C) 2012-2013 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/env.h>
25#include <iprt/file.h>
26#include <iprt/getopt.h>
27#include <iprt/mem.h>
28#include <iprt/path.h>
29#include <iprt/process.h>
30#include <iprt/semaphore.h>
31#include <iprt/thread.h>
32#include <VBox/VBoxGuestLib.h>
33#include <VBox/HostServices/GuestControlSvc.h>
34#include "VBoxServiceInternal.h"
35#include "VBoxServiceControl.h"
36#include "VBoxServiceUtils.h"
37
38using namespace guestControl;
39
40/*******************************************************************************
41* Global Variables *
42*******************************************************************************/
43/** The control interval (milliseconds). */
44static uint32_t g_uControlIntervalMS = 0;
45/** The semaphore we're blocking our main control thread on. */
46static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI;
47/** The VM session ID. Changes whenever the VM is restored or reset. */
48static uint64_t g_idControlSession;
49/** The guest control service client ID. */
50static uint32_t g_uControlSvcClientID = 0;
51/** How many started guest processes are kept into memory for supplying
52 * information to the host. Default is 256 processes. If 0 is specified,
53 * the maximum number of processes is unlimited. */
54static uint32_t g_uControlProcsMaxKept = 256;
55#ifdef DEBUG
56 static bool g_fControlDumpStdErr = false;
57 static bool g_fControlDumpStdOut = false;
58#endif
59/** List of active guest control threads (VBOXSERVICECTRLTHREAD). */
60static RTLISTANCHOR g_lstControlThreadsActive;
61/** List of inactive guest control threads (VBOXSERVICECTRLTHREAD). */
62static RTLISTANCHOR g_lstControlThreadsInactive;
63/** Critical section protecting g_GuestControlExecThreads. */
64static RTCRITSECT g_csControlThreads;
65/** List of guest control sessions (VBOXSERVICECTRLSESSION). */
66RTLISTANCHOR g_lstControlSessions;
67
68/*******************************************************************************
69* Internal Functions *
70*******************************************************************************/
71static int gstcntlHandleSessionOpen(PVBGLR3GUESTCTRLHOSTCTX pHostCtx);
72static int gstcntlHandleSessionClose(PVBGLR3GUESTCTRLHOSTCTX pHostCtx);
73static int gstcntlHandleProcExec(PVBGLR3GUESTCTRLHOSTCTX pHostCtx);
74static int gstcntlHandleProcInput(PVBGLR3GUESTCTRLHOSTCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf);
75static int gstcntlHandleProcOutput(PVBGLR3GUESTCTRLHOSTCTX pHostCtx);
76static int gstcntlHandleProcTerminate(PVBGLR3GUESTCTRLHOSTCTX pHostCtx);
77static int gstcntlHandleProcWaitFor(PVBGLR3GUESTCTRLHOSTCTX pHostCtx);
78static int gstcntlReapThreads(void);
79static void VBoxServiceControlShutdown(void);
80static int vboxServiceControlProcessCloseAll(void);
81static int gstcntlStartAllowed(bool *pbAllowed);
82
83#ifdef DEBUG
84static int gstcntlDumpToFile(const char *pszFileName, void *pvBuf, size_t cbBuf)
85{
86 AssertPtrReturn(pszFileName, VERR_INVALID_POINTER);
87 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
88
89 if (!cbBuf)
90 return VINF_SUCCESS;
91
92 char szFile[RTPATH_MAX];
93
94 int rc = RTPathTemp(szFile, sizeof(szFile));
95 if (RT_SUCCESS(rc))
96 rc = RTPathAppend(szFile, sizeof(szFile), pszFileName);
97
98 if (RT_SUCCESS(rc))
99 {
100 VBoxServiceVerbose(4, "Dumping %ld bytes to \"%s\"\n", cbBuf, szFile);
101
102 RTFILE fh;
103 rc = RTFileOpen(&fh, szFile, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
104 if (RT_SUCCESS(rc))
105 {
106 rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */);
107 RTFileClose(fh);
108 }
109 }
110
111 return rc;
112}
113#endif
114
115
116/** @copydoc VBOXSERVICE::pfnPreInit */
117static DECLCALLBACK(int) VBoxServiceControlPreInit(void)
118{
119#ifdef VBOX_WITH_GUEST_PROPS
120 /*
121 * Read the service options from the VM's guest properties.
122 * Note that these options can be overridden by the command line options later.
123 */
124 uint32_t uGuestPropSvcClientID;
125 int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
126 if (RT_FAILURE(rc))
127 {
128 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
129 {
130 VBoxServiceVerbose(0, "Guest property service is not available, skipping\n");
131 rc = VINF_SUCCESS;
132 }
133 else
134 VBoxServiceError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
135 }
136 else
137 {
138 VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
139 }
140
141 if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
142 rc = VINF_SUCCESS;
143 return rc;
144#else
145 /* Nothing to do here yet. */
146 return VINF_SUCCESS;
147#endif
148}
149
150
151/** @copydoc VBOXSERVICE::pfnOption */
152static DECLCALLBACK(int) VBoxServiceControlOption(const char **ppszShort, int argc, char **argv, int *pi)
153{
154 int rc = -1;
155 if (ppszShort)
156 /* no short options */;
157 else if (!strcmp(argv[*pi], "--control-interval"))
158 rc = VBoxServiceArgUInt32(argc, argv, "", pi,
159 &g_uControlIntervalMS, 1, UINT32_MAX - 1);
160#ifdef DEBUG
161 else if (!strcmp(argv[*pi], "--control-dump-stderr"))
162 {
163 g_fControlDumpStdErr = true;
164 rc = 0; /* Flag this command as parsed. */
165 }
166 else if (!strcmp(argv[*pi], "--control-dump-stdout"))
167 {
168 g_fControlDumpStdOut = true;
169 rc = 0; /* Flag this command as parsed. */
170 }
171#endif
172 return rc;
173}
174
175
176/** @copydoc VBOXSERVICE::pfnInit */
177static DECLCALLBACK(int) VBoxServiceControlInit(void)
178{
179 /*
180 * If not specified, find the right interval default.
181 * Then create the event sem to block on.
182 */
183 if (!g_uControlIntervalMS)
184 g_uControlIntervalMS = 1000;
185
186 int rc = RTSemEventMultiCreate(&g_hControlEvent);
187 AssertRCReturn(rc, rc);
188
189 VbglR3GetSessionId(&g_idControlSession);
190 /* The status code is ignored as this information is not available with VBox < 3.2.10. */
191
192 rc = VbglR3GuestCtrlConnect(&g_uControlSvcClientID);
193 if (RT_SUCCESS(rc))
194 {
195 VBoxServiceVerbose(3, "Service client ID: %#x\n", g_uControlSvcClientID);
196
197 /* Init lists. */
198 RTListInit(&g_lstControlThreadsActive);
199 RTListInit(&g_lstControlThreadsInactive);
200 RTListInit(&g_lstControlSessions);
201
202 /* Init critical section for protecting the thread lists. */
203 rc = RTCritSectInit(&g_csControlThreads);
204 AssertRC(rc);
205 }
206 else
207 {
208 /* If the service was not found, we disable this service without
209 causing VBoxService to fail. */
210 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
211 {
212 VBoxServiceVerbose(0, "Guest control service is not available\n");
213 rc = VERR_SERVICE_DISABLED;
214 }
215 else
216 VBoxServiceError("Failed to connect to the guest control service! Error: %Rrc\n", rc);
217 RTSemEventMultiDestroy(g_hControlEvent);
218 g_hControlEvent = NIL_RTSEMEVENTMULTI;
219 }
220 return rc;
221}
222
223
224/** @copydoc VBOXSERVICE::pfnWorker */
225DECLCALLBACK(int) VBoxServiceControlWorker(bool volatile *pfShutdown)
226{
227 /*
228 * Tell the control thread that it can continue
229 * spawning services.
230 */
231 RTThreadUserSignal(RTThreadSelf());
232 Assert(g_uControlSvcClientID > 0);
233
234 int rc = VINF_SUCCESS;
235
236 /* Allocate a scratch buffer for commands which also send
237 * payload data with them. */
238 uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */
239 AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), VERR_INVALID_PARAMETER);
240 uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf);
241 AssertPtrReturn(pvScratchBuf, VERR_NO_MEMORY);
242
243 VBGLR3GUESTCTRLHOSTCTX ctxHost = { g_uControlSvcClientID,
244 1 /* Default protocol version */ };
245 for (;;)
246 {
247 VBoxServiceVerbose(3, "Waiting for host msg ...\n");
248 uint32_t uMsg = 0;
249 uint32_t cParms = 0;
250 rc = VbglR3GuestCtrlMsgWaitFor(g_uControlSvcClientID, &uMsg, &cParms);
251 if (rc == VERR_TOO_MUCH_DATA)
252 {
253 VBoxServiceVerbose(4, "Message requires %ld parameters, but only 2 supplied -- retrying request (no error!)...\n", cParms);
254 rc = VINF_SUCCESS; /* Try to get "real" message in next block below. */
255 }
256 else if (RT_FAILURE(rc))
257 VBoxServiceVerbose(3, "Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
258 if (RT_SUCCESS(rc))
259 {
260 VBoxServiceVerbose(3, "Msg=%u (%u parms) retrieved\n", uMsg, cParms);
261
262 /* Set number of parameters for current host context. */
263 ctxHost.uNumParms = cParms;
264
265 /* Check for VM session change. */
266 uint64_t idNewSession = g_idControlSession;
267 int rc2 = VbglR3GetSessionId(&idNewSession);
268 if ( RT_SUCCESS(rc2)
269 && (idNewSession != g_idControlSession))
270 {
271 VBoxServiceVerbose(1, "The VM session ID changed\n");
272 g_idControlSession = idNewSession;
273
274 /* Close all opened guest sessions -- all context IDs, sessions etc.
275 * are now invalid. */
276 rc2 = vboxServiceControlProcessCloseAll();
277 AssertRC(rc2);
278 }
279
280 switch (uMsg)
281 {
282 case HOST_CANCEL_PENDING_WAITS:
283 VBoxServiceVerbose(1, "We were asked to quit ...\n");
284 break;
285
286 case HOST_SESSION_CREATE:
287 rc = gstcntlHandleSessionOpen(&ctxHost);
288 break;
289
290 case HOST_SESSION_CLOSE:
291 rc = gstcntlHandleSessionClose(&ctxHost);
292 break;
293
294 case HOST_EXEC_CMD:
295 rc = gstcntlHandleProcExec(&ctxHost);
296 break;
297
298 case HOST_EXEC_SET_INPUT:
299 rc = gstcntlHandleProcInput(&ctxHost,
300 pvScratchBuf, cbScratchBuf);
301 break;
302
303 case HOST_EXEC_GET_OUTPUT:
304 rc = gstcntlHandleProcOutput(&ctxHost);
305 break;
306
307 case HOST_EXEC_TERMINATE:
308 rc = gstcntlHandleProcTerminate(&ctxHost);
309 break;
310
311 case HOST_EXEC_WAIT_FOR:
312 rc = gstcntlHandleProcWaitFor(&ctxHost);
313 break;
314
315 default:
316 VBoxServiceVerbose(3, "Unsupported message from host! Msg=%u\n", uMsg);
317 /* Don't terminate here; just wait for the next message. */
318 break;
319 }
320 }
321
322 /* Do we need to shutdown? */
323 if ( *pfShutdown
324 || (RT_SUCCESS(rc) && uMsg == HOST_CANCEL_PENDING_WAITS))
325 {
326 break;
327 }
328
329 /* Let's sleep for a bit and let others run ... */
330 RTThreadYield();
331 }
332
333 VBoxServiceVerbose(0, "Guest control service stopped\n");
334
335 /* Delete scratch buffer. */
336 if (pvScratchBuf)
337 RTMemFree(pvScratchBuf);
338
339 VBoxServiceVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc);
340 return rc;
341}
342
343
344/**
345 * Handles starting processes on the guest.
346 *
347 * @returns IPRT status code.
348 * @param uClientID The HGCM client session ID.
349 * @param cParms The number of parameters the host is offering.
350 */
351static int gstcntlHandleProcExec(PVBGLR3GUESTCTRLHOSTCTX pHostCtx)
352{
353 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
354
355 int rc;
356 bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */
357
358 if ( (pHostCtx->uProtocol < 2 && pHostCtx->uNumParms == 11)
359 || (pHostCtx->uProtocol >= 2 && pHostCtx->uNumParms == 12)
360 )
361 {
362 VBOXSERVICECTRLPROCSTARTUPINFO proc;
363 RT_ZERO(proc);
364
365 /* Initialize maximum environment block size -- needed as input
366 * parameter to retrieve the stuff from the host. On output this then
367 * will contain the actual block size. */
368 proc.cbEnv = sizeof(proc.szEnv);
369
370 rc = VbglR3GuestCtrlProcGetStart(pHostCtx,
371 /* Command */
372 proc.szCmd, sizeof(proc.szCmd),
373 /* Flags */
374 &proc.uFlags,
375 /* Arguments */
376 proc.szArgs, sizeof(proc.szArgs), &proc.uNumArgs,
377 /* Environment */
378 proc.szEnv, &proc.cbEnv, &proc.uNumEnvVars,
379 /* Credentials; for hosts with VBox < 4.3. */
380 proc.szUser, sizeof(proc.szUser),
381 proc.szPassword, sizeof(proc.szPassword),
382 /* Timelimit */
383 &proc.uTimeLimitMS,
384 /* Process priority */
385 &proc.uPriority,
386 /* Process affinity */
387 proc.uAffinity, sizeof(proc.uAffinity), &proc.uNumAffinity);
388 if (RT_SUCCESS(rc))
389 {
390 VBoxServiceVerbose(3, "Request to start process szCmd=%s, uFlags=0x%x, szArgs=%s, szEnv=%s, szUser=%s, szPassword=%s, uTimeout=%RU32\n",
391 proc.szCmd, proc.uFlags,
392 proc.uNumArgs ? proc.szArgs : "<None>",
393 proc.uNumEnvVars ? proc.szEnv : "<None>",
394 proc.szUser,
395#ifdef DEBUG
396 proc.szPassword,
397#else
398 "XXX", /* Never show passwords in release mode. */
399#endif
400 proc.uTimeLimitMS);
401
402 rc = gstcntlReapThreads();
403 if (RT_FAILURE(rc))
404 VBoxServiceError("Reaping stopped processes failed with rc=%Rrc\n", rc);
405 /* Keep going. */
406
407 rc = gstcntlStartAllowed(&fStartAllowed);
408 if (RT_SUCCESS(rc))
409 {
410 if (fStartAllowed)
411 {
412 rc = GstCntlProcessStart(pHostCtx->uContextID, &proc);
413 }
414 else
415 rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */
416 }
417 }
418 }
419 else
420 rc = VERR_NOT_SUPPORTED; /* Unsupported number of parameters. */
421
422 /* In case of an error we need to notify the host to not wait forever for our response. */
423 if (RT_FAILURE(rc))
424 {
425 VBoxServiceError("Starting process failed with rc=%Rrc\n", rc);
426
427 /*
428 * Note: The context ID can be 0 because we mabye weren't able to fetch the command
429 * from the host. The host in case has to deal with that!
430 */
431 int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx->uClientID, pHostCtx->uContextID,
432 0 /* PID, invalid */,
433 PROC_STS_ERROR, rc,
434 NULL /* pvData */, 0 /* cbData */);
435 if (RT_FAILURE(rc2))
436 {
437 VBoxServiceError("Error sending start process status to host, rc=%Rrc\n", rc2);
438 if (RT_SUCCESS(rc))
439 rc = rc2;
440 }
441 }
442
443 return rc;
444}
445
446
447static int gstcntlHandleProcTerminate(PVBGLR3GUESTCTRLHOSTCTX pHostCtx)
448{
449 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
450
451 uint32_t uPID;
452 int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID);
453 if (RT_SUCCESS(rc))
454 {
455 PVBOXSERVICECTRLREQUEST pRequest;
456 rc = GstCntlProcessRequestAllocEx(&pRequest, VBOXSERVICECTRLREQUEST_PROC_TERM,
457 NULL /* pvBuf */, NULL /* cbBuf */, pHostCtx->uContextID);
458 if (RT_SUCCESS(rc))
459 {
460 rc = GstCntlProcessPerform(uPID, pRequest);
461 GstCntlProcessRequestFree(pRequest);
462 }
463 }
464
465 return rc;
466}
467
468
469static int gstcntlHandleProcWaitFor(PVBGLR3GUESTCTRLHOSTCTX pHostCtx)
470{
471 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
472
473 uint32_t uPID;
474 uint32_t uWaitFlags; uint32_t uTimeoutMS;
475
476 int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS);
477 if (RT_SUCCESS(rc))
478 {
479 PVBOXSERVICECTRLREQUEST pRequest;
480 VBOXSERVICECTRLREQDATA_WAIT_FOR reqData = { uWaitFlags, uTimeoutMS };
481 rc = GstCntlProcessRequestAllocEx(&pRequest, VBOXSERVICECTRLREQUEST_WAIT_FOR,
482 &reqData, sizeof(reqData), pHostCtx->uContextID);
483 if (RT_SUCCESS(rc))
484 {
485 rc = GstCntlProcessPerform(uPID, pRequest);
486 GstCntlProcessRequestFree(pRequest);
487 }
488 }
489
490 return rc;
491}
492
493
494/**
495 * Gets output from stdout/stderr of a specified guest process.
496 *
497 * @return IPRT status code.
498 * @param uPID PID of process to retrieve the output from.
499 * @param uCID Context ID.
500 * @param uHandleId Stream ID (stdout = 0, stderr = 2) to get the output from.
501 * @param cMsTimeout Timeout (in ms) to wait for output becoming
502 * available.
503 * @param pvBuf Pointer to a pre-allocated buffer to store the output.
504 * @param cbBuf Size (in bytes) of the pre-allocated buffer.
505 * @param pcbRead Pointer to number of bytes read. Optional.
506 */
507int VBoxServiceControlExecGetOutput(uint32_t uPID, uint32_t uCID,
508 uint32_t uHandleId, uint32_t cMsTimeout,
509 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
510{
511 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
512 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
513 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
514
515 int rc = VINF_SUCCESS;
516 VBOXSERVICECTRLREQUESTTYPE reqType = VBOXSERVICECTRLREQUEST_UNKNOWN; /* (gcc maybe, well, wrong.) */
517 switch (uHandleId)
518 {
519 case OUTPUT_HANDLE_ID_STDERR:
520 reqType = VBOXSERVICECTRLREQUEST_PROC_STDERR;
521 break;
522
523 case OUTPUT_HANDLE_ID_STDOUT:
524 case OUTPUT_HANDLE_ID_STDOUT_DEPRECATED:
525 reqType = VBOXSERVICECTRLREQUEST_PROC_STDOUT;
526 break;
527
528 default:
529 rc = VERR_INVALID_PARAMETER;
530 break;
531 }
532
533 if (RT_SUCCESS(rc))
534 {
535 PVBOXSERVICECTRLREQUEST pRequest;
536 rc = GstCntlProcessRequestAllocEx(&pRequest, reqType, pvBuf, cbBuf, uCID);
537 if (RT_SUCCESS(rc))
538 {
539 rc = GstCntlProcessPerform(uPID, pRequest);
540 if (RT_SUCCESS(rc) && pcbRead)
541 *pcbRead = pRequest->cbData;
542 GstCntlProcessRequestFree(pRequest);
543 }
544 }
545
546 return rc;
547}
548
549
550/**
551 * Sets the specified guest thread to a certain list.
552 *
553 * @return IPRT status code.
554 * @param enmList List to move thread to.
555 * @param pThread Thread to set inactive.
556 */
557int GstCntlListSet(VBOXSERVICECTRLTHREADLISTTYPE enmList,
558 PVBOXSERVICECTRLTHREAD pThread)
559{
560 AssertReturn(enmList > VBOXSERVICECTRLTHREADLIST_UNKNOWN, VERR_INVALID_PARAMETER);
561 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
562
563 int rc = RTCritSectEnter(&g_csControlThreads);
564 if (RT_SUCCESS(rc))
565 {
566 VBoxServiceVerbose(3, "Setting thread (PID %RU32) to list %d\n",
567 pThread->uPID, enmList);
568
569 PRTLISTANCHOR pAnchor = NULL;
570 switch (enmList)
571 {
572 case VBOXSERVICECTRLTHREADLIST_STOPPED:
573 pAnchor = &g_lstControlThreadsInactive;
574 break;
575
576 case VBOXSERVICECTRLTHREADLIST_RUNNING:
577 pAnchor = &g_lstControlThreadsActive;
578 break;
579
580 default:
581 AssertMsgFailed(("Unknown list type: %u", enmList));
582 break;
583 }
584
585 if (!pAnchor)
586 rc = VERR_INVALID_PARAMETER;
587
588 if (RT_SUCCESS(rc))
589 {
590 if (pThread->pAnchor != NULL)
591 {
592 /* If thread was assigned to a list before,
593 * remove the thread from the old list first. */
594 /* rc = */ RTListNodeRemove(&pThread->Node);
595 }
596
597 /* Add thread to desired list. */
598 /* rc = */ RTListAppend(pAnchor, &pThread->Node);
599 pThread->pAnchor = pAnchor;
600 }
601
602 int rc2 = RTCritSectLeave(&g_csControlThreads);
603 if (RT_SUCCESS(rc))
604 rc = rc2;
605 }
606
607 return VINF_SUCCESS;
608}
609
610
611/**
612 * Injects input to a specified running process.
613 *
614 * @return IPRT status code.
615 * @param uPID PID of process to set the input for.
616 * @param fPendingClose Flag indicating whether this is the last input block sent to the process.
617 * @param pvBuf Pointer to a buffer containing the actual input data.
618 * @param cbBuf Size (in bytes) of the input buffer data.
619 * @param pcbWritten Pointer to number of bytes written to the process. Optional.
620 */
621int VBoxServiceControlSetInput(uint32_t uPID, uint32_t uCID,
622 bool fPendingClose,
623 void *pvBuf, uint32_t cbBuf,
624 uint32_t *pcbWritten)
625{
626 /* pvBuf is optional. */
627 /* cbBuf is optional. */
628 /* pcbWritten is optional. */
629
630 PVBOXSERVICECTRLREQUEST pRequest;
631 int rc = GstCntlProcessRequestAllocEx(&pRequest,
632 fPendingClose
633 ? VBOXSERVICECTRLREQUEST_PROC_STDIN_EOF
634 : VBOXSERVICECTRLREQUEST_PROC_STDIN,
635 pvBuf, cbBuf, uCID);
636 if (RT_SUCCESS(rc))
637 {
638 rc = GstCntlProcessPerform(uPID, pRequest);
639 if (RT_SUCCESS(rc))
640 {
641 if (pcbWritten)
642 *pcbWritten = pRequest->cbData;
643 }
644
645 GstCntlProcessRequestFree(pRequest);
646 }
647
648 return rc;
649}
650
651
652/**
653 * Handles input for a started process by copying the received data into its
654 * stdin pipe.
655 *
656 * @returns IPRT status code.
657 * @param pvScratchBuf The scratch buffer.
658 * @param cbScratchBuf The scratch buffer size for retrieving the input data.
659 */
660static int gstcntlHandleProcInput(PVBGLR3GUESTCTRLHOSTCTX pHostCtx,
661 void *pvScratchBuf, size_t cbScratchBuf)
662{
663 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
664 AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER);
665 AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER);
666
667 uint32_t uPID;
668 uint32_t uFlags;
669 uint32_t cbSize;
670
671 uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */
672 uint32_t cbWritten = 0; /* Number of bytes written to the guest. */
673
674 /*
675 * Ask the host for the input data.
676 */
677 int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &uFlags,
678 pvScratchBuf, cbScratchBuf, &cbSize);
679 if (RT_FAILURE(rc))
680 {
681 VBoxServiceError("[PID %RU32]: Failed to retrieve exec input command! Error: %Rrc\n",
682 uPID, rc);
683 }
684 else if (cbSize > cbScratchBuf)
685 {
686 VBoxServiceError("[PID %RU32]: Too much input received! cbSize=%u, cbScratchBuf=%u\n",
687 uPID, cbSize, cbScratchBuf);
688 rc = VERR_INVALID_PARAMETER;
689 }
690 else
691 {
692 /*
693 * Is this the last input block we need to deliver? Then let the pipe know ...
694 */
695 bool fPendingClose = false;
696 if (uFlags & INPUT_FLAG_EOF)
697 {
698 fPendingClose = true;
699 VBoxServiceVerbose(4, "[PID %RU32]: Got last input block of size %u ...\n",
700 uPID, cbSize);
701 }
702
703 rc = VBoxServiceControlSetInput(uPID, pHostCtx->uContextID, fPendingClose, pvScratchBuf,
704 cbSize, &cbWritten);
705 VBoxServiceVerbose(4, "[PID %RU32]: Written input, CID=%u, rc=%Rrc, uFlags=0x%x, fPendingClose=%d, cbSize=%u, cbWritten=%u\n",
706 uPID, pHostCtx->uContextID, rc, uFlags, fPendingClose, cbSize, cbWritten);
707 if (RT_SUCCESS(rc))
708 {
709 uStatus = INPUT_STS_WRITTEN;
710 uFlags = 0; /* No flags at the moment. */
711 }
712 else
713 {
714 if (rc == VERR_BAD_PIPE)
715 uStatus = INPUT_STS_TERMINATED;
716 else if (rc == VERR_BUFFER_OVERFLOW)
717 uStatus = INPUT_STS_OVERFLOW;
718 }
719 }
720
721 /*
722 * If there was an error and we did not set the host status
723 * yet, then do it now.
724 */
725 if ( RT_FAILURE(rc)
726 && uStatus == INPUT_STS_UNDEFINED)
727 {
728 uStatus = INPUT_STS_ERROR;
729 uFlags = rc;
730 }
731 Assert(uStatus > INPUT_STS_UNDEFINED);
732
733 VBoxServiceVerbose(3, "[PID %RU32]: Input processed, CID=%u, uStatus=%u, uFlags=0x%x, cbWritten=%u\n",
734 uPID, pHostCtx->uContextID, uStatus, uFlags, cbWritten);
735
736 /* Note: Since the context ID is unique the request *has* to be completed here,
737 * regardless whether we got data or not! Otherwise the progress object
738 * on the host never will get completed! */
739 rc = VbglR3GuestCtrlProcCbStatusInput(pHostCtx->uClientID, pHostCtx->uContextID, uPID,
740 uStatus, uFlags, (uint32_t)cbWritten);
741
742 if (RT_FAILURE(rc))
743 VBoxServiceError("[PID %RU32]: Failed to report input status! Error: %Rrc\n",
744 uPID, rc);
745 return rc;
746}
747
748
749/**
750 * Handles the guest control output command.
751 *
752 * @return IPRT status code.
753 */
754static int gstcntlHandleProcOutput(PVBGLR3GUESTCTRLHOSTCTX pHostCtx)
755{
756 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
757
758 uint32_t uPID;
759 uint32_t uHandleID;
760 uint32_t uFlags;
761
762 int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &uFlags);
763 if (RT_SUCCESS(rc))
764 {
765 uint8_t *pBuf = (uint8_t*)RTMemAlloc(_64K);
766 if (pBuf)
767 {
768 uint32_t cbRead = 0;
769 rc = VBoxServiceControlExecGetOutput(uPID, pHostCtx->uContextID, uHandleID, RT_INDEFINITE_WAIT /* Timeout */,
770 pBuf, _64K /* cbSize */, &cbRead);
771 VBoxServiceVerbose(3, "[PID %RU32]: Got output, rc=%Rrc, CID=%u, cbRead=%u, uHandle=%u, uFlags=%u\n",
772 uPID, rc, pHostCtx->uContextID, cbRead, uHandleID, uFlags);
773
774#ifdef DEBUG
775 if ( g_fControlDumpStdErr
776 && uHandleID == OUTPUT_HANDLE_ID_STDERR)
777 {
778 char szPID[RTPATH_MAX];
779 if (!RTStrPrintf(szPID, sizeof(szPID), "VBoxService_PID%u_StdOut.txt", uPID))
780 rc = VERR_BUFFER_UNDERFLOW;
781 if (RT_SUCCESS(rc))
782 rc = gstcntlDumpToFile(szPID, pBuf, cbRead);
783 }
784 else if ( g_fControlDumpStdOut
785 && ( uHandleID == OUTPUT_HANDLE_ID_STDOUT
786 || uHandleID == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED))
787 {
788 char szPID[RTPATH_MAX];
789 if (!RTStrPrintf(szPID, sizeof(szPID), "VBoxService_PID%u_StdOut.txt", uPID))
790 rc = VERR_BUFFER_UNDERFLOW;
791 if (RT_SUCCESS(rc))
792 rc = gstcntlDumpToFile(szPID, pBuf, cbRead);
793 AssertRC(rc);
794 }
795#endif
796 /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary
797 * data which the host needs to work with -- so just pass through all data unfiltered! */
798
799 /* Note: Since the context ID is unique the request *has* to be completed here,
800 * regardless whether we got data or not! Otherwise the progress object
801 * on the host never will get completed! */
802 int rc2 = VbglR3GuestCtrlProcCbOutput(pHostCtx->uClientID, pHostCtx->uContextID, uPID, uHandleID, uFlags,
803 pBuf, cbRead);
804 if (RT_SUCCESS(rc))
805 rc = rc2;
806 else if (rc == VERR_NOT_FOUND) /* It's not critical if guest process (PID) is not found. */
807 rc = VINF_SUCCESS;
808
809 RTMemFree(pBuf);
810 }
811 else
812 rc = VERR_NO_MEMORY;
813 }
814
815 if (RT_FAILURE(rc))
816 VBoxServiceError("[PID %RU32]: Error handling output command! Error: %Rrc\n",
817 uPID, rc);
818 return rc;
819}
820
821
822static int gstcntlHandleSessionOpen(PVBGLR3GUESTCTRLHOSTCTX pHostCtx)
823{
824 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
825
826 VBOXSERVICECTRLSESSIONSTARTUPINFO ssInfo = { 0 };
827 int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx,
828 &ssInfo.uProtocol,
829 ssInfo.szUser, sizeof(ssInfo.szUser),
830 ssInfo.szPassword, sizeof(ssInfo.szPassword),
831 ssInfo.szDomain, sizeof(ssInfo.szDomain),
832 &ssInfo.uFlags, &ssInfo.uSessionID);
833 if (RT_SUCCESS(rc))
834 {
835 /* The session open call has the protocol version the host
836 * wants to use. Store it in the host context for later calls. */
837 pHostCtx->uProtocol = ssInfo.uProtocol;
838
839 rc = GstCntlSessionOpen(&ssInfo, NULL /* Node */);
840 }
841
842 /* Report back session opening status in any case. */
843 int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx->uClientID, pHostCtx->uContextID,
844 GUEST_SESSION_NOTIFYTYPE_OPEN, rc /* uint32_t vs. int */);
845 if (RT_FAILURE(rc2))
846 {
847 VBoxServiceError("Reporting session opening status failed with rc=%Rrc\n", rc2);
848 if (RT_SUCCESS(rc))
849 rc = rc2;
850 }
851
852 VBoxServiceVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc);
853 return rc;
854}
855
856
857static int gstcntlHandleSessionClose(PVBGLR3GUESTCTRLHOSTCTX pHostCtx)
858{
859 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
860
861 uint32_t uSessionID, uFlags;
862
863 int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &uFlags, &uSessionID);
864 if (RT_SUCCESS(rc))
865 {
866 rc = VERR_NOT_FOUND;
867
868 PVBOXSERVICECTRLSESSION pSession;
869 RTListForEach(&g_lstControlSessions, pSession, VBOXSERVICECTRLSESSION, Node)
870 {
871 if (pSession->StartupInfo.uSessionID == uSessionID)
872 {
873 rc = GstCntlSessionClose(pSession, uFlags);
874 break;
875 }
876 }
877 }
878
879 /* Report back session closing status in any case. */
880 int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx->uClientID, pHostCtx->uContextID,
881 GUEST_SESSION_NOTIFYTYPE_CLOSE, rc /* uint32_t vs. int */);
882 if (RT_FAILURE(rc2))
883 {
884 VBoxServiceError("Reporting session closing status failed with rc=%Rrc\n", rc2);
885 if (RT_SUCCESS(rc))
886 rc = rc2;
887 }
888
889 VBoxServiceVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n",
890 uSessionID, rc);
891 return rc;
892}
893
894
895/** @copydoc VBOXSERVICE::pfnStop */
896static DECLCALLBACK(void) VBoxServiceControlStop(void)
897{
898 VBoxServiceVerbose(3, "Stopping ...\n");
899
900 /** @todo Later, figure what to do if we're in RTProcWait(). It's a very
901 * annoying call since doesn't support timeouts in the posix world. */
902 if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
903 RTSemEventMultiSignal(g_hControlEvent);
904
905 /*
906 * Ask the host service to cancel all pending requests so that we can
907 * shutdown properly here.
908 */
909 if (g_uControlSvcClientID)
910 {
911 VBoxServiceVerbose(3, "Cancelling pending waits (client ID=%u) ...\n",
912 g_uControlSvcClientID);
913
914 int rc = VbglR3GuestCtrlCancelPendingWaits(g_uControlSvcClientID);
915 if (RT_FAILURE(rc))
916 VBoxServiceError("Cancelling pending waits failed; rc=%Rrc\n", rc);
917 }
918}
919
920
921/**
922 * Reaps all inactive guest process threads.
923 *
924 * @return IPRT status code.
925 */
926static int gstcntlReapThreads(void)
927{
928 int rc = RTCritSectEnter(&g_csControlThreads);
929 if (RT_SUCCESS(rc))
930 {
931 PVBOXSERVICECTRLTHREAD pThread =
932 RTListGetFirst(&g_lstControlThreadsInactive, VBOXSERVICECTRLTHREAD, Node);
933 while (pThread)
934 {
935 PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLTHREAD, Node);
936 bool fLast = RTListNodeIsLast(&g_lstControlThreadsInactive, &pThread->Node);
937 int rc2 = GstCntlProcessWait(pThread, 30 * 1000 /* 30 seconds max. */,
938 NULL /* rc */);
939 if (RT_SUCCESS(rc2))
940 {
941 RTListNodeRemove(&pThread->Node);
942
943 rc2 = GstCntlProcessFree(pThread);
944 if (RT_FAILURE(rc2))
945 {
946 VBoxServiceError("Freeing guest process thread failed with rc=%Rrc\n", rc2);
947 if (RT_SUCCESS(rc)) /* Keep original failure. */
948 rc = rc2;
949 }
950 }
951 else
952 VBoxServiceError("Waiting on guest process thread failed with rc=%Rrc\n", rc2);
953 /* Keep going. */
954
955 if (fLast)
956 break;
957
958 pThread = pNext;
959 }
960
961 int rc2 = RTCritSectLeave(&g_csControlThreads);
962 if (RT_SUCCESS(rc))
963 rc = rc2;
964 }
965
966 VBoxServiceVerbose(4, "Reaping threads returned with rc=%Rrc\n", rc);
967 return rc;
968}
969
970
971static int vboxServiceControlProcessClose()
972{
973 /** Note: This will be a guest tsession task later. */
974
975 /* Signal all threads in the active list that we want to shutdown. */
976 PVBOXSERVICECTRLTHREAD pThread;
977 RTListForEach(&g_lstControlThreadsActive, pThread, VBOXSERVICECTRLTHREAD, Node)
978 GstCntlProcessStop(pThread);
979
980 /* Wait for all active threads to shutdown and destroy the active thread list. */
981 pThread = RTListGetFirst(&g_lstControlThreadsActive, VBOXSERVICECTRLTHREAD, Node);
982 while (pThread)
983 {
984 PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLTHREAD, Node);
985 bool fLast = RTListNodeIsLast(&g_lstControlThreadsActive, &pThread->Node);
986
987 int rc2 = GstCntlProcessWait(pThread,
988 30 * 1000 /* Wait 30 seconds max. */,
989 NULL /* rc */);
990 if (RT_FAILURE(rc2))
991 {
992 VBoxServiceError("Guest process thread failed to stop; rc=%Rrc\n", rc2);
993 /* Keep going. */
994 }
995
996 if (fLast)
997 break;
998
999 pThread = pNext;
1000 }
1001
1002 int rc = gstcntlReapThreads();
1003 if (RT_FAILURE(rc))
1004 VBoxServiceError("Reaping inactive threads failed with rc=%Rrc\n", rc);
1005
1006 AssertMsg(RTListIsEmpty(&g_lstControlThreadsActive),
1007 ("Guest process active thread list still contains entries when it should not\n"));
1008 AssertMsg(RTListIsEmpty(&g_lstControlThreadsInactive),
1009 ("Guest process inactive thread list still contains entries when it should not\n"));
1010
1011 return rc;
1012}
1013
1014
1015static int vboxServiceControlProcessCloseAll(void)
1016{
1017 return vboxServiceControlProcessClose();
1018}
1019
1020
1021/**
1022 * Destroys all guest process threads which are still active.
1023 */
1024static void VBoxServiceControlShutdown(void)
1025{
1026 VBoxServiceVerbose(2, "Shutting down ...\n");
1027
1028 int rc2 = vboxServiceControlProcessCloseAll();
1029 AssertRC(rc2);
1030
1031 /* Destroy critical section. */
1032 RTCritSectDelete(&g_csControlThreads);
1033
1034 VBoxServiceVerbose(2, "Shutting down complete\n");
1035}
1036
1037
1038/** @copydoc VBOXSERVICE::pfnTerm */
1039static DECLCALLBACK(void) VBoxServiceControlTerm(void)
1040{
1041 VBoxServiceVerbose(3, "Terminating ...\n");
1042
1043 VBoxServiceControlShutdown();
1044
1045 VBoxServiceVerbose(3, "Disconnecting client ID=%u ...\n",
1046 g_uControlSvcClientID);
1047 VbglR3GuestCtrlDisconnect(g_uControlSvcClientID);
1048 g_uControlSvcClientID = 0;
1049
1050 if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
1051 {
1052 RTSemEventMultiDestroy(g_hControlEvent);
1053 g_hControlEvent = NIL_RTSEMEVENTMULTI;
1054 }
1055}
1056
1057
1058/**
1059 * Determines whether starting a new guest process according to the
1060 * maximum number of concurrent guest processes defined is allowed or not.
1061 *
1062 * @return IPRT status code.
1063 * @param pbAllowed True if starting (another) guest process
1064 * is allowed, false if not.
1065 */
1066static int gstcntlStartAllowed(bool *pbAllowed)
1067{
1068 AssertPtrReturn(pbAllowed, VERR_INVALID_POINTER);
1069
1070 int rc = RTCritSectEnter(&g_csControlThreads);
1071 if (RT_SUCCESS(rc))
1072 {
1073 /*
1074 * Check if we're respecting our memory policy by checking
1075 * how many guest processes are started and served already.
1076 */
1077 bool fLimitReached = false;
1078 if (g_uControlProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */
1079 {
1080 uint32_t uProcsRunning = 0;
1081 PVBOXSERVICECTRLTHREAD pThread;
1082 RTListForEach(&g_lstControlThreadsActive, pThread, VBOXSERVICECTRLTHREAD, Node)
1083 uProcsRunning++;
1084
1085 VBoxServiceVerbose(3, "Maximum served guest processes set to %u, running=%u\n",
1086 g_uControlProcsMaxKept, uProcsRunning);
1087
1088 int32_t iProcsLeft = (g_uControlProcsMaxKept - uProcsRunning - 1);
1089 if (iProcsLeft < 0)
1090 {
1091 VBoxServiceVerbose(3, "Maximum running guest processes reached (%u)\n",
1092 g_uControlProcsMaxKept);
1093 fLimitReached = true;
1094 }
1095 }
1096
1097 *pbAllowed = !fLimitReached;
1098
1099 int rc2 = RTCritSectLeave(&g_csControlThreads);
1100 if (RT_SUCCESS(rc))
1101 rc = rc2;
1102 }
1103
1104 return rc;
1105}
1106
1107
1108/**
1109 * Finds a (formerly) started process given by its PID and locks it. Must be unlocked
1110 * by the caller with VBoxServiceControlThreadUnlock().
1111 *
1112 * @return PVBOXSERVICECTRLTHREAD Process structure if found, otherwise NULL.
1113 * @param uPID PID to search for.
1114 */
1115PVBOXSERVICECTRLTHREAD GstCntlLockThread(uint32_t uPID)
1116{
1117 PVBOXSERVICECTRLTHREAD pThread = NULL;
1118 int rc = RTCritSectEnter(&g_csControlThreads);
1119 if (RT_SUCCESS(rc))
1120 {
1121 PVBOXSERVICECTRLTHREAD pThreadCur;
1122 RTListForEach(&g_lstControlThreadsActive, pThreadCur, VBOXSERVICECTRLTHREAD, Node)
1123 {
1124 if (pThreadCur->uPID == uPID)
1125 {
1126 rc = RTCritSectEnter(&pThreadCur->CritSect);
1127 if (RT_SUCCESS(rc))
1128 pThread = pThreadCur;
1129 break;
1130 }
1131 }
1132
1133 int rc2 = RTCritSectLeave(&g_csControlThreads);
1134 if (RT_SUCCESS(rc))
1135 rc = rc2;
1136 }
1137
1138 return pThread;
1139}
1140
1141
1142/**
1143 * Unlocks a previously locked guest process thread.
1144 *
1145 * @param pThread Thread to unlock.
1146 */
1147void GstCntlUnlockThread(const PVBOXSERVICECTRLTHREAD pThread)
1148{
1149 AssertPtr(pThread);
1150
1151 int rc = RTCritSectLeave(&pThread->CritSect);
1152 AssertRC(rc);
1153}
1154
1155
1156/**
1157 * Assigns a valid PID to a guest control thread and also checks if there already was
1158 * another (stale) guest process which was using that PID before and destroys it.
1159 *
1160 * @return IPRT status code.
1161 * @param pThread Thread to assign PID to.
1162 * @param uPID PID to assign to the specified guest control execution thread.
1163 */
1164int GstCntlAssignPID(PVBOXSERVICECTRLTHREAD pThread, uint32_t uPID)
1165{
1166 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
1167 AssertReturn(uPID, VERR_INVALID_PARAMETER);
1168
1169 int rc = RTCritSectEnter(&g_csControlThreads);
1170 if (RT_SUCCESS(rc))
1171 {
1172 /* Search old threads using the desired PID and shut them down completely -- it's
1173 * not used anymore. */
1174 PVBOXSERVICECTRLTHREAD pThreadCur;
1175 bool fTryAgain = false;
1176 do
1177 {
1178 RTListForEach(&g_lstControlThreadsActive, pThreadCur, VBOXSERVICECTRLTHREAD, Node)
1179 {
1180 if (pThreadCur->uPID == uPID)
1181 {
1182 Assert(pThreadCur != pThread); /* can't happen */
1183 uint32_t uTriedPID = uPID;
1184 uPID += 391939;
1185 VBoxServiceVerbose(2, "PID %RU32 was used before, trying again with %u ...\n",
1186 uTriedPID, uPID);
1187 fTryAgain = true;
1188 break;
1189 }
1190 }
1191 } while (fTryAgain);
1192
1193 /* Assign PID to current thread. */
1194 pThread->uPID = uPID;
1195
1196 rc = RTCritSectLeave(&g_csControlThreads);
1197 AssertRC(rc);
1198 }
1199
1200 return rc;
1201}
1202
1203
1204/**
1205 * The 'vminfo' service description.
1206 */
1207VBOXSERVICE g_Control =
1208{
1209 /* pszName. */
1210 "control",
1211 /* pszDescription. */
1212 "Host-driven Guest Control",
1213 /* pszUsage. */
1214#ifdef DEBUG
1215 " [--control-dump-stderr] [--control-dump-stdout]\n"
1216#endif
1217 " [--control-interval <ms>]\n"
1218 " [--control-procs-mem-std[in|out|err] <KB>]"
1219 ,
1220 /* pszOptions. */
1221#ifdef DEBUG
1222 " --control-dump-stderr Dumps all guest proccesses stderr data to the\n"
1223 " temporary directory.\n"
1224 " --control-dump-stdout Dumps all guest proccesses stdout data to the\n"
1225 " temporary directory.\n"
1226#endif
1227 " --control-interval Specifies the interval at which to check for\n"
1228 " new control commands. The default is 1000 ms.\n"
1229 ,
1230 /* methods */
1231 VBoxServiceControlPreInit,
1232 VBoxServiceControlOption,
1233 VBoxServiceControlInit,
1234 VBoxServiceControlWorker,
1235 VBoxServiceControlStop,
1236 VBoxServiceControlTerm
1237};
1238
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