VirtualBox

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

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

build fix

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

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