VirtualBox

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

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

Linux 2.6.28 hack

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