VirtualBox

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

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

GuestCtrl: More code for guest session infrastructure handling (untested, work in progress). Added IGuestSession.status for (later) asynchronous handling.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette