VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp

Last change on this file was 98474, checked in by vboxsync, 15 months ago

Additions: X11: Add possibility restart VBoxClient processes during Guest Additions update, bugref:10359.

This commit makes VBoxClient processes to temporary release reference to vboxguest kernel module and then
restart itself. So module can be reloaded and newly started VBoxClient instance will utilize updated module.

When VBoxClient starts in demonized mode, it forks a child process which represents actual service. Parent
process continues to run and its main function is to restart child when it crashes or terminates with exit
status != 0. This commit makes child process to catch SIGUSR1 and terminate with exit
status VBGLR3EXITCODERELOAD (currently equal to 2). Parent process will detect this and in turn will release
its reference to vboxguest kernel module, allowing to reload it. The parent process will then wait for SIGUSR1
itself. Once received, it will restart itself (loading new, updated VBoxClient process image). This is a part
of the procedure to install and reload/restart Guest Additions kernel modules and user services without
requiring guest reboot.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.1 KB
Line 
1/* $Id: display-svga-session.cpp 98474 2023-02-03 19:20:53Z vboxsync $ */
2/** @file
3 * Guest Additions - VMSVGA Desktop Environment user session assistant.
4 *
5 * This service connects to VBoxDRMClient IPC server, listens for
6 * its commands and reports current display offsets to it. If IPC
7 * server is not available, it forks legacy 'VBoxClient --vmsvga
8 * service and terminates.
9 */
10
11/*
12 * Copyright (C) 2017-2023 Oracle and/or its affiliates.
13 *
14 * This file is part of VirtualBox base platform packages, as
15 * available from https://www.virtualbox.org.
16 *
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU General Public License
19 * as published by the Free Software Foundation, in version 3 of the
20 * License.
21 *
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, see <https://www.gnu.org/licenses>.
29 *
30 * SPDX-License-Identifier: GPL-3.0-only
31 */
32
33/*
34 * This service is an IPC client for VBoxDRMClient daemon. It is also
35 * a proxy bridge to a Desktop Environment specific code (so called
36 * Desktop Environment helpers).
37 *
38 * Once started, it will try to enumerate and probe all the registered
39 * helpers and if appropriate helper found, it will forward incoming IPC
40 * commands to it as well as send helper's commands back to VBoxDRMClient.
41 * Generic helper is a special one. It will be used by default if all the
42 * other helpers are failed on probe. Moreover, generic helper provides
43 * helper functions that can be used by other helpers as well. For example,
44 * once Gnome3 Desktop Environment is running on X11, it will be also use
45 * display offsets change notification monitor of a generic helper.
46 *
47 * Multiple instances of this daemon are allowed to run in parallel
48 * with the following limitations.
49 * A single user cannot run multiple daemon instances per single TTY device,
50 * however, multiple instances are allowed for the user on different
51 * TTY devices (i.e. in case if user runs multiple X servers on different
52 * terminals). On multiple TTY devices multiple users can run multiple
53 * daemon instances (i.e. in case of "switch user" DE configuration when
54 * multiple X/Wayland servers are running on separate TTY devices).
55 */
56
57#include "VBoxClient.h"
58#include "display-ipc.h"
59#include "display-helper.h"
60
61#include <VBox/VBoxGuestLib.h>
62
63#include <iprt/localipc.h>
64#include <iprt/asm.h>
65#include <iprt/errcore.h>
66#include <iprt/path.h>
67#include <iprt/linux/sysfs.h>
68
69
70/** Handle to IPC client connection. */
71VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
72
73/** IPC client handle critical section. */
74static RTCRITSECT g_hClientCritSect;
75
76/** List of available Desktop Environment specific display helpers. */
77static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] =
78{
79 &g_DisplayHelperGnome3, /* GNOME3 helper. */
80 &g_DisplayHelperGeneric, /* Generic helper. */
81 NULL, /* Terminate list. */
82};
83
84/** Selected Desktop Environment specific display helper. */
85static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL;
86
87/** IPC connection session handle. */
88static RTLOCALIPCSESSION g_hSession = 0;
89
90/**
91 * Callback for display offsets change events provided by Desktop Environment specific display helper.
92 *
93 * @returns IPRT status code.
94 * @param cDisplays Number of displays which have changed offset.
95 * @param aDisplays Display data.
96 */
97static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
98{
99 int rc = RTCritSectEnter(&g_hClientCritSect);
100
101 if (RT_SUCCESS(rc))
102 {
103 rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays);
104 int rc2 = RTCritSectLeave(&g_hClientCritSect);
105 if (RT_FAILURE(rc2))
106 VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2);
107 }
108 else
109 VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc);
110
111 return rc;
112}
113
114/**
115 * @interface_method_impl{VBCLSERVICE,pfnInit}
116 */
117static DECLCALLBACK(int) vbclSVGASessionInit(void)
118{
119 int rc;
120 RTLOCALIPCSESSION hSession;
121 int idxDisplayHelper = 0;
122
123 /** Custom log prefix to be used for logger instance of this process. */
124 static const char *pszLogPrefix = "VBoxClient VMSVGA:";
125
126 VBClLogSetLogPrefix(pszLogPrefix);
127
128 rc = RTCritSectInit(&g_hClientCritSect);
129 if (RT_FAILURE(rc))
130 {
131 VBClLogError("unable to init locking, rc=%Rrc\n", rc);
132 return rc;
133 }
134
135 /* Go through list of available Desktop Environment specific helpers and try to pick up one. */
136 while (g_apDisplayHelpers[idxDisplayHelper])
137 {
138 if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe)
139 {
140 VBClLogInfo("probing Desktop Environment helper '%s'\n",
141 g_apDisplayHelpers[idxDisplayHelper]->pszName);
142
143 rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe();
144
145 /* Found compatible helper. */
146 if (RT_SUCCESS(rc))
147 {
148 /* Initialize it. */
149 if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit)
150 {
151 rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit();
152 }
153
154 /* Some helpers might have no .pfnInit(), that's ok. */
155 if (RT_SUCCESS(rc))
156 {
157 /* Subscribe to display offsets change event. */
158 if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification)
159 {
160 g_apDisplayHelpers[idxDisplayHelper]->
161 pfnSubscribeDisplayOffsetChangeNotification(
162 vbclSVGASessionDisplayOffsetChanged);
163 }
164
165 g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper];
166 break;
167 }
168 else
169 VBClLogError("compatible Desktop Environment "
170 "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc);
171 }
172 }
173
174 idxDisplayHelper++;
175 }
176
177 /* Make sure we found compatible Desktop Environment specific helper. */
178 if (g_pDisplayHelper)
179 {
180 VBClLogInfo("using Desktop Environment specific display helper '%s'\n",
181 g_pDisplayHelper->pszName);
182 }
183 else
184 {
185 VBClLogError("unable to find Desktop Environment specific display helper\n");
186 return VERR_NOT_IMPLEMENTED;
187 }
188
189 /* Attempt to connect to VBoxDRMClient IPC server. */
190 rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0);
191 if (RT_SUCCESS(rc))
192 {
193 g_hSession = hSession;
194 }
195 else
196 VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc);
197
198 /* We cannot initialize ourselves, start legacy service and terminate. */
199 if (RT_FAILURE(rc))
200 {
201 /* Free helper resources. */
202 if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
203 g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
204
205 if (g_pDisplayHelper->pfnTerm)
206 {
207 rc = g_pDisplayHelper->pfnTerm();
208 VBClLogInfo("helper service terminated, rc=%Rrc\n", rc);
209 }
210
211 rc = VbglR3DrmLegacyClientStart();
212 VBClLogInfo("starting legacy service, rc=%Rrc\n", rc);
213 /* Force return status, so parent thread wont be trying to start worker thread. */
214 rc = VERR_NOT_AVAILABLE;
215 }
216
217 return rc;
218}
219
220/**
221 * A callback function which is triggered on IPC data receive.
222 *
223 * @returns IPRT status code.
224 * @param idCmd DRM IPC command ID.
225 * @param pvData DRM IPC command payload.
226 * @param cbData Size of DRM IPC command payload.
227 */
228static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
229{
230 VBOXDRMIPCCLTCMD enmCmd =
231 (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ?
232 (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID;
233
234 int rc = VERR_INVALID_PARAMETER;
235
236 AssertReturn(pvData, VERR_INVALID_PARAMETER);
237 AssertReturn(cbData, VERR_INVALID_PARAMETER);
238 AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER);
239
240 switch (enmCmd)
241 {
242 case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY:
243 {
244 if (g_pDisplayHelper->pfnSetPrimaryDisplay)
245 {
246 PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData;
247 static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX;
248
249 if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX
250 && idPrimaryDisplayCached != pCmd->idDisplay)
251 {
252 rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay);
253 /* Update cache. */
254 idPrimaryDisplayCached = pCmd->idDisplay;
255 }
256 else
257 VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay);
258 }
259
260 break;
261 }
262 default:
263 {
264 VBClLogError("received unknown IPC command 0x%x\n", idCmd);
265 break;
266 }
267 }
268
269 return rc;
270}
271
272/**
273 * Reconnect to DRM IPC server.
274 */
275static int vbclSVGASessionReconnect(void)
276{
277 int rc = VERR_GENERAL_FAILURE;
278
279 rc = RTCritSectEnter(&g_hClientCritSect);
280 if (RT_FAILURE(rc))
281 {
282 VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc);
283 return rc;
284 }
285
286 /* Check if session was not closed before. */
287 if (RT_VALID_PTR(g_hSession))
288 {
289 rc = RTLocalIpcSessionClose(g_hSession);
290 if (RT_FAILURE(rc))
291 VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc);
292
293 rc = vbDrmIpcClientReleaseResources(&g_hClient);
294 if (RT_FAILURE(rc))
295 VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc);
296 }
297
298 rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0);
299 if (RT_SUCCESS(rc))
300 {
301 rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
302 if (RT_FAILURE(rc))
303 VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc);
304 }
305 else
306 VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc);
307
308 int rc2 = RTCritSectLeave(&g_hClientCritSect);
309 if (RT_FAILURE(rc2))
310 VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc);
311
312 return rc;
313}
314
315/**
316 * @interface_method_impl{VBCLSERVICE,pfnWorker}
317 */
318static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown)
319{
320 int rc = VINF_SUCCESS;
321
322 /* Notify parent thread that we started successfully. */
323 rc = RTThreadUserSignal(RTThreadSelf());
324 if (RT_FAILURE(rc))
325 VBClLogError("unable to notify parent thread about successful start\n");
326
327 rc = RTCritSectEnter(&g_hClientCritSect);
328
329 if (RT_FAILURE(rc))
330 {
331 VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc);
332 return rc;
333 }
334
335 rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
336 int rc2 = RTCritSectLeave(&g_hClientCritSect);
337 if (RT_FAILURE(rc2))
338 VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc);
339
340 if (RT_FAILURE(rc))
341 {
342 VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc);
343 return rc;
344 }
345
346 for (;;)
347 {
348 rc = vbDrmIpcConnectionHandler(&g_hClient);
349
350 /* Try to shutdown thread as soon as possible. */
351 if (ASMAtomicReadBool(pfShutdown))
352 {
353 /* Shutdown requested. */
354 break;
355 }
356
357 /* Normal case, there was no incoming messages for a while. */
358 if (rc == VERR_TIMEOUT)
359 {
360 continue;
361 }
362 else if (RT_FAILURE(rc))
363 {
364 VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc);
365
366 /* Relax a bit before spinning the loop. */
367 RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
368 /* Try to reconnect to server. */
369 rc = vbclSVGASessionReconnect();
370 }
371 }
372
373 /* Check if session was not closed before. */
374 if (RT_VALID_PTR(g_hSession))
375 {
376 rc2 = RTCritSectEnter(&g_hClientCritSect);
377 if (RT_SUCCESS(rc2))
378 {
379 rc2 = vbDrmIpcClientReleaseResources(&g_hClient);
380 if (RT_FAILURE(rc2))
381 VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2);
382
383 rc2 = RTCritSectLeave(&g_hClientCritSect);
384 if (RT_FAILURE(rc2))
385 VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc);
386 }
387 else
388 VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc);
389 }
390
391 return rc;
392}
393
394/**
395 * @interface_method_impl{VBCLSERVICE,pfnStop}
396 */
397static DECLCALLBACK(void) vbclSVGASessionStop(void)
398{
399 int rc;
400
401 /* Check if session was not closed before. */
402 if (!RT_VALID_PTR(g_hSession))
403 return;
404
405 /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */
406 rc = RTLocalIpcSessionFlush(g_hSession);
407 if (RT_FAILURE(rc))
408 VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc);
409
410 rc = RTLocalIpcSessionCancel(g_hSession);
411 if (RT_FAILURE(rc))
412 VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc);
413}
414
415/**
416 * @interface_method_impl{VBCLSERVICE,pfnTerm}
417 */
418static DECLCALLBACK(int) vbclSVGASessionTerm(void)
419{
420 int rc = VINF_SUCCESS;
421
422 if (g_hSession)
423 {
424 rc = RTLocalIpcSessionClose(g_hSession);
425 g_hSession = 0;
426
427 if (RT_FAILURE(rc))
428 VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc);
429 }
430
431 if (g_pDisplayHelper)
432 {
433 if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
434 g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
435
436 if (g_pDisplayHelper->pfnTerm)
437 {
438 rc = g_pDisplayHelper->pfnTerm();
439 if (RT_FAILURE(rc))
440 VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n",
441 rc, g_pDisplayHelper->pszName);
442 }
443 }
444
445 return VINF_SUCCESS;
446}
447
448VBCLSERVICE g_SvcDisplaySVGASession =
449{
450 "vmsvga-session", /* szName */
451 "VMSVGA display assistant", /* pszDescription */
452 ".vboxclient-vmsvga-session", /* pszPidFilePathTemplate */
453 NULL, /* pszUsage */
454 NULL, /* pszOptions */
455 NULL, /* pfnOption */
456 vbclSVGASessionInit, /* pfnInit */
457 vbclSVGASessionWorker, /* pfnWorker */
458 vbclSVGASessionStop, /* pfnStop */
459 vbclSVGASessionTerm, /* pfnTerm */
460};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use