VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.3 KB
Line 
1/* $Id: display-svga-session.cpp 98103 2023-01-17 14:15:46Z 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 (see also vbclSVGASessionPidFileLock()).
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/** Lock file handle. */
70static RTFILE g_hPidFile;
71/** Full path to PID lock file. */
72static char g_szPidFilePath[RTPATH_MAX];
73
74/** Handle to IPC client connection. */
75VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
76
77/** IPC client handle critical section. */
78static RTCRITSECT g_hClientCritSect;
79
80/** List of available Desktop Environment specific display helpers. */
81static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] =
82{
83 &g_DisplayHelperGnome3, /* GNOME3 helper. */
84 &g_DisplayHelperGeneric, /* Generic helper. */
85 NULL, /* Terminate list. */
86};
87
88/** Selected Desktop Environment specific display helper. */
89static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL;
90
91/** IPC connection session handle. */
92static RTLOCALIPCSESSION g_hSession = 0;
93
94/**
95 * Callback for display offsets change events provided by Desktop Environment specific display helper.
96 *
97 * @returns IPRT status code.
98 * @param cDisplays Number of displays which have changed offset.
99 * @param aDisplays Display data.
100 */
101static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
102{
103 int rc = RTCritSectEnter(&g_hClientCritSect);
104
105 if (RT_SUCCESS(rc))
106 {
107 rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays);
108 int rc2 = RTCritSectLeave(&g_hClientCritSect);
109 if (RT_FAILURE(rc2))
110 VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2);
111 }
112 else
113 VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc);
114
115 return rc;
116}
117
118/**
119 * Prevent multiple instances of the service from start.
120 *
121 * @returns IPRT status code.
122 */
123static int vbclSVGASessionPidFileLock(void)
124{
125 int rc;
126
127 /* Allow parallel running instances of the service for processes
128 * which are running in separate X11/Wayland sessions. Compose
129 * custom PID file name based on currently active TTY device. */
130
131 char *pszPidFileName = RTStrAlloc(RTPATH_MAX);
132 if (pszPidFileName)
133 {
134 rc = RTPathUserHome(g_szPidFilePath, sizeof(g_szPidFilePath));
135 if (RT_SUCCESS(rc))
136 {
137 char pszActiveTTY[128];
138 size_t cchRead;
139
140 RT_ZERO(pszActiveTTY);
141
142 RTStrAAppend(&pszPidFileName, ".vboxclient-vmsvga-session");
143
144 rc = RTLinuxSysFsReadStrFile(pszActiveTTY, sizeof(pszActiveTTY) - 1 /* reserve last byte for string termination */,
145 &cchRead, "class/tty/tty0/active");
146 if (RT_SUCCESS(rc))
147 {
148 RTStrAAppend(&pszPidFileName, "-");
149 RTStrAAppend(&pszPidFileName, pszActiveTTY);
150 }
151 else
152 VBClLogInfo("cannot detect currently active tty device, "
153 "multiple service instances for a single user will not be allowed, rc=%Rrc", rc);
154
155 RTStrAAppend(&pszPidFileName, ".pid");
156
157 RTPathAppend(g_szPidFilePath, sizeof(g_szPidFilePath), pszPidFileName);
158
159 VBClLogVerbose(1, "lock file path: %s\n", g_szPidFilePath);
160 rc = VbglR3PidFile(g_szPidFilePath, &g_hPidFile);
161 }
162 else
163 VBClLogError("unable to get user home directory, rc=%Rrc\n", rc);
164
165 RTStrFree(pszPidFileName);
166 }
167 else
168 rc = VERR_NO_MEMORY;
169
170 return rc;
171}
172
173/**
174 * Release lock file.
175 */
176static void vbclSVGASessionPidFileRelease(void)
177{
178 VbglR3ClosePidFile(g_szPidFilePath, g_hPidFile);
179}
180
181/**
182 * @interface_method_impl{VBCLSERVICE,pfnInit}
183 */
184static DECLCALLBACK(int) vbclSVGASessionInit(void)
185{
186 int rc;
187 RTLOCALIPCSESSION hSession;
188 int idxDisplayHelper = 0;
189
190 /** Custom log prefix to be used for logger instance of this process. */
191 static const char *pszLogPrefix = "VBoxClient VMSVGA:";
192
193 VBClLogSetLogPrefix(pszLogPrefix);
194
195 rc = vbclSVGASessionPidFileLock();
196 if (RT_FAILURE(rc))
197 {
198 VBClLogVerbose(1, "cannot acquire pid lock, rc=%Rrc\n", rc);
199 return rc;
200 }
201
202 rc = RTCritSectInit(&g_hClientCritSect);
203 if (RT_FAILURE(rc))
204 {
205 VBClLogError("unable to init locking, rc=%Rrc\n", rc);
206 return rc;
207 }
208
209 /* Go through list of available Desktop Environment specific helpers and try to pick up one. */
210 while (g_apDisplayHelpers[idxDisplayHelper])
211 {
212 if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe)
213 {
214 VBClLogInfo("probing Desktop Environment helper '%s'\n",
215 g_apDisplayHelpers[idxDisplayHelper]->pszName);
216
217 rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe();
218
219 /* Found compatible helper. */
220 if (RT_SUCCESS(rc))
221 {
222 /* Initialize it. */
223 if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit)
224 {
225 rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit();
226 }
227
228 /* Some helpers might have no .pfnInit(), that's ok. */
229 if (RT_SUCCESS(rc))
230 {
231 /* Subscribe to display offsets change event. */
232 if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification)
233 {
234 g_apDisplayHelpers[idxDisplayHelper]->
235 pfnSubscribeDisplayOffsetChangeNotification(
236 vbclSVGASessionDisplayOffsetChanged);
237 }
238
239 g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper];
240 break;
241 }
242 else
243 VBClLogError("compatible Desktop Environment "
244 "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc);
245 }
246 }
247
248 idxDisplayHelper++;
249 }
250
251 /* Make sure we found compatible Desktop Environment specific helper. */
252 if (g_pDisplayHelper)
253 {
254 VBClLogInfo("using Desktop Environment specific display helper '%s'\n",
255 g_pDisplayHelper->pszName);
256 }
257 else
258 {
259 VBClLogError("unable to find Desktop Environment specific display helper\n");
260 return VERR_NOT_IMPLEMENTED;
261 }
262
263 /* Attempt to connect to VBoxDRMClient IPC server. */
264 rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0);
265 if (RT_SUCCESS(rc))
266 {
267 g_hSession = hSession;
268 }
269 else
270 VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc);
271
272 /* We cannot initialize ourselves, start legacy service and terminate. */
273 if (RT_FAILURE(rc))
274 {
275 /* Free helper resources. */
276 if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
277 g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
278
279 if (g_pDisplayHelper->pfnTerm)
280 {
281 rc = g_pDisplayHelper->pfnTerm();
282 VBClLogInfo("helper service terminated, rc=%Rrc\n", rc);
283 }
284
285 rc = VbglR3DrmLegacyClientStart();
286 VBClLogInfo("starting legacy service, rc=%Rrc\n", rc);
287 /* Force return status, so parent thread wont be trying to start worker thread. */
288 rc = VERR_NOT_AVAILABLE;
289 }
290
291 return rc;
292}
293
294/**
295 * A callback function which is triggered on IPC data receive.
296 *
297 * @returns IPRT status code.
298 * @param idCmd DRM IPC command ID.
299 * @param pvData DRM IPC command payload.
300 * @param cbData Size of DRM IPC command payload.
301 */
302static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
303{
304 VBOXDRMIPCCLTCMD enmCmd =
305 (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ?
306 (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID;
307
308 int rc = VERR_INVALID_PARAMETER;
309
310 AssertReturn(pvData, VERR_INVALID_PARAMETER);
311 AssertReturn(cbData, VERR_INVALID_PARAMETER);
312 AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER);
313
314 switch (enmCmd)
315 {
316 case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY:
317 {
318 if (g_pDisplayHelper->pfnSetPrimaryDisplay)
319 {
320 PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData;
321 static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX;
322
323 if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX
324 && idPrimaryDisplayCached != pCmd->idDisplay)
325 {
326 rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay);
327 /* Update cache. */
328 idPrimaryDisplayCached = pCmd->idDisplay;
329 }
330 else
331 VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay);
332 }
333
334 break;
335 }
336 default:
337 {
338 VBClLogError("received unknown IPC command 0x%x\n", idCmd);
339 break;
340 }
341 }
342
343 return rc;
344}
345
346/**
347 * Reconnect to DRM IPC server.
348 */
349static int vbclSVGASessionReconnect(void)
350{
351 int rc = VERR_GENERAL_FAILURE;
352
353 rc = RTCritSectEnter(&g_hClientCritSect);
354 if (RT_FAILURE(rc))
355 {
356 VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc);
357 return rc;
358 }
359
360 /* Check if session was not closed before. */
361 if (RT_VALID_PTR(g_hSession))
362 {
363 rc = RTLocalIpcSessionClose(g_hSession);
364 if (RT_FAILURE(rc))
365 VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc);
366
367 rc = vbDrmIpcClientReleaseResources(&g_hClient);
368 if (RT_FAILURE(rc))
369 VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc);
370 }
371
372 rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0);
373 if (RT_SUCCESS(rc))
374 {
375 rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
376 if (RT_FAILURE(rc))
377 VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc);
378 }
379 else
380 VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc);
381
382 int rc2 = RTCritSectLeave(&g_hClientCritSect);
383 if (RT_FAILURE(rc2))
384 VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc);
385
386 return rc;
387}
388
389/**
390 * @interface_method_impl{VBCLSERVICE,pfnWorker}
391 */
392static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown)
393{
394 int rc = VINF_SUCCESS;
395
396 /* Notify parent thread that we started successfully. */
397 rc = RTThreadUserSignal(RTThreadSelf());
398 if (RT_FAILURE(rc))
399 VBClLogError("unable to notify parent thread about successful start\n");
400
401 rc = RTCritSectEnter(&g_hClientCritSect);
402
403 if (RT_FAILURE(rc))
404 {
405 VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc);
406 return rc;
407 }
408
409 rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
410 int rc2 = RTCritSectLeave(&g_hClientCritSect);
411 if (RT_FAILURE(rc2))
412 VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc);
413
414 if (RT_FAILURE(rc))
415 {
416 VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc);
417 return rc;
418 }
419
420 for (;;)
421 {
422 rc = vbDrmIpcConnectionHandler(&g_hClient);
423
424 /* Try to shutdown thread as soon as possible. */
425 if (ASMAtomicReadBool(pfShutdown))
426 {
427 /* Shutdown requested. */
428 break;
429 }
430
431 /* Normal case, there was no incoming messages for a while. */
432 if (rc == VERR_TIMEOUT)
433 {
434 continue;
435 }
436 else if (RT_FAILURE(rc))
437 {
438 VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc);
439
440 /* Relax a bit before spinning the loop. */
441 RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
442 /* Try to reconnect to server. */
443 rc = vbclSVGASessionReconnect();
444 }
445 }
446
447 /* Check if session was not closed before. */
448 if (RT_VALID_PTR(g_hSession))
449 {
450 rc2 = RTCritSectEnter(&g_hClientCritSect);
451 if (RT_SUCCESS(rc2))
452 {
453 rc2 = vbDrmIpcClientReleaseResources(&g_hClient);
454 if (RT_FAILURE(rc2))
455 VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2);
456
457 rc2 = RTCritSectLeave(&g_hClientCritSect);
458 if (RT_FAILURE(rc2))
459 VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc);
460 }
461 else
462 VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc);
463 }
464
465 return rc;
466}
467
468/**
469 * @interface_method_impl{VBCLSERVICE,pfnStop}
470 */
471static DECLCALLBACK(void) vbclSVGASessionStop(void)
472{
473 int rc;
474
475 /* Check if session was not closed before. */
476 if (!RT_VALID_PTR(g_hSession))
477 return;
478
479 /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */
480 rc = RTLocalIpcSessionFlush(g_hSession);
481 if (RT_FAILURE(rc))
482 VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc);
483
484 rc = RTLocalIpcSessionCancel(g_hSession);
485 if (RT_FAILURE(rc))
486 VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc);
487}
488
489/**
490 * @interface_method_impl{VBCLSERVICE,pfnTerm}
491 */
492static DECLCALLBACK(int) vbclSVGASessionTerm(void)
493{
494 int rc = VINF_SUCCESS;
495
496 if (g_hSession)
497 {
498 rc = RTLocalIpcSessionClose(g_hSession);
499 g_hSession = 0;
500
501 if (RT_FAILURE(rc))
502 VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc);
503 }
504
505 if (g_pDisplayHelper)
506 {
507 if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
508 g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
509
510 if (g_pDisplayHelper->pfnTerm)
511 {
512 rc = g_pDisplayHelper->pfnTerm();
513 if (RT_FAILURE(rc))
514 VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n",
515 rc, g_pDisplayHelper->pszName);
516 }
517 }
518
519 vbclSVGASessionPidFileRelease();
520
521 return VINF_SUCCESS;
522}
523
524VBCLSERVICE g_SvcDisplaySVGASession =
525{
526 "vmsvga-session", /* szName */
527 "VMSVGA display assistant", /* pszDescription */
528 NULL, /* pszPidFilePath (no pid file lock) */
529 NULL, /* pszUsage */
530 NULL, /* pszOptions */
531 NULL, /* pfnOption */
532 vbclSVGASessionInit, /* pfnInit */
533 vbclSVGASessionWorker, /* pfnWorker */
534 vbclSVGASessionStop, /* pfnStop */
535 vbclSVGASessionTerm, /* pfnTerm */
536};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use