VirtualBox

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

Last change on this file since 93381 was 93380, checked in by vboxsync, 3 years ago

Additions: Linux: scm fixes, bugref:10185.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette