VirtualBox

source: vbox/trunk/src/VBox/Main/linux/HostHardwareLinux.cpp@ 28847

Last change on this file since 28847 was 28847, checked in by vboxsync, 15 years ago

Main/HostHardwareLinux: for now, use hal in preference to fam if available and disable wrong assertion

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 91.4 KB
Line 
1/* $Id: HostHardwareLinux.cpp 28847 2010-04-27 16:57:54Z vboxsync $ */
2/** @file
3 * Classes for handling hardware detection under Linux. Please feel free to
4 * expand these to work for other systems (Solaris!) or to add new ones for
5 * other systems.
6 */
7
8/*
9 * Copyright (C) 2008 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.215389.xyz. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#define LOG_GROUP LOG_GROUP_MAIN
21
22/*******************************************************************************
23* Header Files *
24*******************************************************************************/
25
26#include <HostHardwareLinux.h>
27
28#include <VBox/err.h>
29#include <VBox/log.h>
30
31#ifdef VBOX_USB_WITH_DBUS
32# include <VBox/dbus.h>
33#endif
34
35#include <iprt/asm.h>
36#include <iprt/dir.h>
37#include <iprt/env.h>
38#include <iprt/file.h>
39#include <iprt/mem.h>
40#include <iprt/param.h>
41#include <iprt/path.h>
42#include <iprt/pipe.h>
43#include <iprt/poll.h>
44#include <iprt/socket.h>
45#include <iprt/string.h>
46#include <iprt/thread.h> /* for RTThreadSleep() */
47
48#include <linux/cdrom.h>
49#include <linux/fd.h>
50#include <linux/major.h>
51#include <scsi/scsi.h>
52
53#include <iprt/linux/sysfs.h>
54
55#ifdef VBOX_USB_WITH_SYSFS
56# ifdef VBOX_USB_WITH_FAM
57# include <fam.h>
58# endif
59#endif
60
61#include <vector>
62
63#include <errno.h>
64
65/******************************************************************************
66* Global Variables *
67******************************************************************************/
68
69#ifdef TESTCASE
70static bool testing() { return true; }
71static bool fNoProbe = false;
72static bool noProbe() { return fNoProbe; }
73static void setNoProbe(bool val) { fNoProbe = val; }
74#else
75static bool testing() { return false; }
76static bool noProbe() { return false; }
77static void setNoProbe(bool val) { (void)val; }
78#endif
79
80/******************************************************************************
81* Typedefs and Defines *
82******************************************************************************/
83
84/** When waiting for hotplug events, we currently restart the wait after at
85 * most this many milliseconds. */
86enum { DBUS_POLL_TIMEOUT = 2000 /* ms */ };
87
88static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList,
89 bool isDVD, bool *pfSuccess);
90static int getDriveInfoFromDev(DriveInfoList *pList, bool isDVD,
91 bool *pfSuccess);
92static int getDriveInfoFromSysfs(DriveInfoList *pList, bool isDVD,
93 bool *pfSuccess);
94#ifdef VBOX_USB_WITH_SYSFS
95# ifdef VBOX_USB_WITH_FAM
96static int getUSBDeviceInfoFromSysfs(USBDeviceInfoList *pList, bool *pfSuccess);
97# endif
98# ifdef VBOX_USB_WITH_DBUS
99/* These must be extern to be usable in the RTMemAutoPtr template */
100extern void VBoxHalShutdown (DBusConnection *pConnection);
101extern void VBoxHalShutdownPrivate (DBusConnection *pConnection);
102extern void VBoxDBusConnectionUnref(DBusConnection *pConnection);
103extern void VBoxDBusConnectionCloseAndUnref(DBusConnection *pConnection);
104extern void VBoxDBusMessageUnref(DBusMessage *pMessage);
105
106static int halInit(RTMemAutoPtr <DBusConnection, VBoxHalShutdown> *pConnection);
107static int halInitPrivate(RTMemAutoPtr <DBusConnection, VBoxHalShutdownPrivate> *pConnection);
108static int halFindDeviceStringMatch (DBusConnection *pConnection,
109 const char *pszKey, const char *pszValue,
110 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage);
111/*
112static int halFindDeviceStringMatchVector (DBusConnection *pConnection,
113 const char *pszKey,
114 const char *pszValue,
115 std::vector<iprt::MiniString> *pMatches);
116*/
117static int halGetPropertyStrings (DBusConnection *pConnection,
118 const char *pszUdi, size_t cKeys,
119 const char **papszKeys, char **papszValues,
120 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage);
121/*
122static int halGetPropertyStringsVector (DBusConnection *pConnection,
123 const char *pszUdi, size_t cProps,
124 const char **papszKeys,
125 std::vector<iprt::MiniString> *pMatches,
126 bool *pfMatches, bool *pfSuccess);
127*/
128static int getUSBDeviceInfoFromHal(USBDeviceInfoList *pList, bool *pfSuccess);
129static int getOldUSBDeviceInfoFromHal(USBDeviceInfoList *pList, bool *pfSuccess);
130static int getUSBInterfacesFromHal(std::vector <iprt::MiniString> *pList,
131 const char *pcszUdi, bool *pfSuccess);
132static DBusHandlerResult dbusFilterFunction (DBusConnection *pConnection,
133 DBusMessage *pMessage, void *pvUser);
134# endif /* VBOX_USB_WITH_DBUS */
135#endif /* VBOX_USB_WITH_SYSFS */
136
137
138/** Find the length of a string, ignoring trailing non-ascii or control
139 * characters */
140static size_t strLenStripped(const char *pcsz)
141{
142 size_t cch = 0;
143 for (size_t i = 0; pcsz[i] != '\0'; ++i)
144 if (pcsz[i] > 32 && pcsz[i] < 127)
145 cch = i;
146 return cch + 1;
147}
148
149
150/**
151 * Get the name of a floppy drive according to the Linux floppy driver.
152 * @returns true on success, false if the name was not available (i.e. the
153 * device was not readible, or the file name wasn't a PC floppy
154 * device)
155 * @param pcszNode the path to the device node for the device
156 * @param Number the Linux floppy driver number for the drive. Required.
157 * @param pszName where to store the name retreived
158 */
159static bool floppyGetName(const char *pcszNode, unsigned Number,
160 floppy_drive_name pszName)
161{
162 AssertPtrReturn(pcszNode, false);
163 AssertPtrReturn(pszName, false);
164 AssertReturn(Number <= 7, false);
165 RTFILE File;
166 int rc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
167 if (RT_SUCCESS(rc))
168 {
169 int rcIoCtl;
170 rc = RTFileIoCtl(File, FDGETDRVTYP, pszName, 0, &rcIoCtl);
171 RTFileClose(File);
172 if (RT_SUCCESS(rc) && rcIoCtl >= 0)
173 return true;
174 }
175 return false;
176}
177
178
179/**
180 * Create a UDI and a description for a floppy drive based on a number and the
181 * driver's name for it. We deliberately return an ugly sequence of
182 * characters as the description rather than an English language string to
183 * avoid translation issues.
184 *
185 * @returns true if we know the device to be valid, false otherwise
186 * @param pcszName the floppy driver name for the device (optional)
187 * @param Number the number of the floppy (0 to 3 on FDC 0, 4 to 7 on
188 * FDC 1)
189 * @param pszDesc where to store the device description (optional)
190 * @param cchDesc the size of the buffer in @a pszDesc
191 * @param pszUdi where to store the device UDI (optional)
192 * @param cchUdi the size of the buffer in @a pszUdi
193 */
194static void floppyCreateDeviceStrings(const floppy_drive_name pcszName,
195 unsigned Number, char *pszDesc,
196 size_t cchDesc, char *pszUdi,
197 size_t cchUdi)
198{
199 AssertPtrNullReturnVoid(pcszName);
200 AssertPtrNullReturnVoid(pszDesc);
201 AssertReturnVoid(!pszDesc || cchDesc > 0);
202 AssertPtrNullReturnVoid(pszUdi);
203 AssertReturnVoid(!pszUdi || cchUdi > 0);
204 AssertReturnVoid(Number <= 7);
205 if (pcszName)
206 {
207 const char *pcszSize;
208 switch(pcszName[0])
209 {
210 case 'd': case 'q': case 'h':
211 pcszSize = "5.25\"";
212 break;
213 case 'D': case 'H': case 'E': case 'u':
214 pcszSize = "3.5\"";
215 break;
216 default:
217 pcszSize = "(unknown)";
218 }
219 if (pszDesc)
220 RTStrPrintf(pszDesc, cchDesc, "%s %s K%s", pcszSize, &pcszName[1],
221 Number > 3 ? ", FDC 2" : "");
222 }
223 else
224 {
225 if (pszDesc)
226 RTStrPrintf(pszDesc, cchDesc, "FDD %d%s", (Number & 4) + 1,
227 Number > 3 ? ", FDC 2" : "");
228 }
229 if (pszUdi)
230 RTStrPrintf(pszUdi, cchUdi,
231 "/org/freedesktop/Hal/devices/platform_floppy_%u_storage",
232 Number);
233}
234
235
236/**
237 * Check whether a device number might correspond to a CD-ROM device according
238 * to Documentation/devices.txt in the Linux kernel source.
239 * @returns true if it might, false otherwise
240 * @param Number the device number (major and minor combination)
241 */
242static bool isCdromDevNum(dev_t Number)
243{
244 int major = major(Number);
245 int minor = minor(Number);
246 if ((major == IDE0_MAJOR) && !(minor & 0x3f))
247 return true;
248 if (major == SCSI_CDROM_MAJOR)
249 return true;
250 if (major == CDU31A_CDROM_MAJOR)
251 return true;
252 if (major == GOLDSTAR_CDROM_MAJOR)
253 return true;
254 if (major == OPTICS_CDROM_MAJOR)
255 return true;
256 if (major == SANYO_CDROM_MAJOR)
257 return true;
258 if (major == MITSUMI_X_CDROM_MAJOR)
259 return true;
260 if ((major == IDE1_MAJOR) && !(minor & 0x3f))
261 return true;
262 if (major == MITSUMI_CDROM_MAJOR)
263 return true;
264 if (major == CDU535_CDROM_MAJOR)
265 return true;
266 if (major == MATSUSHITA_CDROM_MAJOR)
267 return true;
268 if (major == MATSUSHITA_CDROM2_MAJOR)
269 return true;
270 if (major == MATSUSHITA_CDROM3_MAJOR)
271 return true;
272 if (major == MATSUSHITA_CDROM4_MAJOR)
273 return true;
274 if (major == AZTECH_CDROM_MAJOR)
275 return true;
276 if (major == 30 /* CM205_CDROM_MAJOR */) /* no #define for some reason */
277 return true;
278 if (major == CM206_CDROM_MAJOR)
279 return true;
280 if ((major == IDE3_MAJOR) && !(minor & 0x3f))
281 return true;
282 if (major == 46 /* Parallel port ATAPI CD-ROM */) /* no #define */
283 return true;
284 if ((major == IDE4_MAJOR) && !(minor & 0x3f))
285 return true;
286 if ((major == IDE5_MAJOR) && !(minor & 0x3f))
287 return true;
288 if ((major == IDE6_MAJOR) && !(minor & 0x3f))
289 return true;
290 if ((major == IDE7_MAJOR) && !(minor & 0x3f))
291 return true;
292 if ((major == IDE8_MAJOR) && !(minor & 0x3f))
293 return true;
294 if ((major == IDE9_MAJOR) && !(minor & 0x3f))
295 return true;
296 if (major == 113 /* VIOCD_MAJOR */)
297 return true;
298 return false;
299}
300
301
302/**
303 * Send an SCSI INQUIRY command to a device and return selected information.
304 * @returns iprt status code
305 * @returns VERR_TRY_AGAIN if the query failed but might succeed next time
306 * @param pcszNode the full path to the device node
307 * @param pu8Type where to store the SCSI device type on success (optional)
308 * @param pchVendor where to store the vendor id string on success (optional)
309 * @param cchVendor the size of the @a pchVendor buffer
310 * @param pchModel where to store the product id string on success (optional)
311 * @param cchModel the size of the @a pchModel buffer
312 * @note check documentation on the SCSI INQUIRY command and the Linux kernel
313 * SCSI headers included above if you want to understand what is going
314 * on in this method.
315 */
316static int cdromDoInquiry(const char *pcszNode, uint8_t *pu8Type,
317 char *pchVendor, size_t cchVendor, char *pchModel,
318 size_t cchModel)
319{
320 LogRelFlowFunc(("pcszNode=%s, pu8Type=%p, pchVendor=%p, cchVendor=%llu, pchModel=%p, cchModel=%llu\n",
321 pcszNode, pu8Type, pchVendor, cchVendor, pchModel,
322 cchModel));
323 AssertPtrReturn(pcszNode, VERR_INVALID_POINTER);
324 AssertPtrNullReturn(pu8Type, VERR_INVALID_POINTER);
325 AssertPtrNullReturn(pchVendor, VERR_INVALID_POINTER);
326 AssertPtrNullReturn(pchModel, VERR_INVALID_POINTER);
327
328 RTFILE hFile;
329 int rc = RTFileOpen(&hFile, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
330 if (RT_SUCCESS(rc))
331 {
332 int rcIoCtl = 0;
333 unsigned char u8Response[96] = { 0 };
334 struct cdrom_generic_command CdromCommandReq;
335 RT_ZERO(CdromCommandReq);
336 CdromCommandReq.cmd[0] = INQUIRY;
337 CdromCommandReq.cmd[4] = sizeof(u8Response);
338 CdromCommandReq.buffer = u8Response;
339 CdromCommandReq.buflen = sizeof(u8Response);
340 CdromCommandReq.data_direction = CGC_DATA_READ;
341 CdromCommandReq.timeout = 5000; /* ms */
342 rc = RTFileIoCtl(hFile, CDROM_SEND_PACKET, &CdromCommandReq, 0, &rcIoCtl);
343 if (RT_SUCCESS(rc) && rcIoCtl < 0)
344 rc = RTErrConvertFromErrno(-CdromCommandReq.stat);
345 RTFileClose(hFile);
346
347 if (RT_SUCCESS(rc))
348 {
349 if (pu8Type)
350 *pu8Type = u8Response[0] & 0x1f;
351 if (pchVendor)
352 RTStrPrintf(pchVendor, cchVendor, "%.8s",
353 &u8Response[8] /* vendor id string */);
354 if (pchModel)
355 RTStrPrintf(pchModel, cchModel, "%.16s",
356 &u8Response[16] /* product id string */);
357 LogRelFlowFunc(("returning success: type=%u, vendor=%.8s, product=%.16s\n",
358 u8Response[0] & 0x1f, &u8Response[8], &u8Response[16]));
359 return VINF_SUCCESS;
360 }
361 }
362 LogRelFlowFunc(("returning %Rrc\n", rc));
363 return rc;
364}
365
366
367/**
368 * Initialise the device strings (description and UDI) for a DVD drive based on
369 * vendor and model name strings.
370 * @param pcszVendor the vendor ID string
371 * @param pcszModel the product ID string
372 * @param pszDesc where to store the description string (optional)
373 * @param cchDesc the size of the buffer in @pszDesc
374 * @param pszUdi where to store the UDI string (optional)
375 * @param cchUdi the size of the buffer in @pszUdi
376 */
377/* static */
378void dvdCreateDeviceStrings(const char *pcszVendor, const char *pcszModel,
379 char *pszDesc, size_t cchDesc, char *pszUdi,
380 size_t cchUdi)
381{
382 AssertPtrReturnVoid(pcszVendor);
383 AssertPtrReturnVoid(pcszModel);
384 AssertPtrNullReturnVoid(pszDesc);
385 AssertReturnVoid(!pszDesc || cchDesc > 0);
386 AssertPtrNullReturnVoid(pszUdi);
387 AssertReturnVoid(!pszUdi || cchUdi > 0);
388 char szCleaned[128];
389 size_t cchVendor = strLenStripped(pcszVendor);
390 size_t cchModel = strLenStripped(pcszModel);
391
392 /* Create a cleaned version of the model string for the UDI string. */
393 for (unsigned i = 0; pcszModel[i] != '\0' && i < sizeof(szCleaned); ++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 (cchVendor > 0)
405 RTStrPrintf(pszDesc, cchDesc, "%.*s %s", cchVendor, pcszVendor,
406 cchModel > 0 ? pcszModel : "(unknown drive model)");
407 else
408 RTStrPrintf(pszDesc, cchDesc, "%s", pcszModel);
409 }
410 /* Construct the UDI string */
411 if (pszUdi)
412 {
413 if (cchModel > 0)
414 RTStrPrintf(pszUdi, cchUdi,
415 "/org/freedesktop/Hal/devices/storage_model_%s",
416 szCleaned);
417 else
418 pszUdi[0] = '\0';
419 }
420}
421
422
423/**
424 * Check whether a device node points to a valid device and create a UDI and
425 * a description for it, and store the device number, if it does.
426 * @returns true if the device is valid, false otherwise
427 * @param pcszNode the path to the device node
428 * @param isDVD are we looking for a DVD device (or a floppy device)?
429 * @param pDevice where to store the device node (optional)
430 * @param pszDesc where to store the device description (optional)
431 * @param cchDesc the size of the buffer in @a pszDesc
432 * @param pszUdi where to store the device UDI (optional)
433 * @param cchUdi the size of the buffer in @a pszUdi
434 */
435static bool devValidateDevice(const char *pcszNode, bool isDVD, dev_t *pDevice,
436 char *pszDesc, size_t cchDesc, char *pszUdi,
437 size_t cchUdi)
438{
439 AssertPtrReturn(pcszNode, false);
440 AssertPtrNullReturn(pDevice, false);
441 AssertPtrNullReturn(pszDesc, false);
442 AssertReturn(!pszDesc || cchDesc > 0, false);
443 AssertPtrNullReturn(pszUdi, false);
444 AssertReturn(!pszUdi || cchUdi > 0, false);
445 RTFSOBJINFO ObjInfo;
446 if (RT_FAILURE(RTPathQueryInfo(pcszNode, &ObjInfo, RTFSOBJATTRADD_UNIX)))
447 return false;
448 if (!RTFS_IS_DEV_BLOCK(ObjInfo.Attr.fMode))
449 return false;
450 if (pDevice)
451 *pDevice = ObjInfo.Attr.u.Unix.Device;
452 if (isDVD)
453 {
454 char szVendor[128], szModel[128];
455 uint8_t u8Type;
456 if (!isCdromDevNum(ObjInfo.Attr.u.Unix.Device))
457 return false;
458 if (RT_FAILURE(cdromDoInquiry(pcszNode, &u8Type,
459 szVendor, sizeof(szVendor),
460 szModel, sizeof(szModel))))
461 return false;
462 if (u8Type != TYPE_ROM)
463 return false;
464 dvdCreateDeviceStrings(szVendor, szModel, pszDesc, cchDesc,
465 pszUdi, cchUdi);
466 }
467 else
468 {
469 /* Floppies on Linux are legacy devices with hardcoded majors and
470 * minors */
471 unsigned Number;
472 floppy_drive_name szName;
473 if (major(ObjInfo.Attr.u.Unix.Device) != FLOPPY_MAJOR)
474 return false;
475 switch (minor(ObjInfo.Attr.u.Unix.Device))
476 {
477 case 0: case 1: case 2: case 3:
478 Number = minor(ObjInfo.Attr.u.Unix.Device);
479 break;
480 case 128: case 129: case 130: case 131:
481 Number = minor(ObjInfo.Attr.u.Unix.Device) - 128 + 4;
482 break;
483 default:
484 return false;
485 }
486 if (!floppyGetName(pcszNode, Number, szName))
487 return false;
488 floppyCreateDeviceStrings(szName, Number, pszDesc, cchDesc, pszUdi,
489 cchUdi);
490 }
491 return true;
492}
493
494
495int VBoxMainDriveInfo::updateDVDs ()
496{
497 LogFlowThisFunc(("entered\n"));
498 int rc = VINF_SUCCESS;
499 bool success = false; /* Have we succeeded in finding anything yet? */
500 try
501 {
502 mDVDList.clear ();
503 /* Always allow the user to override our auto-detection using an
504 * environment variable. */
505 if (RT_SUCCESS(rc) && (!success || testing()))
506 rc = getDriveInfoFromEnv ("VBOX_CDROM", &mDVDList, true /* isDVD */,
507 &success);
508 setNoProbe(false);
509 if (RT_SUCCESS(rc) && (!success | testing()))
510 rc = getDriveInfoFromSysfs(&mDVDList, true /* isDVD */, &success);
511 if (RT_SUCCESS(rc) && testing())
512 {
513 setNoProbe(true);
514 rc = getDriveInfoFromSysfs(&mDVDList, true /* isDVD */, &success);
515 }
516 /* Walk through the /dev subtree if nothing else has helped. */
517 if (RT_SUCCESS(rc) && (!success | testing()))
518 rc = getDriveInfoFromDev(&mDVDList, true /* isDVD */, &success);
519 }
520 catch(std::bad_alloc &e)
521 {
522 rc = VERR_NO_MEMORY;
523 }
524 LogFlowThisFunc(("rc=%Rrc\n", rc));
525 return rc;
526}
527
528int VBoxMainDriveInfo::updateFloppies ()
529{
530 LogFlowThisFunc(("entered\n"));
531 int rc = VINF_SUCCESS;
532 bool success = false; /* Have we succeeded in finding anything yet? */
533 try
534 {
535 mFloppyList.clear ();
536 if (RT_SUCCESS(rc) && (!success || testing()))
537 rc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList,
538 false /* isDVD */, &success);
539 setNoProbe(false);
540 if ( RT_SUCCESS(rc) && (!success || testing()))
541 rc = getDriveInfoFromSysfs(&mFloppyList, false /* isDVD */,
542 &success);
543 if (RT_SUCCESS(rc) && testing())
544 {
545 setNoProbe(true);
546 rc = getDriveInfoFromSysfs(&mFloppyList, false /* isDVD */, &success);
547 }
548 /* Walk through the /dev subtree if nothing else has helped. */
549 if ( RT_SUCCESS(rc) && (!success || testing()))
550 rc = getDriveInfoFromDev(&mFloppyList, false /* isDVD */,
551 &success);
552 }
553 catch(std::bad_alloc &e)
554 {
555 rc = VERR_NO_MEMORY;
556 }
557 LogFlowThisFunc(("rc=%Rrc\n", rc));
558 return rc;
559}
560
561
562/**
563 * Extract the names of drives from an environment variable and add them to a
564 * list if they are valid.
565 * @returns iprt status code
566 * @param pcszVar the name of the environment variable. The variable
567 * value should be a list of device node names, separated
568 * by ':' characters.
569 * @param pList the list to append the drives found to
570 * @param isDVD are we looking for DVD drives or for floppies?
571 * @param pfSuccess this will be set to true if we found at least one drive
572 * and to false otherwise. Optional.
573 */
574/* static */
575int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList,
576 bool isDVD, bool *pfSuccess)
577{
578 AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
579 AssertPtrReturn(pList, VERR_INVALID_POINTER);
580 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
581 LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar,
582 pList, isDVD, pfSuccess));
583 int rc = VINF_SUCCESS;
584 bool success = false;
585 char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
586
587 try
588 {
589 const char *pcszCurrent = pszFreeMe;
590 while (pcszCurrent && *pcszCurrent != '\0')
591 {
592 const char *pcszNext = strchr(pcszCurrent, ':');
593 char szPath[RTPATH_MAX], szReal[RTPATH_MAX];
594 char szDesc[256], szUdi[256];
595 if (pcszNext)
596 RTStrPrintf(szPath, sizeof(szPath), "%.*s",
597 pcszNext - pcszCurrent - 1, pcszCurrent);
598 else
599 RTStrPrintf(szPath, sizeof(szPath), "%s", pcszCurrent);
600 if ( RT_SUCCESS(RTPathReal(szPath, szReal, sizeof(szReal)))
601 && devValidateDevice(szReal, isDVD, NULL, szDesc,
602 sizeof(szDesc), szUdi, sizeof(szUdi)))
603 {
604 pList->push_back(DriveInfo(szReal, szUdi, szDesc));
605 success = true;
606 }
607 pcszCurrent = pcszNext ? pcszNext + 1 : NULL;
608 }
609 if (pfSuccess != NULL)
610 *pfSuccess = success;
611 }
612 catch(std::bad_alloc &e)
613 {
614 rc = VERR_NO_MEMORY;
615 }
616 RTStrFree(pszFreeMe);
617 LogFlowFunc(("rc=%Rrc, success=%d\n", rc, success));
618 return rc;
619}
620
621
622class sysfsBlockDev
623{
624public:
625 sysfsBlockDev(const char *pcszName, bool wantDVD)
626 : mpcszName(pcszName), mwantDVD(wantDVD), misConsistent(true),
627 misValid(false)
628 {
629 if (findDeviceNode())
630 {
631 if (mwantDVD)
632 validateAndInitForDVD();
633 else
634 validateAndInitForFloppy();
635 }
636 }
637private:
638 /** The name of the subdirectory of /sys/block for this device */
639 const char *mpcszName;
640 /** Are we looking for a floppy or a DVD device? */
641 bool mwantDVD;
642 /** The device node for the device */
643 char mszNode[RTPATH_MAX];
644 /** Does the sysfs entry look like we expect it too? This is a canary
645 * for future sysfs ABI changes. */
646 bool misConsistent;
647 /** Is this entry a valid specimen of what we are looking for? */
648 bool misValid;
649 /** Human readible drive description string */
650 char mszDesc[256];
651 /** Unique identifier for the drive. Should be identical to hal's UDI for
652 * the device. May not be unique for two identical drives. */
653 char mszUdi[256];
654private:
655 /* Private methods */
656
657 /**
658 * Fill in the device node member based on the /sys/block subdirectory.
659 * @returns boolean success value
660 */
661 bool findDeviceNode()
662 {
663 dev_t dev = RTLinuxSysFsReadDevNumFile("block/%s/dev", mpcszName);
664 if (dev == 0)
665 {
666 misConsistent = false;
667 return false;
668 }
669 if (RTLinuxFindDevicePath(dev, RTFS_TYPE_DEV_BLOCK, mszNode,
670 sizeof(mszNode), "%s", mpcszName) < 0)
671 return false;
672 return true;
673 }
674
675 /** Check whether the sysfs block entry is valid for a DVD device and
676 * initialise the string data members for the object. We try to get all
677 * the information we need from sysfs if possible, to avoid unnecessarily
678 * poking the device, and if that fails we fall back to an SCSI INQUIRY
679 * command. */
680 void validateAndInitForDVD()
681 {
682 char szVendor[128], szModel[128];
683 ssize_t cchVendor, cchModel;
684 int64_t type = RTLinuxSysFsReadIntFile(10, "block/%s/device/type",
685 mpcszName);
686 if (type >= 0 && type != TYPE_ROM)
687 return;
688 if (type == TYPE_ROM)
689 {
690 cchVendor = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor),
691 "block/%s/device/vendor",
692 mpcszName);
693 if (cchVendor >= 0)
694 {
695 cchModel = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel),
696 "block/%s/device/model",
697 mpcszName);
698 if (cchModel >= 0)
699 {
700 misValid = true;
701 dvdCreateDeviceStrings(szVendor, szModel,
702 mszDesc, sizeof(mszDesc),
703 mszUdi, sizeof(mszUdi));
704 return;
705 }
706 }
707 }
708 if (!noProbe())
709 probeAndInitForDVD();
710 }
711
712 /** Try to find out whether a device is a DVD drive by sending it an
713 * SCSI INQUIRY command. If it is, initialise the string and validity
714 * data members for the object based on the returned data.
715 */
716 void probeAndInitForDVD()
717 {
718 AssertReturnVoid(mszNode[0] != '\0');
719 uint8_t u8Type = 0;
720 char szVendor[128] = "";
721 char szModel[128] = "";
722 int rc = cdromDoInquiry(mszNode, &u8Type, szVendor,
723 sizeof(szVendor), szModel,
724 sizeof(szModel));
725 if (RT_SUCCESS(rc) && (u8Type == TYPE_ROM))
726 {
727 misValid = true;
728 dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc),
729 mszUdi, sizeof(mszUdi));
730 }
731 }
732
733 /** Check whether the sysfs block entry is valid for a floppy device and
734 * initialise the string data members for the object. Since we only
735 * support floppies using the basic "floppy" driver, we check the driver
736 * using the entry name and a driver-specific ioctl. */
737 void validateAndInitForFloppy()
738 {
739 bool haveName = false;
740 floppy_drive_name szName;
741 char szDriver[8];
742 if ( mpcszName[0] != 'f'
743 || mpcszName[1] != 'd'
744 || mpcszName[2] < '0'
745 || mpcszName[2] > '7'
746 || mpcszName[3] != '\0')
747 return;
748 if (!noProbe())
749 haveName = floppyGetName(mszNode, mpcszName[2] - '0', szName);
750 if (RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), "block/%s/%s",
751 mpcszName, "device/driver") >= 0)
752 {
753 if (RTStrCmp(szDriver, "floppy"))
754 return;
755 }
756 else if (!haveName)
757 return;
758 floppyCreateDeviceStrings(haveName ? szName : NULL,
759 mpcszName[2] - '0', mszDesc,
760 sizeof(mszDesc), mszUdi, sizeof(mszUdi));
761 misValid = true;
762 }
763
764public:
765 bool isConsistent()
766 {
767 return misConsistent;
768 }
769 bool isValid()
770 {
771 return misValid;
772 }
773 const char *getDesc()
774 {
775 return mszDesc;
776 }
777 const char *getUdi()
778 {
779 return mszUdi;
780 }
781 const char *getNode()
782 {
783 return mszNode;
784 }
785};
786
787/**
788 * Helper function to query the sysfs subsystem for information about DVD
789 * drives attached to the system.
790 * @returns iprt status code
791 * @param pList where to add information about the drives detected
792 * @param isDVD are we looking for DVDs or floppies?
793 * @param pfSuccess Did we find anything?
794 *
795 * @returns IPRT status code
796 */
797/* static */
798int getDriveInfoFromSysfs(DriveInfoList *pList, bool isDVD, bool *pfSuccess)
799{
800 AssertPtrReturn(pList, VERR_INVALID_POINTER);
801 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
802 LogFlowFunc (("pList=%p, isDVD=%u, pfSuccess=%p\n",
803 pList, (unsigned) isDVD, pfSuccess));
804 PRTDIR pDir = NULL;
805 int rc;
806 bool fSuccess = false;
807 unsigned cFound = 0;
808
809 if (!RTPathExists("/sys"))
810 return VINF_SUCCESS;
811 rc = RTDirOpen(&pDir, "/sys/block");
812 /* This might mean that sysfs semantics have changed */
813 AssertReturn(rc != VERR_FILE_NOT_FOUND, VINF_SUCCESS);
814 fSuccess = true;
815 if (RT_SUCCESS(rc))
816 for (;;)
817 {
818 RTDIRENTRY entry;
819 rc = RTDirRead(pDir, &entry, NULL);
820 Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
821 if (RT_FAILURE(rc)) /* Including overflow and no more files */
822 break;
823 if (entry.szName[0] == '.')
824 continue;
825 sysfsBlockDev dev(entry.szName, isDVD);
826 /* This might mean that sysfs semantics have changed */
827 AssertBreakStmt(dev.isConsistent(), fSuccess = false);
828 if (!dev.isValid())
829 continue;
830 try
831 {
832 pList->push_back(DriveInfo(dev.getNode(), dev.getUdi(),
833 dev.getDesc()));
834 }
835 catch(std::bad_alloc &e)
836 {
837 rc = VERR_NO_MEMORY;
838 break;
839 }
840 ++cFound;
841 }
842 RTDirClose(pDir);
843 if (rc == VERR_NO_MORE_FILES)
844 rc = VINF_SUCCESS;
845 if (RT_FAILURE(rc))
846 /* Clean up again */
847 for (unsigned i = 0; i < cFound; ++i)
848 pList->pop_back();
849 if (pfSuccess)
850 *pfSuccess = fSuccess;
851 LogFlow (("rc=%Rrc, fSuccess=%u\n", rc, (unsigned) fSuccess));
852 return rc;
853}
854
855
856/** Structure for holding information about a drive we have found */
857struct deviceNodeInfo
858{
859 /** The device number */
860 dev_t Device;
861 /** The device node path */
862 char szPath[RTPATH_MAX];
863 /** The device description */
864 char szDesc[256];
865 /** The device UDI */
866 char szUdi[256];
867};
868
869/** The maximum number of devices we will search for. */
870enum { MAX_DEVICE_NODES = 8 };
871/** An array of MAX_DEVICE_NODES devices */
872typedef struct deviceNodeInfo deviceNodeArray[MAX_DEVICE_NODES];
873
874/**
875 * Recursive worker function to walk the /dev tree looking for DVD or floppy
876 * devices.
877 * @returns true if we have already found MAX_DEVICE_NODES devices, false
878 * otherwise
879 * @param pszPath the path to start recursing. The function can modify
880 * this string at and after the terminating zero
881 * @param cchPath the size of the buffer (not the string!) in @a pszPath
882 * @param aDevices where to fill in information about devices that we have
883 * found
884 * @param wantDVD are we looking for DVD devices (or floppies)?
885 */
886static bool devFindDeviceRecursive(char *pszPath, size_t cchPath,
887 deviceNodeArray aDevices, bool wantDVD)
888{
889 /*
890 * Check assumptions made by the code below.
891 */
892 size_t const cchBasePath = strlen(pszPath);
893 AssertReturn(cchBasePath < RTPATH_MAX - 10U, false);
894 AssertReturn(pszPath[cchBasePath - 1] != '/', false);
895
896 PRTDIR pDir;
897 if (RT_FAILURE(RTDirOpen(&pDir, pszPath)))
898 return false;
899 for (;;)
900 {
901 RTDIRENTRY Entry;
902 RTFSOBJINFO ObjInfo;
903 int rc = RTDirRead(pDir, &Entry, NULL);
904 if (RT_FAILURE(rc))
905 break;
906 if (Entry.enmType == RTDIRENTRYTYPE_UNKNOWN)
907 {
908 if (RT_FAILURE(RTPathQueryInfo(pszPath, &ObjInfo,
909 RTFSOBJATTRADD_UNIX)))
910 continue;
911 if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
912 continue;
913 }
914
915 if (Entry.enmType == RTDIRENTRYTYPE_SYMLINK)
916 continue;
917 pszPath[cchBasePath] = '\0';
918 if (RT_FAILURE(RTPathAppend(pszPath, cchPath, Entry.szName)))
919 break;
920
921 /* Do the matching. */
922 dev_t DevNode;
923 char szDesc[256], szUdi[256];
924 if (!devValidateDevice(pszPath, wantDVD, &DevNode, szDesc,
925 sizeof(szDesc), szUdi, sizeof(szUdi)))
926 continue;
927 unsigned i;
928 for (i = 0; i < MAX_DEVICE_NODES; ++i)
929 if (!aDevices[i].Device || (aDevices[i].Device == DevNode))
930 break;
931 AssertBreak(i < MAX_DEVICE_NODES);
932 if (aDevices[i].Device)
933 continue;
934 aDevices[i].Device = DevNode;
935 RTStrPrintf(aDevices[i].szPath, sizeof(aDevices[i].szPath),
936 "%s", pszPath);
937 AssertCompile(sizeof(aDevices[i].szDesc) == sizeof(szDesc));
938 strcpy(aDevices[i].szDesc, szDesc);
939 AssertCompile(sizeof(aDevices[i].szUdi) == sizeof(szUdi));
940 strcpy(aDevices[i].szUdi, szUdi);
941 if (i == MAX_DEVICE_NODES - 1)
942 break;
943 continue;
944
945 /* Recurse into subdirectories. */
946 if ( (Entry.enmType == RTDIRENTRYTYPE_UNKNOWN)
947 && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
948 continue;
949 if (Entry.enmType != RTDIRENTRYTYPE_DIRECTORY)
950 continue;
951 if (Entry.szName[0] == '.')
952 continue;
953
954 if (devFindDeviceRecursive(pszPath, cchPath, aDevices, wantDVD))
955 break;
956 }
957 RTDirClose(pDir);
958 return aDevices[MAX_DEVICE_NODES - 1].Device ? true : false;
959}
960
961
962/**
963 * Recursively walk through the /dev tree and add any DVD or floppy drives we
964 * find and can access to our list. (If we can't access them we can't check
965 * whether or not they are really DVD or floppy drives).
966 * @note this is rather slow (a couple of seconds) for DVD probing on
967 * systems with a static /dev tree, as the current code tries to open
968 * any device node with a major/minor combination that could belong to
969 * a CD-ROM device, and opening a non-existent device can take a non.
970 * negligeable time on Linux. If it is ever necessary to improve this
971 * (static /dev trees are no longer very fashionable these days, and
972 * sysfs looks like it will be with us for a while), we could further
973 * reduce the number of device nodes we open by checking whether the
974 * driver is actually loaded in /proc/devices, and by counting the
975 * of currently attached SCSI CD-ROM devices in /proc/scsi/scsi (yes,
976 * there is a race, but it is probably not important for us).
977 * @returns iprt status code
978 * @param pList the list to append the drives found to
979 * @param isDVD are we looking for DVD drives or for floppies?
980 * @param pfSuccess this will be set to true if we found at least one drive
981 * and to false otherwise. Optional.
982 */
983/* static */
984int getDriveInfoFromDev(DriveInfoList *pList, bool isDVD, bool *pfSuccess)
985{
986 AssertPtrReturn(pList, VERR_INVALID_POINTER);
987 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
988 LogFlowFunc(("pList=%p, isDVD=%d, pfSuccess=%p\n", pList, isDVD,
989 pfSuccess));
990 int rc = VINF_SUCCESS;
991 bool success = false;
992
993 char szPath[RTPATH_MAX] = "/dev";
994 deviceNodeArray aDevices;
995 RT_ZERO(aDevices);
996 devFindDeviceRecursive(szPath, sizeof(szPath), aDevices, isDVD);
997 try
998 {
999 for (unsigned i = 0; i < MAX_DEVICE_NODES; ++i)
1000 {
1001 if (aDevices[i].Device)
1002 {
1003 pList->push_back(DriveInfo(aDevices[i].szPath,
1004 aDevices[i].szUdi, aDevices[i].szDesc));
1005 success = true;
1006 }
1007 }
1008 if (pfSuccess != NULL)
1009 *pfSuccess = success;
1010 }
1011 catch(std::bad_alloc &e)
1012 {
1013 rc = VERR_NO_MEMORY;
1014 }
1015 LogFlowFunc (("rc=%Rrc, success=%d\n", rc, success));
1016 return rc;
1017}
1018
1019
1020int VBoxMainUSBDeviceInfo::UpdateDevices ()
1021{
1022 LogFlowThisFunc(("entered\n"));
1023 int rc = VINF_SUCCESS;
1024 bool success = false; /* Have we succeeded in finding anything yet? */
1025 try
1026 {
1027 mDeviceList.clear();
1028#ifdef VBOX_USB_WITH_SYSFS
1029# ifdef VBOX_USB_WITH_DBUS
1030 bool halSuccess = false;
1031 if ( RT_SUCCESS(rc)
1032 && RT_SUCCESS(RTDBusLoadLib())
1033 && (!success || testing()))
1034 rc = getUSBDeviceInfoFromHal(&mDeviceList, &halSuccess);
1035 /* Try the old API if the new one *succeeded* as only one of them will
1036 * pick up devices anyway. */
1037 if (RT_SUCCESS(rc) && halSuccess && (!success || testing()))
1038 rc = getOldUSBDeviceInfoFromHal(&mDeviceList, &halSuccess);
1039 if (!success)
1040 success = halSuccess;
1041# endif /* VBOX_USB_WITH_DBUS */
1042# ifdef VBOX_USB_WITH_FAM
1043 if ( RT_SUCCESS(rc)
1044 && (!success || testing()))
1045 rc = getUSBDeviceInfoFromSysfs(&mDeviceList, &success);
1046# endif
1047#else /* !VBOX_USB_WITH_SYSFS */
1048 NOREF(success);
1049#endif /* !VBOX_USB_WITH_SYSFS */
1050 }
1051 catch(std::bad_alloc &e)
1052 {
1053 rc = VERR_NO_MEMORY;
1054 }
1055 LogFlowThisFunc(("rc=%Rrc\n", rc));
1056 return rc;
1057}
1058
1059#if defined VBOX_USB_WITH_SYSFS && defined VBOX_USB_WITH_DBUS
1060class hotplugDBusImpl : public VBoxMainHotplugWaiterImpl
1061{
1062 /** The connection to DBus */
1063 RTMemAutoPtr <DBusConnection, VBoxHalShutdownPrivate> mConnection;
1064 /** Semaphore which is set when a device is hotplugged and reset when
1065 * it is read. */
1066 volatile bool mTriggered;
1067 /** A flag to say that we wish to interrupt the current wait. */
1068 volatile bool mInterrupt;
1069
1070public:
1071 /** Test whether this implementation can be used on the current system */
1072 static bool HalAvailable(void)
1073 {
1074 RTMemAutoPtr<DBusConnection, VBoxHalShutdown> dbusConnection;
1075
1076 /* Try to open a test connection to hal */
1077 if (RT_SUCCESS(RTDBusLoadLib()) && RT_SUCCESS(halInit (&dbusConnection)))
1078 return !!dbusConnection;
1079 return false;
1080 }
1081
1082 /** Constructor */
1083 hotplugDBusImpl (void);
1084 virtual ~hotplugDBusImpl (void);
1085 /** @copydoc VBoxMainHotplugWaiter::Wait */
1086 virtual int Wait (RTMSINTERVAL cMillies);
1087 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1088 virtual void Interrupt (void);
1089};
1090
1091/* This constructor sets up a private connection to the DBus daemon, connects
1092 * to the hal service and installs a filter which sets the mTriggered flag in
1093 * the Context structure when a device (not necessarily USB) is added or
1094 * removed. */
1095hotplugDBusImpl::hotplugDBusImpl (void) : mTriggered(false), mInterrupt(false)
1096{
1097 int rc = VINF_SUCCESS;
1098
1099 if (RT_SUCCESS(RTDBusLoadLib()))
1100 {
1101 for (unsigned i = 0; RT_SUCCESS(rc) && i < 5 && !mConnection; ++i)
1102 {
1103 rc = halInitPrivate (&mConnection);
1104 }
1105 if (!mConnection)
1106 rc = VERR_NOT_SUPPORTED;
1107 DBusMessage *pMessage;
1108 while ( RT_SUCCESS(rc)
1109 && (pMessage = dbus_connection_pop_message (mConnection.get())) != NULL)
1110 dbus_message_unref (pMessage); /* empty the message queue. */
1111 if ( RT_SUCCESS(rc)
1112 && !dbus_connection_add_filter (mConnection.get(),
1113 dbusFilterFunction,
1114 (void *) &mTriggered, NULL))
1115 rc = VERR_NO_MEMORY;
1116 if (RT_FAILURE(rc))
1117 mConnection.reset();
1118 }
1119}
1120
1121/* Destructor */
1122hotplugDBusImpl::~hotplugDBusImpl ()
1123{
1124 if (!!mConnection)
1125 dbus_connection_remove_filter (mConnection.get(), dbusFilterFunction,
1126 (void *) &mTriggered);
1127}
1128
1129/* Currently this is implemented using a timed out wait on our private DBus
1130 * connection. Because the connection is private we don't have to worry about
1131 * blocking other users. */
1132int hotplugDBusImpl::Wait(RTMSINTERVAL cMillies)
1133{
1134 int rc = VINF_SUCCESS;
1135 if (!mConnection)
1136 rc = VERR_NOT_SUPPORTED;
1137 bool connected = true;
1138 mTriggered = false;
1139 mInterrupt = false;
1140 unsigned cRealMillies;
1141 if (cMillies != RT_INDEFINITE_WAIT)
1142 cRealMillies = cMillies;
1143 else
1144 cRealMillies = DBUS_POLL_TIMEOUT;
1145 while ( RT_SUCCESS(rc) && connected && !mTriggered
1146 && !mInterrupt)
1147 {
1148 connected = dbus_connection_read_write_dispatch (mConnection.get(),
1149 cRealMillies);
1150 if (mInterrupt)
1151 LogFlowFunc(("wait loop interrupted\n"));
1152 if (cMillies != RT_INDEFINITE_WAIT)
1153 mInterrupt = true;
1154 }
1155 if (!connected)
1156 rc = VERR_TRY_AGAIN;
1157 return rc;
1158}
1159
1160/* Set a flag to tell the Wait not to resume next time it times out. */
1161void hotplugDBusImpl::Interrupt()
1162{
1163 LogFlowFunc(("\n"));
1164 mInterrupt = true;
1165}
1166#endif /* VBOX_USB_WITH_SYSFS && VBOX_USB_WITH_DBUS */
1167
1168class hotplugNullImpl : public VBoxMainHotplugWaiterImpl
1169{
1170public:
1171 hotplugNullImpl (void) {}
1172 virtual ~hotplugNullImpl (void) {}
1173 /** @copydoc VBoxMainHotplugWaiter::Wait */
1174 virtual int Wait (RTMSINTERVAL)
1175 {
1176 return VERR_NOT_SUPPORTED;
1177 }
1178 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1179 virtual void Interrupt (void) {}
1180};
1181
1182#ifdef VBOX_USB_WITH_SYSFS
1183# ifdef VBOX_USB_WITH_FAM
1184
1185# define SYSFS_USB_DEVICE_PATH "/dev/bus/usb"
1186# define SYSFS_WAKEUP_STRING "Wake up!"
1187
1188class hotplugSysfsFAMImpl : public VBoxMainHotplugWaiterImpl
1189{
1190 /** Pipe used to interrupt wait(), the read end. */
1191 RTPIPE mhWakeupPipeR;
1192 /** Pipe used to interrupt wait(), the write end. */
1193 RTPIPE mhWakeupPipeW;
1194 /** Our connection to FAM for polling for changes on sysfs. */
1195 FAMConnection mFAMConnection;
1196 /** Has our connection been initialised? */
1197 bool mfFAMInitialised;
1198 /** The iprt native handle of the FAM fd socket. */
1199 RTSOCKET mhFAMFD;
1200 /** Poll set containing the FAM socket and the termination pipe */
1201 RTPOLLSET mhPollSet;
1202 /** Flag to mark that the Wait() method is currently being called, and to
1203 * ensure that it isn't called multiple times in parallel. */
1204 uint32_t mfWaiting;
1205 /** iprt result code from object initialisation. Should be AssertReturn-ed
1206 * on at the start of all methods. I went this way because I didn't want
1207 * to deal with exceptions. */
1208 int mStatus;
1209 /** ID values associates with the wakeup pipe and the FAM socket for polling
1210 */
1211 enum
1212 {
1213 RPIPE_ID = 1,
1214 FAMFD_ID
1215 };
1216
1217 /** Initialise the connection to the FAM daemon, allocating all required
1218 * resources.
1219 * @returns iprt status code
1220 */
1221 int initConnection(void);
1222
1223 /** Clean up the connection to the FAM daemon, freeing any allocated
1224 * resources and gracefully skipping over any which have not yet been
1225 * allocated or already cleaned up.
1226 * @returns iprt status code
1227 */
1228 void termConnection(void);
1229
1230 /** Clean up any resources in use, gracefully skipping over any which have
1231 * not yet been allocated or already cleaned up. Intended to be called
1232 * from the destructor or after a failed initialisation. */
1233 void term(void);
1234
1235 /** Make sure that the object is correctly initialised and re-initialises
1236 * it if not, and if the maximum number of connection attempts has not been
1237 * reached.
1238 * @returns iprt status value
1239 * @returns VERR_TRY_AGAIN if we are giving up on this attempt but may
1240 * still succeed on future attempts
1241 */
1242 int checkConnection(void);
1243
1244 /** Quick failure test of the checkConnection() function. */
1245 void testCheckConnection(void);
1246
1247 /** Open our connection to FAM and convert the status to iprt.
1248 * @todo really convert the status
1249 */
1250 int openFAM(void)
1251 {
1252 if (FAMOpen(&mFAMConnection) < 0)
1253 return VERR_FAM_OPEN_FAILED;
1254 mfFAMInitialised = true;
1255 return VINF_SUCCESS;
1256 }
1257
1258 /** Monitor a file through the FAM connection. */
1259 int monitorDirectoryFAM(const char *pszName)
1260 {
1261 AssertReturn(mfFAMInitialised, VERR_WRONG_ORDER);
1262 FAMRequest dummyReq;
1263 if (FAMMonitorDirectory(&mFAMConnection, pszName, &dummyReq, NULL) < 0)
1264 return VERR_FAM_MONITOR_DIRECTORY_FAILED;
1265 return VINF_SUCCESS;
1266 }
1267
1268 /** Quick failure test of the monitor function - we temporarily invalidate
1269 * the connection FD to trigger an error path. */
1270 void testMonitorDirectoryFAM(void)
1271 {
1272 int oldFD = FAMCONNECTION_GETFD(&mFAMConnection);
1273 FAMCONNECTION_GETFD(&mFAMConnection) = -1;
1274 Assert(monitorDirectoryFAM("") == VERR_FAM_MONITOR_DIRECTORY_FAILED);
1275 FAMCONNECTION_GETFD(&mFAMConnection) = oldFD;
1276 }
1277
1278 int nextEventFAM(FAMEvent *pEv)
1279 {
1280 if (FAMNextEvent(&mFAMConnection, pEv) == 1)
1281 return VINF_SUCCESS;
1282 mStatus = VERR_FAM_CONNECTION_LOST;
1283 return VERR_TRY_AGAIN;
1284 }
1285
1286 /** Quick failure test of the nextevent function. */
1287 void testNextEventFAM(void)
1288 {
1289 int oldStatus = mStatus;
1290 mStatus = VINF_SUCCESS;
1291 //Assert(nextEventFAM(NULL) == VERR_TRY_AGAIN);
1292 mStatus = oldStatus;
1293 }
1294
1295 /** Close our connection to FAM. We ignore errors as there is no
1296 * documentation as to what they mean, and the only error which might
1297 * interest us (EINTR) should be (but isn't) handled inside the library. */
1298 void closeFAM(void)
1299 {
1300 if (mfFAMInitialised)
1301 FAMClose(&mFAMConnection);
1302 mfFAMInitialised = false;
1303 }
1304
1305 /** Read the wakeup string from the wakeup pipe */
1306 int drainWakeupPipe(void);
1307public:
1308 hotplugSysfsFAMImpl(void);
1309 virtual ~hotplugSysfsFAMImpl(void)
1310 {
1311 term();
1312#ifdef DEBUG
1313 /** The first call to term should mark all resources as freed, so this
1314 * should be a semantic no-op. */
1315 term();
1316#endif
1317 }
1318 /** Is sysfs available on this system? If so we expect that this
1319 * implementation will be usable. */
1320 static bool SysfsAvailable(void)
1321 {
1322 return RTDirExists(SYSFS_USB_DEVICE_PATH);
1323 }
1324 /** @copydoc VBoxMainHotplugWaiter::Wait */
1325 virtual int Wait(RTMSINTERVAL);
1326 /** @copydoc VBoxMainHotplugWaiter::Interrupt */
1327 virtual void Interrupt(void);
1328};
1329
1330hotplugSysfsFAMImpl::hotplugSysfsFAMImpl(void) :
1331 mhWakeupPipeR(NIL_RTPIPE), mhWakeupPipeW(NIL_RTPIPE),
1332 mfFAMInitialised(false), mhFAMFD(NIL_RTSOCKET), mhPollSet(NIL_RTPOLLSET),
1333 mfWaiting(0), mStatus(VERR_WRONG_ORDER)
1334{
1335# ifdef DEBUG
1336 /* Excercise the code path (term() on a not-fully-initialised object) as
1337 * well as we can. On an uninitialised object this method is a sematic
1338 * no-op. */
1339 term();
1340 /* For now this probing method should only be used if nothing else is
1341 * available */
1342 if (!testing())
1343 {
1344# ifndef VBOX_WITH_SYSFS_BY_DEFAULT
1345 /** @todo only create this object if we will use it */
1346 // Assert(!RTFileExists("/proc/bus/usb/devices"));
1347# endif
1348# ifdef VBOX_USB_WITH_DBUS
1349 Assert(!hotplugDBusImpl::HalAvailable());
1350# endif
1351 }
1352# endif
1353 int rc;
1354 do {
1355 if (RT_FAILURE(rc = RTPipeCreate(&mhWakeupPipeR, &mhWakeupPipeW, 0)))
1356 break;
1357 if (RT_FAILURE(rc = initConnection()))
1358 break;
1359# ifdef DEBUG
1360 /** Other tests */
1361 testMonitorDirectoryFAM();
1362 testNextEventFAM();
1363 testCheckConnection();
1364# endif
1365 } while(0);
1366 mStatus = rc;
1367 if (RT_FAILURE(rc))
1368 term();
1369}
1370
1371int hotplugSysfsFAMImpl::initConnection(void)
1372{
1373 int rc;
1374
1375 if (RT_FAILURE(rc = openFAM()))
1376 return rc;
1377 if (RT_FAILURE(rc = RTSocketFromNative
1378 (&mhFAMFD, FAMCONNECTION_GETFD(&mFAMConnection))))
1379 return rc;
1380 if (RT_FAILURE(rc = monitorDirectoryFAM(SYSFS_USB_DEVICE_PATH)))
1381 return rc;
1382 if (RT_FAILURE(rc = RTPollSetCreate(&mhPollSet)))
1383 return rc;
1384 if (RT_FAILURE(rc = RTPollSetAddSocket
1385 (mhPollSet, mhFAMFD, RTPOLL_EVT_READ, FAMFD_ID)))
1386 return rc;
1387 AssertReturn(mhWakeupPipeR != NIL_RTPIPE, VERR_WRONG_ORDER);
1388 if (RT_FAILURE(rc = RTPollSetAddPipe(mhPollSet, mhWakeupPipeR,
1389 RTPOLL_EVT_READ, RPIPE_ID)))
1390 return rc;
1391 return VINF_SUCCESS;
1392}
1393
1394void hotplugSysfsFAMImpl::termConnection(void)
1395{
1396 closeFAM();
1397 mhFAMFD = NIL_RTSOCKET;
1398 RTPollSetDestroy(mhPollSet);
1399 mhPollSet = NIL_RTPOLLSET;
1400}
1401
1402int hotplugSysfsFAMImpl::checkConnection(void)
1403{
1404 /** We should only be called from within Wait(). */
1405 AssertReturn(mfWaiting, VERR_WRONG_ORDER);
1406 if (mStatus == VERR_FAM_CONNECTION_LOST)
1407 {
1408 termConnection();
1409 mStatus = initConnection();
1410 }
1411 return mStatus;
1412}
1413
1414void hotplugSysfsFAMImpl::testCheckConnection(void)
1415{
1416 int oldStatus = mStatus;
1417 mStatus = VERR_UNRESOLVED_ERROR;
1418
1419 bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
1420 AssertReturnVoid(fEntered);
1421 Assert(checkConnection() == VERR_UNRESOLVED_ERROR);
1422 mStatus = VERR_FAM_CONNECTION_LOST;
1423 AssertRC(checkConnection());
1424 mStatus = oldStatus;
1425 mfWaiting = 0;
1426}
1427
1428void hotplugSysfsFAMImpl::term(void)
1429{
1430 /** This would probably be a pending segfault, so die cleanly */
1431 AssertRelease(!mfWaiting);
1432 termConnection();
1433 RTPipeClose(mhWakeupPipeR);
1434 mhWakeupPipeR = NIL_RTPIPE;
1435 RTPipeClose(mhWakeupPipeW);
1436 mhWakeupPipeW = NIL_RTPIPE;
1437}
1438
1439/** Does a FAM event code mean that the available devices have (probably)
1440 * changed? */
1441static int sysfsGetStatusForFAMCode(FAMCodes enmCode)
1442{
1443 if (enmCode == FAMExists || enmCode == FAMEndExist)
1444 return VERR_TRY_AGAIN;
1445 return VINF_SUCCESS;
1446}
1447
1448int hotplugSysfsFAMImpl::drainWakeupPipe(void)
1449{
1450 char szBuf[sizeof(SYSFS_WAKEUP_STRING)];
1451 size_t cbDummy;
1452
1453 int rc = RTPipeRead(mhWakeupPipeR, szBuf, sizeof(szBuf), &cbDummy);
1454 AssertRC(rc);
1455 return VINF_SUCCESS;
1456}
1457
1458int hotplugSysfsFAMImpl::Wait(RTMSINTERVAL aMillies)
1459{
1460 uint32_t id;
1461 int rc;
1462 FAMEvent ev;
1463
1464 bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
1465 AssertReturn(fEntered, VERR_WRONG_ORDER);
1466 do {
1467 if (RT_FAILURE(rc = checkConnection()))
1468 break;
1469 /* timeout returns */
1470 if (RT_FAILURE(rc = RTPoll(mhPollSet, aMillies, NULL, &id)))
1471 break;
1472 if (id == RPIPE_ID)
1473 {
1474 rc = drainWakeupPipe();
1475 break;
1476 }
1477 AssertBreakStmt(id == FAMFD_ID, rc = VERR_NOT_SUPPORTED);
1478 if (RT_FAILURE(rc = nextEventFAM(&ev)))
1479 break;
1480 rc = sysfsGetStatusForFAMCode(ev.code);
1481 } while (false);
1482 mfWaiting = 0;
1483 /* If at all, this should only get called once. */
1484 AssertLogRelMsg( RT_SUCCESS(rc)
1485 || rc == VERR_TRY_AGAIN
1486 || rc == VERR_FAM_OPEN_FAILED
1487 || rc == VERR_TIMEOUT, ("rc = %Rrc\n", rc));
1488 return rc;
1489}
1490
1491void hotplugSysfsFAMImpl::Interrupt(void)
1492{
1493 size_t cbDummy;
1494 int rc = RTPipeWrite(mhWakeupPipeW, SYSFS_WAKEUP_STRING,
1495 sizeof(SYSFS_WAKEUP_STRING), &cbDummy);
1496 if (RT_SUCCESS(rc))
1497 RTPipeFlush(mhWakeupPipeW);
1498}
1499
1500# endif /* VBOX_USB_WITH_FAM */
1501#endif /* VBOX_USB_WTH_SYSFS */
1502
1503VBoxMainHotplugWaiter::VBoxMainHotplugWaiter(void)
1504{
1505#ifdef VBOX_USB_WITH_SYSFS
1506# ifdef VBOX_USB_WITH_DBUS
1507 if (hotplugDBusImpl::HalAvailable())
1508 {
1509 mImpl = new hotplugDBusImpl;
1510 return;
1511 }
1512# endif /* VBOX_USB_WITH_DBUS */
1513# ifdef VBOX_USB_WITH_FAM
1514 if (hotplugSysfsFAMImpl::SysfsAvailable())
1515 {
1516 mImpl = new hotplugSysfsFAMImpl;
1517 return;
1518 }
1519# endif /* VBOX_USB_WITH_FAM */
1520#endif /* VBOX_USB_WITH_SYSFS */
1521 mImpl = new hotplugNullImpl;
1522}
1523
1524#ifdef VBOX_USB_WITH_SYSFS
1525# ifdef VBOX_USB_WITH_FAM
1526class sysfsPathHandler
1527{
1528 /** Called on each element of the sysfs directory. Can e.g. store
1529 * interesting entries in a list. */
1530 virtual bool handle(const char *pcszNode) = 0;
1531public:
1532 bool doHandle(const char *pcszNode)
1533 {
1534 AssertPtr(pcszNode);
1535 Assert(pcszNode[0] == '/');
1536 Assert(RTPathExists(pcszNode));
1537 return handle(pcszNode);
1538 }
1539};
1540
1541/**
1542 * Helper function to walk a sysfs directory for extracting information about
1543 * devices.
1544 * @returns iprt status code
1545 * @param pcszPath Sysfs directory to walk. Must exist.
1546 * @param pHandler Handler object which will be invoked on each directory
1547 * entry
1548 *
1549 * @returns IPRT status code
1550 */
1551/* static */
1552int getDeviceInfoFromSysfs(const char *pcszPath, sysfsPathHandler *pHandler)
1553{
1554 AssertPtrReturn(pcszPath, VERR_INVALID_POINTER);
1555 AssertPtrReturn(pHandler, VERR_INVALID_POINTER);
1556 LogFlowFunc (("pcszPath=%s, pHandler=%p\n", pcszPath, pHandler));
1557 PRTDIR pDir = NULL;
1558 int rc;
1559
1560 rc = RTDirOpen(&pDir, pcszPath);
1561 AssertRCReturn(rc, rc);
1562 while (RT_SUCCESS(rc))
1563 {
1564 RTDIRENTRY entry;
1565 char szPath[RTPATH_MAX], szAbsPath[RTPATH_MAX];
1566
1567 rc = RTDirRead(pDir, &entry, NULL);
1568 Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
1569 /* We break on "no more files" as well as on "real" errors */
1570 if (RT_FAILURE(rc))
1571 break;
1572 if (entry.szName[0] == '.')
1573 continue;
1574 if (RTStrPrintf(szPath, sizeof(szPath), "%s/%s", pcszPath,
1575 entry.szName) >= sizeof(szPath))
1576 rc = VERR_BUFFER_OVERFLOW;
1577 if (RT_FAILURE(rc))
1578 break;
1579 rc = RTPathReal(szPath, szAbsPath, sizeof(szAbsPath));
1580 AssertRCBreak(rc); /* sysfs should guarantee that this exists */
1581 if (!pHandler->doHandle(szAbsPath))
1582 break;
1583 }
1584 RTDirClose(pDir);
1585 if (rc == VERR_NO_MORE_FILES)
1586 rc = VINF_SUCCESS;
1587 LogFlow (("rc=%Rrc\n", rc));
1588 return rc;
1589}
1590
1591
1592#define USBDEVICE_MAJOR 189
1593
1594/** Deduce the bus that a USB device is plugged into from the device node
1595 * number. See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20. */
1596static unsigned usbBusFromDevNum(dev_t devNum)
1597{
1598 AssertReturn(devNum, 0);
1599 AssertReturn(major(devNum) == USBDEVICE_MAJOR, 0);
1600 return (minor(devNum) >> 7) + 1;
1601}
1602
1603
1604/** Deduce the device number of a USB device on the bus from the device node
1605 * number. See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20. */
1606static unsigned usbDeviceFromDevNum(dev_t devNum)
1607{
1608 AssertReturn(devNum, 0);
1609 AssertReturn(major(devNum) == USBDEVICE_MAJOR, 0);
1610 return (minor(devNum) & 127) + 1;
1611}
1612
1613
1614/**
1615 * Tell whether a file in /sys/bus/usb/devices is a device rather than an
1616 * interface. To be used with getDeviceInfoFromSysfs().
1617 */
1618class matchUSBDevice : public sysfsPathHandler
1619{
1620 USBDeviceInfoList *mList;
1621public:
1622 matchUSBDevice(USBDeviceInfoList *pList) : mList(pList) {}
1623private:
1624 virtual bool handle(const char *pcszNode)
1625 {
1626 const char *pcszFile = strrchr(pcszNode, '/');
1627 if (strchr(pcszFile, ':'))
1628 return true;
1629 dev_t devnum = RTLinuxSysFsReadDevNumFile("%s/dev", pcszNode);
1630 /* Sanity test of our static helpers */
1631 Assert(usbBusFromDevNum(makedev(USBDEVICE_MAJOR, 517)) == 5);
1632 Assert(usbDeviceFromDevNum(makedev(USBDEVICE_MAJOR, 517)) == 6);
1633 AssertReturn (devnum, true);
1634 char szDevPath[RTPATH_MAX];
1635 ssize_t cchDevPath;
1636 cchDevPath = RTLinuxFindDevicePath(devnum, RTFS_TYPE_DEV_CHAR,
1637 szDevPath, sizeof(szDevPath),
1638 "/dev/bus/usb/%.3d/%.3d",
1639 usbBusFromDevNum(devnum),
1640 usbDeviceFromDevNum(devnum));
1641 if (cchDevPath < 0)
1642 return true;
1643 try
1644 {
1645 mList->push_back(USBDeviceInfo(szDevPath, pcszNode));
1646 }
1647 catch(std::bad_alloc &e)
1648 {
1649 return false;
1650 }
1651 return true;
1652 }
1653};
1654
1655/**
1656 * Tell whether a file in /sys/bus/usb/devices is an interface rather than a
1657 * device. To be used with getDeviceInfoFromSysfs().
1658 */
1659class matchUSBInterface : public sysfsPathHandler
1660{
1661 USBDeviceInfo *mInfo;
1662public:
1663 /** This constructor is currently used to unit test the class logic in
1664 * debug builds. Since no access is made to anything outside the class,
1665 * this shouldn't cause any slowdown worth mentioning. */
1666 matchUSBInterface(USBDeviceInfo *pInfo) : mInfo(pInfo)
1667 {
1668 Assert(isAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0",
1669 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
1670 Assert(!isAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1",
1671 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
1672 Assert(!isAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/driver",
1673 "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
1674 }
1675private:
1676 /** The logic for testing whether a sysfs address corresponds to an
1677 * interface of a device. Both must be referenced by their canonical
1678 * sysfs paths. This is not tested, as the test requires file-system
1679 * interaction. */
1680 bool isAnInterfaceOf(const char *pcszIface, const char *pcszDev)
1681 {
1682 size_t cchDev = strlen(pcszDev);
1683
1684 AssertPtr(pcszIface);
1685 AssertPtr(pcszDev);
1686 Assert(pcszIface[0] == '/');
1687 Assert(pcszDev[0] == '/');
1688 Assert(pcszDev[cchDev - 1] != '/');
1689 /* If this passes, pcszIface is at least cchDev long */
1690 if (strncmp(pcszIface, pcszDev, cchDev))
1691 return false;
1692 /* If this passes, pcszIface is longer than cchDev */
1693 if (pcszIface[cchDev] != '/')
1694 return false;
1695 /* In sysfs an interface is an immediate subdirectory of the device */
1696 if (strchr(pcszIface + cchDev + 1, '/'))
1697 return false;
1698 /* And it always has a colon in its name */
1699 if (!strchr(pcszIface + cchDev + 1, ':'))
1700 return false;
1701 /* And hopefully we have now elimitated everything else */
1702 return true;
1703 }
1704
1705 virtual bool handle(const char *pcszNode)
1706 {
1707 if (!isAnInterfaceOf(pcszNode, mInfo->mSysfsPath.c_str()))
1708 return true;
1709 try
1710 {
1711 mInfo->mInterfaces.push_back(pcszNode);
1712 }
1713 catch(std::bad_alloc &e)
1714 {
1715 return false;
1716 }
1717 return true;
1718 }
1719};
1720
1721/**
1722 * Helper function to query the sysfs subsystem for information about USB
1723 * devices attached to the system.
1724 * @returns iprt status code
1725 * @param pList where to add information about the drives detected
1726 * @param pfSuccess Did we find anything?
1727 *
1728 * @returns IPRT status code
1729 */
1730static int getUSBDeviceInfoFromSysfs(USBDeviceInfoList *pList,
1731 bool *pfSuccess)
1732{
1733 AssertPtrReturn(pList, VERR_INVALID_POINTER);
1734 AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
1735 LogFlowFunc (("pList=%p, pfSuccess=%p\n",
1736 pList, pfSuccess));
1737 size_t cDevices = pList->size();
1738 matchUSBDevice devHandler(pList);
1739 int rc = getDeviceInfoFromSysfs("/sys/bus/usb/devices", &devHandler);
1740 do {
1741 if (RT_FAILURE(rc))
1742 break;
1743 for (USBDeviceInfoList::iterator pInfo = pList->begin();
1744 pInfo != pList->end(); ++pInfo)
1745 {
1746 matchUSBInterface ifaceHandler(&*pInfo);
1747 rc = getDeviceInfoFromSysfs("/sys/bus/usb/devices", &ifaceHandler);
1748 if (RT_FAILURE(rc))
1749 break;
1750 }
1751 } while(0);
1752 if (RT_FAILURE(rc))
1753 /* Clean up again */
1754 while (pList->size() > cDevices)
1755 pList->pop_back();
1756 if (pfSuccess)
1757 *pfSuccess = RT_SUCCESS(rc);
1758 LogFlow (("rc=%Rrc\n", rc));
1759 return rc;
1760}
1761# endif /* VBOX_USB_WITH_FAM */
1762#endif /* VBOX_USB_WITH_SYSFS */
1763
1764#if defined VBOX_USB_WITH_SYSFS && defined VBOX_USB_WITH_DBUS
1765/** Wrapper class around DBusError for automatic cleanup */
1766class autoDBusError
1767{
1768 DBusError mError;
1769public:
1770 autoDBusError () { dbus_error_init (&mError); }
1771 ~autoDBusError ()
1772 {
1773 if (IsSet())
1774 dbus_error_free (&mError);
1775 }
1776 DBusError &get () { return mError; }
1777 bool IsSet ()
1778 {
1779 Assert((mError.name == NULL) == (mError.message == NULL));
1780 return (mError.name != NULL);
1781 }
1782 bool HasName (const char *pcszName)
1783 {
1784 Assert((mError.name == NULL) == (mError.message == NULL));
1785 return (RTStrCmp (mError.name, pcszName) == 0);
1786 }
1787 void FlowLog ()
1788 {
1789 if (IsSet ())
1790 LogFlow(("DBus error %s: %s\n", mError.name, mError.message));
1791 }
1792};
1793
1794/**
1795 * Helper function for setting up a connection to the DBus daemon and
1796 * registering with the hal service.
1797 *
1798 * @note If libdbus is being loaded at runtime then be sure to call
1799 * VBoxDBusCheckPresence before calling this.
1800 * @returns iprt status code
1801 * @param ppConnection where to store the connection handle
1802 */
1803/* static */
1804int halInit (RTMemAutoPtr <DBusConnection, VBoxHalShutdown> *pConnection)
1805{
1806 AssertReturn(VALID_PTR (pConnection), VERR_INVALID_POINTER);
1807 LogFlowFunc (("pConnection=%p\n", pConnection));
1808 int rc = VINF_SUCCESS;
1809 bool halSuccess = true;
1810 autoDBusError dbusError;
1811
1812 RTMemAutoPtr <DBusConnection, VBoxDBusConnectionUnref> dbusConnection;
1813 dbusConnection = dbus_bus_get (DBUS_BUS_SYSTEM, &dbusError.get());
1814 if (!dbusConnection)
1815 halSuccess = false;
1816 if (halSuccess)
1817 {
1818 dbus_connection_set_exit_on_disconnect (dbusConnection.get(), false);
1819 halSuccess = dbus_bus_name_has_owner (dbusConnection.get(),
1820 "org.freedesktop.Hal", &dbusError.get());
1821 }
1822 if (halSuccess)
1823 {
1824 dbus_bus_add_match (dbusConnection.get(),
1825 "type='signal',"
1826 "interface='org.freedesktop.Hal.Manager',"
1827 "sender='org.freedesktop.Hal',"
1828 "path='/org/freedesktop/Hal/Manager'",
1829 &dbusError.get());
1830 halSuccess = !dbusError.IsSet();
1831 }
1832 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
1833 rc = VERR_NO_MEMORY;
1834 if (halSuccess)
1835 *pConnection = dbusConnection.release();
1836 LogFlowFunc(("rc=%Rrc, (*pConnection).get()=%p\n", rc, (*pConnection).get()));
1837 dbusError.FlowLog();
1838 return rc;
1839}
1840
1841/**
1842 * Helper function for setting up a private connection to the DBus daemon and
1843 * registering with the hal service. Private connections are considered
1844 * unsociable and should not be used unnecessarily (as per the DBus API docs).
1845 *
1846 * @note If libdbus is being loaded at runtime then be sure to call
1847 * VBoxDBusCheckPresence before calling this.
1848 * @returns iprt status code
1849 * @param pConnection where to store the connection handle
1850 */
1851/* static */
1852int halInitPrivate (RTMemAutoPtr <DBusConnection, VBoxHalShutdownPrivate> *pConnection)
1853{
1854 AssertReturn(VALID_PTR (pConnection), VERR_INVALID_POINTER);
1855 LogFlowFunc (("pConnection=%p\n", pConnection));
1856 int rc = VINF_SUCCESS;
1857 bool halSuccess = true;
1858 autoDBusError dbusError;
1859
1860 RTMemAutoPtr <DBusConnection, VBoxDBusConnectionCloseAndUnref> dbusConnection;
1861 dbusConnection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &dbusError.get());
1862 if (!dbusConnection)
1863 halSuccess = false;
1864 if (halSuccess)
1865 {
1866 dbus_connection_set_exit_on_disconnect (dbusConnection.get(), false);
1867 halSuccess = dbus_bus_name_has_owner (dbusConnection.get(),
1868 "org.freedesktop.Hal", &dbusError.get());
1869 }
1870 if (halSuccess)
1871 {
1872 dbus_bus_add_match (dbusConnection.get(),
1873 "type='signal',"
1874 "interface='org.freedesktop.Hal.Manager',"
1875 "sender='org.freedesktop.Hal',"
1876 "path='/org/freedesktop/Hal/Manager'",
1877 &dbusError.get());
1878 halSuccess = !dbusError.IsSet();
1879 }
1880 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
1881 rc = VERR_NO_MEMORY;
1882 if (halSuccess)
1883 *pConnection = dbusConnection.release();
1884 LogFlowFunc(("rc=%Rrc, (*pConnection).get()=%p\n", rc, (*pConnection).get()));
1885 dbusError.FlowLog();
1886 return rc;
1887}
1888
1889/**
1890 * Helper function for shutting down a connection to DBus and hal.
1891 * @param pConnection the connection handle
1892 */
1893/* extern */
1894void VBoxHalShutdown (DBusConnection *pConnection)
1895{
1896 AssertReturnVoid(VALID_PTR (pConnection));
1897 LogFlowFunc (("pConnection=%p\n", pConnection));
1898 autoDBusError dbusError;
1899
1900 dbus_bus_remove_match (pConnection,
1901 "type='signal',"
1902 "interface='org.freedesktop.Hal.Manager',"
1903 "sender='org.freedesktop.Hal',"
1904 "path='/org/freedesktop/Hal/Manager'",
1905 &dbusError.get());
1906 dbus_connection_unref (pConnection);
1907 LogFlowFunc(("returning\n"));
1908 dbusError.FlowLog();
1909}
1910
1911/**
1912 * Helper function for shutting down a private connection to DBus and hal.
1913 * @param pConnection the connection handle
1914 */
1915/* extern */
1916void VBoxHalShutdownPrivate (DBusConnection *pConnection)
1917{
1918 AssertReturnVoid(VALID_PTR (pConnection));
1919 LogFlowFunc (("pConnection=%p\n", pConnection));
1920 autoDBusError dbusError;
1921
1922 dbus_bus_remove_match (pConnection,
1923 "type='signal',"
1924 "interface='org.freedesktop.Hal.Manager',"
1925 "sender='org.freedesktop.Hal',"
1926 "path='/org/freedesktop/Hal/Manager'",
1927 &dbusError.get());
1928 dbus_connection_close (pConnection);
1929 dbus_connection_unref (pConnection);
1930 LogFlowFunc(("returning\n"));
1931 dbusError.FlowLog();
1932}
1933
1934/** Wrapper around dbus_connection_unref. We need this to use it as a real
1935 * function in auto pointers, as a function pointer won't wash here. */
1936/* extern */
1937void VBoxDBusConnectionUnref(DBusConnection *pConnection)
1938{
1939 dbus_connection_unref(pConnection);
1940}
1941
1942/**
1943 * This function closes and unrefs a private connection to dbus. It should
1944 * only be called once no-one else is referencing the connection.
1945 */
1946/* extern */
1947void VBoxDBusConnectionCloseAndUnref(DBusConnection *pConnection)
1948{
1949 dbus_connection_close(pConnection);
1950 dbus_connection_unref(pConnection);
1951}
1952
1953/** Wrapper around dbus_message_unref. We need this to use it as a real
1954 * function in auto pointers, as a function pointer won't wash here. */
1955/* extern */
1956void VBoxDBusMessageUnref(DBusMessage *pMessage)
1957{
1958 dbus_message_unref(pMessage);
1959}
1960
1961/**
1962 * Find the UDIs of hal entries that contain Key=Value property.
1963 * @returns iprt status code. If a non-fatal error occurs, we return success
1964 * but reset pMessage to NULL.
1965 * @param pConnection an initialised connection DBus
1966 * @param pszKey the property key
1967 * @param pszValue the property value
1968 * @param pMessage where to store the return DBus message. This must be
1969 * parsed to get at the UDIs. NOT optional.
1970 */
1971/* static */
1972int halFindDeviceStringMatch (DBusConnection *pConnection, const char *pszKey,
1973 const char *pszValue,
1974 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage)
1975{
1976 AssertReturn( VALID_PTR (pConnection) && VALID_PTR (pszKey)
1977 && VALID_PTR (pszValue) && VALID_PTR (pMessage),
1978 VERR_INVALID_POINTER);
1979 LogFlowFunc (("pConnection=%p, pszKey=%s, pszValue=%s, pMessage=%p\n",
1980 pConnection, pszKey, pszValue, pMessage));
1981 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
1982 bool halSuccess = true; /* We set this to false to abort the operation. */
1983 autoDBusError dbusError;
1984
1985 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, reply;
1986 if (halSuccess && RT_SUCCESS(rc))
1987 {
1988 message = dbus_message_new_method_call ("org.freedesktop.Hal",
1989 "/org/freedesktop/Hal/Manager",
1990 "org.freedesktop.Hal.Manager",
1991 "FindDeviceStringMatch");
1992 if (!message)
1993 rc = VERR_NO_MEMORY;
1994 }
1995 if (halSuccess && RT_SUCCESS(rc))
1996 {
1997 DBusMessageIter iterAppend;
1998 dbus_message_iter_init_append (message.get(), &iterAppend);
1999 dbus_message_iter_append_basic (&iterAppend, DBUS_TYPE_STRING, &pszKey);
2000 dbus_message_iter_append_basic (&iterAppend, DBUS_TYPE_STRING, &pszValue);
2001 reply = dbus_connection_send_with_reply_and_block (pConnection,
2002 message.get(), -1,
2003 &dbusError.get());
2004 if (!reply)
2005 halSuccess = false;
2006 }
2007 *pMessage = reply.release ();
2008 LogFlowFunc (("rc=%Rrc, *pMessage.value()=%p\n", rc, (*pMessage).get()));
2009 dbusError.FlowLog();
2010 return rc;
2011}
2012
2013/**
2014 * Find the UDIs of hal entries that contain Key=Value property and return the
2015 * result on the end of a vector of iprt::MiniString.
2016 * @returns iprt status code. If a non-fatal error occurs, we return success
2017 * but set *pfSuccess to false.
2018 * @param pConnection an initialised connection DBus
2019 * @param pszKey the property key
2020 * @param pszValue the property value
2021 * @param pMatches pointer to an array of iprt::MiniString to append the
2022 * results to. NOT optional.
2023 * @param pfSuccess will be set to true if the operation succeeds
2024 */
2025/* static */
2026int halFindDeviceStringMatchVector (DBusConnection *pConnection,
2027 const char *pszKey, const char *pszValue,
2028 std::vector<iprt::MiniString> *pMatches,
2029 bool *pfSuccess)
2030{
2031 AssertPtrReturn (pConnection, VERR_INVALID_POINTER);
2032 AssertPtrReturn (pszKey, VERR_INVALID_POINTER);
2033 AssertPtrReturn (pszValue, VERR_INVALID_POINTER);
2034 AssertPtrReturn (pMatches, VERR_INVALID_POINTER);
2035 AssertReturn(pfSuccess == NULL || VALID_PTR (pfSuccess), VERR_INVALID_POINTER);
2036 LogFlowFunc (("pConnection=%p, pszKey=%s, pszValue=%s, pMatches=%p, pfSuccess=%p\n",
2037 pConnection, pszKey, pszValue, pMatches, pfSuccess));
2038 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
2039 bool halSuccess = true; /* We set this to false to abort the operation. */
2040
2041 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, replyFind;
2042 DBusMessageIter iterFind, iterUdis;
2043
2044 if (halSuccess && RT_SUCCESS(rc))
2045 {
2046 rc = halFindDeviceStringMatch (pConnection, pszKey, pszValue,
2047 &replyFind);
2048 if (!replyFind)
2049 halSuccess = false;
2050 }
2051 if (halSuccess && RT_SUCCESS(rc))
2052 {
2053 dbus_message_iter_init (replyFind.get(), &iterFind);
2054 if (dbus_message_iter_get_arg_type (&iterFind) != DBUS_TYPE_ARRAY)
2055 halSuccess = false;
2056 }
2057 if (halSuccess && RT_SUCCESS(rc))
2058 dbus_message_iter_recurse (&iterFind, &iterUdis);
2059 for (; halSuccess && RT_SUCCESS(rc)
2060 && dbus_message_iter_get_arg_type (&iterUdis) == DBUS_TYPE_STRING;
2061 dbus_message_iter_next(&iterUdis))
2062 {
2063 /* Now get all UDIs from the iterator */
2064 const char *pszUdi;
2065 dbus_message_iter_get_basic (&iterUdis, &pszUdi);
2066 try
2067 {
2068 pMatches->push_back(pszUdi);
2069 }
2070 catch(std::bad_alloc &e)
2071 {
2072 rc = VERR_NO_MEMORY;
2073 }
2074 }
2075 if (pfSuccess != NULL)
2076 *pfSuccess = halSuccess;
2077 LogFlow (("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
2078 return rc;
2079}
2080
2081/**
2082 * Read a set of string properties for a device. If some of the properties are
2083 * not of type DBUS_TYPE_STRING or do not exist then a NULL pointer will be
2084 * returned for them.
2085 * @returns iprt status code. If the operation failed for non-fatal reasons
2086 * then we return success and leave pMessage untouched - reset it
2087 * before the call to detect this.
2088 * @param pConnection an initialised connection DBus
2089 * @param pszUdi the Udi of the device
2090 * @param cProps the number of property values to look up
2091 * @param papszKeys the keys of the properties to be looked up
2092 * @param papszValues where to store the values of the properties. The
2093 * strings returned will be valid until the message
2094 * returned in @a ppMessage is freed. Undefined if
2095 * the message is NULL.
2096 * @param pMessage where to store the return DBus message. The caller
2097 * is responsible for freeing this once they have
2098 * finished with the value strings. NOT optional.
2099 */
2100/* static */
2101int halGetPropertyStrings (DBusConnection *pConnection, const char *pszUdi,
2102 size_t cProps, const char **papszKeys,
2103 char **papszValues,
2104 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage)
2105{
2106 AssertReturn( VALID_PTR (pConnection) && VALID_PTR (pszUdi)
2107 && VALID_PTR (papszKeys) && VALID_PTR (papszValues)
2108 && VALID_PTR (pMessage),
2109 VERR_INVALID_POINTER);
2110 LogFlowFunc (("pConnection=%p, pszUdi=%s, cProps=%llu, papszKeys=%p, papszValues=%p, pMessage=%p\n",
2111 pConnection, pszUdi, cProps, papszKeys, papszValues, pMessage));
2112 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
2113 bool halSuccess = true; /* We set this to false to abort the operation. */
2114 autoDBusError dbusError;
2115
2116 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, reply;
2117 DBusMessageIter iterGet, iterProps;
2118
2119 /* Initialise the return array to NULLs */
2120 for (size_t i = 0; i < cProps; ++i)
2121 papszValues[i] = NULL;
2122
2123 /* Send a GetAllProperties message to hald */
2124 message = dbus_message_new_method_call ("org.freedesktop.Hal", pszUdi,
2125 "org.freedesktop.Hal.Device",
2126 "GetAllProperties");
2127 if (!message)
2128 rc = VERR_NO_MEMORY;
2129 if (halSuccess && RT_SUCCESS(rc))
2130 {
2131 reply = dbus_connection_send_with_reply_and_block (pConnection,
2132 message.get(), -1,
2133 &dbusError.get());
2134 if (!reply)
2135 halSuccess = false;
2136 }
2137
2138 /* Parse the reply */
2139 if (halSuccess && RT_SUCCESS(rc))
2140 {
2141 dbus_message_iter_init (reply.get(), &iterGet);
2142 if ( dbus_message_iter_get_arg_type (&iterGet) != DBUS_TYPE_ARRAY
2143 && dbus_message_iter_get_element_type (&iterGet) != DBUS_TYPE_DICT_ENTRY)
2144 halSuccess = false;
2145 }
2146 if (halSuccess && RT_SUCCESS(rc))
2147 dbus_message_iter_recurse (&iterGet, &iterProps);
2148 /* Go through all entries in the reply and see if any match our keys. */
2149 while ( halSuccess && RT_SUCCESS(rc)
2150 && dbus_message_iter_get_arg_type (&iterProps)
2151 == DBUS_TYPE_DICT_ENTRY)
2152 {
2153 const char *pszKey;
2154 DBusMessageIter iterEntry, iterValue;
2155 dbus_message_iter_recurse (&iterProps, &iterEntry);
2156 dbus_message_iter_get_basic (&iterEntry, &pszKey);
2157 dbus_message_iter_next (&iterEntry);
2158 dbus_message_iter_recurse (&iterEntry, &iterValue);
2159 /* Fill in any matches. */
2160 for (size_t i = 0; i < cProps; ++i)
2161 if (strcmp (pszKey, papszKeys[i]) == 0)
2162 {
2163 if (dbus_message_iter_get_arg_type (&iterValue) == DBUS_TYPE_STRING)
2164 dbus_message_iter_get_basic (&iterValue, &papszValues[i]);
2165 }
2166 dbus_message_iter_next (&iterProps);
2167 }
2168 if (RT_SUCCESS(rc) && halSuccess)
2169 *pMessage = reply.release();
2170 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
2171 rc = VERR_NO_MEMORY;
2172 LogFlowFunc (("rc=%Rrc, *pMessage.value()=%p\n", rc, (*pMessage).get()));
2173 dbusError.FlowLog();
2174 return rc;
2175}
2176
2177/**
2178 * Read a set of string properties for a device. If some properties do not
2179 * exist or are not of type DBUS_TYPE_STRING, we will still fetch the others.
2180 * @returns iprt status code. If the operation failed for non-fatal reasons
2181 * then we return success and set *pfSuccess to false.
2182 * @param pConnection an initialised connection DBus
2183 * @param pszUdi the Udi of the device
2184 * @param cProps the number of property values to look up
2185 * @param papszKeys the keys of the properties to be looked up
2186 * @param pMatches pointer to an empty array of iprt::MiniString to append the
2187 * results to. NOT optional.
2188 * @param pfMatches pointer to an array of boolean values indicating
2189 * whether the respective property is a string. If this
2190 * is not supplied then all properties must be strings
2191 * for the operation to be considered successful
2192 * @param pfSuccess will be set to true if the operation succeeds
2193 */
2194/* static */
2195int halGetPropertyStringsVector (DBusConnection *pConnection,
2196 const char *pszUdi, size_t cProps,
2197 const char **papszKeys,
2198 std::vector<iprt::MiniString> *pMatches,
2199 bool *pfMatches, bool *pfSuccess)
2200{
2201 AssertPtrReturn (pConnection, VERR_INVALID_POINTER);
2202 AssertPtrReturn (pszUdi, VERR_INVALID_POINTER);
2203 AssertPtrReturn (papszKeys, VERR_INVALID_POINTER);
2204 AssertPtrReturn (pMatches, VERR_INVALID_POINTER);
2205 AssertReturn((pfMatches == NULL) || VALID_PTR (pfMatches), VERR_INVALID_POINTER);
2206 AssertReturn((pfSuccess == NULL) || VALID_PTR (pfSuccess), VERR_INVALID_POINTER);
2207 AssertReturn(pMatches->empty(), VERR_INVALID_PARAMETER);
2208 LogFlowFunc (("pConnection=%p, pszUdi=%s, cProps=%llu, papszKeys=%p, pMatches=%p, pfMatches=%p, pfSuccess=%p\n",
2209 pConnection, pszUdi, cProps, papszKeys, pMatches, pfMatches, pfSuccess));
2210 RTMemAutoPtr <char *> values(cProps);
2211 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message;
2212 bool halSuccess = true;
2213 int rc = halGetPropertyStrings (pConnection, pszUdi, cProps, papszKeys,
2214 values.get(), &message);
2215 if (!message)
2216 halSuccess = false;
2217 for (size_t i = 0; RT_SUCCESS(rc) && halSuccess && i < cProps; ++i)
2218 {
2219 bool fMatches = values[i] != NULL;
2220 if (pfMatches != NULL)
2221 pfMatches[i] = fMatches;
2222 else
2223 halSuccess = fMatches;
2224 try
2225 {
2226 pMatches->push_back(fMatches ? values[i] : "");
2227 }
2228 catch(std::bad_alloc &e)
2229 {
2230 rc = VERR_NO_MEMORY;
2231 }
2232 }
2233 if (pfSuccess != NULL)
2234 *pfSuccess = halSuccess;
2235 if (RT_SUCCESS(rc) && halSuccess)
2236 {
2237 Assert(pMatches->size() == cProps);
2238 AssertForEach(j, size_t, 0, cProps, (pfMatches == NULL)
2239 || (pfMatches[j] == true)
2240 || ((pfMatches[j] == false) && (pMatches[j].size() == 0)));
2241 }
2242 LogFlowFunc (("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
2243 return rc;
2244}
2245
2246
2247/**
2248 * Helper function to query the hal subsystem for information about USB devices
2249 * attached to the system.
2250 * @returns iprt status code
2251 * @param pList where to add information about the devices detected
2252 * @param pfSuccess will be set to true if all interactions with hal
2253 * succeeded and to false otherwise. Optional.
2254 *
2255 * @returns IPRT status code
2256 */
2257/* static */
2258int getUSBDeviceInfoFromHal(USBDeviceInfoList *pList, bool *pfSuccess)
2259{
2260 AssertReturn(VALID_PTR (pList) && (pfSuccess == NULL || VALID_PTR (pfSuccess)),
2261 VERR_INVALID_POINTER);
2262 LogFlowFunc (("pList=%p, pfSuccess=%p\n", pList, pfSuccess));
2263 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
2264 bool halSuccess = true; /* We set this to false to abort the operation. */
2265 autoDBusError dbusError;
2266
2267 RTMemAutoPtr<DBusMessage, VBoxDBusMessageUnref> message, replyFind, replyGet;
2268 RTMemAutoPtr<DBusConnection, VBoxHalShutdown> dbusConnection;
2269 DBusMessageIter iterFind, iterUdis;
2270
2271 /* Connect to hal */
2272 rc = halInit (&dbusConnection);
2273 if (!dbusConnection)
2274 halSuccess = false;
2275 /* Get an array of all devices in the usb_device subsystem */
2276 if (halSuccess && RT_SUCCESS(rc))
2277 {
2278 rc = halFindDeviceStringMatch(dbusConnection.get(), "info.subsystem",
2279 "usb_device", &replyFind);
2280 if (!replyFind)
2281 halSuccess = false;
2282 }
2283 if (halSuccess && RT_SUCCESS(rc))
2284 {
2285 dbus_message_iter_init(replyFind.get(), &iterFind);
2286 if (dbus_message_iter_get_arg_type (&iterFind) != DBUS_TYPE_ARRAY)
2287 halSuccess = false;
2288 }
2289 /* Recurse down into the array and query interesting information about the
2290 * entries. */
2291 if (halSuccess && RT_SUCCESS(rc))
2292 dbus_message_iter_recurse(&iterFind, &iterUdis);
2293 for (; halSuccess && RT_SUCCESS(rc)
2294 && dbus_message_iter_get_arg_type(&iterUdis) == DBUS_TYPE_STRING;
2295 dbus_message_iter_next(&iterUdis))
2296 {
2297 /* Get the device node and the sysfs path for the current entry. */
2298 const char *pszUdi;
2299 dbus_message_iter_get_basic (&iterUdis, &pszUdi);
2300 static const char *papszKeys[] = { "linux.device_file", "linux.sysfs_path" };
2301 char *papszValues[RT_ELEMENTS(papszKeys)];
2302 rc = halGetPropertyStrings(dbusConnection.get(), pszUdi, RT_ELEMENTS(papszKeys),
2303 papszKeys, papszValues, &replyGet);
2304 const char *pszDevice = papszValues[0], *pszSysfsPath = papszValues[1];
2305 /* Get the interfaces. */
2306 if (!!replyGet && pszDevice && pszSysfsPath)
2307 {
2308 USBDeviceInfo info(pszDevice, pszSysfsPath);
2309 bool ifaceSuccess = true; /* If we can't get the interfaces, just
2310 * skip this one device. */
2311 rc = getUSBInterfacesFromHal(&info.mInterfaces, pszUdi, &ifaceSuccess);
2312 if (RT_SUCCESS(rc) && halSuccess && ifaceSuccess)
2313 try
2314 {
2315 pList->push_back(info);
2316 }
2317 catch(std::bad_alloc &e)
2318 {
2319 rc = VERR_NO_MEMORY;
2320 }
2321 }
2322 }
2323 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
2324 rc = VERR_NO_MEMORY;
2325 if (pfSuccess != NULL)
2326 *pfSuccess = halSuccess;
2327 LogFlow(("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
2328 dbusError.FlowLog();
2329 return rc;
2330}
2331
2332/**
2333 * Helper function to query the hal subsystem for information about USB devices
2334 * attached to the system, using the older API.
2335 * @returns iprt status code
2336 * @param pList where to add information about the devices detected
2337 * @param pfSuccess will be set to true if all interactions with hal
2338 * succeeded and to false otherwise. Optional.
2339 *
2340 * @returns IPRT status code
2341 */
2342/* static */
2343int getOldUSBDeviceInfoFromHal(USBDeviceInfoList *pList, bool *pfSuccess)
2344{
2345 AssertReturn(VALID_PTR (pList) && (pfSuccess == NULL || VALID_PTR (pfSuccess)),
2346 VERR_INVALID_POINTER);
2347 LogFlowFunc (("pList=%p, pfSuccess=%p\n", pList, pfSuccess));
2348 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
2349 bool halSuccess = true; /* We set this to false to abort the operation. */
2350 autoDBusError dbusError;
2351
2352 RTMemAutoPtr<DBusMessage, VBoxDBusMessageUnref> message, replyFind, replyGet;
2353 RTMemAutoPtr<DBusConnection, VBoxHalShutdown> dbusConnection;
2354 DBusMessageIter iterFind, iterUdis;
2355
2356 /* Connect to hal */
2357 rc = halInit(&dbusConnection);
2358 if (!dbusConnection)
2359 halSuccess = false;
2360 /* Get an array of all devices in the usb_device subsystem */
2361 if (halSuccess && RT_SUCCESS(rc))
2362 {
2363 rc = halFindDeviceStringMatch(dbusConnection.get(), "info.category",
2364 "usbraw", &replyFind);
2365 if (!replyFind)
2366 halSuccess = false;
2367 }
2368 if (halSuccess && RT_SUCCESS(rc))
2369 {
2370 dbus_message_iter_init(replyFind.get(), &iterFind);
2371 if (dbus_message_iter_get_arg_type(&iterFind) != DBUS_TYPE_ARRAY)
2372 halSuccess = false;
2373 }
2374 /* Recurse down into the array and query interesting information about the
2375 * entries. */
2376 if (halSuccess && RT_SUCCESS(rc))
2377 dbus_message_iter_recurse(&iterFind, &iterUdis);
2378 for (; halSuccess && RT_SUCCESS(rc)
2379 && dbus_message_iter_get_arg_type(&iterUdis) == DBUS_TYPE_STRING;
2380 dbus_message_iter_next(&iterUdis))
2381 {
2382 /* Get the device node and the sysfs path for the current entry. */
2383 const char *pszUdi;
2384 dbus_message_iter_get_basic(&iterUdis, &pszUdi);
2385 static const char *papszKeys[] = { "linux.device_file", "info.parent" };
2386 char *papszValues[RT_ELEMENTS(papszKeys)];
2387 rc = halGetPropertyStrings(dbusConnection.get(), pszUdi, RT_ELEMENTS(papszKeys),
2388 papszKeys, papszValues, &replyGet);
2389 const char *pszDevice = papszValues[0], *pszSysfsPath = papszValues[1];
2390 /* Get the interfaces. */
2391 if (!!replyGet && pszDevice && pszSysfsPath)
2392 {
2393 USBDeviceInfo info(pszDevice, pszSysfsPath);
2394 bool ifaceSuccess = false; /* If we can't get the interfaces, just
2395 * skip this one device. */
2396 rc = getUSBInterfacesFromHal(&info.mInterfaces, pszSysfsPath,
2397 &ifaceSuccess);
2398 if (RT_SUCCESS(rc) && halSuccess && ifaceSuccess)
2399 try
2400 {
2401 pList->push_back(info);
2402 }
2403 catch(std::bad_alloc &e)
2404 {
2405 rc = VERR_NO_MEMORY;
2406 }
2407 }
2408 }
2409 if (dbusError.HasName(DBUS_ERROR_NO_MEMORY))
2410 rc = VERR_NO_MEMORY;
2411 if (pfSuccess != NULL)
2412 *pfSuccess = halSuccess;
2413 LogFlow(("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
2414 dbusError.FlowLog();
2415 return rc;
2416}
2417
2418
2419/**
2420 * Helper function to query the hal subsystem for information about USB devices
2421 * attached to the system.
2422 * @returns iprt status code
2423 * @param pList where to add information about the devices detected. If
2424 * certain interfaces are not found (@a pfFound is false on
2425 * return) this may contain invalid information.
2426 * @param pcszUdi the hal UDI of the device
2427 * @param pfSuccess will be set to true if the operation succeeds and to
2428 * false if it fails for non-critical reasons. Optional.
2429 *
2430 * @returns IPRT status code
2431 */
2432/* static */
2433int getUSBInterfacesFromHal(std::vector<iprt::MiniString> *pList,
2434 const char *pcszUdi, bool *pfSuccess)
2435{
2436 AssertReturn(VALID_PTR(pList) && VALID_PTR(pcszUdi) &&
2437 (pfSuccess == NULL || VALID_PTR (pfSuccess)),
2438 VERR_INVALID_POINTER);
2439 LogFlowFunc(("pList=%p, pcszUdi=%s, pfSuccess=%p\n", pList, pcszUdi,
2440 pfSuccess));
2441 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
2442 bool halSuccess = true; /* We set this to false to abort the operation. */
2443 autoDBusError dbusError;
2444
2445 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, replyFind, replyGet;
2446 RTMemAutoPtr <DBusConnection, VBoxHalShutdown> dbusConnection;
2447 DBusMessageIter iterFind, iterUdis;
2448
2449 rc = halInit(&dbusConnection);
2450 if (!dbusConnection)
2451 halSuccess = false;
2452 if (halSuccess && RT_SUCCESS(rc))
2453 {
2454 /* Look for children of the current UDI. */
2455 rc = halFindDeviceStringMatch(dbusConnection.get(), "info.parent",
2456 pcszUdi, &replyFind);
2457 if (!replyFind)
2458 halSuccess = false;
2459 }
2460 if (halSuccess && RT_SUCCESS(rc))
2461 {
2462 dbus_message_iter_init(replyFind.get(), &iterFind);
2463 if (dbus_message_iter_get_arg_type(&iterFind) != DBUS_TYPE_ARRAY)
2464 halSuccess = false;
2465 }
2466 if (halSuccess && RT_SUCCESS(rc))
2467 dbus_message_iter_recurse(&iterFind, &iterUdis);
2468 for (; halSuccess && RT_SUCCESS(rc)
2469 && dbus_message_iter_get_arg_type(&iterUdis) == DBUS_TYPE_STRING;
2470 dbus_message_iter_next(&iterUdis))
2471 {
2472 /* Now get the sysfs path and the subsystem from the iterator */
2473 const char *pszUdi;
2474 dbus_message_iter_get_basic(&iterUdis, &pszUdi);
2475 static const char *papszKeys[] = { "linux.sysfs_path", "info.subsystem",
2476 "linux.subsystem" };
2477 char *papszValues[RT_ELEMENTS(papszKeys)];
2478 rc = halGetPropertyStrings(dbusConnection.get(), pszUdi, RT_ELEMENTS(papszKeys),
2479 papszKeys, papszValues, &replyGet);
2480 const char *pszSysfsPath = papszValues[0], *pszInfoSubsystem = papszValues[1],
2481 *pszLinuxSubsystem = papszValues[2];
2482 if (!replyGet)
2483 halSuccess = false;
2484 if (!!replyGet && pszSysfsPath == NULL)
2485 halSuccess = false;
2486 if ( halSuccess && RT_SUCCESS(rc)
2487 && RTStrCmp (pszInfoSubsystem, "usb_device") != 0 /* Children of buses can also be devices. */
2488 && RTStrCmp (pszLinuxSubsystem, "usb_device") != 0)
2489 try
2490 {
2491 pList->push_back(pszSysfsPath);
2492 }
2493 catch(std::bad_alloc &e)
2494 {
2495 rc = VERR_NO_MEMORY;
2496 }
2497 }
2498 if (dbusError.HasName(DBUS_ERROR_NO_MEMORY))
2499 rc = VERR_NO_MEMORY;
2500 if (pfSuccess != NULL)
2501 *pfSuccess = halSuccess;
2502 LogFlow(("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
2503 dbusError.FlowLog();
2504 return rc;
2505}
2506
2507/**
2508 * When it is registered with DBus, this function will be called by
2509 * dbus_connection_read_write_dispatch each time a message is received over the
2510 * DBus connection. We check whether that message was caused by a hal device
2511 * hotplug event, and if so we set a flag. dbus_connection_read_write_dispatch
2512 * will return after calling its filter functions, and its caller should then
2513 * check the status of the flag passed to the filter function.
2514 *
2515 * @param pConnection The DBus connection we are using.
2516 * @param pMessage The DBus message which just arrived.
2517 * @param pvUser A pointer to the flag variable we are to set.
2518 */
2519/* static */
2520DBusHandlerResult dbusFilterFunction(DBusConnection * /* pConnection */,
2521 DBusMessage *pMessage, void *pvUser)
2522{
2523 volatile bool *pTriggered = reinterpret_cast<volatile bool *>(pvUser);
2524 if ( dbus_message_is_signal(pMessage, "org.freedesktop.Hal.Manager",
2525 "DeviceAdded")
2526 || dbus_message_is_signal(pMessage, "org.freedesktop.Hal.Manager",
2527 "DeviceRemoved"))
2528 {
2529 *pTriggered = true;
2530 }
2531 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
2532}
2533#endif /* VBOX_USB_WITH_SYSFS && VBOX_USB_WITH_DBUS */
2534
Note: See TracBrowser for help on using the repository browser.

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