From 28460195c2ae90892bf556aff2b80705a8f37795 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Thu, 22 Apr 2010 18:12:36 +0200 Subject: [PATCH] add TAG= to improve event filtering and device enumeration --- Makefile.am | 4 +- libudev/docs/libudev-sections.txt | 2 + libudev/exported_symbols | 1 + libudev/libudev-device-private.c | 46 ++++++- libudev/libudev-device.c | 159 ++++++++++++++++------ libudev/libudev-enumerate.c | 197 ++++++++++++++++++--------- libudev/libudev-monitor.c | 218 ++++++++++++++++++++++-------- libudev/libudev-private.h | 8 +- libudev/libudev-util.c | 75 ++++++++-- libudev/libudev.h | 4 +- udev/udev-event.c | 1 - udev/udev-rules.c | 14 ++ udev/udev.xml | 13 ++ udev/udevadm-monitor.c | 17 ++- udev/udevadm-trigger.c | 7 +- udev/udevadm.xml | 13 ++ 16 files changed, 595 insertions(+), 184 deletions(-) diff --git a/Makefile.am b/Makefile.am index 7403949c..9d3fefd7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,9 +28,9 @@ CLEANFILES = # ------------------------------------------------------------------------------ # libudev # ------------------------------------------------------------------------------ -LIBUDEV_CURRENT=7 +LIBUDEV_CURRENT=8 LIBUDEV_REVISION=0 -LIBUDEV_AGE=7 +LIBUDEV_AGE=8 SUBDIRS += libudev/docs diff --git a/libudev/docs/libudev-sections.txt b/libudev/docs/libudev-sections.txt index ca781fff..3f8c107a 100644 --- a/libudev/docs/libudev-sections.txt +++ b/libudev/docs/libudev-sections.txt @@ -68,6 +68,7 @@ udev_monitor_enable_receiving udev_monitor_get_fd udev_monitor_receive_device udev_monitor_filter_add_match_subsystem_devtype +udev_monitor_filter_add_match_tag udev_monitor_filter_update udev_monitor_filter_remove @@ -85,6 +86,7 @@ udev_enumerate_add_nomatch_subsystem udev_enumerate_add_match_sysattr udev_enumerate_add_nomatch_sysattr udev_enumerate_add_match_property +udev_enumerate_add_match_tag udev_enumerate_add_match_sysname udev_enumerate_add_syspath udev_enumerate_scan_devices diff --git a/libudev/exported_symbols b/libudev/exported_symbols index 61486c6f..c0ca4b9c 100644 --- a/libudev/exported_symbols +++ b/libudev/exported_symbols @@ -60,6 +60,7 @@ udev_monitor_get_udev udev_monitor_get_fd udev_monitor_receive_device udev_monitor_filter_add_match_subsystem_devtype +udev_monitor_filter_add_match_tag udev_monitor_filter_update udev_monitor_filter_remove udev_queue_new diff --git a/libudev/libudev-device-private.c b/libudev/libudev-device-private.c index 5e4381ec..13f1ebf8 100644 --- a/libudev/libudev-device-private.c +++ b/libudev/libudev-device-private.c @@ -1,7 +1,7 @@ /* * libudev - interface to udev device information * - * Copyright (C) 2008 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,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,37 @@ #include "libudev.h" #include "libudev-private.h" +static int udev_device_tag_index(struct udev_device *udev_device, bool add) +{ + struct udev *udev = udev_device_get_udev(udev_device); + struct udev_list_entry *list_entry; + + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) { + char filename[UTIL_PATH_SIZE]; + + util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/tags/", + udev_list_entry_get_name(list_entry), "/", + udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname(udev_device), NULL); + + if (add) { + util_create_path(udev, filename); + symlink(udev_device_get_devpath(udev_device), filename); + if (udev_device_get_sysname_old(udev_device) != NULL) { + char filename_old[UTIL_PATH_SIZE]; + + util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/tags/", + udev_list_entry_get_name(list_entry), + udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname_old(udev_device), NULL); + unlink(filename_old); + } + } else { + unlink(filename); + util_delete_path(udev, filename); + } + } + return 0; +} + int udev_device_update_db(struct udev_device *udev_device) { struct udev *udev = udev_device_get_udev(udev_device); @@ -41,6 +73,8 @@ int udev_device_update_db(struct udev_device *udev_device) udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device)) if (udev_list_entry_get_flags(list_entry)) goto file; + if (udev_device_get_tags_list_entry(udev_device) != NULL) + goto file; if (udev_device_get_devlink_priority(udev_device) != 0) goto file; if (udev_device_get_event_timeout(udev_device) >= 0) @@ -80,7 +114,7 @@ file: if (f == NULL) { err(udev, "unable to create temporary db file '%s': %m\n", filename_tmp); return -1; - } + } if (udev_device_get_devnode(udev_device) != NULL) { fprintf(f, "N:%s\n", &udev_device_get_devnode(udev_device)[devlen]); @@ -100,10 +134,13 @@ file: udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry)); } + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) + fprintf(f, "G:%s\n", udev_list_entry_get_name(list_entry)); fclose(f); rename(filename_tmp, filename); info(udev, "created db file for '%s' in '%s'\n", udev_device_get_devpath(udev_device), filename); out: + udev_device_tag_index(udev_device, true); return 0; } @@ -112,6 +149,7 @@ int udev_device_delete_db(struct udev_device *udev_device) struct udev *udev = udev_device_get_udev(udev_device); char filename[UTIL_PATH_SIZE]; + udev_device_tag_index(udev_device, false); util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/db/", udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname(udev_device), NULL); unlink(filename); @@ -124,9 +162,13 @@ int udev_device_rename_db(struct udev_device *udev_device) char filename_old[UTIL_PATH_SIZE]; char filename[UTIL_PATH_SIZE]; + if (strcmp(udev_device_get_sysname(udev_device), udev_device_get_sysname_old(udev_device)) == 0) + return 0; + util_strscpyl(filename_old, sizeof(filename_old), udev_get_dev_path(udev), "/.udev/db/", udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname_old(udev_device), NULL); util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/db/", udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname(udev_device), NULL); + udev_device_tag_index(udev_device, true); return rename(filename_old, filename); } diff --git a/libudev/libudev-device.c b/libudev/libudev-device.c index 40035453..478fdcb9 100644 --- a/libudev/libudev-device.c +++ b/libudev/libudev-device.c @@ -60,6 +60,7 @@ struct udev_device { struct udev_list_node devlinks_list; struct udev_list_node properties_list; struct udev_list_node sysattr_list; + struct udev_list_node tags_list; unsigned long long int seqnum; int event_timeout; int timeout; @@ -68,18 +69,21 @@ struct udev_device { dev_t devnum; int watch_handle; int maj, min; - 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; + 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; }; struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value) { - udev_device->envp_uptodate = 0; + udev_device->envp_uptodate = false; if (value == NULL) { struct udev_list_entry *list_entry; @@ -149,6 +153,26 @@ void udev_device_add_property_from_string_parse(struct udev_device *udev_device, } 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, "DRIVER=", 7) == 0) { udev_device_set_driver(udev_device, &property[7]); } else if (strncmp(property, "ACTION=", 7) == 0) { @@ -208,6 +232,9 @@ int udev_device_read_db(struct udev_device *udev_device) char line[UTIL_LINE_SIZE]; FILE *f; + if (udev_device->db_loaded) + return 0; + util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/.udev/db/", udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname(udev_device), NULL); @@ -284,6 +311,9 @@ int udev_device_read_db(struct udev_device *udev_device) case 'E': udev_device_add_property_from_string(udev_device, val); break; + case 'G': + udev_device_add_tag(udev_device, val); + break; case 'W': udev_device_set_watch_handle(udev_device, atoi(val)); break; @@ -292,6 +322,7 @@ int udev_device_read_db(struct udev_device *udev_device) fclose(f); info(udev_device->udev, "device %p filled with db file data\n", udev_device); + udev_device->db_loaded = true; return 0; } @@ -303,6 +334,9 @@ 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"); if (f == NULL) @@ -329,21 +363,14 @@ int udev_device_read_uevent_file(struct udev_device *udev_device) } udev_device->devnum = makedev(maj, min); - fclose(f); + udev_device->uevent_loaded = true; 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) @@ -362,6 +389,7 @@ struct udev_device *udev_device_new(struct udev *udev) udev_list_init(&udev_device->devlinks_list); udev_list_init(&udev_device->properties_list); udev_list_init(&udev_device->sysattr_list); + udev_list_init(&udev_device->tags_list); udev_device->event_timeout = -1; udev_device->watch_handle = -1; /* copy global properties */ @@ -420,7 +448,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)); - if (strncmp(&syspath[len], "/devices/", 9) == 0) { + if (strncmp(&path[len], "/devices/", 9) == 0) { char file[UTIL_PATH_SIZE]; /* all "devices" require a "uevent" file */ @@ -648,7 +676,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) @@ -758,12 +786,13 @@ 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_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->envp); free(udev_device->monitor_buf); dbg(udev_device->udev, "udev_device: %p released\n", udev_device); @@ -842,7 +871,7 @@ 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); + udev_device_read_db(udev_device); return udev_device->devnode; } @@ -862,7 +891,7 @@ 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; + 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); @@ -900,9 +929,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; } @@ -925,13 +953,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); 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); } @@ -951,13 +979,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); + } 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; @@ -970,6 +1000,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); } @@ -986,7 +1031,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); } @@ -1004,7 +1049,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; } @@ -1184,7 +1229,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; } @@ -1195,7 +1240,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; } @@ -1216,7 +1261,7 @@ int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink { struct udev_list_entry *list_entry; - udev_device->devlinks_uptodate = 0; + udev_device->devlinks_uptodate = false; list_entry = udev_list_entry_add(udev_device->udev, &udev_device->devlinks_list, devlink, NULL, 1, 0); if (list_entry == NULL) return -ENOMEM; @@ -1225,6 +1270,40 @@ int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink return 0; } +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, 1, 0) != 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); +} + +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) +{ + return udev_list_get_entry(&udev_device->tags_list); +} + +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); + 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 #define MONITOR_BUF_SIZE 4096 static int update_envp_monitor_buf(struct udev_device *udev_device) @@ -1273,7 +1352,7 @@ static int update_envp_monitor_buf(struct udev_device *udev_device) } 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; @@ -1312,7 +1391,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; } @@ -1385,7 +1464,7 @@ int udev_device_set_timeout(struct udev_device *udev_device, int timeout) int udev_device_get_event_timeout(struct udev_device *udev_device) { if (!udev_device->info_loaded) - device_load_info(udev_device); + udev_device_read_db(udev_device); return udev_device->event_timeout; } @@ -1421,7 +1500,7 @@ int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum) int udev_device_get_devlink_priority(struct udev_device *udev_device) { if (!udev_device->info_loaded) - device_load_info(udev_device); + udev_device_read_db(udev_device); return udev_device->devlink_priority; } @@ -1434,7 +1513,7 @@ int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio) int udev_device_get_watch_handle(struct udev_device *udev_device) { if (!udev_device->info_loaded) - device_load_info(udev_device); + udev_device_read_db(udev_device); return udev_device->watch_handle; } diff --git a/libudev/libudev-enumerate.c b/libudev/libudev-enumerate.c index 9a61a61f..da831449 100644 --- a/libudev/libudev-enumerate.c +++ b/libudev/libudev-enumerate.c @@ -1,7 +1,7 @@ /* * libudev - interface to udev device information * - * Copyright (C) 2008 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 @@ -51,6 +51,7 @@ struct udev_enumerate { struct udev_list_node subsystem_nomatch_list; struct udev_list_node sysname_match_list; struct udev_list_node properties_match_list; + struct udev_list_node tags_match_list; struct udev_list_node devices_list; struct syspath *devices; unsigned int devices_cur; @@ -79,6 +80,7 @@ struct udev_enumerate *udev_enumerate_new(struct udev *udev) udev_list_init(&udev_enumerate->subsystem_nomatch_list); udev_list_init(&udev_enumerate->sysname_match_list); udev_list_init(&udev_enumerate->properties_match_list); + udev_list_init(&udev_enumerate->tags_match_list); udev_list_init(&udev_enumerate->devices_list); return udev_enumerate; } @@ -121,6 +123,7 @@ void udev_enumerate_unref(struct udev_enumerate *udev_enumerate) udev_list_cleanup_entries(udev_enumerate->udev, &udev_enumerate->subsystem_nomatch_list); udev_list_cleanup_entries(udev_enumerate->udev, &udev_enumerate->sysname_match_list); udev_list_cleanup_entries(udev_enumerate->udev, &udev_enumerate->properties_match_list); + udev_list_cleanup_entries(udev_enumerate->udev, &udev_enumerate->tags_match_list); udev_list_cleanup_entries(udev_enumerate->udev, &udev_enumerate->devices_list); for (i = 0; i < udev_enumerate->devices_cur; i++) free(udev_enumerate->devices[i].syspath); @@ -191,7 +194,7 @@ static int syspath_cmp(const void *p1, const void *p2) } /* For devices that should be moved to the absolute end of the list */ -static int devices_delay_end(struct udev *udev, const char *syspath) +static bool devices_delay_end(struct udev *udev, const char *syspath) { static const char *delay_device_list[] = { "/block/md", @@ -205,10 +208,10 @@ static int devices_delay_end(struct udev *udev, const char *syspath) for (i = 0; delay_device_list[i] != NULL; i++) { if (strstr(&syspath[len], delay_device_list[i]) != NULL) { dbg(udev, "delaying: %s\n", syspath); - return 1; + return true; } } - return 0; + return false; } /* For devices that should just be moved a little bit later, just @@ -394,16 +397,12 @@ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, co return 0; } -static int match_sysattr_value(struct udev *udev, const char *syspath, const char *sysattr, const char *match_val) +static int match_sysattr_value(struct udev_device *dev, const char *sysattr, const char *match_val) { - struct udev_device *device; const char *val = NULL; bool match = false; - device = udev_device_new_from_syspath(udev, syspath); - if (device == NULL) - return -EINVAL; - val = udev_device_get_sysattr_value(device, sysattr); + val = udev_device_get_sysattr_value(dev, sysattr); if (val == NULL) goto exit; if (match_val == NULL) { @@ -415,7 +414,6 @@ static int match_sysattr_value(struct udev *udev, const char *syspath, const cha goto exit; } exit: - udev_device_unref(device); return match; } @@ -439,6 +437,25 @@ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, con return 0; } +/** + * udev_enumerate_add_match_tag: + * @udev_enumerate: context + * @tag: filter for a tag of the device to include in the list + * + * Returns: 0 on success, otherwise a negative error value. + */ +int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) +{ + if (udev_enumerate == NULL) + return -EINVAL; + if (tag == NULL) + return 0; + if (udev_list_entry_add(udev_enumerate_get_udev(udev_enumerate), + &udev_enumerate->tags_match_list, tag, NULL, 1, 0) == NULL) + return -ENOMEM; + return 0; +} + /** * udev_enumerate_add_match_sysname: * @udev_enumerate: context @@ -458,46 +475,37 @@ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, cons return 0; } -static int match_sysattr(struct udev_enumerate *udev_enumerate, const char *syspath) +static bool match_sysattr(struct udev_enumerate *udev_enumerate, struct udev_device *dev) { - struct udev *udev = udev_enumerate_get_udev(udev_enumerate); struct udev_list_entry *list_entry; /* skip list */ udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->sysattr_nomatch_list)) { - if (match_sysattr_value(udev, syspath, - udev_list_entry_get_name(list_entry), - udev_list_entry_get_value(list_entry))) - return 0; + if (match_sysattr_value(dev, udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry))) + return false; } /* include list */ if (udev_list_get_entry(&udev_enumerate->sysattr_match_list) != NULL) { udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->sysattr_match_list)) { /* anything that does not match, will make it FALSE */ - if (!match_sysattr_value(udev, syspath, - udev_list_entry_get_name(list_entry), - udev_list_entry_get_value(list_entry))) - return 0; + if (!match_sysattr_value(dev, udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry))) + return false; } - return 1; + return true; } - return 1; + return true; } -static int match_property(struct udev_enumerate *udev_enumerate, const char *syspath) +static bool match_property(struct udev_enumerate *udev_enumerate, struct udev_device *dev) { - struct udev_device *dev; struct udev_list_entry *list_entry; - int match = false; + bool match = false; /* no match always matches */ if (udev_list_get_entry(&udev_enumerate->properties_match_list) == NULL) - return 1; - - /* no device does not match */ - dev = udev_device_new_from_syspath(udev_enumerate->udev, syspath); - if (dev == NULL) - return 0; + return true; /* loop over matches */ udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->properties_match_list)) { @@ -525,23 +533,38 @@ static int match_property(struct udev_enumerate *udev_enumerate, const char *sys } } out: - udev_device_unref(dev); return match; } -static int match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) +static bool match_tag(struct udev_enumerate *udev_enumerate, struct udev_device *dev) +{ + struct udev_list_entry *list_entry; + + /* no match always matches */ + if (udev_list_get_entry(&udev_enumerate->tags_match_list) == NULL) + return true; + + /* loop over matches */ + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->tags_match_list)) + if (!udev_device_has_tag(dev, udev_list_entry_get_name(list_entry))) + return false; + + return true; +} + +static bool match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) { struct udev_list_entry *list_entry; if (udev_list_get_entry(&udev_enumerate->sysname_match_list) == NULL) - return 1; + return true; udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->sysname_match_list)) { if (fnmatch(udev_list_entry_get_name(list_entry), sysname, 0) != 0) continue; - return 1; + return true; } - return 0; + return false; } static int scan_dir_and_add_devices(struct udev_enumerate *udev_enumerate, @@ -562,54 +585,53 @@ static int scan_dir_and_add_devices(struct udev_enumerate *udev_enumerate, util_strpcpyl(&s, l, "/", subdir2, NULL); dir = opendir(path); if (dir == NULL) - return -1; + return -ENOENT; for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { char syspath[UTIL_PATH_SIZE]; - char filename[UTIL_PATH_SIZE]; - struct stat statbuf; + struct udev_device *dev; if (dent->d_name[0] == '.') continue; + if (!match_sysname(udev_enumerate, dent->d_name)) continue; util_strscpyl(syspath, sizeof(syspath), path, "/", dent->d_name, NULL); - if (!match_property(udev_enumerate, syspath)) - continue; - if (lstat(syspath, &statbuf) != 0) - continue; - if (S_ISREG(statbuf.st_mode)) + dev = udev_device_new_from_syspath(udev_enumerate->udev, syspath); + if (dev == NULL) continue; - if (S_ISLNK(statbuf.st_mode)) - util_resolve_sys_link(udev, syspath, sizeof(syspath)); - util_strscpyl(filename, sizeof(filename), syspath, "/uevent", NULL); - if (stat(filename, &statbuf) != 0) - continue; - if (!match_sysattr(udev_enumerate, syspath)) - continue; - syspath_add(udev_enumerate, syspath); + if (!match_tag(udev_enumerate, dev)) + goto nomatch; + if (!match_property(udev_enumerate, dev)) + goto nomatch; + if (!match_sysattr(udev_enumerate, dev)) + goto nomatch; + + syspath_add(udev_enumerate, udev_device_get_syspath(dev)); +nomatch: + udev_device_unref(dev); } closedir(dir); return 0; } -static int match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) +static bool match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { struct udev_list_entry *list_entry; udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->subsystem_nomatch_list)) { if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0) - return 0; + return false; } if (udev_list_get_entry(&udev_enumerate->subsystem_match_list) != NULL) { udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->subsystem_match_list)) { if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0) - return 1; + return true; } - return 0; + return false; } - return 1; + return true; } static int scan_dir(struct udev_enumerate *udev_enumerate, const char *basedir, const char *subdir, const char *subsystem) @@ -675,17 +697,59 @@ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) if (udev_enumerate == NULL) return -EINVAL; - util_strscpyl(base, sizeof(base), udev_get_sys_path(udev), "/subsystem", NULL); - if (stat(base, &statbuf) == 0) { - /* we have /subsystem/, forget all the old stuff */ - dbg(udev, "searching '/subsystem/*/devices/*' dir\n"); - scan_dir(udev_enumerate, "subsystem", "devices", NULL); + + if (udev_list_get_entry(&udev_enumerate->tags_match_list) != NULL) { + struct udev_list_entry *list_entry; + + /* scan only tagged devices, use tags reverse-index, instead of searching all devices in /sys */ + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->tags_match_list)) { + DIR *dir; + struct dirent *dent; + char path[UTIL_PATH_SIZE]; + + util_strscpyl(path, sizeof(path), udev_get_dev_path(udev), "/.udev/tags/", + udev_list_entry_get_name(list_entry), NULL); + dir = opendir(path); + if (dir == NULL) + continue; + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + struct udev_device *dev; + char syspath[UTIL_PATH_SIZE]; + char *s; + size_t l; + ssize_t len; + + if (dent->d_name[0] == '.') + continue; + + s = syspath; + l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev), NULL); + len = readlinkat(dirfd(dir), dent->d_name, s, l); + if (len <= 0 || (size_t)len == l) + continue; + s[len] = '\0'; + + dev = udev_device_new_from_syspath(udev_enumerate->udev, syspath); + if (dev == NULL) + continue; + syspath_add(udev_enumerate, udev_device_get_syspath(dev)); + udev_device_unref(dev); + } + } } else { + util_strscpyl(base, sizeof(base), udev_get_sys_path(udev), "/subsystem", NULL); + if (stat(base, &statbuf) == 0) { + /* we have /subsystem/, forget all the old stuff */ + dbg(udev, "searching '/subsystem/*/devices/*' dir\n"); + scan_dir(udev_enumerate, "subsystem", "devices", NULL); + } else { dbg(udev, "searching '/bus/*/devices/*' dir\n"); - scan_dir(udev_enumerate, "bus", "devices", NULL); - dbg(udev, "searching '/class/*' dir\n"); - scan_dir(udev_enumerate, "class", NULL, NULL); + scan_dir(udev_enumerate, "bus", "devices", NULL); + dbg(udev, "searching '/class/*' dir\n"); + scan_dir(udev_enumerate, "class", NULL, NULL); + } } + return 0; } @@ -704,6 +768,7 @@ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) if (udev_enumerate == NULL) return -EINVAL; + util_strscpyl(base, sizeof(base), udev_get_sys_path(udev), "/subsystem", NULL); if (stat(base, &statbuf) == 0) subsysdir = "subsystem"; diff --git a/libudev/libudev-monitor.c b/libudev/libudev-monitor.c index 97e52c42..24e8aead 100644 --- a/libudev/libudev-monitor.c +++ b/libudev/libudev-monitor.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 @@ -49,6 +49,7 @@ struct udev_monitor { struct sockaddr_un sun; socklen_t addrlen; struct udev_list_node filter_subsystem_list; + struct udev_list_node filter_tag_list; }; enum udev_monitor_netlink_group { @@ -57,25 +58,28 @@ enum udev_monitor_netlink_group { UDEV_MONITOR_UDEV, }; -#define UDEV_MONITOR_MAGIC 0xcafe1dea +#define UDEV_MONITOR_MAGIC 0xfeedcafe struct udev_monitor_netlink_header { - /* udev version text */ - char version[16]; + /* "libudev" prefix to distinguish libudev and kernel messages */ + char prefix[8]; /* * magic to protect against daemon <-> library message format mismatch * used in the kernel from socket filter rules; needs to be stored in network order */ unsigned int magic; - /* properties buffer */ - unsigned short properties_off; - unsigned short properties_len; + /* total length of header structure known to the sender */ + unsigned int header_size; + /* properties string buffer */ + unsigned int properties_off; + unsigned int properties_len; /* - * hashes of some common device properties strings to filter with socket filters in - * the client used in the kernel from socket filter rules; needs to be stored in - * network order + * hashes of primary device properties strings, to let libudev subscribers + * use in-kernel socket filters; values need to be stored in network order */ - unsigned int filter_subsystem; - unsigned int filter_devtype; + unsigned int filter_subsystem_hash; + unsigned int filter_devtype_hash; + unsigned int filter_tag_bloom_hi; + unsigned int filter_tag_bloom_lo; }; static struct udev_monitor *udev_monitor_new(struct udev *udev) @@ -88,6 +92,7 @@ static struct udev_monitor *udev_monitor_new(struct udev *udev) udev_monitor->refcount = 1; udev_monitor->udev = udev; udev_list_init(&udev_monitor->filter_subsystem_list); + udev_list_init(&udev_monitor->filter_tag_list); return udev_monitor; } @@ -247,13 +252,14 @@ static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i, */ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) { - static struct sock_filter ins[256]; - static struct sock_fprog filter; + struct sock_filter ins[512]; + struct sock_fprog filter; unsigned int i; struct udev_list_entry *list_entry; int err; - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL) + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL && + udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) return 0; memset(ins, 0x00, sizeof(ins)); @@ -266,35 +272,74 @@ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) /* wrong magic, pass packet */ bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - /* add all subsystem match values */ - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { - unsigned int hash; - - /* load filter_subsystem value in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem)); - hash = util_string_hash32(udev_list_entry_get_name(list_entry)); - if (udev_list_entry_get_value(list_entry) == NULL) { - /* jump if subsystem does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); - } else { - /* jump if subsystem does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); - - /* load filter_devtype value in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype)); - /* jump if value does not match */ - hash = util_string_hash32(udev_list_entry_get_value(list_entry)); - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + if (udev_list_get_entry(&udev_monitor->filter_tag_list) != NULL) { + int tag_matches; + + /* count tag matches, to calculate end of tag match block */ + tag_matches = 0; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) + tag_matches++; + + /* add all tags matches */ + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { + uint64_t tag_bloom_bits = util_string_bloom64(udev_list_entry_get_name(list_entry)); + uint32_t tag_bloom_hi = tag_bloom_bits >> 32; + uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_hi)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); + /* jump to next tag if it does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_lo)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); + /* jump behind end of tag match block if tag matches */ + tag_matches--; + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); } - /* matched, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } - if (i+1 >= ARRAY_SIZE(ins)) - return -1; + /* add all subsystem matches */ + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) != NULL) { + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { + unsigned int hash = util_string_hash32(udev_list_entry_get_name(list_entry)); + + /* load device subsystem value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem_hash)); + if (udev_list_entry_get_value(list_entry) == NULL) { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } else { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); + + /* load device devtype value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype_hash)); + /* jump if value does not match */ + hash = util_string_hash32(udev_list_entry_get_value(list_entry)); + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (i+1 >= ARRAY_SIZE(ins)) + return -1; + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); } - /* nothing matched, drop packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); /* install filter */ filter.len = i; @@ -406,6 +451,7 @@ void udev_monitor_unref(struct udev_monitor *udev_monitor) if (udev_monitor->sock >= 0) close(udev_monitor->sock); udev_list_cleanup_entries(udev_monitor->udev, &udev_monitor->filter_subsystem_list); + udev_list_cleanup_entries(udev_monitor->udev, &udev_monitor->filter_tag_list); dbg(udev_monitor->udev, "monitor %p released\n", udev_monitor); free(udev_monitor); } @@ -445,8 +491,7 @@ static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device * struct udev_list_entry *list_entry; if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL) - return 1; - + goto tag; udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { const char *subsys = udev_list_entry_get_name(list_entry); const char *dsubsys = udev_device_get_subsystem(udev_device); @@ -458,11 +503,22 @@ static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device * devtype = udev_list_entry_get_value(list_entry); if (devtype == NULL) - return 1; + goto tag; ddevtype = udev_device_get_devtype(udev_device); if (ddevtype == NULL) continue; if (strcmp(ddevtype, devtype) == 0) + goto tag; + } + return 0; + +tag: + if (udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) + return 1; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { + const char *tag = udev_list_entry_get_name(list_entry); + + if (udev_device_has_tag(udev_device, tag)) return 1; } return 0; @@ -556,13 +612,11 @@ retry: return NULL; } - if (strncmp(buf, "udev-", 5) == 0) { + if (memcmp(buf, "libudev", 8) == 0) { /* udev message needs proper version magic */ nlh = (struct udev_monitor_netlink_header *) buf; if (nlh->magic != htonl(UDEV_MONITOR_MAGIC)) return NULL; - if (nlh->properties_off < sizeof(struct udev_monitor_netlink_header)) - return NULL; if (nlh->properties_off+32 > buflen) return NULL; bufpos = nlh->properties_off; @@ -626,17 +680,17 @@ retry: int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_monitor *destination, struct udev_device *udev_device) { - struct msghdr smsg; - struct iovec iov[2]; const char *buf; ssize_t blen; ssize_t count; blen = udev_device_get_properties_monitor_buf(udev_device, &buf); if (blen < 32) - return -1; + return -EINVAL; if (udev_monitor->sun.sun_family != 0) { + struct msghdr smsg; + struct iovec iov[2]; const char *action; char header[2048]; char *s; @@ -660,23 +714,41 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, smsg.msg_iovlen = 2; smsg.msg_name = &udev_monitor->sun; smsg.msg_namelen = udev_monitor->addrlen; - } else if (udev_monitor->snl.nl_family != 0) { + count = sendmsg(udev_monitor->sock, &smsg, 0); + info(udev_monitor->udev, "passed %zi bytes to socket monitor %p\n", count, udev_monitor); + return count; + } + + if (udev_monitor->snl.nl_family != 0) { + struct msghdr smsg; + struct iovec iov[2]; const char *val; struct udev_monitor_netlink_header nlh; - + struct udev_list_entry *list_entry; + uint64_t tag_bloom_bits; /* add versioned header */ memset(&nlh, 0x00, sizeof(struct udev_monitor_netlink_header)); - util_strscpy(nlh.version, sizeof(nlh.version), "udev-" VERSION); + memcpy(nlh.prefix, "libudev", 8); nlh.magic = htonl(UDEV_MONITOR_MAGIC); + nlh.header_size = sizeof(struct udev_monitor_netlink_header); val = udev_device_get_subsystem(udev_device); - nlh.filter_subsystem = htonl(util_string_hash32(val)); + nlh.filter_subsystem_hash = htonl(util_string_hash32(val)); val = udev_device_get_devtype(udev_device); if (val != NULL) - nlh.filter_devtype = htonl(util_string_hash32(val)); + nlh.filter_devtype_hash = htonl(util_string_hash32(val)); iov[0].iov_base = &nlh; iov[0].iov_len = sizeof(struct udev_monitor_netlink_header); + /* add tag bloom filter */ + tag_bloom_bits = 0; + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) + tag_bloom_bits |= util_string_bloom64(udev_list_entry_get_name(list_entry)); + if (tag_bloom_bits > 0) { + nlh.filter_tag_bloom_hi = htonl(tag_bloom_bits >> 32); + nlh.filter_tag_bloom_lo = htonl(tag_bloom_bits & 0xffffffff); + } + /* add properties list */ nlh.properties_off = iov[0].iov_len; nlh.properties_len = blen; @@ -697,13 +769,12 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, else smsg.msg_name = &udev_monitor->snl_destination; smsg.msg_namelen = sizeof(struct sockaddr_nl); - } else { - return -1; + count = sendmsg(udev_monitor->sock, &smsg, 0); + info(udev_monitor->udev, "passed %zi bytes to netlink monitor %p\n", count, udev_monitor); + return count; } - count = sendmsg(udev_monitor->sock, &smsg, 0); - info(udev_monitor->udev, "passed %zi bytes to monitor %p\n", count, udev_monitor); - return count; + return -EINVAL; } /** @@ -712,6 +783,9 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, * @subsystem: the subsystem value to match the incoming devices against * @devtype: the devtype value to match the incoming devices against * + * This filer is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * * The filter must be installed before the monitor is switched to listening mode. * * Returns: 0 on success, otherwise a negative error value. @@ -721,13 +795,37 @@ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_mo if (udev_monitor == NULL) return -EINVAL; if (subsystem == NULL) - return 0; + return -EINVAL; if (udev_list_entry_add(udev_monitor->udev, &udev_monitor->filter_subsystem_list, subsystem, devtype, 0, 0) == NULL) return -ENOMEM; return 0; } +/** + * udev_monitor_filter_add_match_tag: + * @udev_monitor: the monitor + * @tag: the name of a tag + * + * This filer is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) +{ + if (udev_monitor == NULL) + return -EINVAL; + if (tag == NULL) + return -EINVAL; + if (udev_list_entry_add(udev_monitor->udev, + &udev_monitor->filter_tag_list, tag, NULL, 0, 0) == NULL) + return -ENOMEM; + return 0; +} + /** * udev_monitor_filter_remove: * @udev_monitor: monitor diff --git a/libudev/libudev-private.h b/libudev/libudev-private.h index 8dc469ec..548f4adb 100644 --- a/libudev/libudev-private.h +++ b/libudev/libudev-private.h @@ -14,6 +14,8 @@ #include #include +#include +#include #include "libudev.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) @@ -84,7 +86,8 @@ const char *udev_device_get_sysname_old(struct udev_device *udev_device); int udev_device_set_devpath_old(struct udev_device *udev_device, const char *devpath_old); const char *udev_device_get_knodename(struct udev_device *udev_device); int udev_device_add_tag(struct udev_device *udev_device, const char *tag); -struct udev_list_entry *udev_device_get_tag_list_entry(struct udev_device *udev_device); +void udev_device_cleanup_tags_list(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); int udev_device_has_tag(struct udev_device *udev_device, const char *tag); int udev_device_set_knodename(struct udev_device *udev_device, const char *knodename); int udev_device_get_timeout(struct udev_device *udev_device); @@ -203,7 +206,8 @@ size_t util_strscpyl(char *dest, size_t size, const char *src, ...) __attribute_ int udev_util_replace_whitespace(const char *str, char *to, size_t len); int udev_util_replace_chars(char *str, const char *white); int udev_util_encode_string(const char *str, char *str_enc, size_t len); -unsigned int util_string_hash32(const char *str); +unsigned int util_string_hash32(const char *key); +uint64_t util_string_bloom64(const char *str); /* libudev-util-private.c */ int util_create_path(struct udev *udev, const char *path); diff --git a/libudev/libudev-util.c b/libudev/libudev-util.c index c0209f9c..3a67b0cd 100644 --- a/libudev/libudev-util.c +++ b/libudev/libudev-util.c @@ -481,15 +481,74 @@ err: return -1; } +/* + * http://sites.google.com/site/murmurhash/ + * + * All code is released to the public domain. For business purposes, + * Murmurhash is under the MIT license. + * + */ +static unsigned int murmur_hash2(const char *key, int len, unsigned int seed) +{ + /* + * 'm' and 'r' are mixing constants generated offline. + * They're not really 'magic', they just happen to work well. + */ + const unsigned int m = 0x5bd1e995; + const int r = 24; + + /* initialize the hash to a 'random' value */ + unsigned int h = seed ^ len; + + /* mix 4 bytes at a time into the hash */ + const unsigned char * data = (const unsigned char *)key; + + while(len >= 4) { + unsigned int k = *(unsigned int *)data; + + k *= m; + k ^= k >> r; + k *= m; + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + /* handle the last few bytes of the input array */ + switch(len) { + case 3: + h ^= data[2] << 16; + case 2: + h ^= data[1] << 8; + case 1: + h ^= data[0]; + h *= m; + }; + + /* do a few final mixes of the hash to ensure the last few bytes are well-incorporated */ + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + unsigned int util_string_hash32(const char *str) { - unsigned int hash = 0; + return murmur_hash2(str, strlen(str), 0); +} - while (str[0] != '\0') { - hash += str[0] << 4; - hash += str[0] >> 4; - hash *= 11; - str++; - } - return hash; +/* get a bunch of bit numbers out of the hash, and set the bits in our bit field */ +uint64_t util_string_bloom64(const char *str) +{ + uint64_t bits = 0; + unsigned int hash = util_string_hash32(str); + + bits |= 1LLU << (hash & 63); + bits |= 1LLU << ((hash >> 6) & 63); + bits |= 1LLU << ((hash >> 12) & 63); + bits |= 1LLU << ((hash >> 18) & 63); + return bits; } diff --git a/libudev/libudev.h b/libudev/libudev.h index 750664f4..3b73fbd6 100644 --- a/libudev/libudev.h +++ b/libudev/libudev.h @@ -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 @@ -118,6 +118,7 @@ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monito /* in-kernel socket filters to select messages that get delivered to a listener */ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype); +int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); int udev_monitor_filter_update(struct udev_monitor *udev_monitor); int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); @@ -138,6 +139,7 @@ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, cons int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); +int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); /* run enumeration with active filters */ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); diff --git a/udev/udev-event.c b/udev/udev-event.c index b2e1baee..212ccc79 100644 --- a/udev/udev-event.c +++ b/udev/udev-event.c @@ -543,7 +543,6 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules) if (strcmp(udev_device_get_action(dev), "remove") == 0) { udev_device_read_db(dev); - udev_device_set_info_loaded(dev); udev_device_delete_db(dev); if (major(udev_device_get_devnum(dev)) != 0) diff --git a/udev/udev-rules.c b/udev/udev-rules.c index 6eb83503..b5016d0b 100644 --- a/udev/udev-rules.c +++ b/udev/udev-rules.c @@ -157,6 +157,7 @@ enum token_type { TK_A_GROUP_ID, /* gid_t */ TK_A_MODE_ID, /* mode_t */ TK_A_ENV, /* val, attr */ + TK_A_TAG, /* val */ TK_A_NAME, /* val */ TK_A_DEVLINK, /* val */ TK_A_EVENT_TIMEOUT, /* int */ @@ -285,6 +286,7 @@ static const char *token_str(enum token_type type) [TK_A_GROUP_ID] = "A GROUP_ID", [TK_A_MODE_ID] = "A MODE_ID", [TK_A_ENV] = "A ENV", + [TK_A_TAG] = "A ENV", [TK_A_NAME] = "A NAME", [TK_A_DEVLINK] = "A DEVLINK", [TK_A_EVENT_TIMEOUT] = "A EVENT_TIMEOUT", @@ -354,6 +356,9 @@ static void dump_token(struct udev_rules *rules, struct token *token) dbg(rules->udev, "%s %s '%s' '%s'(%s)\n", token_str(type), operation_str(op), attr, value, string_glob_str(glob)); break; + case TK_A_TAG: + dbg(rules->udev, "%s %s '%s'\n", token_str(type), operation_str(op), value); + break; case TK_A_STRING_ESCAPE_NONE: case TK_A_STRING_ESCAPE_REPLACE: dbg(rules->udev, "%s\n", token_str(type)); @@ -1003,6 +1008,7 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type, case TK_A_MODE: case TK_A_NAME: case TK_A_GOTO: + case TK_A_TAG: token->key.value_off = add_string(rule_tmp->rules, value); break; case TK_M_ENV: @@ -1350,6 +1356,11 @@ static int add_rule(struct udev_rules *rules, char *line, continue; } + if (strcmp(key, "TAG") == 0) { + rule_add_key(&rule_tmp, TK_A_TAG, op, value, NULL); + continue; + } + if (strcmp(key, "PROGRAM") == 0) { rule_add_key(&rule_tmp, TK_M_PROGRAM, op, value, NULL); continue; @@ -2408,6 +2419,9 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event } break; } + case TK_A_TAG: + udev_device_add_tag(event->dev, &rules->buf[cur->key.value_off]); + break; case TK_A_NAME: { const char *name = &rules->buf[cur->key.value_off]; diff --git a/udev/udev.xml b/udev/udev.xml index 192a6f12..842fd5d5 100644 --- a/udev/udev.xml +++ b/udev/udev.xml @@ -348,6 +348,19 @@ + + + + Attach a tag to a device. This is used to filter events for users + of libudev's monitor functionality, or to enumerate a group of tagged + devices. The implementation can only work efficiently if only a few + tags are attached to a device. It is only meant to be used in + contexts with specific device filter requirements, and not as a + general-purpose flag. Excessive use might result in inefficient event + handling. + + + diff --git a/udev/udevadm-monitor.c b/udev/udevadm-monitor.c index d136c607..fb650846 100644 --- a/udev/udevadm-monitor.c +++ b/udev/udevadm-monitor.c @@ -73,6 +73,7 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) int print_kernel = 0; int print_udev = 0; struct udev_list_node subsystem_match_list; + struct udev_list_node tag_match_list; struct udev_monitor *udev_monitor = NULL; struct udev_monitor *kernel_monitor = NULL; fd_set readfds; @@ -84,13 +85,15 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) { "kernel", no_argument, NULL, 'k' }, { "udev", no_argument, NULL, 'u' }, { "subsystem-match", required_argument, NULL, 's' }, + { "tag-match", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, {} }; udev_list_init(&subsystem_match_list); + udev_list_init(&tag_match_list); while (1) { - option = getopt_long(argc, argv, "epkus:h", options, NULL); + option = getopt_long(argc, argv, "pekus:t:h", options, NULL); if (option == -1) break; @@ -119,12 +122,16 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) udev_list_entry_add(udev, &subsystem_match_list, subsys, devtype, 0, 0); break; } + case 't': + udev_list_entry_add(udev, &tag_match_list, optarg, NULL, 0, 0); + break; case 'h': printf("Usage: udevadm monitor [--property] [--kernel] [--udev] [--help]\n" " --property print the event properties\n" " --kernel print kernel uevents\n" " --udev print udev events\n" " --subsystem-match= filter events by subsystem\n" + " --tag-match= filter events by tag\n" " --help\n\n"); default: goto out; @@ -168,6 +175,13 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys); } + udev_list_entry_foreach(entry, udev_list_get_entry(&tag_match_list)) { + const char *tag = udev_list_entry_get_name(entry); + + if (udev_monitor_filter_add_match_tag(udev_monitor, tag) < 0) + fprintf(stderr, "error: unable to apply tag filter '%s'\n", tag); + } + if (udev_monitor_enable_receiving(udev_monitor) < 0) { fprintf(stderr, "error: unable to subscribe to udev events\n"); rc = 2; @@ -244,5 +258,6 @@ out: udev_monitor_unref(udev_monitor); udev_monitor_unref(kernel_monitor); udev_list_cleanup_entries(udev, &subsystem_match_list); + udev_list_cleanup_entries(udev, &tag_match_list); return rc; } diff --git a/udev/udevadm-trigger.c b/udev/udevadm-trigger.c index 03aa5343..3cb07dda 100644 --- a/udev/udevadm-trigger.c +++ b/udev/udevadm-trigger.c @@ -101,6 +101,7 @@ int udevadm_trigger(struct udev *udev, int argc, char *argv[]) { "attr-match", required_argument, NULL, 'a' }, { "attr-nomatch", required_argument, NULL, 'A' }, { "property-match", required_argument, NULL, 'p' }, + { "tag-match", required_argument, NULL, 'g' }, { "sysname-match", required_argument, NULL, 'y' }, { "help", no_argument, NULL, 'h' }, {} @@ -127,7 +128,7 @@ int udevadm_trigger(struct udev *udev, int argc, char *argv[]) const char *val; char buf[UTIL_PATH_SIZE]; - option = getopt_long(argc, argv, "vnFo:t:hcp:s:S:a:A:y:", options, NULL); + option = getopt_long(argc, argv, "vng:o:t:hcp:s:S:a:A:y:", options, NULL); if (option == -1) break; @@ -172,6 +173,9 @@ int udevadm_trigger(struct udev *udev, int argc, char *argv[]) key = keyval(optarg, &val, buf, sizeof(buf)); udev_enumerate_add_match_property(udev_enumerate, key, val); break; + case 'g': + udev_enumerate_add_match_tag(udev_enumerate, optarg); + break; case 'y': udev_enumerate_add_match_sysname(udev_enumerate, optarg); break; @@ -190,6 +194,7 @@ int udevadm_trigger(struct udev *udev, int argc, char *argv[]) " --attr-match=]> trigger devices with a matching attribute\n" " --attr-nomatch=]> exclude devices with a matching attribute\n" " --property-match== trigger devices with a matching property\n" + " --tag-match== trigger devices with a matching property\n" " --sysname-match= trigger devices with a matching name\n" " --help\n\n"); goto exit; diff --git a/udev/udevadm.xml b/udev/udevadm.xml index fa1742ba..73e6f110 100644 --- a/udev/udevadm.xml +++ b/udev/udevadm.xml @@ -216,6 +216,13 @@ specified multiple times and supports shell style pattern matching. + + + + Trigger events for devices with a matching tag. This option can be + specified multiple times. + + @@ -355,6 +362,12 @@ Filter events by subsystem[/devtype]. Only udev events with a matching subsystem value will pass. + + + + Filter events by property. Only udev events with a given tag attached will pass. + + -- 2.39.5