VirtualBox

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

Last change on this file since 94078 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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

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