VirtualBox

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

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

Main/HostHardwareLinux: introduce an ifdef for building USB with raw sysfs and FAM support

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