VirtualBox

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

Last change on this file since 93560 was 93423, checked in by vboxsync, 3 years ago

Additions: Linux: update description for guest screen resizing code, bugref:10134.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.6 KB
Line 
1/* $Id: display-ipc.cpp 93423 2022-01-24 20:53:37Z 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
209/**
210 * Verify if remote IPC peer process is running by user from allowed group.
211 *
212 * @return IPRT status code.
213 * @param hClientSession IPC session handle.
214 */
215RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession)
216{
217 int rc = VERR_ACCESS_DENIED;
218 RTUID uUid;
219 struct group *pAllowedGroup;
220
221 AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
222
223 /* Get DRM IPC user group entry from system database. */
224 pAllowedGroup = getgrnam(VBOX_DRMIPC_USER_GROUP);
225 if (!pAllowedGroup)
226 return RTErrConvertFromErrno(errno);
227
228 /* Get remote user ID and check if it is in allowed user group. */
229 rc = RTLocalIpcSessionQueryUserId(hClientSession, &uUid);
230 if (RT_SUCCESS(rc))
231 {
232 /* Get user record from system database and look for it in group's members list. */
233 struct passwd *UserRecord = getpwuid(uUid);
234
235 if (UserRecord && UserRecord->pw_name)
236 {
237 while (*pAllowedGroup->gr_mem)
238 {
239 if (RTStrNCmp(*pAllowedGroup->gr_mem, UserRecord->pw_name, LOGIN_NAME_MAX) == 0)
240 return VINF_SUCCESS;
241
242 pAllowedGroup->gr_mem++;
243 }
244 }
245 }
246
247 return rc;
248}
249
250/**
251 * Request remote IPC peer to set primary display (called by IPC server).
252 *
253 * @return IPRT status code.
254 * @param pClient IPC session private data.
255 * @param idDisplay ID of display to be set as primary.
256 */
257RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay)
258{
259 int rc = VERR_GENERAL_FAILURE;
260
261 PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
262 (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY));
263
264 if (pTxListEntry)
265 {
266 PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)(&pTxListEntry->Hdr);
267
268 pCmd->Hdr.idCmd = VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY;
269 pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY);
270 pCmd->idDisplay = idDisplay;
271 pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
272 Assert(pCmd->Hdr.u64Crc);
273
274 /* Put command into queue and trigger TX. */
275 rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
276 if (RT_SUCCESS(rc))
277 {
278 VBClLogVerbose(2, "vbDrmIpcSetPrimaryDisplay: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
279 }
280 else
281 {
282 RTMemFree(pTxListEntry);
283 VBClLogError("vbDrmIpcSetPrimaryDisplay: unable to schedule TX, rc=%Rrc\n", rc);
284 }
285 }
286 else
287 {
288 VBClLogInfo("cannot allocate SET_PRIMARY_DISPLAY command\n");
289 rc = VERR_NO_MEMORY;
290 }
291
292 return rc;
293}
294
295/**
296 * Report to IPC server that display layout offsets have been changed (called by IPC client).
297 *
298 * @return IPRT status code.
299 * @param pClient IPC session private data.
300 * @param cOffsets Number of monitors which have offsets changed.
301 * @param paOffsets Offsets data.
302 */
303RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cOffsets, RTPOINT *paOffsets)
304{
305 int rc = VERR_GENERAL_FAILURE;
306
307 PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
308 (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(
309 DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS));
310
311 if (pTxListEntry)
312 {
313 PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)(&pTxListEntry->Hdr);
314
315 pCmd->Hdr.idCmd = VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS;
316 pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS);
317 pCmd->cOffsets = cOffsets;
318 memcpy(pCmd->paOffsets, paOffsets, cOffsets * sizeof(RTPOINT));
319 pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
320 Assert(pCmd->Hdr.u64Crc);
321
322 /* Put command into queue and trigger TX. */
323 rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
324 if (RT_SUCCESS(rc))
325 {
326 VBClLogVerbose(2, "vbDrmIpcReportDisplayOffsets: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
327 }
328 else
329 {
330 RTMemFree(pTxListEntry);
331 VBClLogError("vbDrmIpcReportDisplayOffsets: unable to schedule TX, rc=%Rrc\n", rc);
332 }
333 }
334 else
335 {
336 VBClLogInfo("cannot allocate REPORT_DISPLAY_OFFSETS command\n");
337 rc = VERR_NO_MEMORY;
338 }
339
340 return rc;
341}
342
343/**
344 * Common function for both IPC server and client which is responsible
345 * for handling IPC communication flow.
346 *
347 * @return IPRT status code.
348 * @param pClient IPC connection private data.
349 */
350RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient)
351{
352 int rc;
353 static uint8_t aInputBuf[VBOX_DRMIPC_RX_BUFFER_SIZE];
354 size_t cbRead = 0;
355 PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry;
356
357 AssertReturn(pClient, VERR_INVALID_PARAMETER);
358
359 /* Make sure we are still connected to IPC server. */
360 if (!pClient->hClientSession)
361 {
362 VBClLogVerbose(2, "connection to IPC server lost\n");
363 return VERR_NET_CONNECTION_RESET_BY_PEER;
364 }
365
366 AssertReturn(pClient->pfnRxCb, VERR_INVALID_PARAMETER);
367
368 /* Make sure we have valid connection handle. By reporting VERR_BROKEN_PIPE,
369 * we trigger reconnect to IPC server. */
370 if (!RT_VALID_PTR(pClient->hClientSession))
371 return VERR_BROKEN_PIPE;
372
373 rc = RTLocalIpcSessionWaitForData(pClient->hClientSession, VBOX_DRMIPC_RX_TIMEOUT_MS);
374 if (RT_SUCCESS(rc))
375 {
376 /* Read IPC message header. */
377 rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf, sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
378 if (RT_SUCCESS(rc))
379 {
380 if (cbRead == sizeof(VBOX_DRMIPC_COMMAND_HEADER))
381 {
382 PVBOX_DRMIPC_COMMAND_HEADER pHdr = (PVBOX_DRMIPC_COMMAND_HEADER)aInputBuf;
383 if (pHdr)
384 {
385 AssertReturn(pHdr->cbData <= sizeof(aInputBuf) - sizeof(VBOX_DRMIPC_COMMAND_HEADER), VERR_INVALID_PARAMETER);
386
387 /* Read the rest of a message. */
388 rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf + sizeof(VBOX_DRMIPC_COMMAND_HEADER), pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
389 AssertRCReturn(rc, rc);
390 AssertReturn(cbRead == (pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER)), VERR_INVALID_PARAMETER);
391
392 uint64_t u64Crc = pHdr->u64Crc;
393
394 /* Verify checksum. */
395 pHdr->u64Crc = 0;
396 if (u64Crc != 0 && RTCrc64(pHdr, pHdr->cbData) == u64Crc)
397 {
398 /* Restore original CRC. */
399 pHdr->u64Crc = u64Crc;
400
401 /* Trigger RX callback. */
402 rc = pClient->pfnRxCb(pHdr->idCmd, (void *)pHdr, pHdr->cbData);
403 VBClLogVerbose(2, "command 0x%X executed, rc=%Rrc\n", pHdr->idCmd, rc);
404 }
405 else
406 {
407 VBClLogError("unable to read from IPC: CRC mismatch, provided crc=0x%X, cmd=0x%X\n", u64Crc, pHdr->idCmd);
408 rc = VERR_NOT_EQUAL;
409 }
410 }
411 else
412 {
413 VBClLogError("unable to read from IPC: zero data received\n");
414 rc = VERR_INVALID_PARAMETER;
415 }
416 }
417 else
418 {
419 VBClLogError("received partial IPC message header (%u bytes)\n", cbRead);
420 rc = VERR_INVALID_PARAMETER;
421 }
422
423 VBClLogVerbose(2, "received %u bytes from IPC\n", cbRead);
424 }
425 else
426 {
427 VBClLogError("unable to read from IPC, rc=%Rrc\n", rc);
428 }
429 }
430
431 /* Check if TX queue has some messages to transfer. */
432 while ((pTxListEntry = vbDrmIpcSessionPickupTxMessage(pClient)) != NULL)
433 {
434 PVBOX_DRMIPC_COMMAND_HEADER pMessageHdr = (PVBOX_DRMIPC_COMMAND_HEADER)(&pTxListEntry->Hdr);
435 Assert(pMessageHdr);
436
437 rc = RTLocalIpcSessionWrite(
438 pClient->hClientSession, (void *)(&pTxListEntry->Hdr), pMessageHdr->cbData);
439 if (RT_SUCCESS(rc))
440 {
441 rc = RTLocalIpcSessionFlush(pClient->hClientSession);
442 if (RT_SUCCESS(rc))
443 VBClLogVerbose(2, "vbDrmIpcConnectionHandler: transferred %u bytes\n", pMessageHdr->cbData);
444 else
445 VBClLogError("vbDrmIpcConnectionHandler: cannot flush IPC connection, transfer of %u bytes failed\n", pMessageHdr->cbData);
446 }
447 else
448 VBClLogError("vbDrmIpcConnectionHandler: cannot TX, rc=%Rrc\n", rc);
449
450 RTMemFree(pTxListEntry);
451 }
452
453 return rc;
454}
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