VirtualBox

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

Last change on this file since 15465 was 15465, checked in by vboxsync, 16 years ago

Main and Devices, USB: use hal, DBus and sysfs on Linux if usbfs is not available. Currently disabled

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 41.1 KB
Line 
1/* $Id: HostHardwareLinux.cpp 15465 2008-12-14 14:31:14Z 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
34#include <iprt/env.h>
35#include <iprt/mem.h>
36#include <iprt/string.h>
37
38#ifdef RT_OS_LINUX
39# include <sys/types.h>
40# include <sys/stat.h>
41# include <unistd.h>
42# include <sys/ioctl.h>
43# include <fcntl.h>
44# include <mntent.h>
45/* bird: This is a hack to work around conflicts between these linux kernel headers
46 * and the GLIBC tcpip headers. They have different declarations of the 4
47 * standard byte order functions. */
48// # define _LINUX_BYTEORDER_GENERIC_H
49# include <linux/cdrom.h>
50# ifdef VBOX_WITH_DBUS
51# include <vbox-dbus.h>
52# endif
53# include <errno.h>
54#endif /* RT_OS_LINUX */
55
56/*******************************************************************************
57* Global Variables *
58*******************************************************************************/
59
60bool g_testHostHardwareLinux = false;
61static bool testing () { return g_testHostHardwareLinux; }
62
63/*******************************************************************************
64* Typedefs and Defines *
65*******************************************************************************/
66
67enum { DBUS_POLL_TIMEOUT = 2000 /* ms */ };
68
69
70static bool validateDevice(const char *deviceNode, bool isDVD);
71static int getDriveInfoFromEnv(const char *pszVar, DriveInfoList *pList,
72 bool isDVD, bool *pfSuccess);
73static int getDVDInfoFromMTab(char *mountTable, DriveInfoList *pList);
74#ifdef VBOX_WITH_DBUS
75/* These must be extern to be used in the RTMemAutoPtr template */
76extern void halShutdown (DBusConnection *pConnection);
77extern void halShutdownPrivate (DBusConnection *pConnection);
78
79static int halInit(RTMemAutoPtr <DBusConnection, halShutdown> *pConnection);
80static int halInitPrivate(RTMemAutoPtr <DBusConnection, halShutdownPrivate> *pConnection);
81static int halFindDeviceStringMatch (DBusConnection *pConnection,
82 const char *pszKey, const char *pszValue,
83 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage);
84static int halGetPropertyStrings (DBusConnection *pConnection,
85 const char *pszUdi, size_t cKeys,
86 const char **papszKeys, char **papszValues,
87 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage);
88static int getDriveInfoFromHal(DriveInfoList *pList, bool isDVD,
89 bool *pfSuccess);
90static int getUSBDeviceInfoFromHal(USBDeviceInfoList *pList, bool *pfSuccess);
91static int getUSBInterfacesFromHal(std::vector <std::string> *pList,
92 const char *pcszUdi, bool *pfSuccess);
93static DBusHandlerResult dbusFilterFunction (DBusConnection *pConnection,
94 DBusMessage *pMessage, void *pvUser);
95#endif /* VBOX_WITH_DBUS */
96
97/**
98 * Updates the list of host DVD drives.
99 *
100 * @returns iprt status code
101 */
102int VBoxMainDriveInfo::updateDVDs ()
103{
104 LogFlowThisFunc (("entered\n"));
105 int rc = VINF_SUCCESS;
106 bool success = false; /* Have we succeeded in finding anything yet? */
107 try
108 {
109 mDVDList.clear ();
110#if defined(RT_OS_LINUX)
111#ifdef VBOX_WITH_DBUS
112 if (RT_SUCCESS (rc) && VBoxDBusCheckPresence() && (!success || testing()))
113 rc = getDriveInfoFromHal(&mDVDList, true /* isDVD */, &success);
114#endif /* VBOX_WITH_DBUS defined */
115 // On Linux without hal, the situation is much more complex. We will take a
116 // heuristical approach and also allow the user to specify a list of host
117 // CDROMs using an environment variable.
118 // The general strategy is to try some known device names and see of they
119 // exist. At last, we'll enumerate the /etc/fstab file (luckily there's an
120 // API to parse it) for CDROM devices. Ok, let's start!
121 if (RT_SUCCESS (rc) && (!success || testing()))
122 rc = getDriveInfoFromEnv ("VBOX_CDROM", &mDVDList, true /* isDVD */,
123 &success);
124 if (RT_SUCCESS (rc) && (!success || testing()))
125 {
126 // this is a good guess usually
127 if (validateDevice("/dev/cdrom", true))
128 mDVDList.push_back (DriveInfo ("/dev/cdrom"));
129
130 // check the mounted drives
131 rc = getDVDInfoFromMTab((char*)"/etc/mtab", &mDVDList);
132
133 // check the drives that can be mounted
134 if (RT_SUCCESS (rc))
135 rc = getDVDInfoFromMTab((char*)"/etc/fstab", &mDVDList);
136 }
137#endif
138 }
139 catch (std::bad_alloc)
140 {
141 rc = VERR_NO_MEMORY;
142 }
143 LogFlowThisFunc (("rc=%Rrc\n", rc));
144 return rc;
145}
146
147/**
148 * Updates the list of host floppy drives.
149 *
150 * @returns iprt status code
151 */
152int VBoxMainDriveInfo::updateFloppies ()
153{
154 LogFlowThisFunc (("entered\n"));
155 int rc = VINF_SUCCESS;
156 bool success = false; /* Have we succeeded in finding anything yet? */
157 try
158 {
159 mFloppyList.clear ();
160#if defined(RT_OS_LINUX)
161#ifdef VBOX_WITH_DBUS
162 if (RT_SUCCESS (rc) && VBoxDBusCheckPresence() && (!success || testing()))
163 rc = getDriveInfoFromHal(&mFloppyList, false /* isDVD */, &success);
164#endif /* VBOX_WITH_DBUS defined */
165 // As with the CDROMs, on Linux we have to take a multi-level approach
166 // involving parsing the mount tables. As this is not bulletproof, we'll
167 // give the user the chance to override the detection by an environment
168 // variable and skip the detection.
169 if (RT_SUCCESS (rc) && (!success || testing()))
170 rc = getDriveInfoFromEnv ("VBOX_FLOPPY", &mFloppyList, false /* isDVD */,
171 &success);
172
173 if (RT_SUCCESS (rc) && (!success || testing()))
174 {
175 // we assume that a floppy is always /dev/fd[x] with x from 0 to 7
176 char devName[10];
177 for (int i = 0; i <= 7; i++)
178 {
179 sprintf(devName, "/dev/fd%d", i);
180 if (validateDevice(devName, false))
181 mFloppyList.push_back (DriveInfo (devName));
182 }
183 }
184#endif
185 }
186 catch (std::bad_alloc)
187 {
188 rc = VERR_NO_MEMORY;
189 }
190 LogFlowThisFunc (("rc=%Rrc\n", rc));
191 return rc;
192}
193
194/**
195 * Updates the list of host USB devices.
196 *
197 * @returns iprt status code
198 */
199int VBoxMainUSBDeviceInfo::UpdateDevices ()
200{
201 LogFlowThisFunc (("entered\n"));
202 int rc = VINF_SUCCESS;
203 bool success = false; /* Have we succeeded in finding anything yet? */
204 try
205 {
206 mDeviceList.clear();
207#if defined(RT_OS_LINUX)
208#ifdef VBOX_WITH_DBUS
209 if (RT_SUCCESS (rc) && VBoxDBusCheckPresence() && (!success || testing()))
210 rc = getUSBDeviceInfoFromHal(&mDeviceList, &success);
211#endif /* VBOX_WITH_DBUS defined */
212#endif /* RT_OS_LINUX */
213 }
214 catch (std::bad_alloc)
215 {
216 rc = VERR_NO_MEMORY;
217 }
218 LogFlowThisFunc (("rc=%Rrc\n", rc));
219 return rc;
220}
221
222struct VBoxMainHotplugWaiter::Context
223{
224#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
225 /** The connection to DBus */
226 RTMemAutoPtr <DBusConnection, halShutdownPrivate> mConnection;
227 /** Semaphore which is set when a device is hotplugged and reset when
228 * it is read. */
229 bool mTriggered;
230 /** A flag to say that we wish to interrupt the current wait. */
231 bool mInterrupt;
232#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
233};
234
235/** Constructor */
236VBoxMainHotplugWaiter::VBoxMainHotplugWaiter ()
237{
238#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
239 int rc = VINF_SUCCESS;
240
241 if (VBoxDBusCheckPresence())
242 {
243 mContext = new Context;
244 for (unsigned i = 0; RT_SUCCESS(rc) && i < 5 && !mContext->mConnection; ++i)
245 {
246 rc = halInitPrivate (&mContext->mConnection);
247 }
248 if (!mContext->mConnection)
249 rc = VERR_NOT_SUPPORTED;
250 DBusMessage *pMessage;
251 while ( RT_SUCCESS (rc)
252 && (pMessage = dbus_connection_pop_message (mContext->mConnection.get())) != NULL)
253 dbus_message_unref (pMessage); /* empty the message queue. */
254 if ( RT_SUCCESS (rc)
255 && !dbus_connection_add_filter (mContext->mConnection.get(),
256 dbusFilterFunction,
257 &mContext->mTriggered, NULL))
258 rc = VERR_NO_MEMORY;
259 if (RT_FAILURE (rc))
260 mContext->mConnection.reset();
261 }
262#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
263}
264
265/** Destructor */
266VBoxMainHotplugWaiter::~VBoxMainHotplugWaiter ()
267{
268#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
269 if (!!mContext->mConnection)
270 dbus_connection_remove_filter (mContext->mConnection.get(), dbusFilterFunction,
271 &mContext->mTriggered);
272 delete mContext;
273#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
274}
275
276int VBoxMainHotplugWaiter::Wait(unsigned cMillies)
277{
278 int rc = VINF_SUCCESS;
279#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
280 if (!mContext->mConnection)
281 rc = VERR_NOT_SUPPORTED;
282 bool connected = true;
283 mContext->mTriggered = false;
284 mContext->mInterrupt = false;
285 unsigned cRealMillies;
286 if (cMillies != RT_INDEFINITE_WAIT)
287 cRealMillies = cMillies;
288 else
289 cRealMillies = DBUS_POLL_TIMEOUT;
290 while ( RT_SUCCESS (rc) && connected && !mContext->mTriggered
291 && !mContext->mInterrupt)
292 {
293 connected = dbus_connection_read_write_dispatch (mContext->mConnection.get(),
294 cRealMillies);
295 if (cMillies != RT_INDEFINITE_WAIT)
296 mContext->mInterrupt = true;
297 }
298 if (!connected)
299 rc = VERR_TRY_AGAIN;
300#else /* !(defined RT_OS_LINUX && defined VBOX_WITH_DBUS) */
301 rc = VERR_NOT_IMPLEMENTED;
302#endif /* !(defined RT_OS_LINUX && defined VBOX_WITH_DBUS) */
303 return rc;
304}
305
306void VBoxMainHotplugWaiter::Interrupt()
307{
308#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
309 mContext->mInterrupt = true;
310#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
311}
312
313#ifdef RT_OS_LINUX
314/**
315 * Helper function to check whether the given device node is a valid drive
316 */
317/* static */
318bool validateDevice(const char *deviceNode, bool isDVD)
319{
320 AssertReturn(VALID_PTR (deviceNode), VERR_INVALID_POINTER);
321 LogFlowFunc (("deviceNode=%s, isDVD=%d\n", deviceNode, isDVD));
322 struct stat statInfo;
323 bool retValue = false;
324
325 // sanity check
326 if (!deviceNode)
327 {
328 return false;
329 }
330
331 // first a simple stat() call
332 if (stat(deviceNode, &statInfo) < 0)
333 {
334 return false;
335 } else
336 {
337 if (isDVD)
338 {
339 if (S_ISCHR(statInfo.st_mode) || S_ISBLK(statInfo.st_mode))
340 {
341 int fileHandle;
342 // now try to open the device
343 fileHandle = open(deviceNode, O_RDONLY | O_NONBLOCK, 0);
344 if (fileHandle >= 0)
345 {
346 cdrom_subchnl cdChannelInfo;
347 cdChannelInfo.cdsc_format = CDROM_MSF;
348 // this call will finally reveal the whole truth
349#ifdef RT_OS_LINUX
350 if ((ioctl(fileHandle, CDROMSUBCHNL, &cdChannelInfo) == 0) ||
351 (errno == EIO) || (errno == ENOENT) ||
352 (errno == EINVAL) || (errno == ENOMEDIUM))
353#endif
354 {
355 retValue = true;
356 }
357 close(fileHandle);
358 }
359 }
360 } else
361 {
362 // floppy case
363 if (S_ISCHR(statInfo.st_mode) || S_ISBLK(statInfo.st_mode))
364 {
365 /// @todo do some more testing, maybe a nice IOCTL!
366 retValue = true;
367 }
368 }
369 }
370 LogFlowFunc (("retValue=%d\n", retValue));
371 return retValue;
372}
373#else /* !RT_OS_LINUX */
374# error Port me! Copying code over from HostImpl.cpp should be most of the job though.
375#endif /* !RT_OS_LINUX */
376
377/**
378 * Extract the names of drives from an environment variable and add them to a
379 * list if they are valid.
380 * @returns iprt status code
381 * @param pszVar the name of the environment variable. The variable
382 * value should be a list of device node names, separated
383 * by ':' characters.
384 * @param pList the list to append the drives found to
385 * @param isDVD are we looking for DVD drives or for floppies?
386 * @param pfSuccess this will be set to true if we found at least one drive
387 * and to false otherwise. Optional.
388 */
389/* static */
390int getDriveInfoFromEnv(const char *pszVar, DriveInfoList *pList,
391 bool isDVD, bool *pfSuccess)
392{
393 AssertReturn( VALID_PTR (pszVar) && VALID_PTR (pList)
394 && (pfSuccess == NULL || VALID_PTR (pfSuccess)),
395 VERR_INVALID_POINTER);
396 LogFlowFunc (("pszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pszVar,
397 pList, isDVD, pfSuccess));
398 int rc = VINF_SUCCESS;
399 bool success = false;
400 RTMemAutoPtr<char, RTStrFree> drive;
401 const char *pszValue = RTEnvGet (pszVar);
402 if (pszValue != NULL)
403 {
404 drive = RTStrDup (pszValue);
405 if (!drive)
406 rc = VERR_NO_MEMORY;
407 }
408 if (pszValue != NULL && RT_SUCCESS (rc))
409 {
410 char *pDrive = drive.get();
411 char *pDriveNext = strchr (pDrive, ':');
412 while (pDrive != NULL && *pDrive != '\0')
413 {
414 if (pDriveNext != NULL)
415 *pDriveNext = '\0';
416 if (validateDevice(pDrive, isDVD))
417 {
418 pList->push_back (DriveInfo (pDrive));
419 success = true;
420 }
421 if (pDriveNext != NULL)
422 {
423 pDrive = pDriveNext + 1;
424 pDriveNext = strchr (pDrive, ':');
425 }
426 else
427 pDrive = NULL;
428 }
429 }
430 if (pfSuccess != NULL)
431 *pfSuccess = success;
432 LogFlowFunc (("rc=%Rrc, success=%d\n", rc, success));
433 return rc;
434}
435
436#ifdef RT_OS_LINUX
437/**
438 * Helper function to parse the given mount file and add found entries
439 */
440/* static */
441int getDVDInfoFromMTab(char *mountTable, DriveInfoList *pList)
442{
443 AssertReturn(VALID_PTR (mountTable) && VALID_PTR (pList),
444 VERR_INVALID_POINTER);
445#ifdef RT_OS_LINUX
446 LogFlowFunc (("mountTable=%s, pList=%p\n", mountTable, pList));
447 int rc = VINF_SUCCESS;
448 FILE *mtab = setmntent(mountTable, "r");
449 if (mtab)
450 {
451 struct mntent *mntent;
452 RTMemAutoPtr <char, RTStrFree> mnt_type, mnt_dev;
453 char *tmp;
454 while (RT_SUCCESS (rc) && (mntent = getmntent(mtab)))
455 {
456 mnt_type = RTStrDup (mntent->mnt_type);
457 mnt_dev = RTStrDup (mntent->mnt_fsname);
458 if (!mnt_type || !mnt_dev)
459 rc = VERR_NO_MEMORY;
460 // supermount fs case
461 if (RT_SUCCESS (rc) && strcmp(mnt_type.get(), "supermount") == 0)
462 {
463 tmp = strstr(mntent->mnt_opts, "fs=");
464 if (tmp)
465 {
466 mnt_type = RTStrDup(tmp + strlen("fs="));
467 if (!mnt_type)
468 rc = VERR_NO_MEMORY;
469 else
470 {
471 tmp = strchr(mnt_type.get(), ',');
472 if (tmp)
473 *tmp = '\0';
474 }
475 }
476 tmp = strstr(mntent->mnt_opts, "dev=");
477 if (tmp)
478 {
479 mnt_dev = RTStrDup(tmp + strlen("dev="));
480 if (!mnt_dev)
481 rc = VERR_NO_MEMORY;
482 else
483 {
484 tmp = strchr(mnt_dev.get(), ',');
485 if (tmp)
486 *tmp = '\0';
487 }
488 }
489 }
490 // use strstr here to cover things fs types like "udf,iso9660"
491 if (RT_SUCCESS (rc) && strstr(mnt_type.get(), "iso9660") == 0)
492 {
493 if (validateDevice(mnt_dev.get(), true))
494 {
495 bool insert = true;
496 struct stat srcInfo;
497 if (stat (mnt_dev.get(), &srcInfo) < 0)
498 insert = false;
499 for (DriveInfoList::const_iterator it = pList->begin();
500 insert && it != pList->end(); ++it)
501 {
502 struct stat destInfo;
503 if ( (stat (it->mDevice.c_str(), &destInfo) == 0)
504 && (srcInfo.st_rdev == destInfo.st_rdev))
505 insert = false;
506 }
507 if (insert)
508 pList->push_back (DriveInfo (mnt_dev.get()));
509 }
510 }
511 }
512 endmntent(mtab);
513 }
514 return rc;
515#endif
516}
517
518#endif /* RT_OS_LINUX */
519
520#if defined(RT_OS_LINUX) && defined(VBOX_WITH_DBUS)
521/* Linux, load libdbus statically */
522
523/** Wrapper class around DBusError for automatic cleanup */
524class autoDBusError
525{
526 DBusError mError;
527public:
528 autoDBusError () { dbus_error_init (&mError); }
529 ~autoDBusError ()
530 {
531 if (IsSet())
532 dbus_error_free (&mError);
533 }
534 DBusError &get () { return mError; }
535 bool IsSet ()
536 {
537 Assert ((mError.name == NULL) == (mError.message == NULL));
538 return (mError.name != NULL);
539 }
540 bool HasName (const char *pszName)
541 {
542 Assert ((mError.name == NULL) == (mError.message == NULL));
543 return (RTStrCmp (mError.name, pszName) == 0);
544 }
545 void FlowLog ()
546 {
547 if (IsSet ())
548 LogFlow(("DBus error %s: %s\n", mError.name, mError.message));
549 }
550};
551
552/**
553 * Helper function for setting up a connection to hal
554 * @returns iprt status code
555 * @param ppConnection where to store the connection handle
556 */
557/* static */
558int halInit (RTMemAutoPtr <DBusConnection, halShutdown> *pConnection)
559{
560 AssertReturn(VALID_PTR (pConnection), VERR_INVALID_POINTER);
561 LogFlowFunc (("pConnection=%p\n", pConnection));
562 int rc = VINF_SUCCESS;
563 bool halSuccess = true;
564 autoDBusError dbusError;
565
566 RTMemAutoPtr <DBusConnection, VBoxDBusConnectionUnref> dbusConnection;
567 dbusConnection = dbus_bus_get (DBUS_BUS_SYSTEM, &dbusError.get());
568 if (!dbusConnection)
569 halSuccess = false;
570 if (halSuccess)
571 {
572 dbus_connection_set_exit_on_disconnect (dbusConnection.get(), false);
573 halSuccess = dbus_bus_name_has_owner (dbusConnection.get(),
574 "org.freedesktop.Hal", &dbusError.get());
575 }
576 if (halSuccess)
577 {
578 dbus_bus_add_match (dbusConnection.get(),
579 "type='signal',"
580 "interface='org.freedesktop.Hal.Manager',"
581 "sender='org.freedesktop.Hal',"
582 "path='/org/freedesktop/Hal/Manager'",
583 &dbusError.get());
584 halSuccess = !dbusError.IsSet();
585 }
586 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
587 rc = VERR_NO_MEMORY;
588 if (halSuccess)
589 *pConnection = dbusConnection.release();
590 LogFlowFunc(("rc=%Rrc, (*pConnection).get()=%p\n", rc, (*pConnection).get()));
591 dbusError.FlowLog();
592 return rc;
593}
594
595/**
596 * Helper function for setting up a private connection to hal
597 * @returns iprt status code
598 * @param pConnection where to store the connection handle
599 */
600/* static */
601int halInitPrivate (RTMemAutoPtr <DBusConnection, halShutdownPrivate> *pConnection)
602{
603 AssertReturn(VALID_PTR (pConnection), VERR_INVALID_POINTER);
604 LogFlowFunc (("pConnection=%p\n", pConnection));
605 int rc = VINF_SUCCESS;
606 bool halSuccess = true;
607 autoDBusError dbusError;
608
609 RTMemAutoPtr <DBusConnection, VBoxDBusConnectionUnref> dbusConnection;
610 dbusConnection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &dbusError.get());
611 if (!dbusConnection)
612 halSuccess = false;
613 if (halSuccess)
614 {
615 dbus_connection_set_exit_on_disconnect (dbusConnection.get(), false);
616 halSuccess = dbus_bus_name_has_owner (dbusConnection.get(),
617 "org.freedesktop.Hal", &dbusError.get());
618 }
619 if (halSuccess)
620 {
621 dbus_bus_add_match (dbusConnection.get(),
622 "type='signal',"
623 "interface='org.freedesktop.Hal.Manager',"
624 "sender='org.freedesktop.Hal',"
625 "path='/org/freedesktop/Hal/Manager'",
626 &dbusError.get());
627 halSuccess = !dbusError.IsSet();
628 }
629 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
630 rc = VERR_NO_MEMORY;
631 if (halSuccess)
632 *pConnection = dbusConnection.release();
633 LogFlowFunc(("rc=%Rrc, (*pConnection).get()=%p\n", rc, (*pConnection).get()));
634 dbusError.FlowLog();
635 return rc;
636}
637
638/**
639 * Helper function for shutting down a connection to hal
640 * @param pConnection the connection handle
641 */
642/* static */
643void halShutdown (DBusConnection *pConnection)
644{
645 AssertReturnVoid(VALID_PTR (pConnection));
646 LogFlowFunc (("pConnection=%p\n", pConnection));
647 autoDBusError dbusError;
648
649 dbus_bus_remove_match (pConnection,
650 "type='signal',"
651 "interface='org.freedesktop.Hal.Manager',"
652 "sender='org.freedesktop.Hal',"
653 "path='/org/freedesktop/Hal/Manager'",
654 &dbusError.get());
655 dbus_connection_unref (pConnection);
656 LogFlowFunc(("returning\n"));
657 dbusError.FlowLog();
658}
659
660/**
661 * Helper function for shutting down a connection to hal
662 * @param pConnection the connection handle
663 */
664/* static */
665void halShutdownPrivate (DBusConnection *pConnection)
666{
667 AssertReturnVoid(VALID_PTR (pConnection));
668 LogFlowFunc (("pConnection=%p\n", pConnection));
669 autoDBusError dbusError;
670
671 dbus_bus_remove_match (pConnection,
672 "type='signal',"
673 "interface='org.freedesktop.Hal.Manager',"
674 "sender='org.freedesktop.Hal',"
675 "path='/org/freedesktop/Hal/Manager'",
676 &dbusError.get());
677 dbus_connection_close (pConnection);
678 dbus_connection_unref (pConnection);
679 LogFlowFunc(("returning\n"));
680 dbusError.FlowLog();
681}
682
683/**
684 * Find the UDIs of hal entries that contain Key=Value property.
685 * @returns iprt status code. If a non-fatal error occurs, we return success
686 * but reset pMessage to NULL.
687 * @param pConnection an initialised connection DBus
688 * @param pszKey the property key
689 * @param pszValue the property value
690 * @param pMessage where to store the return DBus message. This must be
691 * parsed to get at the UDIs. NOT optional.
692 */
693/* static */
694int halFindDeviceStringMatch (DBusConnection *pConnection, const char *pszKey,
695 const char *pszValue,
696 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage)
697{
698 AssertReturn( VALID_PTR (pConnection) && VALID_PTR (pszKey)
699 && VALID_PTR (pszValue) && VALID_PTR (pMessage),
700 VERR_INVALID_POINTER);
701 LogFlowFunc (("pConnection=%p, pszKey=%s, pszValue=%s, pMessage=%p\n",
702 pConnection, pszKey, pszValue, pMessage));
703 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
704 bool halSuccess = true; /* We set this to false to abort the operation. */
705 autoDBusError dbusError;
706
707 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, reply;
708 if (halSuccess && RT_SUCCESS (rc))
709 {
710 message = dbus_message_new_method_call ("org.freedesktop.Hal",
711 "/org/freedesktop/Hal/Manager",
712 "org.freedesktop.Hal.Manager",
713 "FindDeviceStringMatch");
714 if (!message)
715 rc = VERR_NO_MEMORY;
716 }
717 if (halSuccess && RT_SUCCESS (rc))
718 {
719 DBusMessageIter iterAppend;
720 dbus_message_iter_init_append (message.get(), &iterAppend);
721 dbus_message_iter_append_basic (&iterAppend, DBUS_TYPE_STRING, &pszKey);
722 dbus_message_iter_append_basic (&iterAppend, DBUS_TYPE_STRING, &pszValue);
723 reply = dbus_connection_send_with_reply_and_block (pConnection,
724 message.get(), -1,
725 &dbusError.get());
726 if (!reply)
727 halSuccess = false;
728 }
729 *pMessage = reply.release ();
730 LogFlowFunc (("rc=%Rrc, *pMessage.value()=%p\n", rc, (*pMessage).get()));
731 dbusError.FlowLog();
732 return rc;
733}
734
735/**
736 * Read a set of string properties for a device. If some of the properties are
737 * not of type DBUS_TYPE_STRING then a NULL pointer will be returned for them.
738 * @returns iprt status code. If the operation failed for non-fatal reasons
739 * then we return success and leave pMessage untouched - reset it
740 * before the call to detect this.
741 * @param pConnection an initialised connection DBus
742 * @param pszUdi the Udi of the device
743 * @param cProps the number of property values to look up
744 * @param papszKeys the keys of the properties to be looked up
745 * @param papszValues where to store the values of the properties. The
746 * strings returned will be valid until the message
747 * returned in @a ppMessage is freed. Undefined if
748 * the message is NULL.
749 * @param pMessage where to store the return DBus message. The caller
750 * is responsible for freeing this once they have
751 * finished with the value strings. NOT optional.
752 */
753/* static */
754int halGetPropertyStrings (DBusConnection *pConnection, const char *pszUdi,
755 size_t cProps, const char **papszKeys,
756 char **papszValues,
757 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> *pMessage)
758{
759 AssertReturn( VALID_PTR (pConnection) && VALID_PTR (pszUdi)
760 && VALID_PTR (papszKeys) && VALID_PTR (papszValues)
761 && VALID_PTR (pMessage),
762 VERR_INVALID_POINTER);
763 LogFlowFunc (("pConnection=%p, pszUdi=%s, cProps=%llu, papszKeys=%p, papszValues=%p, pMessage=%p\n",
764 pConnection, pszUdi, cProps, papszKeys, papszValues, pMessage));
765 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
766 bool halSuccess = true; /* We set this to false to abort the operation. */
767 autoDBusError dbusError;
768
769 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, reply;
770 DBusMessageIter iterGet, iterProps, iterKey, iterValue;
771
772 /* Initialise the return array to NULLs */
773 for (size_t i = 0; i < cProps; ++i)
774 papszValues[i] = NULL;
775
776 /* Send a GetAllProperties message to hald */
777 message = dbus_message_new_method_call ("org.freedesktop.Hal", pszUdi,
778 "org.freedesktop.Hal.Device",
779 "GetAllProperties");
780 if (!message)
781 rc = VERR_NO_MEMORY;
782 if (halSuccess && RT_SUCCESS (rc))
783 {
784 reply = dbus_connection_send_with_reply_and_block (pConnection,
785 message.get(), -1,
786 &dbusError.get());
787 if (!reply)
788 halSuccess = false;
789 }
790
791 /* Parse the reply */
792 if (halSuccess && RT_SUCCESS (rc))
793 {
794 dbus_message_iter_init (reply.get(), &iterGet);
795 if ( dbus_message_iter_get_arg_type (&iterGet) != DBUS_TYPE_ARRAY
796 && dbus_message_iter_get_element_type (&iterGet) != DBUS_TYPE_DICT_ENTRY)
797 halSuccess = false;
798 }
799 if (halSuccess && RT_SUCCESS (rc))
800 dbus_message_iter_recurse (&iterGet, &iterProps);
801 /* Go through all entries in the reply and see if any match our keys. */
802 while ( halSuccess && RT_SUCCESS (rc)
803 && dbus_message_iter_get_arg_type (&iterProps)
804 == DBUS_TYPE_DICT_ENTRY)
805 {
806 const char *pszKey;
807 DBusMessageIter iterEntry, iterValue;
808 dbus_message_iter_recurse (&iterProps, &iterEntry);
809 dbus_message_iter_get_basic (&iterEntry, &pszKey);
810 dbus_message_iter_next (&iterEntry);
811 dbus_message_iter_recurse (&iterEntry, &iterValue);
812 /* Fill in any matches. */
813 for (size_t i = 0; i < cProps; ++i)
814 if (strcmp (pszKey, papszKeys[i]) == 0)
815 {
816 if (dbus_message_iter_get_arg_type (&iterValue) == DBUS_TYPE_STRING)
817 dbus_message_iter_get_basic (&iterValue, &papszValues[i]);
818 }
819 dbus_message_iter_next (&iterProps);
820 }
821 if (RT_SUCCESS (rc) && halSuccess)
822 *pMessage = reply.release();
823 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
824 rc = VERR_NO_MEMORY;
825 LogFlowFunc (("rc=%Rrc, *pMessage.value()=%p\n", rc, (*pMessage).get()));
826 dbusError.FlowLog();
827 return rc;
828}
829
830/**
831 * Helper function to query the hal subsystem for information about drives
832 * attached to the system.
833 * @returns iprt status code
834 * @param pList where to add information about the drives detected
835 * @param isDVD are we looking for DVDs or floppies?
836 * @param pfSuccess will be set to true if all interactions with hal
837 * succeeded and to false otherwise. Optional.
838 *
839 * @returns IPRT status code
840 */
841/* static */
842int getDriveInfoFromHal(DriveInfoList *pList, bool isDVD, bool *pfSuccess)
843{
844 AssertReturn(VALID_PTR (pList) && (pfSuccess == NULL || VALID_PTR (pfSuccess)),
845 VERR_INVALID_POINTER);
846 LogFlowFunc (("pList=%p, isDVD=%d, pfSuccess=%p\n", pList, isDVD, pfSuccess));
847 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
848 bool halSuccess = true; /* We set this to false to abort the operation. */
849 autoDBusError dbusError;
850
851 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, replyFind, replyGet;
852 RTMemAutoPtr <DBusConnection, halShutdown> dbusConnection;
853 DBusMessageIter iterFind, iterUdis;
854
855 rc = halInit (&dbusConnection);
856 if (!dbusConnection)
857 halSuccess = false;
858 if (halSuccess && RT_SUCCESS (rc))
859 {
860 rc = halFindDeviceStringMatch (dbusConnection.get(), "storage.drive_type",
861 isDVD ? "cdrom" : "floppy", &replyFind);
862 if (!replyFind)
863 halSuccess = false;
864 }
865 if (halSuccess && RT_SUCCESS (rc))
866 {
867 dbus_message_iter_init (replyFind.get(), &iterFind);
868 if (dbus_message_iter_get_arg_type (&iterFind) != DBUS_TYPE_ARRAY)
869 halSuccess = false;
870 }
871 if (halSuccess && RT_SUCCESS (rc))
872 dbus_message_iter_recurse (&iterFind, &iterUdis);
873 for (; halSuccess && RT_SUCCESS (rc)
874 && dbus_message_iter_get_arg_type (&iterUdis) == DBUS_TYPE_STRING;
875 dbus_message_iter_next(&iterUdis))
876 {
877 /* Now get all properties from the iterator */
878 const char *pszUdi;
879 dbus_message_iter_get_basic (&iterUdis, &pszUdi);
880 static const char *papszKeys[] =
881 { "block.device", "info.product", "info.vendor" };
882 char *papszValues[RT_ELEMENTS (papszKeys)];
883 rc = halGetPropertyStrings (dbusConnection.get(), pszUdi, RT_ELEMENTS (papszKeys),
884 papszKeys, papszValues, &replyGet);
885 std::string description;
886 const char *pszDevice = papszValues[0], *pszProduct = papszValues[1],
887 *pszVendor = papszValues[2];
888 if (!!replyGet && pszDevice == NULL)
889 halSuccess = false;
890 if (!!replyGet && pszDevice != NULL)
891 {
892 if ((pszVendor != NULL) && (pszVendor[0] != '\0'))
893 (description += pszVendor) += " ";
894 if ((pszProduct != NULL && pszProduct[0] != '\0'))
895 description += pszProduct;
896 pList->push_back (DriveInfo (pszDevice, pszUdi, description));
897 }
898 }
899 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
900 rc = VERR_NO_MEMORY;
901 if (pfSuccess != NULL)
902 *pfSuccess = halSuccess;
903 LogFlow (("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
904 dbusError.FlowLog();
905 return rc;
906}
907
908/**
909 * Helper function to query the hal subsystem for information about USB devices
910 * attached to the system.
911 * @returns iprt status code
912 * @param pList where to add information about the devices detected
913 * @param pfSuccess will be set to true if all interactions with hal
914 * succeeded and to false otherwise. Optional.
915 *
916 * @returns IPRT status code
917 */
918/* static */
919int getUSBDeviceInfoFromHal(USBDeviceInfoList *pList, bool *pfSuccess)
920{
921 AssertReturn(VALID_PTR (pList) && (pfSuccess == NULL || VALID_PTR (pfSuccess)),
922 VERR_INVALID_POINTER);
923 LogFlowFunc (("pList=%p, pfSuccess=%p\n", pList, pfSuccess));
924 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
925 bool halSuccess = true; /* We set this to false to abort the operation. */
926 autoDBusError dbusError;
927
928 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, replyFind, replyGet;
929 RTMemAutoPtr <DBusConnection, halShutdown> dbusConnection;
930 DBusMessageIter iterFind, iterUdis;
931
932 rc = halInit (&dbusConnection);
933 if (!dbusConnection)
934 halSuccess = false;
935 if (halSuccess && RT_SUCCESS (rc))
936 {
937 rc = halFindDeviceStringMatch (dbusConnection.get(), "info.subsystem",
938 "usb_device", &replyFind);
939 if (!replyFind)
940 halSuccess = false;
941 }
942 if (halSuccess && RT_SUCCESS (rc))
943 {
944 dbus_message_iter_init (replyFind.get(), &iterFind);
945 if (dbus_message_iter_get_arg_type (&iterFind) != DBUS_TYPE_ARRAY)
946 halSuccess = false;
947 }
948 if (halSuccess && RT_SUCCESS (rc))
949 dbus_message_iter_recurse (&iterFind, &iterUdis);
950 for (; halSuccess && RT_SUCCESS (rc)
951 && dbus_message_iter_get_arg_type (&iterUdis) == DBUS_TYPE_STRING;
952 dbus_message_iter_next(&iterUdis))
953 {
954 /* Now get the device node and the sysfs path from the iterator */
955 const char *pszUdi;
956 dbus_message_iter_get_basic (&iterUdis, &pszUdi);
957 static const char *papszKeys[] = { "linux.device_file", "linux.sysfs_path" };
958 char *papszValues[RT_ELEMENTS (papszKeys)];
959 rc = halGetPropertyStrings (dbusConnection.get(), pszUdi, RT_ELEMENTS (papszKeys),
960 papszKeys, papszValues, &replyGet);
961 std::string description;
962 const char *pszDevice = papszValues[0], *pszSysfsPath = papszValues[1];
963 /* Get the interfaces for this device. */
964 if (!!replyGet && pszDevice && pszSysfsPath)
965 {
966 USBDeviceInfo info (pszDevice, pszSysfsPath);
967 bool ifaceSuccess = true; /* If we can't get the interfaces, just
968 * skip this one device. */
969 rc = getUSBInterfacesFromHal (&info.mInterfaces, pszUdi, &ifaceSuccess);
970 if (RT_SUCCESS(rc) && halSuccess && ifaceSuccess)
971 pList->push_back (info);
972 }
973 }
974 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
975 rc = VERR_NO_MEMORY;
976 if (pfSuccess != NULL)
977 *pfSuccess = halSuccess;
978 LogFlow (("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
979 dbusError.FlowLog();
980 return rc;
981}
982
983/**
984 * Helper function to query the hal subsystem for information about USB devices
985 * attached to the system.
986 * @returns iprt status code
987 * @param pList where to add information about the devices detected. If
988 * certain interfaces are not found (@a pfFound is false on
989 * return) this may contain invalid information.
990 * @param pcszUdi the hal UDI of the device
991 * @param pfSuccess will be set to true if the operation succeeds and to
992 * false if it fails for non-critical reasons. Optional.
993 *
994 * @returns IPRT status code
995 */
996/* static */
997int getUSBInterfacesFromHal(std::vector <std::string> *pList,
998 const char *pcszUdi, bool *pfSuccess)
999{
1000 AssertReturn(VALID_PTR (pList) && VALID_PTR (pcszUdi) &&
1001 (pfSuccess == NULL || VALID_PTR (pfSuccess)),
1002 VERR_INVALID_POINTER);
1003 LogFlowFunc (("pList=%p, pcszUdi=%s, pfSuccess=%p\n", pList, pcszUdi,
1004 pfSuccess));
1005 int rc = VINF_SUCCESS; /* We set this to failure on fatal errors. */
1006 bool halSuccess = true; /* We set this to false to abort the operation. */
1007 autoDBusError dbusError;
1008
1009 RTMemAutoPtr <DBusMessage, VBoxDBusMessageUnref> message, replyFind, replyGet;
1010 RTMemAutoPtr <DBusConnection, halShutdown> dbusConnection;
1011 DBusMessageIter iterFind, iterUdis;
1012
1013 rc = halInit (&dbusConnection);
1014 if (!dbusConnection)
1015 halSuccess = false;
1016 if (halSuccess && RT_SUCCESS (rc))
1017 {
1018 /* Look for children of the current UDI. */
1019 rc = halFindDeviceStringMatch (dbusConnection.get(), "info.parent",
1020 pcszUdi, &replyFind);
1021 if (!replyFind)
1022 halSuccess = false;
1023 }
1024 if (halSuccess && RT_SUCCESS (rc))
1025 {
1026 dbus_message_iter_init (replyFind.get(), &iterFind);
1027 if (dbus_message_iter_get_arg_type (&iterFind) != DBUS_TYPE_ARRAY)
1028 halSuccess = false;
1029 }
1030 if (halSuccess && RT_SUCCESS (rc))
1031 dbus_message_iter_recurse (&iterFind, &iterUdis);
1032 for (; halSuccess && RT_SUCCESS (rc)
1033 && dbus_message_iter_get_arg_type (&iterUdis) == DBUS_TYPE_STRING;
1034 dbus_message_iter_next(&iterUdis))
1035 {
1036 /* Now get the sysfs path and the subsystem from the iterator */
1037 const char *pszUdi;
1038 dbus_message_iter_get_basic (&iterUdis, &pszUdi);
1039 static const char *papszKeys[] = { "linux.sysfs_path", "info.subsystem" };
1040 char *papszValues[RT_ELEMENTS (papszKeys)];
1041 rc = halGetPropertyStrings (dbusConnection.get(), pszUdi, RT_ELEMENTS (papszKeys),
1042 papszKeys, papszValues, &replyGet);
1043 std::string description;
1044 const char *pszSysfsPath = papszValues[0], *pszSubsystem = papszValues[1];
1045 if (!replyGet)
1046 halSuccess = false;
1047 if (!!replyGet && pszSysfsPath == NULL)
1048 halSuccess = false;
1049 if ( halSuccess && RT_SUCCESS (rc)
1050 && RTStrCmp (pszSubsystem, "usb_device") != 0) /* Children of buses can also be devices. */
1051 pList->push_back (pszSysfsPath);
1052 }
1053 if (dbusError.HasName (DBUS_ERROR_NO_MEMORY))
1054 rc = VERR_NO_MEMORY;
1055 if (pfSuccess != NULL)
1056 *pfSuccess = halSuccess;
1057 LogFlow (("rc=%Rrc, halSuccess=%d\n", rc, halSuccess));
1058 dbusError.FlowLog();
1059 return rc;
1060}
1061
1062/**
1063 * When it is registered with DBus, this function will be called by
1064 * dbus_connection_read_write_dispatch each time a message is received over the
1065 * DBus connection. We check whether that message was caused by a hal device
1066 * hotplug event, and if so we set a flag. dbus_connection_read_write_dispatch
1067 * will return after calling its filter functions, and its caller should then
1068 * check the status of the flag passed to the filter function.
1069 *
1070 * @param pConnection The DBus connection we are using.
1071 * @param pMessage The DBus message which just arrived.
1072 * @param pvUser A pointer to the flag variable we are to set.
1073 */
1074/* static */
1075DBusHandlerResult dbusFilterFunction (DBusConnection *pConnection,
1076 DBusMessage *pMessage, void *pvUser)
1077{
1078 bool *pTriggered = reinterpret_cast<bool *> (pvUser);
1079 if ( dbus_message_is_signal (pMessage, "org.freedesktop.Hal.Manager",
1080 "DeviceAdded")
1081 || dbus_message_is_signal (pMessage, "org.freedesktop.Hal.Manager",
1082 "DeviceRemoved"))
1083 {
1084 *pTriggered = true;
1085 }
1086 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1087}
1088#endif /* RT_OS_LINUX && VBOX_WITH_DBUS */
1089
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