X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=libudev%2Flibudev-device.c;h=ffde21ad57d9d5274b56ff6871a4c30cfa19dcd1;hp=7644e6c63d26ad192b704665d2b835268f03b127;hb=8958da13c72024c4eaa2996b86fce2959e452db4;hpb=456719b6f941d917e7c9444fa6149f35a188d785 diff --git a/libudev/libudev-device.c b/libudev/libudev-device.c index 7644e6c63..ffde21ad5 100644 --- a/libudev/libudev-device.c +++ b/libudev/libudev-device.c @@ -1,7 +1,7 @@ /* * libudev - interface to udev device information * - * Copyright (C) 2008-2009 Kay Sievers + * Copyright (C) 2008-2010 Kay Sievers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -13,12 +13,17 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include +#include +#include #include "libudev.h" #include "libudev-private.h" @@ -30,7 +35,7 @@ * 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 subsytem. + * a unique name inside that subsystem. */ /** @@ -51,100 +56,219 @@ struct udev_device { char *driver; char *action; char *devpath_old; + char *sysname_old; char *knodename; + char *id_filename; char **envp; char *monitor_buf; size_t monitor_buf_len; struct udev_list_node devlinks_list; struct udev_list_node properties_list; + struct udev_list_node sysattr_value_list; struct udev_list_node sysattr_list; + struct udev_list_node tags_list; unsigned long long int seqnum; - int event_timeout; + unsigned long long int usec_initialized; int timeout; - int num_fake_partitions; int devlink_priority; int refcount; dev_t devnum; + int ifindex; int watch_handle; - unsigned int parent_set:1; - unsigned int subsystem_set:1; - unsigned int devtype_set:1; - unsigned int devlinks_uptodate:1; - unsigned int envp_uptodate:1; - unsigned int driver_set:1; - unsigned int info_loaded:1; - unsigned int ignore_remove:1; + 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; }; -static size_t devpath_to_db_path(struct udev *udev, const char *devpath, char *filename, size_t len) +struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value) { - char *s; - size_t l; + udev_device->envp_uptodate = false; + if (value == NULL) { + struct udev_list_entry *list_entry; - s = filename; - l = util_strpcpyl(&s, len, udev_get_dev_path(udev), "/.udev/db/", NULL); - return util_path_encode(devpath, s, l); + 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->udev, &udev_device->properties_list, key, value, UDEV_LIST_UNIQUE); } -int udev_device_read_db(struct udev_device *udev_device) +static struct udev_list_entry *udev_device_add_property_from_string(struct udev_device *udev_device, const char *property) { - struct stat stats; - char filename[UTIL_PATH_SIZE]; - char line[UTIL_LINE_SIZE]; - FILE *f; + char name[UTIL_LINE_SIZE]; + char *val; - devpath_to_db_path(udev_device->udev, udev_device->devpath, filename, sizeof(filename)); + 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); +} - if (lstat(filename, &stats) != 0) { - dbg(udev_device->udev, "no db file to read %s: %m\n", filename); - return -1; - } - if ((stats.st_mode & S_IFMT) == S_IFLNK) { - char target[UTIL_PATH_SIZE]; - char devnode[UTIL_PATH_SIZE]; - int target_len; +/* + * 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) { + if (property[8] == '/') + udev_device_set_devnode(udev_device, &property[8]); + else + udev_device_set_knodename(udev_device, &property[8]); + } else if (strncmp(property, "DEVLINKS=", 9) == 0) { + char devlinks[UTIL_PATH_SIZE]; + char *slink; char *next; - target_len = readlink(filename, target, sizeof(target)); - if (target_len > 0) - target[target_len] = '\0'; - else { - dbg(udev_device->udev, "error reading db link %s: %m\n", filename); - return -1; - } - - next = strchr(target, ' '); - if (next != NULL) { + util_strscpy(devlinks, sizeof(devlinks), &property[9]); + slink = devlinks; + next = strchr(slink, ' '); + while (next != NULL) { next[0] = '\0'; - next = &next[1]; + udev_device_add_devlink(udev_device, slink, 0); + slink = &next[1]; + next = strchr(slink, ' '); } - util_strscpyl(devnode, sizeof(devnode), udev_get_dev_path(udev_device->udev), "/", target, NULL); - udev_device_set_devnode(udev_device, devnode); - while (next != NULL) { - char devlink[UTIL_PATH_SIZE]; - const char *lnk; + 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; - lnk = next; - next = strchr(next, ' '); - if (next != NULL) { + 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 = &next[1]; + next++; + udev_device_add_tag(udev_device, tag); } - util_strscpyl(devlink, sizeof(devlink), udev_get_dev_path(udev_device->udev), "/", lnk, NULL); - udev_device_add_devlink(udev_device, devlink); } - info(udev_device->udev, "device %p filled with db symlink data '%s'\n", udev_device, udev_device->devnode); - return 0; + } 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, "TIMEOUT=", 8) == 0) { + udev_device_set_timeout(udev_device, strtoull(&property[8], NULL, 10)); + } else if (strncmp(property, "IFINDEX=", 8) == 0) { + udev_device_set_ifindex(udev_device, strtoull(&property[8], NULL, 10)); + } 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. + **/ +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(filename, "r"); + f = fopen(dbfile, "re"); if (f == NULL) { - dbg(udev_device->udev, "error reading db file %s: %m\n", filename); + 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) @@ -158,26 +282,24 @@ int udev_device_read_db(struct udev_device *udev_device) break; case 'S': util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/", val, NULL); - udev_device_add_devlink(udev_device, filename); + udev_device_add_devlink(udev_device, filename, 0); break; case 'L': udev_device_set_devlink_priority(udev_device, atoi(val)); break; - case 'T': - udev_device_set_event_timeout(udev_device, atoi(val)); - break; - case 'A': - udev_device_set_num_fake_partitions(udev_device, atoi(val)); - break; - case 'R': - udev_device_set_ignore_remove(udev_device, atoi(val)); - break; case 'E': - udev_device_add_property_from_string(udev_device, val); + 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); @@ -194,10 +316,14 @@ int udev_device_read_uevent_file(struct udev_device *udev_device) 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, "r"); + f = fopen(filename, "re"); if (f == NULL) return -1; + udev_device->uevent_loaded = true; while (fgets(line, sizeof(line), f)) { char *pos; @@ -213,6 +339,8 @@ int udev_device_read_uevent_file(struct udev_device *udev_device) 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_knodename(udev_device, &line[8]); @@ -220,21 +348,13 @@ int udev_device_read_uevent_file(struct udev_device *udev_device) } udev_device->devnum = makedev(maj, min); - fclose(f); return 0; } -static void device_load_info(struct udev_device *device) -{ - device->info_loaded = 1; - udev_device_read_uevent_file(device); - udev_device_read_db(device); -} - void udev_device_set_info_loaded(struct udev_device *device) { - device->info_loaded = 1; + device->info_loaded = true; } struct udev_device *udev_device_new(struct udev *udev) @@ -252,8 +372,10 @@ struct udev_device *udev_device_new(struct udev *udev) udev_device->udev = udev; udev_list_init(&udev_device->devlinks_list); udev_list_init(&udev_device->properties_list); + udev_list_init(&udev_device->sysattr_value_list); udev_list_init(&udev_device->sysattr_list); - udev_device->event_timeout = -1; + udev_list_init(&udev_device->tags_list); + udev_device->timeout = -1; udev_device->watch_handle = -1; /* copy global properties */ udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev)) @@ -270,7 +392,7 @@ struct udev_device *udev_device_new(struct udev *udev) * @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 sypath is the absolute + * 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 @@ -311,25 +433,7 @@ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char * util_strscpy(path, sizeof(path), syspath); util_resolve_sys_link(udev, path, sizeof(path)); - /* try to resolve the silly block layout if needed */ - if (strncmp(&path[len], "/block/", 7) == 0) { - char block[UTIL_PATH_SIZE]; - char part[UTIL_PATH_SIZE]; - - util_strscpy(block, sizeof(block), path); - pos = strrchr(block, '/'); - if (pos == NULL) - return NULL; - util_strscpy(part, sizeof(part), pos); - pos[0] = '\0'; - if (util_resolve_sys_link(udev, block, sizeof(block)) == 0) - util_strscpyl(path, sizeof(path), block, part, NULL); - } - - /* path exists in sys */ - if (strncmp(&syspath[len], "/devices/", 9) == 0 || - strncmp(&syspath[len], "/class/", 7) == 0 || - strncmp(&syspath[len], "/block/", 7) == 0) { + if (strncmp(&path[len], "/devices/", 9) == 0) { char file[UTIL_PATH_SIZE]; /* all "devices" require a "uevent" file */ @@ -363,10 +467,9 @@ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char * * @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. Character and block device numbers - * are not unique across the two types, they do not share the same - * range of numbers. + * 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. @@ -377,9 +480,6 @@ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, de { char path[UTIL_PATH_SIZE]; const char *type_str; - struct udev_enumerate *udev_enumerate; - struct udev_list_entry *list_entry; - struct udev_device *device = NULL; if (type == 'b') type_str = "block"; @@ -388,52 +488,76 @@ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, de else return NULL; - /* /sys/dev/{block,char}/: link */ - snprintf(path, sizeof(path), "%s/dev/%s/%u:%u", udev_get_sys_path(udev), - type_str, major(devnum), minor(devnum)); - if (util_resolve_sys_link(udev, path, sizeof(path)) == 0) - return udev_device_new_from_syspath(udev, path); + /* use /sys/dev/{block,char}/: 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); +} - udev_enumerate = udev_enumerate_new(udev); - if (udev_enumerate == NULL) - return NULL; +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; - /* fallback to search sys devices for the major/minor */ - if (type == 'b') - udev_enumerate_add_match_subsystem(udev_enumerate, "block"); - else if (type == 'c') - udev_enumerate_add_nomatch_subsystem(udev_enumerate, "block"); - udev_enumerate_scan_devices(udev_enumerate); - udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(udev_enumerate)) { - struct udev_device *device_loop; - - device_loop = udev_device_new_from_syspath(udev, udev_list_entry_get_name(list_entry)); - if (device_loop != NULL) { - if (udev_device_get_devnum(device_loop) == devnum) { - if (type == 'b' && strcmp(udev_device_get_subsystem(device_loop), "block") != 0) - continue; - if (type == 'c' && strcmp(udev_device_get_subsystem(device_loop), "block") == 0) - continue; - device = device_loop; - break; - } - udev_device_unref(device_loop); + 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_enumerate_unref(udev_enumerate); - return device; } /** * udev_device_new_from_subsystem_sysname: * @udev: udev library context - * @subsystem: the subsytem of the device + * @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 subsytem and name string of the device, like "mem", - * "zero", or "block", "sda". + * 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. @@ -510,26 +634,51 @@ 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 + **/ +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; - /* follow "device" link in deprecated sys layout */ - if (strncmp(udev_device->devpath, "/class/", 7) == 0 || - strncmp(udev_device->devpath, "/block/", 7) == 0) { - util_strscpyl(path, sizeof(path), udev_device->syspath, "/device", NULL); - if (util_resolve_sys_link(udev_device->udev, path, sizeof(path)) == 0) { - udev_device_parent = udev_device_new_from_syspath(udev_device->udev, path); - if (udev_device_parent != NULL) - return udev_device_parent; - } - } - util_strscpy(path, sizeof(path), udev_device->syspath); subdir = &path[strlen(udev_get_sys_path(udev_device->udev))+1]; - while (1) { + for (;;) { char *pos; pos = strrchr(subdir, '/'); @@ -554,7 +703,7 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic * child device, and will be cleaned up when the child device * is cleaned up. * - * It is not neccessarily just the upper level directory, empty or not + * 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 @@ -567,7 +716,7 @@ 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 = 1; + udev_device->parent_set = true; udev_device->parent_device = device_new_from_parent(udev_device); } if (udev_device->parent_device != NULL) @@ -578,13 +727,16 @@ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) /** * udev_device_get_parent_with_subsystem_devtype: * @udev_device: udev device to start searching from - * @subsystem: the subsytem of the device + * @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. @@ -674,11 +826,15 @@ void udev_device_unref(struct udev_device *udev_device) free(udev_device->devtype); udev_list_cleanup_entries(udev_device->udev, &udev_device->devlinks_list); udev_list_cleanup_entries(udev_device->udev, &udev_device->properties_list); + udev_list_cleanup_entries(udev_device->udev, &udev_device->sysattr_value_list); + udev_list_cleanup_entries(udev_device->udev, &udev_device->sysattr_list); + udev_list_cleanup_entries(udev_device->udev, &udev_device->tags_list); free(udev_device->action); free(udev_device->driver); free(udev_device->devpath_old); + free(udev_device->sysname_old); free(udev_device->knodename); - udev_list_cleanup_entries(udev_device->udev, &udev_device->sysattr_list); + 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); @@ -756,8 +912,21 @@ const char *udev_device_get_devnode(struct udev_device *udev_device) { if (udev_device == NULL) return NULL; - if (!udev_device->info_loaded) - device_load_info(udev_device); + if (!udev_device->info_loaded) { + udev_device_read_uevent_file(udev_device); + udev_device_read_db(udev_device, NULL); + } + + /* we might get called before we handled an event and have a db, use the kernel-provided name */ + if (udev_device->devnode == NULL && udev_device_get_knodename(udev_device) != NULL) { + char filename[UTIL_NAME_SIZE]; + + util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/", + udev_device_get_knodename(udev_device), NULL); + udev_device_set_devnode(udev_device, filename); + return udev_device->devnode; + } + return udev_device->devnode; } @@ -777,8 +946,8 @@ const char *udev_device_get_subsystem(struct udev_device *udev_device) if (udev_device == NULL) return NULL; if (!udev_device->subsystem_set) { - udev_device->subsystem_set = 1; - /* read "subsytem" link */ + udev_device->subsystem_set = true; + /* read "subsystem" link */ if (util_get_sys_subsystem(udev_device->udev, udev_device->syspath, subsystem, sizeof(subsystem)) > 0) { udev_device_set_subsystem(udev_device, subsystem); return udev_device->subsystem; @@ -815,9 +984,8 @@ 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 = 1; - if (!udev_device->info_loaded) - udev_device_read_uevent_file(udev_device); + udev_device->devtype_set = true; + udev_device_read_uevent_file(udev_device); } return udev_device->devtype; } @@ -840,13 +1008,13 @@ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device * if (udev_device == NULL) return NULL; if (!udev_device->info_loaded) - device_load_info(udev_device); + 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 = 0; + udev_device->devlinks_uptodate = false; udev_list_cleanup_entries(udev_device->udev, &udev_device->devlinks_list); } @@ -866,13 +1034,15 @@ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device { if (udev_device == NULL) return NULL; - if (!udev_device->info_loaded) - device_load_info(udev_device); + 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 = 1; + udev_device->devlinks_uptodate = true; list_entry = udev_device_get_devlinks_list_entry(udev_device); if (list_entry != NULL) { char *s; @@ -885,6 +1055,21 @@ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device 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); } @@ -901,7 +1086,7 @@ const char *udev_device_get_driver(struct udev_device *udev_device) if (udev_device == NULL) return NULL; if (!udev_device->driver_set) { - udev_device->driver_set = 1; + udev_device->driver_set = true; if (util_get_sys_driver(udev_device->udev, udev_device->syspath, driver, sizeof(driver)) > 0) udev_device->driver = strdup(driver); } @@ -919,7 +1104,7 @@ dev_t udev_device_get_devnum(struct udev_device *udev_device) if (udev_device == NULL) return makedev(0, 0); if (!udev_device->info_loaded) - device_load_info(udev_device); + udev_device_read_uevent_file(udev_device); return udev_device->devnum; } @@ -956,6 +1141,44 @@ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device) return udev_device->seqnum; } +/** + * 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. + **/ +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) +{ + udev_device->usec_initialized = usec_initialized; +} + /** * udev_device_get_sysattr_value: * @udev_device: udev device @@ -982,7 +1205,7 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const return NULL; /* look for possibly already cached result */ - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_device->sysattr_list)) { + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_device->sysattr_value_list)) { if (strcmp(udev_list_entry_get_name(list_entry), sysattr) == 0) { dbg(udev_device->udev, "got '%s' (%s) from cache\n", sysattr, udev_list_entry_get_value(list_entry)); @@ -993,7 +1216,7 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const 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->udev, &udev_device->sysattr_list, sysattr, NULL, 0, 0); + udev_list_entry_add(udev_device->udev, &udev_device->sysattr_value_list, sysattr, NULL, 0); goto out; } @@ -1009,16 +1232,18 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const goto out; len = readlink(path, target, sizeof(target)); - if (len > 0) { - target[len] = '\0'; - pos = strrchr(target, '/'); - if (pos != NULL) { - pos = &pos[1]; - dbg(udev_device->udev, "cache '%s' with link value '%s'\n", sysattr, pos); - list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_list, sysattr, pos, 0, 0); - val = udev_list_entry_get_value(list_entry); - } + if (len <= 0 || len == sizeof(target)) + goto out; + target[len] = '\0'; + + pos = strrchr(target, '/'); + if (pos != NULL) { + pos = &pos[1]; + dbg(udev_device->udev, "cache '%s' with link value '%s'\n", sysattr, pos); + list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_value_list, sysattr, pos, 0); + val = udev_list_entry_get_value(list_entry); } + goto out; } @@ -1031,7 +1256,7 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const goto out; /* read attribute value */ - fd = open(path, O_RDONLY); + fd = open(path, O_RDONLY|O_CLOEXEC); if (fd < 0) { dbg(udev_device->udev, "attribute '%s' can not be opened\n", path); goto out; @@ -1047,12 +1272,78 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const 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->udev, &udev_device->sysattr_list, sysattr, value, 0, 0); + list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_value_list, sysattr, value, 0); 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->udev, &udev_device->sysattr_list, + dent->d_name, NULL, 0); + 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 + **/ +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; @@ -1097,7 +1388,7 @@ int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsy udev_device->subsystem = strdup(subsystem); if (udev_device->subsystem == NULL) return -ENOMEM; - udev_device->subsystem_set = 1; + udev_device->subsystem_set = true; udev_device_add_property(udev_device, "SUBSYSTEM", udev_device->subsystem); return 0; } @@ -1108,7 +1399,7 @@ int udev_device_set_devtype(struct udev_device *udev_device, const char *devtype udev_device->devtype = strdup(devtype); if (udev_device->devtype == NULL) return -ENOMEM; - udev_device->devtype_set = 1; + udev_device->devtype_set = true; udev_device_add_property(udev_device, "DEVTYPE", udev_device->devtype); return 0; } @@ -1117,72 +1408,129 @@ int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode { free(udev_device->devnode); udev_device->devnode = strdup(devnode); - if (devnode == NULL) - return 0; 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 udev_device_add_devlink(struct udev_device *udev_device, const char *devlink, int unique) { - udev_device->devlinks_uptodate = 0; - if (udev_list_entry_add(udev_device->udev, &udev_device->devlinks_list, devlink, NULL, 1, 0) == NULL) + struct udev_list_entry *list_entry; + + udev_device->devlinks_uptodate = false; + list_entry = udev_list_entry_add(udev_device->udev, &udev_device->devlinks_list, devlink, NULL, UDEV_LIST_UNIQUE); + if (list_entry == NULL) return -ENOMEM; + if (unique) + udev_list_entry_set_num(list_entry, true); return 0; } -struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value) +const char *udev_device_get_id_filename(struct udev_device *udev_device) { - udev_device->envp_uptodate = 0; - if (value == NULL) { - struct udev_list_entry *list_entry; + if (udev_device->id_filename == NULL) { + if (udev_device_get_subsystem(udev_device) == NULL) + return NULL; - 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; + 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_list_entry_add(udev_device->udev, &udev_device->properties_list, key, value, 1, 0); + return udev_device->id_filename; } -struct udev_list_entry *udev_device_add_property_from_string(struct udev_device *udev_device, const char *property) +/** + * 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. + **/ +int udev_device_get_is_initialized(struct udev_device *udev_device) { - char name[UTIL_PATH_SIZE]; - char *val; + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + return udev_device->is_initialized; +} - 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); +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->udev, &udev_device->tags_list, tag, NULL, UDEV_LIST_UNIQUE) != NULL) + return 0; + return -ENOMEM; +} + +void udev_device_cleanup_tags_list(struct udev_device *udev_device) +{ + udev_device->tags_uptodate = false; + udev_list_cleanup_entries(udev_device->udev, &udev_device->tags_list); } /** - * udev_device_get_property_value: + * udev_device_get_tags_list_entry: * @udev_device: udev device - * @key: property name * - * Returns: the value of a device property, or #NULL if there is no such property. + * 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 **/ -const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) { - struct udev_list_entry *list_entry; - if (udev_device == NULL) return NULL; - if (key == NULL) - return NULL; + return udev_list_get_entry(&udev_device->tags_list); +} - 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_has_tag(struct udev_device *udev_device, const char *tag) +{ + struct udev_list_entry *list_entry; + + if (!udev_device->info_loaded) + udev_device_read_db(udev_device, NULL); + list_entry = udev_device_get_tags_list_entry(udev_device); + list_entry = udev_list_entry_get_by_name(list_entry, tag); + if (list_entry != NULL) + return 1; + return 0; } #define ENVP_SIZE 128 @@ -1227,11 +1575,13 @@ static int update_envp_monitor_buf(struct udev_device *udev_device) 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 = 1; + 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; @@ -1270,7 +1620,7 @@ int udev_device_set_driver(struct udev_device *udev_device, const char *driver) udev_device->driver = strdup(driver); if (udev_device->driver == NULL) return -ENOMEM; - udev_device->driver_set = 1; + udev_device->driver_set = true; udev_device_add_property(udev_device, "DRIVER", udev_device->driver); return 0; } @@ -1282,13 +1632,39 @@ const char *udev_device_get_devpath_old(struct udev_device *udev_device) int udev_device_set_devpath_old(struct udev_device *udev_device, const char *devpath_old) { + const char *pos; + size_t len; + + 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; + udev_device->sysname_old = strdup(&pos[1]); + if (udev_device->sysname_old == NULL) + return -ENOMEM; + + /* some devices have '!' in their name, change that to '/' */ + len = 0; + while (udev_device->sysname_old[len] != '\0') { + if (udev_device->sysname_old[len] == '!') + udev_device->sysname_old[len] = '/'; + len++; + } return 0; } +const char *udev_device_get_sysname_old(struct udev_device *udev_device) +{ + if (udev_device == NULL) + return NULL; + return udev_device->sysname_old; +} + const char *udev_device_get_knodename(struct udev_device *udev_device) { return udev_device->knodename; @@ -1296,9 +1672,13 @@ const char *udev_device_get_knodename(struct udev_device *udev_device) int udev_device_set_knodename(struct udev_device *udev_device, const char *knodename) { + free(udev_device->knodename); udev_device->knodename = strdup(knodename); if (udev_device->knodename == NULL) return -ENOMEM; + /* do not overwrite the udev property with the kernel property */ + if (udev_device->devnode == NULL) + udev_device_add_property(udev_device, "DEVNAME", udev_device->knodename); return 0; } @@ -1309,19 +1689,11 @@ int udev_device_get_timeout(struct udev_device *udev_device) int udev_device_set_timeout(struct udev_device *udev_device, int timeout) { - udev_device->timeout = timeout; - return 0; -} -int udev_device_get_event_timeout(struct udev_device *udev_device) -{ - if (!udev_device->info_loaded) - device_load_info(udev_device); - return udev_device->event_timeout; -} + char num[32]; -int udev_device_set_event_timeout(struct udev_device *udev_device, int event_timeout) -{ - udev_device->event_timeout = event_timeout; + udev_device->timeout = timeout; + snprintf(num, sizeof(num), "%u", timeout); + udev_device_add_property(udev_device, "TIMEOUT", num); return 0; } @@ -1348,54 +1720,55 @@ int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum) return 0; } -int udev_device_get_num_fake_partitions(struct udev_device *udev_device) +int udev_device_get_devlink_priority(struct udev_device *udev_device) { if (!udev_device->info_loaded) - device_load_info(udev_device); - return udev_device->num_fake_partitions; + udev_device_read_db(udev_device, NULL); + return udev_device->devlink_priority; } -int udev_device_set_num_fake_partitions(struct udev_device *udev_device, int num) +int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio) { - udev_device->num_fake_partitions = num; + udev_device->devlink_priority = prio; return 0; } -int udev_device_get_devlink_priority(struct udev_device *udev_device) +int udev_device_get_watch_handle(struct udev_device *udev_device) { if (!udev_device->info_loaded) - device_load_info(udev_device); - return udev_device->devlink_priority; + udev_device_read_db(udev_device, NULL); + return udev_device->watch_handle; } -int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio) +int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) { - udev_device->devlink_priority = prio; + udev_device->watch_handle = handle; return 0; } -int udev_device_get_ignore_remove(struct udev_device *udev_device) +int udev_device_get_ifindex(struct udev_device *udev_device) { if (!udev_device->info_loaded) - device_load_info(udev_device); - return udev_device->ignore_remove; + udev_device_read_uevent_file(udev_device); + return udev_device->ifindex; } -int udev_device_set_ignore_remove(struct udev_device *udev_device, int ignore) +int udev_device_set_ifindex(struct udev_device *udev_device, int ifindex) { - udev_device->ignore_remove = ignore; + char num[32]; + + udev_device->ifindex = ifindex; + snprintf(num, sizeof(num), "%u", ifindex); + udev_device_add_property(udev_device, "IFINDEX", num); return 0; } -int udev_device_get_watch_handle(struct udev_device *udev_device) +bool udev_device_get_db_persist(struct udev_device *udev_device) { - if (!udev_device->info_loaded) - device_load_info(udev_device); - return udev_device->watch_handle; + return udev_device->db_persist; } -int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) +void udev_device_set_db_persist(struct udev_device *udev_device) { - udev_device->watch_handle = handle; - return 0; + udev_device->db_persist = true; }