VirtualBox

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

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

Main/HostHardwareLinux: removed an unused parameter

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