diff options
Diffstat (limited to 'src/libudev')
-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 | 241 | ||||
-rw-r--r-- | src/libudev/libudev-monitor.c | 305 | ||||
-rw-r--r-- | src/libudev/libudev-queue.c | 236 | ||||
-rw-r--r-- | src/libudev/libudev-util.c | 214 | ||||
-rw-r--r-- | src/libudev/libudev-util.h | 24 | ||||
-rw-r--r-- | src/libudev/libudev.c | 154 | ||||
-rw-r--r-- | src/libudev/libudev.h | 191 | ||||
-rw-r--r-- | src/libudev/libudev.pc.in | 19 | ||||
-rw-r--r-- | src/libudev/libudev.sym | 126 | ||||
-rw-r--r-- | src/libudev/meson.build | 29 |
15 files changed, 3051 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..34543a8 --- /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 (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 reliable 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) { + DeviceAction action; + + assert_return_errno(udev_device, NULL, EINVAL); + + if (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..33bd360 --- /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 a 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); + 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..3b2a2cd --- /dev/null +++ b/src/libudev/libudev-list.c @@ -0,0 +1,241 @@ +/* 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) + hashmap_remove(entry->list->unique_entries, entry->name); + else + 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; + int r; + + assert(list); + + 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) { + .list = list, + .name = TAKE_PTR(name), + .value = TAKE_PTR(value), + }; + + if (list->unique) { + r = hashmap_ensure_allocated(&list->unique_entries, &string_hash_ops); + if (r < 0) + return NULL; + + udev_list_entry_free(hashmap_get(list->unique_entries, entry->name)); + + r = hashmap_put(list->unique_entries, entry->name, entry); + if (r < 0) + return NULL; + + list->uptodate = false; + } else + LIST_APPEND(entries, list->entries, entry); + + return TAKE_PTR(entry); +} + +void udev_list_cleanup(struct udev_list *list) { + struct udev_list_entry *i, *n; + + if (!list) + return; + + if (list->unique) { + hashmap_clear_with_destructor(list->unique_entries, udev_list_entry_free); + list->uptodate = false; + } else + LIST_FOREACH_SAFE(entries, i, n, 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; + size_t j; + + 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 (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..a93adbd --- /dev/null +++ b/src/libudev/libudev-monitor.c @@ -0,0 +1,305 @@ +/* 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 < 0) { + if (IN_SET(r, -EINTR, -EAGAIN)) + continue; + + 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) { + assert_return(udev_monitor, -EINVAL); + + return sd_device_monitor_filter_add_match_subsystem_devtype(udev_monitor->monitor, subsystem, devtype); +} + +/** + * 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) { + assert_return(udev_monitor, -EINVAL); + + return sd_device_monitor_filter_add_match_tag(udev_monitor->monitor, tag); +} + +/** + * 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..01b237f --- /dev/null +++ b/src/libudev/libudev-queue.c @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk> +***/ + +#include <errno.h> +#include <stddef.h> +#include <stdlib.h> +#include <sys/inotify.h> +#include <unistd.h> + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-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 access("/run/udev/queue", F_OK) < 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, it just returns the result of + * 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_get_queue_is_empty(udev_queue); +} + +/** + * udev_queue_get_seqnum_is_finished: + * @udev_queue: udev queue context + * @seqnum: sequence number + * + * This function is deprecated, it just returns the result of + * 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_get_queue_is_empty(udev_queue); +} + +/** + * 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) { + _cleanup_close_ int fd = -1; + + assert_return(udev_queue, -EINVAL); + + if (udev_queue->fd >= 0) + return udev_queue->fd; + + fd = inotify_init1(IN_CLOEXEC); + if (fd < 0) + return -errno; + + if (inotify_add_watch(fd, "/run/udev" , IN_DELETE) < 0) + return -errno; + + udev_queue->fd = TAKE_FD(fd); + return udev_queue->fd; +} + +/** + * 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..bbb2879 --- /dev/null +++ b/src/libudev/libudev-util.c @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <ctype.h> +#include <errno.h> + +#include "sd-device.h" + +#include "device-nodes.h" +#include "libudev-util.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "utf8.h" + +/** + * SECTION:libudev-util + * @short_description: utils + * + * Utilities useful when dealing with devices and device node names. + */ + +/* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */ +int util_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) { + char temp[UTIL_PATH_SIZE], *subsys, *sysname, *attr; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *val; + int r; + + if (string[0] != '[') + return -EINVAL; + + strscpy(temp, sizeof(temp), string); + + subsys = &temp[1]; + + sysname = strchr(subsys, '/'); + if (!sysname) + return -EINVAL; + sysname[0] = '\0'; + sysname = &sysname[1]; + + attr = strchr(sysname, ']'); + if (!attr) + return -EINVAL; + attr[0] = '\0'; + attr = &attr[1]; + if (attr[0] == '/') + attr = &attr[1]; + if (attr[0] == '\0') + attr = NULL; + + if (read_value && !attr) + return -EINVAL; + + r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname); + if (r < 0) + return r; + + if (read_value) { + r = sd_device_get_sysattr_value(dev, attr, &val); + if (r < 0 && r != -ENOENT) + return r; + if (r == -ENOENT) + result[0] = '\0'; + else + strscpy(result, maxsize, val); + log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); + } else { + r = sd_device_get_syspath(dev, &val); + if (r < 0) + return r; + + strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL); + log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result); + } + return 0; +} + +size_t util_path_encode(const char *src, char *dest, size_t size) { + size_t i, j; + + assert(src); + assert(dest); + + for (i = 0, j = 0; src[i] != '\0'; i++) { + if (src[i] == '/') { + if (j+4 >= size) { + j = 0; + break; + } + memcpy(&dest[j], "\\x2f", 4); + j += 4; + } else if (src[i] == '\\') { + if (j+4 >= size) { + j = 0; + break; + } + memcpy(&dest[j], "\\x5c", 4); + j += 4; + } else { + if (j+1 >= size) { + j = 0; + break; + } + dest[j] = src[i]; + j++; + } + } + dest[j] = '\0'; + return j; +} + +/* + * Copy from 'str' to 'to', while removing all leading and trailing whitespace, + * and replacing each run of consecutive whitespace with a single underscore. + * The chars from 'str' are copied up to the \0 at the end of the string, or + * at most 'len' chars. This appends \0 to 'to', at the end of the copied + * characters. + * + * If 'len' chars are copied into 'to', the final \0 is placed at len+1 + * (i.e. 'to[len] = \0'), so the 'to' buffer must have at least len+1 + * chars available. + * + * Note this may be called with 'str' == 'to', i.e. to replace whitespace + * in-place in a buffer. This function can handle that situation. + * + * Note that only 'len' characters are read from 'str'. + */ +size_t util_replace_whitespace(const char *str, char *to, size_t len) { + bool is_space = false; + size_t i, j; + + assert(str); + assert(to); + + i = strspn(str, WHITESPACE); + + for (j = 0; j < len && i < len && str[i] != '\0'; i++) { + if (isspace(str[i])) { + is_space = true; + continue; + } + + if (is_space) { + if (j + 1 >= len) + break; + + to[j++] = '_'; + is_space = false; + } + to[j++] = str[i]; + } + + to[j] = '\0'; + return j; +} + +/* allow chars in allow list, plain ascii, hex-escaping and valid utf8 */ +size_t util_replace_chars(char *str, const char *allow) { + size_t i = 0, replaced = 0; + + assert(str); + + while (str[i] != '\0') { + int len; + + if (allow_listed_char_for_devnode(str[i], allow)) { + i++; + continue; + } + + /* accept hex encoding */ + if (str[i] == '\\' && str[i+1] == 'x') { + i += 2; + continue; + } + + /* accept valid utf8 */ + len = utf8_encoded_valid_unichar(str + i, (size_t) -1); + if (len > 1) { + i += len; + continue; + } + + /* if space is allowed, replace whitespace with ordinary space */ + if (isspace(str[i]) && allow && strchr(allow, ' ')) { + str[i] = ' '; + i++; + replaced++; + continue; + } + + /* everything else is replaced with '_' */ + str[i] = '_'; + i++; + replaced++; + } + return replaced; +} + +/** + * 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..15e6214 --- /dev/null +++ b/src/libudev/libudev-util.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libudev.h" + +#include "macro.h" + +/* libudev-util.c */ +#define UTIL_PATH_SIZE 1024 +#define UTIL_NAME_SIZE 512 +#define UTIL_LINE_SIZE 16384 +#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," +size_t util_path_encode(const char *src, char *dest, size_t size); +size_t util_replace_whitespace(const char *str, char *to, size_t len); +size_t util_replace_chars(char *str, const char *white); +int util_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value); + +/* 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..55036de --- /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..b657b7d --- /dev/null +++ b/src/libudev/libudev.pc.in @@ -0,0 +1,19 @@ +# 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=@exec_prefix@ +libdir=@rootlibdir@ +includedir=@includedir@ + +Name: libudev +Description: Library to access udev device information +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -ludev +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..3bd00ff --- /dev/null +++ b/src/libudev/meson.build @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libudev_sources = files(''' + libudev.c + 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 +'''.split()) + +############################################################ + +libudev_sym = files('libudev.sym') +libudev_sym_path = meson.current_source_dir() + '/libudev.sym' + +install_headers('libudev.h') +libudev_h_path = '@0@/libudev.h'.format(meson.current_source_dir()) + +configure_file( + input : 'libudev.pc.in', + output : 'libudev.pc', + configuration : substs, + install_dir : pkgconfiglibdir == 'no' ? '' : pkgconfiglibdir) |