VirtualBox

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

Last change on this file since 60641 was 58052, checked in by vboxsync, 9 years ago

VBoxService: More service introduction pages. A few more cleanups.

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