diff options
author | Fernando Fernandez Mancera <ffmancera@riseup.net> | 2024-03-06 11:03:38 +0100 |
---|---|---|
committer | Fernando Fernandez Mancera <ffmancera@riseup.net> | 2024-03-06 11:03:38 +0100 |
commit | 32b20fbc8ceab25b29f040bac22129df5a28793e (patch) | |
tree | f21db7048ee03cc0c512242bc99a9ccbde3679e5 | |
parent | d3086e8c79bd61432f3f181841eb72ae1cd3bbe9 (diff) | |
parent | e7ad3d93ab74f0d918c1021622735abd2479bcc4 (diff) |
merge: branch 'ff/fix_systemd'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1877
17 files changed, 4163 insertions, 5 deletions
diff --git a/Makefile.am b/Makefile.am index 2b6eab2f97..7eb2b9f865 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2371,7 +2371,10 @@ src_libnm_systemd_shared_libnm_systemd_shared_la_SOURCES = \ src/libnm-systemd-shared/src/basic/btrfs.c \ src/libnm-systemd-shared/src/basic/btrfs.h \ src/libnm-systemd-shared/src/basic/cgroup-util.h \ + src/libnm-systemd-shared/src/basic/chase.h \ src/libnm-systemd-shared/src/basic/constants.h \ + src/libnm-systemd-shared/src/basic/devnum-util.c \ + src/libnm-systemd-shared/src/basic/devnum-util.h \ src/libnm-systemd-shared/src/basic/dns-def.h \ src/libnm-systemd-shared/src/basic/env-file.c \ src/libnm-systemd-shared/src/basic/env-file.h \ @@ -2538,7 +2541,11 @@ src_libnm_systemd_core_libnm_systemd_core_la_SOURCES = \ src/libnm-systemd-core/src/libsystemd-network/network-common.h \ src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c \ src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c \ + src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h \ + src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c \ + src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h \ src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h \ + src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c \ src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h \ src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c \ src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h \ diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build index fbac8c0d44..15e1c8a62b 100644 --- a/src/libnm-systemd-core/meson.build +++ b/src/libnm-systemd-core/meson.build @@ -10,6 +10,8 @@ libnm_systemd_core = static_library( 'src/libsystemd-network/sd-dhcp-duid.c', 'src/libsystemd-network/sd-dhcp6-client.c', 'src/libsystemd-network/sd-dhcp6-lease.c', + 'src/libsystemd/sd-device/device-private.c', + 'src/libsystemd/sd-device/sd-device.c', 'src/libsystemd/sd-event/event-util.c', 'src/libsystemd/sd-event/sd-event.c', 'src/libsystemd/sd-id128/id128-util.c', diff --git a/src/libnm-systemd-core/sd-adapt-core/netif-util.c b/src/libnm-systemd-core/sd-adapt-core/netif-util.c index b10bdf309b..cfb361a387 100644 --- a/src/libnm-systemd-core/sd-adapt-core/netif-util.c +++ b/src/libnm-systemd-core/sd-adapt-core/netif-util.c @@ -3,7 +3,9 @@ #include "nm-sd-adapt-core.h" #include <linux/if.h> +#ifdef __GLIBC__ #include <linux/if_arp.h> +#endif #include "arphrd-util.h" #include "device-util.h" diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c index 943630ab8d..7c20116409 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c @@ -7,7 +7,9 @@ #include <errno.h> #include <sys/ioctl.h> +#ifdef __GLIBC__ #include <linux/if_arp.h> +#endif #include <linux/if_infiniband.h> #include "sd-dhcp6-client.h" diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h new file mode 100644 index 0000000000..a465eb25fd --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-device.h" + +#include "device-private.h" +#include "hashmap.h" +#include "set.h" +#include "time-util.h" + +#define LATEST_UDEV_DATABASE_VERSION 1 + +struct sd_device { + unsigned n_ref; + + /* The database version indicates the supported features by the udev database. + * This is saved and parsed in V field. + * + * 0: None of the following features are supported (systemd version <= 246). + * 1: The current tags (Q) and the database version (V) features are implemented (>= 247). + */ + unsigned database_version; + + sd_device *parent; + + OrderedHashmap *properties; + Iterator properties_iterator; + uint64_t properties_generation; /* changes whenever the properties are changed */ + uint64_t properties_iterator_generation; /* generation when iteration was started */ + + /* the subset of the properties that should be written to the db */ + OrderedHashmap *properties_db; + + Hashmap *sysattr_values; /* cached sysattr values */ + + Set *sysattrs; /* names of sysattrs */ + Iterator sysattrs_iterator; + + Set *all_tags, *current_tags; + Iterator all_tags_iterator, current_tags_iterator; + uint64_t all_tags_iterator_generation, current_tags_iterator_generation; /* generation when iteration was started */ + uint64_t tags_generation; /* changes whenever the tags are changed */ + + Set *devlinks; + Iterator devlinks_iterator; + uint64_t devlinks_generation; /* changes whenever the devlinks are changed */ + uint64_t devlinks_iterator_generation; /* generation when iteration was started */ + int devlink_priority; + + Hashmap *children; + Iterator children_iterator; + bool children_enumerated; + + int ifindex; + char *devtype; + char *devname; + dev_t devnum; + + char **properties_strv; /* the properties hashmap as a strv */ + char *properties_nulstr; /* the same as a nulstr */ + size_t properties_nulstr_len; + + char *syspath; + const char *devpath; + const char *sysnum; + char *sysname; + + char *subsystem; + char *driver_subsystem; /* only set for the 'drivers' subsystem */ + char *driver; + + char *device_id; + + usec_t usec_initialized; + + mode_t devmode; + uid_t devuid; + gid_t devgid; + + uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach */ + + /* only set when device is passed through netlink */ + sd_device_action_t action; + uint64_t seqnum; + + bool parent_set:1; /* no need to try to reload parent */ + bool sysattrs_read:1; /* don't try to re-read sysattrs once read */ + bool property_tags_outdated:1; /* need to update TAGS= or CURRENT_TAGS= property */ + bool property_devlinks_outdated:1; /* need to update DEVLINKS= property */ + bool properties_buf_outdated:1; /* need to reread hashmap */ + bool subsystem_set:1; /* don't reread subsystem */ + bool driver_set:1; /* don't reread driver */ + bool uevent_loaded:1; /* don't reread uevent */ + bool db_loaded; /* don't reread db */ + + bool is_initialized:1; + bool sealed:1; /* don't read more information from uevent/db */ + bool db_persist:1; /* don't clean up the db when switching from initrd to real root */ +}; + +int device_new_aux(sd_device **ret); +int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db); +static inline int device_add_property_internal(sd_device *device, const char *key, const char *value) { + return device_add_property_aux(device, key, value, false); +} + +int device_set_syspath(sd_device *device, const char *_syspath, bool verify); +int device_set_ifindex(sd_device *device, const char *ifindex); +int device_set_devmode(sd_device *device, const char *devmode); +int device_set_devname(sd_device *device, const char *devname); +int device_set_devtype(sd_device *device, const char *devtype); +int device_set_devnum(sd_device *device, const char *major, const char *minor); +int device_set_subsystem(sd_device *device, const char *subsystem); +int device_set_diskseq(sd_device *device, const char *str); +int device_set_drivers_subsystem(sd_device *device); +int device_set_driver(sd_device *device, const char *driver); +int device_set_usec_initialized(sd_device *device, usec_t when); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c new file mode 100644 index 0000000000..eef3d61841 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c @@ -0,0 +1,962 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <ctype.h> +#include <net/if.h> +#include <sys/types.h> + +#include "sd-device.h" + +#include "alloc-util.h" +#include "device-internal.h" +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "macro.h" +#include "mkdir.h" +#include "nulstr-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "tmpfile-util.h" +#include "user-util.h" + +#if 0 /* NM_IGNORED */ +int device_add_property(sd_device *device, const char *key, const char *value) { + int r; + + assert(device); + assert(key); + + r = device_add_property_aux(device, key, value, false); + if (r < 0) + return r; + + if (key[0] != '.') { + r = device_add_property_aux(device, key, value, true); + if (r < 0) + return r; + } + + return 0; +} + +int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) { + _cleanup_free_ char *value = NULL; + va_list ap; + int r; + + assert(device); + assert(key); + + if (!format) + return device_add_property(device, key, NULL); + + va_start(ap, format); + r = vasprintf(&value, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return device_add_property(device, key, value); +} + +void device_set_devlink_priority(sd_device *device, int priority) { + assert(device); + + device->devlink_priority = priority; +} + +void device_set_is_initialized(sd_device *device) { + assert(device); + + device->is_initialized = true; +} + +int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) { + usec_t when; + + assert(device); + + if (device_old && device_old->usec_initialized > 0) + when = device_old->usec_initialized; + else + when = now(CLOCK_MONOTONIC); + + return device_set_usec_initialized(device, when); +} + +uint64_t device_get_properties_generation(sd_device *device) { + assert(device); + + return device->properties_generation; +} + +uint64_t device_get_tags_generation(sd_device *device) { + assert(device); + + return device->tags_generation; +} + +uint64_t device_get_devlinks_generation(sd_device *device) { + assert(device); + + return device->devlinks_generation; +} + +int device_get_devnode_mode(sd_device *device, mode_t *ret) { + int r; + + assert(device); + + r = device_read_db(device); + if (r < 0) + return r; + + if (device->devmode == MODE_INVALID) + return -ENOENT; + + if (ret) + *ret = device->devmode; + + return 0; +} + +int device_get_devnode_uid(sd_device *device, uid_t *ret) { + int r; + + assert(device); + + r = device_read_db(device); + if (r < 0) + return r; + + if (device->devuid == UID_INVALID) + return -ENOENT; + + if (ret) + *ret = device->devuid; + + return 0; +} + +static int device_set_devuid(sd_device *device, const char *uid) { + uid_t u; + int r; + + assert(device); + assert(uid); + + r = parse_uid(uid, &u); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVUID", uid); + if (r < 0) + return r; + + device->devuid = u; + + return 0; +} + +int device_get_devnode_gid(sd_device *device, gid_t *ret) { + int r; + + assert(device); + + r = device_read_db(device); + if (r < 0) + return r; + + if (device->devgid == GID_INVALID) + return -ENOENT; + + if (ret) + *ret = device->devgid; + + return 0; +} + +static int device_set_devgid(sd_device *device, const char *gid) { + gid_t g; + int r; + + assert(device); + assert(gid); + + r = parse_gid(gid, &g); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVGID", gid); + if (r < 0) + return r; + + device->devgid = g; + + return 0; +} + +int device_set_action(sd_device *device, sd_device_action_t a) { + int r; + + assert(device); + assert(a >= 0 && a < _SD_DEVICE_ACTION_MAX); + + r = device_add_property_internal(device, "ACTION", device_action_to_string(a)); + if (r < 0) + return r; + + device->action = a; + + return 0; +} + +static int device_set_action_from_string(sd_device *device, const char *action) { + sd_device_action_t a; + + assert(device); + assert(action); + + a = device_action_from_string(action); + if (a < 0) + return a; + + return device_set_action(device, a); +} + +static int device_set_seqnum(sd_device *device, const char *str) { + uint64_t seqnum; + int r; + + assert(device); + assert(str); + + r = safe_atou64(str, &seqnum); + if (r < 0) + return r; + if (seqnum == 0) + return -EINVAL; + + r = device_add_property_internal(device, "SEQNUM", str); + if (r < 0) + return r; + + device->seqnum = seqnum; + + return 0; +} + +static int device_amend(sd_device *device, const char *key, const char *value) { + int r; + + assert(device); + assert(key); + assert(value); + + if (streq(key, "DEVPATH")) { + char *path; + + path = strjoina("/sys", value); + + /* the caller must verify or trust this data (e.g., if it comes from the kernel) */ + r = device_set_syspath(device, path, false); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set syspath to '%s': %m", path); + } else if (streq(key, "SUBSYSTEM")) { + r = device_set_subsystem(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem to '%s': %m", value); + } else if (streq(key, "DEVTYPE")) { + r = device_set_devtype(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devtype to '%s': %m", value); + } else if (streq(key, "DEVNAME")) { + r = device_set_devname(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devname to '%s': %m", value); + } else if (streq(key, "USEC_INITIALIZED")) { + usec_t t; + + r = safe_atou64(value, &t); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to parse timestamp '%s': %m", value); + + r = device_set_usec_initialized(device, t); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set usec-initialized to '%s': %m", value); + } else if (streq(key, "DRIVER")) { + r = device_set_driver(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set driver to '%s': %m", value); + } else if (streq(key, "IFINDEX")) { + r = device_set_ifindex(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set ifindex to '%s': %m", value); + } else if (streq(key, "DEVMODE")) { + r = device_set_devmode(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devmode to '%s': %m", value); + } else if (streq(key, "DEVUID")) { + r = device_set_devuid(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devuid to '%s': %m", value); + } else if (streq(key, "DEVGID")) { + r = device_set_devgid(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devgid to '%s': %m", value); + } else if (streq(key, "ACTION")) { + r = device_set_action_from_string(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set action to '%s': %m", value); + } else if (streq(key, "SEQNUM")) { + r = device_set_seqnum(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set SEQNUM to '%s': %m", value); + } else if (streq(key, "DISKSEQ")) { + r = device_set_diskseq(device, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set DISKSEQ to '%s': %m", value); + } else if (streq(key, "DEVLINKS")) { + for (const char *p = value;;) { + _cleanup_free_ char *word = NULL; + + /* udev rules may set escaped strings, and sd-device does not modify the input + * strings. So, it is also necessary to keep the strings received through + * sd-device-monitor. */ + r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + r = device_add_devlink(device, word); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to add devlink '%s': %m", word); + } + } else if (STR_IN_SET(key, "TAGS", "CURRENT_TAGS")) { + for (const char *p = value;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + break; + if (isempty(word)) + continue; + + r = device_add_tag(device, word, streq(key, "CURRENT_TAGS")); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to add tag '%s': %m", word); + } + } else if (streq(key, "UDEV_DATABASE_VERSION")) { + r = safe_atou(value, &device->database_version); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to parse udev database version '%s': %m", value); + } else { + r = device_add_property_internal(device, key, value); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to add property '%s=%s': %m", key, value); + } + + return 0; +} + +static int device_append( + sd_device *device, + char *key, + const char **_major, + const char **_minor) { + + const char *major = NULL, *minor = NULL; + char *value; + int r; + + assert(device); + assert(key); + assert(_major); + assert(_minor); + + value = strchr(key, '='); + if (!value) + return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), + "sd-device: Not a key-value pair: '%s'", key); + + *value = '\0'; + + value++; + + if (streq(key, "MAJOR")) + major = value; + else if (streq(key, "MINOR")) + minor = value; + else { + r = device_amend(device, key, value); + if (r < 0) + return r; + } + + if (major) + *_major = major; + + if (minor) + *_minor = minor; + + return 0; +} + +void device_seal(sd_device *device) { + assert(device); + + device->sealed = true; +} + +static int device_verify(sd_device *device) { + int r; + + assert(device); + + if (!device->devpath || !device->subsystem || device->action < 0 || device->seqnum == 0) + return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), + "sd-device: Device created from strv or nulstr lacks devpath, subsystem, action or seqnum."); + + if (streq(device->subsystem, "drivers")) { + r = device_set_drivers_subsystem(device); + if (r < 0) + return r; + } + + device->sealed = true; + + return 0; +} + +int device_new_from_strv(sd_device **ret, char **strv) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *major = NULL, *minor = NULL; + int r; + + assert(ret); + assert(strv); + + r = device_new_aux(&device); + if (r < 0) + return r; + + STRV_FOREACH(key, strv) { + r = device_append(device, *key, &major, &minor); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device); + if (r < 0) + return r; + + *ret = TAKE_PTR(device); + + return 0; +} + +int device_new_from_nulstr(sd_device **ret, char *nulstr, size_t len) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *major = NULL, *minor = NULL; + int r; + + assert(ret); + assert(nulstr); + assert(len); + + r = device_new_aux(&device); + if (r < 0) + return r; + + for (size_t i = 0; i < len; ) { + char *key; + const char *end; + + key = nulstr + i; + end = memchr(key, '\0', len - i); + if (!end) + return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), + "sd-device: Failed to parse nulstr"); + + i += end - key + 1; + + /* netlink messages for some devices contain an unwanted newline at the end of value. + * Let's drop the newline and remaining characters after the newline. */ + truncate_nl(key); + + r = device_append(device, key, &major, &minor); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device); + if (r < 0) + return r; + + *ret = TAKE_PTR(device); + + return 0; +} + +static int device_update_properties_bufs(sd_device *device) { + _cleanup_free_ char **buf_strv = NULL, *buf_nulstr = NULL; + size_t nulstr_len = 0, num = 0; + + assert(device); + + if (!device->properties_buf_outdated) + return 0; + + /* append udev database version */ + buf_nulstr = newdup(char, "UDEV_DATABASE_VERSION=" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\0", + STRLEN("UDEV_DATABASE_VERSION=" STRINGIFY(LATEST_UDEV_DATABASE_VERSION)) + 2); + if (!buf_nulstr) + return -ENOMEM; + + nulstr_len += STRLEN("UDEV_DATABASE_VERSION=" STRINGIFY(LATEST_UDEV_DATABASE_VERSION)) + 1; + num++; + + FOREACH_DEVICE_PROPERTY(device, prop, val) { + size_t len = 0; + + len = strlen(prop) + 1 + strlen(val); + + buf_nulstr = GREEDY_REALLOC0(buf_nulstr, nulstr_len + len + 2); + if (!buf_nulstr) + return -ENOMEM; + + strscpyl(buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL); + nulstr_len += len + 1; + num++; + } + + /* build buf_strv from buf_nulstr */ + buf_strv = new0(char*, num + 1); + if (!buf_strv) + return -ENOMEM; + + size_t i = 0; + NULSTR_FOREACH(p, buf_nulstr) + buf_strv[i++] = p; + assert(i == num); + + free_and_replace(device->properties_nulstr, buf_nulstr); + device->properties_nulstr_len = nulstr_len; + free_and_replace(device->properties_strv, buf_strv); + + device->properties_buf_outdated = false; + return 0; +} + +int device_get_properties_nulstr(sd_device *device, const char **ret_nulstr, size_t *ret_len) { + int r; + + assert(device); + + r = device_update_properties_bufs(device); + if (r < 0) + return r; + + if (ret_nulstr) + *ret_nulstr = device->properties_nulstr; + if (ret_len) + *ret_len = device->properties_nulstr_len; + + return 0; +} + +int device_get_properties_strv(sd_device *device, char ***ret) { + int r; + + assert(device); + + r = device_update_properties_bufs(device); + if (r < 0) + return r; + + if (ret) + *ret = device->properties_strv; + + return 0; +} + +int device_get_devlink_priority(sd_device *device, int *ret) { + int r; + + assert(device); + + r = device_read_db(device); + if (r < 0) + return r; + + if (ret) + *ret = device->devlink_priority; + + return 0; +} + +int device_clone_with_db(sd_device *device, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *dest = NULL; + const char *key, *val; + int r; + + assert(device); + assert(ret); + + /* The device may be already removed. Let's copy minimal set of information that was obtained through + * netlink socket. */ + + r = device_new_aux(&dest); + if (r < 0) + return r; + + /* Seal device to prevent reading the uevent file, as the device may have been already removed. */ + dest->sealed = true; + + /* Copy syspath, then also devname, sysname or sysnum can be obtained. */ + r = device_set_syspath(dest, device->syspath, false); + if (r < 0) + return r; + + /* Copy other information stored in database. Here, do not use FOREACH_DEVICE_PROPERTY() and + * sd_device_get_property_value(), as they calls device_properties_prepare() -> + * device_read_uevent_file(), but as commented in the above, the device may be already removed and + * reading uevent file may fail. */ + ORDERED_HASHMAP_FOREACH_KEY(val, key, device->properties) { + if (streq(key, "MINOR")) + continue; + + if (streq(key, "MAJOR")) { + const char *minor = NULL; + + minor = ordered_hashmap_get(device->properties, "MINOR"); + r = device_set_devnum(dest, val, minor); + } else + r = device_amend(dest, key, val); + if (r < 0) + return r; + + if (streq(key, "SUBSYSTEM") && streq(val, "drivers")) { + r = free_and_strdup(&dest->driver_subsystem, device->driver_subsystem); + if (r < 0) + return r; + } + } + + /* Finally, read the udev database. */ + r = device_read_db_internal(dest, /* force = */ true); + if (r < 0) + return r; + + *ret = TAKE_PTR(dest); + return 0; +} + +void device_cleanup_tags(sd_device *device) { + assert(device); + + device->all_tags = set_free_free(device->all_tags); + device->current_tags = set_free_free(device->current_tags); + device->property_tags_outdated = true; + device->tags_generation++; +} + +void device_cleanup_devlinks(sd_device *device) { + assert(device); + + set_free_free(device->devlinks); + device->devlinks = NULL; + device->property_devlinks_outdated = true; + device->devlinks_generation++; +} + +void device_remove_tag(sd_device *device, const char *tag) { + assert(device); + assert(tag); + + free(set_remove(device->current_tags, tag)); + device->property_tags_outdated = true; + device->tags_generation++; +} + +static int device_tag(sd_device *device, const char *tag, bool add) { + const char *id; + char *path; + int r; + + assert(device); + assert(tag); + + r = device_get_device_id(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/tags/", tag, "/", id); + + if (add) + return touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444); + + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; +} + +int device_tag_index(sd_device *device, sd_device *device_old, bool add) { + int r = 0, k; + + if (add && device_old) + /* delete possible left-over tags */ + FOREACH_DEVICE_TAG(device_old, tag) + if (!sd_device_has_tag(device, tag)) { + k = device_tag(device_old, tag, false); + if (r >= 0 && k < 0) + r = k; + } + + FOREACH_DEVICE_TAG(device, tag) { + k = device_tag(device, tag, add); + if (r >= 0 && k < 0) + r = k; + } + + return r; +} + +static bool device_has_info(sd_device *device) { + assert(device); + + if (!set_isempty(device->devlinks)) + return true; + + if (device->devlink_priority != 0) + return true; + + if (!ordered_hashmap_isempty(device->properties_db)) + return true; + + if (!set_isempty(device->all_tags)) + return true; + + if (!set_isempty(device->current_tags)) + return true; + + return false; +} + +bool device_should_have_db(sd_device *device) { + assert(device); + + if (device_has_info(device)) + return true; + + if (major(device->devnum) != 0) + return true; + + if (device->ifindex != 0) + return true; + + return false; +} + +void device_set_db_persist(sd_device *device) { + assert(device); + + device->db_persist = true; +} +#endif /* NM_IGNORED */ + +static int device_get_db_path(sd_device *device, char **ret) { + const char *id; + char *path; + int r; + + assert(device); + assert(ret); + + r = device_get_device_id(device, &id); + if (r < 0) + return r; + + path = path_join("/run/udev/data/", id); + if (!path) + return -ENOMEM; + + *ret = path; + return 0; +} + +#if 0 /* NM_IGNORED */ +int device_has_db(sd_device *device) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + return access(path, F_OK) >= 0; +} + +int device_update_db(sd_device *device) { + _cleanup_(unlink_and_freep) char *path = NULL, *path_tmp = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(device); + + /* do not store anything for otherwise empty devices */ + if (!device_should_have_db(device)) + return device_delete_db(device); + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + /* write a database file */ + r = mkdir_parents(path, 0755); + if (r < 0) + return log_device_debug_errno(device, r, + "sd-device: Failed to create parent directories of '%s': %m", + path); + + r = fopen_temporary(path, &f, &path_tmp); + if (r < 0) + return log_device_debug_errno(device, r, + "sd-device: Failed to create temporary file for database file '%s': %m", + path); + + /* set 'sticky' bit to indicate that we should not clean the database when we transition from initrd + * to the real root */ + if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0) + return log_device_debug_errno(device, errno, + "sd-device: Failed to chmod temporary database file '%s': %m", + path_tmp); + + if (device_has_info(device)) { + const char *property, *value, *ct; + + if (major(device->devnum) > 0) { + FOREACH_DEVICE_DEVLINK(device, devlink) + fprintf(f, "S:%s\n", devlink + STRLEN("/dev/")); + + if (device->devlink_priority != 0) + fprintf(f, "L:%i\n", device->devlink_priority); + } + + if (device->usec_initialized > 0) + fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized); + + ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db) + fprintf(f, "E:%s=%s\n", property, value); + + FOREACH_DEVICE_TAG(device, tag) + fprintf(f, "G:%s\n", tag); /* Any tag */ + + SET_FOREACH(ct, device->current_tags) + fprintf(f, "Q:%s\n", ct); /* Current tag */ + + /* Always write the latest database version here, instead of the value stored in + * device->database_version, as which may be 0. */ + fputs("V:" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\n", f); + } + + r = fflush_and_check(f); + if (r < 0) + return log_device_debug_errno(device, r, + "sd-device: Failed to flush temporary database file '%s': %m", + path_tmp); + + if (rename(path_tmp, path) < 0) + return log_device_debug_errno(device, errno, + "sd-device: Failed to rename temporary database file '%s' to '%s': %m", + path_tmp, path); + + log_device_debug(device, "sd-device: Created database file '%s' for '%s'.", path, device->devpath); + + path_tmp = mfree(path_tmp); + path = mfree(path); + + return 0; +} + +int device_delete_db(sd_device *device) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; +} +#endif /* NM_IGNORED */ + +int device_read_db_internal(sd_device *device, bool force) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + if (device->db_loaded || (!force && device->sealed)) + return 0; + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + return device_read_db_internal_filename(device, path); +} + +#if 0 /* NM_IGNORED */ +static const char* const device_action_table[_SD_DEVICE_ACTION_MAX] = { + [SD_DEVICE_ADD] = "add", + [SD_DEVICE_REMOVE] = "remove", + [SD_DEVICE_CHANGE] = "change", + [SD_DEVICE_MOVE] = "move", + [SD_DEVICE_ONLINE] = "online", + [SD_DEVICE_OFFLINE] = "offline", + [SD_DEVICE_BIND] = "bind", + [SD_DEVICE_UNBIND] = "unbind", +}; + +DEFINE_STRING_TABLE_LOOKUP(device_action, sd_device_action_t); + +void dump_device_action_table(void) { + DUMP_STRING_TABLE(device_action, sd_device_action_t, _SD_DEVICE_ACTION_MAX); +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h new file mode 100644 index 0000000000..e1f3b6e80c --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <dirent.h> +#include <inttypes.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "sd-device.h" + +#include "macro.h" + +int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum); +int device_new_from_nulstr(sd_device **ret, char *nulstr, size_t len); +int device_new_from_strv(sd_device **ret, char **strv); + +int device_opendir(sd_device *device, const char *subdir, DIR **ret); + +int device_get_property_bool(sd_device *device, const char *key); +int device_get_property_int(sd_device *device, const char *key, int *ret); +int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); +int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value); +int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value); +int device_get_sysattr_bool(sd_device *device, const char *sysattr); +int device_get_device_id(sd_device *device, const char **ret); +int device_get_devlink_priority(sd_device *device, int *ret); +int device_get_devnode_mode(sd_device *device, mode_t *ret); +int device_get_devnode_uid(sd_device *device, uid_t *ret); +int device_get_devnode_gid(sd_device *device, gid_t *ret); + +void device_clear_sysattr_cache(sd_device *device); +int device_cache_sysattr_value(sd_device *device, const char *key, char *value); +int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value); + +void device_seal(sd_device *device); +void device_set_is_initialized(sd_device *device); +void device_set_db_persist(sd_device *device); +void device_set_devlink_priority(sd_device *device, int priority); +int device_ensure_usec_initialized(sd_device *device, sd_device *device_old); +int device_add_devlink(sd_device *device, const char *devlink); +int device_remove_devlink(sd_device *device, const char *devlink); +bool device_has_devlink(sd_device *device, const char *devlink); +int device_add_property(sd_device *device, const char *property, const char *value); +int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4); +int device_add_tag(sd_device *device, const char *tag, bool both); +void device_remove_tag(sd_device *device, const char *tag); +void device_cleanup_tags(sd_device *device); +void device_cleanup_devlinks(sd_device *device); + +uint64_t device_get_properties_generation(sd_device *device); +uint64_t device_get_tags_generation(sd_device *device); +uint64_t device_get_devlinks_generation(sd_device *device); + +int device_properties_prepare(sd_device *device); +int device_get_properties_nulstr(sd_device *device, const char **ret_nulstr, size_t *ret_len); +int device_get_properties_strv(sd_device *device, char ***ret); + +int device_clone_with_db(sd_device *device, sd_device **ret); + +int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +bool device_should_have_db(sd_device *device); +int device_has_db(sd_device *device); +int device_update_db(sd_device *device); +int device_delete_db(sd_device *device); +int device_read_db_internal_filename(sd_device *device, const char *filename); /* For fuzzer */ +int device_read_db_internal(sd_device *device, bool force); +static inline int device_read_db(sd_device *device) { + return device_read_db_internal(device, false); +} + +int device_read_uevent_file(sd_device *device); + +int device_set_action(sd_device *device, sd_device_action_t a); +sd_device_action_t device_action_from_string(const char *s) _pure_; +const char *device_action_to_string(sd_device_action_t a) _const_; +void dump_device_action_table(void); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c b/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c new file mode 100644 index 0000000000..2e7cdcb3d5 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c @@ -0,0 +1,2724 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <ctype.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/types.h> + +#include "sd-device.h" + +#include "alloc-util.h" +#include "chase.h" +#include "device-internal.h" +#include "device-private.h" +#include "device-util.h" +#include "devnum-util.h" +#include "dirent-util.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "hashmap.h" +#if 0 /* NM_IGNORED */ +#include "id128-util.h" +#endif /* NM_IGNORED */ +#include "macro.h" +#include "missing_magic.h" +#if 0 /* NM_IGNORED */ +#include "netlink-util.h" +#endif /* NM_IGNORED */ +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "user-util.h" + +#if 0 /* NM_IGNORED */ +int device_new_aux(sd_device **ret) { + sd_device *device; + + assert(ret); + + device = new(sd_device, 1); + if (!device) + return -ENOMEM; + + *device = (sd_device) { + .n_ref = 1, + .devmode = MODE_INVALID, + .devuid = UID_INVALID, + .devgid = GID_INVALID, + .action = _SD_DEVICE_ACTION_INVALID, + }; + + *ret = device; + return 0; +} + +static sd_device *device_free(sd_device *device) { + assert(device); + + sd_device_unref(device->parent); + free(device->syspath); + free(device->sysname); + free(device->devtype); + free(device->devname); + free(device->subsystem); + free(device->driver_subsystem); + free(device->driver); + free(device->device_id); + free(device->properties_strv); + free(device->properties_nulstr); + + ordered_hashmap_free(device->properties); + ordered_hashmap_free(device->properties_db); + hashmap_free(device->sysattr_values); + set_free(device->sysattrs); + set_free(device->all_tags); + set_free(device->current_tags); + set_free(device->devlinks); + hashmap_free(device->children); + + return mfree(device); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device, sd_device, device_free); +#endif /* NM_IGNORED */ + +int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db) { + OrderedHashmap **properties; + + assert(device); + assert(key); + + if (db) + properties = &device->properties_db; + else + properties = &device->properties; + + if (value) { + _unused_ _cleanup_free_ char *old_value = NULL; + _cleanup_free_ char *new_key = NULL, *new_value = NULL, *old_key = NULL; + int r; + + r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops_free_free); + if (r < 0) + return r; + + new_key = strdup(key); + if (!new_key) + return -ENOMEM; + + new_value = strdup(value); + if (!new_value) + return -ENOMEM; + + old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key); + + /* ordered_hashmap_replace() does not fail when the hashmap already has the entry. */ + r = ordered_hashmap_replace(*properties, new_key, new_value); + if (r < 0) + return r; + + TAKE_PTR(new_key); + TAKE_PTR(new_value); + } else { + _unused_ _cleanup_free_ char *old_value = NULL; + _cleanup_free_ char *old_key = NULL; + + old_value = ordered_hashmap_remove2(*properties, key, (void**) &old_key); + } + + if (!db) { + device->properties_generation++; + device->properties_buf_outdated = true; + } + + return 0; +} + +#if 0 /* NM_IGNORED */ +int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { + _cleanup_free_ char *syspath = NULL; + const char *devpath; + int r; + + assert(device); + assert(_syspath); + + if (verify) { + _cleanup_close_ int fd = -EBADF; + + /* The input path maybe a symlink located outside of /sys. Let's try to chase the symlink at first. + * The primary use case is that e.g. /proc/device-tree is a symlink to /sys/firmware/devicetree/base. + * By chasing symlinks in the path at first, we can call sd_device_new_from_path() with such path. */ + r = chase(_syspath, NULL, 0, &syspath, &fd); + if (r == -ENOENT) + /* the device does not exist (any more?) */ + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "sd-device: Failed to chase symlinks in \"%s\".", _syspath); + if (r < 0) + return log_debug_errno(r, "sd-device: Failed to get target of '%s': %m", _syspath); + + if (!path_startswith(syspath, "/sys")) { + _cleanup_free_ char *real_sys = NULL, *new_syspath = NULL; + char *p; + + /* /sys is a symlink to somewhere sysfs is mounted on? In that case, we convert the path to real sysfs to "/sys". */ + r = chase("/sys", NULL, 0, &real_sys, NULL); + if (r < 0) + return log_debug_errno(r, "sd-device: Failed to chase symlink /sys: %m"); + + p = path_startswith(syspath, real_sys); + if (!p) + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "sd-device: Canonicalized path '%s' does not starts with sysfs mount point '%s'", + syspath, real_sys); + + new_syspath = path_join("/sys", p); + if (!new_syspath) + return log_oom_debug(); + + free_and_replace(syspath, new_syspath); + path_simplify(syspath); + } + + if (path_startswith(syspath, "/sys/devices/")) { + /* For proper devices, stricter rules apply: they must have a 'uevent' file, + * otherwise we won't allow them */ + + if (faccessat(fd, "uevent", F_OK, 0) < 0) { + if (errno == ENOENT) + /* This is not a valid device. Note, this condition is quite often + * satisfied when enumerating devices or finding a parent device. + * Hence, use log_trace_errno() here. */ + return log_trace_errno(SYNTHETIC_ERRNO(ENODEV), + "sd-device: the uevent file \"%s/uevent\" does not exist.", syspath); + if (errno == ENOTDIR) + /* Not actually a directory. */ + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "sd-device: the syspath \"%s\" is not a directory.", syspath); + + return log_debug_errno(errno, "sd-device: cannot find uevent file for %s: %m", syspath); + } + } else { + struct stat st; + + /* For everything else lax rules apply: they just need to be a directory */ + + if (fstat(fd, &st) < 0) + return log_debug_errno(errno, "sd-device: failed to check if syspath \"%s\" is a directory: %m", syspath); + if (!S_ISDIR(st.st_mode)) + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "sd-device: the syspath \"%s\" is not a directory.", syspath); + } + + /* Only operate on sysfs, i.e. refuse going down into /sys/fs/cgroup/ or similar places where + * things are not arranged as kobjects in kernel, and hence don't necessarily have + * kobject/attribute structure. */ + r = getenv_bool_secure("SYSTEMD_DEVICE_VERIFY_SYSFS"); + if (r < 0 && r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_DEVICE_VERIFY_SYSFS value: %m"); + if (r != 0) { + r = fd_is_fs_type(fd, SYSFS_MAGIC); + if (r < 0) + return log_debug_errno(r, "sd-device: failed to check if syspath \"%s\" is backed by sysfs.", syspath); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "sd-device: the syspath \"%s\" is outside of sysfs, refusing.", syspath); + } + } else { + /* must be a subdirectory of /sys */ + if (!path_startswith(_syspath, "/sys/")) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "sd-device: Syspath '%s' is not a subdirectory of /sys", + _syspath); + + r = path_simplify_alloc(_syspath, &syspath); + if (r < 0) + return r; + } + + assert_se(devpath = startswith(syspath, "/sys")); + if (devpath[0] != '/') + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "sd-device: \"/sys\" alone is not a valid device path."); + + r = device_add_property_internal(device, "DEVPATH", devpath); + if (r < 0) + return log_debug_errno(r, "sd-device: Failed to add \"DEVPATH\" property for device \"%s\": %m", syspath); + + free_and_replace(device->syspath, syspath); + device->devpath = devpath; + + /* Unset sysname and sysnum, they will be assigned when requested. */ + device->sysnum = NULL; + device->sysname = mfree(device->sysname); + return 0; +} + +static int device_new_from_syspath(sd_device **ret, const char *syspath, bool strict) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(syspath, -EINVAL); + + if (strict && !path_startswith(syspath, "/sys/")) + return -EINVAL; + + r = device_new_aux(&device); + if (r < 0) + return r; + + r = device_set_syspath(device, syspath, /* verify= */ true); + if (r < 0) + return r; + + *ret = TAKE_PTR(device); + return 0; +} + +_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { + return device_new_from_syspath(ret, syspath, /* strict = */ true); +} + +int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + _cleanup_free_ char *syspath = NULL; + const char *t; + dev_t n; + int r; + + assert(ret); + + if (S_ISCHR(mode)) + t = "char"; + else if (S_ISBLK(mode)) + t = "block"; + else + return -ENOTTY; + + if (major(devnum) == 0) + return -ENODEV; + + if (asprintf(&syspath, "/sys/dev/%s/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0) + return -ENOMEM; + + r = sd_device_new_from_syspath(&dev, syspath); + if (r < 0) + return r; + + r = sd_device_get_devnum(dev, &n); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (n != devnum) + return -ENXIO; + + if (device_in_subsystem(dev, "block") != !!S_ISBLK(mode)) + return -ENXIO; + + *ret = TAKE_PTR(dev); + return 0; +} + +_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) { + assert_return(ret, -EINVAL); + assert_return(IN_SET(type, 'b', 'c'), -EINVAL); + + return device_new_from_mode_and_devnum(ret, type == 'b' ? S_IFBLK : S_IFCHR, devnum); +} + +static int device_new_from_main_ifname(sd_device **ret, const char *ifname) { + const char *syspath; + + assert(ret); + assert(ifname); + + syspath = strjoina("/sys/class/net/", ifname); + return sd_device_new_from_syspath(ret, syspath); +} + +_public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) { + _cleanup_free_ char *main_name = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(ifname, -EINVAL); + + r = device_new_from_main_ifname(ret, ifname); + if (r >= 0) + return r; + + r = rtnl_resolve_ifname_full(NULL, RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, ifname, &main_name, NULL); + if (r < 0) + return r; + + return device_new_from_main_ifname(ret, main_name); +} + +_public_ int sd_device_new_from_ifindex(sd_device **ret, int ifindex) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + _cleanup_free_ char *ifname = NULL; + int r, i; + + assert_return(ret, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + + r = rtnl_get_ifname_full(NULL, ifindex, &ifname, NULL); + if (r < 0) + return r; + + r = device_new_from_main_ifname(&dev, ifname); + if (r < 0) + return r; + + r = sd_device_get_ifindex(dev, &i); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (i != ifindex) + return -ENXIO; + + *ret = TAKE_PTR(dev); + return 0; +} + +static int device_strjoin_new( + const char *a, + const char *b, + const char *c, + const char *d, + sd_device **ret) { + + const char *p; + int r; + + p = strjoina(a, b, c, d); + if (access(p, F_OK) < 0) + return IN_SET(errno, ENOENT, ENAMETOOLONG) ? 0 : -errno; /* If this sysfs is too long then it doesn't exist either */ + + r = sd_device_new_from_syspath(ret, p); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_device_new_from_subsystem_sysname( + sd_device **ret, + const char *subsystem, + const char *sysname) { + + char *name; + int r; + + assert_return(ret, -EINVAL); + assert_return(subsystem, -EINVAL); + assert_return(sysname, -EINVAL); + + if (!path_is_normalized(subsystem)) + return -EINVAL; + if (!path_is_normalized(sysname)) + return -EINVAL; + + /* translate sysname back to sysfs filename */ + name = strdupa_safe(sysname); + string_replace_char(name, '/', '!'); + + if (streq(subsystem, "subsystem")) { + FOREACH_STRING(s, "/sys/bus/", "/sys/class/") { + r = device_strjoin_new(s, name, NULL, NULL, ret); + if (r < 0) + return r; + if (r > 0) + return 0; + } + + } else if (streq(subsystem, "module")) { + r = device_strjoin_new("/sys/module/", name, NULL, NULL, ret); + if (r < 0) + return r; + if (r > 0) + return 0; + + } else if (streq(subsystem, "drivers")) { + const char *sep; + + sep = strchr(name, ':'); + if (sep && sep[1] != '\0') { /* Require ":" and something non-empty after that. */ + + const char *subsys = memdupa_suffix0(name, sep - name); + sep++; + + if (streq(sep, "drivers")) /* If the sysname is "drivers", then it's the drivers directory itself that is meant. */ + r = device_strjoin_new("/sys/bus/", subsys, "/drivers", NULL, ret); + else + r = device_strjoin_new("/sys/bus/", subsys, "/drivers/", sep, ret); + if (r < 0) + return r; + if (r > 0) + return 0; + } + } + + r = device_strjoin_new("/sys/bus/", subsystem, "/devices/", name, ret); + if (r < 0) + return r; + if (r > 0) + return 0; + + r = device_strjoin_new("/sys/class/", subsystem, "/", name, ret); + if (r < 0) + return r; + if (r > 0) + return 0; + + r = device_strjoin_new("/sys/firmware/", subsystem, "/", name, ret); + if (r < 0) + return r; + if (r > 0) + return 0; + + return -ENODEV; +} + +_public_ int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st) { + assert_return(ret, -EINVAL); + assert_return(st, -EINVAL); + + return device_new_from_mode_and_devnum(ret, st->st_mode, st->st_rdev); +} + +_public_ int sd_device_new_from_devname(sd_device **ret, const char *devname) { + struct stat st; + dev_t devnum; + mode_t mode; + + assert_return(ret, -EINVAL); + assert_return(devname, -EINVAL); + + /* This function actually accepts both devlinks and devnames, i.e. both symlinks and device + * nodes below /dev/. */ + + /* Also ignore when the specified path is "/dev". */ + if (isempty(path_startswith(devname, "/dev"))) + return -EINVAL; + + if (device_path_parse_major_minor(devname, &mode, &devnum) >= 0) + /* Let's shortcut when "/dev/block/maj:min" or "/dev/char/maj:min" is specified. + * In that case, we can directly convert the path to syspath, hence it is not necessary + * that the specified path exists. So, this works fine without udevd being running. */ + return device_new_from_mode_and_devnum(ret, mode, devnum); + + if (stat(devname, &st) < 0) + return ERRNO_IS_DEVICE_ABSENT(errno) ? -ENODEV : -errno; + + return sd_device_new_from_stat_rdev(ret, &st); +} + +_public_ int sd_device_new_from_path(sd_device **ret, const char *path) { + assert_return(ret, -EINVAL); + assert_return(path, -EINVAL); + + if (path_startswith(path, "/dev")) + return sd_device_new_from_devname(ret, path); + + return device_new_from_syspath(ret, path, /* strict = */ false); +} +#endif /* NM_IGNORED */ + +int device_set_devtype(sd_device *device, const char *devtype) { + _cleanup_free_ char *t = NULL; + int r; + + assert(device); + assert(devtype); + + t = strdup(devtype); + if (!t) + return -ENOMEM; + + r = device_add_property_internal(device, "DEVTYPE", t); + if (r < 0) + return r; + + return free_and_replace(device->devtype, t); +} + +int device_set_ifindex(sd_device *device, const char *name) { + int r, ifindex; + + assert(device); + assert(name); + + ifindex = parse_ifindex(name); + if (ifindex < 0) + return ifindex; + + r = device_add_property_internal(device, "IFINDEX", name); + if (r < 0) + return r; + + device->ifindex = ifindex; + + return 0; +} + +static int mangle_devname(const char *p, char **ret) { + char *q; + + assert(p); + assert(ret); + + if (!path_is_safe(p)) + return -EINVAL; + + /* When the path is absolute, it must start with "/dev/", but ignore "/dev/" itself. */ + if (path_is_absolute(p)) { + if (isempty(path_startswith(p, "/dev/"))) + return -EINVAL; + + q = strdup(p); + } else + q = path_join("/dev/", p); + if (!q) + return -ENOMEM; + + path_simplify(q); + + *ret = q; + return 0; +} + +int device_set_devname(sd_device *device, const char *devname) { + _cleanup_free_ char *t = NULL; + int r; + + assert(device); + assert(devname); + + r = mangle_devname(devname, &t); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVNAME", t); + if (r < 0) + return r; + + return free_and_replace(device->devname, t); +} + +int device_set_devmode(sd_device *device, const char *_devmode) { + unsigned devmode; + int r; + + assert(device); + assert(_devmode); + + r = safe_atou(_devmode, &devmode); + if (r < 0) + return r; + + if (devmode > 07777) + return -EINVAL; + + r = device_add_property_internal(device, "DEVMODE", _devmode); + if (r < 0) + return r; + + device->devmode = devmode; + + return 0; +} + +int device_set_devnum(sd_device *device, const char *major, const char *minor) { + unsigned maj, min = 0; + int r; + + assert(device); + assert(major); + + r = safe_atou(major, &maj); + if (r < 0) + return r; + if (maj == 0) + return 0; + if (!DEVICE_MAJOR_VALID(maj)) + return -EINVAL; + + if (minor) { + r = safe_atou(minor, &min); + if (r < 0) + return r; + if (!DEVICE_MINOR_VALID(min)) + return -EINVAL; + } + + r = device_add_property_internal(device, "MAJOR", major); + if (r < 0) + return r; + + if (minor) { + r = device_add_property_internal(device, "MINOR", minor); + if (r < 0) + return r; + } + + device->devnum = makedev(maj, min); + + return 0; +} + +int device_set_diskseq(sd_device *device, const char *str) { + uint64_t diskseq; + int r; + + assert(device); + assert(str); + + r = safe_atou64(str, &diskseq); + if (r < 0) + return r; + if (diskseq == 0) + return -EINVAL; + + r = device_add_property_internal(device, "DISKSEQ", str); + if (r < 0) + return r; + + device->diskseq = diskseq; + + return 0; +} + +static int handle_uevent_line( + sd_device *device, + const char *key, + const char *value, + const char **major, + const char **minor) { + + assert(device); + assert(key); + assert(value); + assert(major); + assert(minor); + + if (streq(key, "DEVTYPE")) + return device_set_devtype(device, value); + if (streq(key, "IFINDEX")) + return device_set_ifindex(device, value); + if (streq(key, "DEVNAME")) + return device_set_devname(device, value); + if (streq(key, "DEVMODE")) + return device_set_devmode(device, value); + if (streq(key, "DISKSEQ")) + return device_set_diskseq(device, value); + if (streq(key, "MAJOR")) + *major = value; + else if (streq(key, "MINOR")) + *minor = value; + else + return device_add_property_internal(device, key, value); + + return 0; +} + +int device_read_uevent_file(sd_device *device) { + _cleanup_free_ char *uevent = NULL; + const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; + char *path; + size_t uevent_len = 0; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + assert(device); + + if (device->uevent_loaded || device->sealed) + return 0; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + device->uevent_loaded = true; + + path = strjoina(syspath, "/uevent"); + + r = read_full_virtual_file(path, &uevent, &uevent_len); + if (r == -EACCES || ERRNO_IS_NEG_DEVICE_ABSENT(r)) + /* The uevent files may be write-only, the device may be already removed, or the device + * may not have the uevent file. */ + return 0; + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file '%s': %m", path); + + for (size_t i = 0; i < uevent_len; i++) + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, uevent[i])) { + key = &uevent[i]; + + state = KEY; + } + + break; + case KEY: + if (uevent[i] == '=') { + uevent[i] = '\0'; + + state = PRE_VALUE; + } else if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + log_device_debug(device, "sd-device: Invalid uevent line '%s', ignoring", key); + + state = PRE_KEY; + } + + break; + case PRE_VALUE: + value = &uevent[i]; + state = VALUE; + + _fallthrough_; /* to handle empty property */ + case VALUE: + if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + + r = handle_uevent_line(device, key, value, &major, &minor); + if (r < 0) + log_device_debug_errno(device, r, "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached(); + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + log_device_debug_errno(device, r, "sd-device: Failed to set 'MAJOR=%s' or 'MINOR=%s' from '%s', ignoring: %m", major, strna(minor), path); + } + + return 0; +} + +_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { + int r; + + assert_return(device, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (device->ifindex <= 0) + return -ENOENT; + + if (ifindex) + *ifindex = device->ifindex; + + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { + int r; + + assert_return(ret, -EINVAL); + assert_return(id, -EINVAL); + + switch (id[0]) { + case 'b': + case 'c': { + dev_t devt; + + if (isempty(id)) + return -EINVAL; + + r = parse_devnum(id + 1, &devt); + if (r < 0) + return r; + + return sd_device_new_from_devnum(ret, id[0], devt); + } + + case 'n': { + int ifindex; + + ifindex = parse_ifindex(id + 1); + if (ifindex < 0) + return ifindex; + + return sd_device_new_from_ifindex(ret, ifindex); + } + + case '+': { + const char *subsys, *sep; + + sep = strchr(id + 1, ':'); + if (!sep || sep - id - 1 > NAME_MAX) + return -EINVAL; + + subsys = memdupa_suffix0(id + 1, sep - id - 1); + + return sd_device_new_from_subsystem_sysname(ret, subsys, sep + 1); + } + + default: + return -EINVAL; + } +} +#endif /* NM_IGNORED */ + +_public_ int sd_device_get_syspath(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + + assert(path_startswith(device->syspath, "/sys/")); + + if (ret) + *ret = device->syspath; + + return 0; +} + +#if 0 /* NM_IGNORED */ +DEFINE_PRIVATE_HASH_OPS_FULL( + device_by_path_hash_ops, + char, path_hash_func, path_compare, free, + sd_device, sd_device_unref); + +static int device_enumerate_children_internal(sd_device *device, const char *subdir, Set **stack, Hashmap **children) { + _cleanup_closedir_ DIR *dir = NULL; + int r; + + assert(device); + assert(stack); + assert(children); + + r = device_opendir(device, subdir, &dir); + if (r < 0) + return r; + + FOREACH_DIRENT_ALL(de, dir, return -errno) { + _cleanup_(sd_device_unrefp) sd_device *child = NULL; + _cleanup_free_ char *p = NULL; + + if (dot_or_dot_dot(de->d_name)) + continue; + + if (!IN_SET(de->d_type, DT_LNK, DT_DIR)) + continue; + + if (subdir) + p = path_join(subdir, de->d_name); + else + p = strdup(de->d_name); + if (!p) + return -ENOMEM; + + /* Try to create child device. */ + r = sd_device_new_child(&child, device, p); + if (r >= 0) { + /* OK, this is a child device, saving it. */ + r = hashmap_ensure_put(children, &device_by_path_hash_ops, p, child); + if (r < 0) + return r; + + TAKE_PTR(p); + TAKE_PTR(child); + } else if (r == -ENODEV) { + /* This is not a child device. Push the sub-directory into stack, and read it later. */ + + if (de->d_type == DT_LNK) + /* Do not follow symlinks, otherwise, we will enter an infinite loop, e.g., + * /sys/class/block/nvme0n1/subsystem/nvme0n1/subsystem/nvme0n1/subsystem/… */ + continue; + + r = set_ensure_consume(stack, &path_hash_ops_free, TAKE_PTR(p)); + if (r < 0) + return r; + } else + return r; + } + + return 0; +} + +static int device_enumerate_children(sd_device *device) { + _cleanup_hashmap_free_ Hashmap *children = NULL; + _cleanup_set_free_ Set *stack = NULL; + int r; + + assert(device); + + if (device->children_enumerated) + return 0; /* Already enumerated. */ + + r = device_enumerate_children_internal(device, NULL, &stack, &children); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *subdir = NULL; + + subdir = set_steal_first(stack); + if (!subdir) + break; + + r = device_enumerate_children_internal(device, subdir, &stack, &children); + if (r < 0) + return r; + } + + device->children_enumerated = true; + device->children = TAKE_PTR(children); + return 1; /* Enumerated. */ +} + +_public_ sd_device *sd_device_get_child_first(sd_device *device, const char **ret_suffix) { + int r; + + assert(device); + + r = device_enumerate_children(device); + if (r < 0) { + log_device_debug_errno(device, r, "sd-device: failed to enumerate child devices: %m"); + if (ret_suffix) + *ret_suffix = NULL; + return NULL; + } + + device->children_iterator = ITERATOR_FIRST; + + return sd_device_get_child_next(device, ret_suffix); +} + +_public_ sd_device *sd_device_get_child_next(sd_device *device, const char **ret_suffix) { + sd_device *child; + + assert(device); + + (void) hashmap_iterate(device->children, &device->children_iterator, (void**) &child, (const void**) ret_suffix); + return child; +} + +_public_ int sd_device_new_child(sd_device **ret, sd_device *device, const char *suffix) { + _cleanup_free_ char *path = NULL; + sd_device *child; + const char *s; + int r; + + assert_return(ret, -EINVAL); + assert_return(device, -EINVAL); + assert_return(suffix, -EINVAL); + + if (!path_is_safe(suffix)) + return -EINVAL; + + /* If we have already enumerated children, try to find the child from the cache. */ + child = hashmap_get(device->children, suffix); + if (child) { + *ret = sd_device_ref(child); + return 0; + } + + r = sd_device_get_syspath(device, &s); + if (r < 0) + return r; + + path = path_join(s, suffix); + if (!path) + return -ENOMEM; + + return sd_device_new_from_syspath(ret, path); +} + +static int device_new_from_child(sd_device **ret, sd_device *child) { + _cleanup_free_ char *path = NULL; + const char *syspath; + int r; + + assert(ret); + assert(child); + + r = sd_device_get_syspath(child, &syspath); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *p = NULL; + + r = path_extract_directory(path ?: syspath, &p); + if (r < 0) + return r; + + if (path_equal(p, "/sys")) + return -ENODEV; + + r = sd_device_new_from_syspath(ret, p); + if (r != -ENODEV) + return r; + + free_and_replace(path, p); + } +} + +_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) { + int r; + + assert_return(child, -EINVAL); + + if (!child->parent_set) { + r = device_new_from_child(&child->parent, child); + if (r < 0 && r != -ENODEV) + return r; + + child->parent_set = true; + } + + if (!child->parent) + return -ENOENT; + + if (ret) + *ret = child->parent; + return 0; +} +#endif /* NM_IGNORED */ + +int device_set_subsystem(sd_device *device, const char *subsystem) { + _cleanup_free_ char *s = NULL; + int r; + + assert(device); + + if (subsystem) { + s = strdup(subsystem); + if (!s) + return -ENOMEM; + } + + r = device_add_property_internal(device, "SUBSYSTEM", s); + if (r < 0) + return r; + + device->subsystem_set = true; + return free_and_replace(device->subsystem, s); +} + +int device_set_drivers_subsystem(sd_device *device) { + _cleanup_free_ char *subsystem = NULL; + const char *devpath, *drivers, *p; + int r; + + assert(device); + + r = sd_device_get_devpath(device, &devpath); + if (r < 0) + return r; + + drivers = strstr(devpath, "/drivers/"); + if (!drivers) + drivers = endswith(devpath, "/drivers"); + if (!drivers) + return -EINVAL; + + /* Find the path component immediately before the "/drivers/" string */ + r = path_find_last_component(devpath, /* accept_dot_dot= */ false, &drivers, &p); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + subsystem = strndup(p, r); + if (!subsystem) + return -ENOMEM; + + r = device_set_subsystem(device, "drivers"); + if (r < 0) + return r; + + return free_and_replace(device->driver_subsystem, subsystem); +} + +_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + + if (!device->subsystem_set) { + _cleanup_free_ char *subsystem = NULL; + const char *syspath; + char *path; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + /* read 'subsystem' link */ + path = strjoina(syspath, "/subsystem"); + r = readlink_value(path, &subsystem); + if (r < 0 && r != -ENOENT) + return log_device_debug_errno(device, r, + "sd-device: Failed to read subsystem for %s: %m", + device->devpath); + + if (subsystem) + r = device_set_subsystem(device, subsystem); + /* use implicit names */ + else if (!isempty(path_startswith(device->devpath, "/module/"))) + r = device_set_subsystem(device, "module"); + else if (strstr(syspath, "/drivers/") || endswith(syspath, "/drivers")) + r = device_set_drivers_subsystem(device); + else if (!isempty(PATH_STARTSWITH_SET(device->devpath, "/class/", "/bus/"))) + r = device_set_subsystem(device, "subsystem"); + else { + device->subsystem_set = true; + r = 0; + } + if (r < 0) + return log_device_debug_errno(device, r, + "sd-device: Failed to set subsystem for %s: %m", + device->devpath); + } + + if (!device->subsystem) + return -ENOENT; + + if (ret) + *ret = device->subsystem; + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { + int r; + + assert_return(device, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (!device->devtype) + return -ENOENT; + + if (devtype) + *devtype = device->devtype; + + return !!device->devtype; +} + +_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *device, const char *subsystem, const char *devtype, sd_device **ret) { + int r; + + assert_return(device, -EINVAL); + assert_return(subsystem, -EINVAL); + + for (;;) { + r = sd_device_get_parent(device, &device); + if (r < 0) + return r; + + if (!device_in_subsystem(device, subsystem)) + continue; + + if (devtype && !device_is_devtype(device, devtype)) + continue; + + if (ret) + *ret = device; + return 0; + } +} +#endif /* NM_IGNORED */ + +_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { + int r; + + assert_return(device, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (major(device->devnum) <= 0) + return -ENOENT; + + if (devnum) + *devnum = device->devnum; + + return 0; +} + +int device_set_driver(sd_device *device, const char *driver) { + _cleanup_free_ char *d = NULL; + int r; + + assert(device); + + if (driver) { + d = strdup(driver); + if (!d) + return -ENOMEM; + } + + r = device_add_property_internal(device, "DRIVER", d); + if (r < 0) + return r; + + device->driver_set = true; + return free_and_replace(device->driver, d); +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_device_get_driver(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + + if (!device->driver_set) { + _cleanup_free_ char *driver = NULL; + const char *syspath; + char *path; + int r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/driver"); + r = readlink_value(path, &driver); + if (r < 0 && r != -ENOENT) + return log_device_debug_errno(device, r, + "sd-device: readlink(\"%s\") failed: %m", path); + + r = device_set_driver(device, driver); + if (r < 0) + return log_device_debug_errno(device, r, + "sd-device: Failed to set driver \"%s\": %m", driver); + } + + if (!device->driver) + return -ENOENT; + + if (ret) + *ret = device->driver; + return 0; +} +#endif /* NM_IGNORED */ + +_public_ int sd_device_get_devpath(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + + assert(device->devpath); + assert(device->devpath[0] == '/'); + + if (ret) + *ret = device->devpath; + + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_device_get_devname(sd_device *device, const char **devname) { + int r; + + assert_return(device, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (!device->devname) + return -ENOENT; + + assert(!isempty(path_startswith(device->devname, "/dev/"))); + + if (devname) + *devname = device->devname; + return 0; +} +#endif /* NM_IGNORED */ + +static int device_set_sysname_and_sysnum(sd_device *device) { + _cleanup_free_ char *sysname = NULL; + size_t len, n; + int r; + + assert(device); + + r = path_extract_filename(device->devpath, &sysname); + if (r < 0) + return r; + if (r == O_DIRECTORY) + return -EINVAL; + + /* some devices have '!' in their name, change that to '/' */ + string_replace_char(sysname, '!', '/'); + + n = strspn_from_end(sysname, DIGITS); + len = strlen(sysname); + assert(n <= len); + if (n == len) + n = 0; /* Do not set sysnum for number only sysname. */ + + device->sysnum = n > 0 ? sysname + len - n : NULL; + return free_and_replace(device->sysname, sysname); +} + +_public_ int sd_device_get_sysname(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + + if (!device->sysname) { + r = device_set_sysname_and_sysnum(device); + if (r < 0) + return r; + } + + if (ret) + *ret = device->sysname; + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + + if (!device->sysname) { + r = device_set_sysname_and_sysnum(device); + if (r < 0) + return r; + } + + if (!device->sysnum) + return -ENOENT; + + if (ret) + *ret = device->sysnum; + return 0; +} + +_public_ int sd_device_get_action(sd_device *device, sd_device_action_t *ret) { + assert_return(device, -EINVAL); + + if (device->action < 0) + return -ENOENT; + + if (ret) + *ret = device->action; + + return 0; +} + +_public_ int sd_device_get_seqnum(sd_device *device, uint64_t *ret) { + assert_return(device, -EINVAL); + + if (device->seqnum == 0) + return -ENOENT; + + if (ret) + *ret = device->seqnum; + + return 0; +} + +_public_ int sd_device_get_diskseq(sd_device *device, uint64_t *ret) { + int r; + + assert_return(device, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (device->diskseq == 0) + return -ENOENT; + + if (ret) + *ret = device->diskseq; + + return 0; +} +#endif /* NM_IGNORED */ + +static bool is_valid_tag(const char *tag) { + assert(tag); + + return in_charset(tag, ALPHANUMERICAL "-_") && filename_is_valid(tag); +} + +int device_add_tag(sd_device *device, const char *tag, bool both) { + int r, added; + + assert(device); + assert(tag); + + if (!is_valid_tag(tag)) + return -EINVAL; + + /* Definitely add to the "all" list of tags (i.e. the sticky list) */ + added = set_put_strdup(&device->all_tags, tag); + if (added < 0) + return added; + + /* And optionally, also add it to the current list of tags */ + if (both) { + r = set_put_strdup(&device->current_tags, tag); + if (r < 0) { + if (added > 0) + (void) set_remove(device->all_tags, tag); + + return r; + } + } + + device->tags_generation++; + device->property_tags_outdated = true; + + return 0; +} + +int device_add_devlink(sd_device *device, const char *devlink) { + char *p; + int r; + + assert(device); + assert(devlink); + + r = mangle_devname(devlink, &p); + if (r < 0) + return r; + + r = set_ensure_consume(&device->devlinks, &path_hash_ops_free, p); + if (r < 0) + return r; + + device->devlinks_generation++; + device->property_devlinks_outdated = true; + + return r; /* return 1 when newly added, 0 when already exists */ +} + +#if 0 /* NM_IGNORED */ +int device_remove_devlink(sd_device *device, const char *devlink) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert(device); + assert(devlink); + + r = mangle_devname(devlink, &p); + if (r < 0) + return r; + + s = set_remove(device->devlinks, p); + if (!s) + return 0; /* does not exist */ + + device->devlinks_generation++; + device->property_devlinks_outdated = true; + return 1; /* removed */ +} + +bool device_has_devlink(sd_device *device, const char *devlink) { + assert(device); + assert(devlink); + + return set_contains(device->devlinks, devlink); +} +#endif /* NM_IGNORED */ + +static int device_add_property_internal_from_string(sd_device *device, const char *str) { + _cleanup_free_ char *key = NULL; + char *value; + int r; + + assert(device); + assert(str); + + key = strdup(str); + if (!key) + return -ENOMEM; + + value = strchr(key, '='); + if (!value) + return -EINVAL; + + *value = '\0'; + + if (isempty(++value)) + value = NULL; + + /* Add the property to both sd_device::properties and sd_device::properties_db, + * as this is called by only handle_db_line(). */ + r = device_add_property_aux(device, key, value, false); + if (r < 0) + return r; + + return device_add_property_aux(device, key, value, true); +} + +int device_set_usec_initialized(sd_device *device, usec_t when) { + char s[DECIMAL_STR_MAX(usec_t)]; + int r; + + assert(device); + + xsprintf(s, USEC_FMT, when); + + r = device_add_property_internal(device, "USEC_INITIALIZED", s); + if (r < 0) + return r; + + device->usec_initialized = when; + return 0; +} + +static int handle_db_line(sd_device *device, char key, const char *value) { + int r; + + assert(device); + assert(value); + + switch (key) { + case 'G': /* Any tag */ + case 'Q': /* Current tag */ + return device_add_tag(device, value, key == 'Q'); + + case 'S': { + const char *path; + + path = strjoina("/dev/", value); + return device_add_devlink(device, path); + } + case 'E': + return device_add_property_internal_from_string(device, value); + + case 'I': { + usec_t t; + + r = safe_atou64(value, &t); + if (r < 0) + return r; + + return device_set_usec_initialized(device, t); + } + case 'L': + return safe_atoi(value, &device->devlink_priority); + + case 'W': + /* Deprecated. Previously, watch handle is both saved in database and /run/udev/watch. + * However, the handle saved in database may not be updated when the handle is updated + * or removed. Moreover, it is not necessary to store the handle within the database, + * as its value becomes meaningless when udevd is restarted. */ + return 0; + + case 'V': + return safe_atou(value, &device->database_version); + + default: + log_device_debug(device, "sd-device: Unknown key '%c' in device db, ignoring", key); + return 0; + } +} + +int device_get_device_id(sd_device *device, const char **ret) { + assert(device); + assert(ret); + + if (!device->device_id) { + _cleanup_free_ char *id = NULL; + const char *subsystem; + dev_t devnum; + int ifindex, r; + + r = sd_device_get_subsystem(device, &subsystem); + if (r < 0) + return r; + + if (sd_device_get_devnum(device, &devnum) >= 0) { + /* use dev_t — b259:131072, c254:0 */ + if (asprintf(&id, "%c" DEVNUM_FORMAT_STR, + streq(subsystem, "block") ? 'b' : 'c', + DEVNUM_FORMAT_VAL(devnum)) < 0) + return -ENOMEM; + } else if (sd_device_get_ifindex(device, &ifindex) >= 0) { + /* use netdev ifindex — n3 */ + if (asprintf(&id, "n%u", (unsigned) ifindex) < 0) + return -ENOMEM; + } else { + _cleanup_free_ char *sysname = NULL; + + /* use $subsys:$sysname — pci:0000:00:1f.2 + * sd_device_get_sysname() has '!' translated, get it from devpath */ + r = path_extract_filename(device->devpath, &sysname); + if (r < 0) + return r; + if (r == O_DIRECTORY) + return -EINVAL; + + if (streq(subsystem, "drivers")) { + /* the 'drivers' pseudo-subsystem is special, and needs the real + * subsystem encoded as well */ + assert(device->driver_subsystem); + id = strjoin("+drivers:", device->driver_subsystem, ":", sysname); + } else + id = strjoin("+", subsystem, ":", sysname); + if (!id) + return -ENOMEM; + } + + if (!filename_is_valid(id)) + return -EINVAL; + + device->device_id = TAKE_PTR(id); + } + + *ret = device->device_id; + return 0; +} + +int device_read_db_internal_filename(sd_device *device, const char *filename) { + _cleanup_free_ char *db = NULL; + const char *value = NULL; + size_t db_len = 0; + char key = '\0'; /* Unnecessary initialization to appease gcc-12.0.0-0.4.fc36 */ + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + assert(device); + assert(filename); + + r = read_full_file(filename, &db, &db_len); + if (r < 0) { + if (r == -ENOENT) + return 0; + + return log_device_debug_errno(device, r, "sd-device: Failed to read db '%s': %m", filename); + } + + /* devices with a database entry are initialized */ + device->is_initialized = true; + + device->db_loaded = true; + + for (size_t i = 0; i < db_len; i++) + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, db[i])) { + key = db[i]; + + state = KEY; + } + + break; + case KEY: + if (db[i] != ':') { + log_device_debug(device, "sd-device: Invalid db entry with key '%c', ignoring", key); + + state = INVALID_LINE; + } else { + db[i] = '\0'; + + state = PRE_VALUE; + } + + break; + case PRE_VALUE: + value = &db[i]; + + state = VALUE; + + break; + case INVALID_LINE: + if (strchr(NEWLINE, db[i])) + state = PRE_KEY; + + break; + case VALUE: + if (strchr(NEWLINE, db[i])) { + db[i] = '\0'; + r = handle_db_line(device, key, value); + if (r < 0) + log_device_debug_errno(device, r, "sd-device: Failed to handle db entry '%c:%s', ignoring: %m", + key, value); + + state = PRE_KEY; + } + + break; + default: + return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), "sd-device: invalid db syntax."); + } + + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_device_get_is_initialized(sd_device *device) { + int r; + + assert_return(device, -EINVAL); + + r = device_read_db(device); + if (r == -ENOENT) + /* The device may be already removed or renamed. */ + return false; + if (r < 0) + return r; + + return device->is_initialized; +} + +_public_ int sd_device_get_usec_initialized(sd_device *device, uint64_t *ret) { + int r; + + assert_return(device, -EINVAL); + + r = sd_device_get_is_initialized(device); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + + if (device->usec_initialized == 0) + return -ENODATA; + + if (ret) + *ret = device->usec_initialized; + + return 0; +} + +_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *ret) { + usec_t now_ts, ts; + int r; + + assert_return(device, -EINVAL); + + r = sd_device_get_usec_initialized(device, &ts); + if (r < 0) + return r; + + now_ts = now(CLOCK_MONOTONIC); + + if (now_ts < ts) + return -EIO; + + if (ret) + *ret = usec_sub_unsigned(now_ts, ts); + + return 0; +} + +_public_ const char *sd_device_get_tag_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + device->all_tags_iterator_generation = device->tags_generation; + device->all_tags_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->all_tags, &device->all_tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_tag_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + if (device->all_tags_iterator_generation != device->tags_generation) + return NULL; + + (void) set_iterate(device->all_tags, &device->all_tags_iterator, &v); + return v; +} + +static bool device_database_supports_current_tags(sd_device *device) { + assert(device); + + (void) device_read_db(device); + + /* The current tags (saved in Q field) feature is implemented in database version 1. + * If the database version is 0, then the tags (NOT current tags, saved in G field) are not + * sticky. Thus, we can safely bypass the operations for the current tags (Q) to tags (G). */ + + return device->database_version >= 1; +} + +_public_ const char *sd_device_get_current_tag_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + if (!device_database_supports_current_tags(device)) + return sd_device_get_tag_first(device); + + (void) device_read_db(device); + + device->current_tags_iterator_generation = device->tags_generation; + device->current_tags_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->current_tags, &device->current_tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_current_tag_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + if (!device_database_supports_current_tags(device)) + return sd_device_get_tag_next(device); + + (void) device_read_db(device); + + if (device->current_tags_iterator_generation != device->tags_generation) + return NULL; + + (void) set_iterate(device->current_tags, &device->current_tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_devlink_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + device->devlinks_iterator_generation = device->devlinks_generation; + device->devlinks_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_devlink_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + if (device->devlinks_iterator_generation != device->devlinks_generation) + return NULL; + + (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v); + return v; +} +#endif /* NM_IGNORED */ + +int device_properties_prepare(sd_device *device) { + int r; + + assert(device); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + r = device_read_db(device); + if (r < 0) + return r; + + if (device->property_devlinks_outdated) { + _cleanup_free_ char *devlinks = NULL; + + r = set_strjoin(device->devlinks, " ", false, &devlinks); + if (r < 0) + return r; + + if (!isempty(devlinks)) { + r = device_add_property_internal(device, "DEVLINKS", devlinks); + if (r < 0) + return r; + } + + device->property_devlinks_outdated = false; + } + + if (device->property_tags_outdated) { + _cleanup_free_ char *tags = NULL; + + r = set_strjoin(device->all_tags, ":", true, &tags); + if (r < 0) + return r; + + if (!isempty(tags)) { + r = device_add_property_internal(device, "TAGS", tags); + if (r < 0) + return r; + } + + tags = mfree(tags); + r = set_strjoin(device->current_tags, ":", true, &tags); + if (r < 0) + return r; + + if (!isempty(tags)) { + r = device_add_property_internal(device, "CURRENT_TAGS", tags); + if (r < 0) + return r; + } + + device->property_tags_outdated = false; + } + + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) { + const char *key; + int r; + + assert_return(device, NULL); + + r = device_properties_prepare(device); + if (r < 0) + return NULL; + + device->properties_iterator_generation = device->properties_generation; + device->properties_iterator = ITERATOR_FIRST; + + (void) ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)_value, (const void**)&key); + return key; +} + +_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) { + const char *key; + int r; + + assert_return(device, NULL); + + r = device_properties_prepare(device); + if (r < 0) + return NULL; + + if (device->properties_iterator_generation != device->properties_generation) + return NULL; + + (void) ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)_value, (const void**)&key); + return key; +} + +static int device_sysattrs_read_all_internal(sd_device *device, const char *subdir, Set **stack) { + _cleanup_closedir_ DIR *dir = NULL; + int r; + + assert(device); + assert(stack); + + r = device_opendir(device, subdir, &dir); + if (r == -ENOENT && subdir) + return 0; /* Maybe, this is a child device, and is already removed. */ + if (r < 0) + return r; + + if (subdir) { + if (faccessat(dirfd(dir), "uevent", F_OK, 0) >= 0) + return 0; /* this is a child device, skipping */ + if (errno != ENOENT) { + log_device_debug_errno(device, errno, + "sd-device: Failed to access %s/uevent, ignoring sub-directory %s: %m", + subdir, subdir); + return 0; + } + } + + FOREACH_DIRENT_ALL(de, dir, return -errno) { + _cleanup_free_ char *p = NULL; + struct stat statbuf; + + if (dot_or_dot_dot(de->d_name)) + continue; + + /* only handle symlinks, regular files, and directories */ + if (!IN_SET(de->d_type, DT_LNK, DT_REG, DT_DIR)) + continue; + + if (subdir) { + p = path_join(subdir, de->d_name); + if (!p) + return -ENOMEM; + } + + if (de->d_type == DT_DIR) { + /* push the sub-directory into the stack, and read it later. */ + if (p) + r = set_ensure_consume(stack, &path_hash_ops_free, TAKE_PTR(p)); + else + r = set_put_strdup_full(stack, &path_hash_ops_free, de->d_name); + if (r < 0) + return r; + + continue; + } + + if (fstatat(dirfd(dir), de->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) < 0) + continue; + + if ((statbuf.st_mode & (S_IRUSR | S_IWUSR)) == 0) + continue; + + if (p) + r = set_ensure_consume(&device->sysattrs, &path_hash_ops_free, TAKE_PTR(p)); + else + r = set_put_strdup_full(&device->sysattrs, &path_hash_ops_free, de->d_name); + if (r < 0) + return r; + } + + return 0; +} + +static int device_sysattrs_read_all(sd_device *device) { + _cleanup_set_free_ Set *stack = NULL; + int r; + + assert(device); + + if (device->sysattrs_read) + return 0; + + r = device_sysattrs_read_all_internal(device, NULL, &stack); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *subdir = NULL; + + subdir = set_steal_first(stack); + if (!subdir) + break; + + r = device_sysattrs_read_all_internal(device, subdir, &stack); + if (r < 0) + return r; + } + + device->sysattrs_read = true; + + return 0; +} + +_public_ const char *sd_device_get_sysattr_first(sd_device *device) { + void *v; + int r; + + assert_return(device, NULL); + + if (!device->sysattrs_read) { + r = device_sysattrs_read_all(device); + if (r < 0) { + errno = -r; + return NULL; + } + } + + device->sysattrs_iterator = ITERATOR_FIRST; + + (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_sysattr_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + if (!device->sysattrs_read) + return NULL; + + (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); + return v; +} + +_public_ int sd_device_has_tag(sd_device *device, const char *tag) { + assert_return(device, -EINVAL); + assert_return(tag, -EINVAL); + + (void) device_read_db(device); + + return set_contains(device->all_tags, tag); +} + +_public_ int sd_device_has_current_tag(sd_device *device, const char *tag) { + assert_return(device, -EINVAL); + assert_return(tag, -EINVAL); + + if (!device_database_supports_current_tags(device)) + return sd_device_has_tag(device, tag); + + (void) device_read_db(device); + + return set_contains(device->current_tags, tag); +} +#endif /* NM_IGNORED */ + +_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **ret_value) { + const char *value; + int r; + + assert_return(device, -EINVAL); + assert_return(key, -EINVAL); + + r = device_properties_prepare(device); + if (r < 0) + return r; + + value = ordered_hashmap_get(device->properties, key); + if (!value) + return -ENOENT; + + if (ret_value) + *ret_value = value; + return 0; +} + +#if 0 /* NM_IGNORED */ +int device_get_property_bool(sd_device *device, const char *key) { + const char *value; + int r; + + assert(device); + assert(key); + + r = sd_device_get_property_value(device, key, &value); + if (r < 0) + return r; + + return parse_boolean(value); +} + +int device_get_property_int(sd_device *device, const char *key, int *ret) { + const char *value; + int r, v; + + assert(device); + assert(key); + + r = sd_device_get_property_value(device, key, &value); + if (r < 0) + return r; + + r = safe_atoi(value, &v); + if (r < 0) + return r; + + if (ret) + *ret = v; + return 0; +} + +_public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) { + const char *s; + sd_id128_t id; + int r; + + assert_return(device, -EINVAL); + + /* Retrieves the UUID attached to a uevent when triggering it from userspace via + * sd_device_trigger_with_uuid() or an equivalent interface. Returns -ENOENT if the record is not + * caused by a synthetic event and -ENODATA if it was but no UUID was specified */ + + r = sd_device_get_property_value(device, "SYNTH_UUID", &s); + if (r < 0) + return r; + + if (streq(s, "0")) /* SYNTH_UUID=0 is set whenever a device is triggered by userspace without specifying a UUID */ + return -ENODATA; + + r = sd_id128_from_string(s, &id); + if (r < 0) + return r; + + if (ret) + *ret = id; + + return 0; +} + +void device_clear_sysattr_cache(sd_device *device) { + device->sysattr_values = hashmap_free(device->sysattr_values); +} + +int device_cache_sysattr_value(sd_device *device, const char *key, char *value) { + _unused_ _cleanup_free_ char *old_value = NULL; + _cleanup_free_ char *new_key = NULL; + int r; + + assert(device); + assert(key); + + /* This takes the reference of the input value. The input value may be NULL. + * This replaces the value if it already exists. */ + + /* First, remove the old cache entry. So, we do not need to clear cache on error. */ + old_value = hashmap_remove2(device->sysattr_values, key, (void **) &new_key); + if (!new_key) { + new_key = strdup(key); + if (!new_key) + return -ENOMEM; + } + + r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value); + if (r < 0) + return r; + + TAKE_PTR(new_key); + + return 0; +} + +int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) { + const char *k = NULL, *value; + + assert(device); + assert(key); + + value = hashmap_get2(device->sysattr_values, key, (void **) &k); + if (!k) + return -ESTALE; /* We have not read the attribute. */ + if (!value) + return -ENOENT; /* We have looked up the attribute before and it did not exist. */ + if (ret_value) + *ret_value = value; + return 0; +} + +/* We cache all sysattr lookups. If an attribute does not exist, it is stored + * with a NULL value in the cache, otherwise the returned string is stored */ +_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) { + _cleanup_free_ char *value = NULL, *path = NULL; + const char *syspath; + struct stat statbuf; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + /* look for possibly already cached result */ + r = device_get_cached_sysattr_value(device, sysattr, ret_value); + if (r != -ESTALE) + return r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = path_join(syspath, sysattr); + if (!path) + return -ENOMEM; + + if (lstat(path, &statbuf) < 0) { + int k; + + r = -errno; + + /* remember that we could not access the sysattr */ + k = device_cache_sysattr_value(device, sysattr, NULL); + if (k < 0) + log_device_debug_errno(device, k, + "sd-device: failed to cache attribute '%s' with NULL, ignoring: %m", + sysattr); + + return r; + } else if (S_ISLNK(statbuf.st_mode)) { + /* Some core links return only the last element of the target path, + * these are just values, the paths should not be exposed. */ + if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { + r = readlink_value(path, &value); + if (r < 0) + return r; + } else + return -EINVAL; + } else if (S_ISDIR(statbuf.st_mode)) + /* skip directories */ + return -EISDIR; + else if (!(statbuf.st_mode & S_IRUSR)) + /* skip non-readable files */ + return -EPERM; + else { + size_t size; + + /* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to + * also get the size of the result. See issue #20025. */ + r = read_full_virtual_file(path, &value, &size); + if (r < 0) + return r; + + /* drop trailing newlines */ + while (size > 0 && strchr(NEWLINE, value[--size])) + value[size] = '\0'; + } + + /* Unfortunately, we need to return 'const char*' instead of 'char*'. Hence, failure in caching + * sysattr value is critical unlike the other places. */ + r = device_cache_sysattr_value(device, sysattr, value); + if (r < 0) { + log_device_debug_errno(device, r, + "sd-device: failed to cache attribute '%s' with '%s'%s: %m", + sysattr, value, ret_value ? "" : ", ignoring"); + if (ret_value) + return r; + + return 0; + } + + if (ret_value) + *ret_value = value; + + TAKE_PTR(value); + return 0; +} + +int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value) { + const char *value; + int r; + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return r; + + int v; + r = safe_atoi(value, &v); + if (r < 0) + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); + + if (ret_value) + *ret_value = v; + /* We return "true" if the value is positive. */ + return v > 0; +} + +int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { + const char *value; + int r; + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return r; + + unsigned v; + r = safe_atou(value, &v); + if (r < 0) + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); + + if (ret_value) + *ret_value = v; + /* We return "true" if the value is positive. */ + return v > 0; +} + +int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) { + const char *value; + int r; + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return r; + + uint32_t v; + r = safe_atou32(value, &v); + if (r < 0) + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); + + if (ret_value) + *ret_value = v; + /* We return "true" if the value is positive. */ + return v > 0; +} + +int device_get_sysattr_bool(sd_device *device, const char *sysattr) { + const char *value; + int r; + + assert(device); + assert(sysattr); + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return r; + + return parse_boolean(value); +} + +static void device_remove_cached_sysattr_value(sd_device *device, const char *_key) { + _cleanup_free_ char *key = NULL; + + assert(device); + assert(_key); + + free(hashmap_remove2(device->sysattr_values, _key, (void **) &key)); +} + +_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *_value) { + _cleanup_free_ char *value = NULL, *path = NULL; + const char *syspath; + size_t len; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + /* Set the attribute and save it in the cache. */ + + if (!_value) { + /* If input value is NULL, then clear cache and not write anything. */ + device_remove_cached_sysattr_value(device, sysattr); + return 0; + } + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = path_join(syspath, sysattr); + if (!path) + return -ENOMEM; + + len = strlen(_value); + + /* drop trailing newlines */ + while (len > 0 && strchr(NEWLINE, _value[len - 1])) + len--; + + /* value length is limited to 4k */ + if (len > 4096) + return -EINVAL; + + value = strndup(_value, len); + if (!value) + return -ENOMEM; + + r = write_string_file(path, value, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_NOFOLLOW); + if (r < 0) { + /* On failure, clear cache entry, as we do not know how it fails. */ + device_remove_cached_sysattr_value(device, sysattr); + return r; + } + + /* Do not cache action string written into uevent file. */ + if (streq(sysattr, "uevent")) + return 0; + + r = device_cache_sysattr_value(device, sysattr, value); + if (r < 0) + log_device_debug_errno(device, r, + "sd-device: failed to cache attribute '%s' with '%s', ignoring: %m", + sysattr, value); + else + TAKE_PTR(value); + + return 0; +} + +_public_ int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr, const char *format, ...) { + _cleanup_free_ char *value = NULL; + va_list ap; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + if (!format) { + device_remove_cached_sysattr_value(device, sysattr); + return 0; + } + + va_start(ap, format); + r = vasprintf(&value, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return sd_device_set_sysattr_value(device, sysattr, value); +} + +_public_ int sd_device_trigger(sd_device *device, sd_device_action_t action) { + const char *s; + + assert_return(device, -EINVAL); + + s = device_action_to_string(action); + if (!s) + return -EINVAL; + + /* This uses the simple no-UUID interface of kernel < 4.13 */ + return sd_device_set_sysattr_value(device, "uevent", s); +} + +_public_ int sd_device_trigger_with_uuid( + sd_device *device, + sd_device_action_t action, + sd_id128_t *ret_uuid) { + + const char *s, *j; + sd_id128_t u; + int r; + + assert_return(device, -EINVAL); + + /* If no one wants to know the UUID, use the simple interface from pre-4.13 times */ + if (!ret_uuid) + return sd_device_trigger(device, action); + + s = device_action_to_string(action); + if (!s) + return -EINVAL; + + r = sd_id128_randomize(&u); + if (r < 0) + return r; + + j = strjoina(s, " ", SD_ID128_TO_UUID_STRING(u)); + + r = sd_device_set_sysattr_value(device, "uevent", j); + if (r < 0) + return r; + + *ret_uuid = u; + return 0; +} + +_public_ int sd_device_open(sd_device *device, int flags) { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *devname; + uint64_t q, diskseq = 0; + struct stat st; + dev_t devnum; + int r; + + assert_return(device, -EINVAL); + assert_return(FLAGS_SET(flags, O_PATH) || !FLAGS_SET(flags, O_NOFOLLOW), -EINVAL); + + r = sd_device_get_devname(device, &devname); + if (r == -ENOENT) + return -ENOEXEC; + if (r < 0) + return r; + + r = sd_device_get_devnum(device, &devnum); + if (r == -ENOENT) + return -ENOEXEC; + if (r < 0) + return r; + + fd = open(devname, FLAGS_SET(flags, O_PATH) ? flags : O_CLOEXEC|O_NOFOLLOW|O_PATH); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_rdev != devnum) + return -ENXIO; + + if (device_in_subsystem(device, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) + return -ENXIO; + + /* If flags has O_PATH, then we cannot check diskseq. Let's return earlier. */ + if (FLAGS_SET(flags, O_PATH)) + return TAKE_FD(fd); + + /* If the device is not initialized, then we cannot determine if we should check diskseq through + * ID_IGNORE_DISKSEQ property. Let's skip to check diskseq in that case. */ + r = sd_device_get_is_initialized(device); + if (r < 0) + return r; + if (r > 0) { + r = device_get_property_bool(device, "ID_IGNORE_DISKSEQ"); + if (r < 0 && r != -ENOENT) + return r; + if (r <= 0) { + r = sd_device_get_diskseq(device, &diskseq); + if (r < 0 && r != -ENOENT) + return r; + } + } + + fd2 = fd_reopen(fd, flags); + if (fd2 < 0) + return fd2; + + if (diskseq == 0) + return TAKE_FD(fd2); + + r = fd_get_diskseq(fd2, &q); + if (r < 0) + return r; + + if (q != diskseq) + return -ENXIO; + + return TAKE_FD(fd2); +} + +int device_opendir(sd_device *device, const char *subdir, DIR **ret) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *path = NULL; + const char *syspath; + int r; + + assert(device); + assert(ret); + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + if (subdir) { + if (!path_is_safe(subdir)) + return -EINVAL; + + path = path_join(syspath, subdir); + if (!path) + return -ENOMEM; + } + + d = opendir(path ?: syspath); + if (!d) + return -errno; + + *ret = TAKE_PTR(d); + return 0; +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build index 3d3e42709f..c93930f39c 100644 --- a/src/libnm-systemd-shared/meson.build +++ b/src/libnm-systemd-shared/meson.build @@ -6,6 +6,7 @@ libnm_systemd_shared = static_library( 'nm-sd-utils-shared.c', 'src/basic/alloc-util.c', 'src/basic/btrfs.c', + 'src/basic/devnum-util.c', 'src/basic/env-file.c', 'src/basic/env-util.c', 'src/basic/escape.c', diff --git a/src/libnm-systemd-shared/src/basic/chase.h b/src/libnm-systemd-shared/src/basic/chase.h new file mode 100644 index 0000000000..cfc714b9f7 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/chase.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <dirent.h> +#include <stdio.h> + +#include "stat-util.h" + +typedef enum ChaseFlags { + CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */ + CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */ + CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */ + CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */ + CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */ + CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */ + CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's + * right-most component refers to symlink, return O_PATH fd of the symlink. */ + CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered. + * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ + CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved + * relative to the given directory fd instead of root. */ + CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */ + CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the + * full path is still stored in ret_path and only the returned + * file descriptor will point to the parent directory. Note that + * the result path is the root or '.', then the file descriptor + * also points to the result path even if this flag is set. + * When this specified, chase() will succeed with 1 even if the + * file points to the last path component does not exist. */ + CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. This + * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT. + * Note, chase_and_open() or friends always add CHASE_PARENT flag + * when internally call chase(), hence CHASE_MKDIR_0755 can be + * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */ + CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ +} ChaseFlags; + +bool unsafe_transition(const struct stat *a, const struct stat *b); + +/* How many iterations to execute before returning -ELOOP */ +#define CHASE_MAX 32 + +int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd); + +int chaseat_prefix_root(const char *path, const char *root, char **ret); +int chase_extract_filename(const char *path, const char *root, char **ret); + +int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path); +int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); +int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); +int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path); +int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); +int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path); +int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename); + +int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); + +int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); +int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); +int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); +int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); +int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); +int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); +int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); diff --git a/src/libnm-systemd-shared/src/basic/devnum-util.c b/src/libnm-systemd-shared/src/basic/devnum-util.c new file mode 100644 index 0000000000..b13c9fc63b --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/devnum-util.c @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#include <string.h> +#include <sys/stat.h> + +#include "chase.h" +#include "devnum-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" + +int parse_devnum(const char *s, dev_t *ret) { + const char *major; + unsigned x, y; + size_t n; + int r; + + n = strspn(s, DIGITS); + if (n == 0) + return -EINVAL; + if (n > DECIMAL_STR_MAX(dev_t)) + return -EINVAL; + if (s[n] != ':') + return -EINVAL; + + major = strndupa_safe(s, n); + r = safe_atou(major, &x); + if (r < 0) + return r; + + r = safe_atou(s + n + 1, &y); + if (r < 0) + return r; + + if (!DEVICE_MAJOR_VALID(x) || !DEVICE_MINOR_VALID(y)) + return -ERANGE; + + *ret = makedev(x, y); + return 0; +} + +int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret) { + const char *t; + + /* Generates the /dev/{char|block}/MAJOR:MINOR path for a dev_t */ + + if (S_ISCHR(mode)) + t = "char"; + else if (S_ISBLK(mode)) + t = "block"; + else + return -ENODEV; + + if (asprintf(ret, "/dev/%s/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0) + return -ENOMEM; + + return 0; +} + +int device_path_make_inaccessible(mode_t mode, char **ret) { + char *s; + + assert(ret); + + if (S_ISCHR(mode)) + s = strdup("/run/systemd/inaccessible/chr"); + else if (S_ISBLK(mode)) + s = strdup("/run/systemd/inaccessible/blk"); + else + return -ENODEV; + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +#if 0 /* NM_IGNORED */ +int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) { + _cleanup_free_ char *p = NULL; + int r; + + /* Finds the canonical path for a device, i.e. resolves the /dev/{char|block}/MAJOR:MINOR path to the end. */ + + assert(ret); + + if (devnum_is_zero(devnum)) + /* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in + * /dev/block/ and /dev/char/, hence we handle them specially here. */ + return device_path_make_inaccessible(mode, ret); + + r = device_path_make_major_minor(mode, devnum, &p); + if (r < 0) + return r; + + return chase(p, NULL, 0, ret, NULL); +} +#endif /* NM_IGNORED */ + +int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) { + mode_t mode; + dev_t devnum; + int r; + + /* Tries to extract the major/minor directly from the device path if we can. Handles /dev/block/ and /dev/char/ + * paths, as well out synthetic inaccessible device nodes. Never goes to disk. Returns -ENODEV if the device + * path cannot be parsed like this. */ + + if (path_equal(path, "/run/systemd/inaccessible/chr")) { + mode = S_IFCHR; + devnum = makedev(0, 0); + } else if (path_equal(path, "/run/systemd/inaccessible/blk")) { + mode = S_IFBLK; + devnum = makedev(0, 0); + } else { + const char *w; + + w = path_startswith(path, "/dev/block/"); + if (w) + mode = S_IFBLK; + else { + w = path_startswith(path, "/dev/char/"); + if (!w) + return -ENODEV; + + mode = S_IFCHR; + } + + r = parse_devnum(w, &devnum); + if (r < 0) + return r; + } + + if (ret_mode) + *ret_mode = mode; + if (ret_devnum) + *ret_devnum = devnum; + + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/devnum-util.h b/src/libnm-systemd-shared/src/basic/devnum-util.h new file mode 100644 index 0000000000..e109de9913 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/devnum-util.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <sys/types.h> + +#include "stdio-util.h" + +int parse_devnum(const char *s, dev_t *ret); + +/* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the + * specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of + * major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of + * comparing directly >= 0: it's to trick out -Wtype-limits, which would otherwise complain if the type is unsigned, as + * such a test would be pointless in such a case.) */ + +#define DEVICE_MAJOR_VALID(x) \ + ({ \ + typeof(x) _x = (x), _y = 0; \ + _x >= _y && _x < (UINT32_C(1) << 12); \ + \ + }) + +#define DEVICE_MINOR_VALID(x) \ + ({ \ + typeof(x) _x = (x), _y = 0; \ + _x >= _y && _x < (UINT32_C(1) << 20); \ + }) + +int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret); +int device_path_make_inaccessible(mode_t mode, char **ret); +int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret); +int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum); + +static inline bool devnum_set_and_equal(dev_t a, dev_t b) { + /* Returns true if a and b definitely refer to the same device. If either is zero, this means "don't + * know" and we'll return false */ + return a == b && a != 0; +} + +/* Maximum string length for a major:minor string. (Note that DECIMAL_STR_MAX includes space for a trailing NUL) */ +#define DEVNUM_STR_MAX (DECIMAL_STR_MAX(dev_t)-1+1+DECIMAL_STR_MAX(dev_t)) + +#define DEVNUM_FORMAT_STR "%u:%u" +#define DEVNUM_FORMAT_VAL(d) major(d), minor(d) + +static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) { + return ASSERT_PTR(snprintf_ok(buf, DEVNUM_STR_MAX, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d))); +} + +#define FORMAT_DEVNUM(d) format_devnum((d), (char[DEVNUM_STR_MAX]) {}) + +static inline bool devnum_is_zero(dev_t d) { + return major(d) == 0 && minor(d) == 0; +} diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index 8804c0c1a1..0210d3cdf9 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -156,7 +156,6 @@ int readlink_malloc(const char *p, char **ret) { return readlinkat_malloc(AT_FDCWD, p, ret); } -#if 0 /* NM_IGNORED */ int readlink_value(const char *p, char **ret) { _cleanup_free_ char *link = NULL, *name = NULL; int r; @@ -178,6 +177,7 @@ int readlink_value(const char *p, char **ret) { return 0; } +#if 0 /* NM_IGNORED */ int readlink_and_make_absolute(const char *p, char **ret) { _cleanup_free_ char *target = NULL; int r; diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.c b/src/libnm-systemd-shared/src/basic/hash-funcs.c index 6dcc2f36d9..d20ca3c336 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.c +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.c @@ -22,7 +22,6 @@ DEFINE_HASH_OPS_FULL(string_hash_ops_free_strv_free, char, string_hash_func, string_compare_func, free, char*, strv_free); -#if 0 /* NM_IGNORED */ void path_hash_func(const char *q, struct siphash *state) { bool add_slash = false; @@ -68,7 +67,6 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(path_hash_ops_free, DEFINE_HASH_OPS_FULL(path_hash_ops_free_free, char, path_hash_func, path_compare, free, void, free); -#endif /* NM_IGNORED */ void trivial_hash_func(const void *p, struct siphash *state) { siphash24_compress_typesafe(p, state); diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index 638e4860a6..0e0f53d981 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -219,6 +219,7 @@ int path_make_relative_parent(const char *from_child, const char *to, char **ret return path_make_relative(from, to, ret); } +#endif /* NM_IGNORED */ char* path_startswith_strv(const char *p, char **set) { STRV_FOREACH(s, set) { @@ -232,6 +233,7 @@ char* path_startswith_strv(const char *p, char **set) { return NULL; } +#if 0 /* NM_IGNORED */ int path_strv_make_absolute_cwd(char **l) { int r; diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h index dc11a85f62..65a14a44cb 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.h +++ b/src/libnm-systemd-shared/src/basic/stat-util.h @@ -110,12 +110,14 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret); } var #endif +#if 0 /* NM_IGNORED */ static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) { return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); } static inline nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) { return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); } +#endif /* NM_IGNORED */ void inode_hash_func(const struct stat *q, struct siphash *state); int inode_compare_func(const struct stat *a, const struct stat *b); diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index 539c0c76fe..b48e25c7bf 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -1319,6 +1319,7 @@ bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok) { return in_charset(s1, ok) && in_charset(s2, ok); } +#endif /* NM_IGNORED */ char *string_replace_char(char *str, char old_char, char new_char) { assert(str); @@ -1331,7 +1332,6 @@ char *string_replace_char(char *str, char old_char, char new_char) { return str; } -#endif /* NM_IGNORED */ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { char *b; @@ -1376,7 +1376,6 @@ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { return 0; } -#if 0 /* NM_IGNORED */ size_t strspn_from_end(const char *str, const char *accept) { size_t n = 0; @@ -1392,6 +1391,7 @@ size_t strspn_from_end(const char *str, const char *accept) { return n; } +#if 0 /* NM_IGNORED */ char *strdupspn(const char *a, const char *accept) { if (isempty(a) || isempty(accept)) return strdup(""); |