VirtualBox

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

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

Main/USB: code documentation

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