VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp@ 53517

Last change on this file since 53517 was 50783, checked in by vboxsync, 11 years ago

Main and Runtime/Linux: rip out all code for recursively walking /dev, as it is not needed on modern Linux systems and partly visibly broken.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 40.6 KB
Line 
1/* $Id: HostHardwareLinux.cpp 50783 2014-03-14 09:57:47Z 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-2013 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
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 getDriveInfoFromSysfs(DriveInfoList *pList, bool isDVD,
89 bool *pfSuccess);
90
91/** Find the length of a string, ignoring trailing non-ascii or control
92 * characters */
93static size_t strLenStripped(const char *pcsz)
94{
95 size_t cch = 0;
96 for (size_t i = 0; pcsz[i] != '\0'; ++i)
97 if (pcsz[i] > 32 && pcsz[i] < 127)
98 cch = i;
99 return cch + 1;
100}
101
102
103/**
104 * Get the name of a floppy drive according to the Linux floppy driver.
105 * @returns true on success, false if the name was not available (i.e. the
106 * device was not readable, or the file name wasn't a PC floppy
107 * device)
108 * @param pcszNode the path to the device node for the device
109 * @param Number the Linux floppy driver number for the drive. Required.
110 * @param pszName where to store the name retrieved
111 */
112static bool floppyGetName(const char *pcszNode, unsigned Number,
113 floppy_drive_name pszName)
114{
115 AssertPtrReturn(pcszNode, false);
116 AssertPtrReturn(pszName, false);
117 AssertReturn(Number <= 7, false);
118 RTFILE File;
119 int rc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
120 if (RT_SUCCESS(rc))
121 {
122 int rcIoCtl;
123 rc = RTFileIoCtl(File, FDGETDRVTYP, pszName, 0, &rcIoCtl);
124 RTFileClose(File);
125 if (RT_SUCCESS(rc) && rcIoCtl >= 0)
126 return true;
127 }
128 return false;
129}
130
131
132/**
133 * Create a UDI and a description for a floppy drive based on a number and the
134 * driver's name for it. We deliberately return an ugly sequence of
135 * characters as the description rather than an English language string to
136 * avoid translation issues.
137 *
138 * @returns true if we know the device to be valid, false otherwise
139 * @param pcszName the floppy driver name for the device (optional)
140 * @param Number the number of the floppy (0 to 3 on FDC 0, 4 to 7 on
141 * FDC 1)
142 * @param pszDesc where to store the device description (optional)
143 * @param cchDesc the size of the buffer in @a pszDesc
144 * @param pszUdi where to store the device UDI (optional)
145 * @param cchUdi the size of the buffer in @a pszUdi
146 */
147static void floppyCreateDeviceStrings(const floppy_drive_name pcszName,
148 unsigned Number, char *pszDesc,
149 size_t cchDesc, char *pszUdi,
150 size_t cchUdi)
151{
152 AssertPtrNullReturnVoid(pcszName);
153 AssertPtrNullReturnVoid(pszDesc);
154 AssertReturnVoid(!pszDesc || cchDesc > 0);
155 AssertPtrNullReturnVoid(pszUdi);
156 AssertReturnVoid(!pszUdi || cchUdi > 0);
157 AssertReturnVoid(Number <= 7);
158 if (pcszName)
159 {
160 const char *pcszSize;
161 switch(pcszName[0])
162 {
163 case 'd': case 'q': case 'h':
164 pcszSize = "5.25\"";
165 break;
166 case 'D': case 'H': case 'E': case 'u':
167 pcszSize = "3.5\"";
168 break;
169 default:
170 pcszSize = "(unknown)";
171 }
172 if (pszDesc)
173 RTStrPrintf(pszDesc, cchDesc, "%s %s K%s", pcszSize, &pcszName[1],
174 Number > 3 ? ", FDC 2" : "");
175 }
176 else
177 {
178 if (pszDesc)
179 RTStrPrintf(pszDesc, cchDesc, "FDD %d%s", (Number & 4) + 1,
180 Number > 3 ? ", FDC 2" : "");
181 }
182 if (pszUdi)
183 RTStrPrintf(pszUdi, cchUdi,
184 "/org/freedesktop/Hal/devices/platform_floppy_%u_storage",
185 Number);
186}
187
188
189/**
190 * Check whether a device number might correspond to a CD-ROM device according
191 * to Documentation/devices.txt in the Linux kernel source.
192 * @returns true if it might, false otherwise
193 * @param Number the device number (major and minor combination)
194 */
195static bool isCdromDevNum(dev_t Number)
196{
197 int major = major(Number);
198 int minor = minor(Number);
199 if ((major == IDE0_MAJOR) && !(minor & 0x3f))
200 return true;
201 if (major == SCSI_CDROM_MAJOR)
202 return true;
203 if (major == CDU31A_CDROM_MAJOR)
204 return true;
205 if (major == GOLDSTAR_CDROM_MAJOR)
206 return true;
207 if (major == OPTICS_CDROM_MAJOR)
208 return true;
209 if (major == SANYO_CDROM_MAJOR)
210 return true;
211 if (major == MITSUMI_X_CDROM_MAJOR)
212 return true;
213 if ((major == IDE1_MAJOR) && !(minor & 0x3f))
214 return true;
215 if (major == MITSUMI_CDROM_MAJOR)
216 return true;
217 if (major == CDU535_CDROM_MAJOR)
218 return true;
219 if (major == MATSUSHITA_CDROM_MAJOR)
220 return true;
221 if (major == MATSUSHITA_CDROM2_MAJOR)
222 return true;
223 if (major == MATSUSHITA_CDROM3_MAJOR)
224 return true;
225 if (major == MATSUSHITA_CDROM4_MAJOR)
226 return true;
227 if (major == AZTECH_CDROM_MAJOR)
228 return true;
229 if (major == 30 /* CM205_CDROM_MAJOR */) /* no #define for some reason */
230 return true;
231 if (major == CM206_CDROM_MAJOR)
232 return true;
233 if ((major == IDE3_MAJOR) && !(minor & 0x3f))
234 return true;
235 if (major == 46 /* Parallel port ATAPI CD-ROM */) /* no #define */
236 return true;
237 if ((major == IDE4_MAJOR) && !(minor & 0x3f))
238 return true;
239 if ((major == IDE5_MAJOR) && !(minor & 0x3f))
240 return true;
241 if ((major == IDE6_MAJOR) && !(minor & 0x3f))
242 return true;
243 if ((major == IDE7_MAJOR) && !(minor & 0x3f))
244 return true;
245 if ((major == IDE8_MAJOR) && !(minor & 0x3f))
246 return true;
247 if ((major == IDE9_MAJOR) && !(minor & 0x3f))
248 return true;
249 if (major == 113 /* VIOCD_MAJOR */)
250 return true;
251 return false;
252}
253
254
255/**
256 * Send an SCSI INQUIRY command to a device and return selected information.
257 * @returns iprt status code
258 * @returns VERR_TRY_AGAIN if the query failed but might succeed next time
259 * @param pcszNode the full path to the device node
260 * @param pu8Type where to store the SCSI device type on success (optional)
261 * @param pchVendor where to store the vendor id string on success (optional)
262 * @param cchVendor the size of the @a pchVendor buffer
263 * @param pchModel where to store the product id string on success (optional)
264 * @param cchModel the size of the @a pchModel buffer
265 * @note check documentation on the SCSI INQUIRY command and the Linux kernel
266 * SCSI headers included above if you want to understand what is going
267 * on in this method.
268 */
269static int cdromDoInquiry(const char *pcszNode, uint8_t *pu8Type,
270 char *pchVendor, size_t cchVendor, char *pchModel,
271 size_t cchModel)
272{
273 LogRelFlowFunc(("pcszNode=%s, pu8Type=%p, pchVendor=%p, cchVendor=%llu, pchModel=%p, cchModel=%llu\n",
274 pcszNode, pu8Type, pchVendor, cchVendor, pchModel,
275 cchModel));
276 AssertPtrReturn(pcszNode, VERR_INVALID_POINTER);
277 AssertPtrNullReturn(pu8Type, VERR_INVALID_POINTER);
278 AssertPtrNullReturn(pchVendor, VERR_INVALID_POINTER);
279 AssertPtrNullReturn(pchModel, VERR_INVALID_POINTER);
280
281 RTFILE hFile;
282 int rc = RTFileOpen(&hFile, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
283 if (RT_SUCCESS(rc))
284 {
285 int rcIoCtl = 0;
286 unsigned char u8Response[96] = { 0 };
287 struct cdrom_generic_command CdromCommandReq;
288 RT_ZERO(CdromCommandReq);
289 CdromCommandReq.cmd[0] = INQUIRY;
290 CdromCommandReq.cmd[4] = sizeof(u8Response);
291 CdromCommandReq.buffer = u8Response;
292 CdromCommandReq.buflen = sizeof(u8Response);
293 CdromCommandReq.data_direction = CGC_DATA_READ;
294 CdromCommandReq.timeout = 5000; /* ms */
295 rc = RTFileIoCtl(hFile, CDROM_SEND_PACKET, &CdromCommandReq, 0, &rcIoCtl);
296 if (RT_SUCCESS(rc) && rcIoCtl < 0)
297 rc = RTErrConvertFromErrno(-CdromCommandReq.stat);
298 RTFileClose(hFile);
299
300 if (RT_SUCCESS(rc))
301 {
302 if (pu8Type)
303 *pu8Type = u8Response[0] & 0x1f;
304 if (pchVendor)
305 RTStrPrintf(pchVendor, cchVendor, "%.8s",
306 &u8Response[8] /* vendor id string */);
307 if (pchModel)
308 RTStrPrintf(pchModel, cchModel, "%.16s",
309 &u8Response[16] /* product id string */);
310 LogRelFlowFunc(("returning success: type=%u, vendor=%.8s, product=%.16s\n",
311 u8Response[0] & 0x1f, &u8Response[8], &u8Response[16]));
312 return VINF_SUCCESS;
313 }
314 }
315 LogRelFlowFunc(("returning %Rrc\n", rc));
316 return rc;
317}
318
319
320/**
321 * Initialise the device strings (description and UDI) for a DVD drive based on
322 * vendor and model name strings.
323 * @param pcszVendor the vendor ID string
324 * @param pcszModel the product ID string
325 * @param pszDesc where to store the description string (optional)
326 * @param cchDesc the size of the buffer in @pszDesc
327 * @param pszUdi where to store the UDI string (optional)
328 * @param cchUdi the size of the buffer in @pszUdi
329 */
330/* static */
331void dvdCreateDeviceStrings(const char *pcszVendor, const char *pcszModel,
332 char *pszDesc, size_t cchDesc, char *pszUdi,
333 size_t cchUdi)
334{
335 AssertPtrReturnVoid(pcszVendor);
336 AssertPtrReturnVoid(pcszModel);
337 AssertPtrNullReturnVoid(pszDesc);
338 AssertReturnVoid(!pszDesc || cchDesc > 0);
339 AssertPtrNullReturnVoid(pszUdi);
340 AssertReturnVoid(!pszUdi || cchUdi > 0);
341 char szCleaned[128];
342 size_t cchVendor = strLenStripped(pcszVendor);
343 size_t cchModel = strLenStripped(pcszModel);
344
345 /* Create a cleaned version of the model string for the UDI string. */
346 for (unsigned i = 0; i < sizeof(szCleaned) && pcszModel[i] != '\0'; ++i)
347 if ( (pcszModel[i] >= '0' && pcszModel[i] <= '9')
348 || (pcszModel[i] >= 'A' && pcszModel[i] <= 'z'))
349 szCleaned[i] = pcszModel[i];
350 else
351 szCleaned[i] = '_';
352 szCleaned[RT_MIN(cchModel, sizeof(szCleaned) - 1)] = '\0';
353
354 /* Construct the description string as "Vendor Product" */
355 if (pszDesc)
356 {
357 if (cchVendor > 0)
358 RTStrPrintf(pszDesc, cchDesc, "%.*s %s", cchVendor, pcszVendor,
359 cchModel > 0 ? pcszModel : "(unknown drive model)");
360 else
361 RTStrPrintf(pszDesc, cchDesc, "%s", pcszModel);
362 }
363 /* Construct the UDI string */
364 if (pszUdi)
365 {
366 if (cchModel > 0)
367 RTStrPrintf(pszUdi, cchUdi,
368 "/org/freedesktop/Hal/devices/storage_model_%s",
369 szCleaned);
370 else
371 pszUdi[0] = '\0';
372 }
373}
374
375
376/**
377 * Check whether a device node points to a valid device and create a UDI and
378 * a description for it, and store the device number, if it does.
379 * @returns true if the device is valid, false otherwise
380 * @param pcszNode the path to the device node
381 * @param isDVD are we looking for a DVD device (or a floppy device)?
382 * @param pDevice where to store the device node (optional)
383 * @param pszDesc where to store the device description (optional)
384 * @param cchDesc the size of the buffer in @a pszDesc
385 * @param pszUdi where to store the device UDI (optional)
386 * @param cchUdi the size of the buffer in @a pszUdi
387 */
388static bool devValidateDevice(const char *pcszNode, bool isDVD, dev_t *pDevice,
389 char *pszDesc, size_t cchDesc, char *pszUdi,
390 size_t cchUdi)
391{
392 AssertPtrReturn(pcszNode, false);
393 AssertPtrNullReturn(pDevice, false);
394 AssertPtrNullReturn(pszDesc, false);
395 AssertReturn(!pszDesc || cchDesc > 0, false);
396 AssertPtrNullReturn(pszUdi, false);
397 AssertReturn(!pszUdi || cchUdi > 0, false);
398 RTFSOBJINFO ObjInfo;
399 if (RT_FAILURE(RTPathQueryInfo(pcszNode, &ObjInfo, RTFSOBJATTRADD_UNIX)))
400 return false;
401 if (!RTFS_IS_DEV_BLOCK(ObjInfo.Attr.fMode))
402 return false;
403 if (pDevice)
404 *pDevice = ObjInfo.Attr.u.Unix.Device;
405 if (isDVD)
406 {
407 char szVendor[128], szModel[128];
408 uint8_t u8Type;
409 if (!isCdromDevNum(ObjInfo.Attr.u.Unix.Device))
410 return false;
411 if (RT_FAILURE(cdromDoInquiry(pcszNode, &u8Type,
412 szVendor, sizeof(szVendor),
413 szModel, sizeof(szModel))))
414 return false;
415 if (u8Type != TYPE_ROM)
416 return false;
417 dvdCreateDeviceStrings(szVendor, szModel, pszDesc, cchDesc,
418 pszUdi, cchUdi);
419 }
420 else
421 {
422 /* Floppies on Linux are legacy devices with hardcoded majors and
423 * minors */
424 unsigned Number;
425 floppy_drive_name szName;
426 if (major(ObjInfo.Attr.u.Unix.Device) != FLOPPY_MAJOR)
427 return false;
428 switch (minor(ObjInfo.Attr.u.Unix.Device))
429 {
430 case 0: case 1: case 2: case 3:
431 Number = minor(ObjInfo.Attr.u.Unix.Device);
432 break;
433 case 128: case 129: case 130: case 131:
434 Number = minor(ObjInfo.Attr.u.Unix.Device) - 128 + 4;
435 break;
436 default:
437 return false;
438 }
439 if (!floppyGetName(pcszNode, Number, szName))
440 return false;
441 floppyCreateDeviceStrings(szName, Number, pszDesc, cchDesc, pszUdi,
442 cchUdi);
443 }
444 return true;
445}
446
447
448int VBoxMainDriveInfo::updateDVDs ()
449{
450 LogFlowThisFunc(("entered\n"));
451 int rc = VINF_SUCCESS;
452 bool success = false; /* Have we succeeded in finding anything yet? */
453 try
454 {
455 mDVDList.clear ();
456 /* Always allow the user to override our auto-detection using an
457 * environment variable. */
458 if (RT_SUCCESS(rc) && (!success || testing()))
459 rc = getDriveInfoFromEnv ("VBOX_CDROM", &mDVDList, true /* isDVD */,
460 &success);
461 setNoProbe(false);
462 if (RT_SUCCESS(rc) && (!success | testing()))
463 rc = getDriveInfoFromSysfs(&mDVDList, true /* isDVD */, &success);
464 if (RT_SUCCESS(rc) && testing())
465 {
466 setNoProbe(true);
467 rc = getDriveInfoFromSysfs(&mDVDList, true /* isDVD */, &success);
468 }
469 }
470 catch(std::bad_alloc &e)
471 {
472 rc = VERR_NO_MEMORY;
473 }
474 LogFlowThisFunc(("rc=%Rrc\n", rc));
475 return rc;
476}
477
478int VBoxMainDriveInfo::updateFloppies ()
479{
480 LogFlowThisFunc(("entered\n"));
481 int rc = VINF_SUCCESS;
482 bool success = false; /* Have we succeeded in finding anything yet? */
483 try
484 {
485 mFloppyList.clear ();
486 if (RT_SUCCESS(rc) && (!success || testing()))
487 rc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList,
488 false /* isDVD */, &success);
489 setNoProbe(false);
490 if ( RT_SUCCESS(rc) && (!success || testing()))
491 rc = getDriveInfoFromSysfs(&mFloppyList, false /* isDVD */,
492 &success);
493 if (RT_SUCCESS(rc) && testing())
494 {
495 setNoProbe(true);
496 rc = getDriveInfoFromSysfs(&mFloppyList, false /* isDVD */, &success);
497 }
498 }
499 catch(std::bad_alloc &e)
500 {
501 rc = VERR_NO_MEMORY;
502 }
503 LogFlowThisFunc(("rc=%Rrc\n", rc));
504 return rc;
505}
506
507
508/**
509 * Extract the names of drives from an environment variable and add them to a
510 * list if they are valid.
511 * @returns iprt status code
512 * @param pcszVar the name of the environment variable. The variable
513 * value should be a list of device node names, separated
514 * by ':' characters.
515 * @param pList the list to append the drives found to
516 * @param isDVD are we looking for DVD drives or for floppies?
517 * @param pfSuccess this will be set to true if we found at least one drive
518 * and to false otherwise. Optional.
519 */
520/* static */
521int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList,
522 bool isDVD, bool *pfSuccess)
523{
524 AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
525 AssertPtrReturn(pList, VERR_INVALID_POINTER);
526 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
527 LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar,
528 pList, isDVD, pfSuccess));
529 int rc = VINF_SUCCESS;
530 bool success = false;
531 char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
532
533 try
534 {
535 const char *pcszCurrent = pszFreeMe;
536 while (pcszCurrent && *pcszCurrent != '\0')
537 {
538 const char *pcszNext = strchr(pcszCurrent, ':');
539 char szPath[RTPATH_MAX], szReal[RTPATH_MAX];
540 char szDesc[256], szUdi[256];
541 if (pcszNext)
542 RTStrPrintf(szPath, sizeof(szPath), "%.*s",
543 pcszNext - pcszCurrent - 1, pcszCurrent);
544 else
545 RTStrPrintf(szPath, sizeof(szPath), "%s", pcszCurrent);
546 if ( RT_SUCCESS(RTPathReal(szPath, szReal, sizeof(szReal)))
547 && devValidateDevice(szReal, isDVD, NULL, szDesc,
548 sizeof(szDesc), szUdi, sizeof(szUdi)))
549 {
550 pList->push_back(DriveInfo(szReal, szUdi, szDesc));
551 success = true;
552 }
553 pcszCurrent = pcszNext ? pcszNext + 1 : NULL;
554 }
555 if (pfSuccess != NULL)
556 *pfSuccess = success;
557 }
558 catch(std::bad_alloc &e)
559 {
560 rc = VERR_NO_MEMORY;
561 }
562 RTStrFree(pszFreeMe);
563 LogFlowFunc(("rc=%Rrc, success=%d\n", rc, success));
564 return rc;
565}
566
567
568class sysfsBlockDev
569{
570public:
571 sysfsBlockDev(const char *pcszName, bool wantDVD)
572 : mpcszName(pcszName), mwantDVD(wantDVD), misConsistent(true),
573 misValid(false)
574 {
575 if (findDeviceNode())
576 {
577 if (mwantDVD)
578 validateAndInitForDVD();
579 else
580 validateAndInitForFloppy();
581 }
582 }
583private:
584 /** The name of the subdirectory of /sys/block for this device */
585 const char *mpcszName;
586 /** Are we looking for a floppy or a DVD device? */
587 bool mwantDVD;
588 /** The device node for the device */
589 char mszNode[RTPATH_MAX];
590 /** Does the sysfs entry look like we expect it too? This is a canary
591 * for future sysfs ABI changes. */
592 bool misConsistent;
593 /** Is this entry a valid specimen of what we are looking for? */
594 bool misValid;
595 /** Human readable drive description string */
596 char mszDesc[256];
597 /** Unique identifier for the drive. Should be identical to hal's UDI for
598 * the device. May not be unique for two identical drives. */
599 char mszUdi[256];
600private:
601 /* Private methods */
602
603 /**
604 * Fill in the device node member based on the /sys/block subdirectory.
605 * @returns boolean success value
606 */
607 bool findDeviceNode()
608 {
609 dev_t dev = RTLinuxSysFsReadDevNumFile("block/%s/dev", mpcszName);
610 if (dev == 0)
611 {
612 misConsistent = false;
613 return false;
614 }
615 if (RTLinuxCheckDevicePath(dev, RTFS_TYPE_DEV_BLOCK, mszNode,
616 sizeof(mszNode), "%s", mpcszName) < 0)
617 return false;
618 return true;
619 }
620
621 /** Check whether the sysfs block entry is valid for a DVD device and
622 * initialise the string data members for the object. We try to get all
623 * the information we need from sysfs if possible, to avoid unnecessarily
624 * poking the device, and if that fails we fall back to an SCSI INQUIRY
625 * command. */
626 void validateAndInitForDVD()
627 {
628 char szVendor[128], szModel[128];
629 ssize_t cchVendor, cchModel;
630 int64_t type = RTLinuxSysFsReadIntFile(10, "block/%s/device/type",
631 mpcszName);
632 if (type >= 0 && type != TYPE_ROM)
633 return;
634 if (type == TYPE_ROM)
635 {
636 cchVendor = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor),
637 "block/%s/device/vendor",
638 mpcszName);
639 if (cchVendor >= 0)
640 {
641 cchModel = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel),
642 "block/%s/device/model",
643 mpcszName);
644 if (cchModel >= 0)
645 {
646 misValid = true;
647 dvdCreateDeviceStrings(szVendor, szModel,
648 mszDesc, sizeof(mszDesc),
649 mszUdi, sizeof(mszUdi));
650 return;
651 }
652 }
653 }
654 if (!noProbe())
655 probeAndInitForDVD();
656 }
657
658 /** Try to find out whether a device is a DVD drive by sending it an
659 * SCSI INQUIRY command. If it is, initialise the string and validity
660 * data members for the object based on the returned data.
661 */
662 void probeAndInitForDVD()
663 {
664 AssertReturnVoid(mszNode[0] != '\0');
665 uint8_t u8Type = 0;
666 char szVendor[128] = "";
667 char szModel[128] = "";
668 int rc = cdromDoInquiry(mszNode, &u8Type, szVendor,
669 sizeof(szVendor), szModel,
670 sizeof(szModel));
671 if (RT_SUCCESS(rc) && (u8Type == TYPE_ROM))
672 {
673 misValid = true;
674 dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc),
675 mszUdi, sizeof(mszUdi));
676 }
677 }
678
679 /** Check whether the sysfs block entry is valid for a floppy device and
680 * initialise the string data members for the object. Since we only
681 * support floppies using the basic "floppy" driver, we check the driver
682 * using the entry name and a driver-specific ioctl. */
683 void validateAndInitForFloppy()
684 {
685 bool haveName = false;
686 floppy_drive_name szName;
687 char szDriver[8];
688 if ( mpcszName[0] != 'f'
689 || mpcszName[1] != 'd'
690 || mpcszName[2] < '0'
691 || mpcszName[2] > '7'
692 || mpcszName[3] != '\0')
693 return;
694 if (!noProbe())
695 haveName = floppyGetName(mszNode, mpcszName[2] - '0', szName);
696 if (RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), "block/%s/%s",
697 mpcszName, "device/driver") >= 0)
698 {
699 if (RTStrCmp(szDriver, "floppy"))
700 return;
701 }
702 else if (!haveName)
703 return;
704 floppyCreateDeviceStrings(haveName ? szName : NULL,
705 mpcszName[2] - '0', mszDesc,
706 sizeof(mszDesc), mszUdi, sizeof(mszUdi));
707 misValid = true;
708 }
709
710public:
711 bool isConsistent()
712 {
713 return misConsistent;
714 }
715 bool isValid()
716 {
717 return misValid;
718 }
719 const char *getDesc()
720 {
721 return mszDesc;
722 }
723 const char *getUdi()
724 {
725 return mszUdi;
726 }
727 const char *getNode()
728 {
729 return mszNode;
730 }
731};
732
733/**
734 * Helper function to query the sysfs subsystem for information about DVD
735 * drives attached to the system.
736 * @returns iprt status code
737 * @param pList where to add information about the drives detected
738 * @param isDVD are we looking for DVDs or floppies?
739 * @param pfSuccess Did we find anything?
740 *
741 * @returns IPRT status code
742 */
743/* static */
744int getDriveInfoFromSysfs(DriveInfoList *pList, bool isDVD, bool *pfSuccess)
745{
746 AssertPtrReturn(pList, VERR_INVALID_POINTER);
747 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
748 LogFlowFunc (("pList=%p, isDVD=%u, pfSuccess=%p\n",
749 pList, (unsigned) isDVD, pfSuccess));
750 PRTDIR pDir = NULL;
751 int rc;
752 bool fSuccess = false;
753 unsigned cFound = 0;
754
755 if (!RTPathExists("/sys"))
756 return VINF_SUCCESS;
757 rc = RTDirOpen(&pDir, "/sys/block");
758 /* This might mean that sysfs semantics have changed */
759 AssertReturn(rc != VERR_FILE_NOT_FOUND, VINF_SUCCESS);
760 fSuccess = true;
761 if (RT_SUCCESS(rc))
762 for (;;)
763 {
764 RTDIRENTRY entry;
765 rc = RTDirRead(pDir, &entry, NULL);
766 Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
767 if (RT_FAILURE(rc)) /* Including overflow and no more files */
768 break;
769 if (entry.szName[0] == '.')
770 continue;
771 sysfsBlockDev dev(entry.szName, isDVD);
772 /* This might mean that sysfs semantics have changed */
773 AssertBreakStmt(dev.isConsistent(), fSuccess = false);
774 if (!dev.isValid())
775 continue;
776 try
777 {
778 pList->push_back(DriveInfo(dev.getNode(), dev.getUdi(),
779 dev.getDesc()));
780 }
781 catch(std::bad_alloc &e)
782 {
783 rc = VERR_NO_MEMORY;
784 break;
785 }
786 ++cFound;
787 }
788 RTDirClose(pDir);
789 if (rc == VERR_NO_MORE_FILES)
790 rc = VINF_SUCCESS;
791 if (RT_FAILURE(rc))
792 /* Clean up again */
793 for (unsigned i = 0; i < cFound; ++i)
794 pList->pop_back();
795 if (pfSuccess)
796 *pfSuccess = fSuccess;
797 LogFlow (("rc=%Rrc, fSuccess=%u\n", rc, (unsigned) fSuccess));
798 return rc;
799}
800
801
802/** Helper for readFilePathsFromDir(). Adds a path to the vector if it is not
803 * NULL and not a dotfile (".", "..", ".*"). */
804static int maybeAddPathToVector(const char *pcszPath, const char *pcszEntry,
805 VECTOR_PTR(char *) *pvecpchDevs)
806{
807 char *pszPath;
808
809 if (!pcszPath)
810 return 0;
811 if (pcszEntry[0] == '.')
812 return 0;
813 pszPath = RTStrDup(pcszPath);
814 if (!pszPath)
815 return ENOMEM;
816 if (RT_FAILURE(VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPath)))
817 return ENOMEM;
818 return 0;
819}
820
821/** Helper for readFilePaths(). Adds the entries from the open directory
822 * @a pDir to the vector @a pvecpchDevs using either the full path or the
823 * realpath() and skipping hidden files and files on which realpath() fails. */
824static int readFilePathsFromDir(const char *pcszPath, DIR *pDir,
825 VECTOR_PTR(char *) *pvecpchDevs, int withRealPath)
826{
827 struct dirent entry, *pResult;
828 int err;
829
830 for (err = readdir_r(pDir, &entry, &pResult); pResult;
831 err = readdir_r(pDir, &entry, &pResult))
832 {
833 /* We (implicitly) require that PATH_MAX be defined */
834 char szPath[PATH_MAX + 1], szRealPath[PATH_MAX + 1], *pszPath;
835 if (snprintf(szPath, sizeof(szPath), "%s/%s", pcszPath,
836 entry.d_name) < 0)
837 return errno;
838 if (withRealPath)
839 pszPath = realpath(szPath, szRealPath);
840 else
841 pszPath = szPath;
842 if ((err = maybeAddPathToVector(pszPath, entry.d_name, pvecpchDevs)))
843 return err;
844 }
845 return err;
846}
847
848
849/**
850 * Helper for walkDirectory to dump the names of a directory's entries into a
851 * vector of char pointers.
852 *
853 * @returns zero on success or (positive) posix error value.
854 * @param pcszPath the path to dump.
855 * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
856 * by the caller even on failure.
857 * @param withRealPath whether to canonicalise the filename with realpath
858 */
859static int readFilePaths(const char *pcszPath, VECTOR_PTR(char *) *pvecpchDevs,
860 int withRealPath)
861{
862 DIR *pDir;
863 int err;
864
865 AssertPtrReturn(pvecpchDevs, EINVAL);
866 AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
867 AssertPtrReturn(pcszPath, EINVAL);
868
869 pDir = opendir(pcszPath);
870 if (!pDir)
871 return RTErrConvertFromErrno(errno);
872 err = readFilePathsFromDir(pcszPath, pDir, pvecpchDevs, withRealPath);
873 if (closedir(pDir) < 0 && !err)
874 err = errno;
875 return RTErrConvertFromErrno(err);
876}
877
878
879class hotplugNullImpl : public VBoxMainHotplugWaiterImpl
880{
881public:
882 hotplugNullImpl(const char *) {}
883 virtual ~hotplugNullImpl (void) {}
884 /** @copydoc VBoxMainHotplugWaiter::Wait */
885 virtual int Wait (RTMSINTERVAL)
886 {
887 return VERR_NOT_SUPPORTED;
888 }
889 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
890 virtual void Interrupt (void) {}
891 virtual int getStatus(void)
892 {
893 return VERR_NOT_SUPPORTED;
894 }
895
896};
897
898#ifdef VBOX_USB_WITH_SYSFS
899# ifdef VBOX_USB_WITH_INOTIFY
900/** Class wrapper around an inotify watch (or a group of them to be precise).
901 */
902typedef struct inotifyWatch
903{
904 /** Pointer to the inotify_add_watch() glibc function/Linux API */
905 int (*inotify_add_watch)(int, const char *, uint32_t);
906 /** The native handle of the inotify fd. */
907 int mhInotify;
908} inotifyWatch;
909
910/** The flags we pass to inotify - modify, create, delete, change permissions
911 */
912#define IN_FLAGS 0x306
913
914static int iwAddWatch(inotifyWatch *pSelf, const char *pcszPath)
915{
916 errno = 0;
917 if ( pSelf->inotify_add_watch(pSelf->mhInotify, pcszPath, IN_FLAGS) >= 0
918 || (errno == EACCES))
919 return VINF_SUCCESS;
920 /* Other errors listed in the manpage can be treated as fatal */
921 return RTErrConvertFromErrno(errno);
922}
923
924/** Object initialisation */
925static int iwInit(inotifyWatch *pSelf)
926{
927 int (*inotify_init)(void);
928 int fd, flags;
929 int rc = VINF_SUCCESS;
930
931 AssertPtr(pSelf);
932 pSelf->mhInotify = -1;
933 errno = 0;
934 *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
935 if (!inotify_init)
936 return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
937 *(void **)(&pSelf->inotify_add_watch)
938 = dlsym(RTLD_DEFAULT, "inotify_add_watch");
939 if (!pSelf->inotify_add_watch)
940 return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
941 fd = inotify_init();
942 if (fd < 0)
943 {
944 Assert(errno > 0);
945 return RTErrConvertFromErrno(errno);
946 }
947 Assert(errno == 0);
948
949 flags = fcntl(fd, F_GETFL, NULL);
950 if ( flags < 0
951 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0
952 || fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 /* race here */)
953 {
954 Assert(errno > 0);
955 rc = RTErrConvertFromErrno(errno);
956 }
957 if (RT_FAILURE(rc))
958 close(fd);
959 else
960 {
961 Assert(errno == 0);
962 pSelf->mhInotify = fd;
963 }
964 return rc;
965}
966
967static void iwTerm(inotifyWatch *pSelf)
968{
969 AssertPtrReturnVoid(pSelf);
970 if (pSelf->mhInotify != -1)
971 {
972 close(pSelf->mhInotify);
973 pSelf->mhInotify = -1;
974 }
975}
976
977static int iwGetFD(inotifyWatch *pSelf)
978{
979 AssertPtrReturn(pSelf, -1);
980 return pSelf->mhInotify;
981}
982
983# define SYSFS_WAKEUP_STRING "Wake up!"
984
985class hotplugInotifyImpl : public VBoxMainHotplugWaiterImpl
986{
987 /** Pipe used to interrupt wait(), the read end. */
988 int mhWakeupPipeR;
989 /** Pipe used to interrupt wait(), the write end. */
990 int mhWakeupPipeW;
991 /** The inotify watch set */
992 inotifyWatch mWatches;
993 /** Flag to mark that the Wait() method is currently being called, and to
994 * ensure that it isn't called multiple times in parallel. */
995 volatile uint32_t mfWaiting;
996 /** The root of the USB devices tree. */
997 const char *mpcszDevicesRoot;
998 /** iprt result code from object initialisation. Should be AssertReturn-ed
999 * on at the start of all methods. I went this way because I didn't want
1000 * to deal with exceptions. */
1001 int mStatus;
1002 /** ID values associates with the wakeup pipe and the FAM socket for polling
1003 */
1004 enum
1005 {
1006 RPIPE_ID = 0,
1007 INOTIFY_ID,
1008 MAX_POLLID
1009 };
1010
1011 /** Clean up any resources in use, gracefully skipping over any which have
1012 * not yet been allocated or already cleaned up. Intended to be called
1013 * from the destructor or after a failed initialisation. */
1014 void term(void);
1015
1016 int drainInotify();
1017
1018 /** Read the wakeup string from the wakeup pipe */
1019 int drainWakeupPipe(void);
1020public:
1021 hotplugInotifyImpl(const char *pcszDevicesRoot);
1022 virtual ~hotplugInotifyImpl(void)
1023 {
1024 term();
1025#ifdef DEBUG
1026 /** The first call to term should mark all resources as freed, so this
1027 * should be a semantic no-op. */
1028 term();
1029#endif
1030 }
1031 /** Is inotify available and working on this system? If so we expect that
1032 * this implementation will be usable. */
1033 /** @todo test the "inotify in glibc but not in the kernel" case. */
1034 static bool Available(void)
1035 {
1036 int (*inotify_init)(void);
1037
1038 *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
1039 if (!inotify_init)
1040 return false;
1041 int fd = inotify_init();
1042 if (fd == -1)
1043 return false;
1044 close(fd);
1045 return true;
1046 }
1047
1048 virtual int getStatus(void)
1049 {
1050 return mStatus;
1051 }
1052
1053 /** @copydoc VBoxMainHotplugWaiter::Wait */
1054 virtual int Wait(RTMSINTERVAL);
1055 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1056 virtual void Interrupt(void);
1057};
1058
1059/** Simplified version of RTPipeCreate */
1060static int pipeCreateSimple(int *phPipeRead, int *phPipeWrite)
1061{
1062 AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER);
1063 AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER);
1064
1065 /*
1066 * Create the pipe and set the close-on-exec flag.
1067 */
1068 int aFds[2] = {-1, -1};
1069 if (pipe(aFds))
1070 return RTErrConvertFromErrno(errno);
1071 if ( fcntl(aFds[0], F_SETFD, FD_CLOEXEC) < 0
1072 || fcntl(aFds[1], F_SETFD, FD_CLOEXEC) < 0)
1073 {
1074 int rc = RTErrConvertFromErrno(errno);
1075 close(aFds[0]);
1076 close(aFds[1]);
1077 return rc;
1078 }
1079
1080 *phPipeRead = aFds[0];
1081 *phPipeWrite = aFds[1];
1082
1083 /*
1084 * Before we leave, make sure to shut up SIGPIPE.
1085 */
1086 signal(SIGPIPE, SIG_IGN);
1087 return VINF_SUCCESS;
1088}
1089
1090hotplugInotifyImpl::hotplugInotifyImpl(const char *pcszDevicesRoot) :
1091 mhWakeupPipeR(-1), mhWakeupPipeW(-1), mfWaiting(0),
1092 mpcszDevicesRoot(pcszDevicesRoot), mStatus(VERR_WRONG_ORDER)
1093{
1094# ifdef DEBUG
1095 /* Excercise the code path (term() on a not-fully-initialised object) as
1096 * well as we can. On an uninitialised object this method is a semantic
1097 * no-op. */
1098 mWatches.mhInotify = -1; /* term will access this variable */
1099 term();
1100 /* For now this probing method should only be used if nothing else is
1101 * available */
1102# endif
1103 int rc;
1104 do {
1105 if (RT_FAILURE(rc = iwInit(&mWatches)))
1106 break;
1107 if (RT_FAILURE(rc = iwAddWatch(&mWatches, mpcszDevicesRoot)))
1108 break;
1109 if (RT_FAILURE(rc = pipeCreateSimple(&mhWakeupPipeR, &mhWakeupPipeW)))
1110 break;
1111 } while (0);
1112 mStatus = rc;
1113 if (RT_FAILURE(rc))
1114 term();
1115}
1116
1117void hotplugInotifyImpl::term(void)
1118{
1119 /** This would probably be a pending segfault, so die cleanly */
1120 AssertRelease(!mfWaiting);
1121 if (mhWakeupPipeR != -1)
1122 {
1123 close(mhWakeupPipeR);
1124 mhWakeupPipeR = -1;
1125 }
1126 if (mhWakeupPipeW != -1)
1127 {
1128 close(mhWakeupPipeW);
1129 mhWakeupPipeW = -1;
1130 }
1131 iwTerm(&mWatches);
1132}
1133
1134int hotplugInotifyImpl::drainInotify()
1135{
1136 char chBuf[RTPATH_MAX + 256]; /* Should always be big enough */
1137 ssize_t cchRead;
1138
1139 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1140 errno = 0;
1141 do {
1142 cchRead = read(iwGetFD(&mWatches), chBuf, sizeof(chBuf));
1143 } while (cchRead > 0);
1144 if (cchRead == 0)
1145 return VINF_SUCCESS;
1146 if (cchRead < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
1147 return VINF_SUCCESS;
1148 Assert(errno > 0);
1149 return RTErrConvertFromErrno(errno);
1150}
1151
1152int hotplugInotifyImpl::drainWakeupPipe(void)
1153{
1154 char szBuf[sizeof(SYSFS_WAKEUP_STRING)];
1155 ssize_t cbRead;
1156
1157 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1158 cbRead = read(mhWakeupPipeR, szBuf, sizeof(szBuf));
1159 Assert(cbRead > 0);
1160 NOREF(cbRead);
1161 return VINF_SUCCESS;
1162}
1163
1164int hotplugInotifyImpl::Wait(RTMSINTERVAL aMillies)
1165{
1166 int rc;
1167 char **ppszEntry;
1168 VECTOR_PTR(char *) vecpchDevs;
1169
1170 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1171 bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
1172 AssertReturn(fEntered, VERR_WRONG_ORDER);
1173 VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
1174 do {
1175 struct pollfd pollFD[MAX_POLLID];
1176
1177 rc = readFilePaths(mpcszDevicesRoot, &vecpchDevs, false);
1178 if (RT_SUCCESS(rc))
1179 VEC_FOR_EACH(&vecpchDevs, char *, ppszEntry)
1180 if (RT_FAILURE(rc = iwAddWatch(&mWatches, *ppszEntry)))
1181 break;
1182 if (RT_FAILURE(rc))
1183 break;
1184 pollFD[RPIPE_ID].fd = mhWakeupPipeR;
1185 pollFD[RPIPE_ID].events = POLLIN;
1186 pollFD[INOTIFY_ID].fd = iwGetFD(&mWatches);
1187 pollFD[INOTIFY_ID].events = POLLIN | POLLERR | POLLHUP;
1188 errno = 0;
1189 int cPolled = poll(pollFD, RT_ELEMENTS(pollFD), aMillies);
1190 if (cPolled < 0)
1191 {
1192 Assert(errno > 0);
1193 rc = RTErrConvertFromErrno(errno);
1194 }
1195 else if (pollFD[RPIPE_ID].revents)
1196 {
1197 rc = drainWakeupPipe();
1198 if (RT_SUCCESS(rc))
1199 rc = VERR_INTERRUPTED;
1200 break;
1201 }
1202 else if (!(pollFD[INOTIFY_ID].revents))
1203 {
1204 AssertBreakStmt(cPolled == 0, rc = VERR_INTERNAL_ERROR);
1205 rc = VERR_TIMEOUT;
1206 }
1207 Assert(errno == 0 || (RT_FAILURE(rc) && rc != VERR_TIMEOUT));
1208 if (RT_FAILURE(rc))
1209 break;
1210 AssertBreakStmt(cPolled == 1, rc = VERR_INTERNAL_ERROR);
1211 if (RT_FAILURE(rc = drainInotify()))
1212 break;
1213 } while (false);
1214 mfWaiting = 0;
1215 VEC_CLEANUP_PTR(&vecpchDevs);
1216 return rc;
1217}
1218
1219void hotplugInotifyImpl::Interrupt(void)
1220{
1221 AssertRCReturnVoid(mStatus);
1222 ssize_t cbWritten = write(mhWakeupPipeW, SYSFS_WAKEUP_STRING,
1223 sizeof(SYSFS_WAKEUP_STRING));
1224 if (cbWritten > 0)
1225 fsync(mhWakeupPipeW);
1226}
1227
1228# endif /* VBOX_USB_WITH_INOTIFY */
1229#endif /* VBOX_USB_WTH_SYSFS */
1230
1231VBoxMainHotplugWaiter::VBoxMainHotplugWaiter(const char *pcszDevicesRoot)
1232{
1233 try
1234 {
1235#ifdef VBOX_USB_WITH_SYSFS
1236# ifdef VBOX_USB_WITH_INOTIFY
1237 if (hotplugInotifyImpl::Available())
1238 {
1239 mImpl = new hotplugInotifyImpl(pcszDevicesRoot);
1240 return;
1241 }
1242# endif /* VBOX_USB_WITH_INOTIFY */
1243#endif /* VBOX_USB_WITH_SYSFS */
1244 mImpl = new hotplugNullImpl(pcszDevicesRoot);
1245 }
1246 catch(std::bad_alloc &e)
1247 { }
1248}
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