VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-drm.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: 50.3 KB
Line 
1/* $Id: display-drm.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * Guest Additions - VMSVGA guest screen resize service.
4 *
5 * A user space daemon which communicates with VirtualBox host interface
6 * and performs VMSVGA-specific guest screen resize and communicates with
7 * Desktop Environment helper daemon over IPC.
8 */
9
10/*
11 * Copyright (C) 2016-2023 Oracle and/or its affiliates.
12 *
13 * This file is part of VirtualBox base platform packages, as
14 * available from https://www.virtualbox.org.
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation, in version 3 of the
19 * License.
20 *
21 * This program is distributed in the hope that it will be useful, but
22 * WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, see <https://www.gnu.org/licenses>.
28 *
29 * SPDX-License-Identifier: GPL-3.0-only
30 */
31
32/** @page pg_vboxdrmcliet VBoxDRMClient - The VMSVGA Guest Screen Resize Service
33 *
34 * The VMSVGA Guest Screen Resize Service is a service which communicates with a
35 * guest VMSVGA driver and triggers it to perform screen resize on a guest side.
36 *
37 * This service supposed to be started on early boot. On start it will try to find
38 * compatible VMSVGA graphics card and terminate immediately if not found.
39 * VMSVGA functionality implemented here is only supported starting from vmgfx
40 * driver version 2.10 which was introduced in Linux kernel 4.6. When compatible
41 * graphics card is found, service will start a worker loop in order to receive screen
42 * update data from host and apply it to local DRM stack.
43 *
44 * In addition, it will start a local IPC server in order to communicate with Desktop
45 * Environment specific service(s). Currently, it will propagate to IPC client information regarding to
46 * which display should be set as primary on Desktop Environment level. As well as
47 * receive screen layout change events obtained on Desktop Environment level and send it
48 * back to host, so host and guest will have the same screen layout representation.
49 *
50 * By default, access to IPC server socket is granted to all users. It can be restricted to
51 * only root and users from group 'vboxdrmipc' if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest
52 * property is set and READ-ONLY for guest. User group 'vboxdrmipc' is created during Guest
53 * Additions installation. If this group is removed (or not found due to any reason) prior to
54 * service start, access to IPC server socket will be granted to root only regardless
55 * if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property is set or not. If guest property
56 * is set, but is not READ-ONLY for guest, property is ignored and IPC socket access is granted
57 * to all users.
58 *
59 * Logging is implemented in a way that errors are always printed out, VBClLogVerbose(1) and
60 * VBClLogVerbose(2) are used for debugging purposes. Verbosity level 1 is for messages related
61 * to service itself (excluding IPC), level 2 is for IPC communication debugging. In order to see
62 * logging on a host side it is enough to do:
63 *
64 * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host.
65 *
66 *
67 * Service is running the following threads:
68 *
69 * DrmResizeThread - this thread listens for display layout update events from host.
70 * Once event is received, it either injects new screen layout data into DRM stack,
71 * and/or asks IPC client(s) to set primary display. This thread is accessing IPC
72 * client connection list when it needs to sent new primary display data to all the
73 * connected clients.
74 *
75 * DrmIpcSRV - this thread is a main loop for IPC server. It accepts new connection(s),
76 * authenticates it and starts new client thread IpcCLT-XXX for processing client
77 * requests. This thread is accessing IPC client connection list by adding a new
78 * connection data into it.
79 *
80 * IpcCLT-%u - this thread processes all the client data. Suffix '-%u' in thread name is PID
81 * of a remote client process. Typical name for client thread would be IpcCLT-1234. This
82 * thread is accessing IPC client connection list when it removes connection data from it
83 * when actual IPC connection is closed. Due to IPRT thread name limitation, actual thread
84 * name will be cropped by 15 characters.
85 *
86 *
87 * The following locks are utilized:
88 *
89 * #g_ipcClientConnectionsListCritSect - protects access to list of IPC client connections.
90 * It is used by each thread - DrmResizeThread, DrmIpcSRV and IpcCLT-XXX.
91 *
92 * #g_monitorPositionsCritSect - protects access to display layout data cache and vmwgfx driver
93 * handle, serializes access to host interface and vmwgfx driver handle between
94 * DrmResizeThread and IpcCLT-%u.
95 */
96
97#include "VBoxClient.h"
98#include "display-ipc.h"
99
100#include <VBox/VBoxGuestLib.h>
101#include <VBox/HostServices/GuestPropertySvc.h>
102
103#include <iprt/getopt.h>
104#include <iprt/assert.h>
105#include <iprt/file.h>
106#include <iprt/err.h>
107#include <iprt/string.h>
108#include <iprt/initterm.h>
109#include <iprt/message.h>
110#include <iprt/thread.h>
111#include <iprt/asm.h>
112#include <iprt/localipc.h>
113
114#include <unistd.h>
115#include <stdio.h>
116#include <limits.h>
117#include <signal.h>
118#include <grp.h>
119#include <errno.h>
120
121#ifdef RT_OS_LINUX
122# include <sys/ioctl.h>
123#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */
124# include <sys/ioccom.h>
125#endif
126
127/** Ioctl command to query vmwgfx version information. */
128#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION)
129/** Ioctl command to set new screen layout. */
130#define DRM_IOCTL_VMW_UPDATE_LAYOUT _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT)
131/** A driver name which identifies VMWare driver. */
132#define DRM_DRIVER_NAME "vmwgfx"
133/** VMWare driver compatible version number. On previous versions resizing does not seem work. */
134#define DRM_DRIVER_VERSION_MAJOR_MIN (2)
135#define DRM_DRIVER_VERSION_MINOR_MIN (10)
136
137/** VMWare char device driver minor numbers range. */
138#define VMW_CONTROL_DEVICE_MINOR_START (64)
139#define VMW_RENDER_DEVICE_MINOR_START (128)
140#define VMW_RENDER_DEVICE_MINOR_END (192)
141
142/** Name of DRM resize thread. */
143#define DRM_RESIZE_THREAD_NAME "DrmResizeThread"
144
145/** Name of DRM IPC server thread. */
146#define DRM_IPC_SERVER_THREAD_NAME "DrmIpcSRV"
147/** Maximum length of thread name. */
148#define DRM_IPC_THREAD_NAME_MAX (16)
149/** Name pattern of DRM IPC client thread. */
150#define DRM_IPC_CLIENT_THREAD_NAME_PTR "IpcCLT-%u"
151/** Maximum number of simultaneous IPC client connections. */
152#define DRM_IPC_SERVER_CONNECTIONS_MAX (16)
153
154/** IPC client connections counter. */
155static volatile uint32_t g_cDrmIpcConnections = 0;
156/* A flag which indicates whether access to IPC socket should be restricted.
157 * This flag caches '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property
158 * in order to prevent its retrieving from the host side each time a new IPC
159 * client connects to server. This flag is updated each time when property is
160 * changed on the host side. */
161static volatile bool g_fDrmIpcRestricted;
162
163/** Global handle to vmwgfx file descriptor (protected by #g_monitorPositionsCritSect). */
164static RTFILE g_hDevice = NIL_RTFILE;
165
166/** DRM version structure. */
167struct DRMVERSION
168{
169 int cMajor;
170 int cMinor;
171 int cPatchLevel;
172 size_t cbName;
173 char *pszName;
174 size_t cbDate;
175 char *pszDate;
176 size_t cbDescription;
177 char *pszDescription;
178};
179AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *));
180
181/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The
182 * rects argument is a cast pointer to an array of drm_vmw_rect. */
183struct DRMVMWUPDATELAYOUT
184{
185 uint32_t cOutputs;
186 uint32_t u32Pad;
187 uint64_t ptrRects;
188};
189AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16);
190
191/** A node of IPC client connections list. */
192typedef struct VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE
193{
194 /** The list node. */
195 RTLISTNODE Node;
196 /** List node payload. */
197 PVBOX_DRMIPC_CLIENT pClient;
198} VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE;
199
200/* Pointer to VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE. */
201typedef VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE *PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE;
202
203/** IPC client connections list. */
204static VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE g_ipcClientConnectionsList;
205
206/** IPC client connections list critical section. */
207static RTCRITSECT g_ipcClientConnectionsListCritSect;
208
209/** Critical section used for reporting monitors position back to host. */
210static RTCRITSECT g_monitorPositionsCritSect;
211
212/** Counter of how often our daemon has been re-spawned. */
213unsigned g_cRespawn = 0;
214/** Logging verbosity level. */
215unsigned g_cVerbosity = 0;
216
217/** Path to the PID file. */
218static const char *g_pszPidFile = "/var/run/VBoxDRMClient";
219
220/** Global flag which is triggered when service requested to shutdown. */
221static bool volatile g_fShutdown;
222
223/**
224 * Go over all existing IPC client connection and put set-primary-screen request
225 * data into TX queue of each of them .
226 *
227 * @return IPRT status code.
228 * @param u32PrimaryDisplay Primary display ID.
229 */
230static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay);
231
232/**
233 * Attempts to open DRM device by given path and check if it is
234 * capable for screen resize.
235 *
236 * @return DRM device handle on success, NIL_RTFILE otherwise.
237 * @param szPathPattern Path name pattern to the DRM device.
238 * @param uInstance Driver / device instance.
239 */
240static RTFILE vbDrmTryDevice(const char *szPathPattern, uint8_t uInstance)
241{
242 int rc = VERR_NOT_FOUND;
243 char szPath[PATH_MAX];
244 struct DRMVERSION vmwgfxVersion;
245 RTFILE hDevice = NIL_RTFILE;
246
247 RT_ZERO(szPath);
248 RT_ZERO(vmwgfxVersion);
249
250 rc = RTStrPrintf(szPath, sizeof(szPath), szPathPattern, uInstance);
251 if (RT_SUCCESS(rc))
252 {
253 rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
254 if (RT_SUCCESS(rc))
255 {
256 char szVmwgfxDriverName[sizeof(DRM_DRIVER_NAME)];
257 RT_ZERO(szVmwgfxDriverName);
258
259 vmwgfxVersion.cbName = sizeof(szVmwgfxDriverName);
260 vmwgfxVersion.pszName = szVmwgfxDriverName;
261
262 /* Query driver version information and check if it can be used for screen resizing. */
263 rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &vmwgfxVersion, sizeof(vmwgfxVersion), NULL);
264 if ( RT_SUCCESS(rc)
265 && strncmp(szVmwgfxDriverName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) == 0
266 && ( vmwgfxVersion.cMajor > DRM_DRIVER_VERSION_MAJOR_MIN
267 || ( vmwgfxVersion.cMajor == DRM_DRIVER_VERSION_MAJOR_MIN
268 && vmwgfxVersion.cMinor >= DRM_DRIVER_VERSION_MINOR_MIN)))
269 {
270 VBClLogInfo("found compatible device: %s\n", szPath);
271 }
272 else
273 {
274 RTFileClose(hDevice);
275 hDevice = NIL_RTFILE;
276 rc = VERR_NOT_FOUND;
277 }
278 }
279 }
280 else
281 {
282 VBClLogError("unable to construct path to DRM device: %Rrc\n", rc);
283 }
284
285 return RT_SUCCESS(rc) ? hDevice : NIL_RTFILE;
286}
287
288/**
289 * Attempts to find and open DRM device to be used for screen resize.
290 *
291 * @return DRM device handle on success, NIL_RTFILE otherwise.
292 */
293static RTFILE vbDrmOpenVmwgfx(void)
294{
295 /* Control devices for drm graphics driver control devices go from
296 * controlD64 to controlD127. Render node devices go from renderD128
297 * to renderD192. The driver takes resize hints via the control device
298 * on pre-4.10 (???) kernels and on the render device on newer ones.
299 * At first, try to find control device and render one if not found.
300 */
301 uint8_t i;
302 RTFILE hDevice = NIL_RTFILE;
303
304 /* Lookup control device. */
305 for (i = VMW_CONTROL_DEVICE_MINOR_START; i < VMW_RENDER_DEVICE_MINOR_START; i++)
306 {
307 hDevice = vbDrmTryDevice("/dev/dri/controlD%u", i);
308 if (hDevice != NIL_RTFILE)
309 return hDevice;
310 }
311
312 /* Lookup render device. */
313 for (i = VMW_RENDER_DEVICE_MINOR_START; i <= VMW_RENDER_DEVICE_MINOR_END; i++)
314 {
315 hDevice = vbDrmTryDevice("/dev/dri/renderD%u", i);
316 if (hDevice != NIL_RTFILE)
317 return hDevice;
318 }
319
320 VBClLogError("unable to find DRM device\n");
321
322 return hDevice;
323}
324
325/**
326 * This function converts input monitors layout array passed from DevVMM
327 * into monitors layout array to be passed to DRM stack. Last validation
328 * request is cached.
329 *
330 * @return VINF_SUCCESS on success, VERR_DUPLICATE if monitors layout was not changed, IPRT error code otherwise.
331 * @param aDisplaysIn Input displays array.
332 * @param cDisplaysIn Number of elements in input displays array.
333 * @param aDisplaysOut Output displays array.
334 * @param cDisplaysOutMax Number of elements in output displays array.
335 * @param pu32PrimaryDisplay ID of a display which marked as primary.
336 * @param pcActualDisplays Number of displays to report to DRM stack (number of enabled displays).
337 * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not.
338 * When layout is reported by Desktop Environment helper, aDisplaysIn does not have
339 * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them).
340 */
341static int vbDrmValidateLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn,
342 struct VBOX_DRMIPC_VMWRECT *aDisplaysOut, uint32_t *pu32PrimaryDisplay,
343 uint32_t cDisplaysOutMax, uint32_t *pcActualDisplays, bool fPartialLayout)
344{
345 /* This array is a cache of what was received from DevVMM so far.
346 * DevVMM may send to us partial information bout scree layout. This
347 * cache remembers entire picture. */
348 static struct VMMDevDisplayDef aVmMonitorsCache[VBOX_DRMIPC_MONITORS_MAX];
349 /* Number of valid (enabled) displays in output array. */
350 uint32_t cDisplaysOut = 0;
351 /* Flag indicates that current layout cache is consistent and can be passed to DRM stack. */
352 bool fValid = true;
353
354 /* Make sure input array fits cache size. */
355 if (cDisplaysIn > VBOX_DRMIPC_MONITORS_MAX)
356 {
357 VBClLogError("unable to validate screen layout: input (%u) array does not fit to cache size (%u)\n",
358 cDisplaysIn, VBOX_DRMIPC_MONITORS_MAX);
359 return VERR_INVALID_PARAMETER;
360 }
361
362 /* Make sure there is enough space in output array. */
363 if (cDisplaysIn > cDisplaysOutMax)
364 {
365 VBClLogError("unable to validate screen layout: input array (%u) is bigger than output one (%u)\n",
366 cDisplaysIn, cDisplaysOut);
367 return VERR_INVALID_PARAMETER;
368 }
369
370 /* Make sure input and output arrays are of non-zero size. */
371 if (!(cDisplaysIn > 0 && cDisplaysOutMax > 0))
372 {
373 VBClLogError("unable to validate screen layout: invalid size of either input (%u) or output display array\n",
374 cDisplaysIn, cDisplaysOutMax);
375 return VERR_INVALID_PARAMETER;
376 }
377
378 /* Update cache. */
379 for (uint32_t i = 0; i < cDisplaysIn; i++)
380 {
381 uint32_t idDisplay = !fPartialLayout ? aDisplaysIn[i].idDisplay : i;
382 if (idDisplay < VBOX_DRMIPC_MONITORS_MAX)
383 {
384 if (!fPartialLayout)
385 {
386 aVmMonitorsCache[idDisplay].idDisplay = idDisplay;
387 aVmMonitorsCache[idDisplay].fDisplayFlags = aDisplaysIn[i].fDisplayFlags;
388 aVmMonitorsCache[idDisplay].cBitsPerPixel = aDisplaysIn[i].cBitsPerPixel;
389 }
390
391 aVmMonitorsCache[idDisplay].cx = aDisplaysIn[i].cx;
392 aVmMonitorsCache[idDisplay].cy = aDisplaysIn[i].cy;
393 aVmMonitorsCache[idDisplay].xOrigin = aDisplaysIn[i].xOrigin;
394 aVmMonitorsCache[idDisplay].yOrigin = aDisplaysIn[i].yOrigin;
395 }
396 else
397 {
398 VBClLogError("received display ID (0x%x, position %u) is invalid\n", idDisplay, i);
399 /* If monitor configuration cannot be placed into cache, consider entire cache is invalid. */
400 fValid = false;
401 }
402 }
403
404 /* Now, go though complete cache and check if it is valid. */
405 for (uint32_t i = 0; i < VBOX_DRMIPC_MONITORS_MAX; i++)
406 {
407 if (i == 0)
408 {
409 if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
410 {
411 VBClLogError("unable to validate screen layout: first monitor is not allowed to be disabled\n");
412 fValid = false;
413 }
414 else
415 cDisplaysOut++;
416 }
417 else
418 {
419 /* Check if there is no hole in between monitors (i.e., if current monitor is enabled, but previous one does not). */
420 if ( !(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
421 && aVmMonitorsCache[i - 1].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
422 {
423 VBClLogError("unable to validate screen layout: there is a hole in displays layout config, "
424 "monitor (%u) is ENABLED while (%u) does not\n", i, i - 1);
425 fValid = false;
426 }
427 else
428 {
429 /* Always align screens since unaligned layout will result in disaster. */
430 aVmMonitorsCache[i].xOrigin = aVmMonitorsCache[i - 1].xOrigin + aVmMonitorsCache[i - 1].cx;
431 aVmMonitorsCache[i].yOrigin = aVmMonitorsCache[i - 1].yOrigin;
432
433 /* Only count enabled monitors. */
434 if (!(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
435 cDisplaysOut++;
436 }
437 }
438 }
439
440 /* Copy out layout data. */
441 if (fValid)
442 {
443 /* Start with invalid display ID. */
444 uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX;
445
446 for (uint32_t i = 0; i < cDisplaysOut; i++)
447 {
448 aDisplaysOut[i].x = aVmMonitorsCache[i].xOrigin;
449 aDisplaysOut[i].y = aVmMonitorsCache[i].yOrigin;
450 aDisplaysOut[i].w = aVmMonitorsCache[i].cx;
451 aDisplaysOut[i].h = aVmMonitorsCache[i].cy;
452
453 if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY)
454 {
455 /* Make sure display layout has only one primary display
456 * set (for display 0, host side sets primary flag, so exclude it). */
457 Assert(u32PrimaryDisplay == 0 || u32PrimaryDisplay == VBOX_DRMIPC_MONITORS_MAX);
458 u32PrimaryDisplay = i;
459 }
460
461 VBClLogVerbose(1, "update monitor %u parameters: %dx%d, (%d, %d)\n",
462 i, aDisplaysOut[i].w, aDisplaysOut[i].h, aDisplaysOut[i].x, aDisplaysOut[i].y);
463 }
464
465 *pu32PrimaryDisplay = u32PrimaryDisplay;
466 *pcActualDisplays = cDisplaysOut;
467 }
468
469 return (fValid && cDisplaysOut > 0) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
470}
471
472/**
473 * This function sends screen layout data to DRM stack.
474 *
475 * Helper function for vbDrmPushScreenLayout(). Should be called
476 * under g_monitorPositionsCritSect lock.
477 *
478 * @return VINF_SUCCESS on success, IPRT error code otherwise.
479 * @param hDevice Handle to opened DRM device.
480 * @param paRects Array of screen configuration data.
481 * @param cRects Number of elements in screen configuration array.
482 */
483static int vbDrmSendHints(RTFILE hDevice, struct VBOX_DRMIPC_VMWRECT *paRects, uint32_t cRects)
484{
485 int rc = 0;
486 uid_t curuid;
487
488 /* Store real user id. */
489 curuid = getuid();
490
491 /* Change effective user id. */
492 if (setreuid(0, 0) == 0)
493 {
494 struct DRMVMWUPDATELAYOUT ioctlLayout;
495
496 RT_ZERO(ioctlLayout);
497 ioctlLayout.cOutputs = cRects;
498 ioctlLayout.ptrRects = (uint64_t)paRects;
499
500 rc = RTFileIoCtl(hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT,
501 &ioctlLayout, sizeof(ioctlLayout), NULL);
502
503 if (setreuid(curuid, 0) != 0)
504 {
505 VBClLogError("reset of setreuid failed after drm ioctl");
506 rc = VERR_ACCESS_DENIED;
507 }
508 }
509 else
510 {
511 VBClLogError("setreuid failed during drm ioctl\n");
512 rc = VERR_ACCESS_DENIED;
513 }
514
515 return rc;
516}
517
518/**
519 * This function converts vmwgfx monitors layout data into an array of monitor offsets
520 * and sends it back to the host in order to ensure that host and guest have the same
521 * monitors layout representation.
522 *
523 * @return IPRT status code.
524 * @param cDisplays Number of displays (elements in pDisplays).
525 * @param pDisplays Displays parameters as it was sent to vmwgfx driver.
526 */
527static int drmSendMonitorPositions(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pDisplays)
528{
529 static RTPOINT aPositions[VBOX_DRMIPC_MONITORS_MAX];
530
531 if (!pDisplays || !cDisplays || cDisplays > VBOX_DRMIPC_MONITORS_MAX)
532 {
533 return VERR_INVALID_PARAMETER;
534 }
535
536 /* Prepare monitor offsets list to be sent to the host. */
537 for (uint32_t i = 0; i < cDisplays; i++)
538 {
539 aPositions[i].x = pDisplays[i].x;
540 aPositions[i].y = pDisplays[i].y;
541 }
542
543 return VbglR3SeamlessSendMonitorPositions(cDisplays, aPositions);
544}
545
546/**
547 * Validate and apply screen layout data.
548 *
549 * @return IPRT status code.
550 * @param aDisplaysIn An array with screen layout data.
551 * @param cDisplaysIn Number of elements in aDisplaysIn.
552 * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not.
553 * When layout is reported by Desktop Environment helper, aDisplaysIn does not have
554 * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them).
555 * @param fApply Whether to apply provided display layout data to the DRM stack or send display offsets only.
556 */
557static int vbDrmPushScreenLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, bool fPartialLayout, bool fApply)
558{
559 int rc;
560
561 struct VBOX_DRMIPC_VMWRECT aDisplaysOut[VBOX_DRMIPC_MONITORS_MAX];
562 uint32_t cDisplaysOut = 0;
563
564 uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX;
565
566 rc = RTCritSectEnter(&g_monitorPositionsCritSect);
567 if (RT_FAILURE(rc))
568 {
569 VBClLogError("unable to lock monitor data cache, rc=%Rrc\n", rc);
570 return rc;
571 }
572
573 static uint32_t u32PrimaryDisplayLast = VBOX_DRMIPC_MONITORS_MAX;
574
575 RT_ZERO(aDisplaysOut);
576
577 /* Validate displays layout and push it to DRM stack if valid. */
578 rc = vbDrmValidateLayout(aDisplaysIn, cDisplaysIn, aDisplaysOut, &u32PrimaryDisplay,
579 sizeof(aDisplaysOut), &cDisplaysOut, fPartialLayout);
580 if (RT_SUCCESS(rc))
581 {
582 if (fApply)
583 {
584 rc = vbDrmSendHints(g_hDevice, aDisplaysOut, cDisplaysOut);
585 VBClLogInfo("push screen layout data of %u display(s) to DRM stack, fPartialLayout=%RTbool, rc=%Rrc\n",
586 cDisplaysOut, fPartialLayout, rc);
587 }
588
589 /* In addition, notify host that configuration was successfully applied to the guest vmwgfx driver. */
590 if (RT_SUCCESS(rc))
591 {
592 rc = drmSendMonitorPositions(cDisplaysOut, aDisplaysOut);
593 if (RT_FAILURE(rc))
594 VBClLogError("cannot send host notification: %Rrc\n", rc);
595
596 /* If information about primary display is present in display layout, send it to DE over IPC. */
597 if (u32PrimaryDisplay != VBOX_DRMIPC_MONITORS_MAX
598 && u32PrimaryDisplayLast != u32PrimaryDisplay)
599 {
600 rc = vbDrmIpcBroadcastPrimaryDisplay(u32PrimaryDisplay);
601
602 /* Cache last value in order to avoid sending duplicate data over IPC. */
603 u32PrimaryDisplayLast = u32PrimaryDisplay;
604
605 VBClLogVerbose(2, "DE was notified that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc);
606 }
607 else
608 VBClLogVerbose(2, "do not notify DE second time that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc);
609 }
610 }
611 else if (rc == VERR_DUPLICATE)
612 VBClLogVerbose(2, "do not notify DRM stack about monitors layout change twice, rc=%Rrc\n", rc);
613 else
614 VBClLogError("displays layout is invalid, will not notify guest driver, rc=%Rrc\n", rc);
615
616 int rc2 = RTCritSectLeave(&g_monitorPositionsCritSect);
617 if (RT_FAILURE(rc2))
618 VBClLogError("unable to unlock monitor data cache, rc=%Rrc\n", rc);
619
620 return rc;
621}
622
623/** Worker thread for resize task. */
624static DECLCALLBACK(int) vbDrmResizeWorker(RTTHREAD ThreadSelf, void *pvUser)
625{
626 int rc = VERR_GENERAL_FAILURE;
627
628 RT_NOREF(ThreadSelf);
629 RT_NOREF(pvUser);
630
631 for (;;)
632 {
633 /* Do not acknowledge the first event we query for to pick up old events,
634 * e.g. from before a guest reboot. */
635 bool fAck = false;
636
637 uint32_t events;
638
639 VMMDevDisplayDef aDisplaysIn[VBOX_DRMIPC_MONITORS_MAX];
640 uint32_t cDisplaysIn = 0;
641
642 RT_ZERO(aDisplaysIn);
643
644 /* Query the first size without waiting. This lets us e.g. pick up
645 * the last event before a guest reboot when we start again after. */
646 rc = VbglR3GetDisplayChangeRequestMulti(VBOX_DRMIPC_MONITORS_MAX, &cDisplaysIn, aDisplaysIn, fAck);
647 fAck = true;
648 if (RT_SUCCESS(rc))
649 {
650 rc = vbDrmPushScreenLayout(aDisplaysIn, cDisplaysIn, false, true);
651 if (RT_FAILURE(rc))
652 VBClLogError("Failed to push display change as requested by host, rc=%Rrc\n", rc);
653 }
654 else
655 VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
656
657 do
658 {
659 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_DRMIPC_RX_TIMEOUT_MS, &events);
660 } while ((rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) && !ASMAtomicReadBool(&g_fShutdown));
661
662 if (ASMAtomicReadBool(&g_fShutdown))
663 {
664 VBClLogInfo("exiting resize thread: shutdown requested\n");
665 /* This is a case when we should return positive status. */
666 rc = (rc == VERR_TIMEOUT) ? VINF_SUCCESS : rc;
667 break;
668 }
669 else if (RT_FAILURE(rc))
670 VBClLogFatalError("VBoxDRMClient: resize thread: failure waiting for event, rc=%Rrc\n", rc);
671 }
672
673 return rc;
674}
675
676/**
677 * Go over all existing IPC client connection and put set-primary-screen request
678 * data into TX queue of each of them .
679 *
680 * @return IPRT status code.
681 * @param u32PrimaryDisplay Primary display ID.
682 */
683static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay)
684{
685 int rc;
686
687 rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
688 if (RT_SUCCESS(rc))
689 {
690 if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node))
691 {
692 PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry;
693 RTListForEach(&g_ipcClientConnectionsList.Node, pEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node)
694 {
695 AssertReturn(pEntry, VERR_INVALID_PARAMETER);
696 AssertReturn(pEntry->pClient, VERR_INVALID_PARAMETER);
697 AssertReturn(pEntry->pClient->hThread, VERR_INVALID_PARAMETER);
698
699 rc = vbDrmIpcSetPrimaryDisplay(pEntry->pClient, u32PrimaryDisplay);
700
701 VBClLogInfo("thread %s notified IPC Client that display %u is now primary, rc=%Rrc\n",
702 RTThreadGetName(pEntry->pClient->hThread), u32PrimaryDisplay, rc);
703 }
704 }
705
706 int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
707 if (RT_FAILURE(rc2))
708 VBClLogError("notify DE: unable to leave critical section, rc=%Rrc\n", rc2);
709 }
710 else
711 VBClLogError("notify DE: unable to enter critical section, rc=%Rrc\n", rc);
712
713 return rc;
714}
715
716/**
717 * Main loop for IPC client connection handling.
718 *
719 * @return IPRT status code.
720 * @param pClient Pointer to IPC client data.
721 */
722static int vbDrmIpcConnectionProc(PVBOX_DRMIPC_CLIENT pClient)
723{
724 int rc = VERR_GENERAL_FAILURE;
725
726 AssertReturn(pClient, VERR_INVALID_PARAMETER);
727
728 /* This loop handles incoming messages. */
729 for (;;)
730 {
731 rc = vbDrmIpcConnectionHandler(pClient);
732
733 /* Try to detect if we should shutdown as early as we can. */
734 if (ASMAtomicReadBool(&g_fShutdown))
735 break;
736
737 /* Normal case. No data received within short interval. */
738 if (rc == VERR_TIMEOUT)
739 {
740 continue;
741 }
742 else if (RT_FAILURE(rc))
743 {
744 /* Terminate connection handling in case of error. */
745 VBClLogError("unable to handle IPC session, rc=%Rrc\n", rc);
746 break;
747 }
748 }
749
750 return rc;
751}
752
753/**
754 * Add IPC client connection data into list of connections.
755 *
756 * List size is limited indirectly by DRM_IPC_SERVER_CONNECTIONS_MAX value.
757 * This function should only be invoked from client thread context
758 * (from vbDrmIpcClientWorker() in particular).
759 *
760 * @return IPRT status code.
761 * @param pClientNode Client connection information to add to the list.
762 */
763static int vbDrmIpcClientsListAdd(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode)
764{
765 int rc;
766
767 AssertReturn(pClientNode, VERR_INVALID_PARAMETER);
768
769 rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
770 if (RT_SUCCESS(rc))
771 {
772 RTListAppend(&g_ipcClientConnectionsList.Node, &pClientNode->Node);
773
774 int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
775 if (RT_FAILURE(rc2))
776 VBClLogError("add client connection: unable to leave critical section, rc=%Rrc\n", rc2);
777 }
778 else
779 VBClLogError("add client connection: unable to enter critical section, rc=%Rrc\n", rc);
780
781 return rc;
782}
783
784/**
785 * Remove IPC client connection data from list of connections.
786 *
787 * This function should only be invoked from client thread context
788 * (from vbDrmIpcClientWorker() in particular).
789 *
790 * @return IPRT status code.
791 * @param pClientNode Client connection information to remove from the list.
792 */
793static int vbDrmIpcClientsListRemove(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode)
794{
795 int rc;
796 PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry, pNextEntry, pFound = NULL;
797
798 AssertReturn(pClientNode, VERR_INVALID_PARAMETER);
799
800 rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
801 if (RT_SUCCESS(rc))
802 {
803
804 if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node))
805 {
806 RTListForEachSafe(&g_ipcClientConnectionsList.Node, pEntry, pNextEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node)
807 {
808 if (pEntry == pClientNode)
809 pFound = (PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE)RTListNodeRemoveRet(&pEntry->Node);
810 }
811 }
812 else
813 VBClLogError("remove client connection: connections list empty, node %p not there\n", pClientNode);
814
815 int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
816 if (RT_FAILURE(rc2))
817 VBClLogError("remove client connection: unable to leave critical section, rc=%Rrc\n", rc2);
818 }
819 else
820 VBClLogError("remove client connection: unable to enter critical section, rc=%Rrc\n", rc);
821
822 if (!pFound)
823 VBClLogError("remove client connection: node not found\n");
824
825 return !rc && pFound ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
826}
827
828/**
829 * Convert VBOX_DRMIPC_VMWRECT into VMMDevDisplayDef and check layout correctness.
830 *
831 * VBOX_DRMIPC_VMWRECT does not represent enough information needed for
832 * VMMDevDisplayDef. Missing fields (fDisplayFlags, idDisplay, cBitsPerPixel)
833 * are initialized with default (invalid) values due to this.
834 *
835 * @return True if given screen layout is correct (i.e., has no displays which overlap), False
836 * if it needs to be adjusted before injecting into DRM stack.
837 * @param cDisplays Number of displays in configuration data.
838 * @param pIn A pointer to display configuration data array in form of VBOX_DRMIPC_VMWRECT (input).
839 * @param pOut A pointer to display configuration data array in form of VMMDevDisplayDef (output).
840 */
841static bool vbDrmVmwRectToDisplayDef(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pIn, VMMDevDisplayDef *pOut)
842{
843 bool fCorrect = true;
844
845 for (uint32_t i = 0; i < cDisplays; i++)
846 {
847 /* VBOX_DRMIPC_VMWRECT has no information about this fields. */
848 pOut[i].fDisplayFlags = 0;
849 pOut[i].idDisplay = VBOX_DRMIPC_MONITORS_MAX;
850 pOut[i].cBitsPerPixel = 0;
851
852 pOut[i].xOrigin = pIn[i].x;
853 pOut[i].yOrigin = pIn[i].y;
854 pOut[i].cx = pIn[i].w;
855 pOut[i].cy = pIn[i].h;
856
857 /* Make sure that displays do not overlap within reported screen layout. Ask IPC server to fix layout otherwise. */
858 fCorrect = i > 0
859 && pIn[i].x != (int32_t)pIn[i - 1].w + pIn[i - 1].x
860 ? false
861 : fCorrect;
862 }
863
864 return fCorrect;
865}
866
867/**
868 * @interface_method_impl{VBOX_DRMIPC_CLIENT,pfnRxCb}
869 */
870static DECLCALLBACK(int) vbDrmIpcClientRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
871{
872 int rc = VERR_INVALID_PARAMETER;
873
874 AssertReturn(pvData, VERR_INVALID_PARAMETER);
875 AssertReturn(cbData, VERR_INVALID_PARAMETER);
876
877 switch (idCmd)
878 {
879 case VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS:
880 {
881 VMMDevDisplayDef aDisplays[VBOX_DRMIPC_MONITORS_MAX];
882 bool fCorrect;
883
884 PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)pvData;
885 AssertReturn(cbData == sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS), VERR_INVALID_PARAMETER);
886 AssertReturn(pCmd->cDisplays < VBOX_DRMIPC_MONITORS_MAX, VERR_INVALID_PARAMETER);
887
888 /* Convert input display config into VMMDevDisplayDef representation. */
889 RT_ZERO(aDisplays);
890 fCorrect = vbDrmVmwRectToDisplayDef(pCmd->cDisplays, pCmd->aDisplays, aDisplays);
891
892 rc = vbDrmPushScreenLayout(aDisplays, pCmd->cDisplays, true, !fCorrect);
893 if (RT_FAILURE(rc))
894 VBClLogError("Failed to push display change as requested by Desktop Environment helper, rc=%Rrc\n", rc);
895
896 break;
897 }
898
899 default:
900 {
901 VBClLogError("received unknown IPC command 0x%x\n", idCmd);
902 break;
903 }
904 }
905
906 return rc;
907}
908
909/** Worker thread for IPC client task. */
910static DECLCALLBACK(int) vbDrmIpcClientWorker(RTTHREAD ThreadSelf, void *pvUser)
911{
912 VBOX_DRMIPC_CLIENT hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
913 RTLOCALIPCSESSION hSession = (RTLOCALIPCSESSION)pvUser;
914 int rc;
915
916 AssertReturn(RT_VALID_PTR(hSession), VERR_INVALID_PARAMETER);
917
918 /* Initialize client session resources. */
919 rc = vbDrmIpcClientInit(&hClient, ThreadSelf, hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbDrmIpcClientRxCallBack);
920 if (RT_SUCCESS(rc))
921 {
922 /* Add IPC client connection data into clients list. */
923 VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE hClientNode = { { 0, 0 } , &hClient };
924
925 rc = vbDrmIpcClientsListAdd(&hClientNode);
926 if (RT_SUCCESS(rc))
927 {
928 rc = RTThreadUserSignal(ThreadSelf);
929 if (RT_SUCCESS(rc))
930 {
931 /* Start spinning the connection. */
932 VBClLogInfo("IPC client connection started\n", rc);
933 rc = vbDrmIpcConnectionProc(&hClient);
934 VBClLogInfo("IPC client connection ended, rc=%Rrc\n", rc);
935 }
936 else
937 VBClLogError("unable to report IPC client connection handler start, rc=%Rrc\n", rc);
938
939 /* Remove IPC client connection data from clients list. */
940 rc = vbDrmIpcClientsListRemove(&hClientNode);
941 if (RT_FAILURE(rc))
942 VBClLogError("unable to remove IPC client session from list of connections, rc=%Rrc\n", rc);
943 }
944 else
945 VBClLogError("unable to add IPC client connection to the list, rc=%Rrc\n");
946
947 /* Disconnect remote peer if still connected. */
948 if (RT_VALID_PTR(hSession))
949 {
950 rc = RTLocalIpcSessionClose(hSession);
951 VBClLogInfo("IPC session closed, rc=%Rrc\n", rc);
952 }
953
954 /* Connection handler loop has ended, release session resources. */
955 rc = vbDrmIpcClientReleaseResources(&hClient);
956 if (RT_FAILURE(rc))
957 VBClLogError("unable to release IPC client session, rc=%Rrc\n", rc);
958
959 ASMAtomicDecU32(&g_cDrmIpcConnections);
960 }
961 else
962 VBClLogError("unable to initialize IPC client session, rc=%Rrc\n", rc);
963
964 VBClLogInfo("closing IPC client session, rc=%Rrc\n", rc);
965
966 return rc;
967}
968
969/**
970 * Start processing thread for IPC client requests handling.
971 *
972 * @returns IPRT status code.
973 * @param hSession IPC client connection handle.
974 */
975static int vbDrmIpcClientStart(RTLOCALIPCSESSION hSession)
976{
977 int rc;
978 RTTHREAD hThread = 0;
979 RTPROCESS hProcess = 0;
980
981 rc = RTLocalIpcSessionQueryProcess(hSession, &hProcess);
982 if (RT_SUCCESS(rc))
983 {
984 char pszThreadName[DRM_IPC_THREAD_NAME_MAX];
985 RT_ZERO(pszThreadName);
986
987 RTStrPrintf2(pszThreadName, DRM_IPC_THREAD_NAME_MAX, DRM_IPC_CLIENT_THREAD_NAME_PTR, hProcess);
988
989 /* Attempt to start IPC client connection handler task. */
990 rc = RTThreadCreate(&hThread, vbDrmIpcClientWorker, (void *)hSession, 0,
991 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, pszThreadName);
992 if (RT_SUCCESS(rc))
993 {
994 rc = RTThreadUserWait(hThread, RT_MS_5SEC);
995 }
996 }
997
998 return rc;
999}
1000
1001/** Worker thread for IPC server task. */
1002static DECLCALLBACK(int) vbDrmIpcServerWorker(RTTHREAD ThreadSelf, void *pvUser)
1003{
1004 int rc = VERR_GENERAL_FAILURE;
1005 RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
1006
1007 RT_NOREF1(ThreadSelf);
1008
1009 AssertReturn(hIpcServer, VERR_INVALID_PARAMETER);
1010
1011 /* This loop accepts incoming connections. */
1012 for (;;)
1013 {
1014 RTLOCALIPCSESSION hClientSession;
1015
1016 /* Wait for incoming connection. */
1017 rc = RTLocalIpcServerListen(hIpcServer, &hClientSession);
1018 if (RT_SUCCESS(rc))
1019 {
1020 VBClLogVerbose(2, "new IPC session\n");
1021
1022 if (ASMAtomicIncU32(&g_cDrmIpcConnections) <= DRM_IPC_SERVER_CONNECTIONS_MAX)
1023 {
1024 /* Authenticate remote peer. */
1025 if (ASMAtomicReadBool(&g_fDrmIpcRestricted))
1026 rc = vbDrmIpcAuth(hClientSession);
1027
1028 if (RT_SUCCESS(rc))
1029 {
1030 /* Start incoming connection handler thread. */
1031 rc = vbDrmIpcClientStart(hClientSession);
1032 VBClLogVerbose(2, "connection processing ended, rc=%Rrc\n", rc);
1033 }
1034 else
1035 VBClLogError("IPC authentication failed, rc=%Rrc\n", rc);
1036 }
1037 else
1038 rc = VERR_RESOURCE_BUSY;
1039
1040 /* Release resources in case of error. */
1041 if (RT_FAILURE(rc))
1042 {
1043 VBClLogError("maximum amount of IPC client connections reached, dropping connection\n");
1044
1045 int rc2 = RTLocalIpcSessionClose(hClientSession);
1046 if (RT_FAILURE(rc2))
1047 VBClLogError("unable to close IPC session, rc=%Rrc\n", rc2);
1048
1049 ASMAtomicDecU32(&g_cDrmIpcConnections);
1050 }
1051 }
1052 else
1053 VBClLogError("IPC authentication failed, rc=%Rrc\n", rc);
1054
1055 /* Check shutdown was requested. */
1056 if (ASMAtomicReadBool(&g_fShutdown))
1057 {
1058 VBClLogInfo("exiting IPC thread: shutdown requested\n");
1059 break;
1060 }
1061
1062 /* Wait a bit before spinning a loop if something went wrong. */
1063 if (RT_FAILURE(rc))
1064 RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
1065 }
1066
1067 return rc;
1068}
1069
1070/** A signal handler. */
1071static void vbDrmRequestShutdown(int sig)
1072{
1073 RT_NOREF(sig);
1074 ASMAtomicWriteBool(&g_fShutdown, true);
1075}
1076
1077/**
1078 * Grant access to DRM IPC server socket depending on VM configuration.
1079 *
1080 * If VM has '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property set
1081 * and this property is READ-ONLY for the guest side, access will be
1082 * granted to root and users from 'vboxdrmipc' group only. If group does
1083 * not exists, only root will have access to the socket. When property is
1084 * not set or not READ-ONLY, all users will have access to the socket.
1085 *
1086 * @param hIpcServer IPC server handle.
1087 * @param fRestrict Whether to restrict access to socket or not.
1088 */
1089static void vbDrmSetIpcServerAccessPermissions(RTLOCALIPCSERVER hIpcServer, bool fRestrict)
1090{
1091 int rc;
1092
1093 if (fRestrict)
1094 {
1095 struct group *pGrp;
1096 pGrp = getgrnam(VBOX_DRMIPC_USER_GROUP);
1097 if (pGrp)
1098 {
1099 rc = RTLocalIpcServerGrantGroupAccess(hIpcServer, pGrp->gr_gid);
1100 if (RT_SUCCESS(rc))
1101 VBClLogInfo("IPC server socket access granted to '" VBOX_DRMIPC_USER_GROUP "' users\n");
1102 else
1103 VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "' users, rc=%Rrc\n", rc);
1104
1105 }
1106 else
1107 VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "', group does not exist\n");
1108 }
1109 else
1110 {
1111 rc = RTLocalIpcServerSetAccessMode(hIpcServer,
1112 RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR |
1113 RTFS_UNIX_IRGRP | RTFS_UNIX_IWGRP |
1114 RTFS_UNIX_IROTH | RTFS_UNIX_IWOTH);
1115 if (RT_SUCCESS(rc))
1116 VBClLogInfo("IPC server socket access granted to all users\n");
1117 else
1118 VBClLogError("unable to grant IPC server socket access to all users, rc=%Rrc\n", rc);
1119 }
1120
1121 /* Set flag for the thread which serves incomming IPC connections. */
1122 ASMAtomicWriteBool(&g_fDrmIpcRestricted, fRestrict);
1123}
1124
1125/**
1126 * Wait and handle '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property change.
1127 *
1128 * This function is executed in context of main().
1129 *
1130 * @param hIpcServer IPC server handle.
1131 */
1132static void vbDrmPollIpcServerAccessMode(RTLOCALIPCSERVER hIpcServer)
1133{
1134 HGCMCLIENTID idClient;
1135 int rc;
1136
1137 rc = VbglR3GuestPropConnect(&idClient);
1138 if (RT_SUCCESS(rc))
1139 {
1140 do
1141 {
1142 /* Buffer should be big enough to fit guest property data layout: Name\0Value\0Flags\0fWasDeleted\0. */
1143 static char achBuf[GUEST_PROP_MAX_NAME_LEN];
1144 char *pszName = NULL;
1145 char *pszValue = NULL;
1146 char *pszFlags = NULL;
1147 bool fWasDeleted = false;
1148 uint64_t u64Timestamp = 0;
1149
1150 rc = VbglR3GuestPropWait(idClient, VBGLR3DRMPROPPTR, achBuf, sizeof(achBuf), u64Timestamp,
1151 VBOX_DRMIPC_RX_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp,
1152 &pszFlags, NULL, &fWasDeleted);
1153 if (RT_SUCCESS(rc))
1154 {
1155 uint32_t fFlags = 0;
1156
1157 VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n",
1158 pszName, pszValue, pszFlags, fWasDeleted);
1159
1160 if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags)))
1161 {
1162 if (RTStrNCmp(pszName, VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_MAX_NAME_LEN) == 0)
1163 {
1164 /* Enforce restricted socket access until guest property exist and READ-ONLY for the guest. */
1165 vbDrmSetIpcServerAccessPermissions(hIpcServer, !fWasDeleted && fFlags & GUEST_PROP_F_RDONLYGUEST);
1166 }
1167
1168 } else
1169 VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n",
1170 pszName, pszValue, pszFlags, fWasDeleted);
1171
1172 } else if ( rc != VERR_TIMEOUT
1173 && rc != VERR_INTERRUPTED)
1174 {
1175 VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc);
1176 RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
1177 }
1178
1179 } while (!ASMAtomicReadBool(&g_fShutdown));
1180
1181 VbglR3GuestPropDisconnect(idClient);
1182 }
1183 else
1184 VBClLogError("cannot connect to VM guest properties service, rc=%Rrc\n", rc);
1185}
1186
1187int main(int argc, char *argv[])
1188{
1189 /** Custom log prefix to be used for logger instance of this process. */
1190 static const char *pszLogPrefix = "VBoxDRMClient:";
1191
1192 static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, };
1193 RTGETOPTUNION ValueUnion;
1194 RTGETOPTSTATE GetState;
1195 int ch;
1196
1197 RTFILE hPidFile;
1198
1199 RTLOCALIPCSERVER hIpcServer;
1200 RTTHREAD vbDrmIpcThread;
1201 int rcDrmIpcThread = 0;
1202
1203 RTTHREAD drmResizeThread;
1204 int rcDrmResizeThread = 0;
1205 int rc, rc2 = 0;
1206
1207 rc = RTR3InitExe(argc, &argv, 0);
1208 if (RT_FAILURE(rc))
1209 return RTMsgInitFailure(rc);
1210
1211 rc = VbglR3InitUser();
1212 if (RT_FAILURE(rc))
1213 VBClLogFatalError("VBoxDRMClient: VbglR3InitUser failed: %Rrc", rc);
1214
1215 /* Process command line options. */
1216 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
1217 if (RT_FAILURE(rc))
1218 VBClLogFatalError("VBoxDRMClient: unable to process command line options, rc=%Rrc\n", rc);
1219 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
1220 {
1221 switch (ch)
1222 {
1223 case 'v':
1224 {
1225 g_cVerbosity++;
1226 break;
1227 }
1228
1229 case VERR_GETOPT_UNKNOWN_OPTION:
1230 {
1231 VBClLogFatalError("unknown command line option '%s'\n", ValueUnion.psz);
1232 return RTEXITCODE_SYNTAX;
1233
1234 }
1235
1236 default:
1237 break;
1238 }
1239 }
1240
1241 rc = VBClLogCreate("");
1242 if (RT_FAILURE(rc))
1243 VBClLogFatalError("VBoxDRMClient: failed to setup logging, rc=%Rrc\n", rc);
1244 VBClLogSetLogPrefix(pszLogPrefix);
1245
1246 /* Check PID file before attempting to initialize anything. */
1247 rc = VbglR3PidFile(g_pszPidFile, &hPidFile);
1248 if (rc == VERR_FILE_LOCK_VIOLATION)
1249 {
1250 VBClLogInfo("already running, exiting\n");
1251 return RTEXITCODE_SUCCESS;
1252 }
1253 if (RT_FAILURE(rc))
1254 {
1255 VBClLogError("unable to lock PID file (%Rrc), exiting\n", rc);
1256 return RTEXITCODE_FAILURE;
1257 }
1258
1259 g_hDevice = vbDrmOpenVmwgfx();
1260 if (g_hDevice == NIL_RTFILE)
1261 return RTEXITCODE_FAILURE;
1262
1263 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
1264 if (RT_FAILURE(rc))
1265 {
1266 VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
1267 return RTEXITCODE_FAILURE;
1268 }
1269 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
1270 if (RT_FAILURE(rc))
1271 {
1272 VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
1273 return RTEXITCODE_FAILURE;
1274 }
1275
1276 /* Setup signals: gracefully terminate on SIGINT, SIGTERM. */
1277 if ( signal(SIGINT, vbDrmRequestShutdown) == SIG_ERR
1278 || signal(SIGTERM, vbDrmRequestShutdown) == SIG_ERR)
1279 {
1280 VBClLogError("unable to setup signals\n");
1281 return RTEXITCODE_FAILURE;
1282 }
1283
1284 /* Init IPC client connection list. */
1285 RTListInit(&g_ipcClientConnectionsList.Node);
1286 rc = RTCritSectInit(&g_ipcClientConnectionsListCritSect);
1287 if (RT_FAILURE(rc))
1288 {
1289 VBClLogError("unable to initialize IPC client connection list critical section\n");
1290 return RTEXITCODE_FAILURE;
1291 }
1292
1293 /* Init critical section which is used for reporting monitors offset back to host. */
1294 rc = RTCritSectInit(&g_monitorPositionsCritSect);
1295 if (RT_FAILURE(rc))
1296 {
1297 VBClLogError("unable to initialize monitors position critical section\n");
1298 return RTEXITCODE_FAILURE;
1299 }
1300
1301 /* Instantiate IPC server for VBoxClient service communication. */
1302 rc = RTLocalIpcServerCreate(&hIpcServer, VBOX_DRMIPC_SERVER_NAME, 0);
1303 if (RT_FAILURE(rc))
1304 {
1305 VBClLogError("unable to setup IPC server, rc=%Rrc\n", rc);
1306 return RTEXITCODE_FAILURE;
1307 }
1308
1309 /* Set IPC server socket access permissions according to VM configuration. */
1310 vbDrmSetIpcServerAccessPermissions(hIpcServer, VbglR3DrmRestrictedIpcAccessIsNeeded());
1311
1312 /* Attempt to start DRM resize task. */
1313 rc = RTThreadCreate(&drmResizeThread, vbDrmResizeWorker, NULL, 0,
1314 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_RESIZE_THREAD_NAME);
1315 if (RT_SUCCESS(rc))
1316 {
1317 /* Attempt to start IPC task. */
1318 rc = RTThreadCreate(&vbDrmIpcThread, vbDrmIpcServerWorker, (void *)hIpcServer, 0,
1319 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_IPC_SERVER_THREAD_NAME);
1320 if (RT_SUCCESS(rc))
1321 {
1322 /* Poll for host notification about IPC server socket access mode change. */
1323 vbDrmPollIpcServerAccessMode(hIpcServer);
1324
1325 /* HACK ALERT!
1326 * The sequence of RTThreadWait(drmResizeThread) -> RTLocalIpcServerDestroy() -> RTThreadWait(vbDrmIpcThread)
1327 * is intentional! Once process received a signal, it will pull g_fShutdown flag, which in turn will cause
1328 * drmResizeThread to quit. The vbDrmIpcThread might hang on accept() call, so we terminate IPC server to
1329 * release it and then wait for its termination. */
1330
1331 rc = RTThreadWait(drmResizeThread, RT_INDEFINITE_WAIT, &rcDrmResizeThread);
1332 VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_RESIZE_THREAD_NAME, rcDrmResizeThread);
1333
1334 rc = RTLocalIpcServerCancel(hIpcServer);
1335 if (RT_FAILURE(rc))
1336 VBClLogError("unable to notify IPC server about shutdown, rc=%Rrc\n", rc);
1337
1338 /* Wait for threads to terminate gracefully. */
1339 rc = RTThreadWait(vbDrmIpcThread, RT_INDEFINITE_WAIT, &rcDrmIpcThread);
1340 VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_IPC_SERVER_THREAD_NAME, rcDrmResizeThread);
1341
1342 }
1343 else
1344 VBClLogError("unable to start IPC thread, rc=%Rrc\n", rc);
1345 }
1346 else
1347 VBClLogError("unable to start resize thread, rc=%Rrc\n", rc);
1348
1349 rc = RTLocalIpcServerDestroy(hIpcServer);
1350 if (RT_FAILURE(rc))
1351 VBClLogError("unable to stop IPC server, rc=%Rrc\n", rc);
1352
1353 rc2 = RTCritSectDelete(&g_monitorPositionsCritSect);
1354 if (RT_FAILURE(rc2))
1355 VBClLogError("unable to destroy g_monitorPositionsCritSect critsect, rc=%Rrc\n", rc2);
1356
1357 rc2 = RTCritSectDelete(&g_ipcClientConnectionsListCritSect);
1358 if (RT_FAILURE(rc2))
1359 VBClLogError("unable to destroy g_ipcClientConnectionsListCritSect critsect, rc=%Rrc\n", rc2);
1360
1361 RTFileClose(g_hDevice);
1362
1363 VBClLogInfo("releasing PID file lock\n");
1364 VbglR3ClosePidFile(g_pszPidFile, hPidFile);
1365
1366 VBClLogDestroy();
1367
1368 return rc == 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1369}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use