VirtualBox

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

Last change on this file since 99739 was 99739, checked in by vboxsync, 17 months ago

*: doxygen corrections (mostly about removing @returns from functions returning void).

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