VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp@ 95478

Last change on this file since 95478 was 94427, checked in by vboxsync, 3 years ago

X11/VBoxClient: doxygen fixes (don't duplicate/confuse docs, newer doxygen version dislikes it (sometimes)).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.2 KB
Line 
1/* $Id: display-ipc.cpp 94427 2022-03-31 23:58:08Z vboxsync $ */
2/** @file
3 * Guest Additions - DRM IPC communication core functions.
4 */
5
6/*
7 * Copyright (C) 2017-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*
19 * This module implements connection handling routine which is common for
20 * both IPC server and client (see vbDrmIpcConnectionHandler()). This function
21 * at first tries to read incoming command from IPC socket and if no data has
22 * arrived within VBOX_DRMIPC_RX_TIMEOUT_MS, it checks is there is some data in
23 * TX queue and sends it. TX queue and IPC connection handle is unique per IPC
24 * client and handled in a separate thread of either server or client process.
25 *
26 * Logging is implemented in a way that errors are always printed out,
27 * VBClLogVerbose(2) is used for debugging purposes and reflects what is related to
28 * IPC communication. In order to see logging on a host side it is enough to do:
29 *
30 * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host.
31 */
32
33#include "VBoxClient.h"
34#include "display-ipc.h"
35
36#include <VBox/VBoxGuestLib.h>
37
38#include <iprt/localipc.h>
39#include <iprt/err.h>
40#include <iprt/crc.h>
41#include <iprt/mem.h>
42#include <iprt/asm.h>
43#include <iprt/critsect.h>
44#include <iprt/assert.h>
45
46#include <grp.h>
47#include <pwd.h>
48#include <errno.h>
49#include <limits.h>
50#include <unistd.h>
51
52/**
53 * Calculate size of TX list entry.
54 *
55 * TX list entry consists of RTLISTNODE, DRM IPC message header and message payload.
56 * Given IpcCmd already includes message header and payload. So, TX list entry size
57 * equals to size of IpcCmd plus size of RTLISTNODE.
58 *
59 * @param IpcCmd A structure which represents DRM IPC command.
60 */
61#define DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(IpcCmd) (sizeof(IpcCmd) + RT_UOFFSETOF(VBOX_DRMIPC_TX_LIST_ENTRY, Hdr))
62
63/**
64 * Initialize IPC client private data.
65 *
66 * @return IPRT status code.
67 * @param pClient IPC client private data to be initialized.
68 * @param hThread A thread which server IPC client connection.
69 * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX().
70 * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session.
71 * @param pfnRxCb IPC RX callback function pointer.
72 */
73RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession,
74 uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb)
75{
76 AssertReturn(pClient, VERR_INVALID_PARAMETER);
77 AssertReturn(hThread, VERR_INVALID_PARAMETER);
78 AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
79 AssertReturn(cTxListCapacity, VERR_INVALID_PARAMETER);
80 AssertReturn(pfnRxCb, VERR_INVALID_PARAMETER);
81
82 pClient->hThread = hThread;
83 pClient->hClientSession = hClientSession;
84
85 RT_ZERO(pClient->TxList);
86 RTListInit(&pClient->TxList.Node);
87
88 pClient->cTxListCapacity = cTxListCapacity;
89 ASMAtomicWriteU32(&pClient->cTxListSize, 0);
90
91 pClient->pfnRxCb = pfnRxCb;
92
93 return RTCritSectInit(&pClient->CritSect);
94}
95
96/**
97 * Releases IPC client private data resources.
98 *
99 * @return IPRT status code.
100 * @param pClient IPC session private data to be initialized.
101 */
102RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient)
103{
104 PVBOX_DRMIPC_TX_LIST_ENTRY pEntry, pNextEntry;
105 int rc;
106
107 pClient->hClientSession = 0;
108
109 rc = RTCritSectEnter(&pClient->CritSect);
110 if (RT_SUCCESS(rc))
111 {
112 if (!RTListIsEmpty(&pClient->TxList.Node))
113 {
114 RTListForEachSafe(&pClient->TxList.Node, pEntry, pNextEntry, VBOX_DRMIPC_TX_LIST_ENTRY, Node)
115 {
116 RTListNodeRemove(&pEntry->Node);
117 RTMemFree(pEntry);
118 ASMAtomicDecU32(&pClient->cTxListSize);
119 }
120 }
121
122 rc = RTCritSectLeave(&pClient->CritSect);
123 if (RT_SUCCESS(rc))
124 {
125 rc = RTCritSectDelete(&pClient->CritSect);
126 if (RT_FAILURE(rc))
127 VBClLogError("vbDrmIpcClientReleaseResources: unable to delete critical section, rc=%Rrc\n", rc);
128 }
129 else
130 VBClLogError("vbDrmIpcClientReleaseResources: unable to leave critical section, rc=%Rrc\n", rc);
131 }
132 else
133 VBClLogError("vbDrmIpcClientReleaseResources: unable to enter critical section, rc=%Rrc\n", rc);
134
135 Assert(ASMAtomicReadU32(&pClient->cTxListSize) == 0);
136
137 RT_ZERO(*pClient);
138
139 return rc;
140}
141
142/**
143 * Add message to IPC session TX queue.
144 *
145 * @return IPRT status code.
146 * @param pClient IPC session private data.
147 * @param pEntry Pointer to the message.
148 */
149static int vbDrmIpcSessionScheduleTx(PVBOX_DRMIPC_CLIENT pClient, PVBOX_DRMIPC_TX_LIST_ENTRY pEntry)
150{
151 int rc;
152
153 AssertReturn(pClient, VERR_INVALID_PARAMETER);
154 AssertReturn(pEntry, VERR_INVALID_PARAMETER);
155
156 rc = RTCritSectEnter(&pClient->CritSect);
157 if (RT_SUCCESS(rc))
158 {
159 if (pClient->cTxListSize < pClient->cTxListCapacity)
160 {
161 RTListAppend(&pClient->TxList.Node, &pEntry->Node);
162 pClient->cTxListSize++;
163 }
164 else
165 VBClLogError("vbDrmIpcSessionScheduleTx: TX queue is full\n");
166
167 int rc2 = RTCritSectLeave(&pClient->CritSect);
168 if (RT_FAILURE(rc2))
169 VBClLogError("vbDrmIpcSessionScheduleTx: cannot leave critical section, rc=%Rrc\n", rc2);
170 }
171 else
172 VBClLogError("vbDrmIpcSessionScheduleTx: cannot enter critical section, rc=%Rrc\n", rc);
173
174 return rc;
175}
176
177/**
178 * Pick up message from TX queue if available.
179 *
180 * @return Pointer to list entry or NULL if queue is empty.
181 */
182static PVBOX_DRMIPC_TX_LIST_ENTRY vbDrmIpcSessionPickupTxMessage(PVBOX_DRMIPC_CLIENT pClient)
183{
184 PVBOX_DRMIPC_TX_LIST_ENTRY pEntry = NULL;
185 int rc;
186
187 AssertReturn(pClient, NULL);
188
189 rc = RTCritSectEnter(&pClient->CritSect);
190 if (RT_SUCCESS(rc))
191 {
192 if (!RTListIsEmpty(&pClient->TxList.Node))
193 {
194 pEntry = (PVBOX_DRMIPC_TX_LIST_ENTRY)RTListRemoveFirst(&pClient->TxList.Node, VBOX_DRMIPC_TX_LIST_ENTRY, Node);
195 pClient->cTxListSize--;
196 Assert(pEntry);
197 }
198
199 int rc2 = RTCritSectLeave(&pClient->CritSect);
200 if (RT_FAILURE(rc2))
201 VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot leave critical section, rc=%Rrc\n", rc2);
202 }
203 else
204 VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot enter critical section, rc=%Rrc\n", rc);
205
206 return pEntry;
207}
208
209RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession)
210{
211 int rc = VERR_ACCESS_DENIED;
212 RTUID uUid;
213 struct group *pAllowedGroup;
214
215 AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
216
217 /* Get DRM IPC user group entry from system database. */
218 pAllowedGroup = getgrnam(VBOX_DRMIPC_USER_GROUP);
219 if (!pAllowedGroup)
220 return RTErrConvertFromErrno(errno);
221
222 /* Get remote user ID and check if it is in allowed user group. */
223 rc = RTLocalIpcSessionQueryUserId(hClientSession, &uUid);
224 if (RT_SUCCESS(rc))
225 {
226 /* Get user record from system database and look for it in group's members list. */
227 struct passwd *UserRecord = getpwuid(uUid);
228
229 if (UserRecord && UserRecord->pw_name)
230 {
231 while (*pAllowedGroup->gr_mem)
232 {
233 if (RTStrNCmp(*pAllowedGroup->gr_mem, UserRecord->pw_name, LOGIN_NAME_MAX) == 0)
234 return VINF_SUCCESS;
235
236 pAllowedGroup->gr_mem++;
237 }
238 }
239 }
240
241 return rc;
242}
243
244RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay)
245{
246 int rc = VERR_GENERAL_FAILURE;
247
248 PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
249 (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY));
250
251 if (pTxListEntry)
252 {
253 PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)(&pTxListEntry->Hdr);
254
255 pCmd->Hdr.idCmd = VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY;
256 pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY);
257 pCmd->idDisplay = idDisplay;
258 pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
259 Assert(pCmd->Hdr.u64Crc);
260
261 /* Put command into queue and trigger TX. */
262 rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
263 if (RT_SUCCESS(rc))
264 {
265 VBClLogVerbose(2, "vbDrmIpcSetPrimaryDisplay: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
266 }
267 else
268 {
269 RTMemFree(pTxListEntry);
270 VBClLogError("vbDrmIpcSetPrimaryDisplay: unable to schedule TX, rc=%Rrc\n", rc);
271 }
272 }
273 else
274 {
275 VBClLogInfo("cannot allocate SET_PRIMARY_DISPLAY command\n");
276 rc = VERR_NO_MEMORY;
277 }
278
279 return rc;
280}
281
282/**
283 * Report to IPC server that display layout offsets have been changed (called by IPC client).
284 *
285 * @return IPRT status code.
286 * @param pClient IPC session private data.
287 * @param cDisplays Number of monitors which have offsets changed.
288 * @param aDisplays Offsets data.
289 */
290RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
291{
292 int rc = VERR_GENERAL_FAILURE;
293
294 PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
295 (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(
296 DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS));
297
298 if (pTxListEntry)
299 {
300 PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)(&pTxListEntry->Hdr);
301
302 pCmd->Hdr.idCmd = VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS;
303 pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS);
304 pCmd->cDisplays = cDisplays;
305 memcpy(pCmd->aDisplays, aDisplays, cDisplays * sizeof(struct VBOX_DRMIPC_VMWRECT));
306 pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
307 Assert(pCmd->Hdr.u64Crc);
308
309 /* Put command into queue and trigger TX. */
310 rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
311 if (RT_SUCCESS(rc))
312 {
313 VBClLogVerbose(2, "vbDrmIpcReportDisplayOffsets: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
314 }
315 else
316 {
317 RTMemFree(pTxListEntry);
318 VBClLogError("vbDrmIpcReportDisplayOffsets: unable to schedule TX, rc=%Rrc\n", rc);
319 }
320 }
321 else
322 {
323 VBClLogInfo("cannot allocate REPORT_DISPLAY_OFFSETS command\n");
324 rc = VERR_NO_MEMORY;
325 }
326
327 return rc;
328}
329
330/**
331 * Common function for both IPC server and client which is responsible
332 * for handling IPC communication flow.
333 *
334 * @return IPRT status code.
335 * @param pClient IPC connection private data.
336 */
337RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient)
338{
339 int rc;
340 static uint8_t aInputBuf[VBOX_DRMIPC_RX_BUFFER_SIZE];
341 size_t cbRead = 0;
342 PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry;
343
344 AssertReturn(pClient, VERR_INVALID_PARAMETER);
345
346 /* Make sure we are still connected to IPC server. */
347 if (!pClient->hClientSession)
348 {
349 VBClLogVerbose(2, "connection to IPC server lost\n");
350 return VERR_NET_CONNECTION_RESET_BY_PEER;
351 }
352
353 AssertReturn(pClient->pfnRxCb, VERR_INVALID_PARAMETER);
354
355 /* Make sure we have valid connection handle. By reporting VERR_BROKEN_PIPE,
356 * we trigger reconnect to IPC server. */
357 if (!RT_VALID_PTR(pClient->hClientSession))
358 return VERR_BROKEN_PIPE;
359
360 rc = RTLocalIpcSessionWaitForData(pClient->hClientSession, VBOX_DRMIPC_RX_TIMEOUT_MS);
361 if (RT_SUCCESS(rc))
362 {
363 /* Read IPC message header. */
364 rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf, sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
365 if (RT_SUCCESS(rc))
366 {
367 if (cbRead == sizeof(VBOX_DRMIPC_COMMAND_HEADER))
368 {
369 PVBOX_DRMIPC_COMMAND_HEADER pHdr = (PVBOX_DRMIPC_COMMAND_HEADER)aInputBuf;
370 if (pHdr)
371 {
372 AssertReturn(pHdr->cbData <= sizeof(aInputBuf) - sizeof(VBOX_DRMIPC_COMMAND_HEADER), VERR_INVALID_PARAMETER);
373
374 /* Read the rest of a message. */
375 rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf + sizeof(VBOX_DRMIPC_COMMAND_HEADER), pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
376 AssertRCReturn(rc, rc);
377 AssertReturn(cbRead == (pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER)), VERR_INVALID_PARAMETER);
378
379 uint64_t u64Crc = pHdr->u64Crc;
380
381 /* Verify checksum. */
382 pHdr->u64Crc = 0;
383 if (u64Crc != 0 && RTCrc64(pHdr, pHdr->cbData) == u64Crc)
384 {
385 /* Restore original CRC. */
386 pHdr->u64Crc = u64Crc;
387
388 /* Trigger RX callback. */
389 rc = pClient->pfnRxCb(pHdr->idCmd, (void *)pHdr, pHdr->cbData);
390 VBClLogVerbose(2, "command 0x%X executed, rc=%Rrc\n", pHdr->idCmd, rc);
391 }
392 else
393 {
394 VBClLogError("unable to read from IPC: CRC mismatch, provided crc=0x%X, cmd=0x%X\n", u64Crc, pHdr->idCmd);
395 rc = VERR_NOT_EQUAL;
396 }
397 }
398 else
399 {
400 VBClLogError("unable to read from IPC: zero data received\n");
401 rc = VERR_INVALID_PARAMETER;
402 }
403 }
404 else
405 {
406 VBClLogError("received partial IPC message header (%u bytes)\n", cbRead);
407 rc = VERR_INVALID_PARAMETER;
408 }
409
410 VBClLogVerbose(2, "received %u bytes from IPC\n", cbRead);
411 }
412 else
413 {
414 VBClLogError("unable to read from IPC, rc=%Rrc\n", rc);
415 }
416 }
417
418 /* Check if TX queue has some messages to transfer. */
419 while ((pTxListEntry = vbDrmIpcSessionPickupTxMessage(pClient)) != NULL)
420 {
421 PVBOX_DRMIPC_COMMAND_HEADER pMessageHdr = (PVBOX_DRMIPC_COMMAND_HEADER)(&pTxListEntry->Hdr);
422 Assert(pMessageHdr);
423
424 rc = RTLocalIpcSessionWrite(
425 pClient->hClientSession, (void *)(&pTxListEntry->Hdr), pMessageHdr->cbData);
426 if (RT_SUCCESS(rc))
427 {
428 rc = RTLocalIpcSessionFlush(pClient->hClientSession);
429 if (RT_SUCCESS(rc))
430 VBClLogVerbose(2, "vbDrmIpcConnectionHandler: transferred %u bytes\n", pMessageHdr->cbData);
431 else
432 VBClLogError("vbDrmIpcConnectionHandler: cannot flush IPC connection, transfer of %u bytes failed\n", pMessageHdr->cbData);
433 }
434 else
435 VBClLogError("vbDrmIpcConnectionHandler: cannot TX, rc=%Rrc\n", rc);
436
437 RTMemFree(pTxListEntry);
438 }
439
440 return rc;
441}
Note: See TracBrowser for help on using the repository browser.

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