VirtualBox

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

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

Main: bugref:9224: Fixed possibe doxygen error

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 44.5 KB
Line 
1/* $Id: HostHardwareLinux.cpp 86135 2020-09-16 15:53:04Z 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/**
406 * Check whether the device is the NVME device.
407 * @returns true on success, false if the name was not available (i.e. the
408 * device was not readable, or the file name wasn't a NVME device)
409 * @param pcszNode the path to the device node for the device
410 */
411static bool probeNVME(const char *pcszNode) RT_NOTHROW_DEF
412{
413 AssertPtrReturn(pcszNode, false);
414 RTFILE File;
415 int rc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
416 if (RT_SUCCESS(rc))
417 {
418 int rcIoCtl;
419 rc = RTFileIoCtl(File, NVME_IOCTL_ID, NULL, 0, &rcIoCtl);
420 RTFileClose(File);
421 if (RT_SUCCESS(rc) && rcIoCtl >= 0)
422 return true;
423 }
424 return false;
425}
426
427/**
428 * Check whether a device node points to a valid device and create a UDI and
429 * a description for it, and store the device number, if it does.
430 *
431 * @returns true if the device is valid, false otherwise
432 * @param pcszNode the path to the device node
433 * @param isDVD are we looking for a DVD device (or a floppy device)?
434 * @param pDevice where to store the device node (optional)
435 * @param pszDesc where to store the device description (optional)
436 * @param cbDesc the size of the buffer in @a pszDesc
437 * @param pszUdi where to store the device UDI (optional)
438 * @param cbUdi the size of the buffer in @a pszUdi
439 */
440static bool devValidateDevice(const char *pcszNode, bool isDVD, dev_t *pDevice,
441 char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOTHROW_DEF
442{
443 AssertPtrReturn(pcszNode, false);
444 AssertPtrNullReturn(pDevice, false);
445 AssertPtrNullReturn(pszDesc, false);
446 AssertReturn(!pszDesc || cbDesc > 0, false);
447 AssertPtrNullReturn(pszUdi, false);
448 AssertReturn(!pszUdi || cbUdi > 0, false);
449
450 RTFSOBJINFO ObjInfo;
451 if (RT_FAILURE(RTPathQueryInfo(pcszNode, &ObjInfo, RTFSOBJATTRADD_UNIX)))
452 return false;
453 if (!RTFS_IS_DEV_BLOCK(ObjInfo.Attr.fMode))
454 return false;
455 if (pDevice)
456 *pDevice = ObjInfo.Attr.u.Unix.Device;
457
458 if (isDVD)
459 {
460 char szVendor[128], szModel[128];
461 uint8_t u8Type;
462 if (!isCdromDevNum(ObjInfo.Attr.u.Unix.Device))
463 return false;
464 if (RT_FAILURE(cdromDoInquiry(pcszNode, &u8Type,
465 szVendor, sizeof(szVendor),
466 szModel, sizeof(szModel))))
467 return false;
468 if (u8Type != TYPE_ROM)
469 return false;
470 dvdCreateDeviceStrings(szVendor, szModel, pszDesc, cbDesc, pszUdi, cbUdi);
471 }
472 else
473 {
474 /* Floppies on Linux are legacy devices with hardcoded majors and minors */
475 if (major(ObjInfo.Attr.u.Unix.Device) != FLOPPY_MAJOR)
476 return false;
477
478 unsigned Number;
479 switch (minor(ObjInfo.Attr.u.Unix.Device))
480 {
481 case 0: case 1: case 2: case 3:
482 Number = minor(ObjInfo.Attr.u.Unix.Device);
483 break;
484 case 128: case 129: case 130: case 131:
485 Number = minor(ObjInfo.Attr.u.Unix.Device) - 128 + 4;
486 break;
487 default:
488 return false;
489 }
490
491 floppy_drive_name szName;
492 if (!floppyGetName(pcszNode, Number, szName))
493 return false;
494 floppyCreateDeviceStrings(szName, Number, pszDesc, cbDesc, pszUdi, cbUdi);
495 }
496 return true;
497}
498
499
500int VBoxMainDriveInfo::updateDVDs() RT_NOEXCEPT
501{
502 LogFlowThisFunc(("entered\n"));
503 int rc;
504 try
505 {
506 mDVDList.clear();
507 /* Always allow the user to override our auto-detection using an
508 * environment variable. */
509 bool fSuccess = false; /* Have we succeeded in finding anything yet? */
510 rc = getDriveInfoFromEnv("VBOX_CDROM", &mDVDList, true /* isDVD */, &fSuccess);
511 setNoProbe(false);
512 if (RT_SUCCESS(rc) && (!fSuccess || testing()))
513 rc = getDriveInfoFromSysfs(&mDVDList, DVD, &fSuccess);
514 if (RT_SUCCESS(rc) && testing())
515 {
516 setNoProbe(true);
517 rc = getDriveInfoFromSysfs(&mDVDList, DVD, &fSuccess);
518 }
519 }
520 catch (std::bad_alloc &e)
521 {
522 rc = VERR_NO_MEMORY;
523 }
524 LogFlowThisFunc(("rc=%Rrc\n", rc));
525 return rc;
526}
527
528int VBoxMainDriveInfo::updateFloppies() RT_NOEXCEPT
529{
530 LogFlowThisFunc(("entered\n"));
531 int rc;
532 try
533 {
534 mFloppyList.clear();
535 bool fSuccess = false; /* Have we succeeded in finding anything yet? */
536 rc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList, false /* isDVD */, &fSuccess);
537 setNoProbe(false);
538 if (RT_SUCCESS(rc) && (!fSuccess || testing()))
539 rc = getDriveInfoFromSysfs(&mFloppyList, Floppy, &fSuccess);
540 if (RT_SUCCESS(rc) && testing())
541 {
542 setNoProbe(true);
543 rc = getDriveInfoFromSysfs(&mFloppyList, Floppy, &fSuccess);
544 }
545 }
546 catch (std::bad_alloc &)
547 {
548 rc = VERR_NO_MEMORY;
549 }
550 LogFlowThisFunc(("rc=%Rrc\n", rc));
551 return rc;
552}
553
554int VBoxMainDriveInfo::updateFixedDrives() RT_NOEXCEPT
555{
556 LogFlowThisFunc(("entered\n"));
557 int vrc;
558 try
559 {
560 mFixedDriveList.clear();
561 setNoProbe(false);
562 bool fSuccess = false; /* Have we succeeded in finding anything yet? */
563 vrc = getDriveInfoFromSysfs(&mFixedDriveList, FixedDisk, &fSuccess);
564 if (RT_SUCCESS(vrc) && testing())
565 {
566 setNoProbe(true);
567 vrc = getDriveInfoFromSysfs(&mFixedDriveList, FixedDisk, &fSuccess);
568 }
569 }
570 catch (std::bad_alloc &)
571 {
572 vrc = VERR_NO_MEMORY;
573 }
574 LogFlowThisFunc(("vrc=%Rrc\n", vrc));
575 return vrc;
576}
577
578
579/**
580 * Extract the names of drives from an environment variable and add them to a
581 * list if they are valid.
582 *
583 * @returns iprt status code
584 * @param pcszVar the name of the environment variable. The variable
585 * value should be a list of device node names, separated
586 * by ':' characters.
587 * @param pList the list to append the drives found to
588 * @param isDVD are we looking for DVD drives or for floppies?
589 * @param pfSuccess this will be set to true if we found at least one drive
590 * and to false otherwise. Optional.
591 *
592 * @note This is duplicated in HostHardwareFreeBSD.cpp.
593 */
594static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_DEF
595{
596 AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
597 AssertPtrReturn(pList, VERR_INVALID_POINTER);
598 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
599 LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar, pList, isDVD, pfSuccess));
600 int rc = VINF_SUCCESS;
601 bool success = false;
602 char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
603
604 try
605 {
606 char *pszCurrent = pszFreeMe;
607 while (pszCurrent && *pszCurrent != '\0')
608 {
609 char *pszNext = strchr(pszCurrent, ':');
610 if (pszNext)
611 *pszNext++ = '\0';
612
613 char szReal[RTPATH_MAX];
614 char szDesc[256], szUdi[256];
615 if ( RT_SUCCESS(RTPathReal(pszCurrent, szReal, sizeof(szReal)))
616 && devValidateDevice(szReal, isDVD, NULL, szDesc, sizeof(szDesc), szUdi, sizeof(szUdi)))
617 {
618 pList->push_back(DriveInfo(szReal, szUdi, szDesc));
619 success = true;
620 }
621 pszCurrent = pszNext;
622 }
623 if (pfSuccess != NULL)
624 *pfSuccess = success;
625 }
626 catch (std::bad_alloc &)
627 {
628 rc = VERR_NO_MEMORY;
629 }
630 RTStrFree(pszFreeMe);
631 LogFlowFunc(("rc=%Rrc, success=%d\n", rc, success));
632 return rc;
633}
634
635
636class SysfsBlockDev
637{
638public:
639 SysfsBlockDev(const char *pcszName, SysfsWantDevice_T wantDevice) RT_NOEXCEPT
640 : mpcszName(pcszName), mWantDevice(wantDevice), misConsistent(true), misValid(false)
641 {
642 if (findDeviceNode())
643 {
644 switch (mWantDevice)
645 {
646 case DVD: validateAndInitForDVD(); break;
647 case Floppy: validateAndInitForFloppy(); break;
648 default: validateAndInitForFixedDisk(); break;
649 }
650 }
651 }
652private:
653 /** The name of the subdirectory of /sys/block for this device */
654 const char *mpcszName;
655 /** Are we looking for a floppy, a DVD or a fixed disk device? */
656 SysfsWantDevice_T mWantDevice;
657 /** The device node for the device */
658 char mszNode[RTPATH_MAX];
659 /** Does the sysfs entry look like we expect it too? This is a canary
660 * for future sysfs ABI changes. */
661 bool misConsistent;
662 /** Is this entry a valid specimen of what we are looking for? */
663 bool misValid;
664 /** Human readable drive description string */
665 char mszDesc[256];
666 /** Unique identifier for the drive. Should be identical to hal's UDI for
667 * the device. May not be unique for two identical drives. */
668 char mszUdi[256];
669private:
670 /* Private methods */
671
672 /**
673 * Fill in the device node member based on the /sys/block subdirectory.
674 * @returns boolean success value
675 */
676 bool findDeviceNode() RT_NOEXCEPT
677 {
678 dev_t dev = 0;
679 int rc = RTLinuxSysFsReadDevNumFile(&dev, "block/%s/dev", mpcszName);
680 if (RT_FAILURE(rc) || dev == 0)
681 {
682 misConsistent = false;
683 return false;
684 }
685 rc = RTLinuxCheckDevicePath(dev, RTFS_TYPE_DEV_BLOCK, mszNode, sizeof(mszNode), "%s", mpcszName);
686 return RT_SUCCESS(rc);
687 }
688
689 /** Check whether the sysfs block entry is valid for a DVD device and
690 * initialise the string data members for the object. We try to get all
691 * the information we need from sysfs if possible, to avoid unnecessarily
692 * poking the device, and if that fails we fall back to an SCSI INQUIRY
693 * command. */
694 void validateAndInitForDVD() RT_NOEXCEPT
695 {
696 int64_t type = 0;
697 int rc = RTLinuxSysFsReadIntFile(10, &type, "block/%s/device/type", mpcszName);
698 if (RT_SUCCESS(rc) && type != TYPE_ROM)
699 return;
700 if (type == TYPE_ROM)
701 {
702 char szVendor[128];
703 rc = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor), NULL, "block/%s/device/vendor", mpcszName);
704 if (RT_SUCCESS(rc))
705 {
706 char szModel[128];
707 rc = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel), NULL, "block/%s/device/model", mpcszName);
708 if (RT_SUCCESS(rc))
709 {
710 misValid = true;
711 dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
712 return;
713 }
714 }
715 }
716 if (!noProbe())
717 probeAndInitForDVD();
718 }
719
720 /** Try to find out whether a device is a DVD drive by sending it an
721 * SCSI INQUIRY command. If it is, initialise the string and validity
722 * data members for the object based on the returned data.
723 */
724 void probeAndInitForDVD() RT_NOEXCEPT
725 {
726 AssertReturnVoid(mszNode[0] != '\0');
727 uint8_t bType = 0;
728 char szVendor[128] = "";
729 char szModel[128] = "";
730 int rc = cdromDoInquiry(mszNode, &bType, szVendor, sizeof(szVendor), szModel, sizeof(szModel));
731 if (RT_SUCCESS(rc) && bType == TYPE_ROM)
732 {
733 misValid = true;
734 dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
735 }
736 }
737
738 /** Check whether the sysfs block entry is valid for a floppy device and
739 * initialise the string data members for the object. Since we only
740 * support floppies using the basic "floppy" driver, we check the driver
741 * using the entry name and a driver-specific ioctl. */
742 void validateAndInitForFloppy() RT_NOEXCEPT
743 {
744 floppy_drive_name szName;
745 char szDriver[8];
746 if ( mpcszName[0] != 'f'
747 || mpcszName[1] != 'd'
748 || mpcszName[2] < '0'
749 || mpcszName[2] > '7'
750 || mpcszName[3] != '\0')
751 return;
752 bool fHaveName = false;
753 if (!noProbe())
754 fHaveName = floppyGetName(mszNode, mpcszName[2] - '0', szName);
755 int rc = RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), NULL, "block/%s/%s",
756 mpcszName, "device/driver");
757 if (RT_SUCCESS(rc))
758 {
759 if (RTStrCmp(szDriver, "floppy"))
760 return;
761 }
762 else if (!fHaveName)
763 return;
764 floppyCreateDeviceStrings(fHaveName ? szName : NULL,
765 mpcszName[2] - '0', mszDesc,
766 sizeof(mszDesc), mszUdi, sizeof(mszUdi));
767 misValid = true;
768 }
769
770 void validateAndInitForFixedDisk() RT_NOEXCEPT
771 {
772 /*
773 * For current task only device path is needed. Therefore, device probing
774 * is skipped and other fields are empty if there aren't files in the
775 * device entry.
776 */
777 int64_t type = 0;
778 int rc = RTLinuxSysFsReadIntFile(10, &type, "block/%s/device/type", mpcszName);
779 if (!RT_SUCCESS(rc) || type != TYPE_DISK)
780 {
781 if (noProbe() || !probeNVME(mszNode))
782 {
783 char szDriver[16];
784 rc = RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), NULL, "block/%s/%s",
785 mpcszName, "device/device/driver");
786 if (RT_FAILURE(rc) || RTStrCmp(szDriver, "nvme"))
787 return;
788 }
789 }
790 char szVendor[128];
791 char szModel[128];
792 size_t cbRead = 0;
793 rc = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor), &cbRead, "block/%s/device/vendor", mpcszName);
794 szVendor[cbRead] = '\0';
795 /* Assume the model is always present. Vendor is not present for NVME disks */
796 cbRead = 0;
797 rc = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel), &cbRead, "block/%s/device/model", mpcszName);
798 szModel[cbRead] = '\0';
799 if (RT_SUCCESS(rc))
800 {
801 misValid = true;
802 dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
803 }
804 }
805
806public:
807 bool isConsistent() const RT_NOEXCEPT
808 {
809 return misConsistent;
810 }
811 bool isValid() const RT_NOEXCEPT
812 {
813 return misValid;
814 }
815 const char *getDesc() const RT_NOEXCEPT
816 {
817 return mszDesc;
818 }
819 const char *getUdi() const RT_NOEXCEPT
820 {
821 return mszUdi;
822 }
823 const char *getNode() const RT_NOEXCEPT
824 {
825 return mszNode;
826 }
827};
828
829
830/**
831 * Helper function to query the sysfs subsystem for information about DVD
832 * drives attached to the system.
833 * @returns iprt status code
834 * @param pList where to add information about the drives detected
835 * @param wantDevice The kind of devices we're looking for.
836 * @param pfSuccess Did we find anything?
837 *
838 * @returns IPRT status code
839 * @throws Nothing.
840 */
841static int getDriveInfoFromSysfs(DriveInfoList *pList, SysfsWantDevice_T wantDevice, bool *pfSuccess) RT_NOTHROW_DEF
842{
843 AssertPtrReturn(pList, VERR_INVALID_POINTER);
844 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
845 LogFlowFunc (("pList=%p, wantDevice=%u, pfSuccess=%p\n",
846 pList, (unsigned)wantDevice, pfSuccess));
847 if (!RTPathExists("/sys"))
848 return VINF_SUCCESS;
849
850 bool fSuccess = true;
851 unsigned cFound = 0;
852 RTDIR hDir = NIL_RTDIR;
853 int rc = RTDirOpen(&hDir, "/sys/block");
854 /* This might mean that sysfs semantics have changed */
855 AssertReturn(rc != VERR_FILE_NOT_FOUND, VINF_SUCCESS);
856 if (RT_SUCCESS(rc))
857 {
858 for (;;)
859 {
860 RTDIRENTRY entry;
861 rc = RTDirRead(hDir, &entry, NULL);
862 Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
863 if (RT_FAILURE(rc)) /* Including overflow and no more files */
864 break;
865 if (entry.szName[0] == '.')
866 continue;
867 SysfsBlockDev dev(entry.szName, wantDevice);
868 /* This might mean that sysfs semantics have changed */
869 AssertBreakStmt(dev.isConsistent(), fSuccess = false);
870 if (!dev.isValid())
871 continue;
872 try
873 {
874 pList->push_back(DriveInfo(dev.getNode(), dev.getUdi(), dev.getDesc()));
875 }
876 catch (std::bad_alloc &e)
877 {
878 rc = VERR_NO_MEMORY;
879 break;
880 }
881 ++cFound;
882 }
883 RTDirClose(hDir);
884 }
885 if (rc == VERR_NO_MORE_FILES)
886 rc = VINF_SUCCESS;
887 else if (RT_FAILURE(rc))
888 /* Clean up again */
889 while (cFound-- > 0)
890 pList->pop_back();
891 if (pfSuccess)
892 *pfSuccess = fSuccess;
893 LogFlow (("rc=%Rrc, fSuccess=%u\n", rc, (unsigned)fSuccess));
894 return rc;
895}
896
897
898/** Helper for readFilePathsFromDir(). Adds a path to the vector if it is not
899 * NULL and not a dotfile (".", "..", ".*"). */
900static int maybeAddPathToVector(const char *pcszPath, const char *pcszEntry, VECTOR_PTR(char *) *pvecpchDevs) RT_NOTHROW_DEF
901{
902 if (!pcszPath)
903 return 0;
904 if (pcszEntry[0] == '.')
905 return 0;
906 char *pszPath = RTStrDup(pcszPath);
907 if (pszPath)
908 {
909 int vrc = VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPath);
910 if (RT_SUCCESS(vrc))
911 return 0;
912 }
913 return ENOMEM;
914}
915
916/**
917 * Helper for readFilePaths().
918 *
919 * Adds the entries from the open directory @a pDir to the vector @a pvecpchDevs
920 * using either the full path or the realpath() and skipping hidden files
921 * and files on which realpath() fails.
922 */
923static int readFilePathsFromDir(const char *pcszPath, DIR *pDir, VECTOR_PTR(char *) *pvecpchDevs, int withRealPath) RT_NOTHROW_DEF
924{
925 struct dirent entry, *pResult;
926 int err;
927
928#if RT_GNUC_PREREQ(4, 6)
929# pragma GCC diagnostic push
930# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
931#endif
932 for (err = readdir_r(pDir, &entry, &pResult);
933 pResult /** @todo r=bird: && err == 0 ? */;
934 err = readdir_r(pDir, &entry, &pResult))
935#if RT_GNUC_PREREQ(4, 6)
936# pragma GCC diagnostic pop
937#endif
938 {
939 /* We (implicitly) require that PATH_MAX be defined */
940 char szPath[PATH_MAX + 1], szRealPath[PATH_MAX + 1], *pszPath;
941 if (snprintf(szPath, sizeof(szPath), "%s/%s", pcszPath,
942 entry.d_name) < 0)
943 return errno;
944 if (withRealPath)
945 pszPath = realpath(szPath, szRealPath);
946 else
947 pszPath = szPath;
948 if ((err = maybeAddPathToVector(pszPath, entry.d_name, pvecpchDevs)))
949 return err;
950 }
951 return err;
952}
953
954
955/**
956 * Helper for walkDirectory to dump the names of a directory's entries into a
957 * vector of char pointers.
958 *
959 * @returns zero on success or (positive) posix error value.
960 * @param pcszPath the path to dump.
961 * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
962 * by the caller even on failure.
963 * @param withRealPath whether to canonicalise the filename with realpath
964 */
965static int readFilePaths(const char *pcszPath, VECTOR_PTR(char *) *pvecpchDevs, int withRealPath) RT_NOTHROW_DEF
966{
967 AssertPtrReturn(pvecpchDevs, EINVAL);
968 AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
969 AssertPtrReturn(pcszPath, EINVAL);
970
971 DIR *pDir = opendir(pcszPath);
972 if (!pDir)
973 return RTErrConvertFromErrno(errno);
974 int err = readFilePathsFromDir(pcszPath, pDir, pvecpchDevs, withRealPath);
975 if (closedir(pDir) < 0 && !err)
976 err = errno;
977 return RTErrConvertFromErrno(err);
978}
979
980
981class hotplugNullImpl : public VBoxMainHotplugWaiterImpl
982{
983public:
984 hotplugNullImpl(const char *) {}
985 virtual ~hotplugNullImpl (void) {}
986 /** @copydoc VBoxMainHotplugWaiter::Wait */
987 virtual int Wait (RTMSINTERVAL cMillies)
988 {
989 NOREF(cMillies);
990 return VERR_NOT_SUPPORTED;
991 }
992 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
993 virtual void Interrupt (void) {}
994 virtual int getStatus(void)
995 {
996 return VERR_NOT_SUPPORTED;
997 }
998
999};
1000
1001#ifdef VBOX_USB_WITH_SYSFS
1002# ifdef VBOX_USB_WITH_INOTIFY
1003/** Class wrapper around an inotify watch (or a group of them to be precise).
1004 */
1005typedef struct inotifyWatch
1006{
1007 /** Pointer to the inotify_add_watch() glibc function/Linux API */
1008 int (*inotify_add_watch)(int, const char *, uint32_t);
1009 /** The native handle of the inotify fd. */
1010 int mhInotify;
1011} inotifyWatch;
1012
1013/** The flags we pass to inotify - modify, create, delete, change permissions
1014 */
1015#define IN_FLAGS 0x306
1016
1017static int iwAddWatch(inotifyWatch *pSelf, const char *pcszPath)
1018{
1019 errno = 0;
1020 if ( pSelf->inotify_add_watch(pSelf->mhInotify, pcszPath, IN_FLAGS) >= 0
1021 || (errno == EACCES))
1022 return VINF_SUCCESS;
1023 /* Other errors listed in the manpage can be treated as fatal */
1024 return RTErrConvertFromErrno(errno);
1025}
1026
1027/** Object initialisation */
1028static int iwInit(inotifyWatch *pSelf)
1029{
1030 int (*inotify_init)(void);
1031 int fd, flags;
1032 int rc = VINF_SUCCESS;
1033
1034 AssertPtr(pSelf);
1035 pSelf->mhInotify = -1;
1036 errno = 0;
1037 *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
1038 if (!inotify_init)
1039 return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
1040 *(void **)(&pSelf->inotify_add_watch)
1041 = dlsym(RTLD_DEFAULT, "inotify_add_watch");
1042 if (!pSelf->inotify_add_watch)
1043 return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
1044 fd = inotify_init();
1045 if (fd < 0)
1046 {
1047 Assert(errno > 0);
1048 return RTErrConvertFromErrno(errno);
1049 }
1050 Assert(errno == 0);
1051
1052 flags = fcntl(fd, F_GETFL, NULL);
1053 if ( flags < 0
1054 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0
1055 || fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 /* race here */)
1056 {
1057 Assert(errno > 0);
1058 rc = RTErrConvertFromErrno(errno);
1059 }
1060 if (RT_FAILURE(rc))
1061 close(fd);
1062 else
1063 {
1064 Assert(errno == 0);
1065 pSelf->mhInotify = fd;
1066 }
1067 return rc;
1068}
1069
1070static void iwTerm(inotifyWatch *pSelf)
1071{
1072 AssertPtrReturnVoid(pSelf);
1073 if (pSelf->mhInotify != -1)
1074 {
1075 close(pSelf->mhInotify);
1076 pSelf->mhInotify = -1;
1077 }
1078}
1079
1080static int iwGetFD(inotifyWatch *pSelf)
1081{
1082 AssertPtrReturn(pSelf, -1);
1083 return pSelf->mhInotify;
1084}
1085
1086# define SYSFS_WAKEUP_STRING "Wake up!"
1087
1088class hotplugInotifyImpl : public VBoxMainHotplugWaiterImpl
1089{
1090 /** Pipe used to interrupt wait(), the read end. */
1091 int mhWakeupPipeR;
1092 /** Pipe used to interrupt wait(), the write end. */
1093 int mhWakeupPipeW;
1094 /** The inotify watch set */
1095 inotifyWatch mWatches;
1096 /** Flag to mark that the Wait() method is currently being called, and to
1097 * ensure that it isn't called multiple times in parallel. */
1098 volatile uint32_t mfWaiting;
1099 /** The root of the USB devices tree. */
1100 const char *mpcszDevicesRoot;
1101 /** iprt result code from object initialisation. Should be AssertReturn-ed
1102 * on at the start of all methods. I went this way because I didn't want
1103 * to deal with exceptions. */
1104 int mStatus;
1105 /** ID values associates with the wakeup pipe and the FAM socket for polling
1106 */
1107 enum
1108 {
1109 RPIPE_ID = 0,
1110 INOTIFY_ID,
1111 MAX_POLLID
1112 };
1113
1114 /** Clean up any resources in use, gracefully skipping over any which have
1115 * not yet been allocated or already cleaned up. Intended to be called
1116 * from the destructor or after a failed initialisation. */
1117 void term(void);
1118
1119 int drainInotify();
1120
1121 /** Read the wakeup string from the wakeup pipe */
1122 int drainWakeupPipe(void);
1123public:
1124 hotplugInotifyImpl(const char *pcszDevicesRoot);
1125 virtual ~hotplugInotifyImpl(void)
1126 {
1127 term();
1128#ifdef DEBUG
1129 /** The first call to term should mark all resources as freed, so this
1130 * should be a semantic no-op. */
1131 term();
1132#endif
1133 }
1134 /** Is inotify available and working on this system? If so we expect that
1135 * this implementation will be usable. */
1136 /** @todo test the "inotify in glibc but not in the kernel" case. */
1137 static bool Available(void)
1138 {
1139 int (*inotify_init)(void);
1140
1141 *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
1142 if (!inotify_init)
1143 return false;
1144 int fd = inotify_init();
1145 if (fd == -1)
1146 return false;
1147 close(fd);
1148 return true;
1149 }
1150
1151 virtual int getStatus(void)
1152 {
1153 return mStatus;
1154 }
1155
1156 /** @copydoc VBoxMainHotplugWaiter::Wait */
1157 virtual int Wait(RTMSINTERVAL);
1158 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1159 virtual void Interrupt(void);
1160};
1161
1162/** Simplified version of RTPipeCreate */
1163static int pipeCreateSimple(int *phPipeRead, int *phPipeWrite)
1164{
1165 AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER);
1166 AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER);
1167
1168 /*
1169 * Create the pipe and set the close-on-exec flag.
1170 */
1171 int aFds[2] = {-1, -1};
1172 if (pipe(aFds))
1173 return RTErrConvertFromErrno(errno);
1174 if ( fcntl(aFds[0], F_SETFD, FD_CLOEXEC) < 0
1175 || fcntl(aFds[1], F_SETFD, FD_CLOEXEC) < 0)
1176 {
1177 int rc = RTErrConvertFromErrno(errno);
1178 close(aFds[0]);
1179 close(aFds[1]);
1180 return rc;
1181 }
1182
1183 *phPipeRead = aFds[0];
1184 *phPipeWrite = aFds[1];
1185
1186 /*
1187 * Before we leave, make sure to shut up SIGPIPE.
1188 */
1189 signal(SIGPIPE, SIG_IGN);
1190 return VINF_SUCCESS;
1191}
1192
1193hotplugInotifyImpl::hotplugInotifyImpl(const char *pcszDevicesRoot) :
1194 mhWakeupPipeR(-1), mhWakeupPipeW(-1), mfWaiting(0),
1195 mpcszDevicesRoot(pcszDevicesRoot), mStatus(VERR_WRONG_ORDER)
1196{
1197# ifdef DEBUG
1198 /* Excercise the code path (term() on a not-fully-initialised object) as
1199 * well as we can. On an uninitialised object this method is a semantic
1200 * no-op. */
1201 mWatches.mhInotify = -1; /* term will access this variable */
1202 term();
1203 /* For now this probing method should only be used if nothing else is
1204 * available */
1205# endif
1206 int rc;
1207 do {
1208 if (RT_FAILURE(rc = iwInit(&mWatches)))
1209 break;
1210 if (RT_FAILURE(rc = iwAddWatch(&mWatches, mpcszDevicesRoot)))
1211 break;
1212 if (RT_FAILURE(rc = pipeCreateSimple(&mhWakeupPipeR, &mhWakeupPipeW)))
1213 break;
1214 } while (0);
1215 mStatus = rc;
1216 if (RT_FAILURE(rc))
1217 term();
1218}
1219
1220void hotplugInotifyImpl::term(void)
1221{
1222 /** This would probably be a pending segfault, so die cleanly */
1223 AssertRelease(!mfWaiting);
1224 if (mhWakeupPipeR != -1)
1225 {
1226 close(mhWakeupPipeR);
1227 mhWakeupPipeR = -1;
1228 }
1229 if (mhWakeupPipeW != -1)
1230 {
1231 close(mhWakeupPipeW);
1232 mhWakeupPipeW = -1;
1233 }
1234 iwTerm(&mWatches);
1235}
1236
1237int hotplugInotifyImpl::drainInotify()
1238{
1239 char chBuf[RTPATH_MAX + 256]; /* Should always be big enough */
1240 ssize_t cchRead;
1241
1242 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1243 errno = 0;
1244 do {
1245 cchRead = read(iwGetFD(&mWatches), chBuf, sizeof(chBuf));
1246 } while (cchRead > 0);
1247 if (cchRead == 0)
1248 return VINF_SUCCESS;
1249 if ( cchRead < 0
1250 && ( errno == EAGAIN
1251#if EAGAIN != EWOULDBLOCK
1252 || errno == EWOULDBLOCK
1253#endif
1254 ))
1255 return VINF_SUCCESS;
1256 Assert(errno > 0);
1257 return RTErrConvertFromErrno(errno);
1258}
1259
1260int hotplugInotifyImpl::drainWakeupPipe(void)
1261{
1262 char szBuf[sizeof(SYSFS_WAKEUP_STRING)];
1263 ssize_t cbRead;
1264
1265 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1266 cbRead = read(mhWakeupPipeR, szBuf, sizeof(szBuf));
1267 Assert(cbRead > 0);
1268 NOREF(cbRead);
1269 return VINF_SUCCESS;
1270}
1271
1272int hotplugInotifyImpl::Wait(RTMSINTERVAL aMillies)
1273{
1274 int rc;
1275 char **ppszEntry;
1276 VECTOR_PTR(char *) vecpchDevs;
1277
1278 AssertRCReturn(mStatus, VERR_WRONG_ORDER);
1279 bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
1280 AssertReturn(fEntered, VERR_WRONG_ORDER);
1281 VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
1282 do {
1283 struct pollfd pollFD[MAX_POLLID];
1284
1285 rc = readFilePaths(mpcszDevicesRoot, &vecpchDevs, false);
1286 if (RT_SUCCESS(rc))
1287 VEC_FOR_EACH(&vecpchDevs, char *, ppszEntry)
1288 if (RT_FAILURE(rc = iwAddWatch(&mWatches, *ppszEntry)))
1289 break;
1290 if (RT_FAILURE(rc))
1291 break;
1292 pollFD[RPIPE_ID].fd = mhWakeupPipeR;
1293 pollFD[RPIPE_ID].events = POLLIN;
1294 pollFD[INOTIFY_ID].fd = iwGetFD(&mWatches);
1295 pollFD[INOTIFY_ID].events = POLLIN | POLLERR | POLLHUP;
1296 errno = 0;
1297 int cPolled = poll(pollFD, RT_ELEMENTS(pollFD), aMillies);
1298 if (cPolled < 0)
1299 {
1300 Assert(errno > 0);
1301 rc = RTErrConvertFromErrno(errno);
1302 }
1303 else if (pollFD[RPIPE_ID].revents)
1304 {
1305 rc = drainWakeupPipe();
1306 if (RT_SUCCESS(rc))
1307 rc = VERR_INTERRUPTED;
1308 break;
1309 }
1310 else if (!(pollFD[INOTIFY_ID].revents))
1311 {
1312 AssertBreakStmt(cPolled == 0, rc = VERR_INTERNAL_ERROR);
1313 rc = VERR_TIMEOUT;
1314 }
1315 Assert(errno == 0 || (RT_FAILURE(rc) && rc != VERR_TIMEOUT));
1316 if (RT_FAILURE(rc))
1317 break;
1318 AssertBreakStmt(cPolled == 1, rc = VERR_INTERNAL_ERROR);
1319 if (RT_FAILURE(rc = drainInotify()))
1320 break;
1321 } while (false);
1322 mfWaiting = 0;
1323 VEC_CLEANUP_PTR(&vecpchDevs);
1324 return rc;
1325}
1326
1327void hotplugInotifyImpl::Interrupt(void)
1328{
1329 AssertRCReturnVoid(mStatus);
1330 ssize_t cbWritten = write(mhWakeupPipeW, SYSFS_WAKEUP_STRING,
1331 sizeof(SYSFS_WAKEUP_STRING));
1332 if (cbWritten > 0)
1333 fsync(mhWakeupPipeW);
1334}
1335
1336# endif /* VBOX_USB_WITH_INOTIFY */
1337#endif /* VBOX_USB_WTH_SYSFS */
1338
1339VBoxMainHotplugWaiter::VBoxMainHotplugWaiter(const char *pcszDevicesRoot)
1340{
1341 try
1342 {
1343#ifdef VBOX_USB_WITH_SYSFS
1344# ifdef VBOX_USB_WITH_INOTIFY
1345 if (hotplugInotifyImpl::Available())
1346 {
1347 mImpl = new hotplugInotifyImpl(pcszDevicesRoot);
1348 return;
1349 }
1350# endif /* VBOX_USB_WITH_INOTIFY */
1351#endif /* VBOX_USB_WITH_SYSFS */
1352 mImpl = new hotplugNullImpl(pcszDevicesRoot);
1353 }
1354 catch (std::bad_alloc &e)
1355 { }
1356}
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