blob: d588c0353ab60dd3e0b9e8b0e6215028ba50f023 [file] [log] [blame]
/*
* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#define _GNU_SOURCE /* for RTLD_NEXT in dlfcn.h */
#include <glib.h>
#include <glib/gtypes.h>
#include <glib-object.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
/*
* This purpose of this library is to override libgudev to return
* arbitrary results for selected devices, generally for the purposes
* of testing. Adding the library file to LD_PRELOAD is the general
* way to accomplish this. The arbitrary results to return are
* specified in the environment variable GUDEV_PRELOAD as follows:
*
* FAKEGUDEV_DEVICES=device_file=/dev/pts/1:subsystem=tty:name=pts/1:\
* parent=/dev/pts:property_DEVICE_PROPERTY=123
*
* Multiple devices may be specified; each device_file entry starts a
* new device. The "parent" property on a device specifies a device
* path that will be looked up with
* g_udev_client_query_by_device_file() to find a parent device. This
* may be a real device that the real libgudev will return a device
* for, or it may be another fake device handled by this library.
*
* Unspecified properties/attributes will be returned as NULL.
*
* No mechanism currently exists to escape : and = characters in property names.
*
* Setting the environment variable FAKEGUDEV_BLOCK_REAL causes this
* library to prevent real devices from being iterated over with
* g_udev_query_by_subsystem().
*/
struct GUdevClient;
typedef struct GUdevClient GUdevClient;
typedef struct _FakeGUdevDevice
{
GObject parent;
/*< private >*/
GHashTable *properties;
GUdevClient *client;
const gchar **propkeys;
} FakeGUdevDevice;
typedef struct _FakeGUdevDeviceClass
{
GObjectClass parent_class;
} FakeGUdevDeviceClass;
GType g_udev_device_get_type (void) G_GNUC_CONST;
#define FAKE_G_UDEV_TYPE_DEVICE (fake_g_udev_device_get_type ())
#define FAKE_G_UDEV_DEVICE(o) \
(G_TYPE_CHECK_INSTANCE_CAST ((o), FAKE_G_UDEV_TYPE_DEVICE, FakeGUdevDevice))
G_DEFINE_TYPE (FakeGUdevDevice, fake_g_udev_device, G_TYPE_OBJECT)
/* Map from device paths (/dev/pts/1) to FakeGUdevDevice objects */
static GHashTable *devices_by_path;
/* Map from sysfs paths (/sys/devices/blah) to FakeGUdevDevice objects */
static GHashTable *devices_by_syspath;
/* Map which acts as a set of FakeGUdevDevice objects */
static GHashTable *devices_by_ptr;
/* Prevent subsystem query from listing devices */
static gboolean block_real = FALSE;
static const char *k_env_devices = "FAKEGUDEV_DEVICES";
static const char *k_env_block_real = "FAKEGUDEV_BLOCK_REAL";
static const char *k_delims = ":=";
static const char *k_prop_device_file = "device_file";
static const char *k_prop_devtype = "devtype";
static const char *k_prop_driver = "driver";
static const char *k_prop_name = "name";
static const char *k_prop_parent = "parent";
static const char *k_prop_subsystem = "subsystem";
static const char *k_prop_sysfs_path = "sysfs_path";
static const char *k_property_prefix = "property_";
static const char *k_sysfs_attr_prefix = "sysfs_attr_";
static const char *k_func_q_device_file = "g_udev_client_query_by_device_file";
static const char *k_func_q_sysfs_path = "g_udev_client_query_by_sysfs_path";
static const char *k_func_q_by_subsystem = "g_udev_client_query_by_subsystem";
static const char *k_func_q_by_subsystem_and_name =
"g_udev_client_query_by_subsystem_and_name";
static const char *k_func_get_device_file = "g_udev_device_get_device_file";
static const char *k_func_get_devtype = "g_udev_device_get_devtype";
static const char *k_func_get_driver = "g_udev_device_get_driver";
static const char *k_func_get_name = "g_udev_device_get_name";
static const char *k_func_get_parent = "g_udev_device_get_parent";
static const char *k_func_get_property = "g_udev_device_get_property";
static const char *k_func_get_property_keys = "g_udev_device_get_property_keys";
static const char *k_func_get_subsystem = "g_udev_device_get_subsystem";
static const char *k_func_get_sysfs_path = "g_udev_device_get_sysfs_path";
static const char *k_func_get_sysfs_attr = "g_udev_device_get_sysfs_attr";
/* TODO(njw): set up a _fini() routine to free allocated memory */
static void
g_udev_preload_init ()
{
const char *orig_ev;
char *ev, *saveptr, *name, *value;
FakeGUdevDevice *device;
/* global tables */
devices_by_path = g_hash_table_new (g_str_hash, g_str_equal);
devices_by_syspath = g_hash_table_new (g_str_hash, g_str_equal);
devices_by_ptr = g_hash_table_new (NULL, NULL);
orig_ev = getenv (k_env_devices);
if (orig_ev == NULL)
orig_ev = "";
ev = g_strdup (orig_ev);
name = strtok_r (ev, k_delims, &saveptr);
value = strtok_r (NULL, k_delims, &saveptr);
device = NULL;
while (name != NULL && value != NULL) {
if (strcmp (name, k_prop_device_file) == 0) {
device = FAKE_G_UDEV_DEVICE (g_object_new (FAKE_G_UDEV_TYPE_DEVICE,
NULL));
g_hash_table_insert (devices_by_path, g_strdup (value), device);
g_hash_table_insert (devices_by_ptr, device, NULL);
}
if (device != NULL) {
g_hash_table_insert (device->properties, g_strdup (name),
g_strdup (value));
if (strcmp (name, k_prop_sysfs_path) == 0)
g_hash_table_insert (devices_by_syspath, g_strdup (value), device);
}
name = strtok_r (NULL, k_delims, &saveptr);
value = strtok_r (NULL, k_delims, &saveptr);
}
g_free (ev);
if (getenv (k_env_block_real))
block_real = TRUE;
}
GList *
g_udev_client_query_by_subsystem (GUdevClient *client, const gchar *subsystem)
{
static GList* (*realfunc)();
GHashTableIter iter;
gpointer key, value;
GList *list, *reallist;
if (devices_by_path == NULL)
g_udev_preload_init ();
list = NULL;
g_hash_table_iter_init (&iter, devices_by_path);
while (g_hash_table_iter_next (&iter, &key, &value)) {
FakeGUdevDevice *device = value;
gchar *dev_subsystem =
(gchar *)g_hash_table_lookup (device->properties, k_prop_subsystem);
if (strcmp (subsystem, dev_subsystem) == 0)
list = g_list_append (list, device);
}
if (!block_real) {
if (realfunc == NULL)
realfunc = (GList *(*)()) dlsym (RTLD_NEXT, k_func_q_by_subsystem);
reallist = realfunc (client, subsystem);
list = g_list_concat (list, reallist);
g_list_free (reallist);
}
return list;
}
/*
* This is our hook. We look for a particular device path
* and return a special pointer.
*/
FakeGUdevDevice*
g_udev_client_query_by_device_file (GUdevClient *client,
const gchar *device_file)
{
static FakeGUdevDevice* (*realfunc)();
FakeGUdevDevice *device;
if (devices_by_path == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_path, device_file, NULL,
(gpointer *)&device)) {
/* Stash the client pointer for later use in _get_parent() */
device->client = client;
return g_object_ref (device);
}
if (realfunc == NULL)
realfunc = (FakeGUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_q_device_file);
return realfunc (client, device_file);
}
FakeGUdevDevice*
g_udev_client_query_by_sysfs_path (GUdevClient *client,
const gchar *sysfs_path)
{
static FakeGUdevDevice* (*realfunc)();
FakeGUdevDevice *device;
if (devices_by_path == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_syspath, sysfs_path, NULL,
(gpointer *)&device)) {
/* Stash the client pointer for later use in _get_parent() */
device->client = client;
return g_object_ref (device);
}
if (realfunc == NULL)
realfunc = (FakeGUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_q_sysfs_path);
return realfunc (client, sysfs_path);
}
FakeGUdevDevice *
g_udev_client_query_by_subsystem_and_name (GUdevClient *client,
const gchar *subsystem,
const gchar *name)
{
static FakeGUdevDevice* (*realfunc)();
GHashTableIter iter;
gpointer key, value;
if (devices_by_path == NULL)
g_udev_preload_init ();
g_hash_table_iter_init (&iter, devices_by_path);
while (g_hash_table_iter_next (&iter, &key, &value)) {
FakeGUdevDevice *device = value;
gchar *dev_subsystem =
(gchar *)g_hash_table_lookup (device->properties, k_prop_subsystem);
gchar *dev_name =
(gchar *)g_hash_table_lookup (device->properties, k_prop_name);
if (dev_subsystem && dev_name &&
(strcmp (subsystem, dev_subsystem) == 0) &&
(strcmp (name, dev_name) == 0)) {
device->client = client;
return g_object_ref (device);
}
}
if (realfunc == NULL)
realfunc = (FakeGUdevDevice *(*)()) dlsym (RTLD_NEXT,
k_func_q_by_subsystem_and_name);
return realfunc (client, subsystem, name);
}
/*
* Our device data is a glib hash table with string keys and values;
* the keys and values are owned by the hash table.
*/
/*
* For g_udev_device_*() functions, the general drill is to check if
* the device is "ours", and if not, delegate to the real library
* method.
*/
const gchar *
g_udev_device_get_device_file (FakeGUdevDevice *device)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL))
return (gchar *)g_hash_table_lookup (device->properties,
k_prop_device_file);
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_device_file);
return realfunc (device);
}
const gchar *
g_udev_device_get_devtype (FakeGUdevDevice *device)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL))
return (gchar *)g_hash_table_lookup (device->properties, k_prop_devtype);
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_devtype);
return realfunc (device);
}
const gchar *
g_udev_device_get_driver (FakeGUdevDevice *device)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL))
return (gchar *)g_hash_table_lookup (device->properties, k_prop_driver);
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_driver);
return realfunc (device);
}
const gchar *
g_udev_device_get_name (FakeGUdevDevice *device)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL))
return (gchar *)g_hash_table_lookup (device->properties, k_prop_name);
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_name);
return realfunc (device);
}
const FakeGUdevDevice *
g_udev_device_get_parent (FakeGUdevDevice *device)
{
static const FakeGUdevDevice* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
gchar *parent = (gchar *)g_hash_table_lookup (device->properties,
k_prop_parent);
if (parent == NULL)
return NULL;
return g_udev_client_query_by_device_file (device->client, parent);
}
if (realfunc == NULL)
realfunc = (const FakeGUdevDevice *(*)()) dlsym (RTLD_NEXT,
k_func_get_parent);
return realfunc (device);
return NULL;
}
const gchar *
g_udev_device_get_property (FakeGUdevDevice *device,
const gchar *key)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
gchar *propkey = g_strconcat (k_property_prefix, key, NULL);
const gchar *result = (gchar *)g_hash_table_lookup (device->properties,
propkey);
g_free (propkey);
return result;
}
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_property);
return realfunc (device, key);
}
/*
* All of the g_udev_device_get_property_as_SOMETYPE () functions call
* g_udev_device_get_property() and then operate on the result, so we
* ideally shouldn't need to implement them ourselves, as the real
* udev will start by calling into our version of
* g_udev_device_get_property().
*
* However, that doesn't work. The _as_SOMETYPE () functions validate
* the device pointer first (via G_TYPE_CHECK_INSTANCE_TYPE), which
* segfaults on our non-GObject device pointers. We'd have to fake
* that out more thoroughly in order to pass.
*/
gboolean
g_udev_device_get_property_as_boolean (FakeGUdevDevice *device,
const gchar *key)
{
static gboolean (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
gchar *propkey = g_strconcat (k_property_prefix, key, NULL);
const gchar *result = (gchar *)g_hash_table_lookup (device->properties,
propkey);
g_free (propkey);
if (result &&
(strcmp (result, "1") == 0 || g_ascii_strcasecmp (result, "true") == 0))
return TRUE;
else
return FALSE;
}
if (realfunc == NULL)
realfunc = (gboolean (*)()) dlsym (RTLD_NEXT, k_func_get_property);
return realfunc (device, key);
}
gint
g_udev_device_get_property_as_int (FakeGUdevDevice *device,
const gchar *key)
{
static gint (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
gchar *propkey = g_strconcat (k_property_prefix, key, NULL);
const gchar *result = (gchar *)g_hash_table_lookup (device->properties,
propkey);
g_free (propkey);
return strtol (result, NULL, 0);
}
if (realfunc == NULL)
realfunc = (gint (*)()) dlsym (RTLD_NEXT, k_func_get_property);
return realfunc (device, key);
}
#if 0
/* Not implemented yet */
guint64 g_udev_device_get_property_as_uint64 (FakeGUdevDevice *device,
const gchar *key);
gdouble g_udev_device_get_property_as_double (FakeGUdevDevice *device,
const gchar *key);
const gchar* const *g_udev_device_get_property_as_strv (FakeGUdevDevice *device,
const gchar *key);
#endif
const gchar * const *
g_udev_device_get_property_keys (FakeGUdevDevice *device)
{
static const gchar* const* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
const gchar **keys;
if (device->propkeys)
return device->propkeys;
GList *keylist = g_hash_table_get_keys(device->properties);
GList *key, *prop, *proplist = NULL;
guint propcount = 0;
for (key = keylist; key != NULL ; key = key->next) {
if (strncmp ((char *)key->data, k_property_prefix,
strlen (k_property_prefix)) == 0) {
proplist = g_list_prepend (proplist, key->data +
strlen (k_property_prefix));
propcount++;
}
}
keys = g_malloc ((propcount + 1) * sizeof(*keys));
keys[propcount] = NULL;
for (prop = proplist; prop != NULL ; prop = prop->next)
keys[--propcount] = g_strdup (prop->data);
g_list_free (proplist);
device->propkeys = keys;
return keys;
}
if (realfunc == NULL)
realfunc = (const gchar * const*(*)()) dlsym (RTLD_NEXT,
k_func_get_property_keys);
return realfunc (device);
}
const gchar *
g_udev_device_get_subsystem (FakeGUdevDevice *device)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL))
return (gchar *)g_hash_table_lookup (device->properties, k_prop_subsystem);
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_subsystem);
return realfunc (device);
}
const gchar *
g_udev_device_get_sysfs_attr (FakeGUdevDevice *device, const gchar *name)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
gchar *attrkey = g_strconcat (k_sysfs_attr_prefix, name, NULL);
const gchar *result = (gchar *)g_hash_table_lookup (device->properties,
attrkey);
g_free (attrkey);
return result;
}
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_sysfs_attr);
return realfunc (device, name);
}
/*
* The get_sysfs_attr_as_SOMETYPE() functions have the same problem as
* the get_property_as_SOMETYPE() functions described above.
*/
const gchar *
g_udev_device_get_sysfs_path (FakeGUdevDevice *device)
{
static const gchar* (*realfunc)();
if (devices_by_ptr == NULL)
g_udev_preload_init ();
if (g_hash_table_lookup_extended (devices_by_ptr, device, NULL, NULL)) {
return (gchar *)g_hash_table_lookup (device->properties, k_prop_sysfs_path);
}
if (realfunc == NULL)
realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_sysfs_path);
return realfunc (device);
}
#if 0
/* Not implemented yet */
const gchar *g_udev_device_get_number (FakeGUdevDevice *device);
const gchar *g_udev_device_get_action (FakeGUdevDevice *device);
guint64 g_udev_device_get_seqnum (FakeGUdevDevice *device);
FakeGUdevDeviceType g_udev_device_get_device_type (FakeGUdevDevice *device);
FakeGUdevDeviceNumber g_udev_device_get_device_number (FakeGUdevDevice *device);
const gchar * const *
g_udev_device_get_device_file_symlinks (FakeGUdevDevice *device);
FakeGUdevDevice *
g_udev_device_get_parent_with_subsystem (FakeGUdevDevice *device,
const gchar *subsystem,
const gchar *devtype);
const gchar * const *g_udev_device_get_tags (FakeGUdevDevice *device);
gboolean g_udev_device_get_is_initialized (FakeGUdevDevice *device);
guint64 g_udev_device_get_usec_since_initialized (FakeGUdevDevice *device);
gboolean g_udev_device_has_property (FakeGUdevDevice *device, const gchar *key);
#endif
static void
fake_g_udev_device_init (FakeGUdevDevice *device)
{
device->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
device->propkeys = NULL;
device->client = NULL;
}
static void
fake_g_udev_device_finalize (GObject *object)
{
FakeGUdevDevice *device = FAKE_G_UDEV_DEVICE (object);
g_hash_table_unref (device->properties);
}
static void
fake_g_udev_device_class_init (FakeGUdevDeviceClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->finalize = fake_g_udev_device_finalize;
}