VirtualBox

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

Last change on this file since 86136 was 86136, checked in by vboxsync, 5 years ago

Main: bugref:9224: Fixed compilation error for linux kernel less than 4.4

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette