From b5363fe829c16b31a0c8250c2c8c9a17dd8e08bf Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 14 Sep 2020 15:15:36 +0200 Subject: 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(-) create mode 100644 tests/test-sysfsattr.c 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 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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +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 (); +} -- cgit v1.2.1