VirtualBox

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

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

Main: remove unnecessary includes

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