VirtualBox

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

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

Main/USBProxyServiceLinux: move the implementation of the device enumeration code out of the class and into static C-like functions

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