VirtualBox

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

Last change on this file since 70561 was 69753, checked in by vboxsync, 7 years ago

iprt/dir: Morphing PRTDIR into a handle named RTDIR. (Been wanting to correct this for years. Don't know why I makde it a pointer rather than an abstrct handle like everything else.)

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