diff options
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/display-drm.cpp')
-rw-r--r-- | src/VBox/Additions/x11/VBoxClient/display-drm.cpp | 1369 |
1 files changed, 1369 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/display-drm.cpp b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp new file mode 100644 index 00000000..fa5d56d2 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp @@ -0,0 +1,1369 @@ +/* $Id: display-drm.cpp $ */ +/** @file + * Guest Additions - VMSVGA guest screen resize service. + * + * A user space daemon which communicates with VirtualBox host interface + * and performs VMSVGA-specific guest screen resize and communicates with + * Desktop Environment helper daemon over IPC. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vboxdrmcliet VBoxDRMClient - The VMSVGA Guest Screen Resize Service + * + * The VMSVGA Guest Screen Resize Service is a service which communicates with a + * guest VMSVGA driver and triggers it to perform screen resize on a guest side. + * + * This service supposed to be started on early boot. On start it will try to find + * compatible VMSVGA graphics card and terminate immediately if not found. + * VMSVGA functionality implemented here is only supported starting from vmgfx + * driver version 2.10 which was introduced in Linux kernel 4.6. When compatible + * graphics card is found, service will start a worker loop in order to receive screen + * update data from host and apply it to local DRM stack. + * + * In addition, it will start a local IPC server in order to communicate with Desktop + * Environment specific service(s). Currently, it will propagate to IPC client information regarding to + * which display should be set as primary on Desktop Environment level. As well as + * receive screen layout change events obtained on Desktop Environment level and send it + * back to host, so host and guest will have the same screen layout representation. + * + * By default, access to IPC server socket is granted to all users. It can be restricted to + * only root and users from group 'vboxdrmipc' if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest + * property is set and READ-ONLY for guest. User group 'vboxdrmipc' is created during Guest + * Additions installation. If this group is removed (or not found due to any reason) prior to + * service start, access to IPC server socket will be granted to root only regardless + * if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property is set or not. If guest property + * is set, but is not READ-ONLY for guest, property is ignored and IPC socket access is granted + * to all users. + * + * Logging is implemented in a way that errors are always printed out, VBClLogVerbose(1) and + * VBClLogVerbose(2) are used for debugging purposes. Verbosity level 1 is for messages related + * to service itself (excluding IPC), level 2 is for IPC communication debugging. In order to see + * logging on a host side it is enough to do: + * + * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host. + * + * + * Service is running the following threads: + * + * DrmResizeThread - this thread listens for display layout update events from host. + * Once event is received, it either injects new screen layout data into DRM stack, + * and/or asks IPC client(s) to set primary display. This thread is accessing IPC + * client connection list when it needs to sent new primary display data to all the + * connected clients. + * + * DrmIpcSRV - this thread is a main loop for IPC server. It accepts new connection(s), + * authenticates it and starts new client thread IpcCLT-XXX for processing client + * requests. This thread is accessing IPC client connection list by adding a new + * connection data into it. + * + * IpcCLT-%u - this thread processes all the client data. Suffix '-%u' in thread name is PID + * of a remote client process. Typical name for client thread would be IpcCLT-1234. This + * thread is accessing IPC client connection list when it removes connection data from it + * when actual IPC connection is closed. Due to IPRT thread name limitation, actual thread + * name will be cropped by 15 characters. + * + * + * The following locks are utilized: + * + * #g_ipcClientConnectionsListCritSect - protects access to list of IPC client connections. + * It is used by each thread - DrmResizeThread, DrmIpcSRV and IpcCLT-XXX. + * + * #g_monitorPositionsCritSect - protects access to display layout data cache and vmwgfx driver + * handle, serializes access to host interface and vmwgfx driver handle between + * DrmResizeThread and IpcCLT-%u. + */ + +#include "VBoxClient.h" +#include "display-ipc.h" + +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestPropertySvc.h> + +#include <iprt/getopt.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/thread.h> +#include <iprt/asm.h> +#include <iprt/localipc.h> + +#include <unistd.h> +#include <stdio.h> +#include <limits.h> +#include <signal.h> +#include <grp.h> +#include <errno.h> + +#ifdef RT_OS_LINUX +# include <sys/ioctl.h> +#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */ +# include <sys/ioccom.h> +#endif + +/** Ioctl command to query vmwgfx version information. */ +#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION) +/** Ioctl command to set new screen layout. */ +#define DRM_IOCTL_VMW_UPDATE_LAYOUT _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT) +/** A driver name which identifies VMWare driver. */ +#define DRM_DRIVER_NAME "vmwgfx" +/** VMWare driver compatible version number. On previous versions resizing does not seem work. */ +#define DRM_DRIVER_VERSION_MAJOR_MIN (2) +#define DRM_DRIVER_VERSION_MINOR_MIN (10) + +/** VMWare char device driver minor numbers range. */ +#define VMW_CONTROL_DEVICE_MINOR_START (64) +#define VMW_RENDER_DEVICE_MINOR_START (128) +#define VMW_RENDER_DEVICE_MINOR_END (192) + +/** Name of DRM resize thread. */ +#define DRM_RESIZE_THREAD_NAME "DrmResizeThread" + +/** Name of DRM IPC server thread. */ +#define DRM_IPC_SERVER_THREAD_NAME "DrmIpcSRV" +/** Maximum length of thread name. */ +#define DRM_IPC_THREAD_NAME_MAX (16) +/** Name pattern of DRM IPC client thread. */ +#define DRM_IPC_CLIENT_THREAD_NAME_PTR "IpcCLT-%u" +/** Maximum number of simultaneous IPC client connections. */ +#define DRM_IPC_SERVER_CONNECTIONS_MAX (16) + +/** IPC client connections counter. */ +static volatile uint32_t g_cDrmIpcConnections = 0; +/* A flag which indicates whether access to IPC socket should be restricted. + * This flag caches '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property + * in order to prevent its retrieving from the host side each time a new IPC + * client connects to server. This flag is updated each time when property is + * changed on the host side. */ +static volatile bool g_fDrmIpcRestricted; + +/** Global handle to vmwgfx file descriptor (protected by #g_monitorPositionsCritSect). */ +static RTFILE g_hDevice = NIL_RTFILE; + +/** DRM version structure. */ +struct DRMVERSION +{ + int cMajor; + int cMinor; + int cPatchLevel; + size_t cbName; + char *pszName; + size_t cbDate; + char *pszDate; + size_t cbDescription; + char *pszDescription; +}; +AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *)); + +/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The + * rects argument is a cast pointer to an array of drm_vmw_rect. */ +struct DRMVMWUPDATELAYOUT +{ + uint32_t cOutputs; + uint32_t u32Pad; + uint64_t ptrRects; +}; +AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16); + +/** A node of IPC client connections list. */ +typedef struct VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE +{ + /** The list node. */ + RTLISTNODE Node; + /** List node payload. */ + PVBOX_DRMIPC_CLIENT pClient; +} VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE; + +/* Pointer to VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE. */ +typedef VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE *PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE; + +/** IPC client connections list. */ +static VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE g_ipcClientConnectionsList; + +/** IPC client connections list critical section. */ +static RTCRITSECT g_ipcClientConnectionsListCritSect; + +/** Critical section used for reporting monitors position back to host. */ +static RTCRITSECT g_monitorPositionsCritSect; + +/** Counter of how often our daemon has been re-spawned. */ +unsigned g_cRespawn = 0; +/** Logging verbosity level. */ +unsigned g_cVerbosity = 0; + +/** Path to the PID file. */ +static const char *g_pszPidFile = "/var/run/VBoxDRMClient"; + +/** Global flag which is triggered when service requested to shutdown. */ +static bool volatile g_fShutdown; + +/** + * Go over all existing IPC client connection and put set-primary-screen request + * data into TX queue of each of them . + * + * @return IPRT status code. + * @param u32PrimaryDisplay Primary display ID. + */ +static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay); + +/** + * Attempts to open DRM device by given path and check if it is + * capable for screen resize. + * + * @return DRM device handle on success, NIL_RTFILE otherwise. + * @param szPathPattern Path name pattern to the DRM device. + * @param uInstance Driver / device instance. + */ +static RTFILE vbDrmTryDevice(const char *szPathPattern, uint8_t uInstance) +{ + int rc = VERR_NOT_FOUND; + char szPath[PATH_MAX]; + struct DRMVERSION vmwgfxVersion; + RTFILE hDevice = NIL_RTFILE; + + RT_ZERO(szPath); + RT_ZERO(vmwgfxVersion); + + rc = RTStrPrintf(szPath, sizeof(szPath), szPathPattern, uInstance); + if (RT_SUCCESS(rc)) + { + rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + char szVmwgfxDriverName[sizeof(DRM_DRIVER_NAME)]; + RT_ZERO(szVmwgfxDriverName); + + vmwgfxVersion.cbName = sizeof(szVmwgfxDriverName); + vmwgfxVersion.pszName = szVmwgfxDriverName; + + /* Query driver version information and check if it can be used for screen resizing. */ + rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &vmwgfxVersion, sizeof(vmwgfxVersion), NULL); + if ( RT_SUCCESS(rc) + && strncmp(szVmwgfxDriverName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) == 0 + && ( vmwgfxVersion.cMajor > DRM_DRIVER_VERSION_MAJOR_MIN + || ( vmwgfxVersion.cMajor == DRM_DRIVER_VERSION_MAJOR_MIN + && vmwgfxVersion.cMinor >= DRM_DRIVER_VERSION_MINOR_MIN))) + { + VBClLogInfo("found compatible device: %s\n", szPath); + } + else + { + RTFileClose(hDevice); + hDevice = NIL_RTFILE; + rc = VERR_NOT_FOUND; + } + } + } + else + { + VBClLogError("unable to construct path to DRM device: %Rrc\n", rc); + } + + return RT_SUCCESS(rc) ? hDevice : NIL_RTFILE; +} + +/** + * Attempts to find and open DRM device to be used for screen resize. + * + * @return DRM device handle on success, NIL_RTFILE otherwise. + */ +static RTFILE vbDrmOpenVmwgfx(void) +{ + /* Control devices for drm graphics driver control devices go from + * controlD64 to controlD127. Render node devices go from renderD128 + * to renderD192. The driver takes resize hints via the control device + * on pre-4.10 (???) kernels and on the render device on newer ones. + * At first, try to find control device and render one if not found. + */ + uint8_t i; + RTFILE hDevice = NIL_RTFILE; + + /* Lookup control device. */ + for (i = VMW_CONTROL_DEVICE_MINOR_START; i < VMW_RENDER_DEVICE_MINOR_START; i++) + { + hDevice = vbDrmTryDevice("/dev/dri/controlD%u", i); + if (hDevice != NIL_RTFILE) + return hDevice; + } + + /* Lookup render device. */ + for (i = VMW_RENDER_DEVICE_MINOR_START; i <= VMW_RENDER_DEVICE_MINOR_END; i++) + { + hDevice = vbDrmTryDevice("/dev/dri/renderD%u", i); + if (hDevice != NIL_RTFILE) + return hDevice; + } + + VBClLogError("unable to find DRM device\n"); + + return hDevice; +} + +/** + * This function converts input monitors layout array passed from DevVMM + * into monitors layout array to be passed to DRM stack. Last validation + * request is cached. + * + * @return VINF_SUCCESS on success, VERR_DUPLICATE if monitors layout was not changed, IPRT error code otherwise. + * @param aDisplaysIn Input displays array. + * @param cDisplaysIn Number of elements in input displays array. + * @param aDisplaysOut Output displays array. + * @param cDisplaysOutMax Number of elements in output displays array. + * @param pu32PrimaryDisplay ID of a display which marked as primary. + * @param pcActualDisplays Number of displays to report to DRM stack (number of enabled displays). + * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not. + * When layout is reported by Desktop Environment helper, aDisplaysIn does not have + * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them). + */ +static int vbDrmValidateLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, + struct VBOX_DRMIPC_VMWRECT *aDisplaysOut, uint32_t *pu32PrimaryDisplay, + uint32_t cDisplaysOutMax, uint32_t *pcActualDisplays, bool fPartialLayout) +{ + /* This array is a cache of what was received from DevVMM so far. + * DevVMM may send to us partial information bout scree layout. This + * cache remembers entire picture. */ + static struct VMMDevDisplayDef aVmMonitorsCache[VBOX_DRMIPC_MONITORS_MAX]; + /* Number of valid (enabled) displays in output array. */ + uint32_t cDisplaysOut = 0; + /* Flag indicates that current layout cache is consistent and can be passed to DRM stack. */ + bool fValid = true; + + /* Make sure input array fits cache size. */ + if (cDisplaysIn > VBOX_DRMIPC_MONITORS_MAX) + { + VBClLogError("unable to validate screen layout: input (%u) array does not fit to cache size (%u)\n", + cDisplaysIn, VBOX_DRMIPC_MONITORS_MAX); + return VERR_INVALID_PARAMETER; + } + + /* Make sure there is enough space in output array. */ + if (cDisplaysIn > cDisplaysOutMax) + { + VBClLogError("unable to validate screen layout: input array (%u) is bigger than output one (%u)\n", + cDisplaysIn, cDisplaysOut); + return VERR_INVALID_PARAMETER; + } + + /* Make sure input and output arrays are of non-zero size. */ + if (!(cDisplaysIn > 0 && cDisplaysOutMax > 0)) + { + VBClLogError("unable to validate screen layout: invalid size of either input (%u) or output display array\n", + cDisplaysIn, cDisplaysOutMax); + return VERR_INVALID_PARAMETER; + } + + /* Update cache. */ + for (uint32_t i = 0; i < cDisplaysIn; i++) + { + uint32_t idDisplay = !fPartialLayout ? aDisplaysIn[i].idDisplay : i; + if (idDisplay < VBOX_DRMIPC_MONITORS_MAX) + { + if (!fPartialLayout) + { + aVmMonitorsCache[idDisplay].idDisplay = idDisplay; + aVmMonitorsCache[idDisplay].fDisplayFlags = aDisplaysIn[i].fDisplayFlags; + aVmMonitorsCache[idDisplay].cBitsPerPixel = aDisplaysIn[i].cBitsPerPixel; + } + + aVmMonitorsCache[idDisplay].cx = aDisplaysIn[i].cx; + aVmMonitorsCache[idDisplay].cy = aDisplaysIn[i].cy; + aVmMonitorsCache[idDisplay].xOrigin = aDisplaysIn[i].xOrigin; + aVmMonitorsCache[idDisplay].yOrigin = aDisplaysIn[i].yOrigin; + } + else + { + VBClLogError("received display ID (0x%x, position %u) is invalid\n", idDisplay, i); + /* If monitor configuration cannot be placed into cache, consider entire cache is invalid. */ + fValid = false; + } + } + + /* Now, go though complete cache and check if it is valid. */ + for (uint32_t i = 0; i < VBOX_DRMIPC_MONITORS_MAX; i++) + { + if (i == 0) + { + if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED) + { + VBClLogError("unable to validate screen layout: first monitor is not allowed to be disabled\n"); + fValid = false; + } + else + cDisplaysOut++; + } + else + { + /* Check if there is no hole in between monitors (i.e., if current monitor is enabled, but previous one does not). */ + if ( !(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED) + && aVmMonitorsCache[i - 1].fDisplayFlags & VMMDEV_DISPLAY_DISABLED) + { + VBClLogError("unable to validate screen layout: there is a hole in displays layout config, " + "monitor (%u) is ENABLED while (%u) does not\n", i, i - 1); + fValid = false; + } + else + { + /* Always align screens since unaligned layout will result in disaster. */ + aVmMonitorsCache[i].xOrigin = aVmMonitorsCache[i - 1].xOrigin + aVmMonitorsCache[i - 1].cx; + aVmMonitorsCache[i].yOrigin = aVmMonitorsCache[i - 1].yOrigin; + + /* Only count enabled monitors. */ + if (!(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)) + cDisplaysOut++; + } + } + } + + /* Copy out layout data. */ + if (fValid) + { + /* Start with invalid display ID. */ + uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX; + + for (uint32_t i = 0; i < cDisplaysOut; i++) + { + aDisplaysOut[i].x = aVmMonitorsCache[i].xOrigin; + aDisplaysOut[i].y = aVmMonitorsCache[i].yOrigin; + aDisplaysOut[i].w = aVmMonitorsCache[i].cx; + aDisplaysOut[i].h = aVmMonitorsCache[i].cy; + + if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY) + { + /* Make sure display layout has only one primary display + * set (for display 0, host side sets primary flag, so exclude it). */ + Assert(u32PrimaryDisplay == 0 || u32PrimaryDisplay == VBOX_DRMIPC_MONITORS_MAX); + u32PrimaryDisplay = i; + } + + VBClLogVerbose(1, "update monitor %u parameters: %dx%d, (%d, %d)\n", + i, aDisplaysOut[i].w, aDisplaysOut[i].h, aDisplaysOut[i].x, aDisplaysOut[i].y); + } + + *pu32PrimaryDisplay = u32PrimaryDisplay; + *pcActualDisplays = cDisplaysOut; + } + + return (fValid && cDisplaysOut > 0) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; +} + +/** + * This function sends screen layout data to DRM stack. + * + * Helper function for vbDrmPushScreenLayout(). Should be called + * under g_monitorPositionsCritSect lock. + * + * @return VINF_SUCCESS on success, IPRT error code otherwise. + * @param hDevice Handle to opened DRM device. + * @param paRects Array of screen configuration data. + * @param cRects Number of elements in screen configuration array. + */ +static int vbDrmSendHints(RTFILE hDevice, struct VBOX_DRMIPC_VMWRECT *paRects, uint32_t cRects) +{ + int rc = 0; + uid_t curuid; + + /* Store real user id. */ + curuid = getuid(); + + /* Change effective user id. */ + if (setreuid(0, 0) == 0) + { + struct DRMVMWUPDATELAYOUT ioctlLayout; + + RT_ZERO(ioctlLayout); + ioctlLayout.cOutputs = cRects; + ioctlLayout.ptrRects = (uint64_t)paRects; + + rc = RTFileIoCtl(hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT, + &ioctlLayout, sizeof(ioctlLayout), NULL); + + if (setreuid(curuid, 0) != 0) + { + VBClLogError("reset of setreuid failed after drm ioctl"); + rc = VERR_ACCESS_DENIED; + } + } + else + { + VBClLogError("setreuid failed during drm ioctl\n"); + rc = VERR_ACCESS_DENIED; + } + + return rc; +} + +/** + * This function converts vmwgfx monitors layout data into an array of monitor offsets + * and sends it back to the host in order to ensure that host and guest have the same + * monitors layout representation. + * + * @return IPRT status code. + * @param cDisplays Number of displays (elements in pDisplays). + * @param pDisplays Displays parameters as it was sent to vmwgfx driver. + */ +static int drmSendMonitorPositions(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pDisplays) +{ + static RTPOINT aPositions[VBOX_DRMIPC_MONITORS_MAX]; + + if (!pDisplays || !cDisplays || cDisplays > VBOX_DRMIPC_MONITORS_MAX) + { + return VERR_INVALID_PARAMETER; + } + + /* Prepare monitor offsets list to be sent to the host. */ + for (uint32_t i = 0; i < cDisplays; i++) + { + aPositions[i].x = pDisplays[i].x; + aPositions[i].y = pDisplays[i].y; + } + + return VbglR3SeamlessSendMonitorPositions(cDisplays, aPositions); +} + +/** + * Validate and apply screen layout data. + * + * @return IPRT status code. + * @param aDisplaysIn An array with screen layout data. + * @param cDisplaysIn Number of elements in aDisplaysIn. + * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not. + * When layout is reported by Desktop Environment helper, aDisplaysIn does not have + * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them). + * @param fApply Whether to apply provided display layout data to the DRM stack or send display offsets only. + */ +static int vbDrmPushScreenLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, bool fPartialLayout, bool fApply) +{ + int rc; + + struct VBOX_DRMIPC_VMWRECT aDisplaysOut[VBOX_DRMIPC_MONITORS_MAX]; + uint32_t cDisplaysOut = 0; + + uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX; + + rc = RTCritSectEnter(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to lock monitor data cache, rc=%Rrc\n", rc); + return rc; + } + + static uint32_t u32PrimaryDisplayLast = VBOX_DRMIPC_MONITORS_MAX; + + RT_ZERO(aDisplaysOut); + + /* Validate displays layout and push it to DRM stack if valid. */ + rc = vbDrmValidateLayout(aDisplaysIn, cDisplaysIn, aDisplaysOut, &u32PrimaryDisplay, + sizeof(aDisplaysOut), &cDisplaysOut, fPartialLayout); + if (RT_SUCCESS(rc)) + { + if (fApply) + { + rc = vbDrmSendHints(g_hDevice, aDisplaysOut, cDisplaysOut); + VBClLogInfo("push screen layout data of %u display(s) to DRM stack, fPartialLayout=%RTbool, rc=%Rrc\n", + cDisplaysOut, fPartialLayout, rc); + } + + /* In addition, notify host that configuration was successfully applied to the guest vmwgfx driver. */ + if (RT_SUCCESS(rc)) + { + rc = drmSendMonitorPositions(cDisplaysOut, aDisplaysOut); + if (RT_FAILURE(rc)) + VBClLogError("cannot send host notification: %Rrc\n", rc); + + /* If information about primary display is present in display layout, send it to DE over IPC. */ + if (u32PrimaryDisplay != VBOX_DRMIPC_MONITORS_MAX + && u32PrimaryDisplayLast != u32PrimaryDisplay) + { + rc = vbDrmIpcBroadcastPrimaryDisplay(u32PrimaryDisplay); + + /* Cache last value in order to avoid sending duplicate data over IPC. */ + u32PrimaryDisplayLast = u32PrimaryDisplay; + + VBClLogVerbose(2, "DE was notified that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc); + } + else + VBClLogVerbose(2, "do not notify DE second time that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc); + } + } + else if (rc == VERR_DUPLICATE) + VBClLogVerbose(2, "do not notify DRM stack about monitors layout change twice, rc=%Rrc\n", rc); + else + VBClLogError("displays layout is invalid, will not notify guest driver, rc=%Rrc\n", rc); + + int rc2 = RTCritSectLeave(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to unlock monitor data cache, rc=%Rrc\n", rc); + + return rc; +} + +/** Worker thread for resize task. */ +static DECLCALLBACK(int) vbDrmResizeWorker(RTTHREAD ThreadSelf, void *pvUser) +{ + int rc = VERR_GENERAL_FAILURE; + + RT_NOREF(ThreadSelf); + RT_NOREF(pvUser); + + for (;;) + { + /* Do not acknowledge the first event we query for to pick up old events, + * e.g. from before a guest reboot. */ + bool fAck = false; + + uint32_t events; + + VMMDevDisplayDef aDisplaysIn[VBOX_DRMIPC_MONITORS_MAX]; + uint32_t cDisplaysIn = 0; + + RT_ZERO(aDisplaysIn); + + /* Query the first size without waiting. This lets us e.g. pick up + * the last event before a guest reboot when we start again after. */ + rc = VbglR3GetDisplayChangeRequestMulti(VBOX_DRMIPC_MONITORS_MAX, &cDisplaysIn, aDisplaysIn, fAck); + fAck = true; + if (RT_SUCCESS(rc)) + { + rc = vbDrmPushScreenLayout(aDisplaysIn, cDisplaysIn, false, true); + if (RT_FAILURE(rc)) + VBClLogError("Failed to push display change as requested by host, rc=%Rrc\n", rc); + } + else + VBClLogError("Failed to get display change request, rc=%Rrc\n", rc); + + do + { + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_DRMIPC_RX_TIMEOUT_MS, &events); + } while ((rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) && !ASMAtomicReadBool(&g_fShutdown)); + + if (ASMAtomicReadBool(&g_fShutdown)) + { + VBClLogInfo("exiting resize thread: shutdown requested\n"); + /* This is a case when we should return positive status. */ + rc = (rc == VERR_TIMEOUT) ? VINF_SUCCESS : rc; + break; + } + else if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: resize thread: failure waiting for event, rc=%Rrc\n", rc); + } + + return rc; +} + +/** + * Go over all existing IPC client connection and put set-primary-screen request + * data into TX queue of each of them . + * + * @return IPRT status code. + * @param u32PrimaryDisplay Primary display ID. + */ +static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay) +{ + int rc; + + rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect); + if (RT_SUCCESS(rc)) + { + if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node)) + { + PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry; + RTListForEach(&g_ipcClientConnectionsList.Node, pEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node) + { + AssertReturn(pEntry, VERR_INVALID_PARAMETER); + AssertReturn(pEntry->pClient, VERR_INVALID_PARAMETER); + AssertReturn(pEntry->pClient->hThread, VERR_INVALID_PARAMETER); + + rc = vbDrmIpcSetPrimaryDisplay(pEntry->pClient, u32PrimaryDisplay); + + VBClLogInfo("thread %s notified IPC Client that display %u is now primary, rc=%Rrc\n", + RTThreadGetName(pEntry->pClient->hThread), u32PrimaryDisplay, rc); + } + } + + int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("notify DE: unable to leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("notify DE: unable to enter critical section, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Main loop for IPC client connection handling. + * + * @return IPRT status code. + * @param pClient Pointer to IPC client data. + */ +static int vbDrmIpcConnectionProc(PVBOX_DRMIPC_CLIENT pClient) +{ + int rc = VERR_GENERAL_FAILURE; + + AssertReturn(pClient, VERR_INVALID_PARAMETER); + + /* This loop handles incoming messages. */ + for (;;) + { + rc = vbDrmIpcConnectionHandler(pClient); + + /* Try to detect if we should shutdown as early as we can. */ + if (ASMAtomicReadBool(&g_fShutdown)) + break; + + /* Normal case. No data received within short interval. */ + if (rc == VERR_TIMEOUT) + { + continue; + } + else if (RT_FAILURE(rc)) + { + /* Terminate connection handling in case of error. */ + VBClLogError("unable to handle IPC session, rc=%Rrc\n", rc); + break; + } + } + + return rc; +} + +/** + * Add IPC client connection data into list of connections. + * + * List size is limited indirectly by DRM_IPC_SERVER_CONNECTIONS_MAX value. + * This function should only be invoked from client thread context + * (from vbDrmIpcClientWorker() in particular). + * + * @return IPRT status code. + * @param pClientNode Client connection information to add to the list. + */ +static int vbDrmIpcClientsListAdd(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode) +{ + int rc; + + AssertReturn(pClientNode, VERR_INVALID_PARAMETER); + + rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect); + if (RT_SUCCESS(rc)) + { + RTListAppend(&g_ipcClientConnectionsList.Node, &pClientNode->Node); + + int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("add client connection: unable to leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("add client connection: unable to enter critical section, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Remove IPC client connection data from list of connections. + * + * This function should only be invoked from client thread context + * (from vbDrmIpcClientWorker() in particular). + * + * @return IPRT status code. + * @param pClientNode Client connection information to remove from the list. + */ +static int vbDrmIpcClientsListRemove(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode) +{ + int rc; + PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry, pNextEntry, pFound = NULL; + + AssertReturn(pClientNode, VERR_INVALID_PARAMETER); + + rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect); + if (RT_SUCCESS(rc)) + { + + if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node)) + { + RTListForEachSafe(&g_ipcClientConnectionsList.Node, pEntry, pNextEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node) + { + if (pEntry == pClientNode) + pFound = (PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE)RTListNodeRemoveRet(&pEntry->Node); + } + } + else + VBClLogError("remove client connection: connections list empty, node %p not there\n", pClientNode); + + int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("remove client connection: unable to leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("remove client connection: unable to enter critical section, rc=%Rrc\n", rc); + + if (!pFound) + VBClLogError("remove client connection: node not found\n"); + + return !rc && pFound ? VINF_SUCCESS : VERR_INVALID_PARAMETER; +} + +/** + * Convert VBOX_DRMIPC_VMWRECT into VMMDevDisplayDef and check layout correctness. + * + * VBOX_DRMIPC_VMWRECT does not represent enough information needed for + * VMMDevDisplayDef. Missing fields (fDisplayFlags, idDisplay, cBitsPerPixel) + * are initialized with default (invalid) values due to this. + * + * @return True if given screen layout is correct (i.e., has no displays which overlap), False + * if it needs to be adjusted before injecting into DRM stack. + * @param cDisplays Number of displays in configuration data. + * @param pIn A pointer to display configuration data array in form of VBOX_DRMIPC_VMWRECT (input). + * @param pOut A pointer to display configuration data array in form of VMMDevDisplayDef (output). + */ +static bool vbDrmVmwRectToDisplayDef(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pIn, VMMDevDisplayDef *pOut) +{ + bool fCorrect = true; + + for (uint32_t i = 0; i < cDisplays; i++) + { + /* VBOX_DRMIPC_VMWRECT has no information about this fields. */ + pOut[i].fDisplayFlags = 0; + pOut[i].idDisplay = VBOX_DRMIPC_MONITORS_MAX; + pOut[i].cBitsPerPixel = 0; + + pOut[i].xOrigin = pIn[i].x; + pOut[i].yOrigin = pIn[i].y; + pOut[i].cx = pIn[i].w; + pOut[i].cy = pIn[i].h; + + /* Make sure that displays do not overlap within reported screen layout. Ask IPC server to fix layout otherwise. */ + fCorrect = i > 0 + && pIn[i].x != (int32_t)pIn[i - 1].w + pIn[i - 1].x + ? false + : fCorrect; + } + + return fCorrect; +} + +/** + * @interface_method_impl{VBOX_DRMIPC_CLIENT,pfnRxCb} + */ +static DECLCALLBACK(int) vbDrmIpcClientRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData) +{ + int rc = VERR_INVALID_PARAMETER; + + AssertReturn(pvData, VERR_INVALID_PARAMETER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + switch (idCmd) + { + case VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS: + { + VMMDevDisplayDef aDisplays[VBOX_DRMIPC_MONITORS_MAX]; + bool fCorrect; + + PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)pvData; + AssertReturn(cbData == sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS), VERR_INVALID_PARAMETER); + AssertReturn(pCmd->cDisplays < VBOX_DRMIPC_MONITORS_MAX, VERR_INVALID_PARAMETER); + + /* Convert input display config into VMMDevDisplayDef representation. */ + RT_ZERO(aDisplays); + fCorrect = vbDrmVmwRectToDisplayDef(pCmd->cDisplays, pCmd->aDisplays, aDisplays); + + rc = vbDrmPushScreenLayout(aDisplays, pCmd->cDisplays, true, !fCorrect); + if (RT_FAILURE(rc)) + VBClLogError("Failed to push display change as requested by Desktop Environment helper, rc=%Rrc\n", rc); + + break; + } + + default: + { + VBClLogError("received unknown IPC command 0x%x\n", idCmd); + break; + } + } + + return rc; +} + +/** Worker thread for IPC client task. */ +static DECLCALLBACK(int) vbDrmIpcClientWorker(RTTHREAD ThreadSelf, void *pvUser) +{ + VBOX_DRMIPC_CLIENT hClient = VBOX_DRMIPC_CLIENT_INITIALIZER; + RTLOCALIPCSESSION hSession = (RTLOCALIPCSESSION)pvUser; + int rc; + + AssertReturn(RT_VALID_PTR(hSession), VERR_INVALID_PARAMETER); + + /* Initialize client session resources. */ + rc = vbDrmIpcClientInit(&hClient, ThreadSelf, hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbDrmIpcClientRxCallBack); + if (RT_SUCCESS(rc)) + { + /* Add IPC client connection data into clients list. */ + VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE hClientNode = { { 0, 0 } , &hClient }; + + rc = vbDrmIpcClientsListAdd(&hClientNode); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserSignal(ThreadSelf); + if (RT_SUCCESS(rc)) + { + /* Start spinning the connection. */ + VBClLogInfo("IPC client connection started\n", rc); + rc = vbDrmIpcConnectionProc(&hClient); + VBClLogInfo("IPC client connection ended, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to report IPC client connection handler start, rc=%Rrc\n", rc); + + /* Remove IPC client connection data from clients list. */ + rc = vbDrmIpcClientsListRemove(&hClientNode); + if (RT_FAILURE(rc)) + VBClLogError("unable to remove IPC client session from list of connections, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to add IPC client connection to the list, rc=%Rrc\n"); + + /* Disconnect remote peer if still connected. */ + if (RT_VALID_PTR(hSession)) + { + rc = RTLocalIpcSessionClose(hSession); + VBClLogInfo("IPC session closed, rc=%Rrc\n", rc); + } + + /* Connection handler loop has ended, release session resources. */ + rc = vbDrmIpcClientReleaseResources(&hClient); + if (RT_FAILURE(rc)) + VBClLogError("unable to release IPC client session, rc=%Rrc\n", rc); + + ASMAtomicDecU32(&g_cDrmIpcConnections); + } + else + VBClLogError("unable to initialize IPC client session, rc=%Rrc\n", rc); + + VBClLogInfo("closing IPC client session, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Start processing thread for IPC client requests handling. + * + * @returns IPRT status code. + * @param hSession IPC client connection handle. + */ +static int vbDrmIpcClientStart(RTLOCALIPCSESSION hSession) +{ + int rc; + RTTHREAD hThread = 0; + RTPROCESS hProcess = 0; + + rc = RTLocalIpcSessionQueryProcess(hSession, &hProcess); + if (RT_SUCCESS(rc)) + { + char pszThreadName[DRM_IPC_THREAD_NAME_MAX]; + RT_ZERO(pszThreadName); + + RTStrPrintf2(pszThreadName, DRM_IPC_THREAD_NAME_MAX, DRM_IPC_CLIENT_THREAD_NAME_PTR, hProcess); + + /* Attempt to start IPC client connection handler task. */ + rc = RTThreadCreate(&hThread, vbDrmIpcClientWorker, (void *)hSession, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, pszThreadName); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserWait(hThread, RT_MS_5SEC); + } + } + + return rc; +} + +/** Worker thread for IPC server task. */ +static DECLCALLBACK(int) vbDrmIpcServerWorker(RTTHREAD ThreadSelf, void *pvUser) +{ + int rc = VERR_GENERAL_FAILURE; + RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser; + + RT_NOREF1(ThreadSelf); + + AssertReturn(hIpcServer, VERR_INVALID_PARAMETER); + + /* This loop accepts incoming connections. */ + for (;;) + { + RTLOCALIPCSESSION hClientSession; + + /* Wait for incoming connection. */ + rc = RTLocalIpcServerListen(hIpcServer, &hClientSession); + if (RT_SUCCESS(rc)) + { + VBClLogVerbose(2, "new IPC session\n"); + + if (ASMAtomicIncU32(&g_cDrmIpcConnections) <= DRM_IPC_SERVER_CONNECTIONS_MAX) + { + /* Authenticate remote peer. */ + if (ASMAtomicReadBool(&g_fDrmIpcRestricted)) + rc = vbDrmIpcAuth(hClientSession); + + if (RT_SUCCESS(rc)) + { + /* Start incoming connection handler thread. */ + rc = vbDrmIpcClientStart(hClientSession); + VBClLogVerbose(2, "connection processing ended, rc=%Rrc\n", rc); + } + else + VBClLogError("IPC authentication failed, rc=%Rrc\n", rc); + } + else + rc = VERR_RESOURCE_BUSY; + + /* Release resources in case of error. */ + if (RT_FAILURE(rc)) + { + VBClLogError("maximum amount of IPC client connections reached, dropping connection\n"); + + int rc2 = RTLocalIpcSessionClose(hClientSession); + if (RT_FAILURE(rc2)) + VBClLogError("unable to close IPC session, rc=%Rrc\n", rc2); + + ASMAtomicDecU32(&g_cDrmIpcConnections); + } + } + else + VBClLogError("IPC authentication failed, rc=%Rrc\n", rc); + + /* Check shutdown was requested. */ + if (ASMAtomicReadBool(&g_fShutdown)) + { + VBClLogInfo("exiting IPC thread: shutdown requested\n"); + break; + } + + /* Wait a bit before spinning a loop if something went wrong. */ + if (RT_FAILURE(rc)) + RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS); + } + + return rc; +} + +/** A signal handler. */ +static void vbDrmRequestShutdown(int sig) +{ + RT_NOREF(sig); + ASMAtomicWriteBool(&g_fShutdown, true); +} + +/** + * Grant access to DRM IPC server socket depending on VM configuration. + * + * If VM has '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property set + * and this property is READ-ONLY for the guest side, access will be + * granted to root and users from 'vboxdrmipc' group only. If group does + * not exists, only root will have access to the socket. When property is + * not set or not READ-ONLY, all users will have access to the socket. + * + * @param hIpcServer IPC server handle. + * @param fRestrict Whether to restrict access to socket or not. + */ +static void vbDrmSetIpcServerAccessPermissions(RTLOCALIPCSERVER hIpcServer, bool fRestrict) +{ + int rc; + + if (fRestrict) + { + struct group *pGrp; + pGrp = getgrnam(VBOX_DRMIPC_USER_GROUP); + if (pGrp) + { + rc = RTLocalIpcServerGrantGroupAccess(hIpcServer, pGrp->gr_gid); + if (RT_SUCCESS(rc)) + VBClLogInfo("IPC server socket access granted to '" VBOX_DRMIPC_USER_GROUP "' users\n"); + else + VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "' users, rc=%Rrc\n", rc); + + } + else + VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "', group does not exist\n"); + } + else + { + rc = RTLocalIpcServerSetAccessMode(hIpcServer, + RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR | + RTFS_UNIX_IRGRP | RTFS_UNIX_IWGRP | + RTFS_UNIX_IROTH | RTFS_UNIX_IWOTH); + if (RT_SUCCESS(rc)) + VBClLogInfo("IPC server socket access granted to all users\n"); + else + VBClLogError("unable to grant IPC server socket access to all users, rc=%Rrc\n", rc); + } + + /* Set flag for the thread which serves incomming IPC connections. */ + ASMAtomicWriteBool(&g_fDrmIpcRestricted, fRestrict); +} + +/** + * Wait and handle '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property change. + * + * This function is executed in context of main(). + * + * @param hIpcServer IPC server handle. + */ +static void vbDrmPollIpcServerAccessMode(RTLOCALIPCSERVER hIpcServer) +{ + HGCMCLIENTID idClient; + int rc; + + rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + do + { + /* Buffer should be big enough to fit guest property data layout: Name\0Value\0Flags\0fWasDeleted\0. */ + static char achBuf[GUEST_PROP_MAX_NAME_LEN]; + char *pszName = NULL; + char *pszValue = NULL; + char *pszFlags = NULL; + bool fWasDeleted = false; + uint64_t u64Timestamp = 0; + + rc = VbglR3GuestPropWait(idClient, VBGLR3DRMPROPPTR, achBuf, sizeof(achBuf), u64Timestamp, + VBOX_DRMIPC_RX_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp, + &pszFlags, NULL, &fWasDeleted); + if (RT_SUCCESS(rc)) + { + uint32_t fFlags = 0; + + VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n", + pszName, pszValue, pszFlags, fWasDeleted); + + if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags))) + { + if (RTStrNCmp(pszName, VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_MAX_NAME_LEN) == 0) + { + /* Enforce restricted socket access until guest property exist and READ-ONLY for the guest. */ + vbDrmSetIpcServerAccessPermissions(hIpcServer, !fWasDeleted && fFlags & GUEST_PROP_F_RDONLYGUEST); + } + + } else + VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n", + pszName, pszValue, pszFlags, fWasDeleted); + + } else if ( rc != VERR_TIMEOUT + && rc != VERR_INTERRUPTED) + { + VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc); + RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS); + } + + } while (!ASMAtomicReadBool(&g_fShutdown)); + + VbglR3GuestPropDisconnect(idClient); + } + else + VBClLogError("cannot connect to VM guest properties service, rc=%Rrc\n", rc); +} + +int main(int argc, char *argv[]) +{ + /** Custom log prefix to be used for logger instance of this process. */ + static const char *pszLogPrefix = "VBoxDRMClient:"; + + static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, }; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int ch; + + RTFILE hPidFile; + + RTLOCALIPCSERVER hIpcServer; + RTTHREAD vbDrmIpcThread; + int rcDrmIpcThread = 0; + + RTTHREAD drmResizeThread; + int rcDrmResizeThread = 0; + int rc, rc2 = 0; + + rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: VbglR3InitUser failed: %Rrc", rc); + + /* Process command line options. */ + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */); + if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: unable to process command line options, rc=%Rrc\n", rc); + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'v': + { + g_cVerbosity++; + break; + } + + case VERR_GETOPT_UNKNOWN_OPTION: + { + VBClLogFatalError("unknown command line option '%s'\n", ValueUnion.psz); + return RTEXITCODE_SYNTAX; + + } + + default: + break; + } + } + + rc = VBClLogCreate(""); + if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: failed to setup logging, rc=%Rrc\n", rc); + VBClLogSetLogPrefix(pszLogPrefix); + + /* Check PID file before attempting to initialize anything. */ + rc = VbglR3PidFile(g_pszPidFile, &hPidFile); + if (rc == VERR_FILE_LOCK_VIOLATION) + { + VBClLogInfo("already running, exiting\n"); + return RTEXITCODE_SUCCESS; + } + if (RT_FAILURE(rc)) + { + VBClLogError("unable to lock PID file (%Rrc), exiting\n", rc); + return RTEXITCODE_FAILURE; + } + + g_hDevice = vbDrmOpenVmwgfx(); + if (g_hDevice == NIL_RTFILE) + return RTEXITCODE_FAILURE; + + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + { + VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false); + if (RT_FAILURE(rc)) + { + VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + /* Setup signals: gracefully terminate on SIGINT, SIGTERM. */ + if ( signal(SIGINT, vbDrmRequestShutdown) == SIG_ERR + || signal(SIGTERM, vbDrmRequestShutdown) == SIG_ERR) + { + VBClLogError("unable to setup signals\n"); + return RTEXITCODE_FAILURE; + } + + /* Init IPC client connection list. */ + RTListInit(&g_ipcClientConnectionsList.Node); + rc = RTCritSectInit(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to initialize IPC client connection list critical section\n"); + return RTEXITCODE_FAILURE; + } + + /* Init critical section which is used for reporting monitors offset back to host. */ + rc = RTCritSectInit(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to initialize monitors position critical section\n"); + return RTEXITCODE_FAILURE; + } + + /* Instantiate IPC server for VBoxClient service communication. */ + rc = RTLocalIpcServerCreate(&hIpcServer, VBOX_DRMIPC_SERVER_NAME, 0); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to setup IPC server, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + /* Set IPC server socket access permissions according to VM configuration. */ + vbDrmSetIpcServerAccessPermissions(hIpcServer, VbglR3DrmRestrictedIpcAccessIsNeeded()); + + /* Attempt to start DRM resize task. */ + rc = RTThreadCreate(&drmResizeThread, vbDrmResizeWorker, NULL, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_RESIZE_THREAD_NAME); + if (RT_SUCCESS(rc)) + { + /* Attempt to start IPC task. */ + rc = RTThreadCreate(&vbDrmIpcThread, vbDrmIpcServerWorker, (void *)hIpcServer, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_IPC_SERVER_THREAD_NAME); + if (RT_SUCCESS(rc)) + { + /* Poll for host notification about IPC server socket access mode change. */ + vbDrmPollIpcServerAccessMode(hIpcServer); + + /* HACK ALERT! + * The sequence of RTThreadWait(drmResizeThread) -> RTLocalIpcServerDestroy() -> RTThreadWait(vbDrmIpcThread) + * is intentional! Once process received a signal, it will pull g_fShutdown flag, which in turn will cause + * drmResizeThread to quit. The vbDrmIpcThread might hang on accept() call, so we terminate IPC server to + * release it and then wait for its termination. */ + + rc = RTThreadWait(drmResizeThread, RT_INDEFINITE_WAIT, &rcDrmResizeThread); + VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_RESIZE_THREAD_NAME, rcDrmResizeThread); + + rc = RTLocalIpcServerCancel(hIpcServer); + if (RT_FAILURE(rc)) + VBClLogError("unable to notify IPC server about shutdown, rc=%Rrc\n", rc); + + /* Wait for threads to terminate gracefully. */ + rc = RTThreadWait(vbDrmIpcThread, RT_INDEFINITE_WAIT, &rcDrmIpcThread); + VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_IPC_SERVER_THREAD_NAME, rcDrmResizeThread); + + } + else + VBClLogError("unable to start IPC thread, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to start resize thread, rc=%Rrc\n", rc); + + rc = RTLocalIpcServerDestroy(hIpcServer); + if (RT_FAILURE(rc)) + VBClLogError("unable to stop IPC server, rc=%Rrc\n", rc); + + rc2 = RTCritSectDelete(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to destroy g_monitorPositionsCritSect critsect, rc=%Rrc\n", rc2); + + rc2 = RTCritSectDelete(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to destroy g_ipcClientConnectionsListCritSect critsect, rc=%Rrc\n", rc2); + + RTFileClose(g_hDevice); + + VBClLogInfo("releasing PID file lock\n"); + VbglR3ClosePidFile(g_pszPidFile, hPidFile); + + VBClLogDestroy(); + + return rc == 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} |