diff options
Diffstat (limited to '')
-rw-r--r-- | src/libudev/libudev-device-internal.h | 10 | ||||
-rw-r--r-- | src/libudev/libudev-device.c | 905 | ||||
-rw-r--r-- | src/libudev/libudev-enumerate.c | 458 | ||||
-rw-r--r-- | src/libudev/libudev-hwdb.c | 123 | ||||
-rw-r--r-- | src/libudev/libudev-list-internal.h | 16 | ||||
-rw-r--r-- | src/libudev/libudev-list.c | 235 | ||||
-rw-r--r-- | src/libudev/libudev-monitor.c | 309 | ||||
-rw-r--r-- | src/libudev/libudev-queue.c | 228 | ||||
-rw-r--r-- | src/libudev/libudev-util.c | 27 | ||||
-rw-r--r-- | src/libudev/libudev-util.h | 14 | ||||
-rw-r--r-- | src/libudev/libudev.c | 154 | ||||
-rw-r--r-- | src/libudev/libudev.h | 191 | ||||
-rw-r--r-- | src/libudev/libudev.pc.in | 20 | ||||
-rw-r--r-- | src/libudev/libudev.sym | 126 | ||||
-rw-r--r-- | src/libudev/meson.build | 52 | ||||
-rw-r--r-- | src/libudev/test-libudev.c | 496 | ||||
-rw-r--r-- | src/libudev/test-udev-device-thread.c | 51 |
17 files changed, 3415 insertions, 0 deletions
diff --git a/src/libudev/libudev-device-internal.h b/src/libudev/libudev-device-internal.h new file mode 100644 index 0000000..437d431 --- /dev/null +++ b/src/libudev/libudev-device-internal.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libudev.h" +#include "sd-device.h" + +struct udev_device; + +struct udev_device *udev_device_new(struct udev *udev, sd_device *device); +sd_device *udev_device_get_sd_device(struct udev_device *udev_device); diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c new file mode 100644 index 0000000..8091ff1 --- /dev/null +++ b/src/libudev/libudev-device.c @@ -0,0 +1,905 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/sockios.h> +#include <net/if.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "libudev.h" +#include "sd-device.h" + +#include "alloc-util.h" +#include "device-private.h" +#include "device-util.h" +#include "libudev-device-internal.h" +#include "libudev-list-internal.h" +#include "parse-util.h" +#include "time-util.h" + +/** + * SECTION:libudev-device + * @short_description: kernel sys devices + * + * Representation of kernel sys devices. Devices are uniquely identified + * by their syspath, every device has exactly one path in the kernel sys + * filesystem. Devices usually belong to a kernel subsystem, and have + * a unique name inside that subsystem. + */ + +/** + * udev_device: + * + * Opaque object representing one kernel sys device. + */ +struct udev_device { + struct udev *udev; + + /* real device object */ + sd_device *device; + + /* legacy */ + unsigned n_ref; + + struct udev_device *parent; + bool parent_set; + + struct udev_list *properties; + uint64_t properties_generation; + struct udev_list *all_tags, *current_tags; + uint64_t all_tags_generation, current_tags_generation; + struct udev_list *devlinks; + uint64_t devlinks_generation; + bool properties_read:1; + bool all_tags_read:1; + bool current_tags_read:1; + bool devlinks_read:1; + struct udev_list *sysattrs; + bool sysattrs_read; +}; + +/** + * udev_device_get_seqnum: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have a sequence number. + * + * Returns: the kernel event sequence number, or 0 if there is no sequence number available. + **/ +_public_ unsigned long long udev_device_get_seqnum(struct udev_device *udev_device) { + uint64_t seqnum; + + assert_return_errno(udev_device, 0, EINVAL); + + if (sd_device_get_seqnum(udev_device->device, &seqnum) < 0) + return 0; + + return seqnum; +} + +/** + * udev_device_get_devnum: + * @udev_device: udev device + * + * Get the device major/minor number. + * + * Returns: the dev_t number. + **/ +_public_ dev_t udev_device_get_devnum(struct udev_device *udev_device) { + dev_t devnum; + int r; + + assert_return_errno(udev_device, makedev(0, 0), EINVAL); + + r = sd_device_get_devnum(udev_device->device, &devnum); + if (r == -ENOENT) + return makedev(0, 0); + if (r < 0) + return_with_errno(makedev(0, 0), r); + + return devnum; +} + +/** + * udev_device_get_driver: + * @udev_device: udev device + * + * Get the kernel driver name. + * + * Returns: the driver name string, or #NULL if there is no driver attached. + **/ +_public_ const char *udev_device_get_driver(struct udev_device *udev_device) { + const char *driver; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_driver(udev_device->device, &driver); + if (r < 0) + return_with_errno(NULL, r); + + return driver; +} + +/** + * udev_device_get_devtype: + * @udev_device: udev device + * + * Retrieve the devtype string of the udev device. + * + * Returns: the devtype name of the udev device, or #NULL if it cannot be determined + **/ +_public_ const char *udev_device_get_devtype(struct udev_device *udev_device) { + const char *devtype; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devtype(udev_device->device, &devtype); + if (r == -ENOENT) + return NULL; + if (r < 0) + return_with_errno(NULL, r); + + return devtype; +} + +/** + * udev_device_get_subsystem: + * @udev_device: udev device + * + * Retrieve the subsystem string of the udev device. The string does not + * contain any "/". + * + * Returns: the subsystem name of the udev device, or #NULL if it cannot be determined + **/ +_public_ const char *udev_device_get_subsystem(struct udev_device *udev_device) { + const char *subsystem; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_subsystem(udev_device->device, &subsystem); + if (r < 0) + return_with_errno(NULL, r); + + return subsystem; +} + +/** + * udev_device_get_property_value: + * @udev_device: udev device + * @key: property name + * + * Get the value of a given property. + * + * Returns: the property string, or #NULL if there is no such property. + **/ +_public_ const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) { + const char *value; + int r; + + assert_return_errno(udev_device && key, NULL, EINVAL); + + r = sd_device_get_property_value(udev_device->device, key, &value); + if (r < 0) + return_with_errno(NULL, r); + + return value; +} + +struct udev_device *udev_device_new(struct udev *udev, sd_device *device) { + _cleanup_(udev_list_freep) struct udev_list *properties = NULL, *all_tags = NULL, *current_tags = NULL, *sysattrs = NULL, *devlinks = NULL; + struct udev_device *udev_device; + + assert(device); + + properties = udev_list_new(true); + if (!properties) + return_with_errno(NULL, ENOMEM); + all_tags = udev_list_new(true); + if (!all_tags) + return_with_errno(NULL, ENOMEM); + current_tags = udev_list_new(true); + if (!current_tags) + return_with_errno(NULL, ENOMEM); + sysattrs = udev_list_new(true); + if (!sysattrs) + return_with_errno(NULL, ENOMEM); + devlinks = udev_list_new(true); + if (!devlinks) + return_with_errno(NULL, ENOMEM); + + udev_device = new(struct udev_device, 1); + if (!udev_device) + return_with_errno(NULL, ENOMEM); + + *udev_device = (struct udev_device) { + .n_ref = 1, + .udev = udev, + .device = sd_device_ref(device), + .properties = TAKE_PTR(properties), + .all_tags = TAKE_PTR(all_tags), + .current_tags = TAKE_PTR(current_tags), + .sysattrs = TAKE_PTR(sysattrs), + .devlinks = TAKE_PTR(devlinks), + }; + + return udev_device; +} + +/** + * udev_device_new_from_syspath: + * @udev: udev library context + * @syspath: sys device path including sys directory + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The syspath is the absolute + * path to the device, including the sys mount point. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + r = sd_device_new_from_syspath(&device, syspath); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(udev, device); +} + +/** + * udev_device_new_from_devnum: + * @udev: udev library context + * @type: char or block device + * @devnum: device major/minor number + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by its major/minor number and type. Character and block device + * numbers are not unique across the two types. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + r = sd_device_new_from_devnum(&device, type, devnum); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(udev, device); +} + +/** + * udev_device_new_from_device_id: + * @udev: udev library context + * @id: text string identifying a kernel device + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by a special string: + * b8:2 - block device major:minor + * c128:1 - char device major:minor + * n3 - network device ifindex + * +sound:card29 - kernel driver core subsystem:device name + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + r = sd_device_new_from_device_id(&device, id); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(udev, device); +} + +/** + * udev_device_new_from_subsystem_sysname: + * @udev: udev library context + * @subsystem: the subsystem of the device + * @sysname: the name of the device + * + * Create new udev device, and fill in information from the sys device + * and the udev database entry. The device is looked up by the subsystem + * and name string of the device, like "mem" / "zero", or "block" / "sda". + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + r = sd_device_new_from_subsystem_sysname(&device, subsystem, sysname); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(udev, device); +} + +/** + * udev_device_new_from_environment + * @udev: udev library context + * + * Create new udev device, and fill in information from the + * current process environment. This only works reliably if + * the process is called from a udev rule. It is usually used + * for tools executed from IMPORT= rules. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_environment(struct udev *udev) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + r = device_new_from_strv(&device, environ); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(udev, device); +} + +static struct udev_device *device_new_from_parent(struct udev_device *child) { + sd_device *parent; + int r; + + assert_return_errno(child, NULL, EINVAL); + + r = sd_device_get_parent(child->device, &parent); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(child->udev, parent); +} + +/** + * udev_device_get_parent: + * @udev_device: the device to start searching from + * + * Find the next parent device, and fill in information from the sys + * device and the udev database entry. + * + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. + * + * It is not necessarily just the upper level directory, empty or not + * recognized sys directories are ignored. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL, if it no parent exist. + **/ +_public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + if (!udev_device->parent_set) { + udev_device->parent_set = true; + udev_device->parent = device_new_from_parent(udev_device); + } + + /* TODO: errno will differ here in case parent == NULL */ + return udev_device->parent; +} + +/** + * udev_device_get_parent_with_subsystem_devtype: + * @udev_device: udev device to start searching from + * @subsystem: the subsystem of the device + * @devtype: the type (DEVTYPE) of the device + * + * Find the next parent device, with a matching subsystem and devtype + * value, and fill in information from the sys device and the udev + * database entry. + * + * If devtype is #NULL, only subsystem is checked, and any devtype will + * match. + * + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL if no matching parent exists. + **/ +_public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) { + sd_device *parent; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + /* this relies on the fact that finding the subdevice of a parent or the + parent of a subdevice commute */ + + /* first find the correct sd_device */ + r = sd_device_get_parent_with_subsystem_devtype(udev_device->device, subsystem, devtype, &parent); + if (r < 0) + return_with_errno(NULL, r); + + /* then walk the chain of udev_device parents until the corresponding + one is found */ + while ((udev_device = udev_device_get_parent(udev_device))) + if (udev_device->device == parent) + return udev_device; + + return_with_errno(NULL, ENOENT); +} + +/** + * udev_device_get_udev: + * @udev_device: udev device + * + * Retrieve the udev library context the device was created with. + * + * Returns: the udev library context + **/ +_public_ struct udev *udev_device_get_udev(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + return udev_device->udev; +} + +static struct udev_device *udev_device_free(struct udev_device *udev_device) { + assert(udev_device); + + sd_device_unref(udev_device->device); + udev_device_unref(udev_device->parent); + + udev_list_free(udev_device->properties); + udev_list_free(udev_device->sysattrs); + udev_list_free(udev_device->all_tags); + udev_list_free(udev_device->current_tags); + udev_list_free(udev_device->devlinks); + + return mfree(udev_device); +} + +/** + * udev_device_ref: + * @udev_device: udev device + * + * Take a reference of a udev device. + * + * Returns: the passed udev device + **/ + +/** + * udev_device_unref: + * @udev_device: udev device + * + * Drop a reference of a udev device. If the refcount reaches zero, + * the resources of the device will be released. + * + * Returns: #NULL + **/ +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(struct udev_device, udev_device, udev_device_free); + +/** + * udev_device_get_devpath: + * @udev_device: udev device + * + * Retrieve the kernel devpath value of the udev device. The path + * does not contain the sys mount point, and starts with a '/'. + * + * Returns: the devpath of the udev device + **/ +_public_ const char *udev_device_get_devpath(struct udev_device *udev_device) { + const char *devpath; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devpath(udev_device->device, &devpath); + if (r < 0) + return_with_errno(NULL, r); + + return devpath; +} + +/** + * udev_device_get_syspath: + * @udev_device: udev device + * + * Retrieve the sys path of the udev device. The path is an + * absolute path and starts with the sys mount point. + * + * Returns: the sys path of the udev device + **/ +_public_ const char *udev_device_get_syspath(struct udev_device *udev_device) { + const char *syspath; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_syspath(udev_device->device, &syspath); + if (r < 0) + return_with_errno(NULL, r); + + return syspath; +} + +/** + * udev_device_get_sysname: + * @udev_device: udev device + * + * Get the kernel device name in /sys. + * + * Returns: the name string of the device + **/ +_public_ const char *udev_device_get_sysname(struct udev_device *udev_device) { + const char *sysname; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysname(udev_device->device, &sysname); + if (r < 0) + return_with_errno(NULL, r); + + return sysname; +} + +/** + * udev_device_get_sysnum: + * @udev_device: udev device + * + * Get the instance number of the device. + * + * Returns: the trailing number string of the device name + **/ +_public_ const char *udev_device_get_sysnum(struct udev_device *udev_device) { + const char *sysnum; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysnum(udev_device->device, &sysnum); + if (r == -ENOENT) + return NULL; + if (r < 0) + return_with_errno(NULL, r); + + return sysnum; +} + +/** + * udev_device_get_devnode: + * @udev_device: udev device + * + * Retrieve the device node file name belonging to the udev device. + * The path is an absolute path, and starts with the device directory. + * + * Returns: the device node file name of the udev device, or #NULL if no device node exists + **/ +_public_ const char *udev_device_get_devnode(struct udev_device *udev_device) { + const char *devnode; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devname(udev_device->device, &devnode); + if (r < 0) + return_with_errno(NULL, r); + + return devnode; +} + +/** + * udev_device_get_devlinks_list_entry: + * @udev_device: udev device + * + * Retrieve the list of device links pointing to the device file of + * the udev device. The next list entry can be retrieved with + * udev_list_entry_get_next(), which returns #NULL if no more entries exist. + * The devlink path can be retrieved from the list entry by + * udev_list_entry_get_name(). The path is an absolute path, and starts with + * the device directory. + * + * Returns: the first entry of the device node link list + **/ +_public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_devlinks_generation(udev_device->device) != udev_device->devlinks_generation || + !udev_device->devlinks_read) { + const char *devlink; + + udev_list_cleanup(udev_device->devlinks); + + FOREACH_DEVICE_DEVLINK(udev_device->device, devlink) + if (!udev_list_entry_add(udev_device->devlinks, devlink, NULL)) + return_with_errno(NULL, ENOMEM); + + udev_device->devlinks_read = true; + udev_device->devlinks_generation = device_get_devlinks_generation(udev_device->device); + } + + return udev_list_get_entry(udev_device->devlinks); +} + +/** + * udev_device_get_event_properties_entry: + * @udev_device: udev device + * + * Retrieve the list of key/value device properties of the udev + * device. The next list entry can be retrieved with udev_list_entry_get_next(), + * which returns #NULL if no more entries exist. The property name + * can be retrieved from the list entry by udev_list_entry_get_name(), + * the property value by udev_list_entry_get_value(). + * + * Returns: the first entry of the property list + **/ +_public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_properties_generation(udev_device->device) != udev_device->properties_generation || + !udev_device->properties_read) { + const char *key, *value; + + udev_list_cleanup(udev_device->properties); + + FOREACH_DEVICE_PROPERTY(udev_device->device, key, value) + if (!udev_list_entry_add(udev_device->properties, key, value)) + return_with_errno(NULL, ENOMEM); + + udev_device->properties_read = true; + udev_device->properties_generation = device_get_properties_generation(udev_device->device); + } + + return udev_list_get_entry(udev_device->properties); +} + +/** + * udev_device_get_action: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have an action string. Usual actions are: add, remove, change, move, + * online, offline. + * + * Returns: the kernel action value, or #NULL if there is no action value available. + **/ +_public_ const char *udev_device_get_action(struct udev_device *udev_device) { + sd_device_action_t action; + + assert_return_errno(udev_device, NULL, EINVAL); + + if (sd_device_get_action(udev_device->device, &action) < 0) + return NULL; + + return device_action_to_string(action); +} + +/** + * udev_device_get_usec_since_initialized: + * @udev_device: udev device + * + * Return the number of microseconds passed since udev set up the + * device for the first time. + * + * This is only implemented for devices with need to store properties + * in the udev database. All other devices return 0 here. + * + * Returns: the number of microseconds since the device was first seen. + **/ +_public_ unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device) { + usec_t ts; + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_get_usec_since_initialized(udev_device->device, &ts); + if (r < 0) + return_with_errno(0, r); + + return ts; +} + +/** + * udev_device_get_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * + * The retrieved value is cached in the device. Repeated calls will return the same + * value and not open the attribute again. + * + * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value. + **/ +_public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) { + const char *value; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysattr_value(udev_device->device, sysattr, &value); + if (r < 0) + return_with_errno(NULL, r); + + return value; +} + +/** + * udev_device_set_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * @value: new value to be set + * + * Update the contents of the sys attribute and the cached value of the device. + * + * Returns: Negative error code on failure or 0 on success. + **/ +_public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, const char *value) { + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_set_sysattr_value(udev_device->device, sysattr, value); + if (r < 0) + return r; + + return 0; +} + +/** + * udev_device_get_sysattr_list_entry: + * @udev_device: udev device + * + * Retrieve the list of available sysattrs, with value being empty; + * This just return all available sysfs attributes for a particular + * device without reading their values. + * + * Returns: the first entry of the property list + **/ +_public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + if (!udev_device->sysattrs_read) { + const char *sysattr; + + udev_list_cleanup(udev_device->sysattrs); + + FOREACH_DEVICE_SYSATTR(udev_device->device, sysattr) + if (!udev_list_entry_add(udev_device->sysattrs, sysattr, NULL)) + return_with_errno(NULL, ENOMEM); + + udev_device->sysattrs_read = true; + } + + return udev_list_get_entry(udev_device->sysattrs); +} + +/** + * udev_device_get_is_initialized: + * @udev_device: udev device + * + * Check if udev has already handled the device and has set up + * device node permissions and context, or has renamed a network + * device. + * + * This is only implemented for devices with a device node + * or network interfaces. All other devices return 1 here. + * + * Returns: 1 if the device is set up. 0 otherwise. + **/ +_public_ int udev_device_get_is_initialized(struct udev_device *udev_device) { + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_get_is_initialized(udev_device->device); + if (r < 0) + return_with_errno(0, r); + + return r; +} + +/** + * udev_device_get_tags_list_entry: + * @udev_device: udev device + * + * Retrieve the list of tags attached to the udev device. The next + * list entry can be retrieved with udev_list_entry_get_next(), + * which returns #NULL if no more entries exist. The tag string + * can be retrieved from the list entry by udev_list_entry_get_name(). + * + * Returns: the first entry of the tag list + **/ +_public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_tags_generation(udev_device->device) != udev_device->all_tags_generation || + !udev_device->all_tags_read) { + const char *tag; + + udev_list_cleanup(udev_device->all_tags); + + FOREACH_DEVICE_TAG(udev_device->device, tag) + if (!udev_list_entry_add(udev_device->all_tags, tag, NULL)) + return_with_errno(NULL, ENOMEM); + + udev_device->all_tags_read = true; + udev_device->all_tags_generation = device_get_tags_generation(udev_device->device); + } + + return udev_list_get_entry(udev_device->all_tags); +} + +_public_ struct udev_list_entry *udev_device_get_current_tags_list_entry(struct udev_device *udev_device) { + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_tags_generation(udev_device->device) != udev_device->current_tags_generation || + !udev_device->current_tags_read) { + const char *tag; + + udev_list_cleanup(udev_device->current_tags); + + FOREACH_DEVICE_CURRENT_TAG(udev_device->device, tag) + if (!udev_list_entry_add(udev_device->current_tags, tag, NULL)) + return_with_errno(NULL, ENOMEM); + + udev_device->current_tags_read = true; + udev_device->current_tags_generation = device_get_tags_generation(udev_device->device); + } + + return udev_list_get_entry(udev_device->current_tags); +} + +/** + * udev_device_has_tag: + * @udev_device: udev device + * @tag: tag name + * + * Check if a given device has a certain tag associated. + * + * Returns: 1 if the tag is found. 0 otherwise. + **/ +_public_ int udev_device_has_tag(struct udev_device *udev_device, const char *tag) { + assert_return(udev_device, 0); + + return sd_device_has_tag(udev_device->device, tag) > 0; +} + +_public_ int udev_device_has_current_tag(struct udev_device *udev_device, const char *tag) { + assert_return(udev_device, 0); + + return sd_device_has_current_tag(udev_device->device, tag) > 0; +} + +sd_device *udev_device_get_sd_device(struct udev_device *udev_device) { + assert(udev_device); + + return udev_device->device; +} diff --git a/src/libudev/libudev-enumerate.c b/src/libudev/libudev-enumerate.c new file mode 100644 index 0000000..d71a31c --- /dev/null +++ b/src/libudev/libudev-enumerate.c @@ -0,0 +1,458 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <dirent.h> +#include <errno.h> +#include <fnmatch.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "libudev.h" +#include "sd-device.h" + +#include "alloc-util.h" +#include "device-enumerator-private.h" +#include "device-util.h" +#include "libudev-device-internal.h" +#include "libudev-list-internal.h" + +/** + * SECTION:libudev-enumerate + * @short_description: lookup and sort sys devices + * + * Lookup devices in the sys filesystem, filter devices by properties, + * and return a sorted list of devices. + */ + +/** + * udev_enumerate: + * + * Opaque object representing one device lookup/sort context. + */ +struct udev_enumerate { + struct udev *udev; + unsigned n_ref; + struct udev_list *devices_list; + bool devices_uptodate:1; + + sd_device_enumerator *enumerator; +}; + +/** + * udev_enumerate_new: + * @udev: udev library context + * + * Create an enumeration context to scan /sys. + * + * Returns: an enumeration context. + **/ +_public_ struct udev_enumerate *udev_enumerate_new(struct udev *udev) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + _cleanup_(udev_list_freep) struct udev_list *list = NULL; + struct udev_enumerate *udev_enumerate; + int r; + + r = sd_device_enumerator_new(&e); + if (r < 0) + return_with_errno(NULL, r); + + r = sd_device_enumerator_allow_uninitialized(e); + if (r < 0) + return_with_errno(NULL, r); + + list = udev_list_new(false); + if (!list) + return_with_errno(NULL, ENOMEM); + + udev_enumerate = new(struct udev_enumerate, 1); + if (!udev_enumerate) + return_with_errno(NULL, ENOMEM); + + *udev_enumerate = (struct udev_enumerate) { + .udev = udev, + .n_ref = 1, + .enumerator = TAKE_PTR(e), + .devices_list = TAKE_PTR(list), + }; + + return udev_enumerate; +} + +static struct udev_enumerate *udev_enumerate_free(struct udev_enumerate *udev_enumerate) { + assert(udev_enumerate); + + udev_list_free(udev_enumerate->devices_list); + sd_device_enumerator_unref(udev_enumerate->enumerator); + return mfree(udev_enumerate); +} + +/** + * udev_enumerate_ref: + * @udev_enumerate: context + * + * Take a reference of an enumeration context. + * + * Returns: the passed enumeration context + **/ + +/** + * udev_enumerate_unref: + * @udev_enumerate: context + * + * Drop a reference of an enumeration context. If the refcount reaches zero, + * all resources of the enumeration context will be released. + * + * Returns: #NULL + **/ +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(struct udev_enumerate, udev_enumerate, udev_enumerate_free); + +/** + * udev_enumerate_get_udev: + * @udev_enumerate: context + * + * Get the udev library context. + * + * Returns: a pointer to the context. + */ +_public_ struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate) { + assert_return_errno(udev_enumerate, NULL, EINVAL); + + return udev_enumerate->udev; +} + +/** + * udev_enumerate_get_list_entry: + * @udev_enumerate: context + * + * Get the first entry of the sorted list of device paths. + * + * Returns: a udev_list_entry. + */ +_public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate) { + struct udev_list_entry *e; + + assert_return_errno(udev_enumerate, NULL, EINVAL); + + if (!udev_enumerate->devices_uptodate) { + sd_device *device; + + udev_list_cleanup(udev_enumerate->devices_list); + + FOREACH_DEVICE_AND_SUBSYSTEM(udev_enumerate->enumerator, device) { + const char *syspath; + int r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return_with_errno(NULL, r); + + if (!udev_list_entry_add(udev_enumerate->devices_list, syspath, NULL)) + return_with_errno(NULL, ENOMEM); + } + + udev_enumerate->devices_uptodate = true; + } + + e = udev_list_get_entry(udev_enumerate->devices_list); + if (!e) + return_with_errno(NULL, ENODATA); + + return e; +} + +/** + * udev_enumerate_add_match_subsystem: + * @udev_enumerate: context + * @subsystem: filter for a subsystem of the device to include in the list + * + * Match only devices belonging to a certain kernel subsystem. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!subsystem) + return 0; + + r = sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, true); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_nomatch_subsystem: + * @udev_enumerate: context + * @subsystem: filter for a subsystem of the device to exclude from the list + * + * Match only devices not belonging to a certain kernel subsystem. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!subsystem) + return 0; + + r = sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, false); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_match_sysattr: + * @udev_enumerate: context + * @sysattr: filter for a sys attribute at the device to include in the list + * @value: optional value of the sys attribute + * + * Match only devices with a certain /sys device attribute. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!sysattr) + return 0; + + r = sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, true); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_nomatch_sysattr: + * @udev_enumerate: context + * @sysattr: filter for a sys attribute at the device to exclude from the list + * @value: optional value of the sys attribute + * + * Match only devices not having a certain /sys device attribute. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!sysattr) + return 0; + + r = sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, false); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_match_property: + * @udev_enumerate: context + * @property: filter for a property of the device to include in the list + * @value: value of the property + * + * Match only devices with a certain property. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!property) + return 0; + + r = sd_device_enumerator_add_match_property(udev_enumerate->enumerator, property, value); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_match_tag: + * @udev_enumerate: context + * @tag: filter for a tag of the device to include in the list + * + * Match only devices with a certain tag. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!tag) + return 0; + + r = sd_device_enumerator_add_match_tag(udev_enumerate->enumerator, tag); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_match_parent: + * @udev_enumerate: context + * @parent: parent device where to start searching + * + * Return the devices on the subtree of one given device. The parent + * itself is included in the list. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!parent) + return 0; + + r = sd_device_enumerator_add_match_parent(udev_enumerate->enumerator, udev_device_get_sd_device(parent)); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_match_is_initialized: + * @udev_enumerate: context + * + * Match only devices which udev has set up already. This makes + * sure, that the device node permissions and context are properly set + * and that network devices are fully renamed. + * + * Usually, devices which are found in the kernel but not already + * handled by udev, have still pending events. Services should subscribe + * to monitor events and wait for these devices to become ready, instead + * of using uninitialized devices. + * + * For now, this will not affect devices which do not have a device node + * and are not network interfaces. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + r = device_enumerator_add_match_is_initialized(udev_enumerate->enumerator, MATCH_INITIALIZED_COMPAT); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_match_sysname: + * @udev_enumerate: context + * @sysname: filter for the name of the device to include in the list + * + * Match only devices with a given /sys device name. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) { + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!sysname) + return 0; + + r = sd_device_enumerator_add_match_sysname(udev_enumerate->enumerator, sysname); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_add_syspath: + * @udev_enumerate: context + * @syspath: path of a device + * + * Add a device to the list of devices, to retrieve it back sorted in dependency order. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!syspath) + return 0; + + r = sd_device_new_from_syspath(&device, syspath); + if (r < 0) + return r; + + r = device_enumerator_add_device(udev_enumerate->enumerator, device); + if (r < 0) + return r; + + udev_enumerate->devices_uptodate = false; + return 0; +} + +/** + * udev_enumerate_scan_devices: + * @udev_enumerate: udev enumeration context + * + * Scan /sys for all devices which match the given filters. No matches + * will return all currently available devices. + * + * Returns: 0 on success, otherwise a negative error value. + **/ +_public_ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_scan_devices(udev_enumerate->enumerator); +} + +/** + * udev_enumerate_scan_subsystems: + * @udev_enumerate: udev enumeration context + * + * Scan /sys for all kernel subsystems, including buses, classes, drivers. + * + * Returns: 0 on success, otherwise a negative error value. + **/ +_public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_scan_subsystems(udev_enumerate->enumerator); +} diff --git a/src/libudev/libudev-hwdb.c b/src/libudev/libudev-hwdb.c new file mode 100644 index 0000000..8e9ea97 --- /dev/null +++ b/src/libudev/libudev-hwdb.c @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "sd-hwdb.h" + +#include "alloc-util.h" +#include "hwdb-util.h" +#include "libudev-list-internal.h" + +/** + * SECTION:libudev-hwdb + * @short_description: retrieve properties from the hardware database + * + * Libudev hardware database interface. + */ + +/** + * udev_hwdb: + * + * Opaque object representing the hardware database. + */ +struct udev_hwdb { + unsigned n_ref; + sd_hwdb *hwdb; + struct udev_list *properties_list; +}; + +/** + * udev_hwdb_new: + * @udev: udev library context (unused) + * + * Create a hardware database context to query properties for devices. + * + * Returns: a hwdb context. + **/ +_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { + _cleanup_(udev_list_freep) struct udev_list *list = NULL; + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb_internal = NULL; + struct udev_hwdb *hwdb; + int r; + + r = sd_hwdb_new(&hwdb_internal); + if (r < 0) + return_with_errno(NULL, r); + + list = udev_list_new(true); + if (!list) + return_with_errno(NULL, ENOMEM); + + hwdb = new(struct udev_hwdb, 1); + if (!hwdb) + return_with_errno(NULL, ENOMEM); + + *hwdb = (struct udev_hwdb) { + .n_ref = 1, + .hwdb = TAKE_PTR(hwdb_internal), + .properties_list = TAKE_PTR(list), + }; + + return hwdb; +} + +static struct udev_hwdb *udev_hwdb_free(struct udev_hwdb *hwdb) { + assert(hwdb); + + sd_hwdb_unref(hwdb->hwdb); + udev_list_free(hwdb->properties_list); + return mfree(hwdb); +} + +/** + * udev_hwdb_ref: + * @hwdb: context + * + * Take a reference of a hwdb context. + * + * Returns: the passed enumeration context + **/ + +/** + * udev_hwdb_unref: + * @hwdb: context + * + * Drop a reference of a hwdb context. If the refcount reaches zero, + * all resources of the hwdb context will be released. + * + * Returns: #NULL + **/ +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(struct udev_hwdb, udev_hwdb, udev_hwdb_free); + +/** + * udev_hwdb_get_properties_list_entry: + * @hwdb: context + * @modalias: modalias string + * @flags: (unused) + * + * Lookup a matching device in the hardware database. The lookup key is a + * modalias string, whose formats are defined for the Linux kernel modules. + * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry + * of a list of retrieved properties is returned. + * + * Returns: a udev_list_entry. + */ +_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned flags) { + const char *key, *value; + struct udev_list_entry *e; + + assert_return_errno(hwdb, NULL, EINVAL); + assert_return_errno(modalias, NULL, EINVAL); + + udev_list_cleanup(hwdb->properties_list); + + SD_HWDB_FOREACH_PROPERTY(hwdb->hwdb, modalias, key, value) + if (!udev_list_entry_add(hwdb->properties_list, key, value)) + return_with_errno(NULL, ENOMEM); + + e = udev_list_get_entry(hwdb->properties_list); + if (!e) + return_with_errno(NULL, ENODATA); + + return e; +} diff --git a/src/libudev/libudev-list-internal.h b/src/libudev/libudev-list-internal.h new file mode 100644 index 0000000..c23735e --- /dev/null +++ b/src/libudev/libudev-list-internal.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libudev.h" + +#include "macro.h" + +struct udev_list; + +struct udev_list *udev_list_new(bool unique); +void udev_list_cleanup(struct udev_list *list); +struct udev_list *udev_list_free(struct udev_list *list); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_list *, udev_list_free); + +struct udev_list_entry *udev_list_get_entry(struct udev_list *list); +struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value); diff --git a/src/libudev/libudev-list.c b/src/libudev/libudev-list.c new file mode 100644 index 0000000..0adc1d5 --- /dev/null +++ b/src/libudev/libudev-list.c @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "hashmap.h" +#include "libudev-list-internal.h" +#include "list.h" +#include "sort-util.h" + +/** + * SECTION:libudev-list + * @short_description: list operation + * + * Libudev list operations. + */ + +/** + * udev_list_entry: + * + * Opaque object representing one entry in a list. An entry contains + * contains a name, and optionally a value. + */ +struct udev_list_entry { + struct udev_list *list; + char *name; + char *value; + + LIST_FIELDS(struct udev_list_entry, entries); +}; + +struct udev_list { + Hashmap *unique_entries; + LIST_HEAD(struct udev_list_entry, entries); + bool unique:1; + bool uptodate:1; +}; + +static struct udev_list_entry *udev_list_entry_free(struct udev_list_entry *entry) { + if (!entry) + return NULL; + + if (entry->list) { + if (entry->list->unique && entry->name) + hashmap_remove(entry->list->unique_entries, entry->name); + + if (!entry->list->unique || entry->list->uptodate) + LIST_REMOVE(entries, entry->list->entries, entry); + } + + free(entry->name); + free(entry->value); + + return mfree(entry); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_list_entry *, udev_list_entry_free); + +struct udev_list *udev_list_new(bool unique) { + struct udev_list *list; + + list = new(struct udev_list, 1); + if (!list) + return NULL; + + *list = (struct udev_list) { + .unique = unique, + }; + + return list; +} + +struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *_name, const char *_value) { + _cleanup_(udev_list_entry_freep) struct udev_list_entry *entry = NULL; + _cleanup_free_ char *name = NULL, *value = NULL; + + assert(list); + assert(_name); + + name = strdup(_name); + if (!name) + return NULL; + + if (_value) { + value = strdup(_value); + if (!value) + return NULL; + } + + entry = new(struct udev_list_entry, 1); + if (!entry) + return NULL; + + *entry = (struct udev_list_entry) { + .name = TAKE_PTR(name), + .value = TAKE_PTR(value), + }; + + if (list->unique) { + udev_list_entry_free(hashmap_get(list->unique_entries, entry->name)); + + if (hashmap_ensure_put(&list->unique_entries, &string_hash_ops, entry->name, entry) < 0) + return NULL; + + list->uptodate = false; + } else + LIST_APPEND(entries, list->entries, entry); + + entry->list = list; + + return TAKE_PTR(entry); +} + +void udev_list_cleanup(struct udev_list *list) { + if (!list) + return; + + if (list->unique) { + list->uptodate = false; + hashmap_clear_with_destructor(list->unique_entries, udev_list_entry_free); + } else + LIST_FOREACH(entries, i, list->entries) + udev_list_entry_free(i); +} + +struct udev_list *udev_list_free(struct udev_list *list) { + if (!list) + return NULL; + + udev_list_cleanup(list); + hashmap_free(list->unique_entries); + + return mfree(list); +} + +static int udev_list_entry_compare_func(struct udev_list_entry * const *a, struct udev_list_entry * const *b) { + return strcmp((*a)->name, (*b)->name); +} + +struct udev_list_entry *udev_list_get_entry(struct udev_list *list) { + if (!list) + return NULL; + + if (list->unique && !list->uptodate) { + size_t n; + + LIST_HEAD_INIT(list->entries); + + n = hashmap_size(list->unique_entries); + if (n == 0) + ; + else if (n == 1) + LIST_PREPEND(entries, list->entries, hashmap_first(list->unique_entries)); + else { + _cleanup_free_ struct udev_list_entry **buf = NULL; + struct udev_list_entry *entry, **p; + + buf = new(struct udev_list_entry *, n); + if (!buf) + return NULL; + + p = buf; + HASHMAP_FOREACH(entry, list->unique_entries) + *p++ = entry; + + typesafe_qsort(buf, n, udev_list_entry_compare_func); + + for (size_t j = n; j > 0; j--) + LIST_PREPEND(entries, list->entries, buf[j-1]); + } + + list->uptodate = true; + } + + return list->entries; +} + +/** + * udev_list_entry_get_next: + * @list_entry: current entry + * + * Get the next entry from the list. + * + * Returns: udev_list_entry, #NULL if no more entries are available. + */ +_public_ struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry) { + if (!list_entry) + return NULL; + if (list_entry->list->unique && !list_entry->list->uptodate) + return NULL; + return list_entry->entries_next; +} + +/** + * udev_list_entry_get_by_name: + * @list_entry: current entry + * @name: name string to match + * + * Lookup an entry in the list with a certain name. + * + * Returns: udev_list_entry, #NULL if no matching entry is found. + */ +_public_ struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name) { + if (!list_entry) + return NULL; + if (!list_entry->list->unique || !list_entry->list->uptodate) + return NULL; + return hashmap_get(list_entry->list->unique_entries, name); +} + +/** + * udev_list_entry_get_name: + * @list_entry: current entry + * + * Get the name of a list entry. + * + * Returns: the name string of this entry. + */ +_public_ const char *udev_list_entry_get_name(struct udev_list_entry *list_entry) { + if (!list_entry) + return NULL; + return list_entry->name; +} + +/** + * udev_list_entry_get_value: + * @list_entry: current entry + * + * Get the value of list entry. + * + * Returns: the value string of this entry. + */ +_public_ const char *udev_list_entry_get_value(struct udev_list_entry *list_entry) { + if (!list_entry) + return NULL; + return list_entry->value; +} diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c new file mode 100644 index 0000000..d7c931d --- /dev/null +++ b/src/libudev/libudev-monitor.c @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <poll.h> + +#include "libudev.h" + +#include "alloc-util.h" +#include "device-monitor-private.h" +#include "device-private.h" +#include "device-util.h" +#include "io-util.h" +#include "libudev-device-internal.h" +#include "string-util.h" + +/** + * SECTION:libudev-monitor + * @short_description: device event source + * + * Connects to a device event source. + */ + +/** + * udev_monitor: + * + * Opaque object handling an event source. + */ +struct udev_monitor { + struct udev *udev; + unsigned n_ref; + sd_device_monitor *monitor; +}; + +static MonitorNetlinkGroup monitor_netlink_group_from_string(const char *name) { + if (!name) + return MONITOR_GROUP_NONE; + if (streq(name, "udev")) + return MONITOR_GROUP_UDEV; + if (streq(name, "kernel")) + return MONITOR_GROUP_KERNEL; + return _MONITOR_NETLINK_GROUP_INVALID; +} + +/** + * udev_monitor_new_from_netlink: + * @udev: udev library context + * @name: name of event source + * + * Create new udev monitor and connect to a specified event + * source. Valid sources identifiers are "udev" and "kernel". + * + * Applications should usually not connect directly to the + * "kernel" events, because the devices might not be usable + * at that time, before udev has configured them, and created + * device nodes. Accessing devices at the same time as udev, + * might result in unpredictable behavior. The "udev" events + * are sent out after udev has finished its event processing, + * all rules have been processed, and needed device nodes are + * created. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev monitor. + * + * Returns: a new udev monitor, or #NULL, in case of an error + **/ +_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; + struct udev_monitor *udev_monitor; + MonitorNetlinkGroup g; + int r; + + g = monitor_netlink_group_from_string(name); + if (g < 0) + return_with_errno(NULL, EINVAL); + + r = device_monitor_new_full(&m, g, -1); + if (r < 0) + return_with_errno(NULL, r); + + udev_monitor = new(struct udev_monitor, 1); + if (!udev_monitor) + return_with_errno(NULL, ENOMEM); + + *udev_monitor = (struct udev_monitor) { + .udev = udev, + .n_ref = 1, + .monitor = TAKE_PTR(m), + }; + + return udev_monitor; +} + +/** + * udev_monitor_filter_update: + * @udev_monitor: monitor + * + * Update the installed socket filter. This is only needed, + * if the filter was removed or changed. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) { + assert_return(udev_monitor, -EINVAL); + + return sd_device_monitor_filter_update(udev_monitor->monitor); +} + +/** + * udev_monitor_enable_receiving: + * @udev_monitor: the monitor which should receive events + * + * Binds the @udev_monitor socket to the event source. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) { + assert_return(udev_monitor, -EINVAL); + + return device_monitor_enable_receiving(udev_monitor->monitor); +} + +/** + * udev_monitor_set_receive_buffer_size: + * @udev_monitor: the monitor which should receive events + * @size: the size in bytes + * + * Set the size of the kernel socket buffer. This call needs the + * appropriate privileges to succeed. + * + * Returns: 0 on success, otherwise -1 on error. + */ +_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) { + assert_return(udev_monitor, -EINVAL); + + return sd_device_monitor_set_receive_buffer_size(udev_monitor->monitor, (size_t) size); +} + +static struct udev_monitor *udev_monitor_free(struct udev_monitor *udev_monitor) { + assert(udev_monitor); + + sd_device_monitor_unref(udev_monitor->monitor); + return mfree(udev_monitor); +} + +/** + * udev_monitor_ref: + * @udev_monitor: udev monitor + * + * Take a reference of a udev monitor. + * + * Returns: the passed udev monitor + **/ + +/** + * udev_monitor_unref: + * @udev_monitor: udev monitor + * + * Drop a reference of a udev monitor. If the refcount reaches zero, + * the bound socket will be closed, and the resources of the monitor + * will be released. + * + * Returns: #NULL + **/ +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(struct udev_monitor, udev_monitor, udev_monitor_free); + +/** + * udev_monitor_get_udev: + * @udev_monitor: udev monitor + * + * Retrieve the udev library context the monitor was created with. + * + * Returns: the udev library context + **/ +_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) { + assert_return(udev_monitor, NULL); + + return udev_monitor->udev; +} + +/** + * udev_monitor_get_fd: + * @udev_monitor: udev monitor + * + * Retrieve the socket file descriptor associated with the monitor. + * + * Returns: the socket file descriptor + **/ +_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor) { + assert_return(udev_monitor, -EINVAL); + + return device_monitor_get_fd(udev_monitor->monitor); +} + +static int udev_monitor_receive_sd_device(struct udev_monitor *udev_monitor, sd_device **ret) { + int r; + + assert(udev_monitor); + assert(ret); + + for (;;) { + /* r == 0 means a device is received but it does not pass the current filter. */ + r = device_monitor_receive_device(udev_monitor->monitor, ret); + if (r != 0) + return r; + + for (;;) { + /* Wait for next message */ + r = fd_wait_for_event(device_monitor_get_fd(udev_monitor->monitor), POLLIN, 0); + if (r == -EINTR) + continue; + if (r < 0) + return r; + if (r == 0) + return -EAGAIN; + + /* Receive next message */ + break; + } + } +} + +/** + * udev_monitor_receive_device: + * @udev_monitor: udev monitor + * + * Receive data from the udev monitor socket, allocate a new udev + * device, fill in the received data, and return the device. + * + * Only socket connections with uid=0 are accepted. + * + * The monitor socket is by default set to NONBLOCK. A variant of poll() on + * the file descriptor returned by udev_monitor_get_fd() should to be used to + * wake up when new devices arrive, or alternatively the file descriptor + * switched into blocking mode. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, in case of an error + **/ +_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(udev_monitor, NULL); + + r = udev_monitor_receive_sd_device(udev_monitor, &device); + if (r < 0) + return_with_errno(NULL, r); + + return udev_device_new(udev_monitor->udev, device); +} + +/** + * udev_monitor_filter_add_match_subsystem_devtype: + * @udev_monitor: the monitor + * @subsystem: the subsystem value to match the incoming devices against + * @devtype: the devtype value to match the incoming devices against + * + * This filter is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype) { + int r; + + assert_return(udev_monitor, -EINVAL); + + r = sd_device_monitor_filter_add_match_subsystem_devtype(udev_monitor->monitor, subsystem, devtype); + return r < 0 ? r : 0; +} + +/** + * udev_monitor_filter_add_match_tag: + * @udev_monitor: the monitor + * @tag: the name of a tag + * + * This filter is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) { + int r; + + assert_return(udev_monitor, -EINVAL); + + r = sd_device_monitor_filter_add_match_tag(udev_monitor->monitor, tag); + return r < 0 ? r : 0; +} + +/** + * udev_monitor_filter_remove: + * @udev_monitor: monitor + * + * Remove all filters from monitor. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) { + assert_return(udev_monitor, -EINVAL); + + return sd_device_monitor_filter_remove(udev_monitor->monitor); +} diff --git a/src/libudev/libudev-queue.c b/src/libudev/libudev-queue.c new file mode 100644 index 0000000..7ca17fa --- /dev/null +++ b/src/libudev/libudev-queue.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk> +***/ + +#include <errno.h> +#include <unistd.h> + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "udev-util.h" + +/** + * SECTION:libudev-queue + * @short_description: access to currently active events + * + * This exports the current state of the udev processing queue. + */ + +/** + * udev_queue: + * + * Opaque object representing the current event queue in the udev daemon. + */ +struct udev_queue { + struct udev *udev; + unsigned n_ref; + int fd; +}; + +/** + * udev_queue_new: + * @udev: udev library context + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev queue context. + * + * Returns: the udev queue context, or #NULL on error. + **/ +_public_ struct udev_queue *udev_queue_new(struct udev *udev) { + struct udev_queue *udev_queue; + + udev_queue = new(struct udev_queue, 1); + if (!udev_queue) + return_with_errno(NULL, ENOMEM); + + *udev_queue = (struct udev_queue) { + .udev = udev, + .n_ref = 1, + .fd = -1, + }; + + return udev_queue; +} + +static struct udev_queue *udev_queue_free(struct udev_queue *udev_queue) { + assert(udev_queue); + + safe_close(udev_queue->fd); + return mfree(udev_queue); +} + +/** + * udev_queue_ref: + * @udev_queue: udev queue context + * + * Take a reference of a udev queue context. + * + * Returns: the same udev queue context. + **/ + +/** + * udev_queue_unref: + * @udev_queue: udev queue context + * + * Drop a reference of a udev queue context. If the refcount reaches zero, + * the resources of the queue context will be released. + * + * Returns: #NULL + **/ +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(struct udev_queue, udev_queue, udev_queue_free); + +/** + * udev_queue_get_udev: + * @udev_queue: udev queue context + * + * Retrieve the udev library context the queue context was created with. + * + * Returns: the udev library context. + **/ +_public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) { + assert_return_errno(udev_queue, NULL, EINVAL); + + return udev_queue->udev; +} + +/** + * udev_queue_get_kernel_seqnum: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: 0. + **/ +_public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) { + return 0; +} + +/** + * udev_queue_get_udev_seqnum: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: 0. + **/ +_public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) { + return 0; +} + +/** + * udev_queue_get_udev_is_active: + * @udev_queue: udev queue context + * + * Check if udev is active on the system. + * + * Returns: a flag indicating if udev is active. + **/ +_public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) { + return access("/run/udev/control", F_OK) >= 0; +} + +/** + * udev_queue_get_queue_is_empty: + * @udev_queue: udev queue context + * + * Check if udev is currently processing any events. + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) { + return udev_queue_is_empty() > 0; +} + +/** + * udev_queue_get_seqnum_sequence_is_finished: + * @udev_queue: udev queue context + * @start: first event sequence number + * @end: last event sequence number + * + * This function is deprecated, and equivalent to udev_queue_get_queue_is_empty(). + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, + unsigned long long int start, unsigned long long int end) { + return udev_queue_is_empty() > 0; +} + +/** + * udev_queue_get_seqnum_is_finished: + * @udev_queue: udev queue context + * @seqnum: sequence number + * + * This function is deprecated, and equivalent to udev_queue_get_queue_is_empty(). + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) { + return udev_queue_is_empty() > 0; +} + +/** + * udev_queue_get_queued_list_entry: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: NULL. + **/ +_public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) { + return_with_errno(NULL, ENODATA); +} + +/** + * udev_queue_get_fd: + * @udev_queue: udev queue context + * + * Returns: a file descriptor to watch for a queue to become empty. + */ +_public_ int udev_queue_get_fd(struct udev_queue *udev_queue) { + int r; + + assert_return(udev_queue, -EINVAL); + + if (udev_queue->fd >= 0) + return udev_queue->fd; + + r = udev_queue_init(); + if (r < 0) + return r; + + return udev_queue->fd = r; +} + +/** + * udev_queue_flush: + * @udev_queue: udev queue context + * + * Returns: the result of clearing the watch for queue changes. + */ +_public_ int udev_queue_flush(struct udev_queue *udev_queue) { + int r; + + assert_return(udev_queue, -EINVAL); + + if (udev_queue->fd < 0) + return -EINVAL; + + r = flush_fd(udev_queue->fd); + if (r < 0) + return r; + + return 0; +} diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c new file mode 100644 index 0000000..8c51877 --- /dev/null +++ b/src/libudev/libudev-util.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "device-nodes.h" +#include "libudev-util.h" + +/** + * SECTION:libudev-util + * @short_description: utils + * + * Utilities useful when dealing with devices and device node names. + */ + +/** + * udev_util_encode_string: + * @str: input string to be encoded + * @str_enc: output string to store the encoded input string + * @len: maximum size of the output string, which may be + * four times as long as the input string + * + * Encode all potentially unsafe characters of a string to the + * corresponding 2 char hex value prefixed by '\x'. + * + * Returns: 0 if the entire string was copied, non-zero otherwise. + **/ +_public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len) { + return encode_devnode_name(str, str_enc, len); +} diff --git a/src/libudev/libudev-util.h b/src/libudev/libudev-util.h new file mode 100644 index 0000000..0dc18d4 --- /dev/null +++ b/src/libudev/libudev-util.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libudev.h" + +#include "macro.h" + +/* Cleanup functions */ +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_hwdb*, udev_hwdb_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_queue*, udev_queue_unref); diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c new file mode 100644 index 0000000..7357487 --- /dev/null +++ b/src/libudev/libudev.c @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <ctype.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "string-util.h" + +/** + * SECTION:libudev + * @short_description: libudev context + */ + +/** + * udev: + * + * Opaque object representing the library context. + */ +struct udev { + unsigned n_ref; + void *userdata; +}; + +/** + * udev_get_userdata: + * @udev: udev library context + * + * Retrieve stored data pointer from library context. This might be useful + * to access from callbacks. + * + * Returns: stored userdata + **/ +_public_ void *udev_get_userdata(struct udev *udev) { + assert_return(udev, NULL); + + return udev->userdata; +} + +/** + * udev_set_userdata: + * @udev: udev library context + * @userdata: data pointer + * + * Store custom @userdata in the library context. + **/ +_public_ void udev_set_userdata(struct udev *udev, void *userdata) { + if (!udev) + return; + + udev->userdata = userdata; +} + +/** + * udev_new: + * + * Create udev library context. This only allocates the basic data structure. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev library context. + * + * Returns: a new udev library context + **/ +_public_ struct udev *udev_new(void) { + struct udev *udev; + + udev = new(struct udev, 1); + if (!udev) + return_with_errno(NULL, ENOMEM); + + *udev = (struct udev) { + .n_ref = 1, + }; + + return udev; +} + +/** + * udev_ref: + * @udev: udev library context + * + * Take a reference of the udev library context. + * + * Returns: the passed udev library context + **/ +DEFINE_PUBLIC_TRIVIAL_REF_FUNC(struct udev, udev); + +/** + * udev_unref: + * @udev: udev library context + * + * Drop a reference of the udev library context. If the refcount + * reaches zero, the resources of the context will be released. + * + * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise. + **/ +_public_ struct udev *udev_unref(struct udev *udev) { + if (!udev) + return NULL; + + assert(udev->n_ref > 0); + udev->n_ref--; + if (udev->n_ref > 0) + /* This is different from our convention, but let's keep backward + * compatibility. So, do not use DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC() + * macro to define this function. */ + return udev; + + return mfree(udev); +} + +/** + * udev_set_log_fn: + * @udev: udev library context + * @log_fn: function to be called for log messages + * + * This function is deprecated. + * + **/ +_public_ void udev_set_log_fn( + struct udev *udev, + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) { + return; +} + +/** + * udev_get_log_priority: + * @udev: udev library context + * + * This function is deprecated. + * + **/ +_public_ int udev_get_log_priority(struct udev *udev) { + return log_get_max_level(); +} + +/** + * udev_set_log_priority: + * @udev: udev library context + * @priority: the new log priority + * + * This function is deprecated. + * + **/ +_public_ void udev_set_log_priority(struct udev *udev, int priority) { + log_set_max_level(priority); +} diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h new file mode 100644 index 0000000..aef4a55 --- /dev/null +++ b/src/libudev/libudev.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _LIBUDEV_H_ +#define _LIBUDEV_H_ + +#include <stdarg.h> +#include <sys/sysmacros.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * udev - library context + * + * reads the udev config and system environment + * allows custom logging + */ +struct udev; +struct udev *udev_ref(struct udev *udev); +struct udev *udev_unref(struct udev *udev); +struct udev *udev_new(void); +void udev_set_log_fn(struct udev *udev, + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) __attribute__((__deprecated__)); +int udev_get_log_priority(struct udev *udev) __attribute__((__deprecated__)); +void udev_set_log_priority(struct udev *udev, int priority) __attribute__((__deprecated__)); +void *udev_get_userdata(struct udev *udev); +void udev_set_userdata(struct udev *udev, void *userdata); + +/* + * udev_list + * + * access to libudev generated lists + */ +struct udev_list_entry; +struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry); +struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name); +const char *udev_list_entry_get_name(struct udev_list_entry *list_entry); +const char *udev_list_entry_get_value(struct udev_list_entry *list_entry); +/** + * udev_list_entry_foreach: + * @list_entry: entry to store the current position + * @first_entry: first entry to start with + * + * Helper to iterate over all entries of a list. + */ +#define udev_list_entry_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; \ + list_entry; \ + list_entry = udev_list_entry_get_next(list_entry)) + +/* + * udev_device + * + * access to sysfs/kernel devices + */ +struct udev_device; +struct udev_device *udev_device_ref(struct udev_device *udev_device); +struct udev_device *udev_device_unref(struct udev_device *udev_device); +struct udev *udev_device_get_udev(struct udev_device *udev_device); +struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); +struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); +struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); +struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); +struct udev_device *udev_device_new_from_environment(struct udev *udev); +/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */ +struct udev_device *udev_device_get_parent(struct udev_device *udev_device); +struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, + const char *subsystem, const char *devtype); +/* retrieve device properties */ +const char *udev_device_get_devpath(struct udev_device *udev_device); +const char *udev_device_get_subsystem(struct udev_device *udev_device); +const char *udev_device_get_devtype(struct udev_device *udev_device); +const char *udev_device_get_syspath(struct udev_device *udev_device); +const char *udev_device_get_sysname(struct udev_device *udev_device); +const char *udev_device_get_sysnum(struct udev_device *udev_device); +const char *udev_device_get_devnode(struct udev_device *udev_device); +int udev_device_get_is_initialized(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_current_tags_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device); +const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key); +const char *udev_device_get_driver(struct udev_device *udev_device); +dev_t udev_device_get_devnum(struct udev_device *udev_device); +const char *udev_device_get_action(struct udev_device *udev_device); +unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device); +unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device); +const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr); +int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, const char *value); +int udev_device_has_tag(struct udev_device *udev_device, const char *tag); +int udev_device_has_current_tag(struct udev_device *udev_device, const char *tag); + +/* + * udev_monitor + * + * access to kernel uevents and udev events + */ +struct udev_monitor; +struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); +struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor); +struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); +/* kernel and udev generated events over netlink */ +struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name); +/* bind socket */ +int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor); +int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); +int udev_monitor_get_fd(struct udev_monitor *udev_monitor); +struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor); +/* in-kernel socket filters to select messages that get delivered to a listener */ +int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, + const char *subsystem, const char *devtype); +int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); +int udev_monitor_filter_update(struct udev_monitor *udev_monitor); +int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); + +/* + * udev_enumerate + * + * search sysfs for specific devices and provide a sorted list + */ +struct udev_enumerate; +struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate); +struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_new(struct udev *udev); +/* device properties filter */ +int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); +int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); +int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); +int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent); +int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate); +int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); +/* run enumeration with active filters */ +int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); +int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate); +/* return device list */ +struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate); + +/* + * udev_queue + * + * access to the currently running udev events + */ +struct udev_queue; +struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue); +struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue); +struct udev *udev_queue_get_udev(struct udev_queue *udev_queue); +struct udev_queue *udev_queue_new(struct udev *udev); +unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) __attribute__((__deprecated__)); +unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) __attribute__((__deprecated__)); +int udev_queue_get_udev_is_active(struct udev_queue *udev_queue); +int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue); +int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) __attribute__((__deprecated__)); +int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, + unsigned long long int start, unsigned long long int end) __attribute__((__deprecated__)); +int udev_queue_get_fd(struct udev_queue *udev_queue); +int udev_queue_flush(struct udev_queue *udev_queue); +struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) __attribute__((__deprecated__)); + +/* + * udev_hwdb + * + * access to the static hardware properties database + */ +struct udev_hwdb; +struct udev_hwdb *udev_hwdb_new(struct udev *udev); +struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb); +struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb); +struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned flags); + +/* + * udev_util + * + * udev specific utilities + */ +int udev_util_encode_string(const char *str, char *str_enc, size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in new file mode 100644 index 0000000..1d6487f --- /dev/null +++ b/src/libudev/libudev.pc.in @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +prefix={{PREFIX}} +exec_prefix={{PREFIX}} +libdir={{ROOTLIBDIR}} +includedir={{INCLUDE_DIR}} + +Name: libudev +Description: Library to access udev device information +Version: {{PROJECT_VERSION}} +Libs: -L${libdir} -ludev +Libs.private: -lrt -pthread +Cflags: -I${includedir} diff --git a/src/libudev/libudev.sym b/src/libudev/libudev.sym new file mode 100644 index 0000000..6aa6768 --- /dev/null +++ b/src/libudev/libudev.sym @@ -0,0 +1,126 @@ +/*** + SPDX-License-Identifier: LGPL-2.1-or-later + + systemd 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. +***/ + +LIBUDEV_183 { +global: + udev_device_get_action; + udev_device_get_devlinks_list_entry; + udev_device_get_devnode; + udev_device_get_devnum; + udev_device_get_devpath; + udev_device_get_devtype; + udev_device_get_driver; + udev_device_get_is_initialized; + udev_device_get_parent; + udev_device_get_parent_with_subsystem_devtype; + udev_device_get_properties_list_entry; + udev_device_get_property_value; + udev_device_get_seqnum; + udev_device_get_subsystem; + udev_device_get_sysattr_list_entry; + udev_device_get_sysattr_value; + udev_device_get_sysname; + udev_device_get_sysnum; + udev_device_get_syspath; + udev_device_get_tags_list_entry; + udev_device_get_udev; + udev_device_get_usec_since_initialized; + udev_device_has_tag; + udev_device_new_from_devnum; + udev_device_new_from_environment; + udev_device_new_from_subsystem_sysname; + udev_device_new_from_syspath; + udev_device_ref; + udev_device_unref; + udev_enumerate_add_match_is_initialized; + udev_enumerate_add_match_parent; + udev_enumerate_add_match_property; + udev_enumerate_add_match_subsystem; + udev_enumerate_add_match_sysattr; + udev_enumerate_add_match_sysname; + udev_enumerate_add_match_tag; + udev_enumerate_add_nomatch_subsystem; + udev_enumerate_add_nomatch_sysattr; + udev_enumerate_add_syspath; + udev_enumerate_get_list_entry; + udev_enumerate_get_udev; + udev_enumerate_new; + udev_enumerate_ref; + udev_enumerate_scan_devices; + udev_enumerate_scan_subsystems; + udev_enumerate_unref; + udev_get_log_priority; + udev_get_userdata; + udev_list_entry_get_by_name; + udev_list_entry_get_name; + udev_list_entry_get_next; + udev_list_entry_get_value; + udev_monitor_enable_receiving; + udev_monitor_filter_add_match_subsystem_devtype; + udev_monitor_filter_add_match_tag; + udev_monitor_filter_remove; + udev_monitor_filter_update; + udev_monitor_get_fd; + udev_monitor_get_udev; + udev_monitor_new_from_netlink; + udev_monitor_receive_device; + udev_monitor_ref; + udev_monitor_set_receive_buffer_size; + udev_monitor_unref; + udev_new; + udev_queue_get_kernel_seqnum; + udev_queue_get_queue_is_empty; + udev_queue_get_queued_list_entry; + udev_queue_get_seqnum_is_finished; + udev_queue_get_seqnum_sequence_is_finished; + udev_queue_get_udev; + udev_queue_get_udev_is_active; + udev_queue_get_udev_seqnum; + udev_queue_new; + udev_queue_ref; + udev_queue_unref; + udev_ref; + udev_set_log_fn; + udev_set_log_priority; + udev_set_userdata; + udev_unref; + udev_util_encode_string; +local: + *; +}; + +LIBUDEV_189 { +global: + udev_device_new_from_device_id; +} LIBUDEV_183; + +LIBUDEV_196 { +global: + udev_hwdb_new; + udev_hwdb_ref; + udev_hwdb_unref; + udev_hwdb_get_properties_list_entry; +} LIBUDEV_189; + +LIBUDEV_199 { +global: + udev_device_set_sysattr_value; +} LIBUDEV_196; + +LIBUDEV_215 { +global: + udev_queue_flush; + udev_queue_get_fd; +} LIBUDEV_199; + +LIBUDEV_247 { +global: + udev_device_has_current_tag; + udev_device_get_current_tags_list_entry; +} LIBUDEV_215; diff --git a/src/libudev/meson.build b/src/libudev/meson.build new file mode 100644 index 0000000..92d5407 --- /dev/null +++ b/src/libudev/meson.build @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libudev_sources = files( + 'libudev-device.c', + 'libudev-device-internal.h', + 'libudev-enumerate.c', + 'libudev-hwdb.c', + 'libudev-list.c', + 'libudev-list-internal.h', + 'libudev-monitor.c', + 'libudev-queue.c', + 'libudev-util.c', + 'libudev-util.h', + 'libudev.c', + 'libudev.h') + +############################################################ + +libudev_includes = [includes, include_directories('.')] + +libudev_sym = files('libudev.sym') +libudev_sym_path = meson.current_source_dir() / 'libudev.sym' + +install_headers('libudev.h') +libudev_h_path = meson.current_source_dir() / 'libudev.h' + +libudev_basic = static_library( + 'udev-basic', + libudev_sources, + include_directories : includes, + c_args : ['-fvisibility=default'], + build_by_default : false) + +static_libudev = get_option('static-libudev') +static_libudev_pic = static_libudev == 'true' or static_libudev == 'pic' + +libudev_pc = custom_target( + 'libudev.pc', + input : 'libudev.pc.in', + output : 'libudev.pc', + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : pkgconfiglibdir != 'no', + install_tag : 'devel', + install_dir : pkgconfiglibdir) + +############################################################ + +tests += [ + [files('test-libudev.c'), + [libshared, + libudev_basic]], +] diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c new file mode 100644 index 0000000..e05a062 --- /dev/null +++ b/src/libudev/test-libudev.c @@ -0,0 +1,496 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <getopt.h> +#include <sys/epoll.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "devnum-util.h" +#include "fd-util.h" +#include "libudev-list-internal.h" +#include "libudev-util.h" +#include "log.h" +#include "main-func.h" +#include "stdio-util.h" +#include "string-util.h" +#include "tests.h" +#include "version.h" + +static bool arg_monitor = false; + +static void print_device(struct udev_device *device) { + const char *str; + dev_t devnum; + int count; + struct udev_list_entry *list_entry; + + log_info("*** device: %p ***", device); + str = udev_device_get_action(device); + if (str) + log_info("action: '%s'", str); + + str = udev_device_get_syspath(device); + log_info("syspath: '%s'", str); + + str = udev_device_get_sysname(device); + log_info("sysname: '%s'", str); + + str = udev_device_get_sysnum(device); + if (str) + log_info("sysnum: '%s'", str); + + str = udev_device_get_devpath(device); + log_info("devpath: '%s'", str); + + str = udev_device_get_subsystem(device); + if (str) + log_info("subsystem: '%s'", str); + + str = udev_device_get_devtype(device); + if (str) + log_info("devtype: '%s'", str); + + str = udev_device_get_driver(device); + if (str) + log_info("driver: '%s'", str); + + str = udev_device_get_devnode(device); + if (str) + log_info("devname: '%s'", str); + + devnum = udev_device_get_devnum(device); + if (major(devnum) > 0) + log_info("devnum: %u:%u", major(devnum), minor(devnum)); + + count = 0; + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device)) { + log_info("link: '%s'", udev_list_entry_get_name(list_entry)); + count++; + } + if (count > 0) + log_info("found %i links", count); + + count = 0; + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) { + log_info("property: '%s=%s'", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + count++; + } + if (count > 0) + log_info("found %i properties", count); + + str = udev_device_get_property_value(device, "MAJOR"); + if (str) + log_info("MAJOR: '%s'", str); + + str = udev_device_get_sysattr_value(device, "dev"); + if (str) + log_info("attr{dev}: '%s'", str); +} + +static void test_device(struct udev *udev, const char *syspath) { + _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; + + log_info("/* %s, device %s */", __func__, syspath); + device = udev_device_new_from_syspath(udev, syspath); + if (device) + print_device(device); + else + log_warning_errno(errno, "udev_device_new_from_syspath: %m"); +} + +static void test_device_parents(struct udev *udev, const char *syspath) { + _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; + struct udev_device *device_parent; + + log_info("/* %s, device %s */", __func__, syspath); + device = udev_device_new_from_syspath(udev, syspath); + if (!device) + return; + + log_info("looking at parents"); + device_parent = device; + do { + print_device(device_parent); + device_parent = udev_device_get_parent(device_parent); + } while (device_parent != NULL); + + log_info("looking at parents again"); + device_parent = device; + do { + print_device(device_parent); + device_parent = udev_device_get_parent(device_parent); + } while (device_parent != NULL); +} + +static void test_device_devnum(struct udev *udev) { + dev_t devnum = makedev(1, 3); + _cleanup_(udev_device_unrefp) struct udev_device *device; + + log_info("/* %s, device " DEVNUM_FORMAT_STR " */", __func__, DEVNUM_FORMAT_VAL(devnum)); + + device = udev_device_new_from_devnum(udev, 'c', devnum); + if (device) + print_device(device); + else + log_warning_errno(errno, "udev_device_new_from_devnum: %m"); +} + +static void test_device_subsys_name(struct udev *udev, const char *subsys, const char *dev) { + _cleanup_(udev_device_unrefp) struct udev_device *device; + + log_info("looking up device: '%s:%s'", subsys, dev); + device = udev_device_new_from_subsystem_sysname(udev, subsys, dev); + if (!device) + log_warning_errno(errno, "udev_device_new_from_subsystem_sysname: %m"); + else + print_device(device); +} + +static int enumerate_print_list(struct udev_enumerate *enumerate) { + struct udev_list_entry *list_entry; + int count = 0; + + udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) { + struct udev_device *device; + + device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate), + udev_list_entry_get_name(list_entry)); + if (device) { + log_info("device: '%s' (%s)", + udev_device_get_syspath(device), + udev_device_get_subsystem(device)); + udev_device_unref(device); + count++; + } + } + log_info("found %i devices", count); + return count; +} + +static void test_monitor(struct udev *udev) { + _cleanup_(udev_monitor_unrefp) struct udev_monitor *udev_monitor = NULL; + _cleanup_close_ int fd_ep = -EBADF; + int fd_udev; + struct epoll_event ep_udev = { + .events = EPOLLIN, + }, ep_stdin = { + .events = EPOLLIN, + .data.fd = STDIN_FILENO, + }; + + log_info("/* %s */", __func__); + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + assert_se(fd_ep >= 0); + + udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); + assert_se(udev_monitor != NULL); + + fd_udev = udev_monitor_get_fd(udev_monitor); + ep_udev.data.fd = fd_udev; + + assert_se(udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "block", NULL) >= 0); + assert_se(udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "tty", NULL) >= 0); + assert_se(udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", "usb_device") >= 0); + + assert_se(udev_monitor_enable_receiving(udev_monitor) >= 0); + + assert_se(epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) >= 0); + assert_se(epoll_ctl(fd_ep, EPOLL_CTL_ADD, STDIN_FILENO, &ep_stdin) >= 0); + + for (;;) { + int fdcount; + struct epoll_event ev[4]; + struct udev_device *device; + int i; + + printf("waiting for events from udev, press ENTER to exit\n"); + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1); + printf("epoll fd count: %i\n", fdcount); + + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) { + device = udev_monitor_receive_device(udev_monitor); + if (!device) { + printf("no device from socket\n"); + continue; + } + print_device(device); + udev_device_unref(device); + } else if (ev[i].data.fd == STDIN_FILENO && ev[i].events & EPOLLIN) { + printf("exiting loop\n"); + return; + } + } + } +} + +static void test_queue(struct udev *udev) { + struct udev_queue *udev_queue; + bool empty; + + log_info("/* %s */", __func__); + + assert_se(udev_queue = udev_queue_new(udev)); + + empty = udev_queue_get_queue_is_empty(udev_queue); + log_info("queue is %s", empty ? "empty" : "not empty"); + udev_queue_unref(udev_queue); +} + +static int test_enumerate(struct udev *udev, const char *subsystem) { + struct udev_enumerate *udev_enumerate; + int r; + + log_info("/* %s */", __func__); + + log_info("enumerate '%s'", subsystem == NULL ? "<all>" : subsystem); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_add_match_subsystem(udev_enumerate, subsystem); + udev_enumerate_scan_devices(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + + log_info("enumerate 'net' + duplicated scan + null + zero"); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_add_match_subsystem(udev_enumerate, "net"); + udev_enumerate_scan_devices(udev_enumerate); + udev_enumerate_scan_devices(udev_enumerate); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/null"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero"); + udev_enumerate_add_syspath(udev_enumerate, "/sys/class/mem/zero"); + udev_enumerate_scan_devices(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + + log_info("enumerate 'block'"); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_add_match_subsystem(udev_enumerate,"block"); + r = udev_enumerate_add_match_is_initialized(udev_enumerate); + if (r < 0) { + udev_enumerate_unref(udev_enumerate); + return r; + } + udev_enumerate_scan_devices(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + + log_info("enumerate 'not block'"); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_add_nomatch_subsystem(udev_enumerate, "block"); + udev_enumerate_scan_devices(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + + log_info("enumerate 'pci, mem, vc'"); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_add_match_subsystem(udev_enumerate, "pci"); + udev_enumerate_add_match_subsystem(udev_enumerate, "mem"); + udev_enumerate_add_match_subsystem(udev_enumerate, "vc"); + udev_enumerate_scan_devices(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + + log_info("enumerate 'subsystem'"); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_scan_subsystems(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + + log_info("enumerate 'property IF_FS_*=filesystem'"); + udev_enumerate = udev_enumerate_new(udev); + if (!udev_enumerate) + return -1; + udev_enumerate_add_match_property(udev_enumerate, "ID_FS*", "filesystem"); + udev_enumerate_scan_devices(udev_enumerate); + enumerate_print_list(udev_enumerate); + udev_enumerate_unref(udev_enumerate); + return 0; +} + +static void test_hwdb(struct udev *udev, const char *modalias) { + struct udev_hwdb *hwdb; + struct udev_list_entry *entry; + + log_info("/* %s */", __func__); + + hwdb = udev_hwdb_new(udev); + if (!hwdb) + log_warning_errno(errno, "Failed to open hwdb: %m"); + + udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, modalias, 0)) + log_info("'%s'='%s'", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); + + hwdb = udev_hwdb_unref(hwdb); + assert_se(hwdb == NULL); +} + +static void test_list(void) { + _cleanup_(udev_list_freep) struct udev_list *list = NULL; + struct udev_list_entry *e; + + /* empty list */ + assert_se(list = udev_list_new(false)); + assert_se(!udev_list_get_entry(list)); + list = udev_list_free(list); + + /* unique == false */ + assert_se(list = udev_list_new(false)); + assert_se(udev_list_entry_add(list, "aaa", "hoge")); + assert_se(udev_list_entry_add(list, "aaa", "hogehoge")); + assert_se(udev_list_entry_add(list, "bbb", "foo")); + e = udev_list_get_entry(list); + assert_se(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "aaa")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "hoge")); + e = udev_list_entry_get_next(e); + assert_se(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "aaa")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "hogehoge")); + e = udev_list_entry_get_next(e); + assert_se(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "bbb")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "foo")); + assert_se(!udev_list_entry_get_next(e)); + + assert_se(!udev_list_entry_get_by_name(e, "aaa")); + assert_se(!udev_list_entry_get_by_name(e, "bbb")); + assert_se(!udev_list_entry_get_by_name(e, "ccc")); + list = udev_list_free(list); + + /* unique == true */ + assert_se(list = udev_list_new(true)); + assert_se(udev_list_entry_add(list, "aaa", "hoge")); + assert_se(udev_list_entry_add(list, "aaa", "hogehoge")); + assert_se(udev_list_entry_add(list, "bbb", "foo")); + e = udev_list_get_entry(list); + assert_se(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "aaa")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "hogehoge")); + e = udev_list_entry_get_next(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "bbb")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "foo")); + assert_se(!udev_list_entry_get_next(e)); + + e = udev_list_entry_get_by_name(e, "bbb"); + assert_se(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "bbb")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "foo")); + e = udev_list_entry_get_by_name(e, "aaa"); + assert_se(e); + assert_se(streq_ptr(udev_list_entry_get_name(e), "aaa")); + assert_se(streq_ptr(udev_list_entry_get_value(e), "hogehoge")); + assert_se(!udev_list_entry_get_by_name(e, "ccc")); +} + +static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { + static const struct option options[] = { + { "syspath", required_argument, NULL, 'p' }, + { "subsystem", required_argument, NULL, 's' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "monitor", no_argument, NULL, 'm' }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0) + switch (c) { + case 'p': + *syspath = optarg; + break; + + case 's': + *subsystem = optarg; + break; + + case 'd': + log_set_max_level(LOG_DEBUG); + break; + + case 'h': + printf("--debug --syspath= --subsystem= --help\n"); + return 0; + + case 'V': + printf("%s\n", GIT_VERSION); + return 0; + + case 'm': + arg_monitor = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + return 1; +} + +static int run(int argc, char *argv[]) { + _cleanup_(udev_unrefp) struct udev *udev = NULL; + + const char *syspath = "/devices/virtual/mem/null"; + const char *subsystem = NULL; + int r; + + test_setup_logging(LOG_INFO); + + r = parse_args(argc, argv, &syspath, &subsystem); + if (r <= 0) + return r; + + assert_se(udev = udev_new()); + + /* add sys path if needed */ + if (!startswith(syspath, "/sys")) + syspath = strjoina("/sys/", syspath); + + test_device(udev, syspath); + test_device_devnum(udev); + test_device_subsys_name(udev, "block", "sda"); + test_device_subsys_name(udev, "subsystem", "pci"); + test_device_subsys_name(udev, "drivers", "scsi:sd"); + test_device_subsys_name(udev, "module", "printk"); + test_device_parents(udev, syspath); + + test_enumerate(udev, subsystem); + + test_queue(udev); + + test_hwdb(udev, "usb:v0D50p0011*"); + + if (arg_monitor) + test_monitor(udev); + + test_list(); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/libudev/test-udev-device-thread.c b/src/libudev/test-udev-device-thread.c new file mode 100644 index 0000000..c082fdc --- /dev/null +++ b/src/libudev/test-udev-device-thread.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libudev.h" + +#define handle_error_errno(error, msg) \ + ({ \ + errno = abs(error); \ + perror(msg); \ + EXIT_FAILURE; \ + }) + +static void* thread(void *p) { + struct udev_device **d = p; + + *d = udev_device_unref(*d); + + return NULL; +} + +int main(int argc, char *argv[]) { + struct udev_device *loopback; + struct udev_list_entry *entry, *e; + pthread_t t; + int r; + + loopback = udev_device_new_from_syspath(NULL, "/sys/class/net/lo"); + if (!loopback) + return handle_error_errno(errno, "Failed to create loopback device object"); + + entry = udev_device_get_properties_list_entry(loopback); + udev_list_entry_foreach(e, entry) + printf("%s=%s\n", udev_list_entry_get_name(e), udev_list_entry_get_value(e)); + + r = pthread_create(&t, NULL, thread, &loopback); + if (r != 0) + return handle_error_errno(r, "Failed to create thread"); + + r = pthread_join(t, NULL); + if (r != 0) + return handle_error_errno(r, "Failed to wait thread finished"); + + if (loopback) + return handle_error_errno(r, "loopback device is not unref()ed"); + + return 0; +} |