chiark / gitweb /
add TAG= to improve event filtering and device enumeration
authorKay Sievers <kay.sievers@vrfy.org>
Thu, 22 Apr 2010 16:12:36 +0000 (18:12 +0200)
committerKay Sievers <kay.sievers@vrfy.org>
Thu, 22 Apr 2010 16:12:36 +0000 (18:12 +0200)
16 files changed:
Makefile.am
libudev/docs/libudev-sections.txt
libudev/exported_symbols
libudev/libudev-device-private.c
libudev/libudev-device.c
libudev/libudev-enumerate.c
libudev/libudev-monitor.c
libudev/libudev-private.h
libudev/libudev-util.c
libudev/libudev.h
udev/udev-event.c
udev/udev-rules.c
udev/udev.xml
udev/udevadm-monitor.c
udev/udevadm-trigger.c
udev/udevadm.xml

index 7403949cf31948496943a9e9d6ce4450f0fa473e..9d3fefd702b4731c902b7d3ece7dc2a7580518c9 100644 (file)
@@ -28,9 +28,9 @@ CLEANFILES =
 # ------------------------------------------------------------------------------
 # libudev
 # ------------------------------------------------------------------------------
-LIBUDEV_CURRENT=7
+LIBUDEV_CURRENT=8
 LIBUDEV_REVISION=0
-LIBUDEV_AGE=7
+LIBUDEV_AGE=8
 
 SUBDIRS += libudev/docs
 
index ca781fff230fca5d370822995973941478062385..3f8c107a1fd07aa320bb5cf8be38bdeb39aeda0d 100644 (file)
@@ -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
 </SECTION>
@@ -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
index 61486c6f48d5d77af2e86619ae8ad639380a91e1..c0ca4b9c6d1bd560e5f9a7bfa96b92b2dc6635e7 100644 (file)
@@ -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
index 5e4381ec27af217f715d23a4a4c277c001fadaa8..13f1ebf88304316369d451ecedf00d3ff2f07fc6 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libudev - interface to udev device information
  *
- * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -13,6 +13,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <stddef.h>
+#include <stdbool.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <string.h>
 #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);
 }
index 400354539b4d849c9454e4c435a27526302aa181..478fdcb92d7f80231662b10ccfceef6f1a2c7900 100644 (file)
@@ -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;
 }
 
index 9a61a61f0d4a44d32ca78717b0c2d0c2d5766f54..da831449dcaf5e936a14409e8e68ab12d30a98e2 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libudev - interface to udev device information
  *
- * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -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";
index 97e52c42db430019ed26871da3a9e70b4040d944..24e8aead2635595898aa6479aa25d023607fbdf6 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libudev - interface to udev device information
  *
- * Copyright (C) 2008-2009 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -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
index 8dc469ec9b0e5bcd41ad6d4ce572925d72bd91e8..548f4adbef0c0c3d47d8537f5bfd96512eeda834 100644 (file)
@@ -14,6 +14,8 @@
 
 #include <syslog.h>
 #include <signal.h>
+#include <stdint.h>
+#include <stdbool.h>
 #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);
index c0209f9cc613cdaaf54c4ec533edf4bf91e6d10b..3a67b0cd5d0f15a43c75221bc2750a003f1762b8 100644 (file)
@@ -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;
 }
index 750664f43c71afe6bd0363f1f826c18913bac67f..3b73fbd61bc8a777410ccfc3bb5f691e43abd736 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libudev - interface to udev device information
  *
- * Copyright (C) 2008-2009 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -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);
index b2e1baee1f4d5484ef2144b6860ea420b5cee03c..212ccc795073511c6b0b47111b60fb233642993c 100644 (file)
@@ -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)
index 6eb8350343c3f7c55391376b2d9c2c7f40190d23..b5016d0bc315e11ee292ef6ea8b600606e5305f6 100644 (file)
@@ -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];
index 192a6f1238d806c642322c774d65e469e7f6ad7e..842fd5d52aa8f6d3fdd17a1495fd95c3cbfff9c7 100644 (file)
               </listitem>
             </varlistentry>
 
+            <varlistentry>
+              <term><option>TAG</option></term>
+              <listitem>
+                <para>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.</para>
+              </listitem>
+            </varlistentry>
+
             <varlistentry>
               <term><option>RUN</option></term>
               <listitem>
index d136c6070abe5604b32333eaf6e0c5b2cfb26e9a..fb650846bcd4eca2628cdbe7c13d9fbc588aed3c 100644 (file)
@@ -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=<subsystem[/devtype]> filter events by subsystem\n"
+                              "  --tag-match=<tag>                       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;
 }
index 03aa53437f17a034f2ebb8a2a8e6232702a54efa..3cb07dda9abc7c987ab89fb98d25815385913339 100644 (file)
@@ -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=<file[=<value>]>   trigger devices with a matching attribute\n"
                               "  --attr-nomatch=<file[=<value>]> exclude devices with a matching attribute\n"
                               "  --property-match=<key>=<value>  trigger devices with a matching property\n"
+                              "  --tag-match=<key>=<value>       trigger devices with a matching property\n"
                               "  --sysname-match=<name>          trigger devices with a matching name\n"
                               "  --help\n\n");
                        goto exit;
index fa1742badaef2fd9150c395e41bd5a3ab05f4119..73e6f110a6eb97a2c91e2369f149686b50479688 100644 (file)
                 specified multiple times and supports shell style pattern matching.</para>
               </listitem>
             </varlistentry>
+            <varlistentry>
+              <term><option>--tag-match=<replaceable>property</replaceable></option></term>
+              <listitem>
+                <para>Trigger events for devices with a matching tag. This option can be
+                specified multiple times.</para>
+              </listitem>
+            </varlistentry>
             <varlistentry>
               <term><option>--sysname-match=<replaceable>name</replaceable></option></term>
               <listitem>
                 <para>Filter events by subsystem[/devtype]. Only udev events with a matching subsystem value will pass.</para>
               </listitem>
             </varlistentry>
+            <varlistentry>
+              <term><option>--tag-match=<replaceable>string</replaceable></option></term>
+              <listitem>
+                <para>Filter events by property. Only udev events with a given tag attached will pass.</para>
+              </listitem>
+            </varlistentry>
             <varlistentry>
               <term><option>--help</option></term>
               <listitem>