1 | /** @file
|
---|
2 | *
|
---|
3 | * VirtualBox Guest Service:
|
---|
4 | * Linux guest.
|
---|
5 | */
|
---|
6 |
|
---|
7 | /*
|
---|
8 | * Copyright (C) 2006-2011 Oracle Corporation
|
---|
9 | *
|
---|
10 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
11 | * available from http://www.virtualbox.org. This file is free software;
|
---|
12 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
13 | * General Public License (GPL) as published by the Free Software
|
---|
14 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
15 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
16 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
17 | */
|
---|
18 |
|
---|
19 | #include <sys/types.h>
|
---|
20 | #include <stdlib.h> /* For exit */
|
---|
21 | #include <stdio.h>
|
---|
22 | #include <string.h>
|
---|
23 | #include <unistd.h>
|
---|
24 | #include <errno.h>
|
---|
25 | #include <poll.h>
|
---|
26 | #include <signal.h>
|
---|
27 |
|
---|
28 | #include <X11/Xlib.h>
|
---|
29 | #include <X11/Xatom.h>
|
---|
30 |
|
---|
31 | #include <iprt/critsect.h>
|
---|
32 | #include <iprt/env.h>
|
---|
33 | #include <iprt/file.h>
|
---|
34 | #include <iprt/initterm.h>
|
---|
35 | #include <iprt/message.h>
|
---|
36 | #include <iprt/path.h>
|
---|
37 | #include <iprt/param.h>
|
---|
38 | #include <iprt/stream.h>
|
---|
39 | #include <iprt/string.h>
|
---|
40 | #include <iprt/types.h>
|
---|
41 | #include <VBox/VBoxGuestLib.h>
|
---|
42 | #include <VBox/log.h>
|
---|
43 |
|
---|
44 | #include "VBoxClient.h"
|
---|
45 |
|
---|
46 | static int (*gpfnOldIOErrorHandler)(Display *) = NULL;
|
---|
47 |
|
---|
48 | /** Object representing the service we are running. This has to be global
|
---|
49 | * so that the cleanup routine can access it. */
|
---|
50 | VBoxClient::Service *g_pService;
|
---|
51 | /** The name of our pidfile. It is global for the benefit of the cleanup
|
---|
52 | * routine. */
|
---|
53 | static char g_szPidFile[RTPATH_MAX];
|
---|
54 | /** The file handle of our pidfile. It is global for the benefit of the
|
---|
55 | * cleanup routine. */
|
---|
56 | static RTFILE g_hPidFile;
|
---|
57 | /** Global critical section held during the clean-up routine (to prevent it
|
---|
58 | * being called on multiple threads at once) or things which may not happen
|
---|
59 | * during clean-up (e.g. pausing and resuming the service).
|
---|
60 | */
|
---|
61 | RTCRITSECT g_critSect;
|
---|
62 |
|
---|
63 | /** Clean up if we get a signal or something. This is extern so that we
|
---|
64 | * can call it from other compilation units. */
|
---|
65 | void VBoxClient::CleanUp()
|
---|
66 | {
|
---|
67 | /* We never release this, as we end up with a call to exit(3) which is not
|
---|
68 | * async-safe. Unless we fix this application properly, we should be sure
|
---|
69 | * never to exit from anywhere except from this method. */
|
---|
70 | int rc = RTCritSectEnter(&g_critSect);
|
---|
71 | if (RT_FAILURE(rc))
|
---|
72 | {
|
---|
73 | LogRel(("VBoxClient: Failure while acquiring the global critical section, rc=%Rrc\n", rc));
|
---|
74 | abort();
|
---|
75 | }
|
---|
76 | if (g_pService)
|
---|
77 | g_pService->cleanup();
|
---|
78 | if (g_szPidFile[0] && g_hPidFile)
|
---|
79 | VbglR3ClosePidFile(g_szPidFile, g_hPidFile);
|
---|
80 | VbglR3Term();
|
---|
81 | exit(0);
|
---|
82 | }
|
---|
83 |
|
---|
84 | /**
|
---|
85 | * A standard signal handler which cleans up and exits.
|
---|
86 | */
|
---|
87 | void vboxClientSignalHandler(int cSignal)
|
---|
88 | {
|
---|
89 | LogRel(("VBoxClient: terminated with signal %d\n", cSignal));
|
---|
90 | /** Disable seamless mode */
|
---|
91 | RTPrintf(("VBoxClient: terminating...\n"));
|
---|
92 | VBoxClient::CleanUp();
|
---|
93 | }
|
---|
94 |
|
---|
95 | /**
|
---|
96 | * Xlib error handler for certain errors that we can't avoid.
|
---|
97 | */
|
---|
98 | int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError)
|
---|
99 | {
|
---|
100 | char errorText[1024];
|
---|
101 |
|
---|
102 | XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText));
|
---|
103 | LogRelFlow(("VBoxClient: an X Window protocol error occurred: %s (error code %d). Request code: %d, minor code: %d, serial number: %d\n", errorText, pError->error_code, pError->request_code, pError->minor_code, pError->serial));
|
---|
104 | return 0; /* We should never reach this. */
|
---|
105 | }
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * Xlib error handler for fatal errors. This often means that the programme is still running
|
---|
109 | * when X exits.
|
---|
110 | */
|
---|
111 | static int vboxClientXLibIOErrorHandler(Display *pDisplay)
|
---|
112 | {
|
---|
113 | LogRel(("VBoxClient: a fatal guest X Window error occurred. This may just mean that the Window system was shut down while the client was still running.\n"));
|
---|
114 | VBoxClient::CleanUp();
|
---|
115 | return 0; /* We should never reach this. */
|
---|
116 | }
|
---|
117 |
|
---|
118 | /**
|
---|
119 | * Reset all standard termination signals to call our signal handler, which
|
---|
120 | * cleans up and exits.
|
---|
121 | */
|
---|
122 | void vboxClientSetSignalHandlers(void)
|
---|
123 | {
|
---|
124 | struct sigaction sigAction;
|
---|
125 |
|
---|
126 | LogRelFlowFunc(("\n"));
|
---|
127 | sigAction.sa_handler = vboxClientSignalHandler;
|
---|
128 | sigemptyset(&sigAction.sa_mask);
|
---|
129 | sigAction.sa_flags = 0;
|
---|
130 | sigaction(SIGHUP, &sigAction, NULL);
|
---|
131 | sigaction(SIGINT, &sigAction, NULL);
|
---|
132 | sigaction(SIGQUIT, &sigAction, NULL);
|
---|
133 | sigaction(SIGPIPE, &sigAction, NULL);
|
---|
134 | sigaction(SIGALRM, &sigAction, NULL);
|
---|
135 | sigaction(SIGTERM, &sigAction, NULL);
|
---|
136 | sigaction(SIGUSR1, &sigAction, NULL);
|
---|
137 | sigaction(SIGUSR2, &sigAction, NULL);
|
---|
138 | LogRelFlowFunc(("returning\n"));
|
---|
139 | }
|
---|
140 |
|
---|
141 | /** Connect to the X server and return the "XFree86_VT" root window property,
|
---|
142 | * or 0 on failure. */
|
---|
143 | static unsigned long getXOrgVT(Display *pDisplay)
|
---|
144 | {
|
---|
145 | Atom actualType;
|
---|
146 | int actualFormat;
|
---|
147 | unsigned long cItems, cbLeft, cVT = 0;
|
---|
148 | unsigned long *pValue;
|
---|
149 |
|
---|
150 | XGetWindowProperty(pDisplay, DefaultRootWindow(pDisplay),
|
---|
151 | XInternAtom(pDisplay, "XFree86_VT", False), 0, 1, False,
|
---|
152 | XA_INTEGER, &actualType, &actualFormat, &cItems, &cbLeft,
|
---|
153 | (unsigned char **)&pValue);
|
---|
154 | if (cItems && actualFormat == 32)
|
---|
155 | {
|
---|
156 | cVT = *pValue;
|
---|
157 | XFree(pValue);
|
---|
158 | }
|
---|
159 | return cVT;
|
---|
160 | }
|
---|
161 |
|
---|
162 | /** Check whether the current virtual terminal is the one running the X server.
|
---|
163 | */
|
---|
164 | static void checkVTSysfs(RTFILE hFile, uint32_t cVT)
|
---|
165 | {
|
---|
166 | char szTTY[7] = "";
|
---|
167 | uint32_t cTTY;
|
---|
168 | size_t cbRead;
|
---|
169 | int rc;
|
---|
170 | const char *pcszStage;
|
---|
171 |
|
---|
172 | do {
|
---|
173 | pcszStage = "reading /sys/class/tty/tty0/active";
|
---|
174 | rc = RTFileReadAt(hFile, 0, (void *)szTTY, sizeof(szTTY), &cbRead);
|
---|
175 | if (RT_FAILURE(rc))
|
---|
176 | break;
|
---|
177 | szTTY[cbRead - 1] = '\0';
|
---|
178 | pcszStage = "getting VT number from sysfs file";
|
---|
179 | rc = RTStrToUInt32Full(&szTTY[3], 10, &cTTY);
|
---|
180 | if (RT_FAILURE(rc))
|
---|
181 | break;
|
---|
182 | pcszStage = "entering critical section";
|
---|
183 | rc = RTCritSectEnter(&g_critSect);
|
---|
184 | if (RT_FAILURE(rc))
|
---|
185 | break;
|
---|
186 | pcszStage = "asking service to pause or resume";
|
---|
187 | if (cTTY == cVT)
|
---|
188 | rc = g_pService->resume();
|
---|
189 | else
|
---|
190 | rc = g_pService->pause();
|
---|
191 | if (RT_FAILURE(rc))
|
---|
192 | break;
|
---|
193 | pcszStage = "leaving critical section";
|
---|
194 | rc = RTCritSectLeave(&g_critSect);
|
---|
195 | } while(false);
|
---|
196 | if (RT_FAILURE(rc))
|
---|
197 | {
|
---|
198 | LogRelFunc(("VBoxClient: failed at stage: \"%s\" rc: %Rrc cVT: %d szTTY: %s.\n",
|
---|
199 | pcszStage, rc, (int) cVT, szTTY));
|
---|
200 | if (RTCritSectIsOwner(&g_critSect))
|
---|
201 | RTCritSectLeave(&g_critSect);
|
---|
202 | VBoxClient::CleanUp();
|
---|
203 | }
|
---|
204 | }
|
---|
205 |
|
---|
206 | /** Poll for TTY changes using sysfs and for X server disconnection.
|
---|
207 | * Reading from the start of the pollable file "/sys/class/tty/tty0/active"
|
---|
208 | * returns the currently active TTY as a string of the form "tty<n>", with n
|
---|
209 | * greater than zero. Polling for POLLPRI returns when the TTY changes.
|
---|
210 | * @a cVT should be zero if we do not know the X server's VT. */
|
---|
211 | static void pollTTYAndXServer(Display *pDisplay, uint32_t cVT)
|
---|
212 | {
|
---|
213 | RTFILE hFile = NIL_RTFILE;
|
---|
214 | struct pollfd pollFD[2];
|
---|
215 | unsigned cPollFD = 1;
|
---|
216 | int rc;
|
---|
217 |
|
---|
218 | pollFD[1].fd = -1;
|
---|
219 | pollFD[1].revents = 0;
|
---|
220 | /* This block could be Linux-only, but keeping it on Solaris too, where it
|
---|
221 | * should just fail gracefully, gives us more code path coverage. */
|
---|
222 | if (cVT)
|
---|
223 | {
|
---|
224 | rc = RTFileOpen(&hFile, "/sys/class/tty/tty0/active",
|
---|
225 | RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN);
|
---|
226 | if (RT_SUCCESS(rc))
|
---|
227 | {
|
---|
228 | pollFD[1].fd = RTFileToNative(hFile);
|
---|
229 | pollFD[1].events = POLLPRI;
|
---|
230 | cPollFD = 2;
|
---|
231 | }
|
---|
232 | }
|
---|
233 | AssertRelease(pollFD[1].fd >= 0 || cPollFD == 1);
|
---|
234 | pollFD[0].fd = ConnectionNumber(pDisplay);
|
---|
235 | pollFD[0].events = POLLIN;
|
---|
236 | while (true)
|
---|
237 | {
|
---|
238 | if (hFile != NIL_RTFILE)
|
---|
239 | checkVTSysfs(hFile, cVT);
|
---|
240 | /* The only point of this loop is to trigger the I/O error handler if
|
---|
241 | * appropriate. */
|
---|
242 | while (XPending(pDisplay))
|
---|
243 | {
|
---|
244 | XEvent ev;
|
---|
245 |
|
---|
246 | XNextEvent(pDisplay, &ev);
|
---|
247 | }
|
---|
248 | /* If we get caught in a tight loop for some reason try to limit the
|
---|
249 | * damage. */
|
---|
250 | if (poll(pollFD, cPollFD, 0) > 0)
|
---|
251 | {
|
---|
252 | LogRel(("Monitor thread: unexpectedly fast event, revents=0x%x, 0x%x.\n",
|
---|
253 | pollFD[0].revents, pollFD[1].revents));
|
---|
254 | RTThreadYield();
|
---|
255 | }
|
---|
256 | if ( (poll(pollFD, cPollFD, -1) < 0 && errno != EINTR)
|
---|
257 | || pollFD[0].revents & POLLNVAL
|
---|
258 | || pollFD[1].revents & POLLNVAL)
|
---|
259 | {
|
---|
260 | LogRel(("Monitor thread: poll failed, stopping.\n"));
|
---|
261 | VBoxClient::CleanUp();
|
---|
262 | }
|
---|
263 | }
|
---|
264 | }
|
---|
265 |
|
---|
266 | /**
|
---|
267 | * Thread which notifies the service when we switch to a different VT or back
|
---|
268 | * and cleans up when the X server exits.
|
---|
269 | * @note runs until programme exit.
|
---|
270 | */
|
---|
271 | static int pfnMonitorThread(RTTHREAD self, void *pvUser)
|
---|
272 | {
|
---|
273 | Display *pDisplay;
|
---|
274 | unsigned long cVT;
|
---|
275 | RTFILE hFile;
|
---|
276 |
|
---|
277 | pDisplay = XOpenDisplay(NULL);
|
---|
278 | if (!pDisplay)
|
---|
279 | return VINF_SUCCESS;
|
---|
280 | cVT = getXOrgVT(pDisplay);
|
---|
281 | /* Note: cVT will be 0 if we failed to get it. This is valid. */
|
---|
282 | pollTTYAndXServer(pDisplay, (uint32_t) cVT);
|
---|
283 | /* Should never get here. */
|
---|
284 | return VINF_SUCCESS;
|
---|
285 | }
|
---|
286 |
|
---|
287 | /**
|
---|
288 | * Start the thread which notifies the service when we switch to a different
|
---|
289 | * VT or back, and terminates us when the X server exits. The first is best
|
---|
290 | * effort functionality: XFree86 4.3 and older do not report their VT via the
|
---|
291 | * "XFree86_VT" root window property at all, and pre-2.6.38 Linux does not
|
---|
292 | * provide the interface in "sysfs" which we use. If there is a need for this
|
---|
293 | * to work with pre-2.6.38 Linux we can send the VT_GETSTATE ioctl to
|
---|
294 | * /dev/console at regular intervals.
|
---|
295 | */
|
---|
296 | static int startMonitorThread()
|
---|
297 | {
|
---|
298 | return RTThreadCreate(NULL, pfnMonitorThread, NULL, 0,
|
---|
299 | RTTHREADTYPE_INFREQUENT_POLLER, 0, "MONITOR");
|
---|
300 | }
|
---|
301 |
|
---|
302 | /**
|
---|
303 | * Print out a usage message and exit with success.
|
---|
304 | */
|
---|
305 | void vboxClientUsage(const char *pcszFileName)
|
---|
306 | {
|
---|
307 | RTPrintf("Usage: %s --clipboard|"
|
---|
308 | #ifdef VBOX_WITH_DRAG_AND_DROP
|
---|
309 | "--draganddrop|"
|
---|
310 | #endif
|
---|
311 | "--display|"
|
---|
312 | # ifdef VBOX_WITH_GUEST_PROPS
|
---|
313 | "--checkhostversion|"
|
---|
314 | #endif
|
---|
315 | "--seamless [-d|--nodaemon]\n", pcszFileName);
|
---|
316 | RTPrintf("Start the VirtualBox X Window System guest services.\n\n");
|
---|
317 | RTPrintf("Options:\n");
|
---|
318 | RTPrintf(" --clipboard start the shared clipboard service\n");
|
---|
319 | #ifdef VBOX_WITH_DRAG_AND_DROP
|
---|
320 | RTPrintf(" --draganddrop start the drag and drop service\n");
|
---|
321 | #endif
|
---|
322 | RTPrintf(" --display start the display management service\n");
|
---|
323 | #ifdef VBOX_WITH_GUEST_PROPS
|
---|
324 | RTPrintf(" --checkhostversion start the host version notifier service\n");
|
---|
325 | #endif
|
---|
326 | RTPrintf(" --seamless start the seamless windows service\n");
|
---|
327 | RTPrintf(" -d, --nodaemon continue running as a system service\n");
|
---|
328 | RTPrintf("\n");
|
---|
329 | exit(0);
|
---|
330 | }
|
---|
331 |
|
---|
332 | /**
|
---|
333 | * The main loop for the VBoxClient daemon.
|
---|
334 | * @todo Clean up for readability.
|
---|
335 | */
|
---|
336 | int main(int argc, char *argv[])
|
---|
337 | {
|
---|
338 | bool fDaemonise = true;
|
---|
339 | int rc;
|
---|
340 | const char *pcszFileName, *pcszStage;
|
---|
341 |
|
---|
342 | /* Initialise our runtime before all else. */
|
---|
343 | rc = RTR3InitExe(argc, &argv, 0);
|
---|
344 | if (RT_FAILURE(rc))
|
---|
345 | return RTMsgInitFailure(rc);
|
---|
346 | /* This should never be called twice in one process - in fact one Display
|
---|
347 | * object should probably never be used from multiple threads anyway. */
|
---|
348 | if (!XInitThreads())
|
---|
349 | return 1;
|
---|
350 | /* Get our file name for error output. */
|
---|
351 | pcszFileName = RTPathFilename(argv[0]);
|
---|
352 | if (!pcszFileName)
|
---|
353 | pcszFileName = "VBoxClient";
|
---|
354 | /* Initialise the guest library. */
|
---|
355 | rc = VbglR3InitUser();
|
---|
356 | if (RT_FAILURE(rc))
|
---|
357 | {
|
---|
358 | RTPrintf("%s: failed to connect to the VirtualBox kernel service, rc=%Rrc\n",
|
---|
359 | pcszFileName, rc);
|
---|
360 | return 1;
|
---|
361 | }
|
---|
362 |
|
---|
363 | /* Parse our option(s) */
|
---|
364 | /** @todo Use RTGetOpt() if the arguments become more complex. */
|
---|
365 | for (int i = 1; i < argc; ++i)
|
---|
366 | {
|
---|
367 | rc = VERR_INVALID_PARAMETER;
|
---|
368 | if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--nodaemon"))
|
---|
369 | {
|
---|
370 | /* If the user is running in "no daemon" mode anyway, send critical
|
---|
371 | * logging to stdout as well. */
|
---|
372 | PRTLOGGER pReleaseLog = RTLogRelDefaultInstance();
|
---|
373 |
|
---|
374 | if (pReleaseLog)
|
---|
375 | rc = RTLogDestinations(pReleaseLog, "stdout");
|
---|
376 | if (pReleaseLog && RT_FAILURE(rc))
|
---|
377 | RTPrintf("%s: failed to redivert error output, rc=%Rrc\n",
|
---|
378 | pcszFileName, rc);
|
---|
379 | fDaemonise = false;
|
---|
380 | }
|
---|
381 | else if (!strcmp(argv[i], "--clipboard"))
|
---|
382 | {
|
---|
383 | if (g_pService)
|
---|
384 | break;
|
---|
385 | g_pService = VBoxClient::GetClipboardService();
|
---|
386 | }
|
---|
387 | else if (!strcmp(argv[i], "--display"))
|
---|
388 | {
|
---|
389 | if (g_pService)
|
---|
390 | break;
|
---|
391 | g_pService = VBoxClient::GetDisplayService();
|
---|
392 | }
|
---|
393 | else if (!strcmp(argv[i], "--seamless"))
|
---|
394 | {
|
---|
395 | if (g_pService)
|
---|
396 | break;
|
---|
397 | g_pService = VBoxClient::GetSeamlessService();
|
---|
398 | }
|
---|
399 | else if (!strcmp(argv[i], "--checkhostversion"))
|
---|
400 | {
|
---|
401 | if (g_pService)
|
---|
402 | break;
|
---|
403 | g_pService = VBoxClient::GetHostVersionService();
|
---|
404 | }
|
---|
405 | #ifdef VBOX_WITH_DRAG_AND_DROP
|
---|
406 | else if (!strcmp(argv[i], "--draganddrop"))
|
---|
407 | {
|
---|
408 | if (g_pService)
|
---|
409 | break;
|
---|
410 | g_pService = VBoxClient::GetDragAndDropService();
|
---|
411 | }
|
---|
412 | #endif /* VBOX_WITH_DRAG_AND_DROP */
|
---|
413 | else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
|
---|
414 | {
|
---|
415 | vboxClientUsage(pcszFileName);
|
---|
416 | return 0;
|
---|
417 | }
|
---|
418 | else
|
---|
419 | {
|
---|
420 | RTPrintf("%s: unrecognized option `%s'\n", pcszFileName, argv[i]);
|
---|
421 | RTPrintf("Try `%s --help' for more information\n", pcszFileName);
|
---|
422 | return 1;
|
---|
423 | }
|
---|
424 | rc = VINF_SUCCESS;
|
---|
425 | }
|
---|
426 | if (RT_FAILURE(rc) || !g_pService)
|
---|
427 | {
|
---|
428 | vboxClientUsage(pcszFileName);
|
---|
429 | return 1;
|
---|
430 | }
|
---|
431 |
|
---|
432 | do {
|
---|
433 | pcszStage = "Initialising critical section";
|
---|
434 | rc = RTCritSectInit(&g_critSect);
|
---|
435 | if (RT_FAILURE(rc))
|
---|
436 | break;
|
---|
437 | pcszStage = "Getting home directory for pid-file";
|
---|
438 | rc = RTPathUserHome(g_szPidFile, sizeof(g_szPidFile));
|
---|
439 | if (RT_FAILURE(rc))
|
---|
440 | break;
|
---|
441 | pcszStage = "Creating pid-file path";
|
---|
442 | rc = RTPathAppend(g_szPidFile, sizeof(g_szPidFile),
|
---|
443 | g_pService->getPidFilePath());
|
---|
444 | if (RT_FAILURE(rc))
|
---|
445 | break;
|
---|
446 | pcszStage = "Daemonising";
|
---|
447 | if (fDaemonise)
|
---|
448 | rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */);
|
---|
449 | if (RT_FAILURE(rc))
|
---|
450 | break;
|
---|
451 | pcszStage = "Creating pid-file";
|
---|
452 | if (g_szPidFile[0])
|
---|
453 | rc = VbglR3PidFile(g_szPidFile, &g_hPidFile);
|
---|
454 | if (RT_FAILURE(rc))
|
---|
455 | break;
|
---|
456 | /* Set signal handlers to clean up on exit. */
|
---|
457 | vboxClientSetSignalHandlers();
|
---|
458 | /* Set an X11 error handler, so that we don't die when we get unavoidable
|
---|
459 | * errors. */
|
---|
460 | XSetErrorHandler(vboxClientXLibErrorHandler);
|
---|
461 | /* Set an X11 I/O error handler, so that we can shutdown properly on
|
---|
462 | * fatal errors. */
|
---|
463 | XSetIOErrorHandler(vboxClientXLibIOErrorHandler);
|
---|
464 | pcszStage = "Initialising service";
|
---|
465 | rc = g_pService->init();
|
---|
466 | } while (0);
|
---|
467 | if (RT_FAILURE(rc))
|
---|
468 | {
|
---|
469 | LogRelFunc(("VBoxClient: failed at stage: \"%s\" rc: %Rrc\n",
|
---|
470 | pcszStage, rc));
|
---|
471 | VbglR3Term();
|
---|
472 | return 1;
|
---|
473 | }
|
---|
474 |
|
---|
475 | rc = startMonitorThread();
|
---|
476 | if (RT_FAILURE(rc))
|
---|
477 | LogRel(("Failed to start the monitor thread (%Rrc). Exiting.\n",
|
---|
478 | rc));
|
---|
479 | else
|
---|
480 | g_pService->run(fDaemonise); /* Should never return. */
|
---|
481 | VBoxClient::CleanUp();
|
---|
482 | return 1;
|
---|
483 | }
|
---|