VirtualBox

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

Last change on this file since 39906 was 39906, checked in by vboxsync, 13 years ago

GuestCtrl/VBoxService: Added active/inactive guest process thread handling, put process starting parameters into a struct, shortcut for handling 0-byte write requests.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.7 KB
Line 
1/* $Id: VBoxServiceControl.cpp 39906 2012-01-30 12:50:03Z vboxsync $ */
2/** @file
3 * VBoxServiceControl - Host-driven Guest Control.
4 */
5
6/*
7 * Copyright (C) 2011 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.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/getopt.h>
25#include <iprt/mem.h>
26#include <iprt/semaphore.h>
27#include <iprt/thread.h>
28#include <VBox/VBoxGuestLib.h>
29#include <VBox/HostServices/GuestControlSvc.h>
30#include "VBoxServiceInternal.h"
31#include "VBoxServiceUtils.h"
32
33using namespace guestControl;
34
35/*******************************************************************************
36* Global Variables *
37*******************************************************************************/
38/** The control interval (milliseconds). */
39static uint32_t g_cMsControlInterval = 0;
40/** The semaphore we're blocking our main control thread on. */
41static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI;
42/** The guest control service client ID. */
43static uint32_t g_GuestControlSvcClientID = 0;
44/** How many started guest processes are kept into memory for supplying
45 * information to the host. Default is 25 processes. If 0 is specified,
46 * the maximum number of processes is unlimited. */
47static uint32_t g_GuestControlProcsMaxKept = 25;
48/** List of active guest control threads (VBOXSERVICECTRLTHREAD). */
49static RTLISTANCHOR g_GuestControlThreadsActive;
50/** List of inactive guest control threads (VBOXSERVICECTRLTHREAD). */
51static RTLISTANCHOR g_GuestControlThreadsInactive;
52/** Critical section protecting g_GuestControlExecThreads. */
53static RTCRITSECT g_GuestControlThreadsCritSect;
54
55
56/*******************************************************************************
57* Internal Functions *
58*******************************************************************************/
59/** @todo Shorten "VBoxServiceControl" to "gstsvcCntl". */
60static int VBoxServiceControlReapThreads(void);
61static int VBoxServiceControlStartAllowed(bool *pbAllowed);
62static int VBoxServiceControlHandleCmdStartProc(uint32_t u32ClientId, uint32_t uNumParms);
63static int VBoxServiceControlHandleCmdSetInput(uint32_t u32ClientId, uint32_t uNumParms, size_t cbMaxBufSize);
64static int VBoxServiceControlHandleCmdGetOutput(uint32_t u32ClientId, uint32_t uNumParms);
65
66
67
68/** @copydoc VBOXSERVICE::pfnPreInit */
69static DECLCALLBACK(int) VBoxServiceControlPreInit(void)
70{
71#ifdef VBOX_WITH_GUEST_PROPS
72 /*
73 * Read the service options from the VM's guest properties.
74 * Note that these options can be overridden by the command line options later.
75 */
76 uint32_t uGuestPropSvcClientID;
77 int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
78 if (RT_FAILURE(rc))
79 {
80 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
81 {
82 VBoxServiceVerbose(0, "Control: Guest property service is not available, skipping\n");
83 rc = VINF_SUCCESS;
84 }
85 else
86 VBoxServiceError("Control: Failed to connect to the guest property service! Error: %Rrc\n", rc);
87 }
88 else
89 {
90 rc = VBoxServiceReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--control-procs-max-kept",
91 &g_GuestControlProcsMaxKept, 0, UINT32_MAX - 1);
92
93 VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
94 }
95
96 if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
97 rc = VINF_SUCCESS;
98 return rc;
99#else
100 /* Nothing to do here yet. */
101 return VINF_SUCCESS;
102#endif
103}
104
105
106/** @copydoc VBOXSERVICE::pfnOption */
107static DECLCALLBACK(int) VBoxServiceControlOption(const char **ppszShort, int argc, char **argv, int *pi)
108{
109 int rc = -1;
110 if (ppszShort)
111 /* no short options */;
112 else if (!strcmp(argv[*pi], "--control-interval"))
113 rc = VBoxServiceArgUInt32(argc, argv, "", pi,
114 &g_cMsControlInterval, 1, UINT32_MAX - 1);
115 else if (!strcmp(argv[*pi], "--control-procs-max-kept"))
116 rc = VBoxServiceArgUInt32(argc, argv, "", pi,
117 &g_GuestControlProcsMaxKept, 0, UINT32_MAX - 1);
118 return rc;
119}
120
121
122/** @copydoc VBOXSERVICE::pfnInit */
123static DECLCALLBACK(int) VBoxServiceControlInit(void)
124{
125 /*
126 * If not specified, find the right interval default.
127 * Then create the event sem to block on.
128 */
129 if (!g_cMsControlInterval)
130 g_cMsControlInterval = 1000;
131
132 int rc = RTSemEventMultiCreate(&g_hControlEvent);
133 AssertRCReturn(rc, rc);
134
135 rc = VbglR3GuestCtrlConnect(&g_GuestControlSvcClientID);
136 if (RT_SUCCESS(rc))
137 {
138 VBoxServiceVerbose(3, "Control: Service client ID: %#x\n", g_GuestControlSvcClientID);
139
140 /* Init thread lists. */
141 RTListInit(&g_GuestControlThreadsActive);
142 RTListInit(&g_GuestControlThreadsInactive);
143
144 /* Init critical section for protecting the thread lists. */
145 rc = RTCritSectInit(&g_GuestControlThreadsCritSect);
146 AssertRC(rc);
147 }
148 else
149 {
150 /* If the service was not found, we disable this service without
151 causing VBoxService to fail. */
152 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
153 {
154 VBoxServiceVerbose(0, "Control: Guest control service is not available\n");
155 rc = VERR_SERVICE_DISABLED;
156 }
157 else
158 VBoxServiceError("Control: Failed to connect to the guest control service! Error: %Rrc\n", rc);
159 RTSemEventMultiDestroy(g_hControlEvent);
160 g_hControlEvent = NIL_RTSEMEVENTMULTI;
161 }
162 return rc;
163}
164
165
166/** @copydoc VBOXSERVICE::pfnWorker */
167DECLCALLBACK(int) VBoxServiceControlWorker(bool volatile *pfShutdown)
168{
169 /*
170 * Tell the control thread that it can continue
171 * spawning services.
172 */
173 RTThreadUserSignal(RTThreadSelf());
174 Assert(g_GuestControlSvcClientID > 0);
175
176 int rc = VINF_SUCCESS;
177
178 /*
179 * Execution loop.
180 *
181 * @todo
182 */
183 for (;;)
184 {
185 VBoxServiceVerbose(3, "Control: Waiting for host msg ...\n");
186 uint32_t uMsg;
187 uint32_t cParms;
188 rc = VbglR3GuestCtrlWaitForHostMsg(g_GuestControlSvcClientID, &uMsg, &cParms);
189 if (rc == VERR_TOO_MUCH_DATA)
190 {
191 VBoxServiceVerbose(4, "Control: Message requires %ld parameters, but only 2 supplied -- retrying request (no error!)...\n", cParms);
192 rc = VINF_SUCCESS; /* Try to get "real" message in next block below. */
193 }
194 else if (RT_FAILURE(rc))
195 VBoxServiceVerbose(3, "Control: Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
196 if (RT_SUCCESS(rc))
197 {
198 VBoxServiceVerbose(3, "Control: Msg=%u (%u parms) retrieved\n", uMsg, cParms);
199 switch (uMsg)
200 {
201 case HOST_CANCEL_PENDING_WAITS:
202 VBoxServiceVerbose(3, "Control: Host asked us to quit ...\n");
203 break;
204
205 case HOST_EXEC_CMD:
206 rc = VBoxServiceControlHandleCmdStartProc(g_GuestControlSvcClientID, cParms);
207 break;
208
209 case HOST_EXEC_SET_INPUT:
210 /** @todo Make buffer size configurable via guest properties/argv! */
211 rc = VBoxServiceControlHandleCmdSetInput(g_GuestControlSvcClientID, cParms, _1M /* Buffer size */);
212 break;
213
214 case HOST_EXEC_GET_OUTPUT:
215 rc = VBoxServiceControlHandleCmdGetOutput(g_GuestControlSvcClientID, cParms);
216 break;
217
218 default:
219 VBoxServiceVerbose(3, "Control: Unsupported message from host! Msg=%u\n", uMsg);
220 /* Don't terminate here; just wait for the next message. */
221 break;
222 }
223 }
224
225 /* Do we need to shutdown? */
226 if ( *pfShutdown
227 || uMsg == HOST_CANCEL_PENDING_WAITS)
228 {
229 rc = VINF_SUCCESS;
230 break;
231 }
232
233 /* Let's sleep for a bit and let others run ... */
234 RTThreadYield();
235 }
236
237 return rc;
238}
239
240
241/**
242 * Handles starting processes on the guest.
243 *
244 * @returns IPRT status code.
245 * @param uClientID The HGCM client session ID.
246 * @param cParms The number of parameters the host is offering.
247 */
248static int VBoxServiceControlHandleCmdStartProc(uint32_t uClientID, uint32_t cParms)
249{
250 uint32_t uContextID = 0;
251
252 int rc;
253 bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */
254 if (cParms == 11)
255 {
256 VBOXSERVICECTRLPROCESS proc;
257 RT_ZERO(proc);
258
259 rc = VbglR3GuestCtrlExecGetHostCmdExec(uClientID,
260 cParms,
261 &uContextID,
262 /* Command */
263 proc.szCmd, sizeof(proc.szCmd),
264 /* Flags */
265 &proc.uFlags,
266 /* Arguments */
267 proc.szArgs, sizeof(proc.szArgs), &proc.uNumArgs,
268 /* Environment */
269 proc.szEnv, &proc.cbEnv, &proc.uNumEnvVars,
270 /* Credentials */
271 proc.szUser, sizeof(proc.szUser),
272 proc.szPassword, sizeof(proc.szPassword),
273 /* Timelimit */
274 &proc.uTimeLimitMS);
275 if (RT_SUCCESS(rc))
276 {
277 VBoxServiceVerbose(3, "Control: Request to start process szCmd=%s, uFlags=0x%x, szArgs=%s, szEnv=%s, szUser=%s, uTimeout=%u\n",
278 proc.szCmd, proc.uFlags,
279 proc.uNumArgs ? proc.szArgs : "<None>",
280 proc.uNumEnvVars ? proc.szEnv : "<None>",
281 proc.szUser, proc.uTimeLimitMS);
282
283 rc = VBoxServiceControlReapThreads();
284 if (RT_FAILURE(rc))
285 VBoxServiceError("Control: Reaping stopped processes failed with rc=%Rrc\n", rc);
286 /* Keep going. */
287
288 rc = VBoxServiceControlStartAllowed(&fStartAllowed);
289 if (RT_SUCCESS(rc))
290 {
291 if (fStartAllowed)
292 {
293 rc = VBoxServiceControlThreadStart(uContextID, &proc);
294 }
295 else
296 rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */
297 }
298 }
299 }
300 else
301 rc = VERR_INVALID_PARAMETER; /* Incorrect number of parameters. */
302
303 /* In case of an error we need to notify the host to not wait forever for our response. */
304 if (RT_FAILURE(rc))
305 {
306 VBoxServiceError("Control: Starting process failed with rc=%Rrc\n", rc);
307
308 int rc2 = VbglR3GuestCtrlExecReportStatus(uClientID, uContextID, 0 /* PID, invalid. */,
309 PROC_STS_ERROR, rc,
310 NULL /* pvData */, 0 /* cbData */);
311 if (RT_FAILURE(rc2))
312 {
313 VBoxServiceError("Control: Error sending start process status to host, rc=%Rrc\n", rc2);
314 if (RT_SUCCESS(rc))
315 rc = rc2;
316 }
317 }
318
319 return rc;
320}
321
322
323/**
324 * Gets output from stdout/stderr of a specified guest process.
325 *
326 * @return IPRT status code.
327 * @param uPID PID of process to retrieve the output from.
328 * @param uHandleId Stream ID (stdout = 0, stderr = 2) to get the output from.
329 * @param uTimeout Timeout (in ms) to wait for output becoming available.
330 * @param pvBuf Pointer to a pre-allocated buffer to store the output.
331 * @param cbBuf Size (in bytes) of the pre-allocated buffer.
332 * @param pcbRead Pointer to number of bytes read. Optional.
333 */
334int VBoxServiceControlExecGetOutput(uint32_t uPID, uint32_t uCID,
335 uint32_t uHandleId, uint32_t uTimeout,
336 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
337{
338 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
339 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
340 /* pcbRead is optional. */
341
342 int rc = VINF_SUCCESS;
343 VBOXSERVICECTRLREQUESTTYPE reqType;
344 switch (uHandleId)
345 {
346 case OUTPUT_HANDLE_ID_STDERR:
347 reqType = VBOXSERVICECTRLREQUEST_STDERR_READ;
348 break;
349
350 case OUTPUT_HANDLE_ID_STDOUT:
351 case OUTPUT_HANDLE_ID_STDOUT_DEPRECATED:
352 reqType = VBOXSERVICECTRLREQUEST_STDOUT_READ;
353 break;
354
355 default:
356 rc = VERR_INVALID_PARAMETER;
357 break;
358 }
359
360 PVBOXSERVICECTRLREQUEST pRequest;
361 if (RT_SUCCESS(rc))
362 {
363 rc = VBoxServiceControlThreadRequestAllocEx(&pRequest, reqType,
364 pvBuf, cbBuf, uCID);
365 if (RT_SUCCESS(rc))
366 rc = VBoxServiceControlThreadPerform(uPID, pRequest);
367
368 if (RT_SUCCESS(rc))
369 {
370 if (pcbRead)
371 *pcbRead = pRequest->cbData;
372 }
373
374 VBoxServiceControlThreadRequestFree(pRequest);
375 }
376
377 return rc;
378}
379
380
381/**
382 * Sets the specified guest thread to a certain list.
383 *
384 * @return IPRT status code.
385 * @param enmList List to move thread to.
386 * @param pThread Thread to set inactive.
387 */
388int VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLISTTYPE enmList,
389 PVBOXSERVICECTRLTHREAD pThread)
390{
391 AssertReturn(enmList > VBOXSERVICECTRLTHREADLIST_UNKNOWN, VERR_INVALID_PARAMETER);
392 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
393
394 int rc = RTCritSectEnter(&g_GuestControlThreadsCritSect);
395 if (RT_SUCCESS(rc))
396 {
397 VBoxServiceVerbose(3, "Control: Setting thread (PID %u) inactive\n",
398 pThread->uPID);
399
400 PRTLISTANCHOR pAnchor = NULL;
401 switch (enmList)
402 {
403 case VBOXSERVICECTRLTHREADLIST_STOPPED:
404 pAnchor = &g_GuestControlThreadsInactive;
405 break;
406
407 case VBOXSERVICECTRLTHREADLIST_RUNNING:
408 pAnchor = &g_GuestControlThreadsActive;
409 break;
410
411 default:
412 AssertMsgFailed(("Unknown list type: %u", enmList));
413 break;
414 }
415
416 if (!pAnchor)
417 rc = VERR_INVALID_PARAMETER;
418
419 if (RT_SUCCESS(rc))
420 {
421 if (pThread->pAnchor != NULL)
422 {
423 /* If thread was assigned to a list before,
424 * remove the thread from the old list first. */
425 /* rc = */ RTListNodeRemove(&pThread->Node);
426 }
427
428 /* Add thread to desired list. */
429 /* rc = */ RTListAppend(pAnchor, &pThread->Node);
430 pThread->pAnchor = pAnchor;
431 }
432
433 int rc2 = RTCritSectLeave(&g_GuestControlThreadsCritSect);
434 if (RT_SUCCESS(rc))
435 rc = rc2;
436 }
437
438 return VINF_SUCCESS;
439}
440
441
442/**
443 * Injects input to a specified running process.
444 *
445 * @return IPRT status code.
446 * @param uPID PID of process to set the input for.
447 * @param fPendingClose Flag indicating whether this is the last input block sent to the process.
448 * @param pvBuf Pointer to a buffer containing the actual input data.
449 * @param cbBuf Size (in bytes) of the input buffer data.
450 * @param pcbWritten Pointer to number of bytes written to the process. Optional.
451 */
452int VBoxServiceControlSetInput(uint32_t uPID, uint32_t uCID,
453 bool fPendingClose,
454 void *pvBuf, uint32_t cbBuf,
455 uint32_t *pcbWritten)
456{
457 /* pvBuf is optional. */
458 /* cbBuf is optional. */
459 /* pcbWritten is optional. */
460
461 PVBOXSERVICECTRLREQUEST pRequest;
462 int rc = VBoxServiceControlThreadRequestAllocEx(&pRequest,
463 fPendingClose
464 ? VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF
465 : VBOXSERVICECTRLREQUEST_STDIN_WRITE,
466 pvBuf, cbBuf, uCID);
467 if (RT_SUCCESS(rc))
468 {
469 rc = VBoxServiceControlThreadPerform(uPID, pRequest);
470 if (RT_SUCCESS(rc))
471 {
472 if (pcbWritten)
473 *pcbWritten = pRequest->cbData;
474 }
475
476 VBoxServiceControlThreadRequestFree(pRequest);
477 }
478
479 return rc;
480}
481
482
483/**
484 * Handles input for a started process by copying the received data into its
485 * stdin pipe.
486 *
487 * @returns IPRT status code.
488 * @param idClient The HGCM client session ID.
489 * @param cParms The number of parameters the host is
490 * offering.
491 * @param cMaxBufSize The maximum buffer size for retrieving the input data.
492 */
493static int VBoxServiceControlHandleCmdSetInput(uint32_t idClient, uint32_t cParms, size_t cbMaxBufSize)
494{
495 uint32_t uContextID;
496 uint32_t uPID;
497 uint32_t uFlags;
498 uint32_t cbSize;
499
500 AssertReturn(RT_IS_POWER_OF_TWO(cbMaxBufSize), VERR_INVALID_PARAMETER);
501 uint8_t *pabBuffer = (uint8_t*)RTMemAlloc(cbMaxBufSize);
502 AssertPtrReturn(pabBuffer, VERR_NO_MEMORY);
503
504 uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */
505 uint32_t cbWritten = 0; /* Number of bytes written to the guest. */
506
507 /*
508 * Ask the host for the input data.
509 */
510 int rc = VbglR3GuestCtrlExecGetHostCmdInput(idClient, cParms,
511 &uContextID, &uPID, &uFlags,
512 pabBuffer, cbMaxBufSize, &cbSize);
513 if (RT_FAILURE(rc))
514 {
515 VBoxServiceError("Control: [PID %u]: Failed to retrieve exec input command! Error: %Rrc\n",
516 uPID, rc);
517 }
518 else if (cbSize > cbMaxBufSize)
519 {
520 VBoxServiceError("Control: [PID %u]: Too much input received! cbSize=%u, cbMaxBufSize=%u\n",
521 uPID, cbSize, cbMaxBufSize);
522 rc = VERR_INVALID_PARAMETER;
523 }
524 else
525 {
526 /*
527 * Is this the last input block we need to deliver? Then let the pipe know ...
528 */
529 bool fPendingClose = false;
530 if (uFlags & INPUT_FLAG_EOF)
531 {
532 fPendingClose = true;
533 VBoxServiceVerbose(4, "Control: [PID %u]: Got last input block of size %u ...\n",
534 uPID, cbSize);
535 }
536
537 rc = VBoxServiceControlSetInput(uPID, uContextID, fPendingClose, pabBuffer,
538 cbSize, &cbWritten);
539 VBoxServiceVerbose(4, "Control: [PID %u]: Written input, CID=%u, rc=%Rrc, uFlags=0x%x, fPendingClose=%d, cbSize=%u, cbWritten=%u\n",
540 uPID, uContextID, rc, uFlags, fPendingClose, cbSize, cbWritten);
541 if (RT_SUCCESS(rc))
542 {
543 if (cbWritten || !cbSize) /* Did we write something or was there anything to write at all? */
544 {
545 uStatus = INPUT_STS_WRITTEN;
546 uFlags = 0;
547 }
548 }
549 else
550 {
551 if (rc == VERR_BAD_PIPE)
552 uStatus = INPUT_STS_TERMINATED;
553 else if (rc == VERR_BUFFER_OVERFLOW)
554 uStatus = INPUT_STS_OVERFLOW;
555 }
556 }
557 RTMemFree(pabBuffer);
558
559 /*
560 * If there was an error and we did not set the host status
561 * yet, then do it now.
562 */
563 if ( RT_FAILURE(rc)
564 && uStatus == INPUT_STS_UNDEFINED)
565 {
566 uStatus = INPUT_STS_ERROR;
567 uFlags = rc;
568 }
569 Assert(uStatus > INPUT_STS_UNDEFINED);
570
571 VBoxServiceVerbose(3, "Control: [PID %u]: Input processed, CID=%u, uStatus=%u, uFlags=0x%x, cbWritten=%u\n",
572 uPID, uContextID, uStatus, uFlags, cbWritten);
573
574 /* Note: Since the context ID is unique the request *has* to be completed here,
575 * regardless whether we got data or not! Otherwise the progress object
576 * on the host never will get completed! */
577 rc = VbglR3GuestCtrlExecReportStatusIn(idClient, uContextID, uPID,
578 uStatus, uFlags, (uint32_t)cbWritten);
579
580 if (RT_FAILURE(rc))
581 VBoxServiceError("Control: [PID %u]: Failed to report input status! Error: %Rrc\n",
582 uPID, rc);
583 return rc;
584}
585
586
587/**
588 * Handles the guest control output command.
589 *
590 * @return IPRT status code.
591 * @param idClient The HGCM client session ID.
592 * @param cParms The number of parameters the host is offering.
593 */
594static int VBoxServiceControlHandleCmdGetOutput(uint32_t idClient, uint32_t cParms)
595{
596 uint32_t uContextID;
597 uint32_t uPID;
598 uint32_t uHandleID;
599 uint32_t uFlags;
600
601 int rc = VbglR3GuestCtrlExecGetHostCmdOutput(idClient, cParms,
602 &uContextID, &uPID, &uHandleID, &uFlags);
603 if (RT_SUCCESS(rc))
604 {
605 uint8_t *pBuf = (uint8_t*)RTMemAlloc(_64K);
606 if (pBuf)
607 {
608 uint32_t cbRead = 0;
609 rc = VBoxServiceControlExecGetOutput(uPID, uContextID, uHandleID, RT_INDEFINITE_WAIT /* Timeout */,
610 pBuf, _64K /* cbSize */, &cbRead);
611 VBoxServiceVerbose(3, "Control: [PID %u]: Got output, rc=%Rrc, CID=%u, cbRead=%u, uHandle=%u, uFlags=%u\n",
612 uPID, rc, uContextID, cbRead, uHandleID, uFlags);
613
614 /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary
615 * data which the host needs to work with -- so just pass through all data unfiltered! */
616
617 /* Note: Since the context ID is unique the request *has* to be completed here,
618 * regardless whether we got data or not! Otherwise the progress object
619 * on the host never will get completed! */
620 int rc2 = VbglR3GuestCtrlExecSendOut(idClient, uContextID, uPID, uHandleID, uFlags,
621 pBuf, cbRead);
622 if (RT_SUCCESS(rc))
623 rc = rc2;
624 else if (rc == VERR_NOT_FOUND) /* It's not critical if guest process (PID) is not found. */
625 rc = VINF_SUCCESS;
626
627 RTMemFree(pBuf);
628 }
629 else
630 rc = VERR_NO_MEMORY;
631 }
632
633 if (RT_FAILURE(rc))
634 VBoxServiceError("Control: [PID %u]: Error handling output command! Error: %Rrc\n",
635 uPID, rc);
636 return rc;
637}
638
639
640/** @copydoc VBOXSERVICE::pfnStop */
641static DECLCALLBACK(void) VBoxServiceControlStop(void)
642{
643 VBoxServiceVerbose(3, "Control: Stopping ...\n");
644
645 /** @todo Later, figure what to do if we're in RTProcWait(). It's a very
646 * annoying call since doesn't support timeouts in the posix world. */
647 if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
648 RTSemEventMultiSignal(g_hControlEvent);
649
650 /*
651 * Ask the host service to cancel all pending requests so that we can
652 * shutdown properly here.
653 */
654 if (g_GuestControlSvcClientID)
655 {
656 VBoxServiceVerbose(3, "Control: Cancelling pending waits (client ID=%u) ...\n",
657 g_GuestControlSvcClientID);
658
659 int rc = VbglR3GuestCtrlCancelPendingWaits(g_GuestControlSvcClientID);
660 if (RT_FAILURE(rc))
661 VBoxServiceError("Control: Cancelling pending waits failed; rc=%Rrc\n", rc);
662 }
663}
664
665
666/**
667 * Reaps all inactive guest process threads.
668 *
669 * @return IPRT status code.
670 */
671static int VBoxServiceControlReapThreads(void)
672{
673 int rc = RTCritSectEnter(&g_GuestControlThreadsCritSect);
674 if (RT_SUCCESS(rc))
675 {
676 PVBOXSERVICECTRLTHREAD pThread =
677 RTListGetFirst(&g_GuestControlThreadsInactive, VBOXSERVICECTRLTHREAD, Node);
678 while (pThread)
679 {
680 PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLTHREAD, Node);
681 bool fLast = RTListNodeIsLast(&g_GuestControlThreadsInactive, &pThread->Node);
682
683 int rc2 = VBoxServiceControlThreadWait(pThread, 30 * 1000 /* 30 seconds max. */);
684 if (RT_SUCCESS(rc2))
685 {
686 RTListNodeRemove(&pThread->Node);
687
688 rc2 = VBoxServiceControlThreadFree(pThread);
689 if (RT_FAILURE(rc2))
690 {
691 VBoxServiceError("Control: Stopping guest process thread failed with rc=%Rrc\n", rc2);
692 if (RT_SUCCESS(rc)) /* Keep original failure. */
693 rc = rc2;
694 }
695 }
696 else
697 VBoxServiceError("Control: Waiting on guest process thread failed with rc=%Rrc\n", rc2);
698 /* Keep going. */
699
700 if (fLast)
701 break;
702
703 pThread = pNext;
704 }
705
706 int rc2 = RTCritSectLeave(&g_GuestControlThreadsCritSect);
707 if (RT_SUCCESS(rc))
708 rc = rc2;
709 }
710
711 VBoxServiceVerbose(4, "Control: Reaping threads returned with rc=%Rrc\n", rc);
712 return rc;
713}
714
715
716/**
717 * Destroys all guest process threads which are still active.
718 */
719static void VBoxServiceControlShutdown(void)
720{
721 VBoxServiceVerbose(2, "Control: Shutting down ...\n");
722
723 /* Signal all threads in the active list that we want to shutdown. */
724 PVBOXSERVICECTRLTHREAD pThread;
725 RTListForEach(&g_GuestControlThreadsActive, pThread, VBOXSERVICECTRLTHREAD, Node)
726 VBoxServiceControlThreadStop(pThread);
727
728 /* Wait for all active threads to shutdown and destroy the active thread list. */
729 pThread = RTListGetFirst(&g_GuestControlThreadsActive, VBOXSERVICECTRLTHREAD, Node);
730 while (pThread)
731 {
732 PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLTHREAD, Node);
733 bool fLast = RTListNodeIsLast(&g_GuestControlThreadsActive, &pThread->Node);
734
735 int rc2 = VBoxServiceControlThreadWait(pThread,
736 30 * 1000 /* Wait 30 seconds max. */);
737 if (RT_FAILURE(rc2))
738 VBoxServiceError("Control: Guest process thread failed to stop; rc=%Rrc\n", rc2);
739
740 if (fLast)
741 break;
742
743 pThread = pNext;
744 }
745
746 int rc2 = VBoxServiceControlReapThreads();
747 if (RT_FAILURE(rc2))
748 VBoxServiceError("Control: Reaping inactive threads failed with rc=%Rrc\n", rc2);
749
750 AssertMsg(RTListIsEmpty(&g_GuestControlThreadsActive),
751 ("Guest process active thread list still contains entries when it should not\n"));
752 AssertMsg(RTListIsEmpty(&g_GuestControlThreadsInactive),
753 ("Guest process inactive thread list still contains entries when it should not\n"));
754
755 /* Destroy critical section. */
756 RTCritSectDelete(&g_GuestControlThreadsCritSect);
757
758 VBoxServiceVerbose(2, "Control: Shutting down complete\n");
759}
760
761
762/** @copydoc VBOXSERVICE::pfnTerm */
763static DECLCALLBACK(void) VBoxServiceControlTerm(void)
764{
765 VBoxServiceVerbose(3, "Control: Terminating ...\n");
766
767 VBoxServiceControlShutdown();
768
769 VBoxServiceVerbose(3, "Control: Disconnecting client ID=%u ...\n",
770 g_GuestControlSvcClientID);
771 VbglR3GuestCtrlDisconnect(g_GuestControlSvcClientID);
772 g_GuestControlSvcClientID = 0;
773
774 if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
775 {
776 RTSemEventMultiDestroy(g_hControlEvent);
777 g_hControlEvent = NIL_RTSEMEVENTMULTI;
778 }
779}
780
781
782/**
783 * Determines whether starting a new guest process according to the
784 * maximum number of concurrent guest processes defined is allowed or not.
785 *
786 * @return IPRT status code.
787 * @param pbAllowed True if starting (another) guest process
788 * is allowed, false if not.
789 */
790static int VBoxServiceControlStartAllowed(bool *pbAllowed)
791{
792 AssertPtrReturn(pbAllowed, VERR_INVALID_POINTER);
793
794 int rc = RTCritSectEnter(&g_GuestControlThreadsCritSect);
795 if (RT_SUCCESS(rc))
796 {
797 /*
798 * Check if we're respecting our memory policy by checking
799 * how many guest processes are started and served already.
800 */
801 bool fLimitReached = false;
802 if (g_GuestControlProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */
803 {
804 uint32_t uProcsRunning = 0;
805 PVBOXSERVICECTRLTHREAD pThread;
806 RTListForEach(&g_GuestControlThreadsActive, pThread, VBOXSERVICECTRLTHREAD, Node)
807 uProcsRunning++;
808
809 VBoxServiceVerbose(3, "Control: Maximum served guest processes set to %u, running=%u\n",
810 g_GuestControlProcsMaxKept, uProcsRunning);
811
812 int32_t iProcsLeft = (g_GuestControlProcsMaxKept - uProcsRunning - 1);
813 if (iProcsLeft < 0)
814 {
815 VBoxServiceVerbose(3, "Control: Maximum running guest processes reached (%u)\n",
816 g_GuestControlProcsMaxKept);
817 fLimitReached = true;
818 }
819 }
820
821 *pbAllowed = !fLimitReached;
822
823 int rc2 = RTCritSectLeave(&g_GuestControlThreadsCritSect);
824 if (RT_SUCCESS(rc))
825 rc = rc2;
826 }
827
828 return rc;
829}
830
831
832/**
833 * Finds a (formerly) started process given by its PID and locks it. Must be unlocked
834 * by the caller with VBoxServiceControlThreadUnlock().
835 *
836 * @return PVBOXSERVICECTRLTHREAD Process structure if found, otherwise NULL.
837 * @param uPID PID to search for.
838 */
839PVBOXSERVICECTRLTHREAD VBoxServiceControlLockThread(uint32_t uPID)
840{
841 PVBOXSERVICECTRLTHREAD pThread = NULL;
842 int rc = RTCritSectEnter(&g_GuestControlThreadsCritSect);
843 if (RT_SUCCESS(rc))
844 {
845 PVBOXSERVICECTRLTHREAD pThreadCur;
846 RTListForEach(&g_GuestControlThreadsActive, pThreadCur, VBOXSERVICECTRLTHREAD, Node)
847 {
848 if (pThreadCur->uPID == uPID)
849 {
850 rc = RTCritSectEnter(&pThreadCur->CritSect);
851 if (RT_SUCCESS(rc))
852 pThread = pThreadCur;
853 break;
854 }
855 }
856
857 int rc2 = RTCritSectLeave(&g_GuestControlThreadsCritSect);
858 if (RT_SUCCESS(rc))
859 rc = rc2;
860 }
861
862 return pThread;
863}
864
865
866/**
867 * Unlocks a previously locked guest process thread.
868 *
869 * @param pThread Thread to unlock.
870 */
871void VBoxServiceControlUnlockThread(const PVBOXSERVICECTRLTHREAD pThread)
872{
873 AssertPtr(pThread);
874
875 int rc = RTCritSectLeave(&pThread->CritSect);
876 AssertRC(rc);
877}
878
879
880/**
881 * Assigns a valid PID to a guest control thread and also checks if there already was
882 * another (stale) guest process which was using that PID before and destroys it.
883 *
884 * @return IPRT status code.
885 * @param pThread Thread to assign PID to.
886 * @param uPID PID to assign to the specified guest control execution thread.
887 */
888int VBoxServiceControlAssignPID(PVBOXSERVICECTRLTHREAD pThread, uint32_t uPID)
889{
890 AssertPtrReturn(pThread, VERR_INVALID_POINTER);
891 AssertReturn(uPID, VERR_INVALID_PARAMETER);
892
893 int rc = RTCritSectEnter(&g_GuestControlThreadsCritSect);
894 if (RT_SUCCESS(rc))
895 {
896 /* Search old threads using the desired PID and shut them down completely -- it's
897 * not used anymore. */
898 PVBOXSERVICECTRLTHREAD pThreadCur;
899 bool fTryAgain = false;
900 do
901 {
902 RTListForEach(&g_GuestControlThreadsActive, pThreadCur, VBOXSERVICECTRLTHREAD, Node)
903 {
904 if (pThreadCur->uPID == uPID)
905 {
906 Assert(pThreadCur != pThread); /* can't happen */
907 uint32_t uTriedPID = uPID;
908 uPID += 391939;
909 VBoxServiceVerbose(2, "ControlThread: PID %u was used before, trying again with %u ...\n",
910 uTriedPID, uPID);
911 fTryAgain = true;
912 break;
913 }
914 }
915 } while (fTryAgain);
916
917 /* Assign PID to current thread. */
918 pThread->uPID = uPID;
919
920 rc = RTCritSectLeave(&g_GuestControlThreadsCritSect);
921 AssertRC(rc);
922 }
923
924 return rc;
925}
926
927
928/**
929 * The 'vminfo' service description.
930 */
931VBOXSERVICE g_Control =
932{
933 /* pszName. */
934 "control",
935 /* pszDescription. */
936 "Host-driven Guest Control",
937 /* pszUsage. */
938 " [--control-interval <ms>] [--control-procs-max-kept <x>]\n"
939 " [--control-procs-mem-std[in|out|err] <KB>]"
940 ,
941 /* pszOptions. */
942 " --control-interval Specifies the interval at which to check for\n"
943 " new control commands. The default is 1000 ms.\n"
944 " --control-procs-max-kept\n"
945 " Specifies how many started guest processes are\n"
946 " kept into memory to work with. Default is 25.\n"
947 ,
948 /* methods */
949 VBoxServiceControlPreInit,
950 VBoxServiceControlOption,
951 VBoxServiceControlInit,
952 VBoxServiceControlWorker,
953 VBoxServiceControlStop,
954 VBoxServiceControlTerm
955};
956
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