VirtualBox

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

Last change on this file since 76553 was 76553, checked in by vboxsync, 5 years ago

scm --update-copyright-year

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

© 2023 Oracle
ContactPrivacy policyTerms of Use