VirtualBox

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

Last change on this file since 62468 was 62468, checked in by vboxsync, 8 years ago

Main: scm

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