diff options
author | Kay Sievers <kay.sievers@vrfy.org> | 2012-01-05 22:41:45 +0100 |
---|---|---|
committer | Kay Sievers <kay.sievers@vrfy.org> | 2012-01-06 05:07:10 +0100 |
commit | ad29a9f14fa8b1932c0e418bfcf1c10ce6a35a33 (patch) | |
tree | 9212c3d9d937234c25d19b993664b8ed164869a5 /src/libudev-device.c | |
parent | 9fbe27d9d6dc6e4530ce904d35c74326e415e34e (diff) |
merge udev/, libudev/, systemd/ files in src/; move extras/ to src/
Diffstat (limited to 'src/libudev-device.c')
-rw-r--r-- | src/libudev-device.c | 1737 |
1 files changed, 1737 insertions, 0 deletions
diff --git a/src/libudev-device.c b/src/libudev-device.c new file mode 100644 index 0000000000..e5950bb4b1 --- /dev/null +++ b/src/libudev-device.c @@ -0,0 +1,1737 @@ +/* + * libudev - interface to udev device information + * + * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdbool.h> +#include <errno.h> +#include <string.h> +#include <dirent.h> +#include <fcntl.h> +#include <ctype.h> +#include <net/if.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <linux/sockios.h> + +#include "libudev.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-device + * @short_description: kernel sys devices + * + * Representation of kernel sys devices. Devices are uniquely identified + * by their syspath, every device has exactly one path in the kernel sys + * filesystem. Devices usually belong to a kernel subsystem, and and have + * a unique name inside that subsystem. + */ + +/** + * udev_device: + * + * Opaque object representing one kernel sys device. + */ +struct udev_device { + struct udev *udev; + struct udev_device *parent_device; + char *syspath; + const char *devpath; + char *sysname; + const char *sysnum; + char *devnode; + mode_t devnode_mode; + char *subsystem; + char *devtype; + char *driver; + char *action; + char *devpath_old; + char *id_filename; + char **envp; + char *monitor_buf; + size_t monitor_buf_len; + struct udev_list devlinks_list; + struct udev_list properties_list; + struct udev_list sysattr_value_list; + struct udev_list sysattr_list; + struct udev_list tags_list; + unsigned long long int seqnum; + unsigned long long int usec_initialized; + int devlink_priority; + int refcount; + dev_t devnum; + int ifindex; + int watch_handle; + int maj, min; + bool parent_set; + bool subsystem_set; + bool devtype_set; + bool devlinks_uptodate; + bool envp_uptodate; + bool tags_uptodate; + bool driver_set; + bool info_loaded; + bool db_loaded; + bool uevent_loaded; + bool is_initialized; + bool sysattr_list_read; + bool db_persist; +}; + +/** + * udev_device_get_seqnum: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have a sequence number. + * + * Returns: the kernel event sequence number, or 0 if there is no sequence number available. + **/ +UDEV_EXPORT unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return 0; + return udev_device->seqnum; +} + +static int udev_device_set_seqnum(struct udev_device *udev_device, unsigned long long int seqnum) +{ + char num[32]; + + udev_device->seqnum = seqnum; + snprintf(num, sizeof(num), "%llu", seqnum); + udev_device_add_property(udev_device, "SEQNUM", num); + return 0; +} + +int udev_device_get_ifindex(struct udev_device *udev_device) +{ + if (!udev_device->info_loaded) + udev_device_read_uevent_file(udev_device); + return udev_device->ifindex; +} + +static int udev_device_set_ifindex(struct udev_device *udev_device, int ifindex) +{ + char num[32]; + + udev_device->ifindex = ifindex; + snprintf(num, sizeof(num), "%u", ifindex); + udev_device_add_property(udev_device, "IFINDEX", num); + return 0; +} + +/** + * udev_device_get_devnum: + * @udev_device: udev device + * + * Returns: the device major/minor number. + **/ +UDEV_EXPORT dev_t udev_device_get_devnum(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return makedev(0, 0); + if (!udev_device->info_loaded) + udev_device_read_uevent_file(udev_device); + return udev_device->devnum; +} + +static int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum) +{ + char num[32]; + + udev_device->devnum = devnum; + + snprintf(num, sizeof(num), "%u", major(devnum)); + udev_device_add_property(udev_device, "MAJOR", num); + snprintf(num, sizeof(num), "%u", minor(devnum)); + udev_device_add_property(udev_device, "MINOR", num); + return 0; +} + +const char *udev_device_get_devpath_old(struct udev_device *udev_device) +{ + return udev_device->devpath_old; +} + +static int udev_device_set_devpath_old(struct udev_device *udev_device, const char *devpath_old) +{ + const char *pos; + + free(udev_device->devpath_old); + udev_device->devpath_old = strdup(devpath_old); + if (udev_device->devpath_old == NULL) + return -ENOMEM; + udev_device_add_property(udev_device, "DEVPATH_OLD", udev_device->devpath_old); + + pos = strrchr(udev_device->devpath_old, '/'); + if (pos == NULL) + return -EINVAL; + return 0; +} + +/** + * udev_device_get_driver: + * @udev_device: udev device + * + * Returns: the driver string, or #NULL if there is no driver attached. + **/ +UDEV_EXPORT const char *udev_device_get_driver(struct udev_device *udev_device) +{ + char driver[UTIL_NAME_SIZE]; + + if (udev_device == NULL) + return NULL; + if (!udev_device->driver_set) { + udev_device->driver_set = true; + if (util_get_sys_core_link_value(udev_device->udev, "driver", udev_device->syspath, driver, sizeof(driver)) > 0) + udev_device->driver = strdup(driver); + } + return udev_device->driver; +} + +static int udev_device_set_driver(struct udev_device *udev_device, const char *driver) +{ + free(udev_device->driver); + udev_device->driver = strdup(driver); + if (udev_device->driver == NULL) + return -ENOMEM; + udev_device->driver_set = true; + udev_device_add_property(udev_device, "DRIVER", udev_device->driver); + return 0; +} + +/** + * udev_device_get_devtype: + * @udev_device: udev device + * + * Retrieve the devtype string of the udev device. + * + * Returns: the devtype name of the udev device, or #NULL if it can not be determined + **/ +UDEV_EXPORT const char *udev_device_get_devtype(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + if (!udev_device->devtype_set) { + udev_device->devtype_set = true; + udev_device_read_uevent_file(udev_device); + } + return udev_device->devtype; +} + +static int udev_device_set_devtype(struct udev_device *udev_device, const char *devtype) +{ + free(udev_device->devtype); + udev_device->devtype = strdup(devtype); + if (udev_device->devtype == NULL) + return -ENOMEM; + udev_device->devtype_set = true; + udev_device_add_property(udev_device, "DEVTYPE", udev_device->devtype); + return 0; +} + +static int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsystem) +{ + free(udev_device->subsystem); + udev_device->subsystem = strdup(subsystem); + if (udev_device->subsystem == NULL) + return -ENOMEM; + udev_device->subsystem_set = true; + udev_device_add_property(udev_device, "SUBSYSTEM", udev_device->subsystem); + return 0; +} + +/** + * udev_device_get_subsystem: + * @udev_device: udev device + * + * Retrieve the subsystem string of the udev device. The string does not + * contain any "/". + * + * Returns: the subsystem name of the udev device, or #NULL if it can not be determined + **/ +UDEV_EXPORT const char *udev_device_get_subsystem(struct udev_device *udev_device) +{ + char subsystem[UTIL_NAME_SIZE]; + + if (udev_device == NULL) + return NULL; + if (!udev_device->subsystem_set) { + udev_device->subsystem_set = true; + /* read "subsystem" link */ + if (util_get_sys_core_link_value(udev_device->udev, "subsystem", udev_device->syspath, subsystem, sizeof(subsystem)) > 0) { + udev_device_set_subsystem(udev_device, subsystem); + return udev_device->subsystem; + } + /* implicit names */ + if (strncmp(udev_device->devpath, "/module/", 8) == 0) { + udev_device_set_subsystem(udev_device, "module"); + return udev_device->subsystem; + } + if (strstr(udev_device->devpath, "/drivers/") != NULL) { + udev_device_set_subsystem(udev_device, "drivers"); + return udev_device->subsystem; + } + if (strncmp(udev_device->devpath, "/subsystem/", 11) == 0 || + strncmp(udev_device->devpath, "/class/", 7) == 0 || + strncmp(udev_device->devpath, "/bus/", 5) == 0) { + udev_device_set_subsystem(udev_device, "subsystem"); + return udev_device->subsystem; + } + } + return udev_device->subsystem; +} + +mode_t udev_device_get_devnode_mode(struct udev_device *udev_device) +{ + if (!udev_device->info_loaded) + udev_device_read_uevent_file(udev_device); + return udev_device->devnode_mode; +} + +static int udev_device_set_devnode_mode(struct udev_device *udev_device, mode_t mode) +{ + char num[32]; + + udev_device->devnode_mode = mode; + snprintf(num, sizeof(num), "%#o", mode); + udev_device_add_property(udev_device, "DEVMODE", num); + return 0; +} + +struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value) +{ + udev_device->envp_uptodate = false; + if (value == NULL) { + struct udev_list_entry *list_entry; + + list_entry = udev_device_get_properties_list_entry(udev_device); + list_entry = udev_list_entry_get_by_name(list_entry, key); + if (list_entry != NULL) + udev_list_entry_delete(list_entry); + return NULL; + } + return udev_list_entry_add(&udev_device->properties_list, key, value); +} + +static struct udev_list_entry *udev_device_add_property_from_string(struct udev_device *udev_device, const char *property) +{ + char name[UTIL_LINE_SIZE]; + char *val; + + util_strscpy(name, sizeof(name), property); + val = strchr(name, '='); + if (val == NULL) + return NULL; + val[0] = '\0'; + val = &val[1]; + if (val[0] == '\0') + val = NULL; + return udev_device_add_property(udev_device, name, val); +} + +/* + * parse property string, and if needed, update internal values accordingly + * + * udev_device_add_property_from_string_parse_finish() needs to be + * called after adding properties, and its return value checked + * + * udev_device_set_info_loaded() needs to be set, to avoid trying + * to use a device without a DEVPATH set + */ +void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property) +{ + if (strncmp(property, "DEVPATH=", 8) == 0) { + char path[UTIL_PATH_SIZE]; + + util_strscpyl(path, sizeof(path), udev_get_sys_path(udev_device->udev), &property[8], NULL); + udev_device_set_syspath(udev_device, path); + } else if (strncmp(property, "SUBSYSTEM=", 10) == 0) { + udev_device_set_subsystem(udev_device, &property[10]); + } else if (strncmp(property, "DEVTYPE=", 8) == 0) { + udev_device_set_devtype(udev_device, &property[8]); + } else if (strncmp(property, "DEVNAME=", 8) == 0) { + udev_device_set_devnode(udev_device, &property[8]); + } else if (strncmp(property, "DEVLINKS=", 9) == 0) { + char devlinks[UTIL_PATH_SIZE]; + char *slink; + char *next; + + util_strscpy(devlinks, sizeof(devlinks), &property[9]); + slink = devlinks; + next = strchr(slink, ' '); + while (next != NULL) { + next[0] = '\0'; + udev_device_add_devlink(udev_device, slink, 0); + slink = &next[1]; + next = strchr(slink, ' '); + } + if (slink[0] != '\0') + udev_device_add_devlink(udev_device, slink, 0); + } else if (strncmp(property, "TAGS=", 5) == 0) { + char tags[UTIL_PATH_SIZE]; + char *next; + + util_strscpy(tags, sizeof(tags), &property[5]); + next = strchr(tags, ':'); + if (next != NULL) { + next++; + while (next[0] != '\0') { + char *tag; + + tag = next; + next = strchr(tag, ':'); + if (next == NULL) + break; + next[0] = '\0'; + next++; + udev_device_add_tag(udev_device, tag); + } + } + } else if (strncmp(property, "USEC_INITIALIZED=", 19) == 0) { + udev_device_set_usec_initialized(udev_device, strtoull(&property[19], NULL, 10)); + } else if (strncmp(property, "DRIVER=", 7) == 0) { + udev_device_set_driver(udev_device, &property[7]); + } else if (strncmp(property, "ACTION=", 7) == 0) { + udev_device_set_action(udev_device, &property[7]); + } else if (strncmp(property, "MAJOR=", 6) == 0) { + udev_device->maj = strtoull(&property[6], NULL, 10); + } else if (strncmp(property, "MINOR=", 6) == 0) { + udev_device->min = strtoull(&property[6], NULL, 10); + } else if (strncmp(property, "DEVPATH_OLD=", 12) == 0) { + udev_device_set_devpath_old(udev_device, &property[12]); + } else if (strncmp(property, "SEQNUM=", 7) == 0) { + udev_device_set_seqnum(udev_device, strtoull(&property[7], NULL, 10)); + } else if (strncmp(property, "IFINDEX=", 8) == 0) { + udev_device_set_ifindex(udev_device, strtoull(&property[8], NULL, 10)); + } else if (strncmp(property, "DEVMODE=", 8) == 0) { + udev_device_set_devnode_mode(udev_device, strtoul(&property[8], NULL, 8)); + } else { + udev_device_add_property_from_string(udev_device, property); + } +} + +int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device) +{ + if (udev_device->maj > 0) + udev_device_set_devnum(udev_device, makedev(udev_device->maj, udev_device->min)); + udev_device->maj = 0; + udev_device->min = 0; + + if (udev_device->devpath == NULL || udev_device->subsystem == NULL) + return -EINVAL; + return 0; +} + +/** + * udev_device_get_property_value: + * @udev_device: udev device + * @key: property name + * + * Returns: the value of a device property, or #NULL if there is no such property. + **/ +UDEV_EXPORT const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) +{ + struct udev_list_entry *list_entry; + + if (udev_device == NULL) + return NULL; + if (key == NULL) + return NULL; + + list_entry = udev_device_get_properties_list_entry(udev_device); + list_entry = udev_list_entry_get_by_name(list_entry, key); + return udev_list_entry_get_value(list_entry); +} + +int udev_device_read_db(struct udev_device *udev_device, const char *dbfile) +{ + char filename[UTIL_PATH_SIZE]; + char line[UTIL_LINE_SIZE]; + FILE *f; + + /* providing a database file will always force-load it */ + if (dbfile == NULL) { + const char *id; + + if (udev_device->db_loaded) + return 0; + udev_device->db_loaded = true; + + id = udev_device_get_id_filename(udev_device); + if (id == NULL) + return -1; + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_device->udev), "/data/", id, NULL); + dbfile = filename; + } + + f = fopen(dbfile, "re"); + if (f == NULL) { + info(udev_device->udev, "no db file to read %s: %m\n", dbfile); + return -1; + } + udev_device->is_initialized = true; + + while (fgets(line, sizeof(line), f)) { + ssize_t len; + const char *val; + struct udev_list_entry *entry; + + len = strlen(line); + if (len < 4) + break; + line[len-1] = '\0'; + val = &line[2]; + switch(line[0]) { + case 'S': + util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/", val, NULL); + udev_device_add_devlink(udev_device, filename, 0); + break; + case 'L': + udev_device_set_devlink_priority(udev_device, atoi(val)); + break; + case 'E': + entry = udev_device_add_property_from_string(udev_device, val); + udev_list_entry_set_num(entry, true); + break; + case 'G': + udev_device_add_tag(udev_device, val); + break; + case 'W': + udev_device_set_watch_handle(udev_device, atoi(val)); + break; + case 'I': + udev_device_set_usec_initialized(udev_device, strtoull(val, NULL, 10)); + break; + } + } + fclose(f); + + info(udev_device->udev, "device %p filled with db file data\n", udev_device); + return 0; +} + +int udev_device_read_uevent_file(struct udev_device *udev_device) +{ + char filename[UTIL_PATH_SIZE]; + FILE *f; + char line[UTIL_LINE_SIZE]; + int maj = 0; + int min = 0; + + if (udev_device->uevent_loaded) + return 0; + + util_strscpyl(filename, sizeof(filename), udev_device->syspath, "/uevent", NULL); + f = fopen(filename, "re"); + if (f == NULL) + return -1; + udev_device->uevent_loaded = true; + + while (fgets(line, sizeof(line), f)) { + char *pos; + + pos = strchr(line, '\n'); + if (pos == NULL) + continue; + pos[0] = '\0'; + + if (strncmp(line, "DEVTYPE=", 8) == 0) + udev_device_set_devtype(udev_device, &line[8]); + else if (strncmp(line, "MAJOR=", 6) == 0) + maj = strtoull(&line[6], NULL, 10); + else if (strncmp(line, "MINOR=", 6) == 0) + min = strtoull(&line[6], NULL, 10); + else if (strncmp(line, "IFINDEX=", 8) == 0) + udev_device_set_ifindex(udev_device, strtoull(&line[8], NULL, 10)); + else if (strncmp(line, "DEVNAME=", 8) == 0) + udev_device_set_devnode(udev_device, &line[8]); + else if (strncmp(line, "DEVMODE=", 8) == 0) + udev_device->devnode_mode = strtoul(&line[8], NULL, 8); + + udev_device_add_property_from_string(udev_device, line); + } + + udev_device->devnum = makedev(maj, min); + fclose(f); + return 0; +} + +void udev_device_set_info_loaded(struct udev_device *device) +{ + device->info_loaded = true; +} + +struct udev_device *udev_device_new(struct udev *udev) +{ + struct udev_device *udev_device; + struct udev_list_entry *list_entry; + + if (udev == NULL) + return NULL; + + udev_device = calloc(1, sizeof(struct udev_device)); + if (udev_device == NULL) + return NULL; + udev_device->refcount = 1; + udev_device->udev = udev; + udev_list_init(udev, &udev_device->devlinks_list, true); + udev_list_init(udev, &udev_device->properties_list, true); + udev_list_init(udev, &udev_device->sysattr_value_list, true); + udev_list_init(udev, &udev_device->sysattr_list, false); + udev_list_init(udev, &udev_device->tags_list, true); + udev_device->watch_handle = -1; + /* copy global properties */ + udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev)) + udev_device_add_property(udev_device, + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + dbg(udev_device->udev, "udev_device: %p created\n", udev_device); + return udev_device; +} + +/** + * udev_device_new_from_syspath: + * @udev: udev library context + * @syspath: sys device path including sys directory + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The syspath is the absolute + * path to the device, including the sys mount point. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +UDEV_EXPORT struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) +{ + size_t len; + const char *subdir; + char path[UTIL_PATH_SIZE]; + char *pos; + struct stat statbuf; + struct udev_device *udev_device; + + if (udev == NULL) + return NULL; + if (syspath == NULL) + return NULL; + + /* path starts in sys */ + len = strlen(udev_get_sys_path(udev)); + if (strncmp(syspath, udev_get_sys_path(udev), len) != 0) { + info(udev, "not in sys :%s\n", syspath); + return NULL; + } + + /* path is not a root directory */ + subdir = &syspath[len+1]; + pos = strrchr(subdir, '/'); + if (pos == NULL || pos[1] == '\0' || pos < &subdir[2]) { + dbg(udev, "not a subdir :%s\n", syspath); + return NULL; + } + + /* resolve possible symlink to real path */ + util_strscpy(path, sizeof(path), syspath); + util_resolve_sys_link(udev, path, sizeof(path)); + + if (strncmp(&path[len], "/devices/", 9) == 0) { + char file[UTIL_PATH_SIZE]; + + /* all "devices" require a "uevent" file */ + util_strscpyl(file, sizeof(file), path, "/uevent", NULL); + if (stat(file, &statbuf) != 0) { + dbg(udev, "not a device: %s\n", syspath); + return NULL; + } + } else { + /* everything else just needs to be a directory */ + if (stat(path, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) { + dbg(udev, "directory not found: %s\n", syspath); + return NULL; + } + } + + udev_device = udev_device_new(udev); + if (udev_device == NULL) + return NULL; + + udev_device_set_syspath(udev_device, path); + info(udev, "device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device)); + + return udev_device; +} + +/** + * udev_device_new_from_devnum: + * @udev: udev library context + * @type: char or block device + * @devnum: device major/minor number + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by its major/minor number and type. Character and block device + * numbers are not unique across the two types. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +UDEV_EXPORT struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) +{ + char path[UTIL_PATH_SIZE]; + const char *type_str; + + if (type == 'b') + type_str = "block"; + else if (type == 'c') + type_str = "char"; + else + return NULL; + + /* use /sys/dev/{block,char}/<maj>:<min> link */ + snprintf(path, sizeof(path), "%s/dev/%s/%u:%u", + udev_get_sys_path(udev), type_str, major(devnum), minor(devnum)); + return udev_device_new_from_syspath(udev, path); +} + +struct udev_device *udev_device_new_from_id_filename(struct udev *udev, char *id) +{ + char type; + int maj, min; + char subsys[UTIL_PATH_SIZE]; + char *sysname; + + switch(id[0]) { + case 'b': + case 'c': + if (sscanf(id, "%c%i:%i", &type, &maj, &min) != 3) + return NULL; + return udev_device_new_from_devnum(udev, type, makedev(maj, min)); + case 'n': { + int sk; + struct ifreq ifr; + struct udev_device *dev; + int ifindex; + + ifindex = strtoul(&id[1], NULL, 10); + if (ifindex <= 0) + return NULL; + + sk = socket(PF_INET, SOCK_DGRAM, 0); + if (sk < 0) + return NULL; + memset(&ifr, 0x00, sizeof(struct ifreq)); + ifr.ifr_ifindex = ifindex; + if (ioctl(sk, SIOCGIFNAME, &ifr) != 0) { + close(sk); + return NULL; + } + close(sk); + + dev = udev_device_new_from_subsystem_sysname(udev, "net", ifr.ifr_name); + if (dev == NULL) + return NULL; + if (udev_device_get_ifindex(dev) == ifindex) + return dev; + udev_device_unref(dev); + return NULL; + } + case '+': + util_strscpy(subsys, sizeof(subsys), &id[1]); + sysname = strchr(subsys, ':'); + if (sysname == NULL) + return NULL; + sysname[0] = '\0'; + sysname = &sysname[1]; + return udev_device_new_from_subsystem_sysname(udev, subsys, sysname); + default: + return NULL; + } +} + +/** + * udev_device_new_from_subsystem_sysname: + * @udev: udev library context + * @subsystem: the subsystem of the device + * @sysname: the name of the device + * + * Create new udev device, and fill in information from the sys device + * and the udev database entry. The device is looked up by the subsystem + * and name string of the device, like "mem" / "zero", or "block" / "sda". + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +UDEV_EXPORT struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) +{ + char path_full[UTIL_PATH_SIZE]; + char *path; + size_t l; + struct stat statbuf; + + path = path_full; + l = util_strpcpyl(&path, sizeof(path_full), udev_get_sys_path(udev), NULL); + + if (strcmp(subsystem, "subsystem") == 0) { + util_strscpyl(path, l, "/subsystem/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + + util_strscpyl(path, l, "/bus/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + + util_strscpyl(path, l, "/class/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + goto out; + } + + if (strcmp(subsystem, "module") == 0) { + util_strscpyl(path, l, "/module/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + goto out; + } + + if (strcmp(subsystem, "drivers") == 0) { + char subsys[UTIL_NAME_SIZE]; + char *driver; + + util_strscpy(subsys, sizeof(subsys), sysname); + driver = strchr(subsys, ':'); + if (driver != NULL) { + driver[0] = '\0'; + driver = &driver[1]; + + util_strscpyl(path, l, "/subsystem/", subsys, "/drivers/", driver, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + + util_strscpyl(path, l, "/bus/", subsys, "/drivers/", driver, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + } + goto out; + } + + util_strscpyl(path, l, "/subsystem/", subsystem, "/devices/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + + util_strscpyl(path, l, "/bus/", subsystem, "/devices/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; + + util_strscpyl(path, l, "/class/", subsystem, "/", sysname, NULL); + if (stat(path_full, &statbuf) == 0) + goto found; +out: + return NULL; +found: + return udev_device_new_from_syspath(udev, path_full); +} + +/** + * udev_device_new_from_environment + * @udev: udev library context + * + * Create new udev device, and fill in information from the + * current process environment. This only works reliable if + * the process is called from a udev rule. It is usually used + * for tools executed from IMPORT= rules. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +UDEV_EXPORT struct udev_device *udev_device_new_from_environment(struct udev *udev) +{ + int i; + struct udev_device *udev_device; + + udev_device = udev_device_new(udev); + if (udev_device == NULL) + return NULL; + udev_device_set_info_loaded(udev_device); + + for (i = 0; environ[i] != NULL; i++) + udev_device_add_property_from_string_parse(udev_device, environ[i]); + + if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) { + info(udev, "missing values, invalid device\n"); + udev_device_unref(udev_device); + udev_device = NULL; + } + + return udev_device; +} + +static struct udev_device *device_new_from_parent(struct udev_device *udev_device) +{ + struct udev_device *udev_device_parent = NULL; + char path[UTIL_PATH_SIZE]; + const char *subdir; + + util_strscpy(path, sizeof(path), udev_device->syspath); + subdir = &path[strlen(udev_get_sys_path(udev_device->udev))+1]; + for (;;) { + char *pos; + + pos = strrchr(subdir, '/'); + if (pos == NULL || pos < &subdir[2]) + break; + pos[0] = '\0'; + udev_device_parent = udev_device_new_from_syspath(udev_device->udev, path); + if (udev_device_parent != NULL) + return udev_device_parent; + } + return NULL; +} + +/** + * udev_device_get_parent: + * @udev_device: the device to start searching from + * + * Find the next parent device, and fill in information from the sys + * device and the udev database entry. + * + * The returned the device is not referenced. It is attached to the + * child device, and will be cleaned up when the child device + * is cleaned up. + * + * It is not necessarily just the upper level directory, empty or not + * recognized sys directories are ignored. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL, if it no parent exist. + **/ +UDEV_EXPORT struct udev_device *udev_device_get_parent(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + if (!udev_device->parent_set) { + udev_device->parent_set = true; + udev_device->parent_device = device_new_from_parent(udev_device); + } + if (udev_device->parent_device != NULL) + dbg(udev_device->udev, "returning existing parent %p\n", udev_device->parent_device); + return udev_device->parent_device; +} + +/** + * udev_device_get_parent_with_subsystem_devtype: + * @udev_device: udev device to start searching from + * @subsystem: the subsystem of the device + * @devtype: the type (DEVTYPE) of the device + * + * Find the next parent device, with a matching subsystem and devtype + * value, and fill in information from the sys device and the udev + * database entry. + * + * If devtype is #NULL, only subsystem is checked, and any devtype will + * match. + * + * The returned the device is not referenced. It is attached to the + * child device, and will be cleaned up when the child device + * is cleaned up. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL if no matching parent exists. + **/ +UDEV_EXPORT struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) +{ + struct udev_device *parent; + + if (subsystem == NULL) + return NULL; + + parent = udev_device_get_parent(udev_device); + while (parent != NULL) { + const char *parent_subsystem; + const char *parent_devtype; + + parent_subsystem = udev_device_get_subsystem(parent); + if (parent_subsystem != NULL && strcmp(parent_subsystem, subsystem) == 0) { + if (devtype == NULL) + break; + parent_devtype = udev_device_get_devtype(parent); + if (parent_devtype != NULL && strcmp(parent_devtype, devtype) == 0) + break; + } + parent = udev_device_get_parent(parent); + } + return parent; +} + +/** + * udev_device_get_udev: + * @udev_device: udev device + * + * Retrieve the udev library context the device was created with. + * + * Returns: the udev library context + **/ +UDEV_EXPORT struct udev *udev_device_get_udev(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->udev; +} + +/** + * udev_device_ref: + * @udev_device: udev device + * + * Take a reference of a udev device. + * + * Returns: the passed udev device + **/ +UDEV_EXPORT struct udev_device *udev_device_ref(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + udev_device->refcount++; + return udev_device; +} + +/** + * udev_device_unref: + * @udev_device: udev device + * + * Drop a reference of a udev device. If the refcount reaches zero, + * the resources of the device will be released. + * + **/ +UDEV_EXPORT void udev_device_unref(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return; + udev_device->refcount--; + if (udev_device->refcount > 0) + return; + if (udev_device->parent_device != NULL) + udev_device_unref(udev_device->parent_device); + free(udev_device->syspath); + free(udev_device->sysname); + free(udev_device->devnode); + free(udev_device->subsystem); + free(udev_device->devtype); + udev_list_cleanup(&udev_device->devlinks_list); + udev_list_cleanup(&udev_device->properties_list); + udev_list_cleanup(&udev_device->sysattr_value_list); + udev_list_cleanup(&udev_device->sysattr_list); + udev_list_cleanup(&udev_device->tags_list); + free(udev_device->action); + free(udev_device->driver); + free(udev_device->devpath_old); + free(udev_device->id_filename); + free(udev_device->envp); + free(udev_device->monitor_buf); + dbg(udev_device->udev, "udev_device: %p released\n", udev_device); + free(udev_device); +} + +/** + * udev_device_get_devpath: + * @udev_device: udev device + * + * Retrieve the kernel devpath value of the udev device. The path + * does not contain the sys mount point, and starts with a '/'. + * + * Returns: the devpath of the udev device + **/ +UDEV_EXPORT const char *udev_device_get_devpath(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->devpath; +} + +/** + * udev_device_get_syspath: + * @udev_device: udev device + * + * Retrieve the sys path of the udev device. The path is an + * absolute path and starts with the sys mount point. + * + * Returns: the sys path of the udev device + **/ +UDEV_EXPORT const char *udev_device_get_syspath(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->syspath; +} + +/** + * udev_device_get_sysname: + * @udev_device: udev device + * + * Returns: the sys name of the device device + **/ +UDEV_EXPORT const char *udev_device_get_sysname(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->sysname; +} + +/** + * udev_device_get_sysnum: + * @udev_device: udev device + * + * Returns: the trailing number of of the device name + **/ +UDEV_EXPORT const char *udev_device_get_sysnum(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->sysnum; +} + +/** + * udev_device_get_devnode: + * @udev_device: udev device + * + * Retrieve the device node file name belonging to the udev device. + * The path is an absolute path, and starts with the device directory. + * + * Returns: the device node file name of the udev device, or #NULL if no device node exists + **/ +UDEV_EXPORT const char *udev_device_get_devnode(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + if (udev_device->devnode != NULL) + return udev_device->devnode; + if (!udev_device->info_loaded) + udev_device_read_uevent_file(udev_device); + return udev_device->devnode; +} + +/** + * udev_device_get_devlinks_list_entry: + * @udev_device: udev device + * + * Retrieve the list of device links pointing to the device file of + * the udev device. The next list entry can be retrieved with + * udev_list_entry_next(), which returns #NULL if no more entries exist. + * The devlink path can be retrieved from the list entry by + * udev_list_entry_get_name(). The path is an absolute path, and starts with + * the device directory. + * + * Returns: the first entry of the device node link list + **/ +UDEV_EXPORT struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + return udev_list_get_entry(&udev_device->devlinks_list); +} + +void udev_device_cleanup_devlinks_list(struct udev_device *udev_device) +{ + udev_device->devlinks_uptodate = false; + udev_list_cleanup(&udev_device->devlinks_list); +} + +/** + * udev_device_get_properties_list_entry: + * @udev_device: udev device + * + * Retrieve the list of key/value device properties of the udev + * device. The next list entry can be retrieved with udev_list_entry_next(), + * which returns #NULL if no more entries exist. The property name + * can be retrieved from the list entry by udev_list_get_name(), + * the property value by udev_list_get_value(). + * + * Returns: the first entry of the property list + **/ +UDEV_EXPORT struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + if (!udev_device->info_loaded) { + udev_device_read_uevent_file(udev_device); + udev_device_read_db(udev_device, NULL); + } + if (!udev_device->devlinks_uptodate) { + char symlinks[UTIL_PATH_SIZE]; + struct udev_list_entry *list_entry; + + udev_device->devlinks_uptodate = true; + list_entry = udev_device_get_devlinks_list_entry(udev_device); + if (list_entry != NULL) { + char *s; + size_t l; + + s = symlinks; + l = util_strpcpyl(&s, sizeof(symlinks), udev_list_entry_get_name(list_entry), NULL); + udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry)) + l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry), NULL); + udev_device_add_property(udev_device, "DEVLINKS", symlinks); + } + } + if (!udev_device->tags_uptodate) { + udev_device->tags_uptodate = true; + if (udev_device_get_tags_list_entry(udev_device) != NULL) { + char tags[UTIL_PATH_SIZE]; + struct udev_list_entry *list_entry; + char *s; + size_t l; + + s = tags; + l = util_strpcpyl(&s, sizeof(tags), ":", NULL); + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) + l = util_strpcpyl(&s, l, udev_list_entry_get_name(list_entry), ":", NULL); + udev_device_add_property(udev_device, "TAGS", tags); + } + } + return udev_list_get_entry(&udev_device->properties_list); +} + +/** + * udev_device_get_action: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have an action string. Usual actions are: add, remove, change, online, + * offline. + * + * Returns: the kernel action value, or #NULL if there is no action value available. + **/ +UDEV_EXPORT const char *udev_device_get_action(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->action; +} + +/** + * udev_device_get_usec_since_initialized: + * @udev_device: udev device + * + * Return the number of microseconds passed since udev set up the + * device for the first time. + * + * This is only implemented for devices with need to store properties + * in the udev database. All other devices return 0 here. + * + * Returns: the number of microseconds since the device was first seen. + **/ +UDEV_EXPORT unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device) +{ + unsigned long long now; + + if (udev_device == NULL) + return 0; + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + if (udev_device->usec_initialized == 0) + return 0; + now = now_usec(); + if (now == 0) + return 0; + return now - udev_device->usec_initialized; +} + +unsigned long long udev_device_get_usec_initialized(struct udev_device *udev_device) +{ + return udev_device->usec_initialized; +} + +void udev_device_set_usec_initialized(struct udev_device *udev_device, unsigned long long usec_initialized) +{ + char num[32]; + + udev_device->usec_initialized = usec_initialized; + snprintf(num, sizeof(num), "%llu", usec_initialized); + udev_device_add_property(udev_device, "USEC_INITIALIZED", num); +} + +/** + * udev_device_get_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * + * The retrieved value is cached in the device. Repeated calls will return the same + * value and not open the attribute again. + * + * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value. + **/ +UDEV_EXPORT const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) +{ + struct udev_list_entry *list_entry; + char path[UTIL_PATH_SIZE]; + char value[4096]; + struct stat statbuf; + int fd; + ssize_t size; + const char *val = NULL; + + if (udev_device == NULL) + return NULL; + if (sysattr == NULL) + return NULL; + + /* look for possibly already cached result */ + list_entry = udev_list_get_entry(&udev_device->sysattr_value_list); + list_entry = udev_list_entry_get_by_name(list_entry, sysattr); + if (list_entry != NULL) { + dbg(udev_device->udev, "got '%s' (%s) from cache\n", + sysattr, udev_list_entry_get_value(list_entry)); + return udev_list_entry_get_value(list_entry); + } + + util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", sysattr, NULL); + if (lstat(path, &statbuf) != 0) { + dbg(udev_device->udev, "no attribute '%s', keep negative entry\n", path); + udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, NULL); + goto out; + } + + if (S_ISLNK(statbuf.st_mode)) { + struct udev_device *dev; + + /* + * Some core links return only the last element of the target path, + * these are just values, the paths should not be exposed. + */ + if (strcmp(sysattr, "driver") == 0 || + strcmp(sysattr, "subsystem") == 0 || + strcmp(sysattr, "module") == 0) { + if (util_get_sys_core_link_value(udev_device->udev, sysattr, + udev_device->syspath, value, sizeof(value)) < 0) + return NULL; + dbg(udev_device->udev, "cache '%s' with link value '%s'\n", sysattr, value); + list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, value); + val = udev_list_entry_get_value(list_entry); + goto out; + } + + /* resolve link to a device and return its syspath */ + util_strscpyl(path, sizeof(path), udev_device->syspath, "/", sysattr, NULL); + dev = udev_device_new_from_syspath(udev_device->udev, path); + if (dev != NULL) { + list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, + udev_device_get_syspath(dev)); + val = udev_list_entry_get_value(list_entry); + udev_device_unref(dev); + } + + goto out; + } + + /* skip directories */ + if (S_ISDIR(statbuf.st_mode)) + goto out; + + /* skip non-readable files */ + if ((statbuf.st_mode & S_IRUSR) == 0) + goto out; + + /* read attribute value */ + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + dbg(udev_device->udev, "attribute '%s' can not be opened\n", path); + goto out; + } + size = read(fd, value, sizeof(value)); + close(fd); + if (size < 0) + goto out; + if (size == sizeof(value)) + goto out; + + /* got a valid value, store it in cache and return it */ + value[size] = '\0'; + util_remove_trailing_chars(value, '\n'); + dbg(udev_device->udev, "'%s' has attribute value '%s'\n", path, value); + list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, value); + val = udev_list_entry_get_value(list_entry); +out: + return val; +} + +static int udev_device_sysattr_list_read(struct udev_device *udev_device) +{ + struct dirent *dent; + DIR *dir; + int num = 0; + + if (udev_device == NULL) + return -1; + if (udev_device->sysattr_list_read) + return 0; + + dir = opendir(udev_device_get_syspath(udev_device)); + if (!dir) { + dbg(udev_device->udev, "sysfs dir '%s' can not be opened\n", + udev_device_get_syspath(udev_device)); + return -1; + } + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char path[UTIL_PATH_SIZE]; + struct stat statbuf; + + /* only handle symlinks and regular files */ + if (dent->d_type != DT_LNK && dent->d_type != DT_REG) + continue; + + util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", dent->d_name, NULL); + if (lstat(path, &statbuf) != 0) + continue; + if ((statbuf.st_mode & S_IRUSR) == 0) + continue; + + udev_list_entry_add(&udev_device->sysattr_list, dent->d_name, NULL); + num++; + } + + closedir(dir); + dbg(udev_device->udev, "found %d sysattrs for '%s'\n", num, udev_device_get_syspath(udev_device)); + udev_device->sysattr_list_read = true; + + return num; +} + +/** + * udev_device_get_sysattr_list_entry: + * @udev_device: udev device + * + * Retrieve the list of available sysattrs, with value being empty; + * This just return all available sysfs attributes for a particular + * device without reading their values. + * + * Returns: the first entry of the property list + **/ +UDEV_EXPORT struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) +{ + if (!udev_device->sysattr_list_read) { + int ret; + ret = udev_device_sysattr_list_read(udev_device); + if (0 > ret) + return NULL; + } + + return udev_list_get_entry(&udev_device->sysattr_list); +} + +int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath) +{ + const char *pos; + size_t len; + + free(udev_device->syspath); + udev_device->syspath = strdup(syspath); + if (udev_device->syspath == NULL) + return -ENOMEM; + udev_device->devpath = &udev_device->syspath[strlen(udev_get_sys_path(udev_device->udev))]; + udev_device_add_property(udev_device, "DEVPATH", udev_device->devpath); + + pos = strrchr(udev_device->syspath, '/'); + if (pos == NULL) + return -EINVAL; + udev_device->sysname = strdup(&pos[1]); + if (udev_device->sysname == NULL) + return -ENOMEM; + + /* some devices have '!' in their name, change that to '/' */ + len = 0; + while (udev_device->sysname[len] != '\0') { + if (udev_device->sysname[len] == '!') + udev_device->sysname[len] = '/'; + len++; + } + + /* trailing number */ + while (len > 0 && isdigit(udev_device->sysname[--len])) + udev_device->sysnum = &udev_device->sysname[len]; + + /* sysname is completely numeric */ + if (len == 0) + udev_device->sysnum = NULL; + + return 0; +} + +int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode) +{ + free(udev_device->devnode); + if (devnode[0] != '/') { + if (asprintf(&udev_device->devnode, "%s/%s", udev_get_dev_path(udev_device->udev), devnode) < 0) + udev_device->devnode = NULL; + } else { + udev_device->devnode = strdup(devnode); + } + if (udev_device->devnode == NULL) + return -ENOMEM; + udev_device_add_property(udev_device, "DEVNAME", udev_device->devnode); + return 0; +} + +int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink, int unique) +{ + struct udev_list_entry *list_entry; + + udev_device->devlinks_uptodate = false; + list_entry = udev_list_entry_add(&udev_device->devlinks_list, devlink, NULL); + if (list_entry == NULL) + return -ENOMEM; + if (unique) + udev_list_entry_set_num(list_entry, true); + return 0; +} + +const char *udev_device_get_id_filename(struct udev_device *udev_device) +{ + if (udev_device->id_filename == NULL) { + if (udev_device_get_subsystem(udev_device) == NULL) + return NULL; + + if (major(udev_device_get_devnum(udev_device)) > 0) { + /* use dev_t -- b259:131072, c254:0 */ + if (asprintf(&udev_device->id_filename, "%c%u:%u", + strcmp(udev_device_get_subsystem(udev_device), "block") == 0 ? 'b' : 'c', + major(udev_device_get_devnum(udev_device)), + minor(udev_device_get_devnum(udev_device))) < 0) + udev_device->id_filename = NULL; + } else if (udev_device_get_ifindex(udev_device) > 0) { + /* use netdev ifindex -- n3 */ + if (asprintf(&udev_device->id_filename, "n%u", udev_device_get_ifindex(udev_device)) < 0) + udev_device->id_filename = NULL; + } else { + /* + * use $subsys:$syname -- pci:0000:00:1f.2 + * sysname() has '!' translated, get it from devpath + */ + const char *sysname; + sysname = strrchr(udev_device->devpath, '/'); + if (sysname == NULL) + return NULL; + sysname = &sysname[1]; + if (asprintf(&udev_device->id_filename, "+%s:%s", udev_device_get_subsystem(udev_device), sysname) < 0) + udev_device->id_filename = NULL; + } + } + return udev_device->id_filename; +} + +/** + * udev_device_get_is_initialized: + * @udev_device: udev device + * + * Check if udev has already handled the device and has set up + * device node permissions and context, or has renamed a network + * device. + * + * This is only implemented for devices with a device node + * or network interfaces. All other devices return 1 here. + * + * Returns: 1 if the device is set up. 0 otherwise. + **/ +UDEV_EXPORT int udev_device_get_is_initialized(struct udev_device *udev_device) +{ + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + return udev_device->is_initialized; +} + +void udev_device_set_is_initialized(struct udev_device *udev_device) +{ + udev_device->is_initialized = true; +} + +int udev_device_add_tag(struct udev_device *udev_device, const char *tag) +{ + if (strchr(tag, ':') != NULL || strchr(tag, ' ') != NULL) + return -EINVAL; + udev_device->tags_uptodate = false; + if (udev_list_entry_add(&udev_device->tags_list, tag, NULL) != NULL) + return 0; + return -ENOMEM; +} + +void udev_device_cleanup_tags_list(struct udev_device *udev_device) +{ + udev_device->tags_uptodate = false; + udev_list_cleanup(&udev_device->tags_list); +} + +/** + * udev_device_get_tags_list_entry: + * @udev_device: udev device + * + * Retrieve the list of tags attached to the udev device. The next + * list entry can be retrieved with udev_list_entry_next(), + * which returns #NULL if no more entries exist. The tag string + * can be retrieved from the list entry by udev_list_get_name(). + * + * Returns: the first entry of the tag list + **/ +UDEV_EXPORT struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + return udev_list_get_entry(&udev_device->tags_list); +} + +UDEV_EXPORT int udev_device_has_tag(struct udev_device *udev_device, const char *tag) +{ + struct udev_list_entry *list_entry; + + if (udev_device == NULL) + return false; + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + list_entry = udev_device_get_tags_list_entry(udev_device); + if (udev_list_entry_get_by_name(list_entry, tag) != NULL) + return true; + return false; +} + +#define ENVP_SIZE 128 +#define MONITOR_BUF_SIZE 4096 +static int update_envp_monitor_buf(struct udev_device *udev_device) +{ + struct udev_list_entry *list_entry; + char *s; + size_t l; + unsigned int i; + + /* monitor buffer of property strings */ + free(udev_device->monitor_buf); + udev_device->monitor_buf_len = 0; + udev_device->monitor_buf = malloc(MONITOR_BUF_SIZE); + if (udev_device->monitor_buf == NULL) + return -ENOMEM; + + /* envp array, strings will point into monitor buffer */ + if (udev_device->envp == NULL) + udev_device->envp = malloc(sizeof(char *) * ENVP_SIZE); + if (udev_device->envp == NULL) + return -ENOMEM; + + i = 0; + s = udev_device->monitor_buf; + l = MONITOR_BUF_SIZE; + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device)) { + const char *key; + + key = udev_list_entry_get_name(list_entry); + /* skip private variables */ + if (key[0] == '.') + continue; + + /* add string to envp array */ + udev_device->envp[i++] = s; + if (i+1 >= ENVP_SIZE) + return -EINVAL; + + /* add property string to monitor buffer */ + l = util_strpcpyl(&s, l, key, "=", udev_list_entry_get_value(list_entry), NULL); + if (l == 0) + return -EINVAL; + /* advance past the trailing '\0' that util_strpcpyl() guarantees */ + s++; + l--; + } + udev_device->envp[i] = NULL; + udev_device->monitor_buf_len = s - udev_device->monitor_buf; + udev_device->envp_uptodate = true; + dbg(udev_device->udev, "filled envp/monitor buffer, %u properties, %zu bytes\n", + i, udev_device->monitor_buf_len); + return 0; +} + +char **udev_device_get_properties_envp(struct udev_device *udev_device) +{ + if (!udev_device->envp_uptodate) + if (update_envp_monitor_buf(udev_device) != 0) + return NULL; + return udev_device->envp; +} + +ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf) +{ + if (!udev_device->envp_uptodate) + if (update_envp_monitor_buf(udev_device) != 0) + return -EINVAL; + *buf = udev_device->monitor_buf; + return udev_device->monitor_buf_len; +} + +int udev_device_set_action(struct udev_device *udev_device, const char *action) +{ + free(udev_device->action); + udev_device->action = strdup(action); + if (udev_device->action == NULL) + return -ENOMEM; + udev_device_add_property(udev_device, "ACTION", udev_device->action); + return 0; +} + +int udev_device_get_devlink_priority(struct udev_device *udev_device) +{ + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + return udev_device->devlink_priority; +} + +int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio) +{ + udev_device->devlink_priority = prio; + return 0; +} + +int udev_device_get_watch_handle(struct udev_device *udev_device) +{ + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + return udev_device->watch_handle; +} + +int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) +{ + udev_device->watch_handle = handle; + return 0; +} + +bool udev_device_get_db_persist(struct udev_device *udev_device) +{ + return udev_device->db_persist; +} + +void udev_device_set_db_persist(struct udev_device *udev_device) +{ + udev_device->db_persist = true; +} |