VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use