VirtualBox

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

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

Main/HostHardwareLinux: disable USB probing using hal/DBus by default

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