VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/main.cpp@ 50667

Last change on this file since 50667 was 50464, checked in by vboxsync, 11 years ago

Additions/x11/VBoxClient: clean-up in main code.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 15.7 KB
Line 
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
46static 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. */
50VBoxClient::Service *g_pService;
51/** The name of our pidfile. It is global for the benefit of the cleanup
52 * routine. */
53static char g_szPidFile[RTPATH_MAX];
54/** The file handle of our pidfile. It is global for the benefit of the
55 * cleanup routine. */
56static 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 */
61RTCRITSECT 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. */
65void 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 */
87void 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 */
98int 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 */
111static 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 */
122void 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. */
143static 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 */
164static 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. */
211static 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 */
271static 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 */
296static 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 */
305void 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 */
336int 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}
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