VirtualBox

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

Last change on this file since 94330 was 94077, checked in by vboxsync, 3 years ago

Additions: Linux: VBoxDRMClient: scm fix, ​​bugref:10134.

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