VirtualBox

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

Last change on this file was 102453, checked in by vboxsync, 5 months ago

Additions: X11/Wayland: Add host resize events suppression in VBoxDRMClient, bugref:10134.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use