VirtualBox

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

最後變更 在這個檔案從93469是 93469,由 vboxsync 提交於 3 年 前

Additions: Linux: VBoxDRMClient: make IPC socket access configurable via guest property, bugref:10134.

Guest property '/VirtualBox/GuestAdd/DRMIpcRestricted' when set with RDONLYGUEST
flag is used to restrict DRM IPC server socket access to root and users of 'vboxdrmipc'
group. If property is not set or has no RDONLYGUEST flag, access is granted to all users.

VBoxDRMClient subscribes to guest property update notifications and updates server socket
access mode accordingly in runtime. When switching from unrestricted mode to the restricted
one, all established IPC connections will be kept, new connections will require corresponding
access permissions.

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

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