VirtualBox

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

Last change on this file since 50460 was 50451, checked in by vboxsync, 11 years ago

Additions/x11/VBoxClient: fix for older Linux kernels.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 15.8 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 */
335int main(int argc, char *argv[])
336{
337 if (!XInitThreads())
338 return 1;
339 /* Initialise our runtime before all else. */
340 int rc = RTR3InitExe(argc, &argv, 0);
341 if (RT_FAILURE(rc))
342 return RTMsgInitFailure(rc);
343
344 int rcClipboard;
345 const char *pszFileName = RTPathFilename(argv[0]);
346 bool fDaemonise = true;
347 /* Have any fatal errors occurred yet? */
348 bool fSuccess = true;
349 /* Do we know which service we wish to run? */
350 bool fHaveService = false;
351
352 if (NULL == pszFileName)
353 pszFileName = "VBoxClient";
354
355 /* Initialise our global clean-up critical section */
356 rc = RTCritSectInit(&g_critSect);
357 if (RT_FAILURE(rc))
358 {
359 /* Of course, this should never happen. */
360 RTPrintf("%s: Failed to initialise the global critical section, rc=%Rrc\n", pszFileName, rc);
361 return 1;
362 }
363
364 /* Parse our option(s) */
365 /** @todo Use RTGetOpt() if the arguments become more complex. */
366 for (int i = 1; i < argc; ++i)
367 {
368 if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--nodaemon"))
369 fDaemonise = false;
370 else if (!strcmp(argv[i], "--clipboard"))
371 {
372 if (g_pService == NULL)
373 g_pService = VBoxClient::GetClipboardService();
374 else
375 fSuccess = false;
376 }
377 else if (!strcmp(argv[i], "--display"))
378 {
379 if (g_pService == NULL)
380 g_pService = VBoxClient::GetDisplayService();
381 else
382 fSuccess = false;
383 }
384 else if (!strcmp(argv[i], "--seamless"))
385 {
386 if (g_pService == NULL)
387 g_pService = VBoxClient::GetSeamlessService();
388 else
389 fSuccess = false;
390 }
391 else if (!strcmp(argv[i], "--checkhostversion"))
392 {
393 if (g_pService == NULL)
394 g_pService = VBoxClient::GetHostVersionService();
395 else
396 fSuccess = false;
397 }
398#ifdef VBOX_WITH_DRAG_AND_DROP
399 else if (!strcmp(argv[i], "--draganddrop"))
400 {
401 if (g_pService == NULL)
402 g_pService = VBoxClient::GetDragAndDropService();
403 else
404 fSuccess = false;
405 }
406#endif /* VBOX_WITH_DRAG_AND_DROP */
407 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
408 {
409 vboxClientUsage(pszFileName);
410 return 0;
411 }
412 else
413 {
414 RTPrintf("%s: unrecognized option `%s'\n", pszFileName, argv[i]);
415 RTPrintf("Try `%s --help' for more information\n", pszFileName);
416 return 1;
417 }
418 }
419 if (!fSuccess || !g_pService)
420 {
421 vboxClientUsage(pszFileName);
422 return 1;
423 }
424 /* Get the path for the pidfiles */
425 rc = RTPathUserHome(g_szPidFile, sizeof(g_szPidFile));
426 if (RT_FAILURE(rc))
427 {
428 RTPrintf("VBoxClient: failed to get home directory, rc=%Rrc. Exiting.\n", rc);
429 LogRel(("VBoxClient: failed to get home directory, rc=%Rrc. Exiting.\n", rc));
430 return 1;
431 }
432 rc = RTPathAppend(g_szPidFile, sizeof(g_szPidFile), g_pService->getPidFilePath());
433 if (RT_FAILURE(rc))
434 {
435 RTPrintf("VBoxClient: RTPathAppend failed with rc=%Rrc. Exiting.\n", rc);
436 LogRel(("VBoxClient: RTPathAppend failed with rc=%Rrc. Exiting.\n", rc));
437 return 1;
438 }
439
440 /* Initialise the guest library. */
441 if (RT_FAILURE(VbglR3InitUser()))
442 {
443 RTPrintf("Failed to connect to the VirtualBox kernel service\n");
444 LogRel(("Failed to connect to the VirtualBox kernel service\n"));
445 return 1;
446 }
447 if (fDaemonise)
448 {
449 rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */);
450 if (RT_FAILURE(rc))
451 {
452 RTPrintf("VBoxClient: failed to daemonize. Exiting.\n");
453 LogRel(("VBoxClient: failed to daemonize. Exiting.\n"));
454# ifdef DEBUG
455 RTPrintf("Error %Rrc\n", rc);
456# endif
457 return 1;
458 }
459 }
460 if (g_szPidFile[0] && RT_FAILURE(VbglR3PidFile(g_szPidFile, &g_hPidFile)))
461 {
462 RTPrintf("Failed to create a pidfile. Exiting.\n");
463 LogRel(("Failed to create a pidfile. Exiting.\n"));
464 VbglR3Term();
465 return 1;
466 }
467 /* Set signal handlers to clean up on exit. */
468 vboxClientSetSignalHandlers();
469 /* Set an X11 error handler, so that we don't die when we get unavoidable errors. */
470 XSetErrorHandler(vboxClientXLibErrorHandler);
471 /* Set an X11 I/O error handler, so that we can shutdown properly on fatal errors. */
472 XSetIOErrorHandler(vboxClientXLibIOErrorHandler);
473 rc = g_pService->init();
474 if (RT_FAILURE(rc))
475 {
476 LogRel(("VBoxClient: failed to initialise the service (%Rrc). Exiting.\n",
477 rc));
478 VbglR3Term();
479 return 1;
480 }
481 rc = startMonitorThread();
482 if (RT_FAILURE(rc))
483 {
484 LogRel(("Failed to start the monitor thread (%Rrc). Exiting.\n",
485 rc));
486 VBoxClient::CleanUp();
487 }
488 g_pService->run(fDaemonise);
489 VBoxClient::CleanUp();
490 return 1; /* We should never get here. */
491}
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