VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

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

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