VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/linux/USBGetDevices.cpp

Last change on this file was 99775, checked in by vboxsync, 12 months ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 59.9 KB
Line 
1/* $Id: USBGetDevices.cpp 99775 2023-05-12 12:21:58Z vboxsync $ */
2/** @file
3 * VirtualBox Linux host USB device enumeration.
4 */
5
6/*
7 * Copyright (C) 2006-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/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define VBOX_USB_WITH_USBFS
33#include "USBGetDevices.h"
34
35#include <VBox/err.h>
36#include <VBox/usb.h>
37#include <VBox/usblib.h>
38
39#include <iprt/linux/sysfs.h>
40#include <iprt/cdefs.h>
41#include <iprt/ctype.h>
42#include <iprt/dir.h>
43#include <iprt/env.h>
44#include <iprt/file.h>
45#include <iprt/fs.h>
46#include <iprt/log.h>
47#include <iprt/mem.h>
48#include <iprt/param.h>
49#include <iprt/path.h>
50#include <iprt/string.h>
51#include "vector.h"
52
53#ifdef VBOX_WITH_LINUX_COMPILER_H
54# include <linux/compiler.h>
55#endif
56#include <linux/usbdevice_fs.h>
57
58#include <sys/sysmacros.h>
59#include <sys/types.h>
60#include <sys/stat.h>
61#include <sys/vfs.h>
62
63#include <dirent.h>
64#include <dlfcn.h>
65#include <errno.h>
66#include <fcntl.h>
67#include <stdio.h>
68#include <string.h>
69#include <unistd.h>
70
71
72/*********************************************************************************************************************************
73* Structures and Typedefs *
74*********************************************************************************************************************************/
75/** Structure describing a host USB device */
76typedef struct USBDeviceInfo
77{
78 /** The device node of the device. */
79 char *mDevice;
80 /** The system identifier of the device. Specific to the probing
81 * method. */
82 char *mSysfsPath;
83 /** List of interfaces as sysfs paths */
84 VECTOR_PTR(char *) mvecpszInterfaces;
85} USBDeviceInfo;
86
87
88/**
89 * Does some extra checks to improve the detected device state.
90 *
91 * We cannot distinguish between USED_BY_HOST_CAPTURABLE and
92 * USED_BY_GUEST, HELD_BY_PROXY all that well and it shouldn't be
93 * necessary either.
94 *
95 * We will however, distinguish between the device we have permissions
96 * to open and those we don't. This is necessary for two reasons.
97 *
98 * Firstly, because it's futile to even attempt opening a device which we
99 * don't have access to, it only serves to confuse the user. (That said,
100 * it might also be a bit confusing for the user to see that a USB device
101 * is grayed out with no further explanation, and no way of generating an
102 * error hinting at why this is the case.)
103 *
104 * Secondly and more importantly, we're racing against udevd with respect
105 * to permissions and group settings on newly plugged devices. When we
106 * detect a new device that we cannot access we will poll on it for a few
107 * seconds to give udevd time to fix it. The polling is actually triggered
108 * in the 'new device' case in the compare loop.
109 *
110 * The USBDEVICESTATE_USED_BY_HOST state is only used for this no-access
111 * case, while USBDEVICESTATE_UNSUPPORTED is only used in the 'hub' case.
112 * When it's neither of these, we set USBDEVICESTATE_UNUSED or
113 * USBDEVICESTATE_USED_BY_HOST_CAPTURABLE depending on whether there is
114 * a driver associated with any of the interfaces.
115 *
116 * All except the access check and a special idVendor == 0 precaution
117 * is handled at parse time.
118 *
119 * @returns The adjusted state.
120 * @param pDevice The device.
121 */
122static USBDEVICESTATE usbDeterminState(PCUSBDEVICE pDevice)
123{
124 /*
125 * If it's already flagged as unsupported, there is nothing to do.
126 */
127 USBDEVICESTATE enmState = pDevice->enmState;
128 if (enmState == USBDEVICESTATE_UNSUPPORTED)
129 return USBDEVICESTATE_UNSUPPORTED;
130
131 /*
132 * Root hubs and similar doesn't have any vendor id, just
133 * refuse these device.
134 */
135 if (!pDevice->idVendor)
136 return USBDEVICESTATE_UNSUPPORTED;
137
138 /*
139 * Check if we've got access to the device, if we haven't flag
140 * it as used-by-host.
141 */
142#ifndef VBOX_USB_WITH_SYSFS
143 const char *pszAddress = pDevice->pszAddress;
144#else
145 if (pDevice->pszAddress == NULL)
146 /* We can't do much with the device without an address. */
147 return USBDEVICESTATE_UNSUPPORTED;
148 const char *pszAddress = strstr(pDevice->pszAddress, "//device:");
149 pszAddress = pszAddress != NULL
150 ? pszAddress + sizeof("//device:") - 1
151 : pDevice->pszAddress;
152#endif
153 if ( access(pszAddress, R_OK | W_OK) != 0
154 && errno == EACCES)
155 return USBDEVICESTATE_USED_BY_HOST;
156
157#ifdef VBOX_USB_WITH_SYSFS
158 /**
159 * @todo Check that any other essential fields are present and mark as
160 * invalid if not. Particularly to catch the case where the device was
161 * unplugged while we were reading in its properties.
162 */
163#endif
164
165 return enmState;
166}
167
168
169/**
170 * Dumps a USBDEVICE structure to the log using LogLevel 3.
171 * @param pDev The structure to log.
172 * @todo This is really common code.
173 */
174static void usbLogDevice(PUSBDEVICE pDev)
175{
176 NOREF(pDev);
177 if (LogIs3Enabled())
178 {
179 Log3(("USB device:\n"));
180 Log3(("Product: %s (%x)\n", pDev->pszProduct, pDev->idProduct));
181 Log3(("Manufacturer: %s (Vendor ID %x)\n", pDev->pszManufacturer, pDev->idVendor));
182 Log3(("Serial number: %s (%llx)\n", pDev->pszSerialNumber, pDev->u64SerialHash));
183 Log3(("Device revision: %d\n", pDev->bcdDevice));
184 Log3(("Device class: %x\n", pDev->bDeviceClass));
185 Log3(("Device subclass: %x\n", pDev->bDeviceSubClass));
186 Log3(("Device protocol: %x\n", pDev->bDeviceProtocol));
187 Log3(("USB version number: %d\n", pDev->bcdUSB));
188 Log3(("Device speed: %s\n",
189 pDev->enmSpeed == USBDEVICESPEED_UNKNOWN ? "unknown"
190 : pDev->enmSpeed == USBDEVICESPEED_LOW ? "1.5 MBit/s"
191 : pDev->enmSpeed == USBDEVICESPEED_FULL ? "12 MBit/s"
192 : pDev->enmSpeed == USBDEVICESPEED_HIGH ? "480 MBit/s"
193 : pDev->enmSpeed == USBDEVICESPEED_SUPER ? "5.0 GBit/s"
194 : pDev->enmSpeed == USBDEVICESPEED_VARIABLE ? "variable"
195 : "invalid"));
196 Log3(("Number of configurations: %d\n", pDev->bNumConfigurations));
197 Log3(("Bus number: %d\n", pDev->bBus));
198 Log3(("Port number: %d\n", pDev->bPort));
199 Log3(("Device number: %d\n", pDev->bDevNum));
200 Log3(("Device state: %s\n",
201 pDev->enmState == USBDEVICESTATE_UNSUPPORTED ? "unsupported"
202 : pDev->enmState == USBDEVICESTATE_USED_BY_HOST ? "in use by host"
203 : pDev->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE ? "in use by host, possibly capturable"
204 : pDev->enmState == USBDEVICESTATE_UNUSED ? "not in use"
205 : pDev->enmState == USBDEVICESTATE_HELD_BY_PROXY ? "held by proxy"
206 : pDev->enmState == USBDEVICESTATE_USED_BY_GUEST ? "used by guest"
207 : "invalid"));
208 Log3(("OS device address: %s\n", pDev->pszAddress));
209 }
210}
211
212
213#ifdef VBOX_USB_WITH_USBFS
214
215/**
216 * "reads" the number suffix.
217 *
218 * It's more like validating it and skipping the necessary number of chars.
219 */
220static int usbfsReadSkipSuffix(char **ppszNext)
221{
222 char *pszNext = *ppszNext;
223 if (!RT_C_IS_SPACE(*pszNext) && *pszNext)
224 {
225 /* skip unit */
226 if (pszNext[0] == 'm' && pszNext[1] == 's')
227 pszNext += 2;
228 else if (pszNext[0] == 'm' && pszNext[1] == 'A')
229 pszNext += 2;
230
231 /* skip parenthesis */
232 if (*pszNext == '(')
233 {
234 pszNext = strchr(pszNext, ')');
235 if (!pszNext++)
236 {
237 AssertMsgFailed(("*ppszNext=%s\n", *ppszNext));
238 return VERR_PARSE_ERROR;
239 }
240 }
241
242 /* blank or end of the line. */
243 if (!RT_C_IS_SPACE(*pszNext) && *pszNext)
244 {
245 AssertMsgFailed(("pszNext=%s\n", pszNext));
246 return VERR_PARSE_ERROR;
247 }
248
249 /* it's ok. */
250 *ppszNext = pszNext;
251 }
252
253 return VINF_SUCCESS;
254}
255
256
257/**
258 * Reads a USB number returning the number and the position of the next character to parse.
259 */
260static int usbfsReadNum(const char *pszValue, unsigned uBase, uint32_t u32Mask, void *pvNum, char **ppszNext)
261{
262 /*
263 * Initialize return value to zero and strip leading spaces.
264 */
265 switch (u32Mask)
266 {
267 case 0xff: *(uint8_t *)pvNum = 0; break;
268 case 0xffff: *(uint16_t *)pvNum = 0; break;
269 case 0xffffffff: *(uint32_t *)pvNum = 0; break;
270 }
271 pszValue = RTStrStripL(pszValue);
272 if (*pszValue)
273 {
274 /*
275 * Try convert the number.
276 */
277 char *pszNext;
278 uint32_t u32 = 0;
279 RTStrToUInt32Ex(pszValue, &pszNext, uBase, &u32);
280 if (pszNext == pszValue)
281 {
282 AssertMsgFailed(("pszValue=%d\n", pszValue));
283 return VERR_NO_DATA;
284 }
285
286 /*
287 * Check the range.
288 */
289 if (u32 & ~u32Mask)
290 {
291 AssertMsgFailed(("pszValue=%d u32=%#x lMask=%#x\n", pszValue, u32, u32Mask));
292 return VERR_OUT_OF_RANGE;
293 }
294
295 int vrc = usbfsReadSkipSuffix(&pszNext);
296 if (RT_FAILURE(vrc))
297 return vrc;
298
299 *ppszNext = pszNext;
300
301 /*
302 * Set the value.
303 */
304 switch (u32Mask)
305 {
306 case 0xff: *(uint8_t *)pvNum = (uint8_t)u32; break;
307 case 0xffff: *(uint16_t *)pvNum = (uint16_t)u32; break;
308 case 0xffffffff: *(uint32_t *)pvNum = (uint32_t)u32; break;
309 }
310 }
311 return VINF_SUCCESS;
312}
313
314
315static int usbfsRead8(const char *pszValue, unsigned uBase, uint8_t *pu8, char **ppszNext)
316{
317 return usbfsReadNum(pszValue, uBase, 0xff, pu8, ppszNext);
318}
319
320
321static int usbfsRead16(const char *pszValue, unsigned uBase, uint16_t *pu16, char **ppszNext)
322{
323 return usbfsReadNum(pszValue, uBase, 0xffff, pu16, ppszNext);
324}
325
326
327/**
328 * Reads a USB BCD number returning the number and the position of the next character to parse.
329 * The returned number contains the integer part in the high byte and the decimal part in the low byte.
330 */
331static int usbfsReadBCD(const char *pszValue, unsigned uBase, uint16_t *pu16, char **ppszNext)
332{
333 /*
334 * Initialize return value to zero and strip leading spaces.
335 */
336 *pu16 = 0;
337 pszValue = RTStrStripL(pszValue);
338 if (*pszValue)
339 {
340 /*
341 * Try convert the number.
342 */
343 /* integer part */
344 char *pszNext;
345 uint32_t u32Int = 0;
346 RTStrToUInt32Ex(pszValue, &pszNext, uBase, &u32Int);
347 if (pszNext == pszValue)
348 {
349 AssertMsgFailed(("pszValue=%s\n", pszValue));
350 return VERR_NO_DATA;
351 }
352 if (u32Int & ~0xff)
353 {
354 AssertMsgFailed(("pszValue=%s u32Int=%#x (int)\n", pszValue, u32Int));
355 return VERR_OUT_OF_RANGE;
356 }
357
358 /* skip dot and read decimal part */
359 if (*pszNext != '.')
360 {
361 AssertMsgFailed(("pszValue=%s pszNext=%s (int)\n", pszValue, pszNext));
362 return VERR_PARSE_ERROR;
363 }
364 char *pszValue2 = RTStrStripL(pszNext + 1);
365 uint32_t u32Dec = 0;
366 RTStrToUInt32Ex(pszValue2, &pszNext, uBase, &u32Dec);
367 if (pszNext == pszValue)
368 {
369 AssertMsgFailed(("pszValue=%s\n", pszValue));
370 return VERR_NO_DATA;
371 }
372 if (u32Dec & ~0xff)
373 {
374 AssertMsgFailed(("pszValue=%s u32Dec=%#x\n", pszValue, u32Dec));
375 return VERR_OUT_OF_RANGE;
376 }
377
378 /*
379 * Validate and skip stuff following the number.
380 */
381 int vrc = usbfsReadSkipSuffix(&pszNext);
382 if (RT_FAILURE(vrc))
383 return vrc;
384 *ppszNext = pszNext;
385
386 /*
387 * Set the value.
388 */
389 *pu16 = (uint16_t)((u32Int << 8) | (uint16_t)u32Dec);
390 }
391 return VINF_SUCCESS;
392}
393
394
395/**
396 * Reads a string, i.e. allocates memory and copies it.
397 *
398 * We assume that a string is Utf8 and if that's not the case
399 * (pre-2.6.32-kernels used Latin-1, but so few devices return non-ASCII that
400 * this usually goes unnoticed) then we mercilessly force it to be so.
401 */
402static int usbfsReadStr(const char *pszValue, const char **ppsz)
403{
404 char *psz;
405
406 if (*ppsz)
407 RTStrFree((char *)*ppsz);
408 psz = RTStrDup(pszValue);
409 if (psz)
410 {
411 USBLibPurgeEncoding(psz);
412 *ppsz = psz;
413 return VINF_SUCCESS;
414 }
415 return VERR_NO_MEMORY;
416}
417
418
419/**
420 * Skips the current property.
421 */
422static char *usbfsReadSkip(char *pszValue)
423{
424 char *psz = strchr(pszValue, '=');
425 if (psz)
426 psz = strchr(psz + 1, '=');
427 if (!psz)
428 return strchr(pszValue, '\0');
429 while (psz > pszValue && !RT_C_IS_SPACE(psz[-1]))
430 psz--;
431 Assert(psz > pszValue);
432 return psz;
433}
434
435
436/**
437 * Determine the USB speed.
438 */
439static int usbfsReadSpeed(const char *pszValue, USBDEVICESPEED *pSpd, char **ppszNext)
440{
441 pszValue = RTStrStripL(pszValue);
442 /* verified with Linux 2.4.0 ... Linux 2.6.25 */
443 if (!strncmp(pszValue, RT_STR_TUPLE("1.5")))
444 *pSpd = USBDEVICESPEED_LOW;
445 else if (!strncmp(pszValue, RT_STR_TUPLE("12 ")))
446 *pSpd = USBDEVICESPEED_FULL;
447 else if (!strncmp(pszValue, RT_STR_TUPLE("480")))
448 *pSpd = USBDEVICESPEED_HIGH;
449 else if (!strncmp(pszValue, RT_STR_TUPLE("5000")))
450 *pSpd = USBDEVICESPEED_SUPER;
451 else
452 *pSpd = USBDEVICESPEED_UNKNOWN;
453 while (pszValue[0] != '\0' && !RT_C_IS_SPACE(pszValue[0]))
454 pszValue++;
455 *ppszNext = (char *)pszValue;
456 return VINF_SUCCESS;
457}
458
459
460/**
461 * Compare a prefix and returns pointer to the char following it if it matches.
462 */
463static char *usbfsPrefix(char *psz, const char *pszPref, size_t cchPref)
464{
465 if (strncmp(psz, pszPref, cchPref))
466 return NULL;
467 return psz + cchPref;
468}
469
470
471/** Just a worker for USBProxyServiceLinux::getDevices that avoids some code duplication. */
472static int usbfsAddDeviceToChain(PUSBDEVICE pDev, PUSBDEVICE *ppFirst, PUSBDEVICE **pppNext, const char *pszUsbfsRoot,
473 bool fUnsupportedDevicesToo, int vrc)
474{
475 /* usbDeterminState requires the address. */
476 PUSBDEVICE pDevNew = (PUSBDEVICE)RTMemDup(pDev, sizeof(*pDev));
477 if (pDevNew)
478 {
479 RTStrAPrintf((char **)&pDevNew->pszAddress, "%s/%03d/%03d", pszUsbfsRoot, pDevNew->bBus, pDevNew->bDevNum);
480 if (pDevNew->pszAddress)
481 {
482 pDevNew->enmState = usbDeterminState(pDevNew);
483 if (pDevNew->enmState != USBDEVICESTATE_UNSUPPORTED || fUnsupportedDevicesToo)
484 {
485 if (*pppNext)
486 **pppNext = pDevNew;
487 else
488 *ppFirst = pDevNew;
489 *pppNext = &pDevNew->pNext;
490 }
491 else
492 deviceFree(pDevNew);
493 }
494 else
495 {
496 deviceFree(pDevNew);
497 vrc = VERR_NO_MEMORY;
498 }
499 }
500 else
501 {
502 vrc = VERR_NO_MEMORY;
503 deviceFreeMembers(pDev);
504 }
505
506 return vrc;
507}
508
509
510static int usbfsOpenDevicesFile(const char *pszUsbfsRoot, FILE **ppFile)
511{
512 char *pszPath;
513 FILE *pFile;
514 RTStrAPrintf(&pszPath, "%s/devices", pszUsbfsRoot);
515 if (!pszPath)
516 return VERR_NO_MEMORY;
517 pFile = fopen(pszPath, "r");
518 RTStrFree(pszPath);
519 if (!pFile)
520 return RTErrConvertFromErrno(errno);
521 *ppFile = pFile;
522 return VINF_SUCCESS;
523}
524
525
526/**
527 * USBProxyService::getDevices() implementation for usbfs.
528 *
529 * The @a fUnsupportedDevicesToo flag tells the function to return information
530 * about unsupported devices as well. This is used as a sanity test to check
531 * that a devices file is really what we expect.
532 */
533static PUSBDEVICE usbfsGetDevices(const char *pszUsbfsRoot, bool fUnsupportedDevicesToo)
534{
535 PUSBDEVICE pFirst = NULL;
536 FILE *pFile = NULL;
537 int vrc = usbfsOpenDevicesFile(pszUsbfsRoot, &pFile);
538 if (RT_SUCCESS(vrc))
539 {
540 PUSBDEVICE *ppNext = NULL;
541 int cHits = 0;
542 char szLine[1024];
543 USBDEVICE Dev;
544 RT_ZERO(Dev);
545 Dev.enmState = USBDEVICESTATE_UNUSED;
546
547 /* Set close on exit and hope no one is racing us. */
548 vrc = fcntl(fileno(pFile), F_SETFD, FD_CLOEXEC) >= 0
549 ? VINF_SUCCESS
550 : RTErrConvertFromErrno(errno);
551 while ( RT_SUCCESS(vrc)
552 && fgets(szLine, sizeof(szLine), pFile))
553 {
554 char *psz;
555 char *pszValue;
556
557 /* validate and remove the trailing newline. */
558 psz = strchr(szLine, '\0');
559 if (psz[-1] != '\n' && !feof(pFile))
560 {
561 AssertMsgFailed(("Line too long. (cch=%d)\n", strlen(szLine)));
562 continue;
563 }
564
565 /* strip */
566 psz = RTStrStrip(szLine);
567 if (!*psz)
568 continue;
569
570 /*
571 * Interpret the line.
572 * (Ordered by normal occurrence.)
573 */
574 char ch = psz[0];
575 if (psz[1] != ':')
576 continue;
577 psz = RTStrStripL(psz + 3);
578#define PREFIX(str) ( (pszValue = usbfsPrefix(psz, str, sizeof(str) - 1)) != NULL )
579 switch (ch)
580 {
581 /*
582 * T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=ddd MxCh=dd
583 * | | | | | | | | |__MaxChildren
584 * | | | | | | | |__Device Speed in Mbps
585 * | | | | | | |__DeviceNumber
586 * | | | | | |__Count of devices at this level
587 * | | | | |__Connector/Port on Parent for this device
588 * | | | |__Parent DeviceNumber
589 * | | |__Level in topology for this bus
590 * | |__Bus number
591 * |__Topology info tag
592 */
593 case 'T':
594 /* add */
595 AssertMsg(cHits >= 3 || cHits == 0, ("cHits=%d\n", cHits));
596 if (cHits >= 3)
597 vrc = usbfsAddDeviceToChain(&Dev, &pFirst, &ppNext, pszUsbfsRoot, fUnsupportedDevicesToo, vrc);
598 else
599 deviceFreeMembers(&Dev);
600
601 /* Reset device state */
602 RT_ZERO(Dev);
603 Dev.enmState = USBDEVICESTATE_UNUSED;
604 cHits = 1;
605
606 /* parse the line. */
607 while (*psz && RT_SUCCESS(vrc))
608 {
609 if (PREFIX("Bus="))
610 vrc = usbfsRead8(pszValue, 10, &Dev.bBus, &psz);
611 else if (PREFIX("Port="))
612 vrc = usbfsRead8(pszValue, 10, &Dev.bPort, &psz);
613 else if (PREFIX("Spd="))
614 vrc = usbfsReadSpeed(pszValue, &Dev.enmSpeed, &psz);
615 else if (PREFIX("Dev#="))
616 vrc = usbfsRead8(pszValue, 10, &Dev.bDevNum, &psz);
617 else
618 psz = usbfsReadSkip(psz);
619 psz = RTStrStripL(psz);
620 }
621 break;
622
623 /*
624 * Bandwidth info:
625 * B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd
626 * | | | |__Number of isochronous requests
627 * | | |__Number of interrupt requests
628 * | |__Total Bandwidth allocated to this bus
629 * |__Bandwidth info tag
630 */
631 case 'B':
632 break;
633
634 /*
635 * D: Ver=x.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd
636 * | | | | | | |__NumberConfigurations
637 * | | | | | |__MaxPacketSize of Default Endpoint
638 * | | | | |__DeviceProtocol
639 * | | | |__DeviceSubClass
640 * | | |__DeviceClass
641 * | |__Device USB version
642 * |__Device info tag #1
643 */
644 case 'D':
645 while (*psz && RT_SUCCESS(vrc))
646 {
647 if (PREFIX("Ver="))
648 vrc = usbfsReadBCD(pszValue, 16, &Dev.bcdUSB, &psz);
649 else if (PREFIX("Cls="))
650 {
651 vrc = usbfsRead8(pszValue, 16, &Dev.bDeviceClass, &psz);
652 if (RT_SUCCESS(vrc) && Dev.bDeviceClass == 9 /* HUB */)
653 Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
654 }
655 else if (PREFIX("Sub="))
656 vrc = usbfsRead8(pszValue, 16, &Dev.bDeviceSubClass, &psz);
657 else if (PREFIX("Prot="))
658 vrc = usbfsRead8(pszValue, 16, &Dev.bDeviceProtocol, &psz);
659 //else if (PREFIX("MxPS="))
660 // vrc = usbRead16(pszValue, 10, &Dev.wMaxPacketSize, &psz);
661 else if (PREFIX("#Cfgs="))
662 vrc = usbfsRead8(pszValue, 10, &Dev.bNumConfigurations, &psz);
663 else
664 psz = usbfsReadSkip(psz);
665 psz = RTStrStripL(psz);
666 }
667 cHits++;
668 break;
669
670 /*
671 * P: Vendor=xxxx ProdID=xxxx Rev=xx.xx
672 * | | | |__Product revision number
673 * | | |__Product ID code
674 * | |__Vendor ID code
675 * |__Device info tag #2
676 */
677 case 'P':
678 while (*psz && RT_SUCCESS(vrc))
679 {
680 if (PREFIX("Vendor="))
681 vrc = usbfsRead16(pszValue, 16, &Dev.idVendor, &psz);
682 else if (PREFIX("ProdID="))
683 vrc = usbfsRead16(pszValue, 16, &Dev.idProduct, &psz);
684 else if (PREFIX("Rev="))
685 vrc = usbfsReadBCD(pszValue, 16, &Dev.bcdDevice, &psz);
686 else
687 psz = usbfsReadSkip(psz);
688 psz = RTStrStripL(psz);
689 }
690 cHits++;
691 break;
692
693 /*
694 * String.
695 */
696 case 'S':
697 if (PREFIX("Manufacturer="))
698 vrc = usbfsReadStr(pszValue, &Dev.pszManufacturer);
699 else if (PREFIX("Product="))
700 vrc = usbfsReadStr(pszValue, &Dev.pszProduct);
701 else if (PREFIX("SerialNumber="))
702 {
703 vrc = usbfsReadStr(pszValue, &Dev.pszSerialNumber);
704 if (RT_SUCCESS(vrc))
705 Dev.u64SerialHash = USBLibHashSerial(pszValue);
706 }
707 break;
708
709 /*
710 * C:* #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA
711 * | | | | | |__MaxPower in mA
712 * | | | | |__Attributes
713 * | | | |__ConfiguratioNumber
714 * | | |__NumberOfInterfaces
715 * | |__ "*" indicates the active configuration (others are " ")
716 * |__Config info tag
717 */
718 case 'C':
719 break;
720
721 /*
722 * I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=ssss
723 * | | | | | | | |__Driver name
724 * | | | | | | | or "(none)"
725 * | | | | | | |__InterfaceProtocol
726 * | | | | | |__InterfaceSubClass
727 * | | | | |__InterfaceClass
728 * | | | |__NumberOfEndpoints
729 * | | |__AlternateSettingNumber
730 * | |__InterfaceNumber
731 * |__Interface info tag
732 */
733 case 'I':
734 {
735 /* Check for thing we don't support. */
736 while (*psz && RT_SUCCESS(vrc))
737 {
738 if (PREFIX("Driver="))
739 {
740 const char *pszDriver = NULL;
741 vrc = usbfsReadStr(pszValue, &pszDriver);
742 if ( !pszDriver
743 || !*pszDriver
744 || !strcmp(pszDriver, "(none)")
745 || !strcmp(pszDriver, "(no driver)"))
746 /* no driver */;
747 else if (!strcmp(pszDriver, "hub"))
748 Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
749 else if (Dev.enmState == USBDEVICESTATE_UNUSED)
750 Dev.enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
751 RTStrFree((char *)pszDriver);
752 break; /* last attrib */
753 }
754 else if (PREFIX("Cls="))
755 {
756 uint8_t bInterfaceClass;
757 vrc = usbfsRead8(pszValue, 16, &bInterfaceClass, &psz);
758 if (RT_SUCCESS(vrc) && bInterfaceClass == 9 /* HUB */)
759 Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
760 }
761 else
762 psz = usbfsReadSkip(psz);
763 psz = RTStrStripL(psz);
764 }
765 break;
766 }
767
768
769 /*
770 * E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=dddms
771 * | | | | |__Interval (max) between transfers
772 * | | | |__EndpointMaxPacketSize
773 * | | |__Attributes(EndpointType)
774 * | |__EndpointAddress(I=In,O=Out)
775 * |__Endpoint info tag
776 */
777 case 'E':
778 break;
779
780 }
781#undef PREFIX
782 } /* parse loop */
783 fclose(pFile);
784
785 /*
786 * Add the current entry.
787 */
788 AssertMsg(cHits >= 3 || cHits == 0, ("cHits=%d\n", cHits));
789 if (cHits >= 3)
790 vrc = usbfsAddDeviceToChain(&Dev, &pFirst, &ppNext, pszUsbfsRoot, fUnsupportedDevicesToo, vrc);
791
792 /*
793 * Success?
794 */
795 if (RT_FAILURE(vrc))
796 {
797 while (pFirst)
798 {
799 PUSBDEVICE pFree = pFirst;
800 pFirst = pFirst->pNext;
801 deviceFree(pFree);
802 }
803 }
804 }
805 if (RT_FAILURE(vrc))
806 LogFlow(("USBProxyServiceLinux::getDevices: vrc=%Rrc\n", vrc));
807 return pFirst;
808}
809
810#endif /* VBOX_USB_WITH_USBFS */
811#ifdef VBOX_USB_WITH_SYSFS
812
813static void usbsysfsCleanupDevInfo(USBDeviceInfo *pSelf)
814{
815 RTStrFree(pSelf->mDevice);
816 RTStrFree(pSelf->mSysfsPath);
817 pSelf->mDevice = pSelf->mSysfsPath = NULL;
818 VEC_CLEANUP_PTR(&pSelf->mvecpszInterfaces);
819}
820
821
822static int usbsysfsInitDevInfo(USBDeviceInfo *pSelf, const char *aDevice, const char *aSystemID)
823{
824 pSelf->mDevice = aDevice ? RTStrDup(aDevice) : NULL;
825 pSelf->mSysfsPath = aSystemID ? RTStrDup(aSystemID) : NULL;
826 VEC_INIT_PTR(&pSelf->mvecpszInterfaces, char *, RTStrFree);
827 if ((aDevice && !pSelf->mDevice) || (aSystemID && ! pSelf->mSysfsPath))
828 {
829 usbsysfsCleanupDevInfo(pSelf);
830 return 0;
831 }
832 return 1;
833}
834
835# define USBDEVICE_MAJOR 189
836
837/**
838 * Calculate the bus (a.k.a root hub) number of a USB device from it's sysfs
839 * path.
840 *
841 * sysfs nodes representing root hubs have file names of the form
842 * usb<n>, where n is the bus number; other devices start with that number.
843 * See [http://www.linux-usb.org/FAQ.html#i6] and
844 * [http://www.kernel.org/doc/Documentation/usb/proc_usb_info.txt] for
845 * equivalent information about usbfs.
846 *
847 * @returns a bus number greater than 0 on success or 0 on failure.
848 */
849static unsigned usbsysfsGetBusFromPath(const char *pszPath)
850{
851 const char *pszFile = strrchr(pszPath, '/');
852 if (!pszFile)
853 return 0;
854 unsigned bus = RTStrToUInt32(pszFile + 1);
855 if ( !bus
856 && pszFile[1] == 'u' && pszFile[2] == 's' && pszFile[3] == 'b')
857 bus = RTStrToUInt32(pszFile + 4);
858 return bus;
859}
860
861
862/**
863 * Calculate the device number of a USB device.
864 *
865 * See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20.
866 */
867static dev_t usbsysfsMakeDevNum(unsigned bus, unsigned device)
868{
869 AssertReturn(bus > 0, 0);
870 AssertReturn(((device - 1) & ~127) == 0, 0);
871 AssertReturn(device > 0, 0);
872 return makedev(USBDEVICE_MAJOR, ((bus - 1) << 7) + device - 1);
873}
874
875
876/**
877 * If a file @a pszNode from /sys/bus/usb/devices is a device rather than an
878 * interface add an element for the device to @a pvecDevInfo.
879 */
880static int usbsysfsAddIfDevice(const char *pszDevicesRoot, const char *pszNode, VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo)
881{
882 const char *pszFile = strrchr(pszNode, '/');
883 if (!pszFile)
884 return VERR_INVALID_PARAMETER;
885 if (strchr(pszFile, ':'))
886 return VINF_SUCCESS;
887
888 unsigned bus = usbsysfsGetBusFromPath(pszNode);
889 if (!bus)
890 return VINF_SUCCESS;
891
892 int64_t device;
893 int vrc = RTLinuxSysFsReadIntFile(10, &device, "%s/devnum", pszNode);
894 if (RT_FAILURE(vrc))
895 return VINF_SUCCESS;
896
897 dev_t devnum = usbsysfsMakeDevNum(bus, (int)device);
898 if (!devnum)
899 return VINF_SUCCESS;
900
901 char szDevPath[RTPATH_MAX];
902 vrc = RTLinuxCheckDevicePath(devnum, RTFS_TYPE_DEV_CHAR, szDevPath, sizeof(szDevPath),
903 "%s/%.3d/%.3d", pszDevicesRoot, bus, device);
904 if (RT_FAILURE(vrc))
905 return VINF_SUCCESS;
906
907 USBDeviceInfo info;
908 if (usbsysfsInitDevInfo(&info, szDevPath, pszNode))
909 {
910 vrc = VEC_PUSH_BACK_OBJ(pvecDevInfo, USBDeviceInfo, &info);
911 if (RT_SUCCESS(vrc))
912 return VINF_SUCCESS;
913 }
914 usbsysfsCleanupDevInfo(&info);
915 return VERR_NO_MEMORY;
916}
917
918
919/**
920 * The logic for testing whether a sysfs address corresponds to an interface of
921 * a device.
922 *
923 * Both must be referenced by their canonical sysfs paths. This is not tested,
924 * as the test requires file-system interaction.
925 */
926static bool usbsysfsMuiIsAnInterfaceOf(const char *pszIface, const char *pszDev)
927{
928 size_t cchDev = strlen(pszDev);
929
930 AssertPtr(pszIface);
931 AssertPtr(pszDev);
932 Assert(pszIface[0] == '/');
933 Assert(pszDev[0] == '/');
934 Assert(pszDev[cchDev - 1] != '/');
935
936 /* If this passes, pszIface is at least cchDev long */
937 if (strncmp(pszIface, pszDev, cchDev))
938 return false;
939
940 /* If this passes, pszIface is longer than cchDev */
941 if (pszIface[cchDev] != '/')
942 return false;
943
944 /* In sysfs an interface is an immediate subdirectory of the device */
945 if (strchr(pszIface + cchDev + 1, '/'))
946 return false;
947
948 /* And it always has a colon in its name */
949 if (!strchr(pszIface + cchDev + 1, ':'))
950 return false;
951
952 /* And hopefully we have now elimitated everything else */
953 return true;
954}
955
956
957# ifdef DEBUG
958# ifdef __cplusplus
959/** Unit test the logic in muiIsAnInterfaceOf in debug builds. */
960class testIsAnInterfaceOf
961{
962public:
963 testIsAnInterfaceOf()
964 {
965 Assert(usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0",
966 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
967 Assert(!usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1",
968 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
969 Assert(!usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/driver",
970 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
971 }
972};
973static testIsAnInterfaceOf testIsAnInterfaceOfInst;
974# endif /* __cplusplus */
975# endif /* DEBUG */
976
977
978/**
979 * Tell whether a file in /sys/bus/usb/devices is an interface rather than a
980 * device.
981 */
982static int usbsysfsAddIfInterfaceOf(const char *pszNode, USBDeviceInfo *pInfo)
983{
984 if (!usbsysfsMuiIsAnInterfaceOf(pszNode, pInfo->mSysfsPath))
985 return VINF_SUCCESS;
986
987 char *pszDup = (char *)RTStrDup(pszNode);
988 if (pszDup)
989 {
990 int vrc = VEC_PUSH_BACK_PTR(&pInfo->mvecpszInterfaces, char *, pszDup);
991 if (RT_SUCCESS(vrc))
992 return VINF_SUCCESS;
993 RTStrFree(pszDup);
994 }
995 return VERR_NO_MEMORY;
996}
997
998
999/**
1000 * Helper for usbsysfsReadFilePaths().
1001 *
1002 * Adds the entries from the open directory @a pDir to the vector @a pvecpchDevs
1003 * using either the full path or the realpath() and skipping hidden files and
1004 * files on which realpath() fails.
1005 */
1006static int usbsysfsReadFilePathsFromDir(const char *pszPath, DIR *pDir, VECTOR_PTR(char *) *pvecpchDevs)
1007{
1008 struct dirent entry, *pResult;
1009 int err;
1010
1011#if RT_GNUC_PREREQ(4, 6)
1012# pragma GCC diagnostic push
1013# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1014#endif
1015 for (err = readdir_r(pDir, &entry, &pResult); pResult;
1016 err = readdir_r(pDir, &entry, &pResult))
1017#if RT_GNUC_PREREQ(4, 6)
1018# pragma GCC diagnostic pop
1019#endif
1020 {
1021 char szPath[RTPATH_MAX + 1];
1022 char szRealPath[RTPATH_MAX + 1];
1023 if (entry.d_name[0] == '.')
1024 continue;
1025 if (snprintf(szPath, sizeof(szPath), "%s/%s", pszPath, entry.d_name) < 0)
1026 return RTErrConvertFromErrno(errno); /** @todo r=bird: snprintf isn't document to set errno. Also, wouldn't it be better to continue on errors? Finally, you don't need to copy pszPath each time... */
1027 if (!realpath(szPath, szRealPath))
1028 return RTErrConvertFromErrno(errno);
1029 char *pszPathCopy = RTStrDup(szRealPath);
1030 if (!pszPathCopy)
1031 return VERR_NO_MEMORY;
1032 int vrc = VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPathCopy);
1033 if (RT_FAILURE(vrc))
1034 return vrc;
1035 }
1036 return RTErrConvertFromErrno(err);
1037}
1038
1039
1040/**
1041 * Dump the names of a directory's entries into a vector of char pointers.
1042 *
1043 * @returns zero on success or (positive) posix error value.
1044 * @param pszPath the path to dump.
1045 * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
1046 * by the caller even on failure.
1047 * @param withRealPath whether to canonicalise the filename with realpath
1048 */
1049static int usbsysfsReadFilePaths(const char *pszPath, VECTOR_PTR(char *) *pvecpchDevs)
1050{
1051 AssertPtrReturn(pvecpchDevs, EINVAL);
1052 AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
1053 AssertPtrReturn(pszPath, EINVAL);
1054
1055 DIR *pDir = opendir(pszPath);
1056 if (!pDir)
1057 return RTErrConvertFromErrno(errno);
1058 int vrc = usbsysfsReadFilePathsFromDir(pszPath, pDir, pvecpchDevs);
1059 if (closedir(pDir) < 0 && RT_SUCCESS(vrc))
1060 vrc = RTErrConvertFromErrno(errno);
1061 return vrc;
1062}
1063
1064
1065/**
1066 * Logic for USBSysfsEnumerateHostDevices.
1067 *
1068 * @param pvecDevInfo vector of device information structures to add device
1069 * information to
1070 * @param pvecpchDevs empty scratch vector which will be freed by the caller,
1071 * to simplify exit logic
1072 */
1073static int usbsysfsEnumerateHostDevicesWorker(const char *pszDevicesRoot,
1074 VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo,
1075 VECTOR_PTR(char *) *pvecpchDevs)
1076{
1077
1078 AssertPtrReturn(pvecDevInfo, VERR_INVALID_POINTER);
1079 LogFlowFunc (("pvecDevInfo=%p\n", pvecDevInfo));
1080
1081 int vrc = usbsysfsReadFilePaths("/sys/bus/usb/devices", pvecpchDevs);
1082 if (RT_FAILURE(vrc))
1083 return vrc;
1084
1085 char **ppszEntry;
1086 VEC_FOR_EACH(pvecpchDevs, char *, ppszEntry)
1087 {
1088 vrc = usbsysfsAddIfDevice(pszDevicesRoot, *ppszEntry, pvecDevInfo);
1089 if (RT_FAILURE(vrc))
1090 return vrc;
1091 }
1092
1093 USBDeviceInfo *pInfo;
1094 VEC_FOR_EACH(pvecDevInfo, USBDeviceInfo, pInfo)
1095 VEC_FOR_EACH(pvecpchDevs, char *, ppszEntry)
1096 {
1097 vrc = usbsysfsAddIfInterfaceOf(*ppszEntry, pInfo);
1098 if (RT_FAILURE(vrc))
1099 return vrc;
1100 }
1101 return VINF_SUCCESS;
1102}
1103
1104
1105static int usbsysfsEnumerateHostDevices(const char *pszDevicesRoot, VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo)
1106{
1107 VECTOR_PTR(char *) vecpchDevs;
1108
1109 AssertReturn(VEC_SIZE_OBJ(pvecDevInfo) == 0, VERR_INVALID_PARAMETER);
1110 LogFlowFunc(("entered\n"));
1111 VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
1112 int vrc = usbsysfsEnumerateHostDevicesWorker(pszDevicesRoot, pvecDevInfo, &vecpchDevs);
1113 VEC_CLEANUP_PTR(&vecpchDevs);
1114 LogFlowFunc(("vrc=%Rrc\n", vrc));
1115 return vrc;
1116}
1117
1118
1119/**
1120 * Helper function for extracting the port number on the parent device from
1121 * the sysfs path value.
1122 *
1123 * The sysfs path is a chain of elements separated by forward slashes, and for
1124 * USB devices, the last element in the chain takes the form
1125 * <port>-<port>.[...].<port>[:<config>.<interface>]
1126 * where the first <port> is the port number on the root hub, and the following
1127 * (optional) ones are the port numbers on any other hubs between the device
1128 * and the root hub. The last part (:<config.interface>) is only present for
1129 * interfaces, not for devices. This API should only be called for devices.
1130 * For compatibility with usbfs, which enumerates from zero up, we subtract one
1131 * from the port number.
1132 *
1133 * For root hubs, the last element in the chain takes the form
1134 * usb<hub number>
1135 * and usbfs always returns port number zero.
1136 *
1137 * @returns VBox status code. pu8Port is set on success.
1138 * @param pszPath The sysfs path to parse.
1139 * @param pu8Port Where to store the port number.
1140 */
1141static int usbsysfsGetPortFromStr(const char *pszPath, uint8_t *pu8Port)
1142{
1143 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
1144 AssertPtrReturn(pu8Port, VERR_INVALID_POINTER);
1145
1146 /*
1147 * This should not be possible until we get PCs with USB as their primary bus.
1148 * Note: We don't assert this, as we don't expect the caller to validate the
1149 * sysfs path.
1150 */
1151 const char *pszLastComp = strrchr(pszPath, '/');
1152 if (!pszLastComp)
1153 {
1154 Log(("usbGetPortFromSysfsPath(%s): failed [1]\n", pszPath));
1155 return VERR_INVALID_PARAMETER;
1156 }
1157 pszLastComp++; /* skip the slash */
1158
1159 /*
1160 * This API should not be called for interfaces, so the last component
1161 * of the path should not contain a colon. We *do* assert this, as it
1162 * might indicate a caller bug.
1163 */
1164 AssertMsgReturn(strchr(pszLastComp, ':') == NULL, ("%s\n", pszPath), VERR_INVALID_PARAMETER);
1165
1166 /*
1167 * Look for the start of the last number.
1168 */
1169 const char *pchDash = strrchr(pszLastComp, '-');
1170 const char *pchDot = strrchr(pszLastComp, '.');
1171 if (!pchDash && !pchDot)
1172 {
1173 /* No -/. so it must be a root hub. Check that it's usb<something>. */
1174 if (strncmp(pszLastComp, RT_STR_TUPLE("usb")) != 0)
1175 {
1176 Log(("usbGetPortFromSysfsPath(%s): failed [2]\n", pszPath));
1177 return VERR_INVALID_PARAMETER;
1178 }
1179 return VERR_NOT_SUPPORTED;
1180 }
1181
1182 const char *pszLastPort = pchDot != NULL
1183 ? pchDot + 1
1184 : pchDash + 1;
1185 int vrc = RTStrToUInt8Full(pszLastPort, 10, pu8Port);
1186 if (vrc != VINF_SUCCESS)
1187 {
1188 Log(("usbGetPortFromSysfsPath(%s): failed [3], vrc=%Rrc\n", pszPath, vrc));
1189 return VERR_INVALID_PARAMETER;
1190 }
1191 if (*pu8Port == 0)
1192 {
1193 Log(("usbGetPortFromSysfsPath(%s): failed [4]\n", pszPath));
1194 return VERR_INVALID_PARAMETER;
1195 }
1196
1197 /* usbfs compatibility, 0-based port number. */
1198 *pu8Port = (uint8_t)(*pu8Port - 1);
1199 return VINF_SUCCESS;
1200}
1201
1202
1203/**
1204 * Converts a sysfs BCD value into a uint16_t.
1205 *
1206 * In contrast to usbReadBCD() this function can handle BCD values without
1207 * a decimal separator. This is necessary for parsing bcdDevice.
1208 *
1209 * @param pszBuf Pointer to the string buffer.
1210 * @param pu15 Pointer to the return value.
1211 * @returns IPRT status code.
1212 */
1213static int usbsysfsConvertStrToBCD(const char *pszBuf, uint16_t *pu16)
1214{
1215 char *pszNext;
1216 int32_t i32;
1217
1218 pszBuf = RTStrStripL(pszBuf);
1219 int vrc = RTStrToInt32Ex(pszBuf, &pszNext, 16, &i32);
1220 if ( RT_FAILURE(vrc)
1221 || vrc == VWRN_NUMBER_TOO_BIG
1222 || i32 < 0)
1223 return VERR_NUMBER_TOO_BIG;
1224 if (*pszNext == '.')
1225 {
1226 if (i32 > 255)
1227 return VERR_NUMBER_TOO_BIG;
1228 int32_t i32Lo;
1229 vrc = RTStrToInt32Ex(pszNext+1, &pszNext, 16, &i32Lo);
1230 if ( RT_FAILURE(vrc)
1231 || vrc == VWRN_NUMBER_TOO_BIG
1232 || i32Lo > 255
1233 || i32Lo < 0)
1234 return VERR_NUMBER_TOO_BIG;
1235 i32 = (i32 << 8) | i32Lo;
1236 }
1237 if ( i32 > 65535
1238 || (*pszNext != '\0' && *pszNext != ' '))
1239 return VERR_NUMBER_TOO_BIG;
1240
1241 *pu16 = (uint16_t)i32;
1242 return VINF_SUCCESS;
1243}
1244
1245
1246/**
1247 * Returns the byte value for the given device property or sets the given default if an
1248 * error occurs while obtaining it.
1249 *
1250 * @returns uint8_t value of the given property.
1251 * @param uBase The base of the number in the sysfs property.
1252 * @param fDef The default to set on error.
1253 * @param pszFormat The format string for the property.
1254 * @param ... Arguments for the format string.
1255 */
1256static uint8_t usbsysfsReadDevicePropertyU8Def(unsigned uBase, uint8_t fDef, const char *pszFormat, ...)
1257{
1258 int64_t i64Tmp = 0;
1259
1260 va_list va;
1261 va_start(va, pszFormat);
1262 int vrc = RTLinuxSysFsReadIntFileV(uBase, &i64Tmp, pszFormat, va);
1263 va_end(va);
1264 if (RT_SUCCESS(vrc))
1265 return (uint8_t)i64Tmp;
1266 return fDef;
1267}
1268
1269
1270/**
1271 * Returns the uint16_t value for the given device property or sets the given default if an
1272 * error occurs while obtaining it.
1273 *
1274 * @returns uint16_t value of the given property.
1275 * @param uBase The base of the number in the sysfs property.
1276 * @param u16Def The default to set on error.
1277 * @param pszFormat The format string for the property.
1278 * @param ... Arguments for the format string.
1279 */
1280static uint16_t usbsysfsReadDevicePropertyU16Def(unsigned uBase, uint16_t u16Def, const char *pszFormat, ...)
1281{
1282 int64_t i64Tmp = 0;
1283
1284 va_list va;
1285 va_start(va, pszFormat);
1286 int vrc = RTLinuxSysFsReadIntFileV(uBase, &i64Tmp, pszFormat, va);
1287 va_end(va);
1288 if (RT_SUCCESS(vrc))
1289 return (uint16_t)i64Tmp;
1290 return u16Def;
1291}
1292
1293
1294static void usbsysfsFillInDevice(USBDEVICE *pDev, USBDeviceInfo *pInfo)
1295{
1296 int vrc;
1297 const char *pszSysfsPath = pInfo->mSysfsPath;
1298
1299 /* Fill in the simple fields */
1300 pDev->enmState = USBDEVICESTATE_UNUSED;
1301 pDev->bBus = (uint8_t)usbsysfsGetBusFromPath(pszSysfsPath);
1302 pDev->bDeviceClass = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceClass", pszSysfsPath);
1303 pDev->bDeviceSubClass = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceSubClass", pszSysfsPath);
1304 pDev->bDeviceProtocol = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceProtocol", pszSysfsPath);
1305 pDev->bNumConfigurations = usbsysfsReadDevicePropertyU8Def(10, 0, "%s/bNumConfigurations", pszSysfsPath);
1306 pDev->idVendor = usbsysfsReadDevicePropertyU16Def(16, 0, "%s/idVendor", pszSysfsPath);
1307 pDev->idProduct = usbsysfsReadDevicePropertyU16Def(16, 0, "%s/idProduct", pszSysfsPath);
1308 pDev->bDevNum = usbsysfsReadDevicePropertyU8Def(10, 0, "%s/devnum", pszSysfsPath);
1309
1310 /* Now deal with the non-numeric bits. */
1311 char szBuf[1024]; /* Should be larger than anything a sane device
1312 * will need, and insane devices can be unsupported
1313 * until further notice. */
1314 size_t cchRead;
1315
1316 /* For simplicity, we just do strcmps on the next one. */
1317 vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/speed", pszSysfsPath);
1318 if (RT_FAILURE(vrc) || cchRead == sizeof(szBuf))
1319 pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
1320 else
1321 pDev->enmSpeed = !strcmp(szBuf, "1.5") ? USBDEVICESPEED_LOW
1322 : !strcmp(szBuf, "12") ? USBDEVICESPEED_FULL
1323 : !strcmp(szBuf, "480") ? USBDEVICESPEED_HIGH
1324 : !strcmp(szBuf, "5000") ? USBDEVICESPEED_SUPER
1325 : USBDEVICESPEED_UNKNOWN;
1326
1327 vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/version", pszSysfsPath);
1328 if (RT_FAILURE(vrc) || cchRead == sizeof(szBuf))
1329 pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
1330 else
1331 {
1332 vrc = usbsysfsConvertStrToBCD(szBuf, &pDev->bcdUSB);
1333 if (RT_FAILURE(vrc))
1334 {
1335 pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
1336 pDev->bcdUSB = UINT16_MAX;
1337 }
1338 }
1339
1340 vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/bcdDevice", pszSysfsPath);
1341 if (RT_FAILURE(vrc) || cchRead == sizeof(szBuf))
1342 pDev->bcdDevice = UINT16_MAX;
1343 else
1344 {
1345 vrc = usbsysfsConvertStrToBCD(szBuf, &pDev->bcdDevice);
1346 if (RT_FAILURE(vrc))
1347 pDev->bcdDevice = UINT16_MAX;
1348 }
1349
1350 /* Now do things that need string duplication */
1351 vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/product", pszSysfsPath);
1352 if (RT_SUCCESS(vrc) && cchRead < sizeof(szBuf))
1353 {
1354 USBLibPurgeEncoding(szBuf);
1355 pDev->pszProduct = RTStrDup(szBuf);
1356 }
1357
1358 vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/serial", pszSysfsPath);
1359 if (RT_SUCCESS(vrc) && cchRead < sizeof(szBuf))
1360 {
1361 USBLibPurgeEncoding(szBuf);
1362 pDev->pszSerialNumber = RTStrDup(szBuf);
1363 pDev->u64SerialHash = USBLibHashSerial(szBuf);
1364 }
1365
1366 vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/manufacturer", pszSysfsPath);
1367 if (RT_SUCCESS(vrc) && cchRead < sizeof(szBuf))
1368 {
1369 USBLibPurgeEncoding(szBuf);
1370 pDev->pszManufacturer = RTStrDup(szBuf);
1371 }
1372
1373 /* Work out the port number */
1374 if (RT_FAILURE(usbsysfsGetPortFromStr(pszSysfsPath, &pDev->bPort)))
1375 pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
1376
1377 /* Check the interfaces to see if we can support the device. */
1378 char **ppszIf;
1379 VEC_FOR_EACH(&pInfo->mvecpszInterfaces, char *, ppszIf)
1380 {
1381 vrc = RTLinuxSysFsGetLinkDest(szBuf, sizeof(szBuf), NULL, "%s/driver", *ppszIf);
1382 if (RT_SUCCESS(vrc) && pDev->enmState != USBDEVICESTATE_UNSUPPORTED)
1383 pDev->enmState = (strcmp(szBuf, "hub") == 0)
1384 ? USBDEVICESTATE_UNSUPPORTED
1385 : USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
1386 if (usbsysfsReadDevicePropertyU8Def(16, 9 /* bDev */, "%s/bInterfaceClass", *ppszIf) == 9 /* hub */)
1387 pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
1388 }
1389
1390 /* We use a double slash as a separator in the pszAddress field. This is
1391 * alright as the two paths can't contain a slash due to the way we build
1392 * them. */
1393 char *pszAddress = NULL;
1394 RTStrAPrintf(&pszAddress, "sysfs:%s//device:%s", pszSysfsPath, pInfo->mDevice);
1395 pDev->pszAddress = pszAddress;
1396 pDev->pszBackend = RTStrDup("host");
1397
1398 /* Work out from the data collected whether we can support this device. */
1399 pDev->enmState = usbDeterminState(pDev);
1400 usbLogDevice(pDev);
1401}
1402
1403
1404/**
1405 * USBProxyService::getDevices() implementation for sysfs.
1406 */
1407static PUSBDEVICE usbsysfsGetDevices(const char *pszDevicesRoot, bool fUnsupportedDevicesToo)
1408{
1409 /* Add each of the devices found to the chain. */
1410 PUSBDEVICE pFirst = NULL;
1411 PUSBDEVICE pLast = NULL;
1412 VECTOR_OBJ(USBDeviceInfo) vecDevInfo;
1413 USBDeviceInfo *pInfo;
1414
1415 VEC_INIT_OBJ(&vecDevInfo, USBDeviceInfo, usbsysfsCleanupDevInfo);
1416 int vrc = usbsysfsEnumerateHostDevices(pszDevicesRoot, &vecDevInfo);
1417 if (RT_FAILURE(vrc))
1418 return NULL;
1419 VEC_FOR_EACH(&vecDevInfo, USBDeviceInfo, pInfo)
1420 {
1421 USBDEVICE *pDev = (USBDEVICE *)RTMemAllocZ(sizeof(USBDEVICE));
1422 if (!pDev)
1423 vrc = VERR_NO_MEMORY;
1424 if (RT_SUCCESS(vrc))
1425 usbsysfsFillInDevice(pDev, pInfo);
1426 if ( RT_SUCCESS(vrc)
1427 && ( pDev->enmState != USBDEVICESTATE_UNSUPPORTED
1428 || fUnsupportedDevicesToo)
1429 && pDev->pszAddress != NULL
1430 )
1431 {
1432 if (pLast != NULL)
1433 {
1434 pLast->pNext = pDev;
1435 pLast = pLast->pNext;
1436 }
1437 else
1438 pFirst = pLast = pDev;
1439 }
1440 else
1441 deviceFree(pDev);
1442 if (RT_FAILURE(vrc))
1443 break;
1444 }
1445 if (RT_FAILURE(vrc))
1446 deviceListFree(&pFirst);
1447
1448 VEC_CLEANUP_OBJ(&vecDevInfo);
1449 return pFirst;
1450}
1451
1452#endif /* VBOX_USB_WITH_SYSFS */
1453#ifdef UNIT_TEST
1454
1455/* Set up mock functions for USBProxyLinuxCheckDeviceRoot - here dlsym and close
1456 * for the inotify presence check. */
1457static int testInotifyInitGood(void) { return 0; }
1458static int testInotifyInitBad(void) { return -1; }
1459static bool s_fHaveInotifyLibC = true;
1460static bool s_fHaveInotifyKernel = true;
1461
1462static void *testDLSym(void *handle, const char *symbol)
1463{
1464 RT_NOREF(handle, symbol);
1465 Assert(handle == RTLD_DEFAULT);
1466 Assert(!RTStrCmp(symbol, "inotify_init"));
1467 if (!s_fHaveInotifyLibC)
1468 return NULL;
1469 if (s_fHaveInotifyKernel)
1470 return (void *)(uintptr_t)testInotifyInitGood;
1471 return (void *)(uintptr_t)testInotifyInitBad;
1472}
1473
1474# define dlsym testDLSym
1475# define close(a) do {} while (0)
1476
1477#endif /* UNIT_TEST */
1478
1479/**
1480 * Is inotify available and working on this system?
1481 *
1482 * This is a requirement for using USB with sysfs
1483 */
1484static bool usbsysfsInotifyAvailable(void)
1485{
1486 int (*inotify_init)(void);
1487
1488 *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
1489 if (!inotify_init)
1490 return false;
1491 int fd = inotify_init();
1492 if (fd == -1)
1493 return false;
1494 close(fd);
1495 return true;
1496}
1497
1498#ifdef UNIT_TEST
1499
1500# undef dlsym
1501# undef close
1502
1503/** Unit test list of usbfs addresses of connected devices. */
1504static const char **g_papszUsbfsDeviceAddresses = NULL;
1505
1506static PUSBDEVICE testGetUsbfsDevices(const char *pszUsbfsRoot, bool fUnsupportedDevicesToo)
1507{
1508 RT_NOREF(pszUsbfsRoot, fUnsupportedDevicesToo);
1509 const char **psz;
1510 PUSBDEVICE pList = NULL, pTail = NULL;
1511 for (psz = g_papszUsbfsDeviceAddresses; psz && *psz; ++psz)
1512 {
1513 PUSBDEVICE pNext = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE));
1514 if (pNext)
1515 pNext->pszAddress = RTStrDup(*psz);
1516 if (!pNext || !pNext->pszAddress)
1517 {
1518 if (pNext)
1519 RTMemFree(pNext);
1520 deviceListFree(&pList);
1521 return NULL;
1522 }
1523 if (pTail)
1524 pTail->pNext = pNext;
1525 else
1526 pList = pNext;
1527 pTail = pNext;
1528 }
1529 return pList;
1530}
1531# define usbfsGetDevices testGetUsbfsDevices
1532
1533/**
1534 * Specify the list of devices that will appear to be available through
1535 * usbfs during unit testing (of USBProxyLinuxGetDevices)
1536 * @param pacszDeviceAddresses NULL terminated array of usbfs device addresses
1537 */
1538void TestUSBSetAvailableUsbfsDevices(const char **papszDeviceAddresses)
1539{
1540 g_papszUsbfsDeviceAddresses = papszDeviceAddresses;
1541}
1542
1543/** Unit test list of files reported as accessible by access(3). We only do
1544 * accessible or not accessible. */
1545static const char **g_papszAccessibleFiles = NULL;
1546
1547static int testAccess(const char *pszPath, int mode)
1548{
1549 RT_NOREF(mode);
1550 const char **psz;
1551 for (psz = g_papszAccessibleFiles; psz && *psz; ++psz)
1552 if (!RTStrCmp(pszPath, *psz))
1553 return 0;
1554 return -1;
1555}
1556# define access testAccess
1557
1558
1559/**
1560 * Specify the list of files that access will report as accessible (at present
1561 * we only do accessible or not accessible) during unit testing (of
1562 * USBProxyLinuxGetDevices)
1563 * @param papszAccessibleFiles NULL terminated array of file paths to be
1564 * reported accessible
1565 */
1566void TestUSBSetAccessibleFiles(const char **papszAccessibleFiles)
1567{
1568 g_papszAccessibleFiles = papszAccessibleFiles;
1569}
1570
1571
1572/** The path we pretend the usbfs root is located at, or NULL. */
1573const char *s_pszTestUsbfsRoot;
1574/** Should usbfs be accessible to the current user? */
1575bool s_fTestUsbfsAccessible;
1576/** The path we pretend the device node tree root is located at, or NULL. */
1577const char *s_pszTestDevicesRoot;
1578/** Should the device node tree be accessible to the current user? */
1579bool s_fTestDevicesAccessible;
1580/** The result of the usbfs/inotify-specific init */
1581int s_vrcTestMethodInitResult;
1582/** The value of the VBOX_USB environment variable. */
1583const char *s_pszTestEnvUsb;
1584/** The value of the VBOX_USB_ROOT environment variable. */
1585const char *s_pszTestEnvUsbRoot;
1586
1587
1588/** Select which access methods will be available to the @a init method
1589 * during unit testing, and (hack!) what return code it will see from
1590 * the access method-specific initialisation. */
1591void TestUSBSetupInit(const char *pszUsbfsRoot, bool fUsbfsAccessible,
1592 const char *pszDevicesRoot, bool fDevicesAccessible,
1593 int vrcMethodInitResult)
1594{
1595 s_pszTestUsbfsRoot = pszUsbfsRoot;
1596 s_fTestUsbfsAccessible = fUsbfsAccessible;
1597 s_pszTestDevicesRoot = pszDevicesRoot;
1598 s_fTestDevicesAccessible = fDevicesAccessible;
1599 s_vrcTestMethodInitResult = vrcMethodInitResult;
1600}
1601
1602
1603/** Specify the environment that the @a init method will see during unit
1604 * testing. */
1605void TestUSBSetEnv(const char *pszEnvUsb, const char *pszEnvUsbRoot)
1606{
1607 s_pszTestEnvUsb = pszEnvUsb;
1608 s_pszTestEnvUsbRoot = pszEnvUsbRoot;
1609}
1610
1611/* For testing we redefine anything that accesses the outside world to
1612 * return test values. */
1613# define RTEnvGet(a) \
1614 ( !RTStrCmp(a, "VBOX_USB") ? s_pszTestEnvUsb \
1615 : !RTStrCmp(a, "VBOX_USB_ROOT") ? s_pszTestEnvUsbRoot \
1616 : NULL)
1617# define USBProxyLinuxCheckDeviceRoot(pszPath, fUseNodes) \
1618 ( ((fUseNodes) && s_fTestDevicesAccessible \
1619 && !RTStrCmp(pszPath, s_pszTestDevicesRoot)) \
1620 || (!(fUseNodes) && s_fTestUsbfsAccessible \
1621 && !RTStrCmp(pszPath, s_pszTestUsbfsRoot)))
1622# define RTDirExists(pszDir) \
1623 ( (pszDir) \
1624 && ( !RTStrCmp(pszDir, s_pszTestDevicesRoot) \
1625 || !RTStrCmp(pszDir, s_pszTestUsbfsRoot)))
1626# define RTFileExists(pszFile) \
1627 ( (pszFile) \
1628 && s_pszTestUsbfsRoot \
1629 && !RTStrNCmp(pszFile, s_pszTestUsbfsRoot, strlen(s_pszTestUsbfsRoot)) \
1630 && !RTStrCmp(pszFile + strlen(s_pszTestUsbfsRoot), "/devices"))
1631
1632#endif /* UNIT_TEST */
1633
1634/**
1635 * Use USBFS-like or sysfs/device node-like access method?
1636 *
1637 * Selects the access method that will be used to access USB devices based on
1638 * what is available on the host and what if anything the user has specified
1639 * in the environment.
1640 *
1641 * @returns iprt status value
1642 * @param pfUsingUsbfsDevices on success this will be set to true if
1643 * the prefered access method is USBFS-like and to
1644 * false if it is sysfs/device node-like
1645 * @param ppszDevicesRoot on success the root of the tree of USBFS-like
1646 * device nodes will be stored here
1647 */
1648int USBProxyLinuxChooseMethod(bool *pfUsingUsbfsDevices, const char **ppszDevicesRoot)
1649{
1650 /*
1651 * We have two methods available for getting host USB device data - using
1652 * USBFS and using sysfs. The default choice is sysfs; if that is not
1653 * available we fall back to USBFS.
1654 * In the event of both failing, an appropriate error will be returned.
1655 * The user may also specify a method and root using the VBOX_USB and
1656 * VBOX_USB_ROOT environment variables. In this case we don't check
1657 * the root they provide for validity.
1658 */
1659 bool fUsbfsChosen = false;
1660 bool fSysfsChosen = false;
1661 const char *pszUsbFromEnv = RTEnvGet("VBOX_USB");
1662 const char *pszUsbRoot = NULL;
1663 if (pszUsbFromEnv)
1664 {
1665 bool fValidVBoxUSB = true;
1666
1667 pszUsbRoot = RTEnvGet("VBOX_USB_ROOT");
1668 if (!RTStrICmp(pszUsbFromEnv, "USBFS"))
1669 {
1670 LogRel(("Default USB access method set to \"usbfs\" from environment\n"));
1671 fUsbfsChosen = true;
1672 }
1673 else if (!RTStrICmp(pszUsbFromEnv, "SYSFS"))
1674 {
1675 LogRel(("Default USB method set to \"sysfs\" from environment\n"));
1676 fSysfsChosen = true;
1677 }
1678 else
1679 {
1680 LogRel(("Invalid VBOX_USB environment variable setting \"%s\"\n", pszUsbFromEnv));
1681 fValidVBoxUSB = false;
1682 pszUsbFromEnv = NULL;
1683 }
1684 if (!fValidVBoxUSB && pszUsbRoot)
1685 pszUsbRoot = NULL;
1686 }
1687 if (!pszUsbRoot)
1688 {
1689 if ( !fUsbfsChosen
1690 && USBProxyLinuxCheckDeviceRoot("/dev/vboxusb", true))
1691 {
1692 fSysfsChosen = true;
1693 pszUsbRoot = "/dev/vboxusb";
1694 }
1695 else if ( !fSysfsChosen
1696 && USBProxyLinuxCheckDeviceRoot("/proc/bus/usb", false))
1697 {
1698 fUsbfsChosen = true;
1699 pszUsbRoot = "/proc/bus/usb";
1700 }
1701 }
1702 else if (!USBProxyLinuxCheckDeviceRoot(pszUsbRoot, fSysfsChosen))
1703 pszUsbRoot = NULL;
1704 if (pszUsbRoot)
1705 {
1706 *pfUsingUsbfsDevices = fUsbfsChosen;
1707 *ppszDevicesRoot = pszUsbRoot;
1708 return VINF_SUCCESS;
1709 }
1710 /* else */
1711 return pszUsbFromEnv ? VERR_NOT_FOUND
1712 : RTDirExists("/dev/vboxusb") ? VERR_VUSB_USB_DEVICE_PERMISSION
1713 : RTFileExists("/proc/bus/usb/devices") ? VERR_VUSB_USBFS_PERMISSION
1714 : VERR_NOT_FOUND;
1715}
1716
1717#ifdef UNIT_TEST
1718# undef RTEnvGet
1719# undef USBProxyLinuxCheckDeviceRoot
1720# undef RTDirExists
1721# undef RTFileExists
1722#endif
1723
1724/**
1725 * Check whether a USB device tree root is usable.
1726 *
1727 * @param pszRoot the path to the root of the device tree
1728 * @param fIsDeviceNodes whether this is a device node (or usbfs) tree
1729 * @note returns a pointer into a static array so it will stay valid
1730 */
1731bool USBProxyLinuxCheckDeviceRoot(const char *pszRoot, bool fIsDeviceNodes)
1732{
1733 bool fOK = false;
1734 if (!fIsDeviceNodes) /* usbfs */
1735 {
1736#ifdef VBOX_USB_WITH_USBFS
1737 if (!access(pszRoot, R_OK | X_OK))
1738 {
1739 fOK = true;
1740 PUSBDEVICE pDevices = usbfsGetDevices(pszRoot, true);
1741 if (pDevices)
1742 {
1743 PUSBDEVICE pDevice;
1744 for (pDevice = pDevices; pDevice && fOK; pDevice = pDevice->pNext)
1745 if (access(pDevice->pszAddress, R_OK | W_OK))
1746 fOK = false;
1747 deviceListFree(&pDevices);
1748 }
1749 }
1750#endif
1751 }
1752#ifdef VBOX_USB_WITH_SYSFS
1753 /* device nodes */
1754 else if (usbsysfsInotifyAvailable() && !access(pszRoot, R_OK | X_OK))
1755 fOK = true;
1756#endif
1757 return fOK;
1758}
1759
1760#ifdef UNIT_TEST
1761# undef usbfsGetDevices
1762# undef access
1763#endif
1764
1765/**
1766 * Get the list of USB devices supported by the system.
1767 *
1768 * Result should be freed using #deviceFree or something equivalent.
1769 *
1770 * @param pszDevicesRoot the path to the root of the device tree
1771 * @param fUseSysfs whether to use sysfs (or usbfs) for enumeration
1772 */
1773PUSBDEVICE USBProxyLinuxGetDevices(const char *pszDevicesRoot, bool fUseSysfs)
1774{
1775 if (!fUseSysfs)
1776 {
1777#ifdef VBOX_USB_WITH_USBFS
1778 return usbfsGetDevices(pszDevicesRoot, false);
1779#else
1780 return NULL;
1781#endif
1782 }
1783
1784#ifdef VBOX_USB_WITH_SYSFS
1785 return usbsysfsGetDevices(pszDevicesRoot, false);
1786#else
1787 return NULL;
1788#endif
1789}
1790
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use