VirtualBox

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

Last change on this file since 31647 was 31644, checked in by vboxsync, 14 years ago

Main/HostHardwareLinux: switch the stl vectors to a C implementation

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

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