VirtualBox

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

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

Main: bugref:9224: Added NVME drives enumeration on Linux. Fixed the showing the model of the drive if additional info is not available

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