VirtualBox

source: vbox/trunk/src/VBox/Main/linux/HostHardwareLinux.cpp@ 31564

Last change on this file since 31564 was 31564, checked in by vboxsync, 15 years ago

Main/HostHardwareLinux: removed all the unused hal/DBus stuff

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 53.5 KB
Line 
1/* $Id: HostHardwareLinux.cpp 31564 2010-08-11 12:42:43Z vboxsync $ */
2/** @file
3 * Classes for handling hardware detection under Linux. Please feel free to
4 * expand these to work for other systems (Solaris!) or to add new ones for
5 * other systems.
6 */
7
8/*
9 * Copyright (C) 2008-2010 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.215389.xyz. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#define LOG_GROUP LOG_GROUP_MAIN
21
22/*******************************************************************************
23* Header Files *
24*******************************************************************************/
25
26#include <HostHardwareLinux.h>
27
28#include <VBox/err.h>
29#include <VBox/log.h>
30
31#include <iprt/asm.h>
32#include <iprt/dir.h>
33#include <iprt/env.h>
34#include <iprt/file.h>
35#include <iprt/mem.h>
36#include <iprt/param.h>
37#include <iprt/path.h>
38#include <iprt/string.h>
39#include <iprt/thread.h> /* for RTThreadSleep() */
40
41#include <linux/cdrom.h>
42#include <linux/fd.h>
43#include <linux/major.h>
44#include <scsi/scsi.h>
45
46#include <iprt/linux/sysfs.h>
47
48#ifdef VBOX_USB_WITH_SYSFS
49# ifdef VBOX_USB_WITH_INOTIFY
50# include <dlfcn.h>
51# include <fcntl.h>
52# include <poll.h>
53# include <signal.h>
54# include <unistd.h>
55# endif
56#endif
57
58#include <vector>
59
60#include <errno.h>
61
62/******************************************************************************
63* Global Variables *
64******************************************************************************/
65
66#ifdef TESTCASE
67static bool testing() { return true; }
68static bool fNoProbe = false;
69static bool noProbe() { return fNoProbe; }
70static void setNoProbe(bool val) { fNoProbe = val; }
71#else
72static bool testing() { return false; }
73static bool noProbe() { return false; }
74static void setNoProbe(bool val) { (void)val; }
75#endif
76
77/******************************************************************************
78* Typedefs and Defines *
79******************************************************************************/
80
81static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList,
82 bool isDVD, bool *pfSuccess);
83static int getDriveInfoFromDev(DriveInfoList *pList, bool isDVD,
84 bool *pfSuccess);
85static int getDriveInfoFromSysfs(DriveInfoList *pList, bool isDVD,
86 bool *pfSuccess);
87#ifdef VBOX_USB_WITH_SYSFS
88# ifdef VBOX_USB_WITH_INOTIFY
89static int getUSBDeviceInfoFromSysfs(USBDeviceInfoList *pList, bool *pfSuccess);
90
91/** Function object to be invoked on filenames from a directory. */
92class pathHandler
93{
94 /** Called on each element of the sysfs directory. Can e.g. store
95 * interesting entries in a list. */
96 virtual bool handle(const char *pcszNode) = 0;
97public:
98 bool doHandle(const char *pcszNode)
99 {
100 AssertPtr(pcszNode);
101 Assert(pcszNode[0] == '/');
102 return handle(pcszNode);
103 }
104};
105
106static int walkDirectory(const char *pcszPath, pathHandler *pHandler,
107 bool useRealPath);
108static int getDeviceInfoFromSysfs(const char *pcszPath, pathHandler *pHandler);
109# endif
110#endif /* VBOX_USB_WITH_SYSFS */
111
112
113/** Find the length of a string, ignoring trailing non-ascii or control
114 * characters */
115static size_t strLenStripped(const char *pcsz)
116{
117 size_t cch = 0;
118 for (size_t i = 0; pcsz[i] != '\0'; ++i)
119 if (pcsz[i] > 32 && pcsz[i] < 127)
120 cch = i;
121 return cch + 1;
122}
123
124
125/**
126 * Get the name of a floppy drive according to the Linux floppy driver.
127 * @returns true on success, false if the name was not available (i.e. the
128 * device was not readible, or the file name wasn't a PC floppy
129 * device)
130 * @param pcszNode the path to the device node for the device
131 * @param Number the Linux floppy driver number for the drive. Required.
132 * @param pszName where to store the name retreived
133 */
134static bool floppyGetName(const char *pcszNode, unsigned Number,
135 floppy_drive_name pszName)
136{
137 AssertPtrReturn(pcszNode, false);
138 AssertPtrReturn(pszName, false);
139 AssertReturn(Number <= 7, false);
140 RTFILE File;
141 int rc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
142 if (RT_SUCCESS(rc))
143 {
144 int rcIoCtl;
145 rc = RTFileIoCtl(File, FDGETDRVTYP, pszName, 0, &rcIoCtl);
146 RTFileClose(File);
147 if (RT_SUCCESS(rc) && rcIoCtl >= 0)
148 return true;
149 }
150 return false;
151}
152
153
154/**
155 * Create a UDI and a description for a floppy drive based on a number and the
156 * driver's name for it. We deliberately return an ugly sequence of
157 * characters as the description rather than an English language string to
158 * avoid translation issues.
159 *
160 * @returns true if we know the device to be valid, false otherwise
161 * @param pcszName the floppy driver name for the device (optional)
162 * @param Number the number of the floppy (0 to 3 on FDC 0, 4 to 7 on
163 * FDC 1)
164 * @param pszDesc where to store the device description (optional)
165 * @param cchDesc the size of the buffer in @a pszDesc
166 * @param pszUdi where to store the device UDI (optional)
167 * @param cchUdi the size of the buffer in @a pszUdi
168 */
169static void floppyCreateDeviceStrings(const floppy_drive_name pcszName,
170 unsigned Number, char *pszDesc,
171 size_t cchDesc, char *pszUdi,
172 size_t cchUdi)
173{
174 AssertPtrNullReturnVoid(pcszName);
175 AssertPtrNullReturnVoid(pszDesc);
176 AssertReturnVoid(!pszDesc || cchDesc > 0);
177 AssertPtrNullReturnVoid(pszUdi);
178 AssertReturnVoid(!pszUdi || cchUdi > 0);
179 AssertReturnVoid(Number <= 7);
180 if (pcszName)
181 {
182 const char *pcszSize;
183 switch(pcszName[0])
184 {
185 case 'd': case 'q': case 'h':
186 pcszSize = "5.25\"";
187 break;
188 case 'D': case 'H': case 'E': case 'u':
189 pcszSize = "3.5\"";
190 break;
191 default:
192 pcszSize = "(unknown)";
193 }
194 if (pszDesc)
195 RTStrPrintf(pszDesc, cchDesc, "%s %s K%s", pcszSize, &pcszName[1],
196 Number > 3 ? ", FDC 2" : "");
197 }
198 else
199 {
200 if (pszDesc)
201 RTStrPrintf(pszDesc, cchDesc, "FDD %d%s", (Number & 4) + 1,
202 Number > 3 ? ", FDC 2" : "");
203 }
204 if (pszUdi)
205 RTStrPrintf(pszUdi, cchUdi,
206 "/org/freedesktop/Hal/devices/platform_floppy_%u_storage",
207 Number);
208}
209
210
211/**
212 * Check whether a device number might correspond to a CD-ROM device according
213 * to Documentation/devices.txt in the Linux kernel source.
214 * @returns true if it might, false otherwise
215 * @param Number the device number (major and minor combination)
216 */
217static bool isCdromDevNum(dev_t Number)
218{
219 int major = major(Number);
220 int minor = minor(Number);
221 if ((major == IDE0_MAJOR) && !(minor & 0x3f))
222 return true;
223 if (major == SCSI_CDROM_MAJOR)
224 return true;
225 if (major == CDU31A_CDROM_MAJOR)
226 return true;
227 if (major == GOLDSTAR_CDROM_MAJOR)
228 return true;
229 if (major == OPTICS_CDROM_MAJOR)
230 return true;
231 if (major == SANYO_CDROM_MAJOR)
232 return true;
233 if (major == MITSUMI_X_CDROM_MAJOR)
234 return true;
235 if ((major == IDE1_MAJOR) && !(minor & 0x3f))
236 return true;
237 if (major == MITSUMI_CDROM_MAJOR)
238 return true;
239 if (major == CDU535_CDROM_MAJOR)
240 return true;
241 if (major == MATSUSHITA_CDROM_MAJOR)
242 return true;
243 if (major == MATSUSHITA_CDROM2_MAJOR)
244 return true;
245 if (major == MATSUSHITA_CDROM3_MAJOR)
246 return true;
247 if (major == MATSUSHITA_CDROM4_MAJOR)
248 return true;
249 if (major == AZTECH_CDROM_MAJOR)
250 return true;
251 if (major == 30 /* CM205_CDROM_MAJOR */) /* no #define for some reason */
252 return true;
253 if (major == CM206_CDROM_MAJOR)
254 return true;
255 if ((major == IDE3_MAJOR) && !(minor & 0x3f))
256 return true;
257 if (major == 46 /* Parallel port ATAPI CD-ROM */) /* no #define */
258 return true;
259 if ((major == IDE4_MAJOR) && !(minor & 0x3f))
260 return true;
261 if ((major == IDE5_MAJOR) && !(minor & 0x3f))
262 return true;
263 if ((major == IDE6_MAJOR) && !(minor & 0x3f))
264 return true;
265 if ((major == IDE7_MAJOR) && !(minor & 0x3f))
266 return true;
267 if ((major == IDE8_MAJOR) && !(minor & 0x3f))
268 return true;
269 if ((major == IDE9_MAJOR) && !(minor & 0x3f))
270 return true;
271 if (major == 113 /* VIOCD_MAJOR */)
272 return true;
273 return false;
274}
275
276
277/**
278 * Send an SCSI INQUIRY command to a device and return selected information.
279 * @returns iprt status code
280 * @returns VERR_TRY_AGAIN if the query failed but might succeed next time
281 * @param pcszNode the full path to the device node
282 * @param pu8Type where to store the SCSI device type on success (optional)
283 * @param pchVendor where to store the vendor id string on success (optional)
284 * @param cchVendor the size of the @a pchVendor buffer
285 * @param pchModel where to store the product id string on success (optional)
286 * @param cchModel the size of the @a pchModel buffer
287 * @note check documentation on the SCSI INQUIRY command and the Linux kernel
288 * SCSI headers included above if you want to understand what is going
289 * on in this method.
290 */
291static int cdromDoInquiry(const char *pcszNode, uint8_t *pu8Type,
292 char *pchVendor, size_t cchVendor, char *pchModel,
293 size_t cchModel)
294{
295 LogRelFlowFunc(("pcszNode=%s, pu8Type=%p, pchVendor=%p, cchVendor=%llu, pchModel=%p, cchModel=%llu\n",
296 pcszNode, pu8Type, pchVendor, cchVendor, pchModel,
297 cchModel));
298 AssertPtrReturn(pcszNode, VERR_INVALID_POINTER);
299 AssertPtrNullReturn(pu8Type, VERR_INVALID_POINTER);
300 AssertPtrNullReturn(pchVendor, VERR_INVALID_POINTER);
301 AssertPtrNullReturn(pchModel, VERR_INVALID_POINTER);
302
303 RTFILE hFile;
304 int rc = RTFileOpen(&hFile, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
305 if (RT_SUCCESS(rc))
306 {
307 int rcIoCtl = 0;
308 unsigned char u8Response[96] = { 0 };
309 struct cdrom_generic_command CdromCommandReq;
310 RT_ZERO(CdromCommandReq);
311 CdromCommandReq.cmd[0] = INQUIRY;
312 CdromCommandReq.cmd[4] = sizeof(u8Response);
313 CdromCommandReq.buffer = u8Response;
314 CdromCommandReq.buflen = sizeof(u8Response);
315 CdromCommandReq.data_direction = CGC_DATA_READ;
316 CdromCommandReq.timeout = 5000; /* ms */
317 rc = RTFileIoCtl(hFile, CDROM_SEND_PACKET, &CdromCommandReq, 0, &rcIoCtl);
318 if (RT_SUCCESS(rc) && rcIoCtl < 0)
319 rc = RTErrConvertFromErrno(-CdromCommandReq.stat);
320 RTFileClose(hFile);
321
322 if (RT_SUCCESS(rc))
323 {
324 if (pu8Type)
325 *pu8Type = u8Response[0] & 0x1f;
326 if (pchVendor)
327 RTStrPrintf(pchVendor, cchVendor, "%.8s",
328 &u8Response[8] /* vendor id string */);
329 if (pchModel)
330 RTStrPrintf(pchModel, cchModel, "%.16s",
331 &u8Response[16] /* product id string */);
332 LogRelFlowFunc(("returning success: type=%u, vendor=%.8s, product=%.16s\n",
333 u8Response[0] & 0x1f, &u8Response[8], &u8Response[16]));
334 return VINF_SUCCESS;
335 }
336 }
337 LogRelFlowFunc(("returning %Rrc\n", rc));
338 return rc;
339}
340
341
342/**
343 * Initialise the device strings (description and UDI) for a DVD drive based on
344 * vendor and model name strings.
345 * @param pcszVendor the vendor ID string
346 * @param pcszModel the product ID string
347 * @param pszDesc where to store the description string (optional)
348 * @param cchDesc the size of the buffer in @pszDesc
349 * @param pszUdi where to store the UDI string (optional)
350 * @param cchUdi the size of the buffer in @pszUdi
351 */
352/* static */
353void dvdCreateDeviceStrings(const char *pcszVendor, const char *pcszModel,
354 char *pszDesc, size_t cchDesc, char *pszUdi,
355 size_t cchUdi)
356{
357 AssertPtrReturnVoid(pcszVendor);
358 AssertPtrReturnVoid(pcszModel);
359 AssertPtrNullReturnVoid(pszDesc);
360 AssertReturnVoid(!pszDesc || cchDesc > 0);
361 AssertPtrNullReturnVoid(pszUdi);
362 AssertReturnVoid(!pszUdi || cchUdi > 0);
363 char szCleaned[128];
364 size_t cchVendor = strLenStripped(pcszVendor);
365 size_t cchModel = strLenStripped(pcszModel);
366
367 /* Create a cleaned version of the model string for the UDI string. */
368 for (unsigned i = 0; pcszModel[i] != '\0' && i < sizeof(szCleaned); ++i)
369 if ( (pcszModel[i] >= '0' && pcszModel[i] <= '9')
370 || (pcszModel[i] >= 'A' && pcszModel[i] <= 'z'))
371 szCleaned[i] = pcszModel[i];
372 else
373 szCleaned[i] = '_';
374 szCleaned[RT_MIN(cchModel, sizeof(szCleaned) - 1)] = '\0';
375
376 /* Construct the description string as "Vendor Product" */
377 if (pszDesc)
378 {
379 if (cchVendor > 0)
380 RTStrPrintf(pszDesc, cchDesc, "%.*s %s", cchVendor, pcszVendor,
381 cchModel > 0 ? pcszModel : "(unknown drive model)");
382 else
383 RTStrPrintf(pszDesc, cchDesc, "%s", pcszModel);
384 }
385 /* Construct the UDI string */
386 if (pszUdi)
387 {
388 if (cchModel > 0)
389 RTStrPrintf(pszUdi, cchUdi,
390 "/org/freedesktop/Hal/devices/storage_model_%s",
391 szCleaned);
392 else
393 pszUdi[0] = '\0';
394 }
395}
396
397
398/**
399 * Check whether a device node points to a valid device and create a UDI and
400 * a description for it, and store the device number, if it does.
401 * @returns true if the device is valid, false otherwise
402 * @param pcszNode the path to the device node
403 * @param isDVD are we looking for a DVD device (or a floppy device)?
404 * @param pDevice where to store the device node (optional)
405 * @param pszDesc where to store the device description (optional)
406 * @param cchDesc the size of the buffer in @a pszDesc
407 * @param pszUdi where to store the device UDI (optional)
408 * @param cchUdi the size of the buffer in @a pszUdi
409 */
410static bool devValidateDevice(const char *pcszNode, bool isDVD, dev_t *pDevice,
411 char *pszDesc, size_t cchDesc, char *pszUdi,
412 size_t cchUdi)
413{
414 AssertPtrReturn(pcszNode, false);
415 AssertPtrNullReturn(pDevice, false);
416 AssertPtrNullReturn(pszDesc, false);
417 AssertReturn(!pszDesc || cchDesc > 0, false);
418 AssertPtrNullReturn(pszUdi, false);
419 AssertReturn(!pszUdi || cchUdi > 0, false);
420 RTFSOBJINFO ObjInfo;
421 if (RT_FAILURE(RTPathQueryInfo(pcszNode, &ObjInfo, RTFSOBJATTRADD_UNIX)))
422 return false;
423 if (!RTFS_IS_DEV_BLOCK(ObjInfo.Attr.fMode))
424 return false;
425 if (pDevice)
426 *pDevice = ObjInfo.Attr.u.Unix.Device;
427 if (isDVD)
428 {
429 char szVendor[128], szModel[128];
430 uint8_t u8Type;
431 if (!isCdromDevNum(ObjInfo.Attr.u.Unix.Device))
432 return false;
433 if (RT_FAILURE(cdromDoInquiry(pcszNode, &u8Type,
434 szVendor, sizeof(szVendor),
435 szModel, sizeof(szModel))))
436 return false;
437 if (u8Type != TYPE_ROM)
438 return false;
439 dvdCreateDeviceStrings(szVendor, szModel, pszDesc, cchDesc,
440 pszUdi, cchUdi);
441 }
442 else
443 {
444 /* Floppies on Linux are legacy devices with hardcoded majors and
445 * minors */
446 unsigned Number;
447 floppy_drive_name szName;
448 if (major(ObjInfo.Attr.u.Unix.Device) != FLOPPY_MAJOR)
449 return false;
450 switch (minor(ObjInfo.Attr.u.Unix.Device))
451 {
452 case 0: case 1: case 2: case 3:
453 Number = minor(ObjInfo.Attr.u.Unix.Device);
454 break;
455 case 128: case 129: case 130: case 131:
456 Number = minor(ObjInfo.Attr.u.Unix.Device) - 128 + 4;
457 break;
458 default:
459 return false;
460 }
461 if (!floppyGetName(pcszNode, Number, szName))
462 return false;
463 floppyCreateDeviceStrings(szName, Number, pszDesc, cchDesc, pszUdi,
464 cchUdi);
465 }
466 return true;
467}
468
469
470int VBoxMainDriveInfo::updateDVDs ()
471{
472 LogFlowThisFunc(("entered\n"));
473 int rc = VINF_SUCCESS;
474 bool success = false; /* Have we succeeded in finding anything yet? */
475 try
476 {
477 mDVDList.clear ();
478 /* Always allow the user to override our auto-detection using an
479 * environment variable. */
480 if (RT_SUCCESS(rc) && (!success || testing()))
481 rc = getDriveInfoFromEnv ("VBOX_CDROM", &mDVDList, true /* isDVD */,
482 &success);
483 setNoProbe(false);
484 if (RT_SUCCESS(rc) && (!success | testing()))
485 rc = getDriveInfoFromSysfs(&mDVDList, true /* isDVD */, &success);
486 if (RT_SUCCESS(rc) && testing())
487 {
488 setNoProbe(true);
489 rc = getDriveInfoFromSysfs(&mDVDList, true /* isDVD */, &success);
490 }
491 /* Walk through the /dev subtree if nothing else has helped. */
492 if (RT_SUCCESS(rc) && (!success | testing()))
493 rc = getDriveInfoFromDev(&mDVDList, true /* isDVD */, &success);
494 }
495 catch(std::bad_alloc &e)
496 {
497 rc = VERR_NO_MEMORY;
498 }
499 LogFlowThisFunc(("rc=%Rrc\n", rc));
500 return rc;
501}
502
503int VBoxMainDriveInfo::updateFloppies ()
504{
505 LogFlowThisFunc(("entered\n"));
506 int rc = VINF_SUCCESS;
507 bool success = false; /* Have we succeeded in finding anything yet? */
508 try
509 {
510 mFloppyList.clear ();
511 if (RT_SUCCESS(rc) && (!success || testing()))
512 rc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList,
513 false /* isDVD */, &success);
514 setNoProbe(false);
515 if ( RT_SUCCESS(rc) && (!success || testing()))
516 rc = getDriveInfoFromSysfs(&mFloppyList, false /* isDVD */,
517 &success);
518 if (RT_SUCCESS(rc) && testing())
519 {
520 setNoProbe(true);
521 rc = getDriveInfoFromSysfs(&mFloppyList, false /* isDVD */, &success);
522 }
523 /* Walk through the /dev subtree if nothing else has helped. */
524 if ( RT_SUCCESS(rc) && (!success || testing()))
525 rc = getDriveInfoFromDev(&mFloppyList, false /* isDVD */,
526 &success);
527 }
528 catch(std::bad_alloc &e)
529 {
530 rc = VERR_NO_MEMORY;
531 }
532 LogFlowThisFunc(("rc=%Rrc\n", rc));
533 return rc;
534}
535
536
537/**
538 * Extract the names of drives from an environment variable and add them to a
539 * list if they are valid.
540 * @returns iprt status code
541 * @param pcszVar the name of the environment variable. The variable
542 * value should be a list of device node names, separated
543 * by ':' characters.
544 * @param pList the list to append the drives found to
545 * @param isDVD are we looking for DVD drives or for floppies?
546 * @param pfSuccess this will be set to true if we found at least one drive
547 * and to false otherwise. Optional.
548 */
549/* static */
550int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList,
551 bool isDVD, bool *pfSuccess)
552{
553 AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
554 AssertPtrReturn(pList, VERR_INVALID_POINTER);
555 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
556 LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar,
557 pList, isDVD, pfSuccess));
558 int rc = VINF_SUCCESS;
559 bool success = false;
560 char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
561
562 try
563 {
564 const char *pcszCurrent = pszFreeMe;
565 while (pcszCurrent && *pcszCurrent != '\0')
566 {
567 const char *pcszNext = strchr(pcszCurrent, ':');
568 char szPath[RTPATH_MAX], szReal[RTPATH_MAX];
569 char szDesc[256], szUdi[256];
570 if (pcszNext)
571 RTStrPrintf(szPath, sizeof(szPath), "%.*s",
572 pcszNext - pcszCurrent - 1, pcszCurrent);
573 else
574 RTStrPrintf(szPath, sizeof(szPath), "%s", pcszCurrent);
575 if ( RT_SUCCESS(RTPathReal(szPath, szReal, sizeof(szReal)))
576 && devValidateDevice(szReal, isDVD, NULL, szDesc,
577 sizeof(szDesc), szUdi, sizeof(szUdi)))
578 {
579 pList->push_back(DriveInfo(szReal, szUdi, szDesc));
580 success = true;
581 }
582 pcszCurrent = pcszNext ? pcszNext + 1 : NULL;
583 }
584 if (pfSuccess != NULL)
585 *pfSuccess = success;
586 }
587 catch(std::bad_alloc &e)
588 {
589 rc = VERR_NO_MEMORY;
590 }
591 RTStrFree(pszFreeMe);
592 LogFlowFunc(("rc=%Rrc, success=%d\n", rc, success));
593 return rc;
594}
595
596
597class sysfsBlockDev
598{
599public:
600 sysfsBlockDev(const char *pcszName, bool wantDVD)
601 : mpcszName(pcszName), mwantDVD(wantDVD), misConsistent(true),
602 misValid(false)
603 {
604 if (findDeviceNode())
605 {
606 if (mwantDVD)
607 validateAndInitForDVD();
608 else
609 validateAndInitForFloppy();
610 }
611 }
612private:
613 /** The name of the subdirectory of /sys/block for this device */
614 const char *mpcszName;
615 /** Are we looking for a floppy or a DVD device? */
616 bool mwantDVD;
617 /** The device node for the device */
618 char mszNode[RTPATH_MAX];
619 /** Does the sysfs entry look like we expect it too? This is a canary
620 * for future sysfs ABI changes. */
621 bool misConsistent;
622 /** Is this entry a valid specimen of what we are looking for? */
623 bool misValid;
624 /** Human readible drive description string */
625 char mszDesc[256];
626 /** Unique identifier for the drive. Should be identical to hal's UDI for
627 * the device. May not be unique for two identical drives. */
628 char mszUdi[256];
629private:
630 /* Private methods */
631
632 /**
633 * Fill in the device node member based on the /sys/block subdirectory.
634 * @returns boolean success value
635 */
636 bool findDeviceNode()
637 {
638 dev_t dev = RTLinuxSysFsReadDevNumFile("block/%s/dev", mpcszName);
639 if (dev == 0)
640 {
641 misConsistent = false;
642 return false;
643 }
644 if (RTLinuxFindDevicePath(dev, RTFS_TYPE_DEV_BLOCK, mszNode,
645 sizeof(mszNode), "%s", mpcszName) < 0)
646 return false;
647 return true;
648 }
649
650 /** Check whether the sysfs block entry is valid for a DVD device and
651 * initialise the string data members for the object. We try to get all
652 * the information we need from sysfs if possible, to avoid unnecessarily
653 * poking the device, and if that fails we fall back to an SCSI INQUIRY
654 * command. */
655 void validateAndInitForDVD()
656 {
657 char szVendor[128], szModel[128];
658 ssize_t cchVendor, cchModel;
659 int64_t type = RTLinuxSysFsReadIntFile(10, "block/%s/device/type",
660 mpcszName);
661 if (type >= 0 && type != TYPE_ROM)
662 return;
663 if (type == TYPE_ROM)
664 {
665 cchVendor = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor),
666 "block/%s/device/vendor",
667 mpcszName);
668 if (cchVendor >= 0)
669 {
670 cchModel = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel),
671 "block/%s/device/model",
672 mpcszName);
673 if (cchModel >= 0)
674 {
675 misValid = true;
676 dvdCreateDeviceStrings(szVendor, szModel,
677 mszDesc, sizeof(mszDesc),
678 mszUdi, sizeof(mszUdi));
679 return;
680 }
681 }
682 }
683 if (!noProbe())
684 probeAndInitForDVD();
685 }
686
687 /** Try to find out whether a device is a DVD drive by sending it an
688 * SCSI INQUIRY command. If it is, initialise the string and validity
689 * data members for the object based on the returned data.
690 */
691 void probeAndInitForDVD()
692 {
693 AssertReturnVoid(mszNode[0] != '\0');
694 uint8_t u8Type = 0;
695 char szVendor[128] = "";
696 char szModel[128] = "";
697 int rc = cdromDoInquiry(mszNode, &u8Type, szVendor,
698 sizeof(szVendor), szModel,
699 sizeof(szModel));
700 if (RT_SUCCESS(rc) && (u8Type == TYPE_ROM))
701 {
702 misValid = true;
703 dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc),
704 mszUdi, sizeof(mszUdi));
705 }
706 }
707
708 /** Check whether the sysfs block entry is valid for a floppy device and
709 * initialise the string data members for the object. Since we only
710 * support floppies using the basic "floppy" driver, we check the driver
711 * using the entry name and a driver-specific ioctl. */
712 void validateAndInitForFloppy()
713 {
714 bool haveName = false;
715 floppy_drive_name szName;
716 char szDriver[8];
717 if ( mpcszName[0] != 'f'
718 || mpcszName[1] != 'd'
719 || mpcszName[2] < '0'
720 || mpcszName[2] > '7'
721 || mpcszName[3] != '\0')
722 return;
723 if (!noProbe())
724 haveName = floppyGetName(mszNode, mpcszName[2] - '0', szName);
725 if (RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), "block/%s/%s",
726 mpcszName, "device/driver") >= 0)
727 {
728 if (RTStrCmp(szDriver, "floppy"))
729 return;
730 }
731 else if (!haveName)
732 return;
733 floppyCreateDeviceStrings(haveName ? szName : NULL,
734 mpcszName[2] - '0', mszDesc,
735 sizeof(mszDesc), mszUdi, sizeof(mszUdi));
736 misValid = true;
737 }
738
739public:
740 bool isConsistent()
741 {
742 return misConsistent;
743 }
744 bool isValid()
745 {
746 return misValid;
747 }
748 const char *getDesc()
749 {
750 return mszDesc;
751 }
752 const char *getUdi()
753 {
754 return mszUdi;
755 }
756 const char *getNode()
757 {
758 return mszNode;
759 }
760};
761
762/**
763 * Helper function to query the sysfs subsystem for information about DVD
764 * drives attached to the system.
765 * @returns iprt status code
766 * @param pList where to add information about the drives detected
767 * @param isDVD are we looking for DVDs or floppies?
768 * @param pfSuccess Did we find anything?
769 *
770 * @returns IPRT status code
771 */
772/* static */
773int getDriveInfoFromSysfs(DriveInfoList *pList, bool isDVD, bool *pfSuccess)
774{
775 AssertPtrReturn(pList, VERR_INVALID_POINTER);
776 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
777 LogFlowFunc (("pList=%p, isDVD=%u, pfSuccess=%p\n",
778 pList, (unsigned) isDVD, pfSuccess));
779 PRTDIR pDir = NULL;
780 int rc;
781 bool fSuccess = false;
782 unsigned cFound = 0;
783
784 if (!RTPathExists("/sys"))
785 return VINF_SUCCESS;
786 rc = RTDirOpen(&pDir, "/sys/block");
787 /* This might mean that sysfs semantics have changed */
788 AssertReturn(rc != VERR_FILE_NOT_FOUND, VINF_SUCCESS);
789 fSuccess = true;
790 if (RT_SUCCESS(rc))
791 for (;;)
792 {
793 RTDIRENTRY entry;
794 rc = RTDirRead(pDir, &entry, NULL);
795 Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
796 if (RT_FAILURE(rc)) /* Including overflow and no more files */
797 break;
798 if (entry.szName[0] == '.')
799 continue;
800 sysfsBlockDev dev(entry.szName, isDVD);
801 /* This might mean that sysfs semantics have changed */
802 AssertBreakStmt(dev.isConsistent(), fSuccess = false);
803 if (!dev.isValid())
804 continue;
805 try
806 {
807 pList->push_back(DriveInfo(dev.getNode(), dev.getUdi(),
808 dev.getDesc()));
809 }
810 catch(std::bad_alloc &e)
811 {
812 rc = VERR_NO_MEMORY;
813 break;
814 }
815 ++cFound;
816 }
817 RTDirClose(pDir);
818 if (rc == VERR_NO_MORE_FILES)
819 rc = VINF_SUCCESS;
820 if (RT_FAILURE(rc))
821 /* Clean up again */
822 for (unsigned i = 0; i < cFound; ++i)
823 pList->pop_back();
824 if (pfSuccess)
825 *pfSuccess = fSuccess;
826 LogFlow (("rc=%Rrc, fSuccess=%u\n", rc, (unsigned) fSuccess));
827 return rc;
828}
829
830
831/** Structure for holding information about a drive we have found */
832struct deviceNodeInfo
833{
834 /** The device number */
835 dev_t Device;
836 /** The device node path */
837 char szPath[RTPATH_MAX];
838 /** The device description */
839 char szDesc[256];
840 /** The device UDI */
841 char szUdi[256];
842};
843
844/** The maximum number of devices we will search for. */
845enum { MAX_DEVICE_NODES = 8 };
846/** An array of MAX_DEVICE_NODES devices */
847typedef struct deviceNodeInfo deviceNodeArray[MAX_DEVICE_NODES];
848
849/**
850 * Recursive worker function to walk the /dev tree looking for DVD or floppy
851 * devices.
852 * @returns true if we have already found MAX_DEVICE_NODES devices, false
853 * otherwise
854 * @param pszPath the path to start recursing. The function can modify
855 * this string at and after the terminating zero
856 * @param cchPath the size of the buffer (not the string!) in @a pszPath
857 * @param aDevices where to fill in information about devices that we have
858 * found
859 * @param wantDVD are we looking for DVD devices (or floppies)?
860 */
861static bool devFindDeviceRecursive(char *pszPath, size_t cchPath,
862 deviceNodeArray aDevices, bool wantDVD)
863{
864 /*
865 * Check assumptions made by the code below.
866 */
867 size_t const cchBasePath = strlen(pszPath);
868 AssertReturn(cchBasePath < RTPATH_MAX - 10U, false);
869 AssertReturn(pszPath[cchBasePath - 1] != '/', false);
870
871 PRTDIR pDir;
872 if (RT_FAILURE(RTDirOpen(&pDir, pszPath)))
873 return false;
874 for (;;)
875 {
876 RTDIRENTRY Entry;
877 RTFSOBJINFO ObjInfo;
878 int rc = RTDirRead(pDir, &Entry, NULL);
879 if (RT_FAILURE(rc))
880 break;
881 if (Entry.enmType == RTDIRENTRYTYPE_UNKNOWN)
882 {
883 if (RT_FAILURE(RTPathQueryInfo(pszPath, &ObjInfo,
884 RTFSOBJATTRADD_UNIX)))
885 continue;
886 if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
887 continue;
888 }
889
890 if (Entry.enmType == RTDIRENTRYTYPE_SYMLINK)
891 continue;
892 pszPath[cchBasePath] = '\0';
893 if (RT_FAILURE(RTPathAppend(pszPath, cchPath, Entry.szName)))
894 break;
895
896 /* Do the matching. */
897 dev_t DevNode;
898 char szDesc[256], szUdi[256];
899 if (!devValidateDevice(pszPath, wantDVD, &DevNode, szDesc,
900 sizeof(szDesc), szUdi, sizeof(szUdi)))
901 continue;
902 unsigned i;
903 for (i = 0; i < MAX_DEVICE_NODES; ++i)
904 if (!aDevices[i].Device || (aDevices[i].Device == DevNode))
905 break;
906 AssertBreak(i < MAX_DEVICE_NODES);
907 if (aDevices[i].Device)
908 continue;
909 aDevices[i].Device = DevNode;
910 RTStrPrintf(aDevices[i].szPath, sizeof(aDevices[i].szPath),
911 "%s", pszPath);
912 AssertCompile(sizeof(aDevices[i].szDesc) == sizeof(szDesc));
913 strcpy(aDevices[i].szDesc, szDesc);
914 AssertCompile(sizeof(aDevices[i].szUdi) == sizeof(szUdi));
915 strcpy(aDevices[i].szUdi, szUdi);
916 if (i == MAX_DEVICE_NODES - 1)
917 break;
918 continue;
919
920 /* Recurse into subdirectories. */
921 if ( (Entry.enmType == RTDIRENTRYTYPE_UNKNOWN)
922 && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
923 continue;
924 if (Entry.enmType != RTDIRENTRYTYPE_DIRECTORY)
925 continue;
926 if (Entry.szName[0] == '.')
927 continue;
928
929 if (devFindDeviceRecursive(pszPath, cchPath, aDevices, wantDVD))
930 break;
931 }
932 RTDirClose(pDir);
933 return aDevices[MAX_DEVICE_NODES - 1].Device ? true : false;
934}
935
936
937/**
938 * Recursively walk through the /dev tree and add any DVD or floppy drives we
939 * find and can access to our list. (If we can't access them we can't check
940 * whether or not they are really DVD or floppy drives).
941 * @note this is rather slow (a couple of seconds) for DVD probing on
942 * systems with a static /dev tree, as the current code tries to open
943 * any device node with a major/minor combination that could belong to
944 * a CD-ROM device, and opening a non-existent device can take a non.
945 * negligeable time on Linux. If it is ever necessary to improve this
946 * (static /dev trees are no longer very fashionable these days, and
947 * sysfs looks like it will be with us for a while), we could further
948 * reduce the number of device nodes we open by checking whether the
949 * driver is actually loaded in /proc/devices, and by counting the
950 * of currently attached SCSI CD-ROM devices in /proc/scsi/scsi (yes,
951 * there is a race, but it is probably not important for us).
952 * @returns iprt status code
953 * @param pList the list to append the drives found to
954 * @param isDVD are we looking for DVD drives or for floppies?
955 * @param pfSuccess this will be set to true if we found at least one drive
956 * and to false otherwise. Optional.
957 */
958/* static */
959int getDriveInfoFromDev(DriveInfoList *pList, bool isDVD, bool *pfSuccess)
960{
961 AssertPtrReturn(pList, VERR_INVALID_POINTER);
962 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
963 LogFlowFunc(("pList=%p, isDVD=%d, pfSuccess=%p\n", pList, isDVD,
964 pfSuccess));
965 int rc = VINF_SUCCESS;
966 bool success = false;
967
968 char szPath[RTPATH_MAX] = "/dev";
969 deviceNodeArray aDevices;
970 RT_ZERO(aDevices);
971 devFindDeviceRecursive(szPath, sizeof(szPath), aDevices, isDVD);
972 try
973 {
974 for (unsigned i = 0; i < MAX_DEVICE_NODES; ++i)
975 {
976 if (aDevices[i].Device)
977 {
978 pList->push_back(DriveInfo(aDevices[i].szPath,
979 aDevices[i].szUdi, aDevices[i].szDesc));
980 success = true;
981 }
982 }
983 if (pfSuccess != NULL)
984 *pfSuccess = success;
985 }
986 catch(std::bad_alloc &e)
987 {
988 rc = VERR_NO_MEMORY;
989 }
990 LogFlowFunc (("rc=%Rrc, success=%d\n", rc, success));
991 return rc;
992}
993
994
995int VBoxMainUSBDeviceInfo::UpdateDevices ()
996{
997 LogFlowThisFunc(("entered\n"));
998 int rc = VINF_SUCCESS;
999 bool success = false; /* Have we succeeded in finding anything yet? */
1000 try
1001 {
1002 mDeviceList.clear();
1003#ifdef VBOX_USB_WITH_SYSFS
1004# ifdef VBOX_USB_WITH_INOTIFY
1005 if ( RT_SUCCESS(rc)
1006 && (!success || testing()))
1007 rc = getUSBDeviceInfoFromSysfs(&mDeviceList, &success);
1008# endif
1009#else /* !VBOX_USB_WITH_SYSFS */
1010 NOREF(success);
1011#endif /* !VBOX_USB_WITH_SYSFS */
1012 }
1013 catch(std::bad_alloc &e)
1014 {
1015 rc = VERR_NO_MEMORY;
1016 }
1017 LogFlowThisFunc(("rc=%Rrc\n", rc));
1018 return rc;
1019}
1020
1021class hotplugNullImpl : public VBoxMainHotplugWaiterImpl
1022{
1023public:
1024 hotplugNullImpl (void) {}
1025 virtual ~hotplugNullImpl (void) {}
1026 /** @copydoc VBoxMainHotplugWaiter::Wait */
1027 virtual int Wait (RTMSINTERVAL)
1028 {
1029 return VERR_NOT_SUPPORTED;
1030 }
1031 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1032 virtual void Interrupt (void) {}
1033 virtual int getStatus(void)
1034 {
1035 return VERR_NOT_SUPPORTED;
1036 }
1037
1038};
1039
1040#ifdef VBOX_USB_WITH_SYSFS
1041# ifdef VBOX_USB_WITH_INOTIFY
1042/** Class wrapper around an inotify watch (or a group of them to be precise).
1043 * Inherits from pathHandler so that it can be passed to walkDirectory() to
1044 * easily add all files from a directory. */
1045class inotifyWatch : public pathHandler
1046{
1047 /** Pointer to the inotify_add_watch() glibc function/Linux API */
1048 int (*inotify_add_watch)(int, const char *, uint32_t);
1049 /** The native handle of the inotify fd. */
1050 int mhInotify;
1051 /** Object initialisation status, to save us throwing an exception from
1052 * the constructor if we can't initialise */
1053 int mStatus;
1054
1055 /** Object initialistation */
1056 int initInotify(void);
1057
1058public:
1059 /** Add @a pcszPath to the list of files and directories to be monitored */
1060 virtual bool handle(const char *pcszPath);
1061
1062 inotifyWatch(void) : mhInotify(-1)
1063 {
1064 mStatus = initInotify();
1065 }
1066
1067 ~inotifyWatch(void)
1068 {
1069 if (mhInotify != -1)
1070 {
1071 close(mhInotify);
1072 mhInotify = -1;
1073 }
1074 }
1075
1076 int getStatus(void)
1077 {
1078 return mStatus;
1079 }
1080
1081 int getFD(void)
1082 {
1083 AssertRCReturn(mStatus, -1);
1084 return mhInotify;
1085 }
1086};
1087
1088int inotifyWatch::initInotify(void)
1089{
1090 int (*inotify_init)(void);
1091 int fd, flags;
1092
1093 errno = 0;
1094 *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
1095 if (!inotify_init)
1096 return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
1097 *(void **)(&inotify_add_watch) = dlsym(RTLD_DEFAULT, "inotify_add_watch");
1098 if (!inotify_add_watch)
1099 return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
1100 fd = inotify_init();
1101 if (fd < 0)
1102 {
1103 Assert(errno > 0);
1104 return RTErrConvertFromErrno(errno);
1105 }
1106 Assert(errno == 0);
1107
1108 int rc = VINF_SUCCESS;
1109
1110 flags = fcntl(fd, F_GETFL, NULL);
1111 if ( flags < 0
1112 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
1113 {
1114 Assert(errno > 0);
1115 rc = RTErrConvertFromErrno(errno);
1116 }
1117 if (RT_FAILURE(rc))
1118 close(fd);
1119 else
1120 {
1121 Assert(errno == 0);
1122 mhInotify = fd;
1123 }
1124 return rc;
1125}
1126
1127/** The flags we pass to inotify - modify, create, delete, change permissions
1128 */
1129#define IN_FLAGS 0x306
1130
1131bool inotifyWatch::handle(const char *pcszPath)
1132{
1133 AssertRCReturn(mStatus, false);
1134 errno = 0;
1135 if ( inotify_add_watch(mhInotify, pcszPath, IN_FLAGS) >= 0
1136 || (errno == EACCES))
1137 return true;
1138 /* Other errors listed in the manpage can be treated as fatal */
1139 return false;
1140}
1141
1142# define SYSFS_USB_DEVICE_PATH "/dev/bus/usb"
1143# define SYSFS_WAKEUP_STRING "Wake up!"
1144
1145class hotplugInotifyImpl : public VBoxMainHotplugWaiterImpl
1146{
1147 /** Pipe used to interrupt wait(), the read end. */
1148 int mhWakeupPipeR;
1149 /** Pipe used to interrupt wait(), the write end. */
1150 int mhWakeupPipeW;
1151 /** The inotify watch set */
1152 inotifyWatch mWatches;
1153 /** Flag to mark that the Wait() method is currently being called, and to
1154 * ensure that it isn't called multiple times in parallel. */
1155 volatile uint32_t mfWaiting;
1156 /** iprt result code from object initialisation. Should be AssertReturn-ed
1157 * on at the start of all methods. I went this way because I didn't want
1158 * to deal with exceptions. */
1159 int mStatus;
1160 /** ID values associates with the wakeup pipe and the FAM socket for polling
1161 */
1162 enum
1163 {
1164 RPIPE_ID = 0,
1165 INOTIFY_ID,
1166 MAX_POLLID
1167 };
1168
1169 /** Clean up any resources in use, gracefully skipping over any which have
1170 * not yet been allocated or already cleaned up. Intended to be called
1171 * from the destructor or after a failed initialisation. */
1172 void term(void);
1173
1174 int drainInotify();
1175
1176 /** Read the wakeup string from the wakeup pipe */
1177 int drainWakeupPipe(void);
1178public:
1179 hotplugInotifyImpl(void);
1180 virtual ~hotplugInotifyImpl(void)
1181 {
1182 term();
1183#ifdef DEBUG
1184 /** The first call to term should mark all resources as freed, so this
1185 * should be a semantic no-op. */
1186 term();
1187#endif
1188 }
1189 /** Are sysfs and inotify available on this system? If so we expect that
1190 * this implementation will be usable. */
1191 static bool Available(void)
1192 {
1193 return ( RTDirExists(SYSFS_USB_DEVICE_PATH)
1194 && dlsym(RTLD_DEFAULT, "inotify_init") != NULL);
1195 }
1196
1197 virtual int getStatus(void)
1198 {
1199 return mStatus;
1200 }
1201
1202 /** @copydoc VBoxMainHotplugWaiter::Wait */
1203 virtual int Wait(RTMSINTERVAL);
1204 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1205 virtual void Interrupt(void);
1206};
1207
1208/** Simplified version of RTPipeCreate */
1209static int pipeCreateSimple(int *phPipeRead, int *phPipeWrite)
1210{
1211 AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER);
1212 AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER);
1213
1214 /*
1215 * Create the pipe and set the close-on-exec flag if requested.
1216 */
1217 int aFds[2] = {-1, -1};
1218 if (pipe(aFds))
1219 return RTErrConvertFromErrno(errno);
1220
1221 *phPipeRead = aFds[0];
1222 *phPipeWrite = aFds[1];
1223
1224 /*
1225 * Before we leave, make sure to shut up SIGPIPE.
1226 */
1227 signal(SIGPIPE, SIG_IGN);
1228 return VINF_SUCCESS;
1229}
1230
1231hotplugInotifyImpl::hotplugInotifyImpl(void) :
1232 mhWakeupPipeR(-1), mhWakeupPipeW(-1), mfWaiting(0),
1233 mStatus(VERR_WRONG_ORDER)
1234{
1235# ifdef DEBUG
1236 /* Excercise the code path (term() on a not-fully-initialised object) as
1237 * well as we can. On an uninitialised object this method is a sematic
1238 * no-op. */
1239 term();
1240 /* For now this probing method should only be used if nothing else is
1241 * available */
1242# endif
1243 int rc;
1244 do {
1245 if (RT_FAILURE(rc = mWatches.getStatus()))
1246 break;
1247 mWatches.doHandle(SYSFS_USB_DEVICE_PATH);
1248 if (RT_FAILURE(rc = pipeCreateSimple(&mhWakeupPipeR, &mhWakeupPipeW)))
1249 break;
1250 } while(0);
1251 mStatus = rc;
1252 if (RT_FAILURE(rc))
1253 term();
1254}
1255
1256void hotplugInotifyImpl::term(void)
1257{
1258 /** This would probably be a pending segfault, so die cleanly */
1259 AssertRelease(!mfWaiting);
1260 if (mhWakeupPipeR != -1)
1261 {
1262 close(mhWakeupPipeR);
1263 mhWakeupPipeR = -1;
1264 }
1265 if (mhWakeupPipeW != -1)
1266 {
1267 close(mhWakeupPipeW);
1268 mhWakeupPipeW = -1;
1269 }
1270}
1271
1272int hotplugInotifyImpl::drainInotify()
1273{
1274 char chBuf[RTPATH_MAX + 256]; /* Should always be big enough */
1275 ssize_t cchRead;
1276
1277 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1278 errno = 0;
1279 do {
1280 cchRead = read(mWatches.getFD(), chBuf, sizeof(chBuf));
1281 } while (cchRead > 0);
1282 if (cchRead == 0)
1283 return VINF_SUCCESS;
1284 if (cchRead < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
1285 return VINF_SUCCESS;
1286 Assert(errno > 0);
1287 return RTErrConvertFromErrno(errno);
1288}
1289
1290int hotplugInotifyImpl::drainWakeupPipe(void)
1291{
1292 char szBuf[sizeof(SYSFS_WAKEUP_STRING)];
1293 ssize_t cbRead;
1294
1295 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1296 cbRead = read(mhWakeupPipeR, szBuf, sizeof(szBuf));
1297 Assert(cbRead > 0);
1298 return VINF_SUCCESS;
1299}
1300
1301int hotplugInotifyImpl::Wait(RTMSINTERVAL aMillies)
1302{
1303 int rc;
1304
1305 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1306 bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
1307 AssertReturn(fEntered, VERR_WRONG_ORDER);
1308 do {
1309 struct pollfd pollFD[MAX_POLLID];
1310
1311 if (RT_FAILURE(rc = walkDirectory(SYSFS_USB_DEVICE_PATH, &mWatches,
1312 false)))
1313 break;
1314 pollFD[RPIPE_ID].fd = mhWakeupPipeR;
1315 pollFD[RPIPE_ID].events = POLLIN;
1316 pollFD[INOTIFY_ID].fd = mWatches.getFD();
1317 pollFD[INOTIFY_ID].events = POLLIN | POLLERR | POLLHUP;
1318 errno = 0;
1319 int cPolled = poll(pollFD, RT_ELEMENTS(pollFD), aMillies);
1320 if (cPolled < 0)
1321 {
1322 Assert(errno > 0);
1323 rc = RTErrConvertFromErrno(errno);
1324 }
1325 else if (pollFD[RPIPE_ID].revents)
1326 {
1327 rc = drainWakeupPipe();
1328 if (RT_SUCCESS(rc))
1329 rc = VERR_INTERRUPTED;
1330 break;
1331 }
1332 else if (!(pollFD[INOTIFY_ID].revents))
1333 {
1334 AssertBreakStmt(cPolled == 0, rc = VERR_INTERNAL_ERROR);
1335 rc = VERR_TIMEOUT;
1336 }
1337 Assert(errno == 0 || (RT_FAILURE(rc) && rc != VERR_TIMEOUT));
1338 if (RT_FAILURE(rc))
1339 break;
1340 AssertBreakStmt(cPolled == 1, rc = VERR_INTERNAL_ERROR);
1341 if (RT_FAILURE(rc = drainInotify()))
1342 break;
1343 } while (false);
1344 mfWaiting = 0;
1345 return rc;
1346}
1347
1348void hotplugInotifyImpl::Interrupt(void)
1349{
1350 AssertRCReturnVoid(mStatus);
1351 ssize_t cbWritten = write(mhWakeupPipeW, SYSFS_WAKEUP_STRING,
1352 sizeof(SYSFS_WAKEUP_STRING));
1353 if (cbWritten > 0)
1354 fsync(mhWakeupPipeW);
1355}
1356
1357# endif /* VBOX_USB_WITH_INOTIFY */
1358#endif /* VBOX_USB_WTH_SYSFS */
1359
1360VBoxMainHotplugWaiter::VBoxMainHotplugWaiter(void)
1361{
1362 try
1363 {
1364#ifdef VBOX_USB_WITH_SYSFS
1365# ifdef VBOX_USB_WITH_INOTIFY
1366 if (hotplugInotifyImpl::Available())
1367 {
1368 mImpl = new hotplugInotifyImpl;
1369 return;
1370 }
1371# endif /* VBOX_USB_WITH_INOTIFY */
1372#endif /* VBOX_USB_WITH_SYSFS */
1373 mImpl = new hotplugNullImpl;
1374 }
1375 catch(std::bad_alloc &e)
1376 { }
1377}
1378
1379#ifdef VBOX_USB_WITH_SYSFS
1380# ifdef VBOX_USB_WITH_INOTIFY
1381/**
1382 * Helper function to walk a directory, calling a function object on its files
1383 * @returns iprt status code
1384 * @param pcszPath Directory to walk.
1385 * @param pHandler Handler object which will be invoked on each file
1386 * @param useRealPath Whether to resolve the filename to its real path
1387 * before calling the handler. In this case the target
1388 * must exist.
1389 *
1390 * @returns IPRT status code
1391 */
1392/* static */
1393int walkDirectory(const char *pcszPath, pathHandler *pHandler, bool useRealPath)
1394{
1395 AssertPtrReturn(pcszPath, VERR_INVALID_POINTER);
1396 AssertPtrReturn(pHandler, VERR_INVALID_POINTER);
1397 LogFlowFunc (("pcszPath=%s, pHandler=%p\n", pcszPath, pHandler));
1398 PRTDIR pDir = NULL;
1399 int rc;
1400
1401 rc = RTDirOpen(&pDir, pcszPath);
1402 if (RT_FAILURE(rc))
1403 return rc;
1404 while (RT_SUCCESS(rc))
1405 {
1406 RTDIRENTRY entry;
1407 char szPath[RTPATH_MAX], szAbsPath[RTPATH_MAX];
1408
1409 rc = RTDirRead(pDir, &entry, NULL);
1410 Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
1411 /* We break on "no more files" as well as on "real" errors */
1412 if (RT_FAILURE(rc))
1413 break;
1414 if (entry.szName[0] == '.')
1415 continue;
1416 if (RTStrPrintf(szPath, sizeof(szPath), "%s/%s", pcszPath,
1417 entry.szName) >= sizeof(szPath))
1418 rc = VERR_BUFFER_OVERFLOW;
1419 if (RT_FAILURE(rc))
1420 break;
1421 if (useRealPath)
1422 {
1423 rc = RTPathReal(szPath, szAbsPath, sizeof(szAbsPath));
1424 if (RT_FAILURE(rc))
1425 break; /* The file can vanish if a device is unplugged. */
1426 if (!pHandler->doHandle(szAbsPath))
1427 break;
1428 }
1429 else
1430 if (!pHandler->doHandle(szPath))
1431 break;
1432 }
1433 RTDirClose(pDir);
1434 if (rc == VERR_NO_MORE_FILES)
1435 rc = VINF_SUCCESS;
1436 LogFlow (("rc=%Rrc\n", rc));
1437 return rc;
1438}
1439
1440
1441/**
1442 * Helper function to walk a sysfs directory for extracting information about
1443 * devices.
1444 * @returns iprt status code
1445 * @param pcszPath Sysfs directory to walk. Must exist.
1446 * @param pHandler Handler object which will be invoked on each directory
1447 * entry
1448 *
1449 * @returns IPRT status code
1450 */
1451/* static */
1452int getDeviceInfoFromSysfs(const char *pcszPath, pathHandler *pHandler)
1453{
1454 return walkDirectory(pcszPath, pHandler, true);
1455}
1456
1457
1458#define USBDEVICE_MAJOR 189
1459
1460/** Deduce the bus that a USB device is plugged into from the device node
1461 * number. See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20. */
1462static unsigned usbBusFromDevNum(dev_t devNum)
1463{
1464 AssertReturn(devNum, 0);
1465 AssertReturn(major(devNum) == USBDEVICE_MAJOR, 0);
1466 return (minor(devNum) >> 7) + 1;
1467}
1468
1469
1470/** Deduce the device number of a USB device on the bus from the device node
1471 * number. See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20. */
1472static unsigned usbDeviceFromDevNum(dev_t devNum)
1473{
1474 AssertReturn(devNum, 0);
1475 AssertReturn(major(devNum) == USBDEVICE_MAJOR, 0);
1476 return (minor(devNum) & 127) + 1;
1477}
1478
1479
1480/**
1481 * Tell whether a file in /sys/bus/usb/devices is a device rather than an
1482 * interface. To be used with getDeviceInfoFromSysfs().
1483 */
1484class matchUSBDevice : public pathHandler
1485{
1486 USBDeviceInfoList *mList;
1487public:
1488 matchUSBDevice(USBDeviceInfoList *pList) : mList(pList) {}
1489private:
1490 virtual bool handle(const char *pcszNode)
1491 {
1492 const char *pcszFile = strrchr(pcszNode, '/');
1493 if (strchr(pcszFile, ':'))
1494 return true;
1495 dev_t devnum = RTLinuxSysFsReadDevNumFile("%s/dev", pcszNode);
1496 /* Sanity test of our static helpers */
1497 Assert(usbBusFromDevNum(makedev(USBDEVICE_MAJOR, 517)) == 5);
1498 Assert(usbDeviceFromDevNum(makedev(USBDEVICE_MAJOR, 517)) == 6);
1499 AssertReturn (devnum, true);
1500 char szDevPath[RTPATH_MAX];
1501 ssize_t cchDevPath;
1502 cchDevPath = RTLinuxFindDevicePath(devnum, RTFS_TYPE_DEV_CHAR,
1503 szDevPath, sizeof(szDevPath),
1504 "/dev/bus/usb/%.3d/%.3d",
1505 usbBusFromDevNum(devnum),
1506 usbDeviceFromDevNum(devnum));
1507 if (cchDevPath < 0)
1508 return true;
1509 try
1510 {
1511 mList->push_back(USBDeviceInfo(szDevPath, pcszNode));
1512 }
1513 catch(std::bad_alloc &e)
1514 {
1515 return false;
1516 }
1517 return true;
1518 }
1519};
1520
1521/**
1522 * Tell whether a file in /sys/bus/usb/devices is an interface rather than a
1523 * device. To be used with getDeviceInfoFromSysfs().
1524 */
1525class matchUSBInterface : public pathHandler
1526{
1527 USBDeviceInfo *mInfo;
1528public:
1529 /** This constructor is currently used to unit test the class logic in
1530 * debug builds. Since no access is made to anything outside the class,
1531 * this shouldn't cause any slowdown worth mentioning. */
1532 matchUSBInterface(USBDeviceInfo *pInfo) : mInfo(pInfo)
1533 {
1534 Assert(isAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0",
1535 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
1536 Assert(!isAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1",
1537 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
1538 Assert(!isAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/driver",
1539 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
1540 }
1541private:
1542 /** The logic for testing whether a sysfs address corresponds to an
1543 * interface of a device. Both must be referenced by their canonical
1544 * sysfs paths. This is not tested, as the test requires file-system
1545 * interaction. */
1546 bool isAnInterfaceOf(const char *pcszIface, const char *pcszDev)
1547 {
1548 size_t cchDev = strlen(pcszDev);
1549
1550 AssertPtr(pcszIface);
1551 AssertPtr(pcszDev);
1552 Assert(pcszIface[0] == '/');
1553 Assert(pcszDev[0] == '/');
1554 Assert(pcszDev[cchDev - 1] != '/');
1555 /* If this passes, pcszIface is at least cchDev long */
1556 if (strncmp(pcszIface, pcszDev, cchDev))
1557 return false;
1558 /* If this passes, pcszIface is longer than cchDev */
1559 if (pcszIface[cchDev] != '/')
1560 return false;
1561 /* In sysfs an interface is an immediate subdirectory of the device */
1562 if (strchr(pcszIface + cchDev + 1, '/'))
1563 return false;
1564 /* And it always has a colon in its name */
1565 if (!strchr(pcszIface + cchDev + 1, ':'))
1566 return false;
1567 /* And hopefully we have now elimitated everything else */
1568 return true;
1569 }
1570
1571 virtual bool handle(const char *pcszNode)
1572 {
1573 if (!isAnInterfaceOf(pcszNode, mInfo->mSysfsPath.c_str()))
1574 return true;
1575 try
1576 {
1577 mInfo->mInterfaces.push_back(pcszNode);
1578 }
1579 catch(std::bad_alloc &e)
1580 {
1581 return false;
1582 }
1583 return true;
1584 }
1585};
1586
1587/**
1588 * Helper function to query the sysfs subsystem for information about USB
1589 * devices attached to the system.
1590 * @returns iprt status code
1591 * @param pList where to add information about the drives detected
1592 * @param pfSuccess Did we find anything?
1593 *
1594 * @returns IPRT status code
1595 */
1596static int getUSBDeviceInfoFromSysfs(USBDeviceInfoList *pList,
1597 bool *pfSuccess)
1598{
1599 AssertPtrReturn(pList, VERR_INVALID_POINTER);
1600 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
1601 LogFlowFunc (("pList=%p, pfSuccess=%p\n",
1602 pList, pfSuccess));
1603 size_t cDevices = pList->size();
1604 matchUSBDevice devHandler(pList);
1605 int rc = getDeviceInfoFromSysfs("/sys/bus/usb/devices", &devHandler);
1606 do {
1607 if (RT_FAILURE(rc))
1608 break;
1609 for (USBDeviceInfoList::iterator pInfo = pList->begin();
1610 pInfo != pList->end(); ++pInfo)
1611 {
1612 matchUSBInterface ifaceHandler(&*pInfo);
1613 rc = getDeviceInfoFromSysfs("/sys/bus/usb/devices", &ifaceHandler);
1614 if (RT_FAILURE(rc))
1615 break;
1616 }
1617 } while(0);
1618 if (RT_FAILURE(rc))
1619 /* Clean up again */
1620 while (pList->size() > cDevices)
1621 pList->pop_back();
1622 if (pfSuccess)
1623 *pfSuccess = RT_SUCCESS(rc);
1624 LogFlow (("rc=%Rrc\n", rc));
1625 return rc;
1626}
1627# endif /* VBOX_USB_WITH_INOTIFY */
1628#endif /* VBOX_USB_WITH_SYSFS */
Note: See TracBrowser for help on using the repository browser.

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