HostHardwareLinux.cpp revision 2599750883f13b2a38c421dc073df83e2f9cdae6
/* $Id$ */
/** @file
* Classes for handling hardware detection under Linux. Please feel free to
* expand these to work for other systems (Solaris!) or to add new ones for
* other systems.
*/
/*
* Copyright (C) 2008 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#define LOG_GROUP LOG_GROUP_MAIN
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <HostHardwareLinux.h>
#ifdef RT_OS_LINUX
# include <unistd.h>
# include <fcntl.h>
# include <mntent.h>
/* bird: This is a hack to work around conflicts between these linux kernel headers
* and the GLIBC tcpip headers. They have different declarations of the 4
* standard byte order functions. */
// # define _LINUX_BYTEORDER_GENERIC_H
# define _LINUX_BYTEORDER_SWABB_H
# ifdef VBOX_WITH_DBUS
# include <vbox-dbus.h>
# endif
# include <errno.h>
#endif /* RT_OS_LINUX */
#include <vector>
/*******************************************************************************
* Global Variables *
*******************************************************************************/
bool g_testHostHardwareLinux = false;
static bool testing () { return g_testHostHardwareLinux; }
/*******************************************************************************
* Typedefs and Defines *
*******************************************************************************/
/** When waiting for hotplug events, we currently restart the wait after at
* most this many milliseconds. */
#ifdef VBOX_WITH_DBUS
/* These must be extern to be usable in the RTMemAutoPtr template */
/*
static int halFindDeviceStringMatchVector (DBusConnection *pConnection,
const char *pszKey,
const char *pszValue,
std::vector<iprt::MiniString> *pMatches);
*/
const char **papszKeys, char **papszValues,
/*
static int halGetPropertyStringsVector (DBusConnection *pConnection,
const char *pszUdi, size_t cProps,
const char **papszKeys,
std::vector<iprt::MiniString> *pMatches,
bool *pfMatches, bool *pfSuccess);
*/
bool *pfSuccess);
#endif /* VBOX_WITH_DBUS */
int VBoxMainDriveInfo::updateDVDs ()
{
LogFlowThisFunc (("entered\n"));
int rc = VINF_SUCCESS;
bool success = false; /* Have we succeeded in finding anything yet? */
try
{
#if defined(RT_OS_LINUX)
#ifdef VBOX_WITH_DBUS
#endif /* VBOX_WITH_DBUS defined */
// On Linux without hal, the situation is much more complex. We will take a
// heuristical approach and also allow the user to specify a list of host
// CDROMs using an environment variable.
// The general strategy is to try some known device names and see of they
// API to parse it) for CDROM devices. Ok, let's start!
&success);
{
// this is a good guess usually
if (validateDevice("/dev/cdrom", true))
// check the mounted drives
// check the drives that can be mounted
if (RT_SUCCESS (rc))
}
#endif
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
int VBoxMainDriveInfo::updateFloppies ()
{
LogFlowThisFunc (("entered\n"));
int rc = VINF_SUCCESS;
bool success = false; /* Have we succeeded in finding anything yet? */
try
{
mFloppyList.clear ();
#if defined(RT_OS_LINUX)
#ifdef VBOX_WITH_DBUS
if ( RT_SUCCESS (rc)
&& RT_SUCCESS(VBoxLoadDBusLib())
#endif /* VBOX_WITH_DBUS defined */
// As with the CDROMs, on Linux we have to take a multi-level approach
// involving parsing the mount tables. As this is not bulletproof, we'll
// give the user the chance to override the detection by an environment
// variable and skip the detection.
&success);
{
char devName[10];
for (int i = 0; i <= 7; i++)
{
if (validateDevice(devName, false))
}
}
#endif
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
int VBoxMainUSBDeviceInfo::UpdateDevices ()
{
LogFlowThisFunc (("entered\n"));
int rc = VINF_SUCCESS;
bool success = false; /* Have we succeeded in finding anything yet? */
try
{
bool halSuccess = false;
mDeviceList.clear();
#if defined(RT_OS_LINUX)
#ifdef VBOX_WITH_DBUS
if ( RT_SUCCESS (rc)
&& RT_SUCCESS(VBoxLoadDBusLib())
/* Try the old API if the new one *succeeded* as only one of them will
* pick up devices anyway. */
if (!success)
#endif /* VBOX_WITH_DBUS defined */
#endif /* RT_OS_LINUX */
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
struct VBoxMainHotplugWaiter::Context
{
#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
/** The connection to DBus */
/** Semaphore which is set when a device is hotplugged and reset when
* it is read. */
volatile bool mTriggered;
/** A flag to say that we wish to interrupt the current wait. */
volatile bool mInterrupt;
/** Constructor */
#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
};
/* This constructor sets up a private connection to the DBus daemon, connects
* to the hal service and installs a filter which sets the mTriggered flag in
* the Context structure when a device (not necessarily USB) is added or
* removed. */
{
#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
int rc = VINF_SUCCESS;
if (RT_SUCCESS(VBoxLoadDBusLib()))
{
{
}
if (!mContext->mConnection)
while ( RT_SUCCESS (rc)
if ( RT_SUCCESS (rc)
rc = VERR_NO_MEMORY;
if (RT_FAILURE (rc))
}
#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
}
/* Destructor */
{
#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
if (!!mContext->mConnection)
(void *) &mContext->mTriggered);
delete mContext;
#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
}
/* Currently this is implemented using a timed out wait on our private DBus
* connection. Because the connection is private we don't have to worry about
* blocking other users. */
{
int rc = VINF_SUCCESS;
#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
if (!mContext->mConnection)
bool connected = true;
mContext->mTriggered = false;
mContext->mInterrupt = false;
unsigned cRealMillies;
if (cMillies != RT_INDEFINITE_WAIT)
else
&& !mContext->mInterrupt)
{
if (mContext->mInterrupt)
LogFlowFunc(("wait loop interrupted\n"));
if (cMillies != RT_INDEFINITE_WAIT)
mContext->mInterrupt = true;
}
if (!connected)
rc = VERR_TRY_AGAIN;
#else /* !(defined RT_OS_LINUX && defined VBOX_WITH_DBUS) */
#endif /* !(defined RT_OS_LINUX && defined VBOX_WITH_DBUS) */
return rc;
}
/* Set a flag to tell the Wait not to resume next time it times out. */
void VBoxMainHotplugWaiter::Interrupt()
{
#if defined RT_OS_LINUX && defined VBOX_WITH_DBUS
LogFlowFunc(("\n"));
mContext->mInterrupt = true;
#endif /* defined RT_OS_LINUX && defined VBOX_WITH_DBUS */
}
#ifdef RT_OS_LINUX
/**
* Helper function to check whether the given device node is a valid drive
*/
/* static */
{
bool retValue = false;
// sanity check
if (!deviceNode)
{
return false;
}
// first a simple stat() call
{
return false;
} else
{
if (isDVD)
{
{
int fileHandle;
// now try to open the device
if (fileHandle >= 0)
{
// this call will finally reveal the whole truth
#ifdef RT_OS_LINUX
#endif
{
retValue = true;
}
}
}
} else
{
// floppy case
{
/// @todo do some more testing, maybe a nice IOCTL!
retValue = true;
}
}
}
return retValue;
}
#else /* !RT_OS_LINUX */
#endif /* !RT_OS_LINUX */
/**
* Extract the names of drives from an environment variable and add them to a
* list if they are valid.
* @returns iprt status code
* @param pszVar the name of the environment variable. The variable
* value should be a list of device node names, separated
* by ':' characters.
* @param pList the list to append the drives found to
* @param isDVD are we looking for DVD drives or for floppies?
* @param pfSuccess this will be set to true if we found at least one drive
* and to false otherwise. Optional.
*/
/* static */
{
int rc = VINF_SUCCESS;
bool success = false;
try
{
{
if (!drive)
rc = VERR_NO_MEMORY;
}
{
{
if (pDriveNext != NULL)
*pDriveNext = '\0';
{
success = true;
}
if (pDriveNext != NULL)
{
}
else
}
}
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
#ifdef RT_OS_LINUX
/**
* Helper function to parse the given mount file and add found entries
*/
/* static */
{
#ifdef RT_OS_LINUX
int rc = VINF_SUCCESS;
if (mtab)
{
try
{
char *tmp;
{
rc = VERR_NO_MEMORY;
// supermount fs case
{
if (tmp)
{
if (!mnt_type)
rc = VERR_NO_MEMORY;
else
{
if (tmp)
*tmp = '\0';
}
}
if (tmp)
{
if (!mnt_dev)
rc = VERR_NO_MEMORY;
else
{
if (tmp)
*tmp = '\0';
}
}
}
// use strstr here to cover things fs types like "udf,iso9660"
{
{
bool insert = true;
insert = false;
{
insert = false;
}
if (insert)
}
}
}
}
{
rc = VERR_NO_MEMORY;
}
}
return rc;
#endif
}
#endif /* RT_OS_LINUX */
#if defined(RT_OS_LINUX) && defined(VBOX_WITH_DBUS)
/** Wrapper class around DBusError for automatic cleanup */
class autoDBusError
{
public:
~autoDBusError ()
{
if (IsSet())
}
bool IsSet ()
{
}
{
}
void FlowLog ()
{
if (IsSet ())
}
};
/**
* Helper function for setting up a connection to the DBus daemon and
* registering with the hal service.
*
* @note If libdbus is being loaded at runtime then be sure to call
* VBoxDBusCheckPresence before calling this.
* @returns iprt status code
* @param ppConnection where to store the connection handle
*/
/* static */
{
int rc = VINF_SUCCESS;
bool halSuccess = true;
if (!dbusConnection)
halSuccess = false;
if (halSuccess)
{
}
if (halSuccess)
{
"type='signal',"
"interface='org.freedesktop.Hal.Manager',"
"sender='org.freedesktop.Hal',"
"path='/org/freedesktop/Hal/Manager'",
}
rc = VERR_NO_MEMORY;
if (halSuccess)
return rc;
}
/**
* Helper function for setting up a private connection to the DBus daemon and
* registering with the hal service. Private connections are considered
* unsociable and should not be used unnecessarily (as per the DBus API docs).
*
* @note If libdbus is being loaded at runtime then be sure to call
* VBoxDBusCheckPresence before calling this.
* @returns iprt status code
* @param pConnection where to store the connection handle
*/
/* static */
{
int rc = VINF_SUCCESS;
bool halSuccess = true;
if (!dbusConnection)
halSuccess = false;
if (halSuccess)
{
}
if (halSuccess)
{
"type='signal',"
"interface='org.freedesktop.Hal.Manager',"
"sender='org.freedesktop.Hal',"
"path='/org/freedesktop/Hal/Manager'",
}
rc = VERR_NO_MEMORY;
if (halSuccess)
return rc;
}
/**
* Helper function for shutting down a connection to DBus and hal.
* @param pConnection the connection handle
*/
/* extern */
{
"type='signal',"
"interface='org.freedesktop.Hal.Manager',"
"sender='org.freedesktop.Hal',"
"path='/org/freedesktop/Hal/Manager'",
LogFlowFunc(("returning\n"));
}
/**
* Helper function for shutting down a private connection to DBus and hal.
* @param pConnection the connection handle
*/
/* extern */
{
"type='signal',"
"interface='org.freedesktop.Hal.Manager',"
"sender='org.freedesktop.Hal',"
"path='/org/freedesktop/Hal/Manager'",
LogFlowFunc(("returning\n"));
}
/** Wrapper around dbus_connection_unref. We need this to use it as a real
* function in auto pointers, as a function pointer won't wash here. */
/* extern */
{
}
/**
* This function closes and unrefs a private connection to dbus. It should
* only be called once no-one else is referencing the connection.
*/
/* extern */
{
}
/** Wrapper around dbus_message_unref. We need this to use it as a real
* function in auto pointers, as a function pointer won't wash here. */
/* extern */
{
}
/**
* Find the UDIs of hal entries that contain Key=Value property.
* @returns iprt status code. If a non-fatal error occurs, we return success
* but reset pMessage to NULL.
* @param pConnection an initialised connection DBus
* @param pszKey the property key
* @param pszValue the property value
* @param pMessage where to store the return DBus message. This must be
* parsed to get at the UDIs. NOT optional.
*/
/* static */
const char *pszValue,
{
LogFlowFunc (("pConnection=%p, pszKey=%s, pszValue=%s, pMessage=%p\n",
bool halSuccess = true; /* We set this to false to abort the operation. */
{
"/org/freedesktop/Hal/Manager",
"org.freedesktop.Hal.Manager",
"FindDeviceStringMatch");
if (!message)
rc = VERR_NO_MEMORY;
}
{
if (!reply)
halSuccess = false;
}
return rc;
}
/**
* Find the UDIs of hal entries that contain Key=Value property and return the
* result on the end of a vector of iprt::MiniString.
* @returns iprt status code. If a non-fatal error occurs, we return success
* but set *pfSuccess to false.
* @param pConnection an initialised connection DBus
* @param pszKey the property key
* @param pszValue the property value
* @param pMatches pointer to an array of iprt::MiniString to append the
* results to. NOT optional.
* @param pfSuccess will be set to true if the operation succeeds
*/
/* static */
bool *pfSuccess)
{
LogFlowFunc (("pConnection=%p, pszKey=%s, pszValue=%s, pMatches=%p, pfSuccess=%p\n",
bool halSuccess = true; /* We set this to false to abort the operation. */
{
&replyFind);
if (!replyFind)
halSuccess = false;
}
{
halSuccess = false;
}
{
/* Now get all UDIs from the iterator */
const char *pszUdi;
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
*pfSuccess = halSuccess;
return rc;
}
/**
* Read a set of string properties for a device. If some of the properties are
* not of type DBUS_TYPE_STRING or do not exist then a NULL pointer will be
* returned for them.
* @returns iprt status code. If the operation failed for non-fatal reasons
* then we return success and leave pMessage untouched - reset it
* before the call to detect this.
* @param pConnection an initialised connection DBus
* @param pszUdi the Udi of the device
* @param cProps the number of property values to look up
* @param papszKeys the keys of the properties to be looked up
* @param papszValues where to store the values of the properties. The
* strings returned will be valid until the message
* returned in @a ppMessage is freed. Undefined if
* the message is NULL.
* @param pMessage where to store the return DBus message. The caller
* is responsible for freeing this once they have
* finished with the value strings. NOT optional.
*/
/* static */
char **papszValues,
{
LogFlowFunc (("pConnection=%p, pszUdi=%s, cProps=%llu, papszKeys=%p, papszValues=%p, pMessage=%p\n",
bool halSuccess = true; /* We set this to false to abort the operation. */
/* Initialise the return array to NULLs */
papszValues[i] = NULL;
/* Send a GetAllProperties message to hald */
"org.freedesktop.Hal.Device",
"GetAllProperties");
if (!message)
rc = VERR_NO_MEMORY;
{
if (!reply)
halSuccess = false;
}
/* Parse the reply */
{
halSuccess = false;
}
/* Go through all entries in the reply and see if any match our keys. */
{
const char *pszKey;
/* Fill in any matches. */
{
}
}
rc = VERR_NO_MEMORY;
return rc;
}
/**
* Read a set of string properties for a device. If some properties do not
* exist or are not of type DBUS_TYPE_STRING, we will still fetch the others.
* @returns iprt status code. If the operation failed for non-fatal reasons
* then we return success and set *pfSuccess to false.
* @param pConnection an initialised connection DBus
* @param pszUdi the Udi of the device
* @param cProps the number of property values to look up
* @param papszKeys the keys of the properties to be looked up
* @param pMatches pointer to an empty array of iprt::MiniString to append the
* results to. NOT optional.
* @param pfMatches pointer to an array of boolean values indicating
* whether the respective property is a string. If this
* is not supplied then all properties must be strings
* for the operation to be considered successful
* @param pfSuccess will be set to true if the operation succeeds
*/
/* static */
const char **papszKeys,
{
LogFlowFunc (("pConnection=%p, pszUdi=%s, cProps=%llu, papszKeys=%p, pMatches=%p, pfMatches=%p, pfSuccess=%p\n",
bool halSuccess = true;
if (!message)
halSuccess = false;
{
else
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
*pfSuccess = halSuccess;
{
|| (pfMatches[j] == true)
}
return rc;
}
/**
* Helper function to query the hal subsystem for information about drives
* attached to the system.
* @returns iprt status code
* @param pList where to add information about the drives detected
* @param isDVD are we looking for DVDs or floppies?
* @param pfSuccess will be set to true if all interactions with hal
* succeeded and to false otherwise. Optional.
*
* @returns IPRT status code
*/
/* static */
{
bool halSuccess = true; /* We set this to false to abort the operation. */
try
{
if (!dbusConnection)
halSuccess = false;
{
if (!replyFind)
halSuccess = false;
}
{
halSuccess = false;
}
{
/* Now get all properties from the iterator */
const char *pszUdi;
static const char *papszKeys[] =
{ "block.device", "info.product", "info.vendor" };
halSuccess = false;
{
{
}
}
}
rc = VERR_NO_MEMORY;
*pfSuccess = halSuccess;
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
/**
* Helper function to query the hal subsystem for information about USB devices
* attached to the system.
* @returns iprt status code
* @param pList where to add information about the devices detected
* @param pfSuccess will be set to true if all interactions with hal
* succeeded and to false otherwise. Optional.
*
* @returns IPRT status code
*/
/* static */
{
bool halSuccess = true; /* We set this to false to abort the operation. */
/* Connect to hal */
if (!dbusConnection)
halSuccess = false;
/* Get an array of all devices in the usb_device subsystem */
{
"usb_device", &replyFind);
if (!replyFind)
halSuccess = false;
}
{
halSuccess = false;
}
/* Recurse down into the array and query interesting information about the
* entries. */
{
/* Get the device node and the sysfs path for the current entry. */
const char *pszUdi;
/* Get the interfaces. */
{
bool ifaceSuccess = true; /* If we can't get the interfaces, just
* skip this one device. */
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
}
rc = VERR_NO_MEMORY;
*pfSuccess = halSuccess;
return rc;
}
/**
* Helper function to query the hal subsystem for information about USB devices
* attached to the system, using the older API.
* @returns iprt status code
* @param pList where to add information about the devices detected
* @param pfSuccess will be set to true if all interactions with hal
* succeeded and to false otherwise. Optional.
*
* @returns IPRT status code
*/
/* static */
{
bool halSuccess = true; /* We set this to false to abort the operation. */
/* Connect to hal */
if (!dbusConnection)
halSuccess = false;
/* Get an array of all devices in the usb_device subsystem */
{
"usbraw", &replyFind);
if (!replyFind)
halSuccess = false;
}
{
halSuccess = false;
}
/* Recurse down into the array and query interesting information about the
* entries. */
{
/* Get the device node and the sysfs path for the current entry. */
const char *pszUdi;
/* Get the interfaces. */
{
bool ifaceSuccess = false; /* If we can't get the interfaces, just
* skip this one device. */
&ifaceSuccess);
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
}
rc = VERR_NO_MEMORY;
*pfSuccess = halSuccess;
return rc;
}
/**
* Helper function to query the hal subsystem for information about USB devices
* attached to the system.
* @returns iprt status code
* @param pList where to add information about the devices detected. If
* certain interfaces are not found (@a pfFound is false on
* return) this may contain invalid information.
* @param pcszUdi the hal UDI of the device
* @param pfSuccess will be set to true if the operation succeeds and to
* false if it fails for non-critical reasons. Optional.
*
* @returns IPRT status code
*/
/* static */
{
pfSuccess));
bool halSuccess = true; /* We set this to false to abort the operation. */
if (!dbusConnection)
halSuccess = false;
{
/* Look for children of the current UDI. */
if (!replyFind)
halSuccess = false;
}
{
halSuccess = false;
}
{
/* Now get the sysfs path and the subsystem from the iterator */
const char *pszUdi;
"linux.subsystem" };
if (!replyGet)
halSuccess = false;
halSuccess = false;
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
rc = VERR_NO_MEMORY;
*pfSuccess = halSuccess;
return rc;
}
/**
* When it is registered with DBus, this function will be called by
* dbus_connection_read_write_dispatch each time a message is received over the
* DBus connection. We check whether that message was caused by a hal device
* hotplug event, and if so we set a flag. dbus_connection_read_write_dispatch
* will return after calling its filter functions, and its caller should then
* check the status of the flag passed to the filter function.
*
* @param pConnection The DBus connection we are using.
* @param pMessage The DBus message which just arrived.
* @param pvUser A pointer to the flag variable we are to set.
*/
/* static */
{
volatile bool *pTriggered = reinterpret_cast<volatile bool *> (pvUser);
"DeviceAdded")
"DeviceRemoved"))
{
*pTriggered = true;
}
}
#endif /* RT_OS_LINUX && VBOX_WITH_DBUS */