VirtualBox

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

Last change on this file was 101878, checked in by vboxsync, 6 months ago

Additions: X11/Wayland: Add initial support for clipboard sharing with Gnome and Plasma Wayland guests (not yet enabled), bugref:10194.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 33.8 KB
RevLine 
[55401]1/* $Id: main.cpp 101878 2023-11-06 15:36:24Z vboxsync $ */
[6202]2/** @file
[68562]3 * VirtualBox Guest Additions - X11 Client.
[6202]4 */
5
6/*
[98103]7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
[6202]8 *
[96407]9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
[6202]26 */
27
[76419]28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
[65672]32#include <sys/wait.h>
[6202]33#include <stdlib.h> /* For exit */
[84503]34#include <signal.h>
[18938]35#include <X11/Xlib.h>
[81052]36#include "product-generated.h"
[86872]37#include <iprt/asm.h>
[55440]38#include <iprt/buildconfig.h>
[32832]39#include <iprt/critsect.h>
[86871]40#include <iprt/errno.h>
[81057]41#include <iprt/getopt.h>
[18360]42#include <iprt/initterm.h>
[38636]43#include <iprt/message.h>
[18360]44#include <iprt/path.h>
45#include <iprt/stream.h>
[93551]46#include <iprt/env.h>
[98474]47#include <iprt/process.h>
48#include <iprt/linux/sysfs.h>
[21218]49#include <VBox/VBoxGuestLib.h>
[76419]50#include <VBox/err.h>
[86871]51#include <VBox/version.h>
[18360]52#include "VBoxClient.h"
[6202]53
[76419]54
55/*********************************************************************************************************************************
[81057]56* Defines *
57*********************************************************************************************************************************/
58#define VBOXCLIENT_OPT_SERVICES 980
59#define VBOXCLIENT_OPT_CHECKHOSTVERSION VBOXCLIENT_OPT_SERVICES
60#define VBOXCLIENT_OPT_CLIPBOARD VBOXCLIENT_OPT_SERVICES + 1
[83717]61#define VBOXCLIENT_OPT_DRAGANDDROP VBOXCLIENT_OPT_SERVICES + 2
62#define VBOXCLIENT_OPT_SEAMLESS VBOXCLIENT_OPT_SERVICES + 3
63#define VBOXCLIENT_OPT_VMSVGA VBOXCLIENT_OPT_SERVICES + 4
[93371]64#define VBOXCLIENT_OPT_VMSVGA_SESSION VBOXCLIENT_OPT_SERVICES + 5
[94306]65#define VBOXCLIENT_OPT_DISPLAY VBOXCLIENT_OPT_SERVICES + 6
[99658]66#define VBOXCLIENT_OPT_SESSION_DETECT VBOXCLIENT_OPT_SERVICES + 7
67#define VBOXCLIENT_OPT_SESSION_TYPE VBOXCLIENT_OPT_SERVICES + 8
[100246]68#define VBOXCLIENT_OPT_WAYLAND VBOXCLIENT_OPT_SERVICES + 9
[81057]69
70
71/*********************************************************************************************************************************
[86871]72* Local structures *
73*********************************************************************************************************************************/
74/**
75 * The global service state.
76 */
77typedef struct VBCLSERVICESTATE
78{
79 /** Pointer to the service descriptor. */
80 PVBCLSERVICE pDesc;
81 /** The worker thread. NIL_RTTHREAD if it's the main thread. */
82 RTTHREAD Thread;
83 /** Whether Pre-init was called. */
84 bool fPreInited;
85 /** Shutdown indicator. */
86 bool volatile fShutdown;
87 /** Indicator set by the service thread exiting. */
88 bool volatile fStopped;
89 /** Whether the service was started or not. */
90 bool fStarted;
91} VBCLSERVICESTATE;
92/** Pointer to a service state. */
93typedef VBCLSERVICESTATE *PVBCLSERVICESTATE;
94
95
96/*********************************************************************************************************************************
[76419]97* Global Variables *
98*********************************************************************************************************************************/
[86871]99/** The global service state. */
[99585]100VBCLSERVICESTATE g_Service = { 0 };
[6202]101
[86871]102/** Set by the signal handler when being called. */
[99585]103static volatile bool g_fSignalHandlerCalled = false;
[86871]104/** Critical section for the signal handler. */
[99585]105static RTCRITSECT g_csSignalHandler;
[99660]106/** Flag indicating Whether the service starts in daemonized mode or not. */
[99585]107bool g_fDaemonized = false;
[18360]108/** The name of our pidfile. It is global for the benefit of the cleanup
109 * routine. */
[99585]110static char g_szPidFile[RTPATH_MAX] = "";
[18360]111/** The file handle of our pidfile. It is global for the benefit of the
112 * cleanup routine. */
[99585]113static RTFILE g_hPidFile;
[98474]114/** The name of pidfile for parent (control) process. */
[99585]115static char g_szControlPidFile[RTPATH_MAX] = "";
[98474]116/** The file handle of parent process pidfile. */
[99585]117static RTFILE g_hControlPidFile;
[99620]118/** The display server type to use. */
119static VBGHDISPLAYSERVERTYPE g_enmDisplayServerType = VBGHDISPLAYSERVERTYPE_AUTO;
[98474]120
[50281]121/** Global critical section held during the clean-up routine (to prevent it
122 * being called on multiple threads at once) or things which may not happen
123 * during clean-up (e.g. pausing and resuming the service).
[32832]124 */
[81052]125static RTCRITSECT g_critSect;
[81040]126/** Counter of how often our daemon has been respawned. */
[86871]127unsigned g_cRespawn = 0;
[81040]128/** Logging verbosity level. */
[86871]129unsigned g_cVerbosity = 0;
130/** Absolute path to log file, if any. */
[81052]131static char g_szLogFile[RTPATH_MAX + 128] = "";
[98474]132/** Set by the signal handler when SIGUSR1 received. */
133static volatile bool g_fProcessReloadRequested = false;
[7264]134
[96876]135
[68562]136/**
[86871]137 * Shut down if we get a signal or something.
[68562]138 *
139 * This is extern so that we can call it from other compilation units.
140 */
[86871]141void VBClShutdown(bool fExit /*=true*/)
[6969]142{
[32832]143 /* We never release this, as we end up with a call to exit(3) which is not
[50431]144 * async-safe. Unless we fix this application properly, we should be sure
[32832]145 * never to exit from anywhere except from this method. */
[50431]146 int rc = RTCritSectEnter(&g_critSect);
[32832]147 if (RT_FAILURE(rc))
[81040]148 VBClLogFatalError("Failure while acquiring the global critical section, rc=%Rrc\n", rc);
[90053]149
[90211]150 /* Ask service to stop. */
151 if (g_Service.pDesc &&
152 g_Service.pDesc->pfnStop)
[90053]153 {
[90211]154 ASMAtomicWriteBool(&g_Service.fShutdown, true);
155 g_Service.pDesc->pfnStop();
[90053]156
157 }
158
[25954]159 if (g_szPidFile[0] && g_hPidFile)
[25942]160 VbglR3ClosePidFile(g_szPidFile, g_hPidFile);
[81052]161
162 VBClLogDestroy();
163
[68562]164 if (fExit)
165 exit(RTEXITCODE_SUCCESS);
[6969]166}
167
168/**
[99620]169 * Returns the current display server type.
[99585]170 *
[99620]171 * @returns The display server type.
[99585]172 */
[99620]173VBGHDISPLAYSERVERTYPE VBClGetDisplayServerType(void)
[99585]174{
[99620]175 return g_enmDisplayServerType;
[99585]176}
177
178/**
[6290]179 * Xlib error handler for certain errors that we can't avoid.
[6202]180 */
[52577]181static int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError)
[6202]182{
183 char errorText[1024];
184
185 XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText));
[99585]186 VBClLogError("An X Window protocol error occurred: %s (error code %d). Request code: %d, minor code: %d, serial number: %d\n",
187 errorText, pError->error_code, pError->request_code, pError->minor_code, pError->serial);
[57357]188 return 0;
[6202]189}
190
[6969]191/**
192 * Xlib error handler for fatal errors. This often means that the programme is still running
193 * when X exits.
194 */
[18360]195static int vboxClientXLibIOErrorHandler(Display *pDisplay)
[6969]196{
[62883]197 RT_NOREF1(pDisplay);
[86871]198 VBClLogError("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");
199 VBClShutdown();
[18360]200 return 0; /* We should never reach this. */
[6969]201}
202
[7264]203/**
[86871]204 * A standard signal handler which cleans up and exits.
[7264]205 */
[86871]206static void vboxClientSignalHandler(int iSignal)
[7264]207{
[101878]208 int rc;
209
210 /* On Wayland, SIGPIPE might be issued if compositor no longer wants
211 * to communicate. This should not be a reason for process termination. */
212 if (iSignal == SIGPIPE)
213 return;
214
215 rc = RTCritSectEnter(&g_csSignalHandler);
[86871]216 if (RT_SUCCESS(rc))
217 {
218 if (g_fSignalHandlerCalled)
219 {
220 RTCritSectLeave(&g_csSignalHandler);
221 return;
222 }
223
224 VBClLogVerbose(2, "Received signal %d\n", iSignal);
225 g_fSignalHandlerCalled = true;
226
[98474]227 /* In our internal convention, when VBoxClient process receives SIGUSR1,
228 * this is a trigger for restarting a process with exec() call. Usually
229 * happens as a result of Guest Additions update in order to seamlessly
230 * restart newly installed binaries. */
231 if (iSignal == SIGUSR1)
232 g_fProcessReloadRequested = true;
233
[86871]234 /* Leave critical section before stopping the service. */
235 RTCritSectLeave(&g_csSignalHandler);
236
237 if ( g_Service.pDesc
238 && g_Service.pDesc->pfnStop)
239 {
240 VBClLogVerbose(2, "Notifying service to stop ...\n");
241
242 /* Signal the service to stop. */
243 ASMAtomicWriteBool(&g_Service.fShutdown, true);
244
245 g_Service.pDesc->pfnStop();
246
247 VBClLogVerbose(2, "Service notified to stop, waiting on worker thread to stop ...\n");
248 }
249 }
250}
251
252/**
253 * Reset all standard termination signals to call our signal handler.
254 */
255static int vboxClientSignalHandlerInstall(void)
256{
[7313]257 struct sigaction sigAction;
[7264]258 sigAction.sa_handler = vboxClientSignalHandler;
259 sigemptyset(&sigAction.sa_mask);
[7313]260 sigAction.sa_flags = 0;
[7264]261 sigaction(SIGHUP, &sigAction, NULL);
262 sigaction(SIGINT, &sigAction, NULL);
263 sigaction(SIGQUIT, &sigAction, NULL);
264 sigaction(SIGPIPE, &sigAction, NULL);
265 sigaction(SIGALRM, &sigAction, NULL);
266 sigaction(SIGTERM, &sigAction, NULL);
267 sigaction(SIGUSR1, &sigAction, NULL);
268 sigaction(SIGUSR2, &sigAction, NULL);
[86871]269
270 return RTCritSectInit(&g_csSignalHandler);
[7264]271}
272
273/**
[86871]274 * Uninstalls a previously installed signal handler.
275 */
276static int vboxClientSignalHandlerUninstall(void)
277{
278 signal(SIGTERM, SIG_DFL);
279#ifdef SIGBREAK
280 signal(SIGBREAK, SIG_DFL);
281#endif
282
283 return RTCritSectDelete(&g_csSignalHandler);
284}
285
286/**
[7264]287 * Print out a usage message and exit with success.
288 */
[68562]289static void vboxClientUsage(const char *pcszFileName)
[7264]290{
[86871]291 RTPrintf(VBOX_PRODUCT " VBoxClient "
292 VBOX_VERSION_STRING "\n"
[96399]293 "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
[86871]294
[78162]295 RTPrintf("Usage: %s "
296#ifdef VBOX_WITH_SHARED_CLIPBOARD
297 "--clipboard|"
298#endif
[39454]299#ifdef VBOX_WITH_DRAG_AND_DROP
300 "--draganddrop|"
301#endif
[86871]302#ifdef VBOX_WITH_GUEST_PROPS
[39454]303 "--checkhostversion|"
304#endif
[86871]305#ifdef VBOX_WITH_SEAMLESS
[80378]306 "--seamless|"
[86871]307#endif
308#ifdef VBOX_WITH_VMSVGA
[93371]309 "--vmsvga|"
310 "--vmsvga-session"
[86871]311#endif
312 "\n[-d|--nodaemon]\n", pcszFileName);
313 RTPrintf("\n");
[10651]314 RTPrintf("Options:\n");
[78162]315#ifdef VBOX_WITH_SHARED_CLIPBOARD
[86883]316 RTPrintf(" --clipboard starts the shared clipboard service\n");
[78162]317#endif
[39454]318#ifdef VBOX_WITH_DRAG_AND_DROP
[86883]319 RTPrintf(" --draganddrop starts the drag and drop service\n");
[39454]320#endif
321#ifdef VBOX_WITH_GUEST_PROPS
[86883]322 RTPrintf(" --checkhostversion starts the host version notifier service\n");
[39454]323#endif
[86871]324#ifdef VBOX_WITH_SEAMLESS
[86883]325 RTPrintf(" --seamless starts the seamless windows service\n");
[86871]326#endif
327#ifdef VBOX_WITH_VMSVGA
[86883]328 RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for X11/Wayland guests\n");
[93385]329#ifdef RT_OS_LINUX
[93371]330 RTPrintf(" --vmsvga-session starts Desktop Environment specific screen assistant for X11/Wayland guests\n"
331 " (VMSVGA graphics adapter only)\n");
[93385]332#else
333 RTPrintf(" --vmsvga-session an alias for --vmsvga\n");
[86871]334#endif
[94306]335 RTPrintf(" --display starts VMSVGA dynamic resizing for legacy guests\n");
[93385]336#endif
[99585]337 RTPrintf(" --session-type specifies the session type to use (auto, x11, wayland)\n");
[99658]338 RTPrintf(" --session-detect detects and prints the current session type\n"
339 " (exit code 0 if detection succeeded)\n");
[100246]340#ifdef VBOX_WITH_WAYLAND_ADDITIONS
341 RTPrintf(" --wayland starts the shared clipboard and drag-and-drop services for Wayland\n");
342#endif
[86883]343 RTPrintf(" -f, --foreground run in the foreground (no daemonizing)\n");
344 RTPrintf(" -d, --nodaemon continues running as a system service\n");
345 RTPrintf(" -h, --help shows this help text\n");
346 RTPrintf(" -l, --logfile <path> enables logging to a file\n");
347 RTPrintf(" -v, --verbose increases logging verbosity level\n");
348 RTPrintf(" -V, --version shows version information\n");
[10651]349 RTPrintf("\n");
[7264]350}
351
352/**
[68562]353 * Complains about seeing more than one service specification.
354 *
355 * @returns RTEXITCODE_SYNTAX.
356 */
357static int vbclSyntaxOnlyOneService(void)
358{
359 RTMsgError("More than one service specified! Only one, please.");
360 return RTEXITCODE_SYNTAX;
361}
362
363/**
[86871]364 * The service thread.
365 *
366 * @returns Whatever the worker function returns.
367 * @param ThreadSelf My thread handle.
368 * @param pvUser The service index.
369 */
370static DECLCALLBACK(int) vbclThread(RTTHREAD ThreadSelf, void *pvUser)
371{
372 PVBCLSERVICESTATE pState = (PVBCLSERVICESTATE)pvUser;
373 AssertPtrReturn(pState, VERR_INVALID_POINTER);
374
375#ifndef RT_OS_WINDOWS
376 /*
377 * Block all signals for this thread. Only the main thread will handle signals.
378 */
379 sigset_t signalMask;
380 sigfillset(&signalMask);
381 pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
382#endif
383
384 AssertPtrReturn(pState->pDesc->pfnWorker, VERR_INVALID_POINTER);
385 int rc = pState->pDesc->pfnWorker(&pState->fShutdown);
386
387 VBClLogVerbose(2, "Worker loop ended with %Rrc\n", rc);
388
389 ASMAtomicXchgBool(&pState->fShutdown, true);
390 RTThreadUserSignal(ThreadSelf);
391 return rc;
392}
393
394/**
[98474]395 * Wait for SIGUSR1 and re-exec.
396 */
397static void vbclHandleUpdateStarted(char *const argv[])
398{
399 /* Context of parent process */
400 sigset_t signalMask;
401 int iSignal;
402 int rc;
403
404 /* Release reference to guest driver. */
405 VbglR3Term();
406
407 sigemptyset(&signalMask);
408 sigaddset(&signalMask, SIGUSR1);
409 rc = pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
410
411 if (rc == 0)
412 {
413 LogRel(("%s: waiting for Guest Additions installation to be completed\n",
414 g_Service.pDesc->pszDesc));
415
416 /* Wait for SIGUSR1. */
417 rc = sigwait(&signalMask, &iSignal);
418 if (rc == 0)
419 {
420 LogRel(("%s: Guest Additions installation to be completed, reloading service\n",
421 g_Service.pDesc->pszDesc));
422
423 /* Release pidfile, otherwise new VBoxClient instance won't be able to quire it. */
424 VBClShutdown(false);
425
426 rc = RTProcCreate(argv[0], argv, RTENV_DEFAULT,
427 RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL);
428 if (RT_SUCCESS(rc))
429 LogRel(("%s: service restarted\n", g_Service.pDesc->pszDesc));
430 else
431 LogRel(("%s: cannot replace running image with %s (%s), automatic service reloading has failed\n",
432 g_Service.pDesc->pszDesc, argv[0], strerror(errno)));
433 }
434 else
435 LogRel(("%s: cannot wait for signal (%s), automatic service reloading has failed\n",
436 g_Service.pDesc->pszDesc, strerror(errno)));
437 }
438 else
439 LogRel(("%s: failed to setup signal handler, automatic service reloading has failed\n",
440 g_Service.pDesc->pszDesc));
441
442 exit(RT_BOOL(rc != 0));
443}
444
445/**
446 * Compose pidfile name.
447 *
448 * @returns IPRT status code.
449 * @param szBuf Buffer to store pidfile name into.
450 * @param cbBuf Size of buffer.
451 * @param szTemplate Null-terminated string which contains pidfile name.
452 * @param fParentProcess Whether pidfile path should be composed for
453 * parent (control) process or for a child (actual
454 * service) process.
455 */
456static int vbclGetPidfileName(char *szBuf, size_t cbBuf, const char *szTemplate,
457 bool fParentProcess)
458{
459 int rc;
460 char pszActiveTTY[128];
461 size_t cchRead;
462
463 RT_ZERO(pszActiveTTY);
464
465 AssertPtrReturn(szBuf, VERR_INVALID_PARAMETER);
466 AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER);
467 AssertPtrReturn(szTemplate, VERR_INVALID_PARAMETER);
468
469 rc = RTPathUserHome(szBuf, cbBuf);
470 if (RT_FAILURE(rc))
471 VBClLogFatalError("%s: getting home directory failed: %Rrc\n",
472 g_Service.pDesc->pszDesc, rc);
473
474 if (RT_SUCCESS(rc))
475 rc = RTPathAppend(szBuf, cbBuf, szTemplate);
476
477#ifdef RT_OS_LINUX
478 if (RT_SUCCESS(rc))
479 rc = RTLinuxSysFsReadStrFile(pszActiveTTY, sizeof(pszActiveTTY) - 1 /* reserve last byte for string termination */,
480 &cchRead, "class/tty/tty0/active");
481 if (RT_SUCCESS(rc))
482 {
483 RTStrCat(szBuf, cbBuf, "-");
484 RTStrCat(szBuf, cbBuf, pszActiveTTY);
485 }
486 else
487 VBClLogInfo("%s: cannot detect currently active tty device, "
488 "multiple service instances for a single user will not be allowed, rc=%Rrc",
489 g_Service.pDesc->pszDesc, rc);
490#endif /* RT_OS_LINUX */
491
492 if (RT_SUCCESS(rc))
493 RTStrCat(szBuf, cbBuf, fParentProcess ? "-control.pid" : "-service.pid");
494
495 if (RT_FAILURE(rc))
496 VBClLogFatalError("%s: reating PID file path failed: %Rrc\n",
497 g_Service.pDesc->pszDesc, rc);
498
499 return rc;
500}
501
502/**
[7264]503 * The main loop for the VBoxClient daemon.
504 */
[6202]505int main(int argc, char *argv[])
506{
[86871]507 /* Note: No VBClLogXXX calls before actually creating the log. */
508
509 /* Initialize our runtime before all else. */
[68562]510 int rc = RTR3InitExe(argc, &argv, 0);
[38636]511 if (RT_FAILURE(rc))
512 return RTMsgInitFailure(rc);
[68562]513
[98474]514 /* A flag which is returned to the parent process when Guest Additions update started. */
515 bool fUpdateStarted = false;
516
[68562]517 /* Get our file name for usage info and hints. */
518 const char *pcszFileName = RTPathFilename(argv[0]);
[50464]519 if (!pcszFileName)
520 pcszFileName = "VBoxClient";
[32832]521
[81057]522 /* Parse our option(s). */
523 static const RTGETOPTDEF s_aOptions[] =
524 {
[99585]525 { "--nodaemon", 'd', RTGETOPT_REQ_NOTHING },
526 { "--foreground", 'f', RTGETOPT_REQ_NOTHING },
527 { "--help", 'h', RTGETOPT_REQ_NOTHING },
528 { "--logfile", 'l', RTGETOPT_REQ_STRING },
529 { "--version", 'V', RTGETOPT_REQ_NOTHING },
530 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
[81057]531
532 /* Services */
[86871]533#ifdef VBOX_WITH_GUEST_PROPS
[99585]534 { "--checkhostversion", VBOXCLIENT_OPT_CHECKHOSTVERSION, RTGETOPT_REQ_NOTHING },
[86871]535#endif
[81057]536#ifdef VBOX_WITH_SHARED_CLIPBOARD
[99585]537 { "--clipboard", VBOXCLIENT_OPT_CLIPBOARD, RTGETOPT_REQ_NOTHING },
[81057]538#endif
539#ifdef VBOX_WITH_DRAG_AND_DROP
[99585]540 { "--draganddrop", VBOXCLIENT_OPT_DRAGANDDROP, RTGETOPT_REQ_NOTHING },
[81057]541#endif
[86871]542#ifdef VBOX_WITH_SEAMLESS
[99585]543 { "--seamless", VBOXCLIENT_OPT_SEAMLESS, RTGETOPT_REQ_NOTHING },
[86871]544#endif
545#ifdef VBOX_WITH_VMSVGA
[99585]546 { "--vmsvga", VBOXCLIENT_OPT_VMSVGA, RTGETOPT_REQ_NOTHING },
547 { "--vmsvga-session", VBOXCLIENT_OPT_VMSVGA_SESSION, RTGETOPT_REQ_NOTHING },
548 { "--display", VBOXCLIENT_OPT_DISPLAY, RTGETOPT_REQ_NOTHING },
[86871]549#endif
[99658]550 { "--session-detect", VBOXCLIENT_OPT_SESSION_DETECT, RTGETOPT_REQ_NOTHING },
[100246]551 { "--session-type", VBOXCLIENT_OPT_SESSION_TYPE, RTGETOPT_REQ_STRING },
552#ifdef VBOX_WITH_WAYLAND_ADDITIONS
553 { "--wayland", VBOXCLIENT_OPT_WAYLAND, RTGETOPT_REQ_NOTHING }
554#endif
[81057]555 };
556
557 int ch;
558 RTGETOPTUNION ValueUnion;
559 RTGETOPTSTATE GetState;
560 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
[86871]561 if (RT_FAILURE(rc))
562 return RTMsgErrorExitFailure("Failed to parse command line options, rc=%Rrc\n", rc);
563
[81057]564 AssertRC(rc);
565
[68562]566 bool fDaemonise = true;
[81057]567 bool fRespawn = true;
568
569 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
[6202]570 {
[81057]571 /* For options that require an argument, ValueUnion has received the value. */
572 switch (ch)
[50464]573 {
[81057]574 case 'd':
575 {
576 fDaemonise = false;
577 break;
578 }
579
580 case 'h':
581 {
582 vboxClientUsage(pcszFileName);
583 return RTEXITCODE_SUCCESS;
584 }
585
586 case 'f':
587 {
588 fDaemonise = false;
589 fRespawn = false;
590 break;
591 }
592
593 case 'l':
594 {
595 rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz);
596 if (RT_FAILURE(rc))
[86871]597 return RTMsgErrorExitFailure("Unable to set log file path, rc=%Rrc\n", rc);
[81057]598 break;
599 }
600
601 case 'n':
602 {
[86871]603 fRespawn = false;
[81057]604 break;
605 }
606
607 case 'v':
608 {
609 g_cVerbosity++;
610 break;
611 }
612
613 case 'V':
614 {
615 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
616 return RTEXITCODE_SUCCESS;
617 }
618
619 /* Services */
[86871]620#ifdef VBOX_WITH_GUEST_PROPS
[83715]621 case VBOXCLIENT_OPT_CHECKHOSTVERSION:
622 {
[86871]623 if (g_Service.pDesc)
[83715]624 return vbclSyntaxOnlyOneService();
[86871]625 g_Service.pDesc = &g_SvcHostVersion;
[83715]626 break;
627 }
[86871]628#endif
[78162]629#ifdef VBOX_WITH_SHARED_CLIPBOARD
[81057]630 case VBOXCLIENT_OPT_CLIPBOARD:
631 {
[86871]632 if (g_Service.pDesc)
[81057]633 return vbclSyntaxOnlyOneService();
[86871]634 g_Service.pDesc = &g_SvcClipboard;
[81057]635 break;
636 }
[78162]637#endif
[39454]638#ifdef VBOX_WITH_DRAG_AND_DROP
[81057]639 case VBOXCLIENT_OPT_DRAGANDDROP:
640 {
[86871]641 if (g_Service.pDesc)
[81057]642 return vbclSyntaxOnlyOneService();
[86871]643 g_Service.pDesc = &g_SvcDragAndDrop;
[70126]644 break;
[81057]645 }
646#endif
[86871]647#ifdef VBOX_WITH_SEAMLESS
[81057]648 case VBOXCLIENT_OPT_SEAMLESS:
649 {
[86871]650 if (g_Service.pDesc)
[81057]651 return vbclSyntaxOnlyOneService();
[86871]652 g_Service.pDesc = &g_SvcSeamless;
[81057]653 break;
654 }
[86871]655#endif
656#ifdef VBOX_WITH_VMSVGA
[81057]657 case VBOXCLIENT_OPT_VMSVGA:
658 {
[86871]659 if (g_Service.pDesc)
[81057]660 return vbclSyntaxOnlyOneService();
[86871]661 g_Service.pDesc = &g_SvcDisplaySVGA;
[81057]662 break;
663 }
[93385]664
[93371]665 case VBOXCLIENT_OPT_VMSVGA_SESSION:
666 {
667 if (g_Service.pDesc)
668 return vbclSyntaxOnlyOneService();
[93385]669# ifdef RT_OS_LINUX
[93371]670 g_Service.pDesc = &g_SvcDisplaySVGASession;
[93385]671# else
672 g_Service.pDesc = &g_SvcDisplaySVGA;
673# endif
[93371]674 break;
675 }
[94306]676
677 case VBOXCLIENT_OPT_DISPLAY:
678 {
679 if (g_Service.pDesc)
680 return vbclSyntaxOnlyOneService();
681 g_Service.pDesc = &g_SvcDisplayLegacy;
682 break;
683 }
[86871]684#endif
[99658]685 case VBOXCLIENT_OPT_SESSION_DETECT:
686 {
687 rc = VBClLogCreateEx("" /* No file logging */, false /* No header */);
688 if (RT_SUCCESS(rc))
689 {
[99689]690 /* Make sure that we increase the verbosity (if needed), to gain some more insights
691 * when detecting the display server. */
692 rc = VBClLogModify("stdout", g_cVerbosity);
693 if (RT_SUCCESS(rc))
694 {
695 VBGHDISPLAYSERVERTYPE const enmType = VBGHDisplayServerTypeDetect();
696 VBClLogInfo("Detected session: %s\n", VBGHDisplayServerTypeToStr(enmType));
697 return enmType != VBGHDISPLAYSERVERTYPE_NONE ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
698 }
[99658]699 }
700
701 return RTEXITCODE_FAILURE;
702 }
703
[99585]704 case VBOXCLIENT_OPT_SESSION_TYPE:
705 {
706 if (!RTStrICmp(ValueUnion.psz, "x11"))
[99620]707 g_enmDisplayServerType = VBGHDISPLAYSERVERTYPE_X11;
[99585]708 else if (!RTStrICmp(ValueUnion.psz, "wayland"))
[100063]709 g_enmDisplayServerType = VBGHDISPLAYSERVERTYPE_PURE_WAYLAND;
[99585]710 else if (!RTStrICmp(ValueUnion.psz, "none"))
[99620]711 g_enmDisplayServerType = VBGHDISPLAYSERVERTYPE_NONE;
[99585]712 else if (!RTStrICmp(ValueUnion.psz, "auto"))
[99620]713 g_enmDisplayServerType = VBGHDISPLAYSERVERTYPE_AUTO;
[99585]714 else
715 {
716 RTMsgError("Session type \"%s\" is invalid; defaulting to \"auto\" instead.\n", ValueUnion.psz);
[99620]717 g_enmDisplayServerType = VBGHDISPLAYSERVERTYPE_AUTO;
[99585]718 }
719 break;
720 }
[100246]721#ifdef VBOX_WITH_WAYLAND_ADDITIONS
722 case VBOXCLIENT_OPT_WAYLAND:
723 {
724 if (g_Service.pDesc)
725 return vbclSyntaxOnlyOneService();
726 g_Service.pDesc = &g_SvcWayland;
727 break;
728 }
729#endif
[99585]730
[86871]731 case VINF_GETOPT_NOT_OPTION:
732 break;
[81057]733
734 case VERR_GETOPT_UNKNOWN_OPTION:
[86871]735 RT_FALL_THROUGH();
736 default:
[81057]737 {
[86871]738 if ( g_Service.pDesc
739 && g_Service.pDesc->pfnOption)
740 {
741 rc = g_Service.pDesc->pfnOption(NULL, argc, argv, &GetState.iNext);
742 }
743 else /* No service specified yet. */
744 rc = VERR_NOT_FOUND;
[81057]745
[86871]746 if (RT_FAILURE(rc))
747 {
748 RTMsgError("unrecognized option '%s'", ValueUnion.psz);
749 RTMsgInfo("Try '%s --help' for more information", pcszFileName);
750 return RTEXITCODE_SYNTAX;
751 }
[81057]752 break;
[86871]753 }
[81057]754
755 } /* switch */
756 } /* while RTGetOpt */
757
[86871]758 if (!g_Service.pDesc)
759 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No service specified. Quitting because nothing to do!");
[25942]760
[81038]761 /* Initialize VbglR3 before we do anything else with the logger. */
762 rc = VbglR3InitUser();
763 if (RT_FAILURE(rc))
[86871]764 return RTMsgErrorExitFailure("VbglR3InitUser failed: %Rrc", rc);
[81038]765
[93371]766 rc = VBClLogCreate(g_szLogFile[0] ? g_szLogFile : "");
[81052]767 if (RT_FAILURE(rc))
[99658]768 return RTEXITCODE_FAILURE; /* Error message already printed in VBClLogCreateEx(). */
[81052]769
[99689]770 /* If the user is running in "no daemon" mode, send critical logging to stdout as well. */
771 rc = VBClLogModify(fDaemonise ? "" : "stdout", g_cVerbosity);
772 if (RT_FAILURE(rc))
773 return RTEXITCODE_FAILURE; /* Error message already printed in VBClLogModify(). */
[81038]774
[99585]775 VBClLogInfo("VBoxClient %s r%s started. Verbose level = %d\n",
776 RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity);
777
778 /* Try to detect the current session type early on, if needed. */
[99620]779 if (g_enmDisplayServerType == VBGHDISPLAYSERVERTYPE_AUTO)
[99585]780 {
[99620]781 g_enmDisplayServerType = VBGHDisplayServerTypeDetect();
[99585]782 }
783 else
[99620]784 VBClLogInfo("Session type was manually set to: %s\n", VBGHDisplayServerTypeToStr(g_enmDisplayServerType));
[99585]785
[99620]786 VBClLogInfo("Session type is: %s\n", VBGHDisplayServerTypeToStr(g_enmDisplayServerType));
[99585]787
[86871]788 VBClLogInfo("Service: %s\n", g_Service.pDesc->pszDesc);
789
[52577]790 rc = RTCritSectInit(&g_critSect);
[50281]791 if (RT_FAILURE(rc))
[86871]792 VBClLogFatalError("Initializing critical section failed: %Rrc\n", rc);
[98474]793 if (g_Service.pDesc->pszPidFilePathTemplate)
[58993]794 {
[98474]795 /* Get pidfile name for parent (control) process. */
796 rc = vbclGetPidfileName(g_szControlPidFile, sizeof(g_szControlPidFile), g_Service.pDesc->pszPidFilePathTemplate, true);
[58993]797 if (RT_FAILURE(rc))
[98474]798 return RTEXITCODE_FAILURE;
799
800 /* Get pidfile name for service process. */
801 rc = vbclGetPidfileName(g_szPidFile, sizeof(g_szPidFile), g_Service.pDesc->pszPidFilePathTemplate, false);
[58993]802 if (RT_FAILURE(rc))
[98474]803 return RTEXITCODE_FAILURE;
[93371]804 }
805
806 if (fDaemonise)
[98474]807 {
[99660]808 VBClLogInfo("Daemonizing service ...\n");
[98474]809 rc = VbglR3DaemonizeEx(false /* fNoChDir */, false /* fNoClose */, fRespawn, &g_cRespawn,
810 true /* fReturnOnUpdate */, &fUpdateStarted, g_szControlPidFile, &g_hControlPidFile);
[99660]811 if (RT_SUCCESS(rc))
812 {
813 g_fDaemonized = true;
814
815 if (fUpdateStarted) /* This combination only works in context of parent process. */
816 vbclHandleUpdateStarted(argv);
817 }
818 else
819 return RTMsgErrorExitFailure("Daemonizing service failed: %Rrc\n", rc);
[98474]820 }
821
[93371]822 if (g_szPidFile[0])
823 {
824 rc = VbglR3PidFile(g_szPidFile, &g_hPidFile);
[58993]825 if (rc == VERR_FILE_LOCK_VIOLATION) /* Already running. */
[98474]826 {
827 VBClLogInfo("%s: service already running, exitting\n",
828 g_Service.pDesc->pszDesc);
[60980]829 return RTEXITCODE_SUCCESS;
[98474]830 }
[58993]831 if (RT_FAILURE(rc))
[98474]832 {
833 VBClLogFatalError("Creating PID file %s failed: %Rrc\n", g_szPidFile, rc);
834 return RTEXITCODE_FAILURE;
835 }
[58993]836 }
[86871]837
[99620]838 if (g_enmDisplayServerType == VBGHDISPLAYSERVERTYPE_X11)
[99585]839 {
840 /* This should never be called twice in one process - in fact one Display
841 * object should probably never be used from multiple threads anyway. */
842 if (!XInitThreads())
843 return RTMsgErrorExitFailure("Failed to initialize X11 threads\n");
844 /* Set an X11 error handler, so that we don't die when we get unavoidable
845 * errors. */
846 XSetErrorHandler(vboxClientXLibErrorHandler);
847 /* Set an X11 I/O error handler, so that we can shutdown properly on
848 * fatal errors. */
849 XSetIOErrorHandler(vboxClientXLibIOErrorHandler);
850 }
[86871]851
852 bool fSignalHandlerInstalled = false;
[60980]853 if (RT_SUCCESS(rc))
854 {
[86871]855 rc = vboxClientSignalHandlerInstall();
856 if (RT_SUCCESS(rc))
857 fSignalHandlerInstalled = true;
858 }
859
860 if ( RT_SUCCESS(rc)
861 && g_Service.pDesc->pfnInit)
862 {
863 VBClLogInfo("Initializing service ...\n");
864 rc = g_Service.pDesc->pfnInit();
865 }
866
867 if (RT_SUCCESS(rc))
868 {
869 VBClLogInfo("Creating worker thread ...\n");
870 rc = RTThreadCreate(&g_Service.Thread, vbclThread, (void *)&g_Service, 0,
871 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_Service.pDesc->pszName);
[60980]872 if (RT_FAILURE(rc))
[86871]873 {
874 VBClLogError("Creating worker thread failed, rc=%Rrc\n", rc);
875 }
876 else
877 {
878 g_Service.fStarted = true;
879
880 /* Wait for the thread to initialize. */
881 /** @todo There is a race between waiting and checking
882 * the fShutdown flag of a thread here and processing
883 * the thread's actual worker loop. If the thread decides
884 * to exit the loop before we skipped the fShutdown check
885 * below the service will fail to start! */
886 /** @todo This presumably means either a one-shot service or that
887 * something has gone wrong. In the second case treating it as failure
888 * to start is probably right, so we need a way to signal the first
889 * rather than leaving the idle thread hanging around. A flag in the
890 * service description? */
891 RTThreadUserWait(g_Service.Thread, RT_MS_1MIN);
892 if (g_Service.fShutdown)
893 {
894 VBClLogError("Service failed to start!\n");
895 rc = VERR_GENERAL_FAILURE;
896 }
897 else
898 {
899 VBClLogInfo("Service started\n");
900
901 int rcThread;
902 rc = RTThreadWait(g_Service.Thread, RT_INDEFINITE_WAIT, &rcThread);
903 if (RT_SUCCESS(rc))
904 rc = rcThread;
905
906 if (RT_FAILURE(rc))
907 VBClLogError("Waiting on worker thread to stop failed, rc=%Rrc\n", rc);
908
909 if (g_Service.pDesc->pfnTerm)
910 {
911 VBClLogInfo("Terminating service\n");
912
913 int rc2 = g_Service.pDesc->pfnTerm();
914 if (RT_SUCCESS(rc))
915 rc = rc2;
916
917 if (RT_SUCCESS(rc))
918 {
919 VBClLogInfo("Service terminated\n");
920 }
921 else
922 VBClLogError("Service failed to terminate, rc=%Rrc\n", rc);
923 }
924 }
925 }
[60980]926 }
[86871]927
928 if (RT_FAILURE(rc))
929 {
930 if (rc == VERR_NOT_AVAILABLE)
931 VBClLogInfo("Service is not availabe, skipping\n");
932 else if (rc == VERR_NOT_SUPPORTED)
933 VBClLogInfo("Service is not supported on this platform, skipping\n");
934 else
935 VBClLogError("Service ended with error %Rrc\n", rc);
936 }
[60980]937 else
[86871]938 VBClLogVerbose(2, "Service ended\n");
939
940 if (fSignalHandlerInstalled)
[60980]941 {
[86871]942 int rc2 = vboxClientSignalHandlerUninstall();
943 AssertRC(rc2);
[60980]944 }
[86871]945
946 VBClShutdown(false /*fExit*/);
947
948 /** @todo r=andy Should we return an appropriate exit code if the service failed to init?
949 * Must be tested carefully with our init scripts first. */
[98474]950 return g_fProcessReloadRequested ? VBGLR3EXITCODERELOAD : RTEXITCODE_SUCCESS;
[6202]951}
[86871]952
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use