VirtualBox

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

Last change on this file since 98099 was 98099, checked in by vboxsync, 2 years ago

Main/HostHardwareLinux.cpp: Get rid of the for(;;) loop introduced in r155199 because the code is not supposed to be executed in a loop (would break USB device detection and was just working by accident because readFilePaths() would return an error due to vecpchDevs being initialized in the second iteration, asserting in debug builds), previously it was do {} while(false) loop which was ugly as well

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