1/*
2 * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7#define _GNU_SOURCE /* for RTLD_NEXT in dlfcn.h */
8
9#include <glib.h>
10#include <glib-object.h>
11#include <gudev/gudev.h>
12
13#include <dlfcn.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17
18/*
19 * The purpose of this library is to override libgudev to return
20 * arbitrary results for selected devices, generally for the purposes
21 * of testing. Adding the library file to LD_PRELOAD is the general
22 * way to accomplish this. The arbitrary results to return are
23 * specified using environment variable GUDEV_PRELOAD. GUDEV_PRELOAD is a ':'
24 * separated list of absolute paths to file that contain device descriptions for
25 * fake devices.
26 *
27 * Device description files are standard GKeyFile's. Each device is a group. By
28 * convention, we use the device name as the group name. A device description
29 * looks so
30 *
31 * [device]
32 * name=device
33 * property_FOO=BAR
34 *
35 * property_<name> are the special GUdevDevice properties that can be obtain
36 * with a call to g_udev_get_property.
37 * The "parent" property on a device specifies a device path that will be looked
38 * up with g_udev_client_query_by_device_file() to find a parent device. This
39 * may be a real device that the real libgudev will return a device for, or it
40 * may be another fake device handled by this library.
41 * Unspecified properties/attributes will be returned as NULL.
42 * For examples, see test_files directory.
43 *
44 * Setting the environment variable FAKEGUDEV_BLOCK_REAL causes this
45 * library to prevent real devices from being iterated over with
46 * g_udev_query_by_subsystem().
47 */
48
49#ifdef FAKE_G_UDEV_DEBUG
50static const char *k_tmp_logging_file_full_path = "/tmp/fakegudev.dbg";
51static FILE *debug_file;
52
53#define fake_g_udev_debug_init() \
54  debug_file = fopen (k_tmp_logging_file_full_path, "w")
55
56#define fake_g_udev_debug(...) \
57  do { \
58    if (debug_file) { \
59      fprintf (debug_file, __VA_ARGS__); \
60      fprintf (debug_file, "\n"); \
61    } \
62  } while (0)
63
64#define fake_g_udev_debug_finish() \
65  do { \
66    if (debug_file) { \
67      fclose (debug_file); \
68      debug_file = NULL; \
69    } \
70  } while (0)
71
72#else  /* FAKE_G_UDEV_DEBUG */
73#define fake_g_udev_debug_init()
74#define fake_g_udev_debug(...)
75#define fake_g_udev_debug_finish()
76#endif  /* FAKE_G_UDEV_DEBUG */
77
78
79typedef struct _FakeGUdevDeviceClass FakeGUdevDeviceClass;
80typedef struct _FakeGUdevDevice FakeGUdevDevice;
81typedef struct _FakeGUdevDevicePrivate FakeGUdevDevicePrivate;
82
83#define FAKE_G_UDEV_TYPE_DEVICE (fake_g_udev_device_get_type ())
84#define FAKE_G_UDEV_DEVICE(obj) \
85    (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
86                                 FAKE_G_UDEV_TYPE_DEVICE, \
87                                 FakeGUdevDevice))
88#define FAKE_G_UDEV_IS_DEVICE(obj) \
89    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
90                                 FAKE_G_UDEV_TYPE_DEVICE))
91#define FAKE_G_UDEV_DEVICE_CLASS(klass) \
92    (G_TYPE_CHECK_CLASS_CAST ((klass), \
93                              FAKE_G_UDEV_TYPE_DEVICE, \
94                              FakeGUdevDeviceClass))
95#define FAKE_G_UDEV_IS_DEVICE_CLASS(klass) \
96    (G_TYPE_CHECK_CLASS_TYPE ((klass), \
97                              FAKE_G_UDEV_TYPE_DEVICE))
98#define FAKE_G_UDEV_DEVICE_GET_CLASS(obj) \
99    (G_TYPE_INSTANCE_GET_CLASS ((obj), \
100                                FAKE_G_UDEV_TYPE_DEVICE, \
101                                FakeGUdevDeviceClass))
102
103struct _FakeGUdevDevice
104{
105  GUdevDevice parent;
106  FakeGUdevDevicePrivate *priv;
107};
108
109struct _FakeGUdevDeviceClass
110{
111  GUdevDeviceClass parent_class;
112};
113
114GType fake_g_udev_device_get_type (void) G_GNUC_CONST;
115
116/* end header */
117
118struct _FakeGUdevDevicePrivate
119{
120  GHashTable *properties;
121  GUdevClient *client;
122  const gchar **propkeys;
123};
124
125G_DEFINE_TYPE (FakeGUdevDevice, fake_g_udev_device, G_UDEV_TYPE_DEVICE)
126
127
128/* Map from device paths (/dev/pts/1) to FakeGUdevDevice objects */
129static GHashTable *devices_by_path;
130
131/* Map from sysfs paths (/sys/devices/blah) to FakeGUdevDevice objects */
132static GHashTable *devices_by_syspath;
133
134/* Map which acts as a set of FakeGUdevDevice objects */
135static GHashTable *devices_by_ptr;
136
137/* Prevent subsystem query from listing devices */
138static gboolean block_real = FALSE;
139
140static const char *k_env_devices = "FAKEGUDEV_DEVICES";
141static const char *k_env_block_real = "FAKEGUDEV_BLOCK_REAL";
142static const char *k_prop_device_file = "device_file";
143static const char *k_prop_devtype = "devtype";
144static const char *k_prop_driver = "driver";
145static const char *k_prop_name = "name";
146static const char *k_prop_parent = "parent";
147static const char *k_prop_subsystem = "subsystem";
148static const char *k_prop_sysfs_path = "sysfs_path";
149static const char *k_property_prefix = "property_";
150static const char *k_sysfs_attr_prefix = "sysfs_attr_";
151
152static const char *k_func_q_device_file = "g_udev_client_query_by_device_file";
153static const char *k_func_q_sysfs_path = "g_udev_client_query_by_sysfs_path";
154static const char *k_func_q_by_subsystem = "g_udev_client_query_by_subsystem";
155static const char *k_func_q_by_subsystem_and_name =
156    "g_udev_client_query_by_subsystem_and_name";
157static const char *k_func_get_device_file = "g_udev_device_get_device_file";
158static const char *k_func_get_devtype = "g_udev_device_get_devtype";
159static const char *k_func_get_driver = "g_udev_device_get_driver";
160static const char *k_func_get_name = "g_udev_device_get_name";
161static const char *k_func_get_parent = "g_udev_device_get_parent";
162static const char *k_func_get_property = "g_udev_device_get_property";
163static const char *k_func_get_property_keys = "g_udev_device_get_property_keys";
164static const char *k_func_get_subsystem = "g_udev_device_get_subsystem";
165static const char *k_func_get_sysfs_path = "g_udev_device_get_sysfs_path";
166static const char *k_func_get_sysfs_attr = "g_udev_device_get_sysfs_attr";
167
168static void
169abort_on_error (GError *error) {
170  if (!error)
171    return;
172
173  fake_g_udev_debug ("Aborting on error: |%s|", error->message);
174  fake_g_udev_debug_finish ();
175  g_assert (0);
176}
177
178static void
179load_fake_devices_from_file (const gchar *device_descriptor_file)
180{
181  GKeyFile *key_file;
182  gchar **groups;
183  gsize num_groups, group_iter;
184  FakeGUdevDevice *fake_device;
185  GError *error = NULL;
186
187  key_file = g_key_file_new();
188  if (!g_key_file_load_from_file (key_file,
189                                  device_descriptor_file,
190                                  G_KEY_FILE_NONE,
191                                  &error))
192    abort_on_error (error);
193
194  groups = g_key_file_get_groups(key_file, &num_groups);
195
196  for (group_iter = 0; group_iter < num_groups; ++group_iter) {
197    gchar *group;
198    gchar **keys;
199    gsize num_keys, key_iter;
200    gchar *id;
201
202    group = groups[group_iter];
203    fake_g_udev_debug ("Loading fake device %s", group);
204
205    /* Ensure some basic properties exist. */
206    if (!g_key_file_has_key (key_file, group, k_prop_device_file, &error)) {
207      fake_g_udev_debug ("Warning: Device %s does not have a |%s|.",
208                         group, k_prop_device_file);
209      if (error) {
210        g_error_free (error);
211        error = NULL;
212      }
213    }
214    if (!g_key_file_has_key (key_file, group, k_prop_sysfs_path, &error)) {
215      fake_g_udev_debug ("Warning: Device %s does not have a |%s|.",
216                         group, k_prop_sysfs_path);
217      if (error) {
218        g_error_free (error);
219        error = NULL;
220      }
221    }
222
223    /* Ensure this device has not been seen before. */
224    id = g_key_file_get_string (key_file, group, k_prop_device_file, &error);
225    abort_on_error (error);
226    if (g_hash_table_lookup_extended (devices_by_path, id, NULL, NULL)) {
227      fake_g_udev_debug ("Multiple devices with |%s| = |%s|. Skipping latest.",
228                         k_prop_device_file, id);
229      g_free (id);
230      continue;
231    }
232    g_free (id);
233
234    id = g_key_file_get_string (key_file, group, k_prop_sysfs_path, &error);
235    abort_on_error (error);
236    if (g_hash_table_lookup_extended (devices_by_syspath, id, NULL, NULL)) {
237      fake_g_udev_debug ("Multiple devices with |%s| = |%s|. Skipping latest.",
238                         k_prop_sysfs_path, id);
239      g_free (id);
240      continue;
241    }
242    g_free (id);
243
244
245    /* Now add the fake device with all its properties. */
246    fake_device = FAKE_G_UDEV_DEVICE (g_object_new (FAKE_G_UDEV_TYPE_DEVICE,
247                                                    NULL));
248    g_hash_table_insert (devices_by_ptr, g_object_ref (fake_device), NULL);
249
250    keys = g_key_file_get_keys (key_file, group, &num_keys, &error);
251    abort_on_error (error);
252    for (key_iter = 0; key_iter < num_keys; ++key_iter) {
253      gchar *key, *value;
254
255      key = keys[key_iter];
256      value = g_key_file_get_string (key_file, group, key, &error);
257      abort_on_error (error);
258
259      g_hash_table_insert (fake_device->priv->properties, g_strdup (key),
260                           g_strdup (value));
261      if (g_strcmp0 (key, k_prop_device_file) == 0) {
262        g_hash_table_insert (devices_by_path,
263                             g_strdup (value),
264                             g_object_ref (fake_device));
265      }
266      if (g_strcmp0 (key, k_prop_sysfs_path) == 0) {
267        g_hash_table_insert (devices_by_syspath,
268                             g_strdup (value),
269                             g_object_ref (fake_device));
270      }
271
272      g_free (value);
273    }
274
275    g_strfreev (keys);
276  }
277
278  g_strfreev (groups);
279  g_key_file_free (key_file);
280}
281
282static void
283load_fake_devices (const gchar *device_descriptor_files)
284{
285  gchar **files, **file_iter;
286
287  if (!device_descriptor_files) {
288    fake_g_udev_debug ("No device descriptor file given!");
289    return;
290  }
291
292  files = g_strsplit(device_descriptor_files, ":", 0);
293  for (file_iter = files; *file_iter; ++file_iter) {
294    fake_g_udev_debug ("Reading devices from |%s|", *file_iter);
295    load_fake_devices_from_file (*file_iter);
296  }
297
298  g_strfreev (files);
299}
300
301/*
302 * Don't initialize the global data in this library using the library
303 * constructor. GLib may not be setup when this library is loaded.
304 */
305static void
306g_udev_preload_init (void)
307{
308
309  /* global tables */
310  devices_by_path = g_hash_table_new_full (g_str_hash, g_str_equal,
311                                           g_free, g_object_unref);
312  devices_by_syspath = g_hash_table_new_full (g_str_hash, g_str_equal,
313                                              g_free, g_object_unref);
314  devices_by_ptr = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
315
316  load_fake_devices (getenv (k_env_devices));
317
318  if (getenv (k_env_block_real))
319    block_real = TRUE;
320}
321
322/* If |device| is a FakeGUdevDevice registered earlier with the libarary, cast
323 * |device| into a FakeGUdevDevice, otherwise return NULL
324 */
325static FakeGUdevDevice *
326get_fake_g_udev_device (GUdevDevice *device)
327{
328  FakeGUdevDevice *fake_device;
329
330  if (devices_by_ptr == NULL)
331    g_udev_preload_init ();
332
333  if (!FAKE_G_UDEV_IS_DEVICE (device))
334    return NULL;
335  fake_device = FAKE_G_UDEV_DEVICE (device);
336
337  g_return_val_if_fail (
338      g_hash_table_lookup_extended (devices_by_ptr, fake_device, NULL, NULL),
339      NULL);
340  return fake_device;
341}
342
343void __attribute__ ((constructor))
344fake_g_udev_init (void)
345{
346  fake_g_udev_debug_init ();
347  fake_g_udev_debug ("Initialized FakeGUdev library.\n");
348}
349
350void __attribute__ ((destructor))
351fake_g_udev_fini (void)
352{
353  if (devices_by_path)
354    g_hash_table_unref (devices_by_path);
355  if (devices_by_syspath)
356    g_hash_table_unref (devices_by_syspath);
357  if (devices_by_ptr)
358    g_hash_table_unref (devices_by_ptr);
359  fake_g_udev_debug ("Quit FakeGUdev library.\n");
360  fake_g_udev_debug_finish ();
361}
362
363GList *
364g_udev_client_query_by_subsystem (GUdevClient *client, const gchar *subsystem)
365{
366  static GList* (*realfunc)();
367  GHashTableIter iter;
368  gpointer key, value;
369  GList *list, *reallist;
370
371  if (devices_by_path == NULL)
372    g_udev_preload_init ();
373
374  list = NULL;
375  g_hash_table_iter_init (&iter, devices_by_path);
376  while (g_hash_table_iter_next (&iter, &key, &value)) {
377    FakeGUdevDevice *fake_device = value;
378    const gchar *dev_subsystem =
379        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
380                                            k_prop_subsystem);
381    if (strcmp (subsystem, dev_subsystem) == 0)
382      list = g_list_append (list, G_UDEV_DEVICE (fake_device));
383  }
384
385  if (!block_real) {
386    if (realfunc == NULL)
387      realfunc = (GList *(*)()) dlsym (RTLD_NEXT, k_func_q_by_subsystem);
388    reallist = realfunc (client, subsystem);
389    list = g_list_concat (list, reallist);
390  }
391
392  return list;
393}
394
395/*
396 * This is our hook. We look for a particular device path
397 * and return a special pointer.
398 */
399GUdevDevice *
400g_udev_client_query_by_device_file (GUdevClient *client,
401                                    const gchar *device_file)
402{
403  static GUdevDevice* (*realfunc)();
404  FakeGUdevDevice *fake_device;
405
406  if (devices_by_path == NULL)
407    g_udev_preload_init ();
408
409  if (g_hash_table_lookup_extended (devices_by_path,
410                                    device_file,
411                                    NULL,
412                                    (gpointer *)&fake_device)) {
413    /* Stash the client pointer for later use in _get_parent() */
414    fake_device->priv->client = client;
415    return g_object_ref (G_UDEV_DEVICE (fake_device));
416  }
417
418  if (realfunc == NULL)
419    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_q_device_file);
420  return realfunc (client, device_file);
421}
422
423GUdevDevice *
424g_udev_client_query_by_sysfs_path (GUdevClient *client,
425                                   const gchar *sysfs_path)
426{
427  static GUdevDevice* (*realfunc)();
428  FakeGUdevDevice *fake_device;
429
430  if (devices_by_path == NULL)
431    g_udev_preload_init ();
432
433  if (g_hash_table_lookup_extended (devices_by_syspath, sysfs_path, NULL,
434                                    (gpointer *)&fake_device)) {
435    /* Stash the client pointer for later use in _get_parent() */
436    fake_device->priv->client = client;
437    return g_object_ref (G_UDEV_DEVICE (fake_device));
438  }
439
440  if (realfunc == NULL)
441    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_q_sysfs_path);
442  return realfunc (client, sysfs_path);
443}
444
445
446GUdevDevice *
447g_udev_client_query_by_subsystem_and_name (GUdevClient *client,
448                                           const gchar *subsystem,
449                                           const gchar *name)
450{
451  static GUdevDevice* (*realfunc)();
452  GHashTableIter iter;
453  gpointer key, value;
454
455  if (devices_by_path == NULL)
456    g_udev_preload_init ();
457
458  g_hash_table_iter_init (&iter, devices_by_path);
459  while (g_hash_table_iter_next (&iter, &key, &value)) {
460    FakeGUdevDevice *fake_device = value;
461    const gchar *dev_subsystem =
462        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
463                                            k_prop_subsystem);
464    const gchar *dev_name =
465        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
466                                            k_prop_name);
467    if (dev_subsystem && dev_name &&
468        (strcmp (subsystem, dev_subsystem) == 0) &&
469        (strcmp (name, dev_name) == 0)) {
470      fake_device->priv->client = client;
471      return g_object_ref (G_UDEV_DEVICE (fake_device));
472    }
473  }
474
475  if (realfunc == NULL)
476    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT,
477                                           k_func_q_by_subsystem_and_name);
478  return realfunc (client, subsystem, name);
479}
480
481
482/*
483 * Our device data is a glib hash table with string keys and values;
484 * the keys and values are owned by the hash table.
485 */
486
487/*
488 * For g_udev_device_*() functions, the general drill is to check if
489 * the device is "ours", and if not, delegate to the real library
490 * method.
491 */
492const gchar *
493g_udev_device_get_device_file (GUdevDevice *device)
494{
495  static const gchar* (*realfunc)();
496  FakeGUdevDevice * fake_device;
497
498  fake_device = get_fake_g_udev_device (device);
499  if (fake_device)
500    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
501                                               k_prop_device_file);
502
503  if (realfunc == NULL)
504    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_device_file);
505  return realfunc (device);
506}
507
508const gchar *
509g_udev_device_get_devtype (GUdevDevice *device)
510{
511  static const gchar* (*realfunc)();
512  FakeGUdevDevice * fake_device;
513
514  fake_device = get_fake_g_udev_device (device);
515  if (fake_device)
516    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
517                                               k_prop_devtype);
518
519  if (realfunc == NULL)
520    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_devtype);
521  return realfunc (device);
522}
523
524const gchar *
525g_udev_device_get_driver (GUdevDevice *device)
526{
527  static const gchar* (*realfunc)();
528  FakeGUdevDevice * fake_device;
529
530  fake_device = get_fake_g_udev_device (device);
531  if (fake_device)
532    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
533                                               k_prop_driver);
534
535  if (realfunc == NULL)
536    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_driver);
537  return realfunc (device);
538}
539
540const gchar *
541g_udev_device_get_name (GUdevDevice *device)
542{
543  static const gchar* (*realfunc)();
544  FakeGUdevDevice * fake_device;
545
546  fake_device = get_fake_g_udev_device (device);
547  if (fake_device)
548    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
549                                               k_prop_name);
550
551  if (realfunc == NULL)
552    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_name);
553  return realfunc (device);
554}
555
556GUdevDevice *
557g_udev_device_get_parent (GUdevDevice *device)
558{
559  static GUdevDevice* (*realfunc)();
560  FakeGUdevDevice * fake_device;
561
562  fake_device = get_fake_g_udev_device (device);
563  if (fake_device) {
564    const gchar *parent =
565        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
566                                            k_prop_parent);
567    if (parent == NULL)
568      return NULL;
569    return g_udev_client_query_by_device_file (fake_device->priv->client,
570                                               parent);
571  }
572
573  if (realfunc == NULL)
574    realfunc = (GUdevDevice *(*)()) dlsym (RTLD_NEXT, k_func_get_parent);
575  return realfunc (device);
576}
577
578const gchar *
579g_udev_device_get_property (GUdevDevice *device,
580                            const gchar *key)
581{
582  static const gchar* (*realfunc)();
583  FakeGUdevDevice * fake_device;
584
585  fake_device = get_fake_g_udev_device (device);
586  if (fake_device) {
587    gchar *propkey = g_strconcat (k_property_prefix, key, NULL);
588    const gchar *result =
589        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
590                                            propkey);
591    g_free (propkey);
592    return result;
593  }
594
595  if (realfunc == NULL)
596    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_property);
597  return realfunc (device, key);
598}
599
600/*
601 * All of the g_udev_device_get_property_as_SOMETYPE () functions call
602 * g_udev_device_get_property() and then operate on the result, so we
603 * don't  need to implement them ourselves, as the real udev will start by
604 * calling into our version of g_udev_device_get_property().
605  */
606#if 0
607gboolean
608g_udev_device_get_property_as_boolean (GUdevDevice *device,
609                                       const gchar *key);
610gint
611g_udev_device_get_property_as_int (GUdevDevice *device,
612                                   const gchar *key);
613guint64 g_udev_device_get_property_as_uint64 (FakeGUdevDevice *device,
614                                              const gchar  *key);
615gdouble g_udev_device_get_property_as_double (FakeGUdevDevice *device,
616                                              const gchar  *key);
617
618const gchar* const *g_udev_device_get_property_as_strv (FakeGUdevDevice *device,
619                                                        const gchar  *key);
620#endif
621
622const gchar * const *
623g_udev_device_get_property_keys (GUdevDevice *device)
624{
625  static const gchar* const* (*realfunc)();
626  FakeGUdevDevice * fake_device;
627
628  fake_device = get_fake_g_udev_device (device);
629  if (fake_device) {
630    const gchar **keys;
631    if (fake_device->priv->propkeys)
632      return fake_device->priv->propkeys;
633
634    GList *keylist = g_hash_table_get_keys (fake_device->priv->properties);
635    GList *key, *prop, *proplist = NULL;
636    guint propcount = 0;
637    for (key = keylist; key != NULL; key = key->next) {
638      if (strncmp ((char *)key->data,
639                   k_property_prefix,
640                   strlen (k_property_prefix)) == 0) {
641        proplist = g_list_prepend (proplist,
642                                   key->data + strlen (k_property_prefix));
643        propcount++;
644      }
645    }
646    keys = g_malloc ((propcount + 1) * sizeof(*keys));
647    keys[propcount] = NULL;
648    for (prop = proplist; prop != NULL; prop = prop->next)
649      keys[--propcount] = prop->data;
650    g_list_free (proplist);
651    fake_device->priv->propkeys = keys;
652
653    return keys;
654  }
655
656  if (realfunc == NULL)
657    realfunc = (const gchar * const*(*)()) dlsym (RTLD_NEXT,
658                                                  k_func_get_property_keys);
659  return realfunc (device);
660}
661
662
663const gchar *
664g_udev_device_get_subsystem (GUdevDevice *device)
665{
666  static const gchar* (*realfunc)();
667  FakeGUdevDevice * fake_device;
668
669  fake_device = get_fake_g_udev_device (device);
670  if (fake_device)
671    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
672                                               k_prop_subsystem);
673
674  if (realfunc == NULL)
675    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_subsystem);
676  return realfunc (device);
677}
678
679/*
680 * The get_sysfs_attr_as_SOMETYPE() functions are also handled magically, as are
681 * the get_property_as_SOMETYPE() functions described above.
682 */
683const gchar *
684g_udev_device_get_sysfs_attr (GUdevDevice *device, const gchar *name)
685{
686  static const gchar* (*realfunc)();
687  FakeGUdevDevice * fake_device;
688
689  fake_device = get_fake_g_udev_device (device);
690  if (fake_device) {
691    gchar *attrkey = g_strconcat (k_sysfs_attr_prefix, name, NULL);
692    const gchar *result =
693        (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
694                                            attrkey);
695    g_free (attrkey);
696    return result;
697  }
698
699  if (realfunc == NULL)
700    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_sysfs_attr);
701  return realfunc (device, name);
702}
703
704
705const gchar *
706g_udev_device_get_sysfs_path (GUdevDevice *device)
707{
708  static const gchar* (*realfunc)();
709  FakeGUdevDevice * fake_device;
710
711  fake_device = get_fake_g_udev_device (device);
712  if (fake_device)
713    return (const gchar *)g_hash_table_lookup (fake_device->priv->properties,
714                                               k_prop_sysfs_path);
715
716  if (realfunc == NULL)
717    realfunc = (const gchar *(*)()) dlsym (RTLD_NEXT, k_func_get_sysfs_path);
718  return realfunc (device);
719}
720
721#if 0
722/* Not implemented yet */
723const gchar *g_udev_device_get_number (FakeGUdevDevice *device);
724const gchar *g_udev_device_get_action (FakeGUdevDevice *device);
725guint64 g_udev_device_get_seqnum (FakeGUdevDevice *device);
726FakeGUdevDeviceType g_udev_device_get_device_type (FakeGUdevDevice *device);
727FakeGUdevDeviceNumber g_udev_device_get_device_number (FakeGUdevDevice *device);
728const gchar * const *
729g_udev_device_get_device_file_symlinks (FakeGUdevDevice *device);
730FakeGUdevDevice *
731g_udev_device_get_parent_with_subsystem (FakeGUdevDevice *device,
732                                         const gchar *subsystem,
733                                         const gchar *devtype);
734const gchar * const *g_udev_device_get_tags (FakeGUdevDevice *device);
735gboolean g_udev_device_get_is_initialized (FakeGUdevDevice *device);
736guint64 g_udev_device_get_usec_since_initialized (FakeGUdevDevice *device);
737gboolean g_udev_device_has_property (FakeGUdevDevice *device, const gchar *key);
738#endif
739
740static void
741fake_g_udev_device_init (FakeGUdevDevice *device)
742{
743  device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device,
744                                              FAKE_G_UDEV_TYPE_DEVICE,
745                                              FakeGUdevDevicePrivate);
746
747  device->priv->properties = g_hash_table_new_full (g_str_hash,
748                                                    g_str_equal,
749                                                    g_free,
750                                                    g_free);
751  device->priv->propkeys = NULL;
752  device->priv->client = NULL;
753}
754
755static void
756fake_g_udev_device_finalize (GObject *object)
757{
758  FakeGUdevDevice *device = FAKE_G_UDEV_DEVICE (object);
759
760  if (device->priv->client)
761    g_object_unref (device->priv->client);
762  g_free (device->priv->propkeys);
763  g_hash_table_unref (device->priv->properties);
764}
765
766static void
767fake_g_udev_device_class_init (FakeGUdevDeviceClass *klass)
768{
769  GObjectClass *gobject_class = (GObjectClass *) klass;
770
771  gobject_class->finalize = fake_g_udev_device_finalize;
772
773  g_type_class_add_private (klass, sizeof (FakeGUdevDevicePrivate));
774}
775