/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include "sd-device.h" #include "alloc-util.h" #include "device-internal.h" #include "device-private.h" #include "device-util.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "hashmap.h" #include "macro.h" #include "parse-util.h" #include "path-util.h" #include "set.h" #include "socket-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "strxcpyx.h" #include "util.h" 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, .watch_handle = -1, .devmode = (mode_t) -1, .devuid = (uid_t) -1, .devgid = (gid_t) -1, .action = _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->id_filename); free(device->properties_strv); free(device->properties_nulstr); ordered_hashmap_free_free_free(device->properties); ordered_hashmap_free_free_free(device->properties_db); hashmap_free_free_free(device->sysattr_values); set_free(device->sysattrs); set_free(device->all_tags); set_free(device->current_tags); set_free(device->devlinks); return mfree(device); } DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device, sd_device, device_free); 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) { _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL; int r; r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops); if (r < 0) return r; key = strdup(_key); if (!key) return -ENOMEM; value = strdup(_value); if (!value) return -ENOMEM; old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key); r = ordered_hashmap_replace(*properties, key, value); if (r < 0) return r; key = NULL; value = NULL; } else { _cleanup_free_ char *key = NULL; _cleanup_free_ char *value = NULL; value = ordered_hashmap_remove2(*properties, _key, (void**) &key); } if (!db) { device->properties_generation++; device->properties_buf_outdated = true; } return 0; } 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); /* 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); if (verify) { r = chase_symlinks(_syspath, NULL, 0, &syspath, NULL); if (r == -ENOENT) return -ENODEV; /* the device does not exist (any more?) */ 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_symlinks("/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 -ENOMEM; free_and_replace(syspath, new_syspath); path_simplify(syspath, false); } if (path_startswith(syspath, "/sys/devices/")) { char *path; /* all 'devices' require an 'uevent' file */ path = strjoina(syspath, "/uevent"); r = access(path, F_OK); if (r < 0) { if (errno == ENOENT) /* this is not a valid device */ return -ENODEV; return log_debug_errno(errno, "sd-device: %s does not have an uevent file: %m", syspath); } } else { /* everything else just needs to be a directory */ if (!is_dir(syspath, false)) return -ENODEV; } } else { syspath = strdup(_syspath); if (!syspath) return -ENOMEM; } devpath = syspath + STRLEN("/sys"); if (devpath[0] == '\0') /* '/sys' alone is not a valid device path */ return -ENODEV; r = device_add_property_internal(device, "DEVPATH", devpath); if (r < 0) return r; free_and_replace(device->syspath, syspath); device->devpath = devpath; return 0; } _public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; int r; assert_return(ret, -EINVAL); assert_return(syspath, -EINVAL); r = device_new_aux(&device); if (r < 0) return r; r = device_set_syspath(device, syspath, true); if (r < 0) return r; *ret = TAKE_PTR(device); return 0; } _public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) { char *syspath; char id[DECIMAL_STR_MAX(unsigned) * 2 + 1]; assert_return(ret, -EINVAL); assert_return(IN_SET(type, 'b', 'c'), -EINVAL); /* use /sys/dev/{block,char}/: link */ xsprintf(id, "%u:%u", major(devnum), minor(devnum)); syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id); return sd_device_new_from_syspath(ret, syspath); } _public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) { char *name, *syspath; size_t len = 0; assert_return(ret, -EINVAL); assert_return(subsystem, -EINVAL); assert_return(sysname, -EINVAL); if (streq(subsystem, "subsystem")) { syspath = strjoina("/sys/subsystem/", sysname); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); syspath = strjoina("/sys/bus/", sysname); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); syspath = strjoina("/sys/class/", sysname); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); } else if (streq(subsystem, "module")) { syspath = strjoina("/sys/module/", sysname); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); } else if (streq(subsystem, "drivers")) { char subsys[PATH_MAX]; char *driver; strscpy(subsys, sizeof(subsys), sysname); driver = strchr(subsys, ':'); if (driver) { driver[0] = '\0'; driver++; syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); syspath = strjoina("/sys/bus/", subsys, "/drivers/", driver); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); } } /* translate sysname back to sysfs filename */ name = strdupa(sysname); while (name[len] != '\0') { if (name[len] == '/') name[len] = '!'; len++; } syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", name); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); syspath = strjoina("/sys/bus/", subsystem, "/devices/", name); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); syspath = strjoina("/sys/class/", subsystem, "/", name); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); syspath = strjoina("/sys/firmware/", subsystem, "/", sysname); if (access(syspath, F_OK) >= 0) return sd_device_new_from_syspath(ret, syspath); return -ENODEV; } 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; } int device_set_devname(sd_device *device, const char *devname) { _cleanup_free_ char *t = NULL; int r; assert(device); assert(devname); if (devname[0] != '/') t = strjoin("/dev/", devname); else t = strdup(devname); if (!t) return -ENOMEM; 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 = 0, min = 0; int r; assert(device); assert(major); r = safe_atou(major, &maj); if (r < 0) return r; if (!maj) return 0; if (minor) { r = safe_atou(minor, &min); if (r < 0) return r; } 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; } static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) { int r; assert(device); assert(key); assert(value); assert(major); assert(minor); if (streq(key, "DEVTYPE")) { r = device_set_devtype(device, value); if (r < 0) return r; } else if (streq(key, "IFINDEX")) { r = device_set_ifindex(device, value); if (r < 0) return r; } else if (streq(key, "DEVNAME")) { r = device_set_devname(device, value); if (r < 0) return r; } else if (streq(key, "DEVMODE")) { r = device_set_devmode(device, value); if (r < 0) return r; } else if (streq(key, "MAJOR")) *major = value; else if (streq(key, "MINOR")) *minor = value; else { r = device_add_property_internal(device, key, value); if (r < 0) return r; } 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; unsigned i; 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; path = strjoina(syspath, "/uevent"); r = read_full_file(path, &uevent, &uevent_len); if (r == -EACCES) { /* empty uevent files may be write-only */ device->uevent_loaded = true; return 0; } if (r == -ENOENT) /* some devices may not have uevent files, see set_syspath() */ return 0; if (r < 0) return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file '%s': %m", path); device->uevent_loaded = true; for (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("Invalid state when parsing uevent file"); } 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, 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; } _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_dev(id + 1, &devt); if (r < 0) return r; return sd_device_new_from_devnum(ret, id[0], devt); } case 'n': { _cleanup_(sd_device_unrefp) sd_device *device = NULL; _cleanup_close_ int sk = -1; struct ifreq ifr = {}; int ifindex; r = ifr.ifr_ifindex = parse_ifindex(&id[1]); if (r < 0) return r; sk = socket_ioctl_fd(); if (sk < 0) return sk; r = ioctl(sk, SIOCGIFNAME, &ifr); if (r < 0) return -errno; r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name); if (r < 0) return r; r = sd_device_get_ifindex(device, &ifindex); if (r < 0) return r; /* this is racey, so we might end up with the wrong device */ if (ifr.ifr_ifindex != ifindex) return -ENODEV; *ret = TAKE_PTR(device); return 0; } case '+': { char subsys[PATH_MAX]; char *sysname; (void) strscpy(subsys, sizeof(subsys), id + 1); sysname = strchr(subsys, ':'); if (!sysname) return -EINVAL; sysname[0] = '\0'; sysname++; return sd_device_new_from_subsystem_sysname(ret, subsys, sysname); } default: return -EINVAL; } } _public_ int sd_device_get_syspath(sd_device *device, const char **ret) { assert_return(device, -EINVAL); assert_return(ret, -EINVAL); assert(path_startswith(device->syspath, "/sys/")); *ret = device->syspath; return 0; } static int device_new_from_child(sd_device **ret, sd_device *child) { _cleanup_free_ char *path = NULL; const char *subdir, *syspath; int r; assert(ret); assert(child); r = sd_device_get_syspath(child, &syspath); if (r < 0) return r; path = strdup(syspath); if (!path) return -ENOMEM; subdir = path + STRLEN("/sys"); for (;;) { char *pos; pos = strrchr(subdir, '/'); if (!pos || pos < subdir + 2) break; *pos = '\0'; r = sd_device_new_from_syspath(ret, path); if (r < 0) continue; return 0; } return -ENODEV; } _public_ int sd_device_get_parent(sd_device *child, sd_device **ret) { assert_return(ret, -EINVAL); assert_return(child, -EINVAL); if (!child->parent_set) { child->parent_set = true; (void) device_new_from_child(&child->parent, child); } if (!child->parent) return -ENOENT; *ret = child->parent; return 0; } int device_set_subsystem(sd_device *device, const char *_subsystem) { _cleanup_free_ char *subsystem = NULL; int r; assert(device); assert(_subsystem); subsystem = strdup(_subsystem); if (!subsystem) return -ENOMEM; r = device_add_property_internal(device, "SUBSYSTEM", subsystem); if (r < 0) return r; device->subsystem_set = true; return free_and_replace(device->subsystem, subsystem); } static int device_set_drivers_subsystem(sd_device *device, const char *_subsystem) { _cleanup_free_ char *subsystem = NULL; int r; assert(device); assert(_subsystem); assert(*_subsystem); subsystem = strdup(_subsystem); 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) { const char *syspath, *drivers = NULL; int r; assert_return(ret, -EINVAL); assert_return(device, -EINVAL); r = sd_device_get_syspath(device, &syspath); if (r < 0) return r; if (!device->subsystem_set) { _cleanup_free_ char *subsystem = NULL; char *path; /* read 'subsystem' link */ path = strjoina(syspath, "/subsystem"); r = readlink_value(path, &subsystem); if (r >= 0) r = device_set_subsystem(device, subsystem); /* use implicit names */ else if (path_startswith(device->devpath, "/module/")) r = device_set_subsystem(device, "module"); else if (!(drivers = strstr(syspath, "/drivers/")) && PATH_STARTSWITH_SET(device->devpath, "/subsystem/", "/class/", "/bus/")) r = device_set_subsystem(device, "subsystem"); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem for %s: %m", device->devpath); device->subsystem_set = true; } else if (!device->driver_subsystem_set) drivers = strstr(syspath, "/drivers/"); if (!device->driver_subsystem_set) { if (drivers) { _cleanup_free_ char *subpath = NULL; subpath = strndup(syspath, drivers - syspath); if (!subpath) r = -ENOMEM; else { const char *subsys; subsys = strrchr(subpath, '/'); if (!subsys) r = -EINVAL; else r = device_set_drivers_subsystem(device, subsys + 1); } if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem for driver %s: %m", device->devpath); } device->driver_subsystem_set = true; } if (!device->subsystem) return -ENOENT; *ret = device->subsystem; return 0; } _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 *child, const char *subsystem, const char *devtype, sd_device **ret) { sd_device *parent = NULL; int r; assert_return(child, -EINVAL); assert_return(subsystem, -EINVAL); r = sd_device_get_parent(child, &parent); while (r >= 0) { const char *parent_subsystem = NULL; const char *parent_devtype = NULL; (void) sd_device_get_subsystem(parent, &parent_subsystem); if (streq_ptr(parent_subsystem, subsystem)) { if (!devtype) break; (void) sd_device_get_devtype(parent, &parent_devtype); if (streq_ptr(parent_devtype, devtype)) break; } r = sd_device_get_parent(parent, &parent); } if (r < 0) return r; *ret = parent; return 0; } _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 *driver = NULL; int r; assert(device); assert(_driver); driver = strdup(_driver); if (!driver) return -ENOMEM; r = device_add_property_internal(device, "DRIVER", driver); if (r < 0) return r; device->driver_set = true; return free_and_replace(device->driver, driver); } _public_ int sd_device_get_driver(sd_device *device, const char **ret) { assert_return(device, -EINVAL); assert_return(ret, -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 = device_set_driver(device, driver); if (r < 0) return log_device_debug_errno(device, r, "sd-device: Failed to set driver for %s: %m", device->devpath); } else if (r == -ENOENT) device->driver_set = true; else return log_device_debug_errno(device, r, "sd-device: Failed to set driver for %s: %m", device->devpath); } if (!device->driver) return -ENOENT; *ret = device->driver; return 0; } _public_ int sd_device_get_devpath(sd_device *device, const char **devpath) { assert_return(device, -EINVAL); assert_return(devpath, -EINVAL); assert(device->devpath); assert(device->devpath[0] == '/'); *devpath = device->devpath; return 0; } _public_ int sd_device_get_devname(sd_device *device, const char **devname) { int r; assert_return(device, -EINVAL); assert_return(devname, -EINVAL); r = device_read_uevent_file(device); if (r < 0) return r; if (!device->devname) return -ENOENT; assert(path_startswith(device->devname, "/dev/")); *devname = device->devname; return 0; } static int device_set_sysname(sd_device *device) { _cleanup_free_ char *sysname = NULL; const char *sysnum = NULL; const char *pos; size_t len = 0; if (!device->devpath) return -EINVAL; pos = strrchr(device->devpath, '/'); if (!pos) return -EINVAL; pos++; /* devpath is not a root directory */ if (*pos == '\0' || pos <= device->devpath) return -EINVAL; sysname = strdup(pos); if (!sysname) return -ENOMEM; /* some devices have '!' in their name, change that to '/' */ while (sysname[len] != '\0') { if (sysname[len] == '!') sysname[len] = '/'; len++; } /* trailing number */ while (len > 0 && isdigit(sysname[--len])) sysnum = &sysname[len]; if (len == 0) sysnum = NULL; device->sysname_set = true; device->sysnum = sysnum; 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); assert_return(ret, -EINVAL); if (!device->sysname_set) { r = device_set_sysname(device); if (r < 0) return r; } assert_return(device->sysname, -ENOENT); *ret = device->sysname; return 0; } _public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { int r; assert_return(device, -EINVAL); assert_return(ret, -EINVAL); if (!device->sysname_set) { r = device_set_sysname(device); if (r < 0) return r; } if (!device->sysnum) return -ENOENT; *ret = device->sysnum; return 0; } static bool is_valid_tag(const char *tag) { assert(tag); return !strchr(tag, ':') && !strchr(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) { int r; assert(device); assert(devlink); r = set_put_strdup(&device->devlinks, devlink); if (r < 0) return r; device->devlinks_generation++; device->property_devlinks_outdated = true; return 0; } 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) { char *path; int r; assert(device); assert(value); switch (key) { case 'G': /* Any tag */ case 'Q': /* Current tag */ r = device_add_tag(device, value, key == 'Q'); if (r < 0) return r; break; case 'S': path = strjoina("/dev/", value); r = device_add_devlink(device, path); if (r < 0) return r; break; case 'E': r = device_add_property_internal_from_string(device, value); if (r < 0) return r; break; case 'I': { usec_t t; r = safe_atou64(value, &t); if (r < 0) return r; r = device_set_usec_initialized(device, t); if (r < 0) return r; break; } case 'L': r = safe_atoi(value, &device->devlink_priority); if (r < 0) return r; break; case 'W': r = safe_atoi(value, &device->watch_handle); if (r < 0) return r; break; case 'V': r = safe_atou(value, &device->database_version); if (r < 0) return r; break; default: log_device_debug(device, "sd-device: Unknown key '%c' in device db, ignoring", key); } return 0; } int device_get_id_filename(sd_device *device, const char **ret) { assert(device); assert(ret); if (!device->id_filename) { _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) { assert(subsystem); /* use dev_t — b259:131072, c254:0 */ r = asprintf(&id, "%c%u:%u", streq(subsystem, "block") ? 'b' : 'c', major(devnum), minor(devnum)); if (r < 0) return -ENOMEM; } else if (sd_device_get_ifindex(device, &ifindex) >= 0) { /* use netdev ifindex — n3 */ r = asprintf(&id, "n%u", (unsigned) ifindex); if (r < 0) return -ENOMEM; } else { /* use $subsys:$sysname — pci:0000:00:1f.2 * sysname() has '!' translated, get it from devpath */ const char *sysname; sysname = basename(device->devpath); if (!sysname) return -EINVAL; if (!subsystem) return -EINVAL; if (streq(subsystem, "drivers")) /* the 'drivers' pseudo-subsystem is special, and needs the real subsystem * encoded as well */ id = strjoin("+drivers:", device->driver_subsystem, ":", sysname); else id = strjoin("+", subsystem, ":", sysname); if (!id) return -ENOMEM; } device->id_filename = TAKE_PTR(id); } *ret = device->id_filename; return 0; } int device_read_db_internal_filename(sd_device *device, const char *filename) { _cleanup_free_ char *db = NULL; const char *value; size_t db_len, i; char key; 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 (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; } int device_read_db_internal(sd_device *device, bool force) { const char *id, *path; int r; assert(device); if (device->db_loaded || (!force && device->sealed)) return 0; r = device_get_id_filename(device, &id); if (r < 0) return r; path = strjoina("/run/udev/data/", id); return device_read_db_internal_filename(device, path); } _public_ int sd_device_get_is_initialized(sd_device *device) { int r; assert_return(device, -EINVAL); r = device_read_db(device); if (r < 0) return r; return device->is_initialized; } _public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) { usec_t now_ts; int r; assert_return(device, -EINVAL); assert_return(usec, -EINVAL); r = device_read_db(device); if (r < 0) return r; if (!device->is_initialized) return -EBUSY; if (!device->usec_initialized) return -ENODATA; now_ts = now(clock_boottime_or_monotonic()); if (now_ts < device->usec_initialized) return -EIO; *usec = now_ts - device->usec_initialized; 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; } 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; } _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) { _cleanup_free_ char *path_dir = NULL; _cleanup_closedir_ DIR *dir = NULL; struct dirent *dent; const char *syspath; int r; r = sd_device_get_syspath(device, &syspath); if (r < 0) return r; if (subdir) { _cleanup_free_ char *p = NULL; p = path_join(syspath, subdir, "uevent"); if (!p) return -ENOMEM; if (access(p, F_OK) >= 0) /* this is a child device, skipping */ return 0; if (errno != ENOENT) { log_device_debug_errno(device, errno, "sd-device: Failed to stat %s, ignoring subdir: %m", p); return 0; } path_dir = path_join(syspath, subdir); if (!path_dir) return -ENOMEM; } dir = opendir(path_dir ?: syspath); if (!dir) return -errno; FOREACH_DIRENT_ALL(dent, dir, return -errno) { _cleanup_free_ char *path = NULL, *p = NULL; struct stat statbuf; if (dot_or_dot_dot(dent->d_name)) continue; /* only handle symlinks, regular files, and directories */ if (!IN_SET(dent->d_type, DT_LNK, DT_REG, DT_DIR)) continue; if (subdir) { p = path_join(subdir, dent->d_name); if (!p) return -ENOMEM; } if (dent->d_type == DT_DIR) { /* read subdirectory */ r = device_sysattrs_read_all_internal(device, p ?: dent->d_name); if (r < 0) return r; continue; } path = path_join(syspath, p ?: dent->d_name); if (!path) return -ENOMEM; if (lstat(path, &statbuf) != 0) continue; if (!(statbuf.st_mode & S_IRUSR)) continue; r = set_put_strdup(&device->sysattrs, p ?: dent->d_name); if (r < 0) return r; } return 0; } static int device_sysattrs_read_all(sd_device *device) { int r; assert(device); if (device->sysattrs_read) return 0; r = device_sysattrs_read_all_internal(device, NULL); 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); } _public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) { 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 (_value) *_value = value; return 0; } /* replaces the value if it already exists */ static int device_add_sysattr_value(sd_device *device, const char *_key, char *value) { _cleanup_free_ char *key = NULL; _cleanup_free_ char *value_old = NULL; int r; assert(device); assert(_key); r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops); if (r < 0) return r; value_old = hashmap_remove2(device->sysattr_values, _key, (void **)&key); if (!key) { key = strdup(_key); if (!key) return -ENOMEM; } r = hashmap_put(device->sysattr_values, key, value); if (r < 0) return r; TAKE_PTR(key); return 0; } static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) { const char *key = NULL, *value; assert(device); assert(_key); value = hashmap_get2(device->sysattr_values, _key, (void **) &key); if (!key) return -ENOENT; if (_value) *_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 **_value) { _cleanup_free_ char *value = NULL; const char *path, *syspath, *cached_value = NULL; struct stat statbuf; int r; assert_return(device, -EINVAL); assert_return(sysattr, -EINVAL); /* look for possibly already cached result */ r = device_get_sysattr_value(device, sysattr, &cached_value); if (r != -ENOENT) { if (r < 0) return r; if (!cached_value) /* we looked up the sysattr before and it did not exist */ return -ENOENT; if (_value) *_value = cached_value; return 0; } r = sd_device_get_syspath(device, &syspath); if (r < 0) return r; path = prefix_roota(syspath, sysattr); r = lstat(path, &statbuf); if (r < 0) { /* remember that we could not access the sysattr */ r = device_add_sysattr_value(device, sysattr, NULL); if (r < 0) return r; return -ENOENT; } 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 -EINVAL; } else if (!(statbuf.st_mode & S_IRUSR)) { /* skip non-readable files */ return -EPERM; } else { size_t size; /* read attribute value */ r = read_full_virtual_file(path, &value, &size); if (r < 0) return r; /* drop trailing newlines */ while (size > 0 && value[--size] == '\n') value[size] = '\0'; } r = device_add_sysattr_value(device, sysattr, value); if (r < 0) return r; *_value = TAKE_PTR(value); return 0; } static void device_remove_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)); } /* set the attribute and save it in the cache. If a NULL value is passed the * attribute is cleared from the cache */ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *_value) { _cleanup_free_ char *value = NULL; const char *syspath, *path; size_t len; int r; assert_return(device, -EINVAL); assert_return(sysattr, -EINVAL); if (!_value) { device_remove_sysattr_value(device, sysattr); return 0; } r = sd_device_get_syspath(device, &syspath); if (r < 0) return r; path = prefix_roota(syspath, sysattr); len = strlen(_value); /* drop trailing newlines */ while (len > 0 && _value[len - 1] == '\n') 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) { if (r == -ELOOP) return -EINVAL; if (r == -EISDIR) return r; r = free_and_strdup(&value, ""); if (r < 0) return r; r = device_add_sysattr_value(device, sysattr, value); if (r < 0) return r; TAKE_PTR(value); return -ENXIO; } r = device_add_sysattr_value(device, sysattr, value); if (r < 0) return r; 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_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); }