/*
 * Copyright (c) 2012 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-object.h>
#include <gudev/gudev.h>

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * The 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 using environment variable GUDEV_PRELOAD. GUDEV_PRELOAD is a ':'
 * separated list of absolute paths to file that contain device descriptions for
 * fake devices.
 *
 * Device description files are standard GKeyFile's. Each device is a group. By
 * convention, we use the device name as the group name. A device description
 * looks so
 *
 * [device]
 * name=device
 * property_FOO=BAR
 *
 * property_<name> are the special GUdevDevice properties that can be obtain
 * with a call to g_udev_get_property.
 * 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.
 * For examples, see test_files directory.
 *
 * Setting the environment variable FAKEGUDEV_BLOCK_REAL causes this
 * library to prevent real devices from being iterated over with
 * g_udev_query_by_subsystem().
 */

#ifdef FAKE_G_UDEV_DEBUG
static const char *k_tmp_logging_file_full_path = "/tmp/fakegudev.dbg";
static FILE *debug_file;

#define fake_g_udev_debug_init() \
  debug_file = fopen (k_tmp_logging_file_full_path, "w")

#define fake_g_udev_debug(...) \
  do { \
    if (debug_file) { \
      fprintf (debug_file, __VA_ARGS__); \
      fprintf (debug_file, "\n"); \
    } \
  } while (0)

#define fake_g_udev_debug_finish() \
  do { \
    if (debug_file) { \
      fclose (debug_file); \
      debug_file = NULL; \
    } \
  } while (0)

#else  /* FAKE_G_UDEV_DEBUG */
#define fake_g_udev_debug_init()
#define fake_g_udev_debug(...)
#define fake_g_udev_debug_finish()
#endif  /* FAKE_G_UDEV_DEBUG */


typedef struct _FakeGUdevDeviceClass FakeGUdevDeviceClass;
typedef struct _FakeGUdevDevice FakeGUdevDevice;
typedef struct _FakeGUdevDevicePrivate FakeGUdevDevicePrivate;

#define FAKE_G_UDEV_TYPE_DEVICE (fake_g_udev_device_get_type ())
#define FAKE_G_UDEV_DEVICE(obj) \
    (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
                                 FAKE_G_UDEV_TYPE_DEVICE, \
                                 FakeGUdevDevice))
#define FAKE_G_UDEV_IS_DEVICE(obj) \
    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                                 FAKE_G_UDEV_TYPE_DEVICE))
#define FAKE_G_UDEV_DEVICE_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_CAST ((klass), \
                              FAKE_G_UDEV_TYPE_DEVICE, \
                              FakeGUdevDeviceClass))
#define FAKE_G_UDEV_IS_DEVICE_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_TYPE ((klass), \
                              FAKE_G_UDEV_TYPE_DEVICE))
#define FAKE_G_UDEV_DEVICE_GET_CLASS(obj) \
    (G_TYPE_INSTANCE_GET_CLASS ((obj), \
                                FAKE_G_UDEV_TYPE_DEVICE, \
                                FakeGUdevDeviceClass))

struct _FakeGUdevDevice
{
  GUdevDevice parent;
  FakeGUdevDevicePrivate *priv;
};

struct _FakeGUdevDeviceClass
{
  GUdevDeviceClass parent_class;
};

GType fake_g_udev_device_get_type (void) G_GNUC_CONST;

/* end header */

struct _FakeGUdevDevicePrivate
{
  GHashTable *properties;
  GUdevClient *client;
  const gchar **propkeys;
};

G_DEFINE_TYPE (FakeGUdevDevice, fake_g_udev_device, G_UDEV_TYPE_DEVICE)


/* 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_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";

static void
abort_on_error (GError *error) {
  if (!error)
    return;

  fake_g_udev_debug ("Aborting on error: |%s|", error->message);
  fake_g_udev_debug_finish ();
  g_assert (0);
}

static void
load_fake_devices_from_file (const gchar *device_descriptor_file)
{
  GKeyFile *key_file;
  gchar **groups;
  gsize num_groups, group_iter;
  FakeGUdevDevice *fake_device;
  GError *error = NULL;

  key_file = g_key_file_new();
  if (!g_key_file_load_from_file (key_file,
                                  device_descriptor_file,
                                  G_KEY_FILE_NONE,
                                  &error))
    abort_on_error (error);

  groups = g_key_file_get_groups(key_file, &num_groups);

  for (group_iter = 0; group_iter < num_groups; ++group_iter) {
    gchar *group;
    gchar **keys;
    gsize num_keys, key_iter;
    gchar *id;

    group = groups[group_iter];
    fake_g_udev_debug ("Loading fake device %s", group);

    /* Ensure some basic properties exist. */
    if (!g_key_file_has_key (key_file, group, k_prop_device_file, &error)) {
      fake_g_udev_debug ("Warning: Device %s does not have a |%s|.",
                         group, k_prop_device_file);
      if (error) {
        g_error_free (error);
        error = NULL;
      }
    }
    if (!g_key_file_has_key (key_file, group, k_prop_sysfs_path, &error)) {
      fake_g_udev_debug ("Warning: Device %s does not have a |%s|.",
                         group, k_prop_sysfs_path);
      if (error) {
        g_error_free (error);
        error = NULL;
      }
    }

    /* Ensure this device has not been seen before. */
    id = g_key_file_get_string (key_file, group, k_prop_device_file, &error);
    abort_on_error (error);
    if (g_hash_table_lookup_extended (devices_by_path, id, NULL, NULL)) {
      fake_g_udev_debug ("Multiple devices with |%s| = |%s|. Skipping latest.",
                         k_prop_device_file, id);
      g_free (id);
      continue;
    }
    g_free (id);

    id = g_key_file_get_string (key_file, group, k_prop_sysfs_path, &error);
    abort_on_error (error);
    if (g_hash_table_lookup_extended (devices_by_syspath, id, NULL, NULL)) {
      fake_g_udev_debug ("Multiple devices with |%s| = |%s|. Skipping latest.",
                         k_prop_sysfs_path, id);
      g_free (id);
      continue;
    }
    g_free (id);


    /* Now add the fake device with all its properties. */
    fake_device = FAKE_G_UDEV_DEVICE (g_object_new (FAKE_G_UDEV_TYPE_DEVICE,
                                                    NULL));
    g_hash_table_insert (devices_by_ptr, g_object_ref (fake_device), NULL);

    keys = g_key_file_get_keys (key_file, group, &num_keys, &error);
    abort_on_error (error);
    for (key_iter = 0; key_iter < num_keys; ++key_iter) {
      gchar *key, *value;

      key = keys[key_iter];
      value = g_key_file_get_string (key_file, group, key, &error);
      abort_on_error (error);

      g_hash_table_insert (fake_device->priv->properties, g_strdup (key),
                           g_strdup (value));
      if (g_strcmp0 (key, k_prop_device_file) == 0) {
        g_hash_table_insert (devices_by_path,
                             g_strdup (value),
                             g_object_ref (fake_device));
      }
      if (g_strcmp0 (key, k_prop_sysfs_path) == 0) {
        g_hash_table_insert (devices_by_syspath,
                             g_strdup (value),
                             g_object_ref (fake_device));
      }

      g_free (value);
    }

    g_strfreev (keys);
  }

  g_strfreev (groups);
  g_key_file_free (key_file);
}

static void
load_fake_devices (const gchar *device_descriptor_files)
{
  gchar **files, **file_iter;

  if (!device_descriptor_files) {
    fake_g_udev_debug ("No device descriptor file given!");
    return;
  }

  files = g_strsplit(device_descriptor_files, ":", 0);
  for (file_iter = files; *file_iter; ++file_iter) {
    fake_g_udev_debug ("Reading devices from |%s|", *file_iter);
    load_fake_devices_from_file (*file_iter);
  }

  g_strfreev (files);
}

/*
 * Don't initialize the global data in this library using the library
 * constructor. GLib may not be setup when this library is loaded.
 */
static void
g_udev_preload_init (void)
{

  /* global tables */
  devices_by_path = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           g_free, g_object_unref);
  devices_by_syspath = g_hash_table_new_full (g_str_hash, g_str_equal,
                                              g_free, g_object_unref);
  devices_by_ptr = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);

  load_fake_devices (getenv (k_env_devices));

  if (getenv (k_env_block_real))
    block_real = TRUE;
}

/* If |device| is a FakeGUdevDevice registered earlier with the libarary, cast
 * |device| into a FakeGUdevDevice, otherwise return NULL
 */
static FakeGUdevDevice *
get_fake_g_udev_device (GUdevDevice *device)
{
  FakeGUdevDevice *fake_device;

  if (devices_by_ptr == NULL)
    g_udev_preload_init ();

  if (!FAKE_G_UDEV_IS_DEVICE (device))
    return NULL;
  fake_device = FAKE_G_UDEV_DEVICE (device);

  g_return_val_if_fail (
      g_hash_table_lookup_extended (devices_by_ptr, fake_device, NULL, NULL),
      NULL);
  return fake_device;
}

void __attribute__ ((constructor))
fake_g_udev_init (void)
{
  fake_g_udev_debug_init ();
  fake_g_udev_debug ("Initialized FakeGUdev library.\n");
}

void __attribute__ ((destructor))
fake_g_udev_fini (void)
{
  if (devices_by_path)
    g_hash_table_unref (devices_by_path);
  if (devices_by_syspath)
    g_hash_table_unref (devices_by_syspath);
  if (devices_by_ptr)
    g_hash_table_unref (devices_by_ptr);
  fake_g_udev_debug ("Quit FakeGUdev library.\n");
  fake_g_udev_debug_finish ();
}

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 *fake_device = value;
    const gchar *dev_subsystem =
        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
                                            k_prop_subsystem);
    if (strcmp (subsystem, dev_subsystem) == 0)
      list = g_list_append (list, G_UDEV_DEVICE (fake_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);
  }

  return list;
}

/*
 * This is our hook. We look for a particular device path
 * and return a special pointer.
 */
GUdevDevice *
g_udev_client_query_by_device_file (GUdevClient *client,
                                    const gchar *device_file)
{
  static GUdevDevice* (*realfunc)();
  FakeGUdevDevice *fake_device;

  if (devices_by_path == NULL)
    g_udev_preload_init ();

  if (g_hash_table_lookup_extended (devices_by_path,
                                    device_file,
                                    NULL,
                                    (gpointer *)&fake_device)) {
    /* Stash the client pointer for later use in _get_parent() */
    fake_device->priv->client = client;
    return g_object_ref (G_UDEV_DEVICE (fake_device));
  }

  if (realfunc == NULL)
    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_q_device_file);
  return realfunc (client, device_file);
}

GUdevDevice *
g_udev_client_query_by_sysfs_path (GUdevClient *client,
                                   const gchar *sysfs_path)
{
  static GUdevDevice* (*realfunc)();
  FakeGUdevDevice *fake_device;

  if (devices_by_path == NULL)
    g_udev_preload_init ();

  if (g_hash_table_lookup_extended (devices_by_syspath, sysfs_path, NULL,
                                    (gpointer *)&fake_device)) {
    /* Stash the client pointer for later use in _get_parent() */
    fake_device->priv->client = client;
    return g_object_ref (G_UDEV_DEVICE (fake_device));
  }

  if (realfunc == NULL)
    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_q_sysfs_path);
  return realfunc (client, sysfs_path);
}


GUdevDevice *
g_udev_client_query_by_subsystem_and_name (GUdevClient *client,
                                           const gchar *subsystem,
                                           const gchar *name)
{
  static GUdevDevice* (*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 *fake_device = value;
    const gchar *dev_subsystem =
        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
                                            k_prop_subsystem);
    const gchar *dev_name =
        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
                                            k_prop_name);
    if (dev_subsystem && dev_name &&
        (strcmp (subsystem, dev_subsystem) == 0) &&
        (strcmp (name, dev_name) == 0)) {
      fake_device->priv->client = client;
      return g_object_ref (G_UDEV_DEVICE (fake_device));
    }
  }

  if (realfunc == NULL)
    realfunc = (GUdevDevice *(*)()) 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 (GUdevDevice *device)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device)
    return (const gchar *)g_hash_table_lookup (fake_device->priv->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 (GUdevDevice *device)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device)
    return (const gchar *)g_hash_table_lookup (fake_device->priv->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 (GUdevDevice *device)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device)
    return (const gchar *)g_hash_table_lookup (fake_device->priv->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 (GUdevDevice *device)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device)
    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
                                               k_prop_name);

  if (realfunc == NULL)
    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_name);
  return realfunc (device);
}

GUdevDevice *
g_udev_device_get_parent (GUdevDevice *device)
{
  static GUdevDevice* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device) {
    const gchar *parent =
        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
                                            k_prop_parent);
    if (parent == NULL)
      return NULL;
    return g_udev_client_query_by_device_file (fake_device->priv->client,
                                               parent);
  }

  if (realfunc == NULL)
    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_get_parent);
  return realfunc (device);
}

const gchar *
g_udev_device_get_property (GUdevDevice *device,
                            const gchar *key)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device) {
    gchar *propkey = g_strconcat (k_property_prefix, key, NULL);
    const gchar *result =
        (const gchar *)g_hash_table_lookup (fake_device->priv->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
 * don't  need to implement them ourselves, as the real udev will start by
 * calling into our version of g_udev_device_get_property().
  */
#if 0
gboolean
g_udev_device_get_property_as_boolean (GUdevDevice *device,
                                       const gchar *key);
gint
g_udev_device_get_property_as_int (GUdevDevice *device,
                                   const gchar *key);
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 (GUdevDevice *device)
{
  static const gchar* const* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device) {
    const gchar **keys;
    if (fake_device->priv->propkeys)
      return fake_device->priv->propkeys;

    GList *keylist = g_hash_table_get_keys (fake_device->priv->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] = prop->data;
    g_list_free (proplist);
    fake_device->priv->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 (GUdevDevice *device)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device)
    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
                                               k_prop_subsystem);

  if (realfunc == NULL)
    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_subsystem);
  return realfunc (device);
}

/*
 * The get_sysfs_attr_as_SOMETYPE() functions are also handled magically, as are
 * the get_property_as_SOMETYPE() functions described above.
 */
const gchar *
g_udev_device_get_sysfs_attr (GUdevDevice *device, const gchar *name)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device) {
    gchar *attrkey = g_strconcat (k_sysfs_attr_prefix, name, NULL);
    const gchar *result =
        (const gchar *)g_hash_table_lookup (fake_device->priv->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);
}


const gchar *
g_udev_device_get_sysfs_path (GUdevDevice *device)
{
  static const gchar* (*realfunc)();
  FakeGUdevDevice * fake_device;

  fake_device = get_fake_g_udev_device (device);
  if (fake_device)
    return (const gchar *)g_hash_table_lookup (fake_device->priv->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->priv = G_TYPE_INSTANCE_GET_PRIVATE (device,
                                              FAKE_G_UDEV_TYPE_DEVICE,
                                              FakeGUdevDevicePrivate);

  device->priv->properties = g_hash_table_new_full (g_str_hash,
                                                    g_str_equal,
                                                    g_free,
                                                    g_free);
  device->priv->propkeys = NULL;
  device->priv->client = NULL;
}

static void
fake_g_udev_device_finalize (GObject *object)
{
  FakeGUdevDevice *device = FAKE_G_UDEV_DEVICE (object);

  if (device->priv->client)
    g_object_unref (device->priv->client);
  g_free (device->priv->propkeys);
  g_hash_table_unref (device->priv->properties);
}

static void
fake_g_udev_device_class_init (FakeGUdevDeviceClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;

  gobject_class->finalize = fake_g_udev_device_finalize;

  g_type_class_add_private (klass, sizeof (FakeGUdevDevicePrivate));
}