[libgudev/wip/hadess/add-uncached-helpers] gudev: Add helpers to get uncached sysfs attributes




commit b5363fe829c16b31a0c8250c2c8c9a17dd8e08bf
Author: Bastien Nocera <hadess hadess net>
Date:   Mon Sep 14 15:15:36 2020 +0200

    gudev: Add helpers to get uncached sysfs attributes
    
    We very often need to access the current value of sysfs attributes. Add
    functions that do I/O on the sysfs files and update the cache.

 docs/gudev-sections.txt |   7 ++
 gudev/gudevdevice.c     | 260 ++++++++++++++++++++++++++++++++++++++++++++++--
 gudev/gudevdevice.h     |  15 +++
 libgudev-1.0.sym        |   7 ++
 tests/Makefile.am       |   6 +-
 tests/test-sysfsattr.c  |  80 +++++++++++++++
 6 files changed, 367 insertions(+), 8 deletions(-)
---
diff --git a/docs/gudev-sections.txt b/docs/gudev-sections.txt
index 90765ee..86efd13 100644
--- a/docs/gudev-sections.txt
+++ b/docs/gudev-sections.txt
@@ -61,6 +61,13 @@ g_udev_device_get_sysfs_attr_as_uint64
 g_udev_device_get_sysfs_attr_as_double
 g_udev_device_get_sysfs_attr_as_boolean
 g_udev_device_get_sysfs_attr_as_strv
+g_udev_device_has_sysfs_attr_uncached
+g_udev_device_get_sysfs_attr_uncached
+g_udev_device_get_sysfs_attr_as_int_uncached
+g_udev_device_get_sysfs_attr_as_uint64_uncached
+g_udev_device_get_sysfs_attr_as_double_uncached
+g_udev_device_get_sysfs_attr_as_boolean_uncached
+g_udev_device_get_sysfs_attr_as_strv_uncached
 <SUBSECTION Standard>
 G_UDEV_DEVICE
 G_UDEV_IS_DEVICE
diff --git a/gudev/gudevdevice.c b/gudev/gudevdevice.c
index 631d126..df6ebd1 100644
--- a/gudev/gudevdevice.c
+++ b/gudev/gudevdevice.c
@@ -91,6 +91,7 @@ struct _GUdevDevicePrivate
   gchar **tags;
   GHashTable *prop_strvs;
   GHashTable *sysfs_attr_strvs;
+  GHashTable *sysfs_attr;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GUdevDevice, g_udev_device, G_TYPE_OBJECT, G_ADD_PRIVATE(GUdevDevice))
@@ -114,6 +115,9 @@ g_udev_device_finalize (GObject *object)
   if (device->priv->sysfs_attr_strvs != NULL)
     g_hash_table_unref (device->priv->sysfs_attr_strvs);
 
+  if (device->priv->sysfs_attr != NULL)
+    g_hash_table_unref (device->priv->sysfs_attr);
+
   if (G_OBJECT_CLASS (g_udev_device_parent_class)->finalize != NULL)
     (* G_OBJECT_CLASS (g_udev_device_parent_class)->finalize) (object);
 }
@@ -140,6 +144,10 @@ _g_udev_device_new (struct udev_device *udevice)
 
   device =  G_UDEV_DEVICE (g_object_new (G_UDEV_TYPE_DEVICE, NULL));
   device->priv->udevice = udev_device_ref (udevice);
+  device->priv->sysfs_attr = g_hash_table_new_full (g_str_hash,
+                                                    g_str_equal,
+                                                    g_free,
+                                                    g_free);
 
   return device;
 }
@@ -746,7 +754,8 @@ g_udev_device_get_sysfs_attr_keys (GUdevDevice *device)
  * Check if a the sysfs attribute with the given key exists. The
  * retrieved value is cached in the device. Repeated calls will
  * return the same result and not check for the presence of the
- * attribute again.
+ * attribute again, unless updated through one of the "uncached"
+ * functions.
  *
  * Returns: %TRUE only if the value for @key exist.
  */
@@ -766,7 +775,8 @@ g_udev_device_has_sysfs_attr (GUdevDevice  *device,
  *
  * Look up the sysfs attribute with @name on @device. The retrieved value
  * is cached in the device. Repeated calls will return the same value and
- * not open the attribute again.
+ * not open the attribute again, unless updated through one of the
+ * "uncached" functions.
  *
  * Returns: (nullable): The value of the sysfs attribute or %NULL if
  * there is no such attribute. Do not free this string, it is owned by
@@ -776,8 +786,14 @@ const gchar *
 g_udev_device_get_sysfs_attr (GUdevDevice  *device,
                               const gchar  *name)
 {
+  const char *attr;
+
   g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
   g_return_val_if_fail (name != NULL, NULL);
+
+  attr = g_hash_table_lookup (device->priv->sysfs_attr, name);
+  if (attr)
+    return attr;
   return udev_device_get_sysattr_value (device->priv->udevice, name);
 }
 
@@ -788,7 +804,8 @@ g_udev_device_get_sysfs_attr (GUdevDevice  *device,
  *
  * Look up the sysfs attribute with @name on @device and convert it to an integer
  * using strtol(). The retrieved value is cached in the device. Repeated calls
- * will return the same value and not open the attribute again.
+ * will return the same value and not open the attribute again, unless updated
+ * through one of the "uncached" functions.
  *
  * Returns: The value of the sysfs attribute or 0 if there is no such
  * attribute.
@@ -821,7 +838,7 @@ out:
  * Look up the sysfs attribute with @name on @device and convert it to an unsigned
  * 64-bit integer using g_ascii_strtoull(). The retrieved value is cached in the
  * device. Repeated calls will return the same value and not open the attribute
- * again.
+ * again, unless updated through one of the "uncached" functions.
  *
  * Returns: The value of the sysfs attribute or 0 if there is no such
  * attribute.
@@ -854,7 +871,7 @@ out:
  * Look up the sysfs attribute with @name on @device and convert it to a double
  * precision floating point number using strtod(). The retrieved value is cached
  * in the device. Repeated calls will return the same value and not open the
- * attribute again.
+ * attribute again, unless updated through one of the "uncached" functions.
  *
  * Returns: The value of the sysfs attribute or 0.0 if there is no such
  * attribute.
@@ -888,7 +905,8 @@ out:
  * boolean. This is done by doing a case-insensitive string comparison
  * on the string value against "1" and "true". The retrieved value is
  * cached in the device. Repeated calls will return the same value and
- * not open the attribute again.
+ * not open the attribute again, unless updated through one of the
+ * "uncached" functions.
  *
  * Returns: The value of the sysfs attribute or %FALSE if there is no such
  * attribute.
@@ -926,7 +944,8 @@ g_udev_device_get_sysfs_attr_as_boolean (GUdevDevice  *device,
  * not taken into account).
  *
  * The retrieved value is cached in the device. Repeated calls will return
- * the same value and not open the attribute again.
+ * the same value and not open the attribute again, unless updated through
+ * one of the "uncached" functions.
  *
  * Returns: (nullable) (transfer none) (array zero-terminated=1) (element-type utf8):
  * The value of the sysfs attribute split into tokens or %NULL if
@@ -967,6 +986,233 @@ out:
   return (const gchar* const *) result;
 }
 
+/**
+ * g_udev_device_has_sysfs_attr_uncached:
+ * @device: A #GUdevDevice.
+ * @key: Name of sysfs attribute.
+ *
+ * Check if a the sysfs attribute with the given key exists. The
+ * retrieved value is cached in the device. Repeated calls will
+ * return the same result and not check for the presence of the
+ * attribute again, unless updated through one of the "uncached"
+ * functions.
+ *
+ * Returns: %TRUE only if the value for @key exist.
+ */
+gboolean
+g_udev_device_has_sysfs_attr_uncached (GUdevDevice  *device,
+                                       const gchar  *key)
+{
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+  return g_udev_device_get_sysfs_attr_uncached (device, key) != NULL;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_uncached:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device. This function does
+ * blocking I/O, and updates the sysfs attributes cache.
+ *
+ * Returns: (nullable): The value of the sysfs attribute or %NULL if
+ * there is no such attribute. Do not free this string, it is owned by
+ * @device.
+ */
+const gchar *
+g_udev_device_get_sysfs_attr_uncached (GUdevDevice  *device,
+                                       const gchar  *name)
+{
+  g_autofree char *path = NULL;
+  char *contents = NULL;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  path = g_build_filename (udev_device_get_syspath (device->priv->udevice), name, NULL);
+  if (!g_file_get_contents (path, &contents, NULL, NULL))
+    return NULL;
+  g_hash_table_insert (device->priv->sysfs_attr, g_strdup (name), contents);
+
+  return contents;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_int_uncached:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to an integer
+ * using strtol(). This function does blocking I/O, and updates the sysfs
+ * attributes cache.
+ *
+ * Returns: The value of the sysfs attribute or 0 if there is no such
+ * attribute.
+ */
+gint
+g_udev_device_get_sysfs_attr_as_int_uncached (GUdevDevice  *device,
+                                              const gchar  *name)
+{
+  gint result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  g_return_val_if_fail (name != NULL, 0);
+
+  result = 0;
+  s = g_udev_device_get_sysfs_attr_uncached (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = strtol (s, NULL, 0);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_uint64_uncached:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to an unsigned
+ * 64-bit integer using g_ascii_strtoull(). This function does blocking I/O, and
+ * updates the sysfs attributes cache.
+ *
+ * Returns: The value of the sysfs attribute or 0 if there is no such
+ * attribute.
+ */
+guint64
+g_udev_device_get_sysfs_attr_as_uint64_uncached (GUdevDevice  *device,
+                                                 const gchar  *name)
+{
+  guint64 result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0);
+  g_return_val_if_fail (name != NULL, 0);
+
+  result = 0;
+  s = g_udev_device_get_sysfs_attr_uncached (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = g_ascii_strtoull (s, NULL, 0);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_double_uncached:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to a double
+ * precision floating point number using strtod(). This function does blocking
+ * I/O, and updates the sysfs attributes cache.
+ *
+ * Returns: The value of the sysfs attribute or 0.0 if there is no such
+ * attribute.
+ */
+gdouble
+g_udev_device_get_sysfs_attr_as_double_uncached (GUdevDevice  *device,
+                                                 const gchar  *name)
+{
+  gdouble result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), 0.0);
+  g_return_val_if_fail (name != NULL, 0.0);
+
+  result = 0.0;
+  s = g_udev_device_get_sysfs_attr_uncached (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = strtod (s, NULL);
+out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_boolean_uncached:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and convert it to an
+ * boolean. This is done by doing a case-insensitive string comparison
+ * on the string value against "1" and "true". This function does
+ * blocking I/O, and updates the sysfs attributes cache.
+ *
+ * Returns: The value of the sysfs attribute or %FALSE if there is no such
+ * attribute.
+ */
+gboolean
+g_udev_device_get_sysfs_attr_as_boolean_uncached (GUdevDevice  *device,
+                                                  const gchar  *name)
+{
+  gboolean result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), FALSE);
+  g_return_val_if_fail (name != NULL, FALSE);
+
+  result = FALSE;
+  s = g_udev_device_get_sysfs_attr_uncached (device, name);
+  if (s == NULL)
+    goto out;
+
+  if (strcmp (s, "1") == 0 || g_ascii_strcasecmp (s, "true") == 0)
+    result = TRUE;
+ out:
+  return result;
+}
+
+/**
+ * g_udev_device_get_sysfs_attr_as_strv_uncached:
+ * @device: A #GUdevDevice.
+ * @name: Name of the sysfs attribute.
+ *
+ * Look up the sysfs attribute with @name on @device and return the result of
+ * splitting it into non-empty tokens split at white space (only space (' '),
+ * form-feed ('\f'), newline ('\n'), carriage return ('\r'), horizontal
+ * tab ('\t'), and vertical tab ('\v') are considered; the locale is
+ * not taken into account).
+ *
+ * This function does blocking I/O, and updates the sysfs attributes cache.
+ *
+ * Returns: (nullable) (transfer none) (array zero-terminated=1) (element-type utf8):
+ * The value of the sysfs attribute split into tokens or %NULL if
+ * there is no such attribute. This array is owned by @device and
+ * should not be freed by the caller.
+ */
+const gchar * const *
+g_udev_device_get_sysfs_attr_as_strv_uncached (GUdevDevice  *device,
+                                               const gchar  *name)
+{
+  gchar **result;
+  const gchar *s;
+
+  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  result = NULL;
+  s = g_udev_device_get_sysfs_attr_uncached (device, name);
+  if (s == NULL)
+    goto out;
+
+  result = split_at_whitespace (s);
+  if (result == NULL)
+    goto out;
+
+  if (device->priv->sysfs_attr_strvs == NULL)
+    device->priv->sysfs_attr_strvs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
(GDestroyNotify) g_strfreev);
+  g_hash_table_insert (device->priv->sysfs_attr_strvs, g_strdup (name), result);
+
+out:
+  return (const gchar* const *) result;
+}
+
 /**
  * g_udev_device_get_tags:
  * @device: A #GUdevDevice.
diff --git a/gudev/gudevdevice.h b/gudev/gudevdevice.h
index 4691ce0..dfcecd6 100644
--- a/gudev/gudevdevice.h
+++ b/gudev/gudevdevice.h
@@ -129,6 +129,21 @@ const gchar* const *g_udev_device_get_sysfs_attr_as_strv    (GUdevDevice  *devic
                                                              const gchar  *name);
 const gchar* const *g_udev_device_get_tags                  (GUdevDevice  *device);
 
+gboolean            g_udev_device_has_sysfs_attr_uncached            (GUdevDevice  *device,
+                                                                      const gchar  *key);
+const gchar        *g_udev_device_get_sysfs_attr_uncached            (GUdevDevice  *device,
+                                                                      const gchar  *name);
+gint                g_udev_device_get_sysfs_attr_as_int_uncached     (GUdevDevice  *device,
+                                                                      const gchar  *name);
+guint64             g_udev_device_get_sysfs_attr_as_uint64_uncached  (GUdevDevice  *device,
+                                                                      const gchar  *name);
+gdouble             g_udev_device_get_sysfs_attr_as_double_uncached  (GUdevDevice  *device,
+                                                                      const gchar  *name);
+gboolean            g_udev_device_get_sysfs_attr_as_boolean_uncached (GUdevDevice  *device,
+                                                                      const gchar  *name);
+const gchar* const *g_udev_device_get_sysfs_attr_as_strv_uncached    (GUdevDevice  *device,
+                                                                      const gchar  *name);
+
 G_END_DECLS
 
 #endif /* __G_UDEV_DEVICE_H__ */
diff --git a/libgudev-1.0.sym b/libgudev-1.0.sym
index f4cd038..ab9cccf 100644
--- a/libgudev-1.0.sym
+++ b/libgudev-1.0.sym
@@ -34,6 +34,13 @@ global:
         g_udev_device_get_sysfs_attr_as_int;
         g_udev_device_get_sysfs_attr_as_strv;
         g_udev_device_get_sysfs_attr_as_uint64;
+        g_udev_device_has_sysfs_attr_uncached;
+        g_udev_device_get_sysfs_attr_uncached;
+        g_udev_device_get_sysfs_attr_as_int_uncached;
+        g_udev_device_get_sysfs_attr_as_uint64_uncached;
+        g_udev_device_get_sysfs_attr_as_double_uncached;
+        g_udev_device_get_sysfs_attr_as_boolean_uncached;
+        g_udev_device_get_sysfs_attr_as_strv_uncached;
         g_udev_device_get_sysfs_attr_keys;
         g_udev_device_get_sysfs_path;
         g_udev_device_get_tags;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4352a81..4cb60ca 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -4,13 +4,17 @@ all-local: check-local
 
 if HAVE_UMOCKDEV
 
-noinst_PROGRAMS = test-enumerator-filter
+noinst_PROGRAMS = test-enumerator-filter test-sysfsattr
 TEST_PROGS += $(noinst_PROGRAMS)
 
 test_enumerator_filter_SOURCES = test-enumerator-filter.c
 test_enumerator_filter_CFLAGS = $(UMOCKDEV_CFLAGS) -I$(top_srcdir)
 test_enumerator_filter_LDADD = $(UMOCKDEV_LIBS) $(top_builddir)/libgudev-1.0.la
 
+test_sysfsattr_SOURCES = test-sysfsattr.c
+test_sysfsattr_CFLAGS = $(UMOCKDEV_CFLAGS) -I$(top_srcdir)
+test_sysfsattr_LDADD = $(UMOCKDEV_LIBS) $(top_builddir)/libgudev-1.0.la
+
 endif
 
 EXTRA_DIST = test-enumerator-filter.c
diff --git a/tests/test-sysfsattr.c b/tests/test-sysfsattr.c
new file mode 100644
index 0000000..b8b2ec1
--- /dev/null
+++ b/tests/test-sysfsattr.c
@@ -0,0 +1,80 @@
+/* umockdev example: use libumockdev in C to fake a battery
+ * Build with:
+ * gcc battery.c -Wall `pkg-config --cflags --libs umockdev-1.0 gio-2.0` -o /tmp/battery
+ * Run with:
+ * umockdev-wrapper /tmp/battery
+ *
+ * Copyright (C) 2013 Canonical Ltd.
+ * Author: Martin Pitt <martin pitt ubuntu com>
+ *
+ * umockdev is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * umockdev is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <locale.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <umockdev.h>
+
+#include <gudev/gudev.h>
+
+static void
+test_uncached_sysfs_attr (void)
+{
+       /* create test bed */
+       UMockdevTestbed *testbed = umockdev_testbed_new ();
+
+       /* Relies on a test bed having been set up */
+       g_assert (umockdev_in_mock_environment ());
+
+       umockdev_testbed_add_device (testbed, "platform", "dev1", NULL,
+                                    "dytc_lapmode", "0", NULL,
+                                    "ID_MODEL", "KoolGadget", NULL);
+
+       /* Check the number of items in GUdevClient */
+       const gchar *subsystems[] = { "platform", NULL};
+       GUdevClient *client = g_udev_client_new (subsystems);
+       GUdevDevice *dev;
+       g_autofree char *lapmode_path = NULL;
+       FILE *sysfsfp;
+
+       GList *devices = g_udev_client_query_by_subsystem (client, NULL);
+       g_assert_cmpint (g_list_length (devices), ==, 1);
+       dev = devices->data;
+       lapmode_path = g_build_filename (g_udev_device_get_sysfs_path (dev), "dytc_lapmode", NULL);
+       /* First access */
+       g_assert_false (g_udev_device_get_sysfs_attr_as_boolean (dev, "dytc_lapmode"));
+       sysfsfp = fopen (lapmode_path, "w");
+       fprintf (sysfsfp, "%s", "1");
+       fclose (sysfsfp);
+       /* This is cached */
+       g_assert_false (g_udev_device_get_sysfs_attr_as_boolean (dev, "dytc_lapmode"));
+       /* This is uncached, and updates the cache */
+       g_assert_true (g_udev_device_get_sysfs_attr_as_boolean_uncached (dev, "dytc_lapmode"));
+       g_assert_true (g_udev_device_get_sysfs_attr_as_boolean (dev, "dytc_lapmode"));
+
+       g_list_free_full (devices, g_object_unref);
+}
+
+int main(int argc, char **argv)
+{
+       setlocale (LC_ALL, NULL);
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/gudev/uncached_sysfs_attr", test_uncached_sysfs_attr);
+
+       return g_test_run ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]