VirtualBox

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

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

scm copyright and license note update

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