VirtualBox

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

Last change on this file since 75824 was 75824, checked in by vboxsync, 6 years ago

VBoxGuestControl: Optimizing message handling - part 4. bugref:9313

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.4 KB
Line 
1/* $Id: VBoxServiceControl.cpp 75824 2018-11-29 22:12:53Z vboxsync $ */
2/** @file
3 * VBoxServiceControl - Host-driven Guest Control.
4 */
5
6/*
7 * Copyright (C) 2012-2017 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/** @page pg_vgsvc_gstctrl VBoxService - Guest Control
19 *
20 * The Guest Control subservice helps implementing the IGuest APIs.
21 *
22 * The communication between this service (and its children) and IGuest goes
23 * over the HGCM GuestControl service.
24 *
25 * The IGuest APIs provides means to manipulate (control) files, directories,
26 * symbolic links and processes within the guest. Most of these means requires
27 * credentials of a guest OS user to operate, though some restricted ones
28 * operates directly as the VBoxService user (root / system service account).
29 *
30 * The current design is that a subprocess is spawned for handling operations as
31 * a given user. This process is represented as IGuestSession in the API. The
32 * subprocess will be spawned as the given use, giving up the privileges the
33 * parent subservice had.
34 *
35 * It will try handle as many of the operations directly from within the
36 * subprocess, but for more complicated things (or things that haven't yet been
37 * converted), it will spawn a helper process that does the actual work.
38 *
39 * These helpers are the typically modeled on similar unix core utilities, like
40 * mkdir, rm, rmdir, cat and so on. The helper tools can also be launched
41 * directly from VBoxManage by the user by prepending the 'vbox_' prefix to the
42 * unix command.
43 *
44 */
45
46
47/*********************************************************************************************************************************
48* Header Files *
49*********************************************************************************************************************************/
50#include <iprt/asm.h>
51#include <iprt/assert.h>
52#include <iprt/env.h>
53#include <iprt/file.h>
54#include <iprt/getopt.h>
55#include <iprt/mem.h>
56#include <iprt/path.h>
57#include <iprt/process.h>
58#include <iprt/semaphore.h>
59#include <iprt/thread.h>
60#include <VBox/VBoxGuestLib.h>
61#include <VBox/HostServices/GuestControlSvc.h>
62#include "VBoxServiceInternal.h"
63#include "VBoxServiceControl.h"
64#include "VBoxServiceUtils.h"
65
66using namespace guestControl;
67
68
69/*********************************************************************************************************************************
70* Global Variables *
71*********************************************************************************************************************************/
72/** The control interval (milliseconds). */
73static uint32_t g_msControlInterval = 0;
74/** The semaphore we're blocking our main control thread on. */
75static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI;
76/** The VM session ID. Changes whenever the VM is restored or reset. */
77static uint64_t g_idControlSession;
78/** The guest control service client ID. */
79uint32_t g_idControlSvcClient = 0;
80#if 0 /** @todo process limit */
81/** How many started guest processes are kept into memory for supplying
82 * information to the host. Default is 256 processes. If 0 is specified,
83 * the maximum number of processes is unlimited. */
84static uint32_t g_uControlProcsMaxKept = 256;
85#endif
86/** List of guest control session threads (VBOXSERVICECTRLSESSIONTHREAD).
87 * A guest session thread represents a forked guest session process
88 * of VBoxService. */
89RTLISTANCHOR g_lstControlSessionThreads;
90/** The local session object used for handling all session-related stuff.
91 * When using the legacy guest control protocol (< 2), this session runs
92 * under behalf of the VBoxService main process. On newer protocol versions
93 * each session is a forked version of VBoxService using the appropriate
94 * user credentials for opening a guest session. These forked sessions then
95 * are kept in VBOXSERVICECTRLSESSIONTHREAD structures. */
96VBOXSERVICECTRLSESSION g_Session;
97/** Copy of VbglR3GuestCtrlSupportsOptimizations().*/
98bool g_fControlSupportsOptimizations = true;
99
100
101/*********************************************************************************************************************************
102* Internal Functions *
103*********************************************************************************************************************************/
104static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx);
105static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx);
106static void vgsvcGstCtrlShutdown(void);
107
108
109/**
110 * @interface_method_impl{VBOXSERVICE,pfnPreInit}
111 */
112static DECLCALLBACK(int) vgsvcGstCtrlPreInit(void)
113{
114 int rc;
115#ifdef VBOX_WITH_GUEST_PROPS
116 /*
117 * Read the service options from the VM's guest properties.
118 * Note that these options can be overridden by the command line options later.
119 */
120 uint32_t uGuestPropSvcClientID;
121 rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
122 if (RT_FAILURE(rc))
123 {
124 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
125 {
126 VGSvcVerbose(0, "Guest property service is not available, skipping\n");
127 rc = VINF_SUCCESS;
128 }
129 else
130 VGSvcError("Failed to connect to the guest property service, rc=%Rrc\n", rc);
131 }
132 else
133 VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
134
135 if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
136 rc = VINF_SUCCESS;
137#else
138 /* Nothing to do here yet. */
139 rc = VINF_SUCCESS;
140#endif
141
142 if (RT_SUCCESS(rc))
143 {
144 /* Init session object. */
145 rc = VGSvcGstCtrlSessionInit(&g_Session, 0 /* Flags */);
146 }
147
148 return rc;
149}
150
151
152/**
153 * @interface_method_impl{VBOXSERVICE,pfnOption}
154 */
155static DECLCALLBACK(int) vgsvcGstCtrlOption(const char **ppszShort, int argc, char **argv, int *pi)
156{
157 int rc = -1;
158 if (ppszShort)
159 /* no short options */;
160 else if (!strcmp(argv[*pi], "--control-interval"))
161 rc = VGSvcArgUInt32(argc, argv, "", pi,
162 &g_msControlInterval, 1, UINT32_MAX - 1);
163#ifdef DEBUG
164 else if (!strcmp(argv[*pi], "--control-dump-stdout"))
165 {
166 g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT;
167 rc = 0; /* Flag this command as parsed. */
168 }
169 else if (!strcmp(argv[*pi], "--control-dump-stderr"))
170 {
171 g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR;
172 rc = 0; /* Flag this command as parsed. */
173 }
174#endif
175 return rc;
176}
177
178
179/**
180 * @interface_method_impl{VBOXSERVICE,pfnInit}
181 */
182static DECLCALLBACK(int) vgsvcGstCtrlInit(void)
183{
184 /*
185 * If not specified, find the right interval default.
186 * Then create the event sem to block on.
187 */
188 if (!g_msControlInterval)
189 g_msControlInterval = 1000;
190
191 int rc = RTSemEventMultiCreate(&g_hControlEvent);
192 AssertRCReturn(rc, rc);
193
194 VbglR3GetSessionId(&g_idControlSession); /* The status code is ignored as this information is not available with VBox < 3.2.10. */
195
196 RTListInit(&g_lstControlSessionThreads);
197
198 /*
199 * Try connect to the host service and tell it we want to be master (if supported).
200 */
201 rc = VbglR3GuestCtrlConnect(&g_idControlSvcClient);
202 if (RT_SUCCESS(rc))
203 {
204 g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient);
205 if (g_fControlSupportsOptimizations)
206 rc = VbglR3GuestCtrlMakeMeMaster(g_idControlSvcClient);
207 if (RT_SUCCESS(rc))
208 {
209 VGSvcVerbose(3, "Guest control service client ID=%RU32%s\n",
210 g_idControlSvcClient, g_fControlSupportsOptimizations ? " w/ optimizations" : "");
211 return VINF_SUCCESS;
212 }
213 VGSvcError("Failed to become guest control master: %Rrc\n", rc);
214 VbglR3GuestCtrlDisconnect(g_idControlSvcClient);
215 }
216 else
217 {
218 /* If the service was not found, we disable this service without
219 causing VBoxService to fail. */
220 if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
221 {
222 VGSvcVerbose(0, "Guest control service is not available\n");
223 rc = VERR_SERVICE_DISABLED;
224 }
225 else
226 VGSvcError("Failed to connect to the guest control service! Error: %Rrc\n", rc);
227 }
228 RTSemEventMultiDestroy(g_hControlEvent);
229 g_hControlEvent = NIL_RTSEMEVENTMULTI;
230 g_idControlSvcClient = 0;
231 return rc;
232}
233
234
235/**
236 * @interface_method_impl{VBOXSERVICE,pfnWorker}
237 */
238static DECLCALLBACK(int) vgsvcGstCtrlWorker(bool volatile *pfShutdown)
239{
240 /*
241 * Tell the control thread that it can continue spawning services.
242 */
243 RTThreadUserSignal(RTThreadSelf());
244 Assert(g_idControlSvcClient > 0);
245
246 /* Allocate a scratch buffer for commands which also send
247 * payload data with them. */
248 uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */
249 AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), VERR_INVALID_PARAMETER);
250 uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf);
251 AssertReturn(pvScratchBuf, VERR_NO_MEMORY);
252
253 VBGLR3GUESTCTRLCMDCTX ctxHost = { g_idControlSvcClient, 0 /*idContext*/, 2 /*uProtocol*/, 0 /*cParms*/ };
254
255 int rc = VINF_SUCCESS; /* (shut up compiler warnings) */
256 int cRetrievalFailed = 0; /* Number of failed message retrievals in a row. */
257 while (!*pfShutdown)
258 {
259 VGSvcVerbose(3, "GstCtrl: Waiting for host msg ...\n");
260 uint32_t idMsg = 0;
261 uint32_t cParms = 0;
262 rc = VbglR3GuestCtrlMsgPeekWait(g_idControlSvcClient, &idMsg, &cParms);
263 if (RT_SUCCESS(rc))
264 {
265 cRetrievalFailed = 0; /* Reset failed retrieval count. */
266 VGSvcVerbose(4, "idMsg=%RU32 (%s) (%RU32 parms) retrieved\n", idMsg, GstCtrlHostFnName((eHostFn)idMsg), cParms);
267
268 /*
269 * Close all open guest sessions if the VM was restored (all context IDs,
270 * sessions, etc. are now invalid).
271 *
272 * Note! This will suck performance wise if we get a lot of messages thru here.
273 * What about the service returning a HOST_MSG_RESTORED message?
274 */
275 uint64_t idNewSession = g_idControlSession;
276 int rc2 = VbglR3GetSessionId(&idNewSession);
277 if ( RT_SUCCESS(rc2)
278 && idNewSession != g_idControlSession)
279 {
280 VGSvcVerbose(1, "The VM session ID changed (i.e. restored).\n");
281 g_idControlSession = idNewSession;
282 rc2 = VGSvcGstCtrlSessionClose(&g_Session);
283 AssertRC(rc2);
284 }
285
286 /*
287 * Handle the host message.
288 */
289 ctxHost.uNumParms = cParms;
290 switch (idMsg)
291 {
292 case HOST_CANCEL_PENDING_WAITS:
293 VGSvcVerbose(1, "We were asked to quit ...\n");
294 break;
295
296 case HOST_SESSION_CREATE:
297 rc = vgsvcGstCtrlHandleSessionOpen(&ctxHost);
298 break;
299
300 /* This message is also sent to the child session process (by the host). */
301 case HOST_SESSION_CLOSE:
302 rc = vgsvcGstCtrlHandleSessionClose(&ctxHost);
303 break;
304
305 default:
306 if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
307 {
308 rc = VbglR3GuestCtrlMsgSkip(g_idControlSvcClient, VERR_NOT_SUPPORTED, idMsg);
309 VGSvcVerbose(1, "Skipped unexpected message idMsg=%RU32 (%s), cParms=%RU32 (rc=%Rrc)\n",
310 idMsg, GstCtrlHostFnName((eHostFn)idMsg), cParms, rc);
311 }
312 else
313 {
314 rc = VbglR3GuestCtrlMsgSkipOld(g_idControlSvcClient);
315 VGSvcVerbose(3, "Skipped idMsg=%RU32, cParms=%RU32, rc=%Rrc\n", idMsg, cParms, rc);
316 }
317 break;
318 }
319
320 /* Do we need to shutdown? */
321 if (idMsg == HOST_CANCEL_PENDING_WAITS)
322 break;
323
324 /* Let's sleep for a bit and let others run ... */
325 RTThreadYield();
326 }
327 else
328 {
329 /* Note: VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
330 /** @todo r=bird: Above comment makes no sense. How can you get a timeout in a blocking HGCM call? */
331 VGSvcError("GstCtrl: Getting host message failed with %Rrc\n", rc);
332
333 /* Check for VM session change. */
334 uint64_t idNewSession = g_idControlSession;
335 int rc2 = VbglR3GetSessionId(&idNewSession);
336 if ( RT_SUCCESS(rc2)
337 && (idNewSession != g_idControlSession))
338 {
339 VGSvcVerbose(1, "GstCtrl: The VM session ID changed\n");
340 g_idControlSession = idNewSession;
341
342 /* Close all opened guest sessions -- all context IDs, sessions etc.
343 * are now invalid. */
344 rc2 = VGSvcGstCtrlSessionClose(&g_Session);
345 AssertRC(rc2);
346
347 /* Do a reconnect. */
348 VGSvcVerbose(1, "Reconnecting to HGCM service ...\n");
349 rc2 = VbglR3GuestCtrlConnect(&g_idControlSvcClient);
350 if (RT_SUCCESS(rc2))
351 {
352 VGSvcVerbose(3, "Guest control service client ID=%RU32\n", g_idControlSvcClient);
353 cRetrievalFailed = 0;
354 continue; /* Skip waiting. */
355 }
356 VGSvcError("Unable to re-connect to HGCM service, rc=%Rrc, bailing out\n", rc);
357 break;
358 }
359
360 if (rc == VERR_INTERRUPTED)
361 RTThreadYield(); /* To be on the safe side... */
362 else if (++cRetrievalFailed <= 16) /** @todo Make this configurable? */
363 RTThreadSleep(1000); /* Wait a bit before retrying. */
364 else
365 {
366 VGSvcError("Too many failed attempts in a row to get next message, bailing out\n");
367 break;
368 }
369 }
370 }
371
372 VGSvcVerbose(0, "Guest control service stopped\n");
373
374 /* Delete scratch buffer. */
375 if (pvScratchBuf)
376 RTMemFree(pvScratchBuf);
377
378 VGSvcVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc);
379 return rc;
380}
381
382
383static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx)
384{
385 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
386
387 /*
388 * Retrieve the message parameters.
389 */
390 VBOXSERVICECTRLSESSIONSTARTUPINFO ssInfo = { 0 };
391 int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx,
392 &ssInfo.uProtocol,
393 ssInfo.szUser, sizeof(ssInfo.szUser),
394 ssInfo.szPassword, sizeof(ssInfo.szPassword),
395 ssInfo.szDomain, sizeof(ssInfo.szDomain),
396 &ssInfo.fFlags, &ssInfo.uSessionID);
397 if (RT_SUCCESS(rc))
398 {
399 /*
400 * Flat out refuse to work with protocol v1 hosts.
401 */
402 if (ssInfo.uProtocol == 2)
403 {
404 pHostCtx->uProtocol = ssInfo.uProtocol;
405 VGSvcVerbose(3, "Client ID=%RU32 now is using protocol %RU32\n", pHostCtx->uClientID, pHostCtx->uProtocol);
406
407/** @todo Someone explain why this code isn't in this file too? v1 support? */
408 rc = VGSvcGstCtrlSessionThreadCreate(&g_lstControlSessionThreads, &ssInfo, NULL /* ppSessionThread */);
409 /* Report failures to the host (successes are taken care of by the session thread). */
410 }
411 else
412 {
413 VGSvcError("The host wants to use protocol v%u, we only support v2!\n", ssInfo.uProtocol);
414 rc = VERR_VERSION_MISMATCH;
415 }
416 if (RT_FAILURE(rc))
417 {
418 int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, GUEST_SESSION_NOTIFYTYPE_ERROR, rc);
419 if (RT_FAILURE(rc2))
420 VGSvcError("Reporting session error status on open failed with rc=%Rrc\n", rc2);
421 }
422 }
423 else
424 {
425 VGSvcError("Error fetching parameters for opening guest session: %Rrc\n", rc);
426 VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
427 }
428 VGSvcVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc);
429 return rc;
430}
431
432
433static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx)
434{
435 AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
436
437 uint32_t idSession;
438 uint32_t fFlags;
439 int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &fFlags, &idSession);
440 if (RT_SUCCESS(rc))
441 {
442 rc = VERR_NOT_FOUND;
443
444 PVBOXSERVICECTRLSESSIONTHREAD pThread;
445 RTListForEach(&g_lstControlSessionThreads, pThread, VBOXSERVICECTRLSESSIONTHREAD, Node)
446 {
447 if (pThread->StartupInfo.uSessionID == idSession)
448 {
449 rc = VGSvcGstCtrlSessionThreadDestroy(pThread, fFlags);
450 break;
451 }
452 }
453
454#if 0 /** @todo A bit of a mess here as this message goes to both to this process (master) and the session process. */
455 if (RT_FAILURE(rc))
456 {
457 /* Report back on failure. On success this will be done
458 * by the forked session thread. */
459 int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx,
460 GUEST_SESSION_NOTIFYTYPE_ERROR, rc);
461 if (RT_FAILURE(rc2))
462 {
463 VGSvcError("Reporting session error status on close failed with rc=%Rrc\n", rc2);
464 if (RT_SUCCESS(rc))
465 rc = rc2;
466 }
467 }
468#endif
469 VGSvcVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n", idSession, rc);
470 }
471 else
472 {
473 VGSvcError("Error fetching parameters for closing guest session: %Rrc\n", rc);
474 VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
475 }
476 return rc;
477}
478
479
480/**
481 * @interface_method_impl{VBOXSERVICE,pfnStop}
482 */
483static DECLCALLBACK(void) vgsvcGstCtrlStop(void)
484{
485 VGSvcVerbose(3, "Stopping ...\n");
486
487 /** @todo Later, figure what to do if we're in RTProcWait(). It's a very
488 * annoying call since doesn't support timeouts in the posix world. */
489 if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
490 RTSemEventMultiSignal(g_hControlEvent);
491
492 /*
493 * Ask the host service to cancel all pending requests for the main
494 * control thread so that we can shutdown properly here.
495 */
496 if (g_idControlSvcClient)
497 {
498 VGSvcVerbose(3, "Cancelling pending waits (client ID=%u) ...\n",
499 g_idControlSvcClient);
500
501 int rc = VbglR3GuestCtrlCancelPendingWaits(g_idControlSvcClient);
502 if (RT_FAILURE(rc))
503 VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);
504 }
505}
506
507
508/**
509 * Destroys all guest process threads which are still active.
510 */
511static void vgsvcGstCtrlShutdown(void)
512{
513 VGSvcVerbose(2, "Shutting down ...\n");
514
515 int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */);
516 if (RT_FAILURE(rc2))
517 VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2);
518
519 rc2 = VGSvcGstCtrlSessionClose(&g_Session);
520 if (RT_FAILURE(rc2))
521 VGSvcError("Closing session failed with rc=%Rrc\n", rc2);
522
523 VGSvcVerbose(2, "Shutting down complete\n");
524}
525
526
527/**
528 * @interface_method_impl{VBOXSERVICE,pfnTerm}
529 */
530static DECLCALLBACK(void) vgsvcGstCtrlTerm(void)
531{
532 VGSvcVerbose(3, "Terminating ...\n");
533
534 vgsvcGstCtrlShutdown();
535
536 VGSvcVerbose(3, "Disconnecting client ID=%u ...\n", g_idControlSvcClient);
537 VbglR3GuestCtrlDisconnect(g_idControlSvcClient);
538 g_idControlSvcClient = 0;
539
540 if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
541 {
542 RTSemEventMultiDestroy(g_hControlEvent);
543 g_hControlEvent = NIL_RTSEMEVENTMULTI;
544 }
545}
546
547
548/**
549 * The 'vminfo' service description.
550 */
551VBOXSERVICE g_Control =
552{
553 /* pszName. */
554 "control",
555 /* pszDescription. */
556 "Host-driven Guest Control",
557 /* pszUsage. */
558#ifdef DEBUG
559 " [--control-dump-stderr] [--control-dump-stdout]\n"
560#endif
561 " [--control-interval <ms>]"
562 ,
563 /* pszOptions. */
564#ifdef DEBUG
565 " --control-dump-stderr Dumps all guest proccesses stderr data to the\n"
566 " temporary directory.\n"
567 " --control-dump-stdout Dumps all guest proccesses stdout data to the\n"
568 " temporary directory.\n"
569#endif
570 " --control-interval Specifies the interval at which to check for\n"
571 " new control commands. The default is 1000 ms.\n"
572 ,
573 /* methods */
574 vgsvcGstCtrlPreInit,
575 vgsvcGstCtrlOption,
576 vgsvcGstCtrlInit,
577 vgsvcGstCtrlWorker,
578 vgsvcGstCtrlStop,
579 vgsvcGstCtrlTerm
580};
581
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