summaryrefslogtreecommitdiff
path: root/src/libudev
diff options
context:
space:
mode:
Diffstat (limited to 'src/libudev')
-rw-r--r--src/libudev/.gitignore1
l---------src/libudev/Makefile1
-rw-r--r--src/libudev/libudev-device-private.c192
-rw-r--r--src/libudev/libudev-device.c1758
-rw-r--r--src/libudev/libudev-enumerate.c956
-rw-r--r--src/libudev/libudev-hwdb-def.h74
-rw-r--r--src/libudev/libudev-hwdb.c392
-rw-r--r--src/libudev/libudev-list.c355
-rw-r--r--src/libudev/libudev-monitor.c782
-rw-r--r--src/libudev/libudev-private.h180
-rw-r--r--src/libudev/libudev-queue-private.c406
-rw-r--r--src/libudev/libudev-queue.c480
-rw-r--r--src/libudev/libudev-util.c725
-rw-r--r--src/libudev/libudev.c314
-rw-r--r--src/libudev/libudev.h204
-rw-r--r--src/libudev/libudev.pc.in18
-rw-r--r--src/libudev/libudev.sym110
17 files changed, 6948 insertions, 0 deletions
diff --git a/src/libudev/.gitignore b/src/libudev/.gitignore
new file mode 100644
index 0000000000..0c8a5d5231
--- /dev/null
+++ b/src/libudev/.gitignore
@@ -0,0 +1 @@
+/libudev.pc
diff --git a/src/libudev/Makefile b/src/libudev/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/libudev/Makefile
@@ -0,0 +1 @@
+../Makefile \ No newline at end of file
diff --git a/src/libudev/libudev-device-private.c b/src/libudev/libudev-device-private.c
new file mode 100644
index 0000000000..c123057907
--- /dev/null
+++ b/src/libudev/libudev-device-private.c
@@ -0,0 +1,192 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static void udev_device_tag(struct udev_device *dev, const char *tag, bool add)
+{
+ const char *id;
+ char filename[UTIL_PATH_SIZE];
+
+ id = udev_device_get_id_filename(dev);
+ if (id == NULL)
+ return;
+ util_strscpyl(filename, sizeof(filename), "/run/udev/tags/", tag, "/", id, NULL);
+
+ if (add) {
+ int fd;
+
+ mkdir_parents(filename, 0755);
+ fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
+ if (fd >= 0)
+ close(fd);
+ } else {
+ unlink(filename);
+ }
+}
+
+int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add)
+{
+ struct udev_list_entry *list_entry;
+ bool found;
+
+ if (add && dev_old != NULL) {
+ /* delete possible left-over tags */
+ udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(dev_old)) {
+ const char *tag_old = udev_list_entry_get_name(list_entry);
+ struct udev_list_entry *list_entry_current;
+
+ found = false;
+ udev_list_entry_foreach(list_entry_current, udev_device_get_tags_list_entry(dev)) {
+ const char *tag = udev_list_entry_get_name(list_entry_current);
+
+ if (streq(tag, tag_old)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ udev_device_tag(dev_old, tag_old, false);
+ }
+ }
+
+ udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(dev))
+ udev_device_tag(dev, udev_list_entry_get_name(list_entry), add);
+
+ return 0;
+}
+
+static bool device_has_info(struct udev_device *udev_device)
+{
+ struct udev_list_entry *list_entry;
+
+ if (udev_device_get_devlinks_list_entry(udev_device) != NULL)
+ return true;
+ if (udev_device_get_devlink_priority(udev_device) != 0)
+ return true;
+ udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device))
+ if (udev_list_entry_get_num(list_entry))
+ return true;
+ if (udev_device_get_tags_list_entry(udev_device) != NULL)
+ return true;
+ if (udev_device_get_watch_handle(udev_device) >= 0)
+ return true;
+ return false;
+}
+
+int udev_device_update_db(struct udev_device *udev_device)
+{
+ struct udev *udev = udev_device_get_udev(udev_device);
+ bool has_info;
+ const char *id;
+ char filename[UTIL_PATH_SIZE];
+ char filename_tmp[UTIL_PATH_SIZE];
+ FILE *f;
+ int r;
+
+ id = udev_device_get_id_filename(udev_device);
+ if (id == NULL)
+ return -1;
+
+ has_info = device_has_info(udev_device);
+ util_strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL);
+
+ /* do not store anything for otherwise empty devices */
+ if (!has_info &&
+ major(udev_device_get_devnum(udev_device)) == 0 &&
+ udev_device_get_ifindex(udev_device) == 0) {
+ unlink(filename);
+ return 0;
+ }
+
+ /* write a database file */
+ util_strscpyl(filename_tmp, sizeof(filename_tmp), filename, ".tmp", NULL);
+ mkdir_parents(filename_tmp, 0755);
+ f = fopen(filename_tmp, "we");
+ if (f == NULL) {
+ udev_err(udev, "unable to create temporary db file '%s': %m\n", filename_tmp);
+ return -1;
+ }
+
+ /*
+ * set 'sticky' bit to indicate that we should not clean the
+ * database when we transition from initramfs to the real root
+ */
+ if (udev_device_get_db_persist(udev_device))
+ fchmod(fileno(f), 01644);
+
+ if (has_info) {
+ struct udev_list_entry *list_entry;
+
+ if (major(udev_device_get_devnum(udev_device)) > 0) {
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(udev_device))
+ fprintf(f, "S:%s\n", udev_list_entry_get_name(list_entry) + strlen("/dev/"));
+ if (udev_device_get_devlink_priority(udev_device) != 0)
+ fprintf(f, "L:%i\n", udev_device_get_devlink_priority(udev_device));
+ if (udev_device_get_watch_handle(udev_device) >= 0)
+ fprintf(f, "W:%i\n", udev_device_get_watch_handle(udev_device));
+ }
+
+ if (udev_device_get_usec_initialized(udev_device) > 0)
+ fprintf(f, "I:%llu\n", (unsigned long long)udev_device_get_usec_initialized(udev_device));
+
+ udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device)) {
+ if (!udev_list_entry_get_num(list_entry))
+ continue;
+ fprintf(f, "E:%s=%s\n",
+ 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);
+ r = rename(filename_tmp, filename);
+ if (r < 0)
+ return -1;
+ udev_dbg(udev, "created %s file '%s' for '%s'\n", has_info ? "db" : "empty",
+ filename, udev_device_get_devpath(udev_device));
+ return 0;
+}
+
+int udev_device_delete_db(struct udev_device *udev_device)
+{
+ const char *id;
+ char filename[UTIL_PATH_SIZE];
+
+ id = udev_device_get_id_filename(udev_device);
+ if (id == NULL)
+ return -1;
+ util_strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL);
+ unlink(filename);
+ return 0;
+}
diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c
new file mode 100644
index 0000000000..acf8e24d15
--- /dev/null
+++ b/src/libudev/libudev-device.c
@@ -0,0 +1,1758 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <net/if.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <linux/sockios.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-device
+ * @short_description: kernel sys devices
+ *
+ * Representation of kernel sys devices. Devices are uniquely identified
+ * by their syspath, every device has exactly one path in the kernel sys
+ * filesystem. Devices usually belong to a kernel subsystem, and and have
+ * a unique name inside that subsystem.
+ */
+
+/**
+ * udev_device:
+ *
+ * Opaque object representing one kernel sys device.
+ */
+struct udev_device {
+ struct udev *udev;
+ struct udev_device *parent_device;
+ char *syspath;
+ const char *devpath;
+ char *sysname;
+ const char *sysnum;
+ char *devnode;
+ mode_t devnode_mode;
+ char *subsystem;
+ char *devtype;
+ char *driver;
+ char *action;
+ char *devpath_old;
+ char *id_filename;
+ char **envp;
+ char *monitor_buf;
+ size_t monitor_buf_len;
+ struct udev_list devlinks_list;
+ struct udev_list properties_list;
+ struct udev_list sysattr_value_list;
+ struct udev_list sysattr_list;
+ struct udev_list tags_list;
+ unsigned long long int seqnum;
+ usec_t usec_initialized;
+ int devlink_priority;
+ int refcount;
+ dev_t devnum;
+ int ifindex;
+ int watch_handle;
+ int maj, min;
+ bool parent_set;
+ bool subsystem_set;
+ bool devtype_set;
+ bool devlinks_uptodate;
+ bool envp_uptodate;
+ bool tags_uptodate;
+ bool driver_set;
+ bool info_loaded;
+ bool db_loaded;
+ bool uevent_loaded;
+ bool is_initialized;
+ bool sysattr_list_read;
+ bool db_persist;
+};
+
+/**
+ * udev_device_get_seqnum:
+ * @udev_device: udev device
+ *
+ * This is only valid if the device was received through a monitor. Devices read from
+ * sys do not have a sequence number.
+ *
+ * Returns: the kernel event sequence number, or 0 if there is no sequence number available.
+ **/
+_public_ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return 0;
+ return udev_device->seqnum;
+}
+
+static int udev_device_set_seqnum(struct udev_device *udev_device, unsigned long long int seqnum)
+{
+ char num[32];
+
+ udev_device->seqnum = seqnum;
+ snprintf(num, sizeof(num), "%llu", seqnum);
+ udev_device_add_property(udev_device, "SEQNUM", num);
+ return 0;
+}
+
+int udev_device_get_ifindex(struct udev_device *udev_device)
+{
+ if (!udev_device->info_loaded)
+ udev_device_read_uevent_file(udev_device);
+ return udev_device->ifindex;
+}
+
+static int udev_device_set_ifindex(struct udev_device *udev_device, int ifindex)
+{
+ char num[32];
+
+ udev_device->ifindex = ifindex;
+ snprintf(num, sizeof(num), "%u", ifindex);
+ udev_device_add_property(udev_device, "IFINDEX", num);
+ return 0;
+}
+
+/**
+ * udev_device_get_devnum:
+ * @udev_device: udev device
+ *
+ * Get the device major/minor number.
+ *
+ * Returns: the dev_t number.
+ **/
+_public_ dev_t udev_device_get_devnum(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return makedev(0, 0);
+ if (!udev_device->info_loaded)
+ udev_device_read_uevent_file(udev_device);
+ return udev_device->devnum;
+}
+
+static int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum)
+{
+ char num[32];
+
+ udev_device->devnum = devnum;
+
+ snprintf(num, sizeof(num), "%u", major(devnum));
+ udev_device_add_property(udev_device, "MAJOR", num);
+ snprintf(num, sizeof(num), "%u", minor(devnum));
+ udev_device_add_property(udev_device, "MINOR", num);
+ return 0;
+}
+
+const char *udev_device_get_devpath_old(struct udev_device *udev_device)
+{
+ return udev_device->devpath_old;
+}
+
+static int udev_device_set_devpath_old(struct udev_device *udev_device, const char *devpath_old)
+{
+ const char *pos;
+
+ free(udev_device->devpath_old);
+ udev_device->devpath_old = strdup(devpath_old);
+ if (udev_device->devpath_old == NULL)
+ return -ENOMEM;
+ udev_device_add_property(udev_device, "DEVPATH_OLD", udev_device->devpath_old);
+
+ pos = strrchr(udev_device->devpath_old, '/');
+ if (pos == NULL)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * udev_device_get_driver:
+ * @udev_device: udev device
+ *
+ * Get the kernel driver name.
+ *
+ * Returns: the driver name string, or #NULL if there is no driver attached.
+ **/
+_public_ const char *udev_device_get_driver(struct udev_device *udev_device)
+{
+ char driver[UTIL_NAME_SIZE];
+
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->driver_set) {
+ udev_device->driver_set = true;
+ if (util_get_sys_core_link_value(udev_device->udev, "driver", udev_device->syspath, driver, sizeof(driver)) > 0)
+ udev_device->driver = strdup(driver);
+ }
+ return udev_device->driver;
+}
+
+static int udev_device_set_driver(struct udev_device *udev_device, const char *driver)
+{
+ free(udev_device->driver);
+ udev_device->driver = strdup(driver);
+ if (udev_device->driver == NULL)
+ return -ENOMEM;
+ udev_device->driver_set = true;
+ udev_device_add_property(udev_device, "DRIVER", udev_device->driver);
+ return 0;
+}
+
+/**
+ * udev_device_get_devtype:
+ * @udev_device: udev device
+ *
+ * Retrieve the devtype string of the udev device.
+ *
+ * Returns: the devtype name of the udev device, or #NULL if it can not be determined
+ **/
+_public_ const char *udev_device_get_devtype(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->devtype_set) {
+ udev_device->devtype_set = true;
+ udev_device_read_uevent_file(udev_device);
+ }
+ return udev_device->devtype;
+}
+
+static int udev_device_set_devtype(struct udev_device *udev_device, const char *devtype)
+{
+ free(udev_device->devtype);
+ udev_device->devtype = strdup(devtype);
+ if (udev_device->devtype == NULL)
+ return -ENOMEM;
+ udev_device->devtype_set = true;
+ udev_device_add_property(udev_device, "DEVTYPE", udev_device->devtype);
+ return 0;
+}
+
+int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsystem)
+{
+ free(udev_device->subsystem);
+ udev_device->subsystem = strdup(subsystem);
+ if (udev_device->subsystem == NULL)
+ return -ENOMEM;
+ udev_device->subsystem_set = true;
+ udev_device_add_property(udev_device, "SUBSYSTEM", udev_device->subsystem);
+ return 0;
+}
+
+/**
+ * udev_device_get_subsystem:
+ * @udev_device: udev device
+ *
+ * Retrieve the subsystem string of the udev device. The string does not
+ * contain any "/".
+ *
+ * Returns: the subsystem name of the udev device, or #NULL if it can not be determined
+ **/
+_public_ const char *udev_device_get_subsystem(struct udev_device *udev_device)
+{
+ char subsystem[UTIL_NAME_SIZE];
+
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->subsystem_set) {
+ udev_device->subsystem_set = true;
+ /* read "subsystem" link */
+ if (util_get_sys_core_link_value(udev_device->udev, "subsystem", udev_device->syspath, subsystem, sizeof(subsystem)) > 0) {
+ udev_device_set_subsystem(udev_device, subsystem);
+ return udev_device->subsystem;
+ }
+ /* implicit names */
+ if (startswith(udev_device->devpath, "/module/")) {
+ udev_device_set_subsystem(udev_device, "module");
+ return udev_device->subsystem;
+ }
+ if (strstr(udev_device->devpath, "/drivers/") != NULL) {
+ udev_device_set_subsystem(udev_device, "drivers");
+ return udev_device->subsystem;
+ }
+ if (startswith(udev_device->devpath, "/subsystem/") ||
+ startswith(udev_device->devpath, "/class/") ||
+ startswith(udev_device->devpath, "/bus/")) {
+ udev_device_set_subsystem(udev_device, "subsystem");
+ return udev_device->subsystem;
+ }
+ }
+ return udev_device->subsystem;
+}
+
+mode_t udev_device_get_devnode_mode(struct udev_device *udev_device)
+{
+ if (!udev_device->info_loaded)
+ udev_device_read_uevent_file(udev_device);
+ return udev_device->devnode_mode;
+}
+
+static int udev_device_set_devnode_mode(struct udev_device *udev_device, mode_t mode)
+{
+ char num[32];
+
+ udev_device->devnode_mode = mode;
+ snprintf(num, sizeof(num), "%#o", mode);
+ udev_device_add_property(udev_device, "DEVMODE", num);
+ return 0;
+}
+
+struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value)
+{
+ udev_device->envp_uptodate = false;
+ if (value == NULL) {
+ struct udev_list_entry *list_entry;
+
+ list_entry = udev_device_get_properties_list_entry(udev_device);
+ list_entry = udev_list_entry_get_by_name(list_entry, key);
+ if (list_entry != NULL)
+ udev_list_entry_delete(list_entry);
+ return NULL;
+ }
+ return udev_list_entry_add(&udev_device->properties_list, key, value);
+}
+
+static struct udev_list_entry *udev_device_add_property_from_string(struct udev_device *udev_device, const char *property)
+{
+ char name[UTIL_LINE_SIZE];
+ char *val;
+
+ util_strscpy(name, sizeof(name), property);
+ val = strchr(name, '=');
+ if (val == NULL)
+ return NULL;
+ val[0] = '\0';
+ val = &val[1];
+ if (val[0] == '\0')
+ val = NULL;
+ return udev_device_add_property(udev_device, name, val);
+}
+
+/*
+ * parse property string, and if needed, update internal values accordingly
+ *
+ * udev_device_add_property_from_string_parse_finish() needs to be
+ * called after adding properties, and its return value checked
+ *
+ * udev_device_set_info_loaded() needs to be set, to avoid trying
+ * to use a device without a DEVPATH set
+ */
+void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property)
+{
+ if (startswith(property, "DEVPATH=")) {
+ char path[UTIL_PATH_SIZE];
+
+ util_strscpyl(path, sizeof(path), "/sys", &property[8], NULL);
+ udev_device_set_syspath(udev_device, path);
+ } else if (startswith(property, "SUBSYSTEM=")) {
+ udev_device_set_subsystem(udev_device, &property[10]);
+ } else if (startswith(property, "DEVTYPE=")) {
+ udev_device_set_devtype(udev_device, &property[8]);
+ } else if (startswith(property, "DEVNAME=")) {
+ udev_device_set_devnode(udev_device, &property[8]);
+ } else if (startswith(property, "DEVLINKS=")) {
+ char devlinks[UTIL_PATH_SIZE];
+ char *slink;
+ char *next;
+
+ util_strscpy(devlinks, sizeof(devlinks), &property[9]);
+ slink = devlinks;
+ next = strchr(slink, ' ');
+ while (next != NULL) {
+ next[0] = '\0';
+ udev_device_add_devlink(udev_device, slink);
+ slink = &next[1];
+ next = strchr(slink, ' ');
+ }
+ if (slink[0] != '\0')
+ udev_device_add_devlink(udev_device, slink);
+ } else if (startswith(property, "TAGS=")) {
+ 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 (startswith(property, "USEC_INITIALIZED=")) {
+ udev_device_set_usec_initialized(udev_device, strtoull(&property[19], NULL, 10));
+ } else if (startswith(property, "DRIVER=")) {
+ udev_device_set_driver(udev_device, &property[7]);
+ } else if (startswith(property, "ACTION=")) {
+ udev_device_set_action(udev_device, &property[7]);
+ } else if (startswith(property, "MAJOR=")) {
+ udev_device->maj = strtoull(&property[6], NULL, 10);
+ } else if (startswith(property, "MINOR=")) {
+ udev_device->min = strtoull(&property[6], NULL, 10);
+ } else if (startswith(property, "DEVPATH_OLD=")) {
+ udev_device_set_devpath_old(udev_device, &property[12]);
+ } else if (startswith(property, "SEQNUM=")) {
+ udev_device_set_seqnum(udev_device, strtoull(&property[7], NULL, 10));
+ } else if (startswith(property, "IFINDEX=")) {
+ udev_device_set_ifindex(udev_device, strtoull(&property[8], NULL, 10));
+ } else if (startswith(property, "DEVMODE=")) {
+ udev_device_set_devnode_mode(udev_device, strtoul(&property[8], NULL, 8));
+ } else {
+ udev_device_add_property_from_string(udev_device, property);
+ }
+}
+
+int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device)
+{
+ if (udev_device->maj > 0)
+ udev_device_set_devnum(udev_device, makedev(udev_device->maj, udev_device->min));
+ udev_device->maj = 0;
+ udev_device->min = 0;
+
+ if (udev_device->devpath == NULL || udev_device->subsystem == NULL)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * udev_device_get_property_value:
+ * @udev_device: udev device
+ * @key: property name
+ *
+ * Get the value of a given property.
+ *
+ * Returns: the property string, or #NULL if there is no such property.
+ **/
+_public_ const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key)
+{
+ struct udev_list_entry *list_entry;
+
+ if (udev_device == NULL)
+ return NULL;
+ if (key == NULL)
+ return NULL;
+
+ list_entry = udev_device_get_properties_list_entry(udev_device);
+ list_entry = udev_list_entry_get_by_name(list_entry, key);
+ return udev_list_entry_get_value(list_entry);
+}
+
+int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
+{
+ char filename[UTIL_PATH_SIZE];
+ char line[UTIL_LINE_SIZE];
+ FILE *f;
+
+ /* providing a database file will always force-load it */
+ if (dbfile == NULL) {
+ const char *id;
+
+ if (udev_device->db_loaded)
+ return 0;
+ udev_device->db_loaded = true;
+
+ id = udev_device_get_id_filename(udev_device);
+ if (id == NULL)
+ return -1;
+ util_strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL);
+ dbfile = filename;
+ }
+
+ f = fopen(dbfile, "re");
+ if (f == NULL) {
+ udev_dbg(udev_device->udev, "no db file to read %s: %m\n", dbfile);
+ return -1;
+ }
+ udev_device->is_initialized = true;
+
+ while (fgets(line, sizeof(line), f)) {
+ ssize_t len;
+ const char *val;
+ struct udev_list_entry *entry;
+
+ len = strlen(line);
+ if (len < 4)
+ break;
+ line[len-1] = '\0';
+ val = &line[2];
+ switch(line[0]) {
+ case 'S':
+ util_strscpyl(filename, sizeof(filename), "/dev/", val, NULL);
+ udev_device_add_devlink(udev_device, filename);
+ break;
+ case 'L':
+ udev_device_set_devlink_priority(udev_device, atoi(val));
+ break;
+ case 'E':
+ entry = udev_device_add_property_from_string(udev_device, val);
+ udev_list_entry_set_num(entry, true);
+ break;
+ case 'G':
+ udev_device_add_tag(udev_device, val);
+ break;
+ case 'W':
+ udev_device_set_watch_handle(udev_device, atoi(val));
+ break;
+ case 'I':
+ udev_device_set_usec_initialized(udev_device, strtoull(val, NULL, 10));
+ break;
+ }
+ }
+ fclose(f);
+
+ udev_dbg(udev_device->udev, "device %p filled with db file data\n", udev_device);
+ return 0;
+}
+
+int udev_device_read_uevent_file(struct udev_device *udev_device)
+{
+ char filename[UTIL_PATH_SIZE];
+ FILE *f;
+ char line[UTIL_LINE_SIZE];
+ int maj = 0;
+ int min = 0;
+
+ if (udev_device->uevent_loaded)
+ return 0;
+
+ util_strscpyl(filename, sizeof(filename), udev_device->syspath, "/uevent", NULL);
+ f = fopen(filename, "re");
+ if (f == NULL)
+ return -1;
+ udev_device->uevent_loaded = true;
+
+ while (fgets(line, sizeof(line), f)) {
+ char *pos;
+
+ pos = strchr(line, '\n');
+ if (pos == NULL)
+ continue;
+ pos[0] = '\0';
+
+ if (startswith(line, "DEVTYPE=")) {
+ udev_device_set_devtype(udev_device, &line[8]);
+ continue;
+ }
+ if (startswith(line, "IFINDEX=")) {
+ udev_device_set_ifindex(udev_device, strtoull(&line[8], NULL, 10));
+ continue;
+ }
+ if (startswith(line, "DEVNAME=")) {
+ udev_device_set_devnode(udev_device, &line[8]);
+ continue;
+ }
+
+ if (startswith(line, "MAJOR="))
+ maj = strtoull(&line[6], NULL, 10);
+ else if (startswith(line, "MINOR="))
+ min = strtoull(&line[6], NULL, 10);
+ else if (startswith(line, "DEVMODE="))
+ udev_device->devnode_mode = strtoul(&line[8], NULL, 8);
+
+ udev_device_add_property_from_string(udev_device, line);
+ }
+
+ udev_device->devnum = makedev(maj, min);
+ fclose(f);
+ return 0;
+}
+
+void udev_device_set_info_loaded(struct udev_device *device)
+{
+ device->info_loaded = true;
+}
+
+struct udev_device *udev_device_new(struct udev *udev)
+{
+ struct udev_device *udev_device;
+ struct udev_list_entry *list_entry;
+
+ if (udev == NULL)
+ return NULL;
+
+ udev_device = calloc(1, sizeof(struct udev_device));
+ if (udev_device == NULL)
+ return NULL;
+ udev_device->refcount = 1;
+ udev_device->udev = udev;
+ udev_list_init(udev, &udev_device->devlinks_list, true);
+ udev_list_init(udev, &udev_device->properties_list, true);
+ udev_list_init(udev, &udev_device->sysattr_value_list, true);
+ udev_list_init(udev, &udev_device->sysattr_list, false);
+ udev_list_init(udev, &udev_device->tags_list, true);
+ udev_device->watch_handle = -1;
+ /* copy global properties */
+ udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev))
+ udev_device_add_property(udev_device,
+ udev_list_entry_get_name(list_entry),
+ udev_list_entry_get_value(list_entry));
+ return udev_device;
+}
+
+/**
+ * udev_device_new_from_syspath:
+ * @udev: udev library context
+ * @syspath: sys device path including sys directory
+ *
+ * Create new udev device, and fill in information from the sys
+ * device and the udev database entry. The syspath is the absolute
+ * path to the device, including the sys mount point.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+_public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath)
+{
+ const char *subdir;
+ char path[UTIL_PATH_SIZE];
+ char *pos;
+ struct stat statbuf;
+ struct udev_device *udev_device;
+
+ if (udev == NULL)
+ return NULL;
+ if (syspath == NULL)
+ return NULL;
+
+ /* path starts in sys */
+ if (!startswith(syspath, "/sys")) {
+ udev_dbg(udev, "not in sys :%s\n", syspath);
+ return NULL;
+ }
+
+ /* path is not a root directory */
+ subdir = syspath + strlen("/sys");
+ pos = strrchr(subdir, '/');
+ if (pos == NULL || pos[1] == '\0' || pos < &subdir[2])
+ return NULL;
+
+ /* resolve possible symlink to real path */
+ util_strscpy(path, sizeof(path), syspath);
+ util_resolve_sys_link(udev, path, sizeof(path));
+
+ if (startswith(path + strlen("/sys"), "/devices/")) {
+ char file[UTIL_PATH_SIZE];
+
+ /* all "devices" require a "uevent" file */
+ util_strscpyl(file, sizeof(file), path, "/uevent", NULL);
+ if (stat(file, &statbuf) != 0)
+ return NULL;
+ } else {
+ /* everything else just needs to be a directory */
+ if (stat(path, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
+ return NULL;
+ }
+
+ udev_device = udev_device_new(udev);
+ if (udev_device == NULL)
+ return NULL;
+
+ udev_device_set_syspath(udev_device, path);
+ udev_dbg(udev, "device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device));
+
+ return udev_device;
+}
+
+/**
+ * udev_device_new_from_devnum:
+ * @udev: udev library context
+ * @type: char or block device
+ * @devnum: device major/minor number
+ *
+ * Create new udev device, and fill in information from the sys
+ * device and the udev database entry. The device is looked-up
+ * by its major/minor number and type. Character and block device
+ * numbers are not unique across the two types.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+_public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum)
+{
+ char path[UTIL_PATH_SIZE];
+ const char *type_str;
+
+ if (type == 'b')
+ type_str = "block";
+ else if (type == 'c')
+ type_str = "char";
+ else
+ return NULL;
+
+ /* use /sys/dev/{block,char}/<maj>:<min> link */
+ snprintf(path, sizeof(path), "/sys/dev/%s/%u:%u",
+ type_str, major(devnum), minor(devnum));
+ return udev_device_new_from_syspath(udev, path);
+}
+
+/**
+ * udev_device_new_from_device_id:
+ * @udev: udev library context
+ * @id: text string identifying a kernel device
+ *
+ * Create new udev device, and fill in information from the sys
+ * device and the udev database entry. The device is looked-up
+ * by a special string:
+ * b8:2 - block device major:minor
+ * c128:1 - char device major:minor
+ * n3 - network device ifindex
+ * +sound:card29 - kernel driver core subsystem:device name
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, char *id)
+{
+ char type;
+ int maj, min;
+ char subsys[UTIL_PATH_SIZE];
+ char *sysname;
+
+ switch(id[0]) {
+ case 'b':
+ case 'c':
+ if (sscanf(id, "%c%i:%i", &type, &maj, &min) != 3)
+ return NULL;
+ return udev_device_new_from_devnum(udev, type, makedev(maj, min));
+ case 'n': {
+ int sk;
+ struct ifreq ifr;
+ struct udev_device *dev;
+ int ifindex;
+
+ ifindex = strtoul(&id[1], NULL, 10);
+ if (ifindex <= 0)
+ return NULL;
+
+ sk = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sk < 0)
+ return NULL;
+ memset(&ifr, 0x00, sizeof(struct ifreq));
+ ifr.ifr_ifindex = ifindex;
+ if (ioctl(sk, SIOCGIFNAME, &ifr) != 0) {
+ close(sk);
+ return NULL;
+ }
+ close(sk);
+
+ dev = udev_device_new_from_subsystem_sysname(udev, "net", ifr.ifr_name);
+ if (dev == NULL)
+ return NULL;
+ if (udev_device_get_ifindex(dev) == ifindex)
+ return dev;
+ udev_device_unref(dev);
+ return NULL;
+ }
+ case '+':
+ util_strscpy(subsys, sizeof(subsys), &id[1]);
+ sysname = strchr(subsys, ':');
+ if (sysname == NULL)
+ return NULL;
+ sysname[0] = '\0';
+ sysname = &sysname[1];
+ return udev_device_new_from_subsystem_sysname(udev, subsys, sysname);
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * udev_device_new_from_subsystem_sysname:
+ * @udev: udev library context
+ * @subsystem: the subsystem of the device
+ * @sysname: the name of the device
+ *
+ * Create new udev device, and fill in information from the sys device
+ * and the udev database entry. The device is looked up by the subsystem
+ * and name string of the device, like "mem" / "zero", or "block" / "sda".
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+_public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname)
+{
+ char path[UTIL_PATH_SIZE];
+ struct stat statbuf;
+
+ if (streq(subsystem, "subsystem")) {
+ util_strscpyl(path, sizeof(path), "/sys/subsystem/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+
+ util_strscpyl(path, sizeof(path), "/sys/bus/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+
+ util_strscpyl(path, sizeof(path), "/sys/class/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+ goto out;
+ }
+
+ if (streq(subsystem, "module")) {
+ util_strscpyl(path, sizeof(path), "/sys/module/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+ goto out;
+ }
+
+ if (streq(subsystem, "drivers")) {
+ char subsys[UTIL_NAME_SIZE];
+ char *driver;
+
+ util_strscpy(subsys, sizeof(subsys), sysname);
+ driver = strchr(subsys, ':');
+ if (driver != NULL) {
+ driver[0] = '\0';
+ driver = &driver[1];
+
+ util_strscpyl(path, sizeof(path), "/sys/subsystem/", subsys, "/drivers/", driver, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+
+ util_strscpyl(path, sizeof(path), "/sys/bus/", subsys, "/drivers/", driver, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+ }
+ goto out;
+ }
+
+ util_strscpyl(path, sizeof(path), "/sys/subsystem/", subsystem, "/devices/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+
+ util_strscpyl(path, sizeof(path), "/sys/bus/", subsystem, "/devices/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+
+ util_strscpyl(path, sizeof(path), "/sys/class/", subsystem, "/", sysname, NULL);
+ if (stat(path, &statbuf) == 0)
+ goto found;
+out:
+ return NULL;
+found:
+ return udev_device_new_from_syspath(udev, path);
+}
+
+/**
+ * udev_device_new_from_environment
+ * @udev: udev library context
+ *
+ * Create new udev device, and fill in information from the
+ * current process environment. This only works reliable if
+ * the process is called from a udev rule. It is usually used
+ * for tools executed from IMPORT= rules.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, if it does not exist
+ **/
+_public_ struct udev_device *udev_device_new_from_environment(struct udev *udev)
+{
+ int i;
+ struct udev_device *udev_device;
+
+ udev_device = udev_device_new(udev);
+ if (udev_device == NULL)
+ return NULL;
+ udev_device_set_info_loaded(udev_device);
+
+ for (i = 0; environ[i] != NULL; i++)
+ udev_device_add_property_from_string_parse(udev_device, environ[i]);
+
+ if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) {
+ udev_dbg(udev, "missing values, invalid device\n");
+ udev_device_unref(udev_device);
+ udev_device = NULL;
+ }
+
+ return udev_device;
+}
+
+static struct udev_device *device_new_from_parent(struct udev_device *udev_device)
+{
+ struct udev_device *udev_device_parent = NULL;
+ char path[UTIL_PATH_SIZE];
+ const char *subdir;
+
+ util_strscpy(path, sizeof(path), udev_device->syspath);
+ subdir = path + strlen("/sys/");
+ for (;;) {
+ char *pos;
+
+ pos = strrchr(subdir, '/');
+ if (pos == NULL || pos < &subdir[2])
+ break;
+ pos[0] = '\0';
+ udev_device_parent = udev_device_new_from_syspath(udev_device->udev, path);
+ if (udev_device_parent != NULL)
+ return udev_device_parent;
+ }
+ return NULL;
+}
+
+/**
+ * udev_device_get_parent:
+ * @udev_device: the device to start searching from
+ *
+ * Find the next parent device, and fill in information from the sys
+ * device and the udev database entry.
+ *
+ * The returned the device is not referenced. It is attached to the
+ * child device, and will be cleaned up when the child device
+ * is cleaned up.
+ *
+ * It is not necessarily just the upper level directory, empty or not
+ * recognized sys directories are ignored.
+ *
+ * It can be called as many times as needed, without caring about
+ * references.
+ *
+ * Returns: a new udev device, or #NULL, if it no parent exist.
+ **/
+_public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->parent_set) {
+ udev_device->parent_set = true;
+ udev_device->parent_device = device_new_from_parent(udev_device);
+ }
+ return udev_device->parent_device;
+}
+
+/**
+ * udev_device_get_parent_with_subsystem_devtype:
+ * @udev_device: udev device to start searching from
+ * @subsystem: the subsystem of the device
+ * @devtype: the type (DEVTYPE) of the device
+ *
+ * Find the next parent device, with a matching subsystem and devtype
+ * value, and fill in information from the sys device and the udev
+ * database entry.
+ *
+ * If devtype is #NULL, only subsystem is checked, and any devtype will
+ * match.
+ *
+ * The returned the device is not referenced. It is attached to the
+ * child device, and will be cleaned up when the child device
+ * is cleaned up.
+ *
+ * It can be called as many times as needed, without caring about
+ * references.
+ *
+ * Returns: a new udev device, or #NULL if no matching parent exists.
+ **/
+_public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype)
+{
+ struct udev_device *parent;
+
+ if (subsystem == NULL)
+ return NULL;
+
+ parent = udev_device_get_parent(udev_device);
+ while (parent != NULL) {
+ const char *parent_subsystem;
+ const char *parent_devtype;
+
+ parent_subsystem = udev_device_get_subsystem(parent);
+ if (parent_subsystem != NULL && streq(parent_subsystem, subsystem)) {
+ if (devtype == NULL)
+ break;
+ parent_devtype = udev_device_get_devtype(parent);
+ if (parent_devtype != NULL && streq(parent_devtype, devtype))
+ break;
+ }
+ parent = udev_device_get_parent(parent);
+ }
+ return parent;
+}
+
+/**
+ * udev_device_get_udev:
+ * @udev_device: udev device
+ *
+ * Retrieve the udev library context the device was created with.
+ *
+ * Returns: the udev library context
+ **/
+_public_ struct udev *udev_device_get_udev(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ return udev_device->udev;
+}
+
+/**
+ * udev_device_ref:
+ * @udev_device: udev device
+ *
+ * Take a reference of a udev device.
+ *
+ * Returns: the passed udev device
+ **/
+_public_ struct udev_device *udev_device_ref(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ udev_device->refcount++;
+ return udev_device;
+}
+
+/**
+ * udev_device_unref:
+ * @udev_device: udev device
+ *
+ * Drop a reference of a udev device. If the refcount reaches zero,
+ * the resources of the device will be released.
+ *
+ * Returns: the passed udev device if it has still an active reference, or #NULL otherwise.
+ **/
+_public_ struct udev_device *udev_device_unref(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ udev_device->refcount--;
+ if (udev_device->refcount > 0)
+ return udev_device;
+ if (udev_device->parent_device != NULL)
+ udev_device_unref(udev_device->parent_device);
+ free(udev_device->syspath);
+ free(udev_device->sysname);
+ free(udev_device->devnode);
+ free(udev_device->subsystem);
+ free(udev_device->devtype);
+ udev_list_cleanup(&udev_device->devlinks_list);
+ udev_list_cleanup(&udev_device->properties_list);
+ udev_list_cleanup(&udev_device->sysattr_value_list);
+ udev_list_cleanup(&udev_device->sysattr_list);
+ udev_list_cleanup(&udev_device->tags_list);
+ free(udev_device->action);
+ free(udev_device->driver);
+ free(udev_device->devpath_old);
+ free(udev_device->id_filename);
+ free(udev_device->envp);
+ free(udev_device->monitor_buf);
+ free(udev_device);
+ return NULL;
+}
+
+/**
+ * udev_device_get_devpath:
+ * @udev_device: udev device
+ *
+ * Retrieve the kernel devpath value of the udev device. The path
+ * does not contain the sys mount point, and starts with a '/'.
+ *
+ * Returns: the devpath of the udev device
+ **/
+_public_ const char *udev_device_get_devpath(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ return udev_device->devpath;
+}
+
+/**
+ * udev_device_get_syspath:
+ * @udev_device: udev device
+ *
+ * Retrieve the sys path of the udev device. The path is an
+ * absolute path and starts with the sys mount point.
+ *
+ * Returns: the sys path of the udev device
+ **/
+_public_ const char *udev_device_get_syspath(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ return udev_device->syspath;
+}
+
+/**
+ * udev_device_get_sysname:
+ * @udev_device: udev device
+ *
+ * Get the kernel device name in /sys.
+ *
+ * Returns: the name string of the device device
+ **/
+_public_ const char *udev_device_get_sysname(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ return udev_device->sysname;
+}
+
+/**
+ * udev_device_get_sysnum:
+ * @udev_device: udev device
+ *
+ * Get the instance number of the device.
+ *
+ * Returns: the trailing number string of the device name
+ **/
+_public_ const char *udev_device_get_sysnum(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ return udev_device->sysnum;
+}
+
+/**
+ * udev_device_get_devnode:
+ * @udev_device: udev device
+ *
+ * Retrieve the device node file name belonging to the udev device.
+ * The path is an absolute path, and starts with the device directory.
+ *
+ * Returns: the device node file name of the udev device, or #NULL if no device node exists
+ **/
+_public_ const char *udev_device_get_devnode(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ if (udev_device->devnode != NULL)
+ return udev_device->devnode;
+ if (!udev_device->info_loaded)
+ udev_device_read_uevent_file(udev_device);
+ return udev_device->devnode;
+}
+
+/**
+ * udev_device_get_devlinks_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of device links pointing to the device file of
+ * the udev device. The next list entry can be retrieved with
+ * udev_list_entry_get_next(), which returns #NULL if no more entries exist.
+ * The devlink path can be retrieved from the list entry by
+ * udev_list_entry_get_name(). The path is an absolute path, and starts with
+ * the device directory.
+ *
+ * Returns: the first entry of the device node link list
+ **/
+_public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ return udev_list_get_entry(&udev_device->devlinks_list);
+}
+
+void udev_device_cleanup_devlinks_list(struct udev_device *udev_device)
+{
+ udev_device->devlinks_uptodate = false;
+ udev_list_cleanup(&udev_device->devlinks_list);
+}
+
+/**
+ * udev_device_get_properties_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of key/value device properties of the udev
+ * device. The next list entry can be retrieved with udev_list_entry_get_next(),
+ * which returns #NULL if no more entries exist. The property name
+ * can be retrieved from the list entry by udev_list_entry_get_name(),
+ * the property value by udev_list_entry_get_value().
+ *
+ * Returns: the first entry of the property list
+ **/
+_public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->info_loaded) {
+ udev_device_read_uevent_file(udev_device);
+ udev_device_read_db(udev_device, NULL);
+ }
+ if (!udev_device->devlinks_uptodate) {
+ char symlinks[UTIL_PATH_SIZE];
+ struct udev_list_entry *list_entry;
+
+ udev_device->devlinks_uptodate = true;
+ list_entry = udev_device_get_devlinks_list_entry(udev_device);
+ if (list_entry != NULL) {
+ char *s;
+ size_t l;
+
+ s = symlinks;
+ l = util_strpcpyl(&s, sizeof(symlinks), udev_list_entry_get_name(list_entry), NULL);
+ udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry))
+ l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry), NULL);
+ udev_device_add_property(udev_device, "DEVLINKS", symlinks);
+ }
+ }
+ if (!udev_device->tags_uptodate) {
+ udev_device->tags_uptodate = true;
+ if (udev_device_get_tags_list_entry(udev_device) != NULL) {
+ char tags[UTIL_PATH_SIZE];
+ struct udev_list_entry *list_entry;
+ char *s;
+ size_t l;
+
+ s = tags;
+ l = util_strpcpyl(&s, sizeof(tags), ":", NULL);
+ udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device))
+ l = util_strpcpyl(&s, l, udev_list_entry_get_name(list_entry), ":", NULL);
+ udev_device_add_property(udev_device, "TAGS", tags);
+ }
+ }
+ return udev_list_get_entry(&udev_device->properties_list);
+}
+
+/**
+ * udev_device_get_action:
+ * @udev_device: udev device
+ *
+ * This is only valid if the device was received through a monitor. Devices read from
+ * sys do not have an action string. Usual actions are: add, remove, change, online,
+ * offline.
+ *
+ * Returns: the kernel action value, or #NULL if there is no action value available.
+ **/
+_public_ const char *udev_device_get_action(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ return udev_device->action;
+}
+
+/**
+ * udev_device_get_usec_since_initialized:
+ * @udev_device: udev device
+ *
+ * Return the number of microseconds passed since udev set up the
+ * device for the first time.
+ *
+ * This is only implemented for devices with need to store properties
+ * in the udev database. All other devices return 0 here.
+ *
+ * Returns: the number of microseconds since the device was first seen.
+ **/
+_public_ unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device)
+{
+ usec_t now_ts;
+
+ if (udev_device == NULL)
+ return 0;
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ if (udev_device->usec_initialized == 0)
+ return 0;
+ now_ts = now(CLOCK_MONOTONIC);
+ if (now_ts == 0)
+ return 0;
+ return now_ts - udev_device->usec_initialized;
+}
+
+usec_t udev_device_get_usec_initialized(struct udev_device *udev_device)
+{
+ return udev_device->usec_initialized;
+}
+
+void udev_device_set_usec_initialized(struct udev_device *udev_device, usec_t usec_initialized)
+{
+ char num[32];
+
+ udev_device->usec_initialized = usec_initialized;
+ snprintf(num, sizeof(num), "%llu", (unsigned long long)usec_initialized);
+ udev_device_add_property(udev_device, "USEC_INITIALIZED", num);
+}
+
+/**
+ * udev_device_get_sysattr_value:
+ * @udev_device: udev device
+ * @sysattr: attribute name
+ *
+ * The retrieved value is cached in the device. Repeated calls will return the same
+ * value and not open the attribute again.
+ *
+ * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value.
+ **/
+_public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr)
+{
+ struct udev_list_entry *list_entry;
+ char path[UTIL_PATH_SIZE];
+ char value[4096];
+ struct stat statbuf;
+ int fd;
+ ssize_t size;
+ const char *val = NULL;
+
+ if (udev_device == NULL)
+ return NULL;
+ if (sysattr == NULL)
+ return NULL;
+
+ /* look for possibly already cached result */
+ list_entry = udev_list_get_entry(&udev_device->sysattr_value_list);
+ list_entry = udev_list_entry_get_by_name(list_entry, sysattr);
+ if (list_entry != NULL)
+ return udev_list_entry_get_value(list_entry);
+
+ util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", sysattr, NULL);
+ if (lstat(path, &statbuf) != 0) {
+ udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, NULL);
+ goto out;
+ }
+
+ if (S_ISLNK(statbuf.st_mode)) {
+ struct udev_device *dev;
+
+ /*
+ * Some core links return only the last element of the target path,
+ * these are just values, the paths should not be exposed.
+ */
+ if (streq(sysattr, "driver") ||
+ streq(sysattr, "subsystem") ||
+ streq(sysattr, "module")) {
+ if (util_get_sys_core_link_value(udev_device->udev, sysattr,
+ udev_device->syspath, value, sizeof(value)) < 0)
+ return NULL;
+ list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, value);
+ val = udev_list_entry_get_value(list_entry);
+ goto out;
+ }
+
+ /* resolve link to a device and return its syspath */
+ util_strscpyl(path, sizeof(path), udev_device->syspath, "/", sysattr, NULL);
+ dev = udev_device_new_from_syspath(udev_device->udev, path);
+ if (dev != NULL) {
+ list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr,
+ udev_device_get_syspath(dev));
+ val = udev_list_entry_get_value(list_entry);
+ udev_device_unref(dev);
+ }
+
+ goto out;
+ }
+
+ /* skip directories */
+ if (S_ISDIR(statbuf.st_mode))
+ goto out;
+
+ /* skip non-readable files */
+ if ((statbuf.st_mode & S_IRUSR) == 0)
+ goto out;
+
+ /* read attribute value */
+ fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ goto out;
+ size = read(fd, value, sizeof(value));
+ close(fd);
+ if (size < 0)
+ goto out;
+ if (size == sizeof(value))
+ goto out;
+
+ /* got a valid value, store it in cache and return it */
+ value[size] = '\0';
+ util_remove_trailing_chars(value, '\n');
+ list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, value);
+ val = udev_list_entry_get_value(list_entry);
+out:
+ return val;
+}
+
+static int udev_device_sysattr_list_read(struct udev_device *udev_device)
+{
+ struct dirent *dent;
+ DIR *dir;
+ int num = 0;
+
+ if (udev_device == NULL)
+ return -1;
+ if (udev_device->sysattr_list_read)
+ return 0;
+
+ dir = opendir(udev_device_get_syspath(udev_device));
+ if (!dir)
+ return -1;
+
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char path[UTIL_PATH_SIZE];
+ struct stat statbuf;
+
+ /* only handle symlinks and regular files */
+ if (dent->d_type != DT_LNK && dent->d_type != DT_REG)
+ continue;
+
+ util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", dent->d_name, NULL);
+ if (lstat(path, &statbuf) != 0)
+ continue;
+ if ((statbuf.st_mode & S_IRUSR) == 0)
+ continue;
+
+ udev_list_entry_add(&udev_device->sysattr_list, dent->d_name, NULL);
+ num++;
+ }
+
+ closedir(dir);
+ udev_device->sysattr_list_read = true;
+
+ return num;
+}
+
+/**
+ * udev_device_get_sysattr_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of available sysattrs, with value being empty;
+ * This just return all available sysfs attributes for a particular
+ * device without reading their values.
+ *
+ * Returns: the first entry of the property list
+ **/
+_public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device)
+{
+ if (!udev_device->sysattr_list_read) {
+ int ret;
+ ret = udev_device_sysattr_list_read(udev_device);
+ if (0 > ret)
+ return NULL;
+ }
+
+ return udev_list_get_entry(&udev_device->sysattr_list);
+}
+
+int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath)
+{
+ const char *pos;
+ size_t len;
+
+ free(udev_device->syspath);
+ udev_device->syspath = strdup(syspath);
+ if (udev_device->syspath == NULL)
+ return -ENOMEM;
+ udev_device->devpath = udev_device->syspath + strlen("/sys");
+ udev_device_add_property(udev_device, "DEVPATH", udev_device->devpath);
+
+ pos = strrchr(udev_device->syspath, '/');
+ if (pos == NULL)
+ return -EINVAL;
+ udev_device->sysname = strdup(&pos[1]);
+ if (udev_device->sysname == NULL)
+ return -ENOMEM;
+
+ /* some devices have '!' in their name, change that to '/' */
+ len = 0;
+ while (udev_device->sysname[len] != '\0') {
+ if (udev_device->sysname[len] == '!')
+ udev_device->sysname[len] = '/';
+ len++;
+ }
+
+ /* trailing number */
+ while (len > 0 && isdigit(udev_device->sysname[--len]))
+ udev_device->sysnum = &udev_device->sysname[len];
+
+ /* sysname is completely numeric */
+ if (len == 0)
+ udev_device->sysnum = NULL;
+
+ return 0;
+}
+
+int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode)
+{
+ free(udev_device->devnode);
+ if (devnode[0] != '/') {
+ if (asprintf(&udev_device->devnode, "/dev/%s", devnode) < 0)
+ udev_device->devnode = NULL;
+ } else {
+ udev_device->devnode = strdup(devnode);
+ }
+ if (udev_device->devnode == NULL)
+ return -ENOMEM;
+ udev_device_add_property(udev_device, "DEVNAME", udev_device->devnode);
+ return 0;
+}
+
+int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink)
+{
+ struct udev_list_entry *list_entry;
+
+ udev_device->devlinks_uptodate = false;
+ list_entry = udev_list_entry_add(&udev_device->devlinks_list, devlink, NULL);
+ if (list_entry == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+const char *udev_device_get_id_filename(struct udev_device *udev_device)
+{
+ if (udev_device->id_filename == NULL) {
+ if (udev_device_get_subsystem(udev_device) == NULL)
+ return NULL;
+
+ if (major(udev_device_get_devnum(udev_device)) > 0) {
+ /* use dev_t -- b259:131072, c254:0 */
+ if (asprintf(&udev_device->id_filename, "%c%u:%u",
+ streq(udev_device_get_subsystem(udev_device), "block") ? 'b' : 'c',
+ major(udev_device_get_devnum(udev_device)),
+ minor(udev_device_get_devnum(udev_device))) < 0)
+ udev_device->id_filename = NULL;
+ } else if (udev_device_get_ifindex(udev_device) > 0) {
+ /* use netdev ifindex -- n3 */
+ if (asprintf(&udev_device->id_filename, "n%u", udev_device_get_ifindex(udev_device)) < 0)
+ udev_device->id_filename = NULL;
+ } else {
+ /*
+ * use $subsys:$syname -- pci:0000:00:1f.2
+ * sysname() has '!' translated, get it from devpath
+ */
+ const char *sysname;
+ sysname = strrchr(udev_device->devpath, '/');
+ if (sysname == NULL)
+ return NULL;
+ sysname = &sysname[1];
+ if (asprintf(&udev_device->id_filename, "+%s:%s", udev_device_get_subsystem(udev_device), sysname) < 0)
+ udev_device->id_filename = NULL;
+ }
+ }
+ return udev_device->id_filename;
+}
+
+/**
+ * udev_device_get_is_initialized:
+ * @udev_device: udev device
+ *
+ * Check if udev has already handled the device and has set up
+ * device node permissions and context, or has renamed a network
+ * device.
+ *
+ * This is only implemented for devices with a device node
+ * or network interfaces. All other devices return 1 here.
+ *
+ * Returns: 1 if the device is set up. 0 otherwise.
+ **/
+_public_ int udev_device_get_is_initialized(struct udev_device *udev_device)
+{
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ return udev_device->is_initialized;
+}
+
+void udev_device_set_is_initialized(struct udev_device *udev_device)
+{
+ udev_device->is_initialized = true;
+}
+
+int udev_device_add_tag(struct udev_device *udev_device, const char *tag)
+{
+ if (strchr(tag, ':') != NULL || strchr(tag, ' ') != NULL)
+ return -EINVAL;
+ udev_device->tags_uptodate = false;
+ if (udev_list_entry_add(&udev_device->tags_list, tag, NULL) != NULL)
+ return 0;
+ return -ENOMEM;
+}
+
+void udev_device_cleanup_tags_list(struct udev_device *udev_device)
+{
+ udev_device->tags_uptodate = false;
+ udev_list_cleanup(&udev_device->tags_list);
+}
+
+/**
+ * udev_device_get_tags_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of tags attached to the udev device. The next
+ * list entry can be retrieved with udev_list_entry_get_next(),
+ * which returns #NULL if no more entries exist. The tag string
+ * can be retrieved from the list entry by udev_list_entry_get_name().
+ *
+ * Returns: the first entry of the tag list
+ **/
+_public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device)
+{
+ if (udev_device == NULL)
+ return NULL;
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ return udev_list_get_entry(&udev_device->tags_list);
+}
+
+/**
+ * udev_device_has_tag:
+ * @udev_device: udev device
+ * @tag: tag name
+ *
+ * Check if a given device has a certain tag associated.
+ *
+ * Returns: 1 if the tag is found. 0 otherwise.
+ **/
+_public_ int udev_device_has_tag(struct udev_device *udev_device, const char *tag)
+{
+ struct udev_list_entry *list_entry;
+
+ if (udev_device == NULL)
+ return false;
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ list_entry = udev_device_get_tags_list_entry(udev_device);
+ if (udev_list_entry_get_by_name(list_entry, tag) != NULL)
+ return true;
+ return false;
+}
+
+#define ENVP_SIZE 128
+#define MONITOR_BUF_SIZE 4096
+static int update_envp_monitor_buf(struct udev_device *udev_device)
+{
+ struct udev_list_entry *list_entry;
+ char *s;
+ size_t l;
+ unsigned int i;
+
+ /* monitor buffer of property strings */
+ free(udev_device->monitor_buf);
+ udev_device->monitor_buf_len = 0;
+ udev_device->monitor_buf = malloc(MONITOR_BUF_SIZE);
+ if (udev_device->monitor_buf == NULL)
+ return -ENOMEM;
+
+ /* envp array, strings will point into monitor buffer */
+ if (udev_device->envp == NULL)
+ udev_device->envp = malloc(sizeof(char *) * ENVP_SIZE);
+ if (udev_device->envp == NULL)
+ return -ENOMEM;
+
+ i = 0;
+ s = udev_device->monitor_buf;
+ l = MONITOR_BUF_SIZE;
+ udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(udev_device)) {
+ const char *key;
+
+ key = udev_list_entry_get_name(list_entry);
+ /* skip private variables */
+ if (key[0] == '.')
+ continue;
+
+ /* add string to envp array */
+ udev_device->envp[i++] = s;
+ if (i+1 >= ENVP_SIZE)
+ return -EINVAL;
+
+ /* add property string to monitor buffer */
+ l = util_strpcpyl(&s, l, key, "=", udev_list_entry_get_value(list_entry), NULL);
+ if (l == 0)
+ return -EINVAL;
+ /* advance past the trailing '\0' that util_strpcpyl() guarantees */
+ s++;
+ l--;
+ }
+ udev_device->envp[i] = NULL;
+ udev_device->monitor_buf_len = s - udev_device->monitor_buf;
+ udev_device->envp_uptodate = true;
+ return 0;
+}
+
+char **udev_device_get_properties_envp(struct udev_device *udev_device)
+{
+ if (!udev_device->envp_uptodate)
+ if (update_envp_monitor_buf(udev_device) != 0)
+ return NULL;
+ return udev_device->envp;
+}
+
+ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf)
+{
+ if (!udev_device->envp_uptodate)
+ if (update_envp_monitor_buf(udev_device) != 0)
+ return -EINVAL;
+ *buf = udev_device->monitor_buf;
+ return udev_device->monitor_buf_len;
+}
+
+int udev_device_set_action(struct udev_device *udev_device, const char *action)
+{
+ free(udev_device->action);
+ udev_device->action = strdup(action);
+ if (udev_device->action == NULL)
+ return -ENOMEM;
+ udev_device_add_property(udev_device, "ACTION", udev_device->action);
+ return 0;
+}
+
+int udev_device_get_devlink_priority(struct udev_device *udev_device)
+{
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ return udev_device->devlink_priority;
+}
+
+int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio)
+{
+ udev_device->devlink_priority = prio;
+ return 0;
+}
+
+int udev_device_get_watch_handle(struct udev_device *udev_device)
+{
+ if (!udev_device->info_loaded)
+ udev_device_read_db(udev_device, NULL);
+ return udev_device->watch_handle;
+}
+
+int udev_device_set_watch_handle(struct udev_device *udev_device, int handle)
+{
+ udev_device->watch_handle = handle;
+ return 0;
+}
+
+bool udev_device_get_db_persist(struct udev_device *udev_device)
+{
+ return udev_device->db_persist;
+}
+
+void udev_device_set_db_persist(struct udev_device *udev_device)
+{
+ udev_device->db_persist = true;
+}
diff --git a/src/libudev/libudev-enumerate.c b/src/libudev/libudev-enumerate.c
new file mode 100644
index 0000000000..172965511b
--- /dev/null
+++ b/src/libudev/libudev-enumerate.c
@@ -0,0 +1,956 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-enumerate
+ * @short_description: lookup and sort sys devices
+ *
+ * Lookup devices in the sys filesystem, filter devices by properties,
+ * and return a sorted list of devices.
+ */
+
+struct syspath {
+ char *syspath;
+ size_t len;
+};
+
+/**
+ * udev_enumerate:
+ *
+ * Opaque object representing one device lookup/sort context.
+ */
+struct udev_enumerate {
+ struct udev *udev;
+ int refcount;
+ struct udev_list sysattr_match_list;
+ struct udev_list sysattr_nomatch_list;
+ struct udev_list subsystem_match_list;
+ struct udev_list subsystem_nomatch_list;
+ struct udev_list sysname_match_list;
+ struct udev_list properties_match_list;
+ struct udev_list tags_match_list;
+ struct udev_device *parent_match;
+ struct udev_list devices_list;
+ struct syspath *devices;
+ unsigned int devices_cur;
+ unsigned int devices_max;
+ bool devices_uptodate:1;
+ bool match_is_initialized;
+};
+
+/**
+ * udev_enumerate_new:
+ * @udev: udev library context
+ *
+ * Create an enumeration context to scan /sys.
+ *
+ * Returns: an enumeration context.
+ **/
+_public_ struct udev_enumerate *udev_enumerate_new(struct udev *udev)
+{
+ struct udev_enumerate *udev_enumerate;
+
+ udev_enumerate = calloc(1, sizeof(struct udev_enumerate));
+ if (udev_enumerate == NULL)
+ return NULL;
+ udev_enumerate->refcount = 1;
+ udev_enumerate->udev = udev;
+ udev_list_init(udev, &udev_enumerate->sysattr_match_list, false);
+ udev_list_init(udev, &udev_enumerate->sysattr_nomatch_list, false);
+ udev_list_init(udev, &udev_enumerate->subsystem_match_list, true);
+ udev_list_init(udev, &udev_enumerate->subsystem_nomatch_list, true);
+ udev_list_init(udev, &udev_enumerate->sysname_match_list, true);
+ udev_list_init(udev, &udev_enumerate->properties_match_list, false);
+ udev_list_init(udev, &udev_enumerate->tags_match_list, true);
+ udev_list_init(udev, &udev_enumerate->devices_list, false);
+ return udev_enumerate;
+}
+
+/**
+ * udev_enumerate_ref:
+ * @udev_enumerate: context
+ *
+ * Take a reference of a enumeration context.
+ *
+ * Returns: the passed enumeration context
+ **/
+_public_ struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate)
+{
+ if (udev_enumerate == NULL)
+ return NULL;
+ udev_enumerate->refcount++;
+ return udev_enumerate;
+}
+
+/**
+ * udev_enumerate_unref:
+ * @udev_enumerate: context
+ *
+ * Drop a reference of an enumeration context. If the refcount reaches zero,
+ * all resources of the enumeration context will be released.
+ *
+ * Returns: the passed enumeration context if it has still an active reference, or #NULL otherwise.
+ **/
+_public_ struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate)
+{
+ unsigned int i;
+
+ if (udev_enumerate == NULL)
+ return NULL;
+ udev_enumerate->refcount--;
+ if (udev_enumerate->refcount > 0)
+ return udev_enumerate;
+ udev_list_cleanup(&udev_enumerate->sysattr_match_list);
+ udev_list_cleanup(&udev_enumerate->sysattr_nomatch_list);
+ udev_list_cleanup(&udev_enumerate->subsystem_match_list);
+ udev_list_cleanup(&udev_enumerate->subsystem_nomatch_list);
+ udev_list_cleanup(&udev_enumerate->sysname_match_list);
+ udev_list_cleanup(&udev_enumerate->properties_match_list);
+ udev_list_cleanup(&udev_enumerate->tags_match_list);
+ udev_device_unref(udev_enumerate->parent_match);
+ udev_list_cleanup(&udev_enumerate->devices_list);
+ for (i = 0; i < udev_enumerate->devices_cur; i++)
+ free(udev_enumerate->devices[i].syspath);
+ free(udev_enumerate->devices);
+ free(udev_enumerate);
+ return NULL;
+}
+
+/**
+ * udev_enumerate_get_udev:
+ * @udev_enumerate: context
+ *
+ * Get the udev library context.
+ *
+ * Returns: a pointer to the context.
+ */
+_public_ struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate)
+{
+ if (udev_enumerate == NULL)
+ return NULL;
+ return udev_enumerate->udev;
+}
+
+static int syspath_add(struct udev_enumerate *udev_enumerate, const char *syspath)
+{
+ char *path;
+ struct syspath *entry;
+
+ /* double array size if needed */
+ if (udev_enumerate->devices_cur >= udev_enumerate->devices_max) {
+ struct syspath *buf;
+ unsigned int add;
+
+ add = udev_enumerate->devices_max;
+ if (add < 1024)
+ add = 1024;
+ buf = realloc(udev_enumerate->devices, (udev_enumerate->devices_max + add) * sizeof(struct syspath));
+ if (buf == NULL)
+ return -ENOMEM;
+ udev_enumerate->devices = buf;
+ udev_enumerate->devices_max += add;
+ }
+
+ path = strdup(syspath);
+ if (path == NULL)
+ return -ENOMEM;
+ entry = &udev_enumerate->devices[udev_enumerate->devices_cur];
+ entry->syspath = path;
+ entry->len = strlen(path);
+ udev_enumerate->devices_cur++;
+ udev_enumerate->devices_uptodate = false;
+ return 0;
+}
+
+static int syspath_cmp(const void *p1, const void *p2)
+{
+ const struct syspath *path1 = p1;
+ const struct syspath *path2 = p2;
+ size_t len;
+ int ret;
+
+ len = MIN(path1->len, path2->len);
+ ret = memcmp(path1->syspath, path2->syspath, len);
+ if (ret == 0) {
+ if (path1->len < path2->len)
+ ret = -1;
+ else if (path1->len > path2->len)
+ ret = 1;
+ }
+ return ret;
+}
+
+/* For devices that should be moved to the absolute end of the list */
+static bool devices_delay_end(struct udev *udev, const char *syspath)
+{
+ static const char *delay_device_list[] = {
+ "/block/md",
+ "/block/dm-",
+ NULL
+ };
+ int i;
+
+ for (i = 0; delay_device_list[i] != NULL; i++) {
+ if (strstr(syspath + strlen("/sys"), delay_device_list[i]) != NULL)
+ return true;
+ }
+ return false;
+}
+
+/* For devices that should just be moved a little bit later, just
+ * before the point where some common path prefix changes. Returns the
+ * number of characters that make up that common prefix */
+static size_t devices_delay_later(struct udev *udev, const char *syspath)
+{
+ const char *c;
+
+ /* For sound cards the control device must be enumerated last
+ * to make sure it's the final device node that gets ACLs
+ * applied. Applications rely on this fact and use ACL changes
+ * on the control node as an indicator that the ACL change of
+ * the entire sound card completed. The kernel makes this
+ * guarantee when creating those devices, and hence we should
+ * too when enumerating them. */
+
+ if ((c = strstr(syspath, "/sound/card"))) {
+ c += 11;
+ c += strcspn(c, "/");
+
+ if (startswith(c, "/controlC"))
+ return c - syspath + 1;
+ }
+
+ return 0;
+}
+
+/**
+ * udev_enumerate_get_list_entry:
+ * @udev_enumerate: context
+ *
+ * Get the first entry of the sorted list of device paths.
+ *
+ * Returns: a udev_list_entry.
+ */
+_public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate)
+{
+ if (udev_enumerate == NULL)
+ return NULL;
+ if (!udev_enumerate->devices_uptodate) {
+ unsigned int i;
+ unsigned int max;
+ struct syspath *prev = NULL, *move_later = NULL;
+ size_t move_later_prefix = 0;
+
+ udev_list_cleanup(&udev_enumerate->devices_list);
+ qsort(udev_enumerate->devices, udev_enumerate->devices_cur, sizeof(struct syspath), syspath_cmp);
+
+ max = udev_enumerate->devices_cur;
+ for (i = 0; i < max; i++) {
+ struct syspath *entry = &udev_enumerate->devices[i];
+
+ /* skip duplicated entries */
+ if (prev != NULL &&
+ entry->len == prev->len &&
+ memcmp(entry->syspath, prev->syspath, entry->len) == 0)
+ continue;
+ prev = entry;
+
+ /* skip to be delayed devices, and add them to the end of the list */
+ if (devices_delay_end(udev_enumerate->udev, entry->syspath)) {
+ syspath_add(udev_enumerate, entry->syspath);
+ /* need to update prev here for the case realloc() gives a different address */
+ prev = &udev_enumerate->devices[i];
+ continue;
+ }
+
+ /* skip to be delayed devices, and move the to
+ * the point where the prefix changes. We can
+ * only move one item at a time. */
+ if (!move_later) {
+ move_later_prefix = devices_delay_later(udev_enumerate->udev, entry->syspath);
+
+ if (move_later_prefix > 0) {
+ move_later = entry;
+ continue;
+ }
+ }
+
+ if (move_later &&
+ strncmp(entry->syspath, move_later->syspath, move_later_prefix) != 0) {
+
+ udev_list_entry_add(&udev_enumerate->devices_list, move_later->syspath, NULL);
+ move_later = NULL;
+ }
+
+ udev_list_entry_add(&udev_enumerate->devices_list, entry->syspath, NULL);
+ }
+
+ if (move_later)
+ udev_list_entry_add(&udev_enumerate->devices_list, move_later->syspath, NULL);
+
+ /* add and cleanup delayed devices from end of list */
+ for (i = max; i < udev_enumerate->devices_cur; i++) {
+ struct syspath *entry = &udev_enumerate->devices[i];
+
+ udev_list_entry_add(&udev_enumerate->devices_list, entry->syspath, NULL);
+ free(entry->syspath);
+ }
+ udev_enumerate->devices_cur = max;
+
+ udev_enumerate->devices_uptodate = true;
+ }
+ return udev_list_get_entry(&udev_enumerate->devices_list);
+}
+
+/**
+ * udev_enumerate_add_match_subsystem:
+ * @udev_enumerate: context
+ * @subsystem: filter for a subsystem of the device to include in the list
+ *
+ * Match only devices belonging to a certain kernel subsystem.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (subsystem == NULL)
+ return 0;
+ if (udev_list_entry_add(&udev_enumerate->subsystem_match_list, subsystem, NULL) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_nomatch_subsystem:
+ * @udev_enumerate: context
+ * @subsystem: filter for a subsystem of the device to exclude from the list
+ *
+ * Match only devices not belonging to a certain kernel subsystem.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (subsystem == NULL)
+ return 0;
+ if (udev_list_entry_add(&udev_enumerate->subsystem_nomatch_list, subsystem, NULL) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_match_sysattr:
+ * @udev_enumerate: context
+ * @sysattr: filter for a sys attribute at the device to include in the list
+ * @value: optional value of the sys attribute
+ *
+ * Match only devices with a certain /sys device attribute.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (sysattr == NULL)
+ return 0;
+ if (udev_list_entry_add(&udev_enumerate->sysattr_match_list, sysattr, value) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_nomatch_sysattr:
+ * @udev_enumerate: context
+ * @sysattr: filter for a sys attribute at the device to exclude from the list
+ * @value: optional value of the sys attribute
+ *
+ * Match only devices not having a certain /sys device attribute.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (sysattr == NULL)
+ return 0;
+ if (udev_list_entry_add(&udev_enumerate->sysattr_nomatch_list, sysattr, value) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+static int match_sysattr_value(struct udev_device *dev, const char *sysattr, const char *match_val)
+{
+ const char *val = NULL;
+ bool match = false;
+
+ val = udev_device_get_sysattr_value(dev, sysattr);
+ if (val == NULL)
+ goto exit;
+ if (match_val == NULL) {
+ match = true;
+ goto exit;
+ }
+ if (fnmatch(match_val, val, 0) == 0) {
+ match = true;
+ goto exit;
+ }
+exit:
+ return match;
+}
+
+/**
+ * udev_enumerate_add_match_property:
+ * @udev_enumerate: context
+ * @property: filter for a property of the device to include in the list
+ * @value: value of the property
+ *
+ * Match only devices with a certain property.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (property == NULL)
+ return 0;
+ if (udev_list_entry_add(&udev_enumerate->properties_match_list, property, value) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_match_tag:
+ * @udev_enumerate: context
+ * @tag: filter for a tag of the device to include in the list
+ *
+ * Match only devices with a certain tag.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ 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->tags_match_list, tag, NULL) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_match_parent:
+ * @udev_enumerate: context
+ * @parent: parent device where to start searching
+ *
+ * Return the devices on the subtree of one given device. The parent
+ * itself is included in the list.
+ *
+ * A reference for the device is held until the udev_enumerate context
+ * is cleaned up.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (parent == NULL)
+ return 0;
+ if (udev_enumerate->parent_match != NULL)
+ udev_device_unref(udev_enumerate->parent_match);
+ udev_enumerate->parent_match = udev_device_ref(parent);
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_match_is_initialized:
+ * @udev_enumerate: context
+ *
+ * Match only devices which udev has set up already. This makes
+ * sure, that the device node permissions and context are properly set
+ * and that network devices are fully renamed.
+ *
+ * Usually, devices which are found in the kernel but not already
+ * handled by udev, have still pending events. Services should subscribe
+ * to monitor events and wait for these devices to become ready, instead
+ * of using uninitialized devices.
+ *
+ * For now, this will not affect devices which do not have a device node
+ * and are not network interfaces.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ udev_enumerate->match_is_initialized = true;
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_match_sysname:
+ * @udev_enumerate: context
+ * @sysname: filter for the name of the device to include in the list
+ *
+ * Match only devices with a given /sys device name.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (sysname == NULL)
+ return 0;
+ if (udev_list_entry_add(&udev_enumerate->sysname_match_list, sysname, NULL) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+static bool match_sysattr(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+ 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(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(dev, udev_list_entry_get_name(list_entry),
+ udev_list_entry_get_value(list_entry)))
+ return false;
+ }
+ return true;
+ }
+ return true;
+}
+
+static bool match_property(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+ struct udev_list_entry *list_entry;
+ bool match = false;
+
+ /* no match always matches */
+ if (udev_list_get_entry(&udev_enumerate->properties_match_list) == NULL)
+ return true;
+
+ /* loop over matches */
+ udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->properties_match_list)) {
+ const char *match_key = udev_list_entry_get_name(list_entry);
+ const char *match_value = udev_list_entry_get_value(list_entry);
+ struct udev_list_entry *property_entry;
+
+ /* loop over device properties */
+ udev_list_entry_foreach(property_entry, udev_device_get_properties_list_entry(dev)) {
+ const char *dev_key = udev_list_entry_get_name(property_entry);
+ const char *dev_value = udev_list_entry_get_value(property_entry);
+
+ if (fnmatch(match_key, dev_key, 0) != 0)
+ continue;
+ if (match_value == NULL && dev_value == NULL) {
+ match = true;
+ goto out;
+ }
+ if (match_value == NULL || dev_value == NULL)
+ continue;
+ if (fnmatch(match_value, dev_value, 0) == 0) {
+ match = true;
+ goto out;
+ }
+ }
+ }
+out:
+ return match;
+}
+
+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_parent(struct udev_enumerate *udev_enumerate, struct udev_device *dev)
+{
+ if (udev_enumerate->parent_match == NULL)
+ return true;
+
+ return startswith(udev_device_get_devpath(dev), udev_device_get_devpath(udev_enumerate->parent_match));
+}
+
+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 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 true;
+ }
+ return false;
+}
+
+static int scan_dir_and_add_devices(struct udev_enumerate *udev_enumerate,
+ const char *basedir, const char *subdir1, const char *subdir2)
+{
+ char path[UTIL_PATH_SIZE];
+ size_t l;
+ char *s;
+ DIR *dir;
+ struct dirent *dent;
+
+ s = path;
+ l = util_strpcpyl(&s, sizeof(path), "/sys/", basedir, NULL);
+ if (subdir1 != NULL)
+ l = util_strpcpyl(&s, l, "/", subdir1, NULL);
+ if (subdir2 != NULL)
+ util_strpcpyl(&s, l, "/", subdir2, NULL);
+ dir = opendir(path);
+ if (dir == NULL)
+ return -ENOENT;
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char syspath[UTIL_PATH_SIZE];
+ 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);
+ dev = udev_device_new_from_syspath(udev_enumerate->udev, syspath);
+ if (dev == NULL)
+ continue;
+
+ if (udev_enumerate->match_is_initialized) {
+ /*
+ * All devices with a device node or network interfaces
+ * possibly need udev to adjust the device node permission
+ * or context, or rename the interface before it can be
+ * reliably used from other processes.
+ *
+ * For now, we can only check these types of devices, we
+ * might not store a database, and have no way to find out
+ * for all other types of devices.
+ */
+ if (!udev_device_get_is_initialized(dev) &&
+ (major(udev_device_get_devnum(dev)) > 0 || udev_device_get_ifindex(dev) > 0))
+ goto nomatch;
+ }
+ if (!match_parent(udev_enumerate, dev))
+ goto nomatch;
+ 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 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 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 true;
+ }
+ return false;
+ }
+ return true;
+}
+
+static int scan_dir(struct udev_enumerate *udev_enumerate, const char *basedir, const char *subdir, const char *subsystem)
+{
+ char path[UTIL_PATH_SIZE];
+ DIR *dir;
+ struct dirent *dent;
+
+ util_strscpyl(path, sizeof(path), "/sys/", basedir, NULL);
+ dir = opendir(path);
+ if (dir == NULL)
+ return -1;
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ if (dent->d_name[0] == '.')
+ continue;
+ if (!match_subsystem(udev_enumerate, subsystem != NULL ? subsystem : dent->d_name))
+ continue;
+ scan_dir_and_add_devices(udev_enumerate, basedir, dent->d_name, subdir);
+ }
+ closedir(dir);
+ return 0;
+}
+
+/**
+ * udev_enumerate_add_syspath:
+ * @udev_enumerate: context
+ * @syspath: path of a device
+ *
+ * Add a device to the list of devices, to retrieve it back sorted in dependency order.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath)
+{
+ struct udev_device *udev_device;
+
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+ if (syspath == NULL)
+ return 0;
+ /* resolve to real syspath */
+ udev_device = udev_device_new_from_syspath(udev_enumerate->udev, syspath);
+ if (udev_device == NULL)
+ return -EINVAL;
+ syspath_add(udev_enumerate, udev_device_get_syspath(udev_device));
+ udev_device_unref(udev_device);
+ return 0;
+}
+
+static int scan_devices_tags(struct udev_enumerate *udev_enumerate)
+{
+ 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), "/run/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;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ dev = udev_device_new_from_device_id(udev_enumerate->udev, dent->d_name);
+ if (dev == NULL)
+ continue;
+
+ if (!match_subsystem(udev_enumerate, udev_device_get_subsystem(dev)))
+ goto nomatch;
+ if (!match_sysname(udev_enumerate, udev_device_get_sysname(dev)))
+ goto nomatch;
+ if (!match_parent(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 parent_add_child(struct udev_enumerate *enumerate, const char *path)
+{
+ struct udev_device *dev;
+
+ dev = udev_device_new_from_syspath(enumerate->udev, path);
+ if (dev == NULL)
+ return -ENODEV;
+
+ if (!match_subsystem(enumerate, udev_device_get_subsystem(dev)))
+ return 0;
+ if (!match_sysname(enumerate, udev_device_get_sysname(dev)))
+ return 0;
+ if (!match_property(enumerate, dev))
+ return 0;
+ if (!match_sysattr(enumerate, dev))
+ return 0;
+
+ syspath_add(enumerate, udev_device_get_syspath(dev));
+ udev_device_unref(dev);
+ return 1;
+}
+
+static int parent_crawl_children(struct udev_enumerate *enumerate, const char *path, int maxdepth)
+{
+ DIR *d;
+ struct dirent *dent;
+
+ d = opendir(path);
+ if (d == NULL)
+ return -errno;
+
+ for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
+ char *child;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ if (dent->d_type != DT_DIR)
+ continue;
+ if (asprintf(&child, "%s/%s", path, dent->d_name) < 0)
+ continue;
+ parent_add_child(enumerate, child);
+ if (maxdepth > 0)
+ parent_crawl_children(enumerate, child, maxdepth-1);
+ free(child);
+ }
+
+ closedir(d);
+ return 0;
+}
+
+static int scan_devices_children(struct udev_enumerate *enumerate)
+{
+ const char *path;
+
+ path = udev_device_get_syspath(enumerate->parent_match);
+ parent_add_child(enumerate, path);
+ return parent_crawl_children(enumerate, path, 256);
+}
+
+static int scan_devices_all(struct udev_enumerate *udev_enumerate)
+{
+ struct stat statbuf;
+
+ if (stat("/sys/subsystem", &statbuf) == 0) {
+ /* we have /subsystem/, forget all the old stuff */
+ scan_dir(udev_enumerate, "subsystem", "devices", NULL);
+ } else {
+ scan_dir(udev_enumerate, "bus", "devices", NULL);
+ scan_dir(udev_enumerate, "class", NULL, NULL);
+ }
+ return 0;
+}
+
+/**
+ * udev_enumerate_scan_devices:
+ * @udev_enumerate: udev enumeration context
+ *
+ * Scan /sys for all devices which match the given filters. No matches
+ * will return all currently available devices.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ **/
+_public_ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate)
+{
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+
+ /* efficiently lookup tags only, we maintain a reverse-index */
+ if (udev_list_get_entry(&udev_enumerate->tags_match_list) != NULL)
+ return scan_devices_tags(udev_enumerate);
+
+ /* walk the subtree of one parent device only */
+ if (udev_enumerate->parent_match != NULL)
+ return scan_devices_children(udev_enumerate);
+
+ /* scan devices of all subsystems */
+ return scan_devices_all(udev_enumerate);
+}
+
+/**
+ * udev_enumerate_scan_subsystems:
+ * @udev_enumerate: udev enumeration context
+ *
+ * Scan /sys for all kernel subsystems, including buses, classes, drivers.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ **/
+_public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate)
+{
+ struct stat statbuf;
+ const char *subsysdir;
+
+ if (udev_enumerate == NULL)
+ return -EINVAL;
+
+ /* all kernel modules */
+ if (match_subsystem(udev_enumerate, "module"))
+ scan_dir_and_add_devices(udev_enumerate, "module", NULL, NULL);
+
+ if (stat("/sys/subsystem", &statbuf) == 0)
+ subsysdir = "subsystem";
+ else
+ subsysdir = "bus";
+
+ /* all subsystems (only buses support coldplug) */
+ if (match_subsystem(udev_enumerate, "subsystem"))
+ scan_dir_and_add_devices(udev_enumerate, subsysdir, NULL, NULL);
+
+ /* all subsystem drivers */
+ if (match_subsystem(udev_enumerate, "drivers"))
+ scan_dir(udev_enumerate, subsysdir, "drivers", "drivers");
+ return 0;
+}
diff --git a/src/libudev/libudev-hwdb-def.h b/src/libudev/libudev-hwdb-def.h
new file mode 100644
index 0000000000..e167e2805b
--- /dev/null
+++ b/src/libudev/libudev-hwdb-def.h
@@ -0,0 +1,74 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifndef _LIBUDEV_HWDB_DEF_H_
+#define _LIBUDEV_HWDB_DEF_H_
+
+#include "sparse-endian.h"
+
+#define HWDB_SIG { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' }
+
+/* on-disk trie objects */
+_packed_ struct trie_header_f {
+ uint8_t signature[8];
+
+ /* version of tool which created the file */
+ le64_t tool_version;
+ le64_t file_size;
+
+ /* size of structures to allow them to grow */
+ le64_t header_size;
+ le64_t node_size;
+ le64_t child_entry_size;
+ le64_t value_entry_size;
+
+ /* offset of the root trie node */
+ le64_t nodes_root_off;
+
+ /* size of the nodes and string section */
+ le64_t nodes_len;
+ le64_t strings_len;
+};
+
+_packed_ struct trie_node_f {
+ /* prefix of lookup string, shared by all children */
+ le64_t prefix_off;
+ /* size of children entry array appended to the node */
+ uint8_t children_count;
+ uint8_t padding[7];
+ /* size of value entry array appended to the node */
+ le64_t values_count;
+};
+
+/* array of child entries, follows directly the node record */
+_packed_ struct trie_child_entry_f {
+ /* index of the child node */
+ uint8_t c;
+ uint8_t padding[7];
+ /* offset of the child node */
+ le64_t child_off;
+};
+
+/* array of value entries, follows directly the node record/child array */
+_packed_ struct trie_value_entry_f {
+ le64_t key_off;
+ le64_t value_off;
+};
+
+#endif
diff --git a/src/libudev/libudev-hwdb.c b/src/libudev/libudev-hwdb.c
new file mode 100644
index 0000000000..751b34209e
--- /dev/null
+++ b/src/libudev/libudev-hwdb.c
@@ -0,0 +1,392 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@vrfy.org>
+ Copyright 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <sys/mman.h>
+
+#include "libudev-private.h"
+#include "libudev-hwdb-def.h"
+
+/**
+ * SECTION:libudev-hwdb
+ * @short_description: retrieve properties from the hardware database
+ *
+ * Libudev hardware database interface.
+ */
+
+/**
+ * udev_hwdb:
+ *
+ * Opaque object representing the hardware database.
+ */
+struct udev_hwdb {
+ struct udev *udev;
+ int refcount;
+
+ FILE *f;
+ struct stat st;
+ union {
+ struct trie_header_f *head;
+ const char *map;
+ };
+
+ struct udev_list properties_list;
+};
+
+struct linebuf {
+ char bytes[LINE_MAX];
+ size_t size;
+ size_t len;
+};
+
+static void linebuf_init(struct linebuf *buf) {
+ buf->size = 0;
+ buf->len = 0;
+}
+
+static const char *linebuf_get(struct linebuf *buf) {
+ if (buf->len + 1 >= sizeof(buf->bytes))
+ return NULL;
+ buf->bytes[buf->len] = '\0';
+ return buf->bytes;
+}
+
+static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) {
+ if (buf->len + len >= sizeof(buf->bytes))
+ return false;
+ memcpy(buf->bytes + buf->len, s, len);
+ buf->len += len;
+ return true;
+}
+
+static bool linebuf_add_char(struct linebuf *buf, char c)
+{
+ if (buf->len + 1 >= sizeof(buf->bytes))
+ return false;
+ buf->bytes[buf->len++] = c;
+ return true;
+}
+
+static void linebuf_rem(struct linebuf *buf, size_t count) {
+ assert(buf->len >= count);
+ buf->len -= count;
+}
+
+static void linebuf_rem_char(struct linebuf *buf) {
+ linebuf_rem(buf, 1);
+}
+
+static const struct trie_child_entry_f *trie_node_children(struct udev_hwdb *hwdb, const struct trie_node_f *node) {
+ return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size));
+}
+
+static const struct trie_value_entry_f *trie_node_values(struct udev_hwdb *hwdb, const struct trie_node_f *node) {
+ const char *base = (const char *)node;
+
+ base += le64toh(hwdb->head->node_size);
+ base += node->children_count * le64toh(hwdb->head->child_entry_size);
+ return (const struct trie_value_entry_f *)base;
+}
+
+static const struct trie_node_f *trie_node_from_off(struct udev_hwdb *hwdb, le64_t off) {
+ return (const struct trie_node_f *)(hwdb->map + le64toh(off));
+}
+
+static const char *trie_string(struct udev_hwdb *hwdb, le64_t off) {
+ return hwdb->map + le64toh(off);
+}
+
+static int trie_children_cmp_f(const void *v1, const void *v2) {
+ const struct trie_child_entry_f *n1 = v1;
+ const struct trie_child_entry_f *n2 = v2;
+
+ return n1->c - n2->c;
+}
+
+static const struct trie_node_f *node_lookup_f(struct udev_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) {
+ struct trie_child_entry_f *child;
+ struct trie_child_entry_f search;
+
+ search.c = c;
+ child = bsearch(&search, trie_node_children(hwdb, node), node->children_count,
+ le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
+ if (child)
+ return trie_node_from_off(hwdb, child->child_off);
+ return NULL;
+}
+
+static int hwdb_add_property(struct udev_hwdb *hwdb, const char *key, const char *value) {
+ /* TODO: add sub-matches (+) against DMI data */
+ if (key[0] != ' ')
+ return 0;
+ if (udev_list_entry_add(&hwdb->properties_list, key+1, value) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+static int trie_fnmatch_f(struct udev_hwdb *hwdb, const struct trie_node_f *node, size_t p,
+ struct linebuf *buf, const char *search) {
+ size_t len;
+ size_t i;
+ const char *prefix;
+ int err;
+
+ prefix = trie_string(hwdb, node->prefix_off);
+ len = strlen(prefix + p);
+ linebuf_add(buf, prefix + p, len);
+
+ for (i = 0; i < node->children_count; i++) {
+ const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i];
+
+ linebuf_add_char(buf, child->c);
+ err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
+ if (err < 0)
+ return err;
+ linebuf_rem_char(buf);
+ }
+
+ if (node->values_count && fnmatch(linebuf_get(buf), search, 0) == 0)
+ for (i = 0; i < node->values_count; i++) {
+ err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off),
+ trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off));
+ if (err < 0)
+ return err;
+ }
+
+ linebuf_rem(buf, len);
+ return 0;
+}
+
+static int trie_search_f(struct udev_hwdb *hwdb, const char *search) {
+ struct linebuf buf;
+ const struct trie_node_f *node;
+ size_t i = 0;
+ int err;
+
+ linebuf_init(&buf);
+
+ node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
+ while (node) {
+ const struct trie_node_f *child;
+ size_t p = 0;
+
+ if (node->prefix_off) {
+ uint8_t c;
+
+ for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
+ if (c == '*' || c == '?' || c == '[')
+ return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
+ if (c != search[i + p])
+ return 0;
+ }
+ i += p;
+ }
+
+ child = node_lookup_f(hwdb, node, '*');
+ if (child) {
+ linebuf_add_char(&buf, '*');
+ err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
+ if (err < 0)
+ return err;
+ linebuf_rem_char(&buf);
+ }
+
+ child = node_lookup_f(hwdb, node, '?');
+ if (child) {
+ linebuf_add_char(&buf, '?');
+ err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
+ if (err < 0)
+ return err;
+ linebuf_rem_char(&buf);
+ }
+
+ child = node_lookup_f(hwdb, node, '[');
+ if (child) {
+ linebuf_add_char(&buf, '[');
+ err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
+ if (err < 0)
+ return err;
+ linebuf_rem_char(&buf);
+ }
+
+ if (search[i] == '\0') {
+ size_t n;
+
+ for (n = 0; n < node->values_count; n++) {
+ err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off),
+ trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+ }
+
+ child = node_lookup_f(hwdb, node, search[i]);
+ node = child;
+ i++;
+ }
+ return 0;
+}
+
+/**
+ * udev_hwdb_new:
+ * @udev: udev library context
+ *
+ * Create a hardware database context to query properties for devices.
+ *
+ * Returns: a hwdb context.
+ **/
+_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) {
+ struct udev_hwdb *hwdb;
+ const char sig[] = HWDB_SIG;
+
+ hwdb = new0(struct udev_hwdb, 1);
+ if (!hwdb)
+ return NULL;
+
+ hwdb->refcount = 1;
+ udev_list_init(udev, &hwdb->properties_list, true);
+
+ hwdb->f = fopen("/etc/udev/hwdb.bin", "re");
+ if (!hwdb->f) {
+ log_debug("error reading /etc/udev/hwdb.bin: %m");
+ udev_hwdb_unref(hwdb);
+ return NULL;
+ }
+
+ if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
+ (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) {
+ log_debug("error reading /etc/udev/hwdb.bin: %m");
+ udev_hwdb_unref(hwdb);
+ return NULL;
+ }
+
+ hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
+ if (hwdb->map == MAP_FAILED) {
+ log_debug("error mapping /etc/udev/hwdb.bin: %m");
+ udev_hwdb_unref(hwdb);
+ return NULL;
+ }
+
+ if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
+ (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
+ log_debug("error recognizing the format of /etc/udev/hwdb.bin");
+ udev_hwdb_unref(hwdb);
+ return NULL;
+ }
+
+ log_debug("=== trie on-disk ===\n");
+ log_debug("tool version: %llu", (unsigned long long)le64toh(hwdb->head->tool_version));
+ log_debug("file size: %8llu bytes\n", (unsigned long long)hwdb->st.st_size);
+ log_debug("header size %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->header_size));
+ log_debug("strings %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->strings_len));
+ log_debug("nodes %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->nodes_len));
+ return hwdb;
+}
+
+/**
+ * udev_hwdb_ref:
+ * @hwdb: context
+ *
+ * Take a reference of a hwdb context.
+ *
+ * Returns: the passed enumeration context
+ **/
+_public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) {
+ if (!hwdb)
+ return NULL;
+ hwdb->refcount++;
+ return hwdb;
+}
+
+/**
+ * udev_hwdb_unref:
+ * @hwdb: context
+ *
+ * Drop a reference of a hwdb context. If the refcount reaches zero,
+ * all resources of the hwdb context will be released.
+ *
+ * Returns: the passed hwdb context if it has still an active reference, or #NULL otherwise.
+ **/
+_public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) {
+ if (!hwdb)
+ return NULL;
+ hwdb->refcount--;
+ if (hwdb->refcount > 0)
+ return hwdb;
+ if (hwdb->map)
+ munmap((void *)hwdb->map, hwdb->st.st_size);
+ if (hwdb->f)
+ fclose(hwdb->f);
+ udev_list_cleanup(&hwdb->properties_list);
+ free(hwdb);
+ return NULL;
+}
+
+bool udev_hwdb_validate(struct udev_hwdb *hwdb) {
+ struct stat st;
+
+ if (!hwdb)
+ return false;
+ if (!hwdb->f)
+ return false;
+ if (fstat(fileno(hwdb->f), &st) < 0)
+ return true;
+ if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
+ return true;
+ return false;
+}
+
+/**
+ * udev_hwdb_get_properties_list_entry:
+ * @hwdb: context
+ * @modalias: modalias string
+ * @flags: (unused)
+ *
+ * Lookup a matching device in the hardware database. The lookup key is a
+ * modalias string, whose formats are defined for the Linux kernel modules.
+ * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry
+ * of a list of retrieved properties is returned.
+ *
+ * Returns: a udev_list_entry.
+ */
+_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) {
+ int err;
+
+ if (!hwdb->f) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ udev_list_cleanup(&hwdb->properties_list);
+ err = trie_search_f(hwdb, modalias);
+ if (err < 0) {
+ errno = -err;
+ return NULL;
+ }
+ return udev_list_get_entry(&hwdb->properties_list);
+}
diff --git a/src/libudev/libudev-list.c b/src/libudev/libudev-list.c
new file mode 100644
index 0000000000..59ba69e279
--- /dev/null
+++ b/src/libudev/libudev-list.c
@@ -0,0 +1,355 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-list
+ * @short_description: list operation
+ *
+ * Libudev list operations.
+ */
+
+/**
+ * udev_list_entry:
+ *
+ * Opaque object representing one entry in a list. An entry contains
+ * contains a name, and optionally a value.
+ */
+struct udev_list_entry {
+ struct udev_list_node node;
+ struct udev_list *list;
+ char *name;
+ char *value;
+ int num;
+};
+
+/* the list's head points to itself if empty */
+void udev_list_node_init(struct udev_list_node *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+int udev_list_node_is_empty(struct udev_list_node *list)
+{
+ return list->next == list;
+}
+
+static void udev_list_node_insert_between(struct udev_list_node *new,
+ struct udev_list_node *prev,
+ struct udev_list_node *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list)
+{
+ udev_list_node_insert_between(new, list->prev, list);
+}
+
+void udev_list_node_remove(struct udev_list_node *entry)
+{
+ struct udev_list_node *prev = entry->prev;
+ struct udev_list_node *next = entry->next;
+
+ next->prev = prev;
+ prev->next = next;
+
+ entry->prev = NULL;
+ entry->next = NULL;
+}
+
+/* return list entry which embeds this node */
+static inline struct udev_list_entry *list_node_to_entry(struct udev_list_node *node)
+{
+ return container_of(node, struct udev_list_entry, node);
+}
+
+void udev_list_init(struct udev *udev, struct udev_list *list, bool unique)
+{
+ memset(list, 0x00, sizeof(struct udev_list));
+ list->udev = udev;
+ list->unique = unique;
+ udev_list_node_init(&list->node);
+}
+
+/* insert entry into a list as the last element */
+void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list)
+{
+ /* inserting before the list head make the node the last node in the list */
+ udev_list_node_insert_between(&new->node, list->node.prev, &list->node);
+ new->list = list;
+}
+
+/* insert entry into a list, before a given existing entry */
+void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry)
+{
+ udev_list_node_insert_between(&new->node, entry->node.prev, &entry->node);
+ new->list = entry->list;
+}
+
+/* binary search in sorted array */
+static int list_search(struct udev_list *list, const char *name)
+{
+ unsigned int first, last;
+
+ first = 0;
+ last = list->entries_cur;
+ while (first < last) {
+ unsigned int i;
+ int cmp;
+
+ i = (first + last)/2;
+ cmp = strcmp(name, list->entries[i]->name);
+ if (cmp < 0)
+ last = i;
+ else if (cmp > 0)
+ first = i+1;
+ else
+ return i;
+ }
+
+ /* not found, return negative insertion-index+1 */
+ return -(first+1);
+}
+
+struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value)
+{
+ struct udev_list_entry *entry;
+ int i = 0;
+
+ if (list->unique) {
+ /* lookup existing name or insertion-index */
+ i = list_search(list, name);
+ if (i >= 0) {
+ entry = list->entries[i];
+
+ free(entry->value);
+ if (value == NULL) {
+ entry->value = NULL;
+ return entry;
+ }
+ entry->value = strdup(value);
+ if (entry->value == NULL)
+ return NULL;
+ return entry;
+ }
+ }
+
+ /* add new name */
+ entry = calloc(1, sizeof(struct udev_list_entry));
+ if (entry == NULL)
+ return NULL;
+ entry->name = strdup(name);
+ if (entry->name == NULL) {
+ free(entry);
+ return NULL;
+ }
+ if (value != NULL) {
+ entry->value = strdup(value);
+ if (entry->value == NULL) {
+ free(entry->name);
+ free(entry);
+ return NULL;
+ }
+ }
+
+ if (list->unique) {
+ /* allocate or enlarge sorted array if needed */
+ if (list->entries_cur >= list->entries_max) {
+ struct udev_list_entry **entries;
+ unsigned int add;
+
+ add = list->entries_max;
+ if (add < 1)
+ add = 64;
+ entries = realloc(list->entries, (list->entries_max + add) * sizeof(struct udev_list_entry *));
+ if (entries == NULL) {
+ free(entry->name);
+ free(entry->value);
+ free(entry);
+ return NULL;
+ }
+ list->entries = entries;
+ list->entries_max += add;
+ }
+
+ /* the negative i returned the insertion index */
+ i = (-i)-1;
+
+ /* insert into sorted list */
+ if ((unsigned int)i < list->entries_cur)
+ udev_list_entry_insert_before(entry, list->entries[i]);
+ else
+ udev_list_entry_append(entry, list);
+
+ /* insert into sorted array */
+ memmove(&list->entries[i+1], &list->entries[i],
+ (list->entries_cur - i) * sizeof(struct udev_list_entry *));
+ list->entries[i] = entry;
+ list->entries_cur++;
+ } else {
+ udev_list_entry_append(entry, list);
+ }
+
+ return entry;
+}
+
+void udev_list_entry_delete(struct udev_list_entry *entry)
+{
+ if (entry->list->entries != NULL) {
+ int i;
+ struct udev_list *list = entry->list;
+
+ /* remove entry from sorted array */
+ i = list_search(list, entry->name);
+ if (i >= 0) {
+ memmove(&list->entries[i], &list->entries[i+1],
+ ((list->entries_cur-1) - i) * sizeof(struct udev_list_entry *));
+ list->entries_cur--;
+ }
+ }
+
+ udev_list_node_remove(&entry->node);
+ free(entry->name);
+ free(entry->value);
+ free(entry);
+}
+
+void udev_list_cleanup(struct udev_list *list)
+{
+ struct udev_list_entry *entry_loop;
+ struct udev_list_entry *entry_tmp;
+
+ free(list->entries);
+ list->entries = NULL;
+ list->entries_cur = 0;
+ list->entries_max = 0;
+ udev_list_entry_foreach_safe(entry_loop, entry_tmp, udev_list_get_entry(list))
+ udev_list_entry_delete(entry_loop);
+}
+
+struct udev_list_entry *udev_list_get_entry(struct udev_list *list)
+{
+ if (udev_list_node_is_empty(&list->node))
+ return NULL;
+ return list_node_to_entry(list->node.next);
+}
+
+/**
+ * udev_list_entry_get_next:
+ * @list_entry: current entry
+ *
+ * Get the next entry from the list.
+ *
+ * Returns: udev_list_entry, #NULL if no more entries are available.
+ */
+_public_ struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry)
+{
+ struct udev_list_node *next;
+
+ if (list_entry == NULL)
+ return NULL;
+ next = list_entry->node.next;
+ /* empty list or no more entries */
+ if (next == &list_entry->list->node)
+ return NULL;
+ return list_node_to_entry(next);
+}
+
+/**
+ * udev_list_entry_get_by_name:
+ * @list_entry: current entry
+ * @name: name string to match
+ *
+ * Lookup an entry in the list with a certain name.
+ *
+ * Returns: udev_list_entry, #NULL if no matching entry is found.
+ */
+_public_ struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name)
+{
+ int i;
+
+ if (list_entry == NULL)
+ return NULL;
+
+ if (!list_entry->list->unique)
+ return NULL;
+
+ i = list_search(list_entry->list, name);
+ if (i < 0)
+ return NULL;
+ return list_entry->list->entries[i];
+}
+
+/**
+ * udev_list_entry_get_name:
+ * @list_entry: current entry
+ *
+ * Get the name of a list entry.
+ *
+ * Returns: the name string of this entry.
+ */
+_public_ const char *udev_list_entry_get_name(struct udev_list_entry *list_entry)
+{
+ if (list_entry == NULL)
+ return NULL;
+ return list_entry->name;
+}
+
+/**
+ * udev_list_entry_get_value:
+ * @list_entry: current entry
+ *
+ * Get the value of list entry.
+ *
+ * Returns: the value string of this entry.
+ */
+_public_ const char *udev_list_entry_get_value(struct udev_list_entry *list_entry)
+{
+ if (list_entry == NULL)
+ return NULL;
+ return list_entry->value;
+}
+
+int udev_list_entry_get_num(struct udev_list_entry *list_entry)
+{
+ if (list_entry == NULL)
+ return -EINVAL;
+ return list_entry->num;
+}
+
+void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num)
+{
+ if (list_entry == NULL)
+ return;
+ list_entry->num = num;
+}
diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c
new file mode 100644
index 0000000000..b02ea8808c
--- /dev/null
+++ b/src/libudev/libudev-monitor.c
@@ -0,0 +1,782 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/filter.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+#include "socket-util.h"
+
+/**
+ * SECTION:libudev-monitor
+ * @short_description: device event source
+ *
+ * Connects to a device event source.
+ */
+
+/**
+ * udev_monitor:
+ *
+ * Opaque object handling an event source.
+ */
+struct udev_monitor {
+ struct udev *udev;
+ int refcount;
+ int sock;
+ union sockaddr_union snl;
+ union sockaddr_union snl_trusted_sender;
+ union sockaddr_union snl_destination;
+ socklen_t addrlen;
+ struct udev_list filter_subsystem_list;
+ struct udev_list filter_tag_list;
+ bool bound;
+};
+
+enum udev_monitor_netlink_group {
+ UDEV_MONITOR_NONE,
+ UDEV_MONITOR_KERNEL,
+ UDEV_MONITOR_UDEV,
+};
+
+#define UDEV_MONITOR_MAGIC 0xfeedcafe
+struct udev_monitor_netlink_header {
+ /* "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;
+ /* 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 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_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)
+{
+ struct udev_monitor *udev_monitor;
+
+ udev_monitor = calloc(1, sizeof(struct udev_monitor));
+ if (udev_monitor == NULL)
+ return NULL;
+ udev_monitor->refcount = 1;
+ udev_monitor->udev = udev;
+ udev_list_init(udev, &udev_monitor->filter_subsystem_list, false);
+ udev_list_init(udev, &udev_monitor->filter_tag_list, true);
+ return udev_monitor;
+}
+
+struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd)
+{
+ struct udev_monitor *udev_monitor;
+ unsigned int group;
+
+ if (udev == NULL)
+ return NULL;
+
+ if (name == NULL)
+ group = UDEV_MONITOR_NONE;
+ else if (strcmp(name, "udev") == 0)
+ group = UDEV_MONITOR_UDEV;
+ else if (strcmp(name, "kernel") == 0)
+ group = UDEV_MONITOR_KERNEL;
+ else
+ return NULL;
+
+ udev_monitor = udev_monitor_new(udev);
+ if (udev_monitor == NULL)
+ return NULL;
+
+ if (fd < 0) {
+ udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT);
+ if (udev_monitor->sock == -1) {
+ udev_err(udev, "error getting socket: %m\n");
+ free(udev_monitor);
+ return NULL;
+ }
+ } else {
+ udev_monitor->bound = true;
+ udev_monitor->sock = fd;
+ }
+
+ udev_monitor->snl.nl.nl_family = AF_NETLINK;
+ udev_monitor->snl.nl.nl_groups = group;
+
+ /* default destination for sending */
+ udev_monitor->snl_destination.nl.nl_family = AF_NETLINK;
+ udev_monitor->snl_destination.nl.nl_groups = UDEV_MONITOR_UDEV;
+
+ return udev_monitor;
+}
+
+/**
+ * udev_monitor_new_from_netlink:
+ * @udev: udev library context
+ * @name: name of event source
+ *
+ * Create new udev monitor and connect to a specified event
+ * source. Valid sources identifiers are "udev" and "kernel".
+ *
+ * Applications should usually not connect directly to the
+ * "kernel" events, because the devices might not be useable
+ * at that time, before udev has configured them, and created
+ * device nodes. Accessing devices at the same time as udev,
+ * might result in unpredictable behavior. The "udev" events
+ * are sent out after udev has finished its event processing,
+ * all rules have been processed, and needed device nodes are
+ * created.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev monitor.
+ *
+ * Returns: a new udev monitor, or #NULL, in case of an error
+ **/
+_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
+{
+ return udev_monitor_new_from_netlink_fd(udev, name, -1);
+}
+
+static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i,
+ unsigned short code, unsigned int data)
+{
+ struct sock_filter *ins = &inss[*i];
+
+ ins->code = code;
+ ins->k = data;
+ (*i)++;
+}
+
+static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i,
+ unsigned short code, unsigned int data,
+ unsigned short jt, unsigned short jf)
+{
+ struct sock_filter *ins = &inss[*i];
+
+ ins->code = code;
+ ins->jt = jt;
+ ins->jf = jf;
+ ins->k = data;
+ (*i)++;
+}
+
+/**
+ * udev_monitor_filter_update:
+ * @udev_monitor: monitor
+ *
+ * Update the installed socket filter. This is only needed,
+ * if the filter was removed or changed.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor)
+{
+ 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 &&
+ udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL)
+ return 0;
+
+ memset(ins, 0x00, sizeof(ins));
+ i = 0;
+
+ /* load magic in A */
+ bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic));
+ /* jump if magic matches */
+ bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0);
+ /* wrong magic, pass packet */
+ bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
+
+ 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);
+ }
+
+ /* nothing matched, drop packet */
+ bpf_stmt(ins, &i, BPF_RET|BPF_K, 0);
+ }
+
+ /* 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 >= ELEMENTSOF(ins))
+ return -1;
+ }
+
+ /* 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 */
+ memset(&filter, 0x00, sizeof(filter));
+ filter.len = i;
+ filter.filter = ins;
+ err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
+ return err;
+}
+
+int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender)
+{
+ udev_monitor->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid;
+ return 0;
+}
+/**
+ * udev_monitor_enable_receiving:
+ * @udev_monitor: the monitor which should receive events
+ *
+ * Binds the @udev_monitor socket to the event source.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
+{
+ int err = 0;
+ const int on = 1;
+
+ if (udev_monitor->snl.nl.nl_family == 0)
+ return -EINVAL;
+
+ udev_monitor_filter_update(udev_monitor);
+
+ if (!udev_monitor->bound) {
+ err = bind(udev_monitor->sock,
+ &udev_monitor->snl.sa, sizeof(struct sockaddr_nl));
+ if (err == 0)
+ udev_monitor->bound = true;
+ }
+
+ if (err >= 0) {
+ union sockaddr_union snl;
+ socklen_t addrlen;
+
+ /*
+ * get the address the kernel has assigned us
+ * it is usually, but not necessarily the pid
+ */
+ addrlen = sizeof(struct sockaddr_nl);
+ err = getsockname(udev_monitor->sock, &snl.sa, &addrlen);
+ if (err == 0)
+ udev_monitor->snl.nl.nl_pid = snl.nl.nl_pid;
+ } else {
+ udev_err(udev_monitor->udev, "bind failed: %m\n");
+ return err;
+ }
+
+ /* enable receiving of sender credentials */
+ setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+ return 0;
+}
+
+/**
+ * udev_monitor_set_receive_buffer_size:
+ * @udev_monitor: the monitor which should receive events
+ * @size: the size in bytes
+ *
+ * Set the size of the kernel socket buffer. This call needs the
+ * appropriate privileges to succeed.
+ *
+ * Returns: 0 on success, otherwise -1 on error.
+ */
+_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size)
+{
+ if (udev_monitor == NULL)
+ return -1;
+ return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size));
+}
+
+int udev_monitor_disconnect(struct udev_monitor *udev_monitor)
+{
+ int err;
+
+ err = close(udev_monitor->sock);
+ udev_monitor->sock = -1;
+ return err;
+}
+
+/**
+ * udev_monitor_ref:
+ * @udev_monitor: udev monitor
+ *
+ * Take a reference of a udev monitor.
+ *
+ * Returns: the passed udev monitor
+ **/
+_public_ struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return NULL;
+ udev_monitor->refcount++;
+ return udev_monitor;
+}
+
+/**
+ * udev_monitor_unref:
+ * @udev_monitor: udev monitor
+ *
+ * Drop a reference of a udev monitor. If the refcount reaches zero,
+ * the bound socket will be closed, and the resources of the monitor
+ * will be released.
+ *
+ * Returns: the passed udev monitor if it has still an active reference, or #NULL otherwise.
+ **/
+_public_ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return NULL;
+ udev_monitor->refcount--;
+ if (udev_monitor->refcount > 0)
+ return udev_monitor;
+ if (udev_monitor->sock >= 0)
+ close(udev_monitor->sock);
+ udev_list_cleanup(&udev_monitor->filter_subsystem_list);
+ udev_list_cleanup(&udev_monitor->filter_tag_list);
+ free(udev_monitor);
+ return NULL;
+}
+
+/**
+ * udev_monitor_get_udev:
+ * @udev_monitor: udev monitor
+ *
+ * Retrieve the udev library context the monitor was created with.
+ *
+ * Returns: the udev library context
+ **/
+_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return NULL;
+ return udev_monitor->udev;
+}
+
+/**
+ * udev_monitor_get_fd:
+ * @udev_monitor: udev monitor
+ *
+ * Retrieve the socket file descriptor associated with the monitor.
+ *
+ * Returns: the socket file descriptor
+ **/
+_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return -1;
+ return udev_monitor->sock;
+}
+
+static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device *udev_device)
+{
+ struct udev_list_entry *list_entry;
+
+ if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL)
+ 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);
+ const char *devtype;
+ const char *ddevtype;
+
+ if (strcmp(dsubsys, subsys) != 0)
+ continue;
+
+ devtype = udev_list_entry_get_value(list_entry);
+ if (devtype == NULL)
+ 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;
+}
+
+/**
+ * udev_monitor_receive_device:
+ * @udev_monitor: udev monitor
+ *
+ * Receive data from the udev monitor socket, allocate a new udev
+ * device, fill in the received data, and return the device.
+ *
+ * Only socket connections with uid=0 are accepted.
+ *
+ * The monitor socket is by default set to NONBLOCK. A variant of poll() on
+ * the file descriptor returned by udev_monitor_get_fd() should to be used to
+ * wake up when new devices arrive, or alternatively the file descriptor
+ * switched into blocking mode.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, in case of an error
+ **/
+_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
+{
+ struct udev_device *udev_device;
+ struct msghdr smsg;
+ struct iovec iov;
+ char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+ struct cmsghdr *cmsg;
+ union sockaddr_union snl;
+ struct ucred *cred;
+ char buf[8192];
+ ssize_t buflen;
+ ssize_t bufpos;
+ struct udev_monitor_netlink_header *nlh;
+
+retry:
+ if (udev_monitor == NULL)
+ return NULL;
+ iov.iov_base = &buf;
+ iov.iov_len = sizeof(buf);
+ memset (&smsg, 0x00, sizeof(struct msghdr));
+ smsg.msg_iov = &iov;
+ smsg.msg_iovlen = 1;
+ smsg.msg_control = cred_msg;
+ smsg.msg_controllen = sizeof(cred_msg);
+
+ if (udev_monitor->snl.nl.nl_family != 0) {
+ smsg.msg_name = &snl;
+ smsg.msg_namelen = sizeof(snl);
+ }
+
+ buflen = recvmsg(udev_monitor->sock, &smsg, 0);
+ if (buflen < 0) {
+ if (errno != EINTR)
+ udev_dbg(udev_monitor->udev, "unable to receive message\n");
+ return NULL;
+ }
+
+ if (buflen < 32 || (size_t)buflen >= sizeof(buf)) {
+ udev_dbg(udev_monitor->udev, "invalid message length\n");
+ return NULL;
+ }
+
+ if (udev_monitor->snl.nl.nl_family != 0) {
+ if (snl.nl.nl_groups == 0) {
+ /* unicast message, check if we trust the sender */
+ if (udev_monitor->snl_trusted_sender.nl.nl_pid == 0 ||
+ snl.nl.nl_pid != udev_monitor->snl_trusted_sender.nl.nl_pid) {
+ udev_dbg(udev_monitor->udev, "unicast netlink message ignored\n");
+ return NULL;
+ }
+ } else if (snl.nl.nl_groups == UDEV_MONITOR_KERNEL) {
+ if (snl.nl.nl_pid > 0) {
+ udev_dbg(udev_monitor->udev, "multicast kernel netlink message from pid %d ignored\n",
+ snl.nl.nl_pid);
+ return NULL;
+ }
+ }
+ }
+
+ cmsg = CMSG_FIRSTHDR(&smsg);
+ if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+ udev_dbg(udev_monitor->udev, "no sender credentials received, message ignored\n");
+ return NULL;
+ }
+
+ cred = (struct ucred *)CMSG_DATA(cmsg);
+ if (cred->uid != 0) {
+ udev_dbg(udev_monitor->udev, "sender uid=%d, message ignored\n", cred->uid);
+ return NULL;
+ }
+
+ 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)) {
+ udev_err(udev_monitor->udev, "unrecognized message signature (%x != %x)\n",
+ nlh->magic, htonl(UDEV_MONITOR_MAGIC));
+ return NULL;
+ }
+ if (nlh->properties_off+32 > (size_t)buflen)
+ return NULL;
+ bufpos = nlh->properties_off;
+ } else {
+ /* kernel message with header */
+ bufpos = strlen(buf) + 1;
+ if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) {
+ udev_dbg(udev_monitor->udev, "invalid message length\n");
+ return NULL;
+ }
+
+ /* check message header */
+ if (strstr(buf, "@/") == NULL) {
+ udev_dbg(udev_monitor->udev, "unrecognized message header\n");
+ return NULL;
+ }
+ }
+
+ udev_device = udev_device_new(udev_monitor->udev);
+ if (udev_device == NULL)
+ return NULL;
+ udev_device_set_info_loaded(udev_device);
+
+ while (bufpos < buflen) {
+ char *key;
+ size_t keylen;
+
+ key = &buf[bufpos];
+ keylen = strlen(key);
+ if (keylen == 0)
+ break;
+ bufpos += keylen + 1;
+ udev_device_add_property_from_string_parse(udev_device, key);
+ }
+
+ if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) {
+ udev_dbg(udev_monitor->udev, "missing values, invalid device\n");
+ udev_device_unref(udev_device);
+ return NULL;
+ }
+
+ /* skip device, if it does not pass the current filter */
+ if (!passes_filter(udev_monitor, udev_device)) {
+ struct pollfd pfd[1];
+ int rc;
+
+ udev_device_unref(udev_device);
+
+ /* if something is queued, get next device */
+ pfd[0].fd = udev_monitor->sock;
+ pfd[0].events = POLLIN;
+ rc = poll(pfd, 1, 0);
+ if (rc > 0)
+ goto retry;
+ return NULL;
+ }
+
+ return udev_device;
+}
+
+int udev_monitor_send_device(struct udev_monitor *udev_monitor,
+ struct udev_monitor *destination, struct udev_device *udev_device)
+{
+ const char *buf;
+ ssize_t blen;
+ ssize_t count;
+ 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;
+
+ if (udev_monitor->snl.nl.nl_family == 0)
+ return -EINVAL;
+
+ blen = udev_device_get_properties_monitor_buf(udev_device, &buf);
+ if (blen < 32)
+ return -EINVAL;
+
+ /* add versioned header */
+ memset(&nlh, 0x00, sizeof(struct udev_monitor_netlink_header));
+ 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_hash = htonl(util_string_hash32(val));
+ val = udev_device_get_devtype(udev_device);
+ if (val != NULL)
+ 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;
+ iov[1].iov_base = (char *)buf;
+ iov[1].iov_len = blen;
+
+ memset(&smsg, 0x00, sizeof(struct msghdr));
+ smsg.msg_iov = iov;
+ smsg.msg_iovlen = 2;
+ /*
+ * Use custom address for target, or the default one.
+ *
+ * If we send to a multicast group, we will get
+ * ECONNREFUSED, which is expected.
+ */
+ if (destination != NULL)
+ smsg.msg_name = &destination->snl;
+ else
+ smsg.msg_name = &udev_monitor->snl_destination;
+ smsg.msg_namelen = sizeof(struct sockaddr_nl);
+ count = sendmsg(udev_monitor->sock, &smsg, 0);
+ udev_dbg(udev_monitor->udev, "passed %zi bytes to netlink monitor %p\n", count, udev_monitor);
+ return count;
+}
+
+/**
+ * udev_monitor_filter_add_match_subsystem_devtype:
+ * @udev_monitor: the monitor
+ * @subsystem: the subsystem value to match the incoming devices against
+ * @devtype: the devtype value to match the incoming devices against
+ *
+ * This filter 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.
+ */
+_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
+{
+ if (udev_monitor == NULL)
+ return -EINVAL;
+ if (subsystem == NULL)
+ return -EINVAL;
+ if (udev_list_entry_add(&udev_monitor->filter_subsystem_list, subsystem, devtype) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_monitor_filter_add_match_tag:
+ * @udev_monitor: the monitor
+ * @tag: the name of a tag
+ *
+ * This filter 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.
+ */
+_public_ 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->filter_tag_list, tag, NULL) == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+/**
+ * udev_monitor_filter_remove:
+ * @udev_monitor: monitor
+ *
+ * Remove all filters from monitor.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor)
+{
+ static struct sock_fprog filter = { 0, NULL };
+
+ udev_list_cleanup(&udev_monitor->filter_subsystem_list);
+ return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
+}
diff --git a/src/libudev/libudev-private.h b/src/libudev/libudev-private.h
new file mode 100644
index 0000000000..ff1cc8cefd
--- /dev/null
+++ b/src/libudev/libudev-private.h
@@ -0,0 +1,180 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifndef _LIBUDEV_PRIVATE_H_
+#define _LIBUDEV_PRIVATE_H_
+
+#include <syslog.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "libudev.h"
+#include "macro.h"
+#include "util.h"
+#include "mkdir.h"
+
+#define READ_END 0
+#define WRITE_END 1
+
+/* avoid (sometimes expensive) calculations of parameters for debug output */
+#define udev_log_cond(udev, prio, arg...) \
+ do { \
+ if (udev_get_log_priority(udev) >= prio) \
+ udev_log(udev, prio, __FILE__, __LINE__, __FUNCTION__, ## arg); \
+ } while (0)
+
+#define udev_dbg(udev, arg...) udev_log_cond(udev, LOG_DEBUG, ## arg)
+#define udev_info(udev, arg...) udev_log_cond(udev, LOG_INFO, ## arg)
+#define udev_err(udev, arg...) udev_log_cond(udev, LOG_ERR, ## arg)
+
+/* libudev.c */
+void udev_log(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, ...)
+ __attribute__((format(printf, 6, 7)));
+int udev_get_rules_path(struct udev *udev, char **path[], usec_t *ts_usec[]);
+struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value);
+struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev);
+
+/* libudev-device.c */
+struct udev_device *udev_device_new(struct udev *udev);
+mode_t udev_device_get_devnode_mode(struct udev_device *udev_device);
+int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsystem);
+int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath);
+int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode);
+int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink);
+void udev_device_cleanup_devlinks_list(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value);
+void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property);
+int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device);
+char **udev_device_get_properties_envp(struct udev_device *udev_device);
+ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf);
+int udev_device_read_db(struct udev_device *udev_device, const char *dbfile);
+int udev_device_read_uevent_file(struct udev_device *udev_device);
+int udev_device_set_action(struct udev_device *udev_device, const char *action);
+const char *udev_device_get_devpath_old(struct udev_device *udev_device);
+const char *udev_device_get_id_filename(struct udev_device *udev_device);
+void udev_device_set_is_initialized(struct udev_device *udev_device);
+int udev_device_add_tag(struct udev_device *udev_device, const char *tag);
+void udev_device_cleanup_tags_list(struct udev_device *udev_device);
+usec_t udev_device_get_usec_initialized(struct udev_device *udev_device);
+void udev_device_set_usec_initialized(struct udev_device *udev_device, usec_t usec_initialized);
+int udev_device_get_devlink_priority(struct udev_device *udev_device);
+int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio);
+int udev_device_get_watch_handle(struct udev_device *udev_device);
+int udev_device_set_watch_handle(struct udev_device *udev_device, int handle);
+int udev_device_get_ifindex(struct udev_device *udev_device);
+void udev_device_set_info_loaded(struct udev_device *device);
+bool udev_device_get_db_persist(struct udev_device *udev_device);
+void udev_device_set_db_persist(struct udev_device *udev_device);
+
+/* libudev-device-private.c */
+int udev_device_update_db(struct udev_device *udev_device);
+int udev_device_delete_db(struct udev_device *udev_device);
+int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add);
+
+/* libudev-monitor.c - netlink/unix socket communication */
+int udev_monitor_disconnect(struct udev_monitor *udev_monitor);
+int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender);
+int udev_monitor_send_device(struct udev_monitor *udev_monitor,
+ struct udev_monitor *destination, struct udev_device *udev_device);
+struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd);
+
+/* libudev-list.c */
+struct udev_list_node {
+ struct udev_list_node *next, *prev;
+};
+struct udev_list {
+ struct udev *udev;
+ struct udev_list_node node;
+ struct udev_list_entry **entries;
+ unsigned int entries_cur;
+ unsigned int entries_max;
+ bool unique;
+};
+#define UDEV_LIST(list) struct udev_list_node list = { &(list), &(list) }
+void udev_list_node_init(struct udev_list_node *list);
+int udev_list_node_is_empty(struct udev_list_node *list);
+void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list);
+void udev_list_node_remove(struct udev_list_node *entry);
+#define udev_list_node_foreach(node, list) \
+ for (node = (list)->next; \
+ node != list; \
+ node = (node)->next)
+#define udev_list_node_foreach_safe(node, tmp, list) \
+ for (node = (list)->next, tmp = (node)->next; \
+ node != list; \
+ node = tmp, tmp = (tmp)->next)
+void udev_list_init(struct udev *udev, struct udev_list *list, bool unique);
+void udev_list_cleanup(struct udev_list *list);
+struct udev_list_entry *udev_list_get_entry(struct udev_list *list);
+struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value);
+void udev_list_entry_delete(struct udev_list_entry *entry);
+void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry);
+void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list);
+int udev_list_entry_get_num(struct udev_list_entry *list_entry);
+void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num);
+#define udev_list_entry_foreach_safe(entry, tmp, first) \
+ for (entry = first, tmp = udev_list_entry_get_next(entry); \
+ entry != NULL; \
+ entry = tmp, tmp = udev_list_entry_get_next(tmp))
+
+/* libudev-queue.c */
+unsigned long long int udev_get_kernel_seqnum(struct udev *udev);
+int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum);
+ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size);
+ssize_t udev_queue_skip_devpath(FILE *queue_file);
+
+/* libudev-queue-private.c */
+struct udev_queue_export *udev_queue_export_new(struct udev *udev);
+struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export);
+void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export);
+int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device);
+int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device);
+
+/* libudev-hwdb.c */
+bool udev_hwdb_validate(struct udev_hwdb *hwdb);
+
+/* libudev-util.c */
+#define UTIL_PATH_SIZE 1024
+#define UTIL_NAME_SIZE 512
+#define UTIL_LINE_SIZE 16384
+#define UDEV_ALLOWED_CHARS_INPUT "/ $%?,"
+ssize_t util_get_sys_core_link_value(struct udev *udev, const char *slink, const char *syspath, char *value, size_t size);
+int util_resolve_sys_link(struct udev *udev, char *syspath, size_t size);
+int util_log_priority(const char *priority);
+size_t util_path_encode(const char *src, char *dest, size_t size);
+void util_remove_trailing_chars(char *path, char c);
+size_t util_strpcpy(char **dest, size_t size, const char *src);
+size_t util_strpcpyl(char **dest, size_t size, const char *src, ...) __attribute__((sentinel));
+size_t util_strscpy(char *dest, size_t size, const char *src);
+size_t util_strscpyl(char *dest, size_t size, const char *src, ...) __attribute__((sentinel));
+int util_replace_whitespace(const char *str, char *to, size_t len);
+int util_replace_chars(char *str, const char *white);
+unsigned int util_string_hash32(const char *key);
+uint64_t util_string_bloom64(const char *str);
+
+/* libudev-util-private.c */
+int util_delete_path(struct udev *udev, const char *path);
+uid_t util_lookup_user(struct udev *udev, const char *user);
+gid_t util_lookup_group(struct udev *udev, const char *group);
+int util_resolve_subsys_kernel(struct udev *udev, const char *string, char *result, size_t maxsize, int read_value);
+ssize_t print_kmsg(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+#endif
diff --git a/src/libudev/libudev-queue-private.c b/src/libudev/libudev-queue-private.c
new file mode 100644
index 0000000000..80d7ceef2b
--- /dev/null
+++ b/src/libudev/libudev-queue-private.c
@@ -0,0 +1,406 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+ Copyright 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/*
+ * DISCLAIMER - The file format mentioned here is private to udev/libudev,
+ * and may be changed without notice.
+ *
+ * The udev event queue is exported as a binary log file.
+ * Each log record consists of a sequence number followed by the device path.
+ *
+ * When a new event is queued, its details are appended to the log.
+ * When the event finishes, a second record is appended to the log
+ * with the same sequence number but a devpath len of 0.
+ *
+ * Example:
+ * { 0x0000000000000001 }
+ * { 0x0000000000000001, 0x0019, "/devices/virtual/mem/null" },
+ * { 0x0000000000000002, 0x001b, "/devices/virtual/mem/random" },
+ * { 0x0000000000000001, 0x0000 },
+ * { 0x0000000000000003, 0x0019, "/devices/virtual/mem/zero" },
+ *
+ * Events 2 and 3 are still queued, but event 1 has finished.
+ *
+ * The queue does not grow indefinitely. It is periodically re-created
+ * to remove finished events. Atomic rename() makes this transparent to readers.
+ *
+ * The queue file starts with a single sequence number which specifies the
+ * minimum sequence number in the log that follows. Any events prior to this
+ * sequence number have already finished.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+static int rebuild_queue_file(struct udev_queue_export *udev_queue_export);
+
+struct udev_queue_export {
+ struct udev *udev;
+ int queued_count; /* number of unfinished events exported in queue file */
+ FILE *queue_file;
+ unsigned long long int seqnum_max; /* earliest sequence number in queue file */
+ unsigned long long int seqnum_min; /* latest sequence number in queue file */
+ int waste_bytes; /* queue file bytes wasted on finished events */
+};
+
+struct udev_queue_export *udev_queue_export_new(struct udev *udev)
+{
+ struct udev_queue_export *udev_queue_export;
+ unsigned long long int initial_seqnum;
+
+ if (udev == NULL)
+ return NULL;
+
+ udev_queue_export = calloc(1, sizeof(struct udev_queue_export));
+ if (udev_queue_export == NULL)
+ return NULL;
+ udev_queue_export->udev = udev;
+
+ initial_seqnum = udev_get_kernel_seqnum(udev);
+ udev_queue_export->seqnum_min = initial_seqnum;
+ udev_queue_export->seqnum_max = initial_seqnum;
+
+ udev_queue_export_cleanup(udev_queue_export);
+ if (rebuild_queue_file(udev_queue_export) != 0) {
+ free(udev_queue_export);
+ return NULL;
+ }
+
+ return udev_queue_export;
+}
+
+struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export)
+{
+ if (udev_queue_export == NULL)
+ return NULL;
+ if (udev_queue_export->queue_file != NULL)
+ fclose(udev_queue_export->queue_file);
+ free(udev_queue_export);
+ return NULL;
+}
+
+void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export)
+{
+ if (udev_queue_export == NULL)
+ return;
+ unlink("/run/udev/queue.tmp");
+ unlink("/run/udev/queue.bin");
+}
+
+static int skip_to(FILE *file, long offset)
+{
+ long old_offset;
+
+ /* fseek may drop buffered data, avoid it for small seeks */
+ old_offset = ftell(file);
+ if (offset > old_offset && offset - old_offset <= BUFSIZ) {
+ size_t skip_bytes = offset - old_offset;
+ char *buf = alloca(skip_bytes);
+
+ if (fread(buf, skip_bytes, 1, file) != skip_bytes)
+ return -1;
+ }
+
+ return fseek(file, offset, SEEK_SET);
+}
+
+struct queue_devpaths {
+ unsigned int devpaths_first; /* index of first queued event */
+ unsigned int devpaths_size;
+ long devpaths[]; /* seqnum -> offset of devpath in queue file (or 0) */
+};
+
+/*
+ * Returns a table mapping seqnum to devpath file offset for currently queued events.
+ * devpaths[i] represents the event with seqnum = i + udev_queue_export->seqnum_min.
+ */
+static struct queue_devpaths *build_index(struct udev_queue_export *udev_queue_export)
+{
+ struct queue_devpaths *devpaths;
+ unsigned long long int range;
+ long devpath_offset;
+ ssize_t devpath_len;
+ unsigned long long int seqnum;
+ unsigned long long int n;
+ unsigned int i;
+
+ /* seek to the first event in the file */
+ rewind(udev_queue_export->queue_file);
+ udev_queue_read_seqnum(udev_queue_export->queue_file, &seqnum);
+
+ /* allocate the table */
+ range = udev_queue_export->seqnum_min - udev_queue_export->seqnum_max;
+ if (range - 1 > INT_MAX) {
+ udev_err(udev_queue_export->udev, "queue file overflow\n");
+ return NULL;
+ }
+ devpaths = calloc(1, sizeof(struct queue_devpaths) + (range + 1) * sizeof(long));
+ if (devpaths == NULL)
+ return NULL;
+ devpaths->devpaths_size = range + 1;
+
+ /* read all records and populate the table */
+ for (;;) {
+ if (udev_queue_read_seqnum(udev_queue_export->queue_file, &seqnum) < 0)
+ break;
+ n = seqnum - udev_queue_export->seqnum_max;
+ if (n >= devpaths->devpaths_size)
+ goto read_error;
+
+ devpath_offset = ftell(udev_queue_export->queue_file);
+ devpath_len = udev_queue_skip_devpath(udev_queue_export->queue_file);
+ if (devpath_len < 0)
+ goto read_error;
+
+ if (devpath_len > 0)
+ devpaths->devpaths[n] = devpath_offset;
+ else
+ devpaths->devpaths[n] = 0;
+ }
+
+ /* find first queued event */
+ for (i = 0; i < devpaths->devpaths_size; i++) {
+ if (devpaths->devpaths[i] != 0)
+ break;
+ }
+ devpaths->devpaths_first = i;
+
+ return devpaths;
+
+read_error:
+ udev_err(udev_queue_export->udev, "queue file corrupted\n");
+ free(devpaths);
+ return NULL;
+}
+
+static int rebuild_queue_file(struct udev_queue_export *udev_queue_export)
+{
+ unsigned long long int seqnum;
+ struct queue_devpaths *devpaths = NULL;
+ FILE *new_queue_file = NULL;
+ unsigned int i;
+
+ /* read old queue file */
+ if (udev_queue_export->queue_file != NULL) {
+ devpaths = build_index(udev_queue_export);
+ if (devpaths != NULL)
+ udev_queue_export->seqnum_max += devpaths->devpaths_first;
+ }
+ if (devpaths == NULL) {
+ udev_queue_export->queued_count = 0;
+ udev_queue_export->seqnum_max = udev_queue_export->seqnum_min;
+ }
+
+ /* create new queue file */
+ new_queue_file = fopen("/run/udev/queue.tmp", "w+e");
+ if (new_queue_file == NULL)
+ goto error;
+ seqnum = udev_queue_export->seqnum_max;
+ fwrite(&seqnum, 1, sizeof(unsigned long long int), new_queue_file);
+
+ /* copy unfinished events only to the new file */
+ if (devpaths != NULL) {
+ for (i = devpaths->devpaths_first; i < devpaths->devpaths_size; i++) {
+ char devpath[UTIL_PATH_SIZE];
+ int err;
+ unsigned short devpath_len;
+
+ if (devpaths->devpaths[i] != 0)
+ {
+ skip_to(udev_queue_export->queue_file, devpaths->devpaths[i]);
+ err = udev_queue_read_devpath(udev_queue_export->queue_file, devpath, sizeof(devpath));
+ devpath_len = err;
+
+ fwrite(&seqnum, sizeof(unsigned long long int), 1, new_queue_file);
+ fwrite(&devpath_len, sizeof(unsigned short), 1, new_queue_file);
+ fwrite(devpath, 1, devpath_len, new_queue_file);
+ }
+ seqnum++;
+ }
+ free(devpaths);
+ devpaths = NULL;
+ }
+ fflush(new_queue_file);
+ if (ferror(new_queue_file))
+ goto error;
+
+ /* rename the new file on top of the old one */
+ if (rename("/run/udev/queue.tmp", "/run/udev/queue.bin") != 0)
+ goto error;
+
+ if (udev_queue_export->queue_file != NULL)
+ fclose(udev_queue_export->queue_file);
+ udev_queue_export->queue_file = new_queue_file;
+ udev_queue_export->waste_bytes = 0;
+
+ return 0;
+
+error:
+ udev_err(udev_queue_export->udev, "failed to create queue file: %m\n");
+ udev_queue_export_cleanup(udev_queue_export);
+
+ if (udev_queue_export->queue_file != NULL) {
+ fclose(udev_queue_export->queue_file);
+ udev_queue_export->queue_file = NULL;
+ }
+ if (new_queue_file != NULL)
+ fclose(new_queue_file);
+
+ if (devpaths != NULL)
+ free(devpaths);
+ udev_queue_export->queued_count = 0;
+ udev_queue_export->waste_bytes = 0;
+ udev_queue_export->seqnum_max = udev_queue_export->seqnum_min;
+
+ return -1;
+}
+
+static int write_queue_record(struct udev_queue_export *udev_queue_export,
+ unsigned long long int seqnum, const char *devpath, size_t devpath_len)
+{
+ unsigned short len;
+
+ if (udev_queue_export->queue_file == NULL)
+ return -1;
+
+ if (fwrite(&seqnum, sizeof(unsigned long long int), 1, udev_queue_export->queue_file) != 1)
+ goto write_error;
+
+ len = (devpath_len < USHRT_MAX) ? devpath_len : USHRT_MAX;
+ if (fwrite(&len, sizeof(unsigned short), 1, udev_queue_export->queue_file) != 1)
+ goto write_error;
+ if (len > 0) {
+ if (fwrite(devpath, 1, len, udev_queue_export->queue_file) != len)
+ goto write_error;
+ }
+
+ /* *must* flush output; caller may fork */
+ if (fflush(udev_queue_export->queue_file) != 0)
+ goto write_error;
+
+ return 0;
+
+write_error:
+ /* if we failed half way through writing a record to a file,
+ we should not try to write any further records to it. */
+ udev_err(udev_queue_export->udev, "error writing to queue file: %m\n");
+ fclose(udev_queue_export->queue_file);
+ udev_queue_export->queue_file = NULL;
+
+ return -1;
+}
+
+enum device_state {
+ DEVICE_QUEUED,
+ DEVICE_FINISHED,
+};
+
+static inline size_t queue_record_size(size_t devpath_len)
+{
+ return sizeof(unsigned long long int) + sizeof(unsigned short int) + devpath_len;
+}
+
+static int update_queue(struct udev_queue_export *udev_queue_export,
+ struct udev_device *udev_device, enum device_state state)
+{
+ unsigned long long int seqnum = udev_device_get_seqnum(udev_device);
+ const char *devpath = NULL;
+ size_t devpath_len = 0;
+ int bytes;
+ int err;
+
+ /* FINISHED records have a zero length devpath */
+ if (state == DEVICE_QUEUED) {
+ devpath = udev_device_get_devpath(udev_device);
+ devpath_len = strlen(devpath);
+ }
+
+ /* recover from an earlier failed rebuild */
+ if (udev_queue_export->queue_file == NULL) {
+ if (rebuild_queue_file(udev_queue_export) != 0)
+ return -1;
+ }
+
+ /* if we're removing the last event from the queue, that's the best time to rebuild it */
+ if (state != DEVICE_QUEUED && udev_queue_export->queued_count == 1) {
+ /* we don't need to read the old queue file */
+ fclose(udev_queue_export->queue_file);
+ udev_queue_export->queue_file = NULL;
+ rebuild_queue_file(udev_queue_export);
+ return 0;
+ }
+
+ /* try to rebuild the queue files before they grow larger than one page. */
+ bytes = ftell(udev_queue_export->queue_file) + queue_record_size(devpath_len);
+ if ((udev_queue_export->waste_bytes > bytes / 2) && bytes > 4096)
+ rebuild_queue_file(udev_queue_export);
+
+ /* don't record a finished event, if we already dropped the event in a failed rebuild */
+ if (seqnum < udev_queue_export->seqnum_max)
+ return 0;
+
+ /* now write to the queue */
+ if (state == DEVICE_QUEUED) {
+ udev_queue_export->queued_count++;
+ udev_queue_export->seqnum_min = seqnum;
+ } else {
+ udev_queue_export->waste_bytes += queue_record_size(devpath_len) + queue_record_size(0);
+ udev_queue_export->queued_count--;
+ }
+ err = write_queue_record(udev_queue_export, seqnum, devpath, devpath_len);
+
+ /* try to handle ENOSPC */
+ if (err != 0 && udev_queue_export->queued_count == 0) {
+ udev_queue_export_cleanup(udev_queue_export);
+ err = rebuild_queue_file(udev_queue_export);
+ }
+
+ return err;
+}
+
+static int update(struct udev_queue_export *udev_queue_export,
+ struct udev_device *udev_device, enum device_state state)
+{
+ if (update_queue(udev_queue_export, udev_device, state) != 0)
+ return -1;
+
+ return 0;
+}
+
+int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device)
+{
+ return update(udev_queue_export, udev_device, DEVICE_QUEUED);
+}
+
+int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device)
+{
+ return update(udev_queue_export, udev_device, DEVICE_FINISHED);
+}
diff --git a/src/libudev/libudev-queue.c b/src/libudev/libudev-queue.c
new file mode 100644
index 0000000000..08d52ab1f1
--- /dev/null
+++ b/src/libudev/libudev-queue.c
@@ -0,0 +1,480 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+ Copyright 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-queue
+ * @short_description: access to currently active events
+ *
+ * The udev daemon processes events asynchronously. All events which do not have
+ * interdependencies run in parallel. This exports the current state of the
+ * event processing queue, and the current event sequence numbers from the kernel
+ * and the udev daemon.
+ */
+
+/**
+ * udev_queue:
+ *
+ * Opaque object representing the current event queue in the udev daemon.
+ */
+struct udev_queue {
+ struct udev *udev;
+ int refcount;
+ struct udev_list queue_list;
+};
+
+/**
+ * udev_queue_new:
+ * @udev: udev library context
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev queue context.
+ *
+ * Returns: the udev queue context, or #NULL on error.
+ **/
+_public_ struct udev_queue *udev_queue_new(struct udev *udev)
+{
+ struct udev_queue *udev_queue;
+
+ if (udev == NULL)
+ return NULL;
+
+ udev_queue = calloc(1, sizeof(struct udev_queue));
+ if (udev_queue == NULL)
+ return NULL;
+ udev_queue->refcount = 1;
+ udev_queue->udev = udev;
+ udev_list_init(udev, &udev_queue->queue_list, false);
+ return udev_queue;
+}
+
+/**
+ * udev_queue_ref:
+ * @udev_queue: udev queue context
+ *
+ * Take a reference of a udev queue context.
+ *
+ * Returns: the same udev queue context.
+ **/
+_public_ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue)
+{
+ if (udev_queue == NULL)
+ return NULL;
+ udev_queue->refcount++;
+ return udev_queue;
+}
+
+/**
+ * udev_queue_unref:
+ * @udev_queue: udev queue context
+ *
+ * Drop a reference of a udev queue context. If the refcount reaches zero,
+ * the resources of the queue context will be released.
+ *
+ * Returns: the passed queue context if it has still an active reference, or #NULL otherwise.
+ **/
+_public_ struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue)
+{
+ if (udev_queue == NULL)
+ return NULL;
+ udev_queue->refcount--;
+ if (udev_queue->refcount > 0)
+ return udev_queue;
+ udev_list_cleanup(&udev_queue->queue_list);
+ free(udev_queue);
+ return NULL;
+}
+
+/**
+ * udev_queue_get_udev:
+ * @udev_queue: udev queue context
+ *
+ * Retrieve the udev library context the queue context was created with.
+ *
+ * Returns: the udev library context.
+ **/
+_public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue)
+{
+ if (udev_queue == NULL)
+ return NULL;
+ return udev_queue->udev;
+}
+
+unsigned long long int udev_get_kernel_seqnum(struct udev *udev)
+{
+ unsigned long long int seqnum;
+ int fd;
+ char buf[32];
+ ssize_t len;
+
+ fd = open("/sys/kernel/uevent_seqnum", O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return 0;
+ len = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (len <= 2)
+ return 0;
+ buf[len-1] = '\0';
+ seqnum = strtoull(buf, NULL, 10);
+ return seqnum;
+}
+
+/**
+ * udev_queue_get_kernel_seqnum:
+ * @udev_queue: udev queue context
+ *
+ * Get the current kernel event sequence number.
+ *
+ * Returns: the sequence number.
+ **/
+_public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue)
+{
+ unsigned long long int seqnum;
+
+ if (udev_queue == NULL)
+ return -EINVAL;
+
+ seqnum = udev_get_kernel_seqnum(udev_queue->udev);
+ return seqnum;
+}
+
+int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum)
+{
+ if (fread(seqnum, sizeof(unsigned long long int), 1, queue_file) != 1)
+ return -1;
+
+ return 0;
+}
+
+ssize_t udev_queue_skip_devpath(FILE *queue_file)
+{
+ unsigned short int len;
+
+ if (fread(&len, sizeof(unsigned short int), 1, queue_file) == 1) {
+ char *devpath = alloca(len);
+
+ /* use fread to skip, fseek might drop buffered data */
+ if (fread(devpath, 1, len, queue_file) == len)
+ return len;
+ }
+
+ return -1;
+}
+
+ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size)
+{
+ unsigned short int read_bytes = 0;
+ unsigned short int len;
+
+ if (fread(&len, sizeof(unsigned short int), 1, queue_file) != 1)
+ return -1;
+
+ read_bytes = (len < size - 1) ? len : size - 1;
+ if (fread(devpath, 1, read_bytes, queue_file) != read_bytes)
+ return -1;
+ devpath[read_bytes] = '\0';
+
+ /* if devpath was too long, skip unread characters */
+ if (read_bytes != len) {
+ unsigned short int skip_bytes = len - read_bytes;
+ char *buf = alloca(skip_bytes);
+
+ if (fread(buf, 1, skip_bytes, queue_file) != skip_bytes)
+ return -1;
+ }
+
+ return read_bytes;
+}
+
+static FILE *open_queue_file(struct udev_queue *udev_queue, unsigned long long int *seqnum_start)
+{
+ FILE *queue_file;
+
+ queue_file = fopen("/run/udev/queue.bin", "re");
+ if (queue_file == NULL)
+ return NULL;
+
+ if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) {
+ udev_err(udev_queue->udev, "corrupt queue file\n");
+ fclose(queue_file);
+ return NULL;
+ }
+
+ return queue_file;
+}
+
+/**
+ * udev_queue_get_udev_seqnum:
+ * @udev_queue: udev queue context
+ *
+ * Get the last known udev event sequence number.
+ *
+ * Returns: the sequence number.
+ **/
+_public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue)
+{
+ unsigned long long int seqnum_udev;
+ FILE *queue_file;
+
+ queue_file = open_queue_file(udev_queue, &seqnum_udev);
+ if (queue_file == NULL)
+ return 0;
+
+ for (;;) {
+ unsigned long long int seqnum;
+ ssize_t devpath_len;
+
+ if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+ break;
+ devpath_len = udev_queue_skip_devpath(queue_file);
+ if (devpath_len < 0)
+ break;
+ if (devpath_len > 0)
+ seqnum_udev = seqnum;
+ }
+
+ fclose(queue_file);
+ return seqnum_udev;
+}
+
+/**
+ * udev_queue_get_udev_is_active:
+ * @udev_queue: udev queue context
+ *
+ * Check if udev is active on the system.
+ *
+ * Returns: a flag indicating if udev is active.
+ **/
+_public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue)
+{
+ unsigned long long int seqnum_start;
+ FILE *queue_file;
+
+ queue_file = open_queue_file(udev_queue, &seqnum_start);
+ if (queue_file == NULL)
+ return 0;
+
+ fclose(queue_file);
+ return 1;
+}
+
+/**
+ * udev_queue_get_queue_is_empty:
+ * @udev_queue: udev queue context
+ *
+ * Check if udev is currently processing any events.
+ *
+ * Returns: a flag indicating if udev is currently handling events.
+ **/
+_public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue)
+{
+ unsigned long long int seqnum_kernel;
+ unsigned long long int seqnum_udev = 0;
+ int queued = 0;
+ int is_empty = 0;
+ FILE *queue_file;
+
+ if (udev_queue == NULL)
+ return -EINVAL;
+ queue_file = open_queue_file(udev_queue, &seqnum_udev);
+ if (queue_file == NULL)
+ return 1;
+
+ for (;;) {
+ unsigned long long int seqnum;
+ ssize_t devpath_len;
+
+ if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+ break;
+ devpath_len = udev_queue_skip_devpath(queue_file);
+ if (devpath_len < 0)
+ break;
+
+ if (devpath_len > 0) {
+ queued++;
+ seqnum_udev = seqnum;
+ } else {
+ queued--;
+ }
+ }
+
+ if (queued > 0)
+ goto out;
+
+ seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue);
+ if (seqnum_udev < seqnum_kernel)
+ goto out;
+
+ is_empty = 1;
+
+out:
+ fclose(queue_file);
+ return is_empty;
+}
+
+/**
+ * udev_queue_get_seqnum_sequence_is_finished:
+ * @udev_queue: udev queue context
+ * @start: first event sequence number
+ * @end: last event sequence number
+ *
+ * Check if udev is currently processing any events in a given sequence number range.
+ *
+ * Returns: a flag indicating if any of the sequence numbers in the given range is currently active.
+ **/
+_public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+ unsigned long long int start, unsigned long long int end)
+{
+ unsigned long long int seqnum;
+ ssize_t devpath_len;
+ int unfinished;
+ FILE *queue_file;
+
+ if (udev_queue == NULL)
+ return -EINVAL;
+ queue_file = open_queue_file(udev_queue, &seqnum);
+ if (queue_file == NULL)
+ return 1;
+ if (start < seqnum)
+ start = seqnum;
+ if (start > end) {
+ fclose(queue_file);
+ return 1;
+ }
+ if (end - start > INT_MAX - 1) {
+ fclose(queue_file);
+ return -EOVERFLOW;
+ }
+
+ /*
+ * we might start with 0, and handle the initial seqnum
+ * only when we find an entry in the queue file
+ **/
+ unfinished = end - start;
+
+ do {
+ if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+ break;
+ devpath_len = udev_queue_skip_devpath(queue_file);
+ if (devpath_len < 0)
+ break;
+
+ /*
+ * we might start with an empty or re-build queue file, where
+ * the initial seqnum is not recorded as finished
+ */
+ if (start == seqnum && devpath_len > 0)
+ unfinished++;
+
+ if (devpath_len == 0) {
+ if (seqnum >= start && seqnum <= end)
+ unfinished--;
+ }
+ } while (unfinished > 0);
+
+ fclose(queue_file);
+
+ return (unfinished == 0);
+}
+
+/**
+ * udev_queue_get_seqnum_is_finished:
+ * @udev_queue: udev queue context
+ * @seqnum: sequence number
+ *
+ * Check if udev is currently processing a given sequence number.
+ *
+ * Returns: a flag indicating if the given sequence number is currently active.
+ **/
+_public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum)
+{
+ if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum))
+ return 0;
+
+ return 1;
+}
+
+/**
+ * udev_queue_get_queued_list_entry:
+ * @udev_queue: udev queue context
+ *
+ * Get the first entry of the list of queued events.
+ *
+ * Returns: a udev_list_entry.
+ **/
+_public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue)
+{
+ unsigned long long int seqnum;
+ FILE *queue_file;
+
+ if (udev_queue == NULL)
+ return NULL;
+ udev_list_cleanup(&udev_queue->queue_list);
+
+ queue_file = open_queue_file(udev_queue, &seqnum);
+ if (queue_file == NULL)
+ return NULL;
+
+ for (;;) {
+ char syspath[UTIL_PATH_SIZE];
+ char *s;
+ size_t l;
+ ssize_t len;
+ char seqnum_str[32];
+ struct udev_list_entry *list_entry;
+
+ if (udev_queue_read_seqnum(queue_file, &seqnum) < 0)
+ break;
+ snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum);
+
+ s = syspath;
+ l = util_strpcpy(&s, sizeof(syspath), "/sys");
+ len = udev_queue_read_devpath(queue_file, s, l);
+ if (len < 0)
+ break;
+
+ if (len > 0) {
+ udev_list_entry_add(&udev_queue->queue_list, syspath, seqnum_str);
+ } else {
+ udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) {
+ if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) {
+ udev_list_entry_delete(list_entry);
+ break;
+ }
+ }
+ }
+ }
+ fclose(queue_file);
+
+ return udev_list_get_entry(&udev_queue->queue_list);
+}
diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c
new file mode 100644
index 0000000000..cb9ed9c84b
--- /dev/null
+++ b/src/libudev/libudev-util.c
@@ -0,0 +1,725 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+
+/**
+ * SECTION:libudev-util
+ * @short_description: utils
+ *
+ * Utilities useful when dealing with devices and device node names.
+ */
+
+int util_delete_path(struct udev *udev, const char *path)
+{
+ char p[UTIL_PATH_SIZE];
+ char *pos;
+ int err = 0;
+
+ if (path[0] == '/')
+ while(path[1] == '/')
+ path++;
+ util_strscpy(p, sizeof(p), path);
+ pos = strrchr(p, '/');
+ if (pos == p || pos == NULL)
+ return 0;
+
+ for (;;) {
+ *pos = '\0';
+ pos = strrchr(p, '/');
+
+ /* don't remove the last one */
+ if ((pos == p) || (pos == NULL))
+ break;
+
+ err = rmdir(p);
+ if (err < 0) {
+ if (errno == ENOENT)
+ err = 0;
+ break;
+ }
+ }
+ return err;
+}
+
+uid_t util_lookup_user(struct udev *udev, const char *user)
+{
+ char *endptr;
+ struct passwd pwbuf;
+ struct passwd *pw;
+ uid_t uid;
+ size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ char *buf = alloca(buflen);
+
+ if (strcmp(user, "root") == 0)
+ return 0;
+ uid = strtoul(user, &endptr, 10);
+ if (endptr[0] == '\0')
+ return uid;
+
+ errno = getpwnam_r(user, &pwbuf, buf, buflen, &pw);
+ if (pw != NULL)
+ return pw->pw_uid;
+ if (errno == 0 || errno == ENOENT || errno == ESRCH)
+ udev_err(udev, "specified user '%s' unknown\n", user);
+ else
+ udev_err(udev, "error resolving user '%s': %m\n", user);
+ return 0;
+}
+
+gid_t util_lookup_group(struct udev *udev, const char *group)
+{
+ char *endptr;
+ struct group grbuf;
+ struct group *gr;
+ gid_t gid = 0;
+ size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ char *buf = NULL;
+
+ if (strcmp(group, "root") == 0)
+ return 0;
+ gid = strtoul(group, &endptr, 10);
+ if (endptr[0] == '\0')
+ return gid;
+ gid = 0;
+ for (;;) {
+ char *newbuf;
+
+ newbuf = realloc(buf, buflen);
+ if (!newbuf)
+ break;
+ buf = newbuf;
+ errno = getgrnam_r(group, &grbuf, buf, buflen, &gr);
+ if (gr != NULL) {
+ gid = gr->gr_gid;
+ } else if (errno == ERANGE) {
+ buflen *= 2;
+ continue;
+ } else if (errno == 0 || errno == ENOENT || errno == ESRCH) {
+ udev_err(udev, "specified group '%s' unknown\n", group);
+ } else {
+ udev_err(udev, "error resolving group '%s': %m\n", group);
+ }
+ break;
+ }
+ free(buf);
+ return gid;
+}
+
+/* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */
+int util_resolve_subsys_kernel(struct udev *udev, const char *string,
+ char *result, size_t maxsize, int read_value)
+{
+ char temp[UTIL_PATH_SIZE];
+ char *subsys;
+ char *sysname;
+ struct udev_device *dev;
+ char *attr;
+
+ if (string[0] != '[')
+ return -1;
+
+ util_strscpy(temp, sizeof(temp), string);
+
+ subsys = &temp[1];
+
+ sysname = strchr(subsys, '/');
+ if (sysname == NULL)
+ return -1;
+ sysname[0] = '\0';
+ sysname = &sysname[1];
+
+ attr = strchr(sysname, ']');
+ if (attr == NULL)
+ return -1;
+ attr[0] = '\0';
+ attr = &attr[1];
+ if (attr[0] == '/')
+ attr = &attr[1];
+ if (attr[0] == '\0')
+ attr = NULL;
+
+ if (read_value && attr == NULL)
+ return -1;
+
+ dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname);
+ if (dev == NULL)
+ return -1;
+
+ if (read_value) {
+ const char *val;
+
+ val = udev_device_get_sysattr_value(dev, attr);
+ if (val != NULL)
+ util_strscpy(result, maxsize, val);
+ else
+ result[0] = '\0';
+ udev_dbg(udev, "value '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result);
+ } else {
+ size_t l;
+ char *s;
+
+ s = result;
+ l = util_strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL);
+ if (attr != NULL)
+ util_strpcpyl(&s, l, "/", attr, NULL);
+ udev_dbg(udev, "path '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result);
+ }
+ udev_device_unref(dev);
+ return 0;
+}
+ssize_t util_get_sys_core_link_value(struct udev *udev, const char *slink, const char *syspath, char *value, size_t size)
+{
+ char path[UTIL_PATH_SIZE];
+ char target[UTIL_PATH_SIZE];
+ ssize_t len;
+ const char *pos;
+
+ util_strscpyl(path, sizeof(path), syspath, "/", slink, NULL);
+ len = readlink(path, target, sizeof(target));
+ if (len <= 0 || len == (ssize_t)sizeof(target))
+ return -1;
+ target[len] = '\0';
+ pos = strrchr(target, '/');
+ if (pos == NULL)
+ return -1;
+ pos = &pos[1];
+ return util_strscpy(value, size, pos);
+}
+
+int util_resolve_sys_link(struct udev *udev, char *syspath, size_t size)
+{
+ char link_target[UTIL_PATH_SIZE];
+
+ ssize_t len;
+ int i;
+ int back;
+ char *base = NULL;
+
+ len = readlink(syspath, link_target, sizeof(link_target));
+ if (len <= 0 || len == (ssize_t)sizeof(link_target))
+ return -1;
+ link_target[len] = '\0';
+
+ for (back = 0; startswith(&link_target[back * 3], "../"); back++)
+ ;
+ for (i = 0; i <= back; i++) {
+ base = strrchr(syspath, '/');
+ if (base == NULL)
+ return -EINVAL;
+ base[0] = '\0';
+ }
+ if (base == NULL)
+ return -EINVAL;
+ util_strscpyl(base, size - (base - syspath), "/", &link_target[back * 3], NULL);
+ return 0;
+}
+
+int util_log_priority(const char *priority)
+{
+ char *endptr;
+ int prio;
+
+ prio = strtol(priority, &endptr, 10);
+ if (endptr[0] == '\0' || isspace(endptr[0]))
+ return prio;
+ if (startswith(priority, "err"))
+ return LOG_ERR;
+ if (startswith(priority, "info"))
+ return LOG_INFO;
+ if (startswith(priority, "debug"))
+ return LOG_DEBUG;
+ return 0;
+}
+
+size_t util_path_encode(const char *src, char *dest, size_t size)
+{
+ size_t i, j;
+
+ for (i = 0, j = 0; src[i] != '\0'; i++) {
+ if (src[i] == '/') {
+ if (j+4 >= size) {
+ j = 0;
+ break;
+ }
+ memcpy(&dest[j], "\\x2f", 4);
+ j += 4;
+ } else if (src[i] == '\\') {
+ if (j+4 >= size) {
+ j = 0;
+ break;
+ }
+ memcpy(&dest[j], "\\x5c", 4);
+ j += 4;
+ } else {
+ if (j+1 >= size) {
+ j = 0;
+ break;
+ }
+ dest[j] = src[i];
+ j++;
+ }
+ }
+ dest[j] = '\0';
+ return j;
+}
+
+void util_remove_trailing_chars(char *path, char c)
+{
+ size_t len;
+
+ if (path == NULL)
+ return;
+ len = strlen(path);
+ while (len > 0 && path[len-1] == c)
+ path[--len] = '\0';
+}
+
+/*
+ * Concatenates strings. In any case, terminates in _all_ cases with '\0'
+ * and moves the @dest pointer forward to the added '\0'. Returns the
+ * remaining size, and 0 if the string was truncated.
+ */
+size_t util_strpcpy(char **dest, size_t size, const char *src)
+{
+ size_t len;
+
+ len = strlen(src);
+ if (len >= size) {
+ if (size > 1)
+ *dest = mempcpy(*dest, src, size-1);
+ size = 0;
+ *dest[0] = '\0';
+ } else {
+ if (len > 0) {
+ *dest = mempcpy(*dest, src, len);
+ size -= len;
+ }
+ *dest[0] = '\0';
+ }
+ return size;
+}
+
+/* concatenates list of strings, moves dest forward */
+size_t util_strpcpyl(char **dest, size_t size, const char *src, ...)
+{
+ va_list va;
+
+ va_start(va, src);
+ do {
+ size = util_strpcpy(dest, size, src);
+ src = va_arg(va, char *);
+ } while (src != NULL);
+ va_end(va);
+
+ return size;
+}
+
+/* copies string */
+size_t util_strscpy(char *dest, size_t size, const char *src)
+{
+ char *s;
+
+ s = dest;
+ return util_strpcpy(&s, size, src);
+}
+
+/* concatenates list of strings */
+size_t util_strscpyl(char *dest, size_t size, const char *src, ...)
+{
+ va_list va;
+ char *s;
+
+ va_start(va, src);
+ s = dest;
+ do {
+ size = util_strpcpy(&s, size, src);
+ src = va_arg(va, char *);
+ } while (src != NULL);
+ va_end(va);
+
+ return size;
+}
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+ unsigned char c = (unsigned char)str[0];
+
+ if (c < 0x80)
+ return 1;
+ if ((c & 0xe0) == 0xc0)
+ return 2;
+ if ((c & 0xf0) == 0xe0)
+ return 3;
+ if ((c & 0xf8) == 0xf0)
+ return 4;
+ if ((c & 0xfc) == 0xf8)
+ return 5;
+ if ((c & 0xfe) == 0xfc)
+ return 6;
+ return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+ int unichar;
+ int len;
+ int i;
+
+ len = utf8_encoded_expected_len(str);
+ switch (len) {
+ case 1:
+ return (int)str[0];
+ case 2:
+ unichar = str[0] & 0x1f;
+ break;
+ case 3:
+ unichar = (int)str[0] & 0x0f;
+ break;
+ case 4:
+ unichar = (int)str[0] & 0x07;
+ break;
+ case 5:
+ unichar = (int)str[0] & 0x03;
+ break;
+ case 6:
+ unichar = (int)str[0] & 0x01;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i = 1; i < len; i++) {
+ if (((int)str[i] & 0xc0) != 0x80)
+ return -1;
+ unichar <<= 6;
+ unichar |= (int)str[i] & 0x3f;
+ }
+
+ return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+ if (unichar < 0x80)
+ return 1;
+ if (unichar < 0x800)
+ return 2;
+ if (unichar < 0x10000)
+ return 3;
+ if (unichar < 0x200000)
+ return 4;
+ if (unichar < 0x4000000)
+ return 5;
+ return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+ if (unichar > 0x10ffff)
+ return 0;
+ if ((unichar & 0xfffff800) == 0xd800)
+ return 0;
+ if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+ return 0;
+ if ((unichar & 0xffff) == 0xffff)
+ return 0;
+ return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+ int len;
+ int unichar;
+ int i;
+
+ len = utf8_encoded_expected_len(str);
+ if (len == 0)
+ return -1;
+
+ /* ascii is valid */
+ if (len == 1)
+ return 1;
+
+ /* check if expected encoded chars are available */
+ for (i = 0; i < len; i++)
+ if ((str[i] & 0x80) != 0x80)
+ return -1;
+
+ unichar = utf8_encoded_to_unichar(str);
+
+ /* check if encoded length matches encoded value */
+ if (utf8_unichar_to_encoded_len(unichar) != len)
+ return -1;
+
+ /* check if value has valid range */
+ if (!utf8_unichar_valid_range(unichar))
+ return -1;
+
+ return len;
+}
+
+int util_replace_whitespace(const char *str, char *to, size_t len)
+{
+ size_t i, j;
+
+ /* strip trailing whitespace */
+ len = strnlen(str, len);
+ while (len && isspace(str[len-1]))
+ len--;
+
+ /* strip leading whitespace */
+ i = 0;
+ while (isspace(str[i]) && (i < len))
+ i++;
+
+ j = 0;
+ while (i < len) {
+ /* substitute multiple whitespace with a single '_' */
+ if (isspace(str[i])) {
+ while (isspace(str[i]))
+ i++;
+ to[j++] = '_';
+ }
+ to[j++] = str[i++];
+ }
+ to[j] = '\0';
+ return 0;
+}
+
+static int is_whitelisted(char c, const char *white)
+{
+ if ((c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ strchr("#+-.:=@_", c) != NULL ||
+ (white != NULL && strchr(white, c) != NULL))
+ return 1;
+ return 0;
+}
+
+/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */
+int util_replace_chars(char *str, const char *white)
+{
+ size_t i = 0;
+ int replaced = 0;
+
+ while (str[i] != '\0') {
+ int len;
+
+ if (is_whitelisted(str[i], white)) {
+ i++;
+ continue;
+ }
+
+ /* accept hex encoding */
+ if (str[i] == '\\' && str[i+1] == 'x') {
+ i += 2;
+ continue;
+ }
+
+ /* accept valid utf8 */
+ len = utf8_encoded_valid_unichar(&str[i]);
+ if (len > 1) {
+ i += len;
+ continue;
+ }
+
+ /* if space is allowed, replace whitespace with ordinary space */
+ if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) {
+ str[i] = ' ';
+ i++;
+ replaced++;
+ continue;
+ }
+
+ /* everything else is replaced with '_' */
+ str[i] = '_';
+ i++;
+ replaced++;
+ }
+ return replaced;
+}
+
+/**
+ * udev_util_encode_string:
+ * @str: input string to be encoded
+ * @str_enc: output string to store the encoded input string
+ * @len: maximum size of the output string, which may be
+ * four times as long as the input string
+ *
+ * Encode all potentially unsafe characters of a string to the
+ * corresponding 2 char hex value prefixed by '\x'.
+ *
+ * Returns: 0 if the entire string was copied, non-zero otherwise.
+ **/
+_public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len)
+{
+ size_t i, j;
+
+ if (str == NULL || str_enc == NULL)
+ return -1;
+
+ for (i = 0, j = 0; str[i] != '\0'; i++) {
+ int seqlen;
+
+ seqlen = utf8_encoded_valid_unichar(&str[i]);
+ if (seqlen > 1) {
+ if (len-j < (size_t)seqlen)
+ goto err;
+ memcpy(&str_enc[j], &str[i], seqlen);
+ j += seqlen;
+ i += (seqlen-1);
+ } else if (str[i] == '\\' || !is_whitelisted(str[i], NULL)) {
+ if (len-j < 4)
+ goto err;
+ sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]);
+ j += 4;
+ } else {
+ if (len-j < 1)
+ goto err;
+ str_enc[j] = str[i];
+ j++;
+ }
+ }
+ if (len-j < 1)
+ goto err;
+ str_enc[j] = '\0';
+ return 0;
+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)
+{
+ return murmur_hash2(str, strlen(str), 0);
+}
+
+/* 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;
+}
+
+ssize_t print_kmsg(const char *fmt, ...)
+{
+ int fd;
+ va_list ap;
+ char text[1024];
+ ssize_t len;
+ ssize_t ret;
+
+ fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ len = snprintf(text, sizeof(text), "<30>systemd-udevd[%u]: ", getpid());
+
+ va_start(ap, fmt);
+ len += vsnprintf(text + len, sizeof(text) - len, fmt, ap);
+ va_end(ap);
+
+ ret = write(fd, text, len);
+ if (ret < 0)
+ ret = -errno;
+ close(fd);
+ return ret;
+}
diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c
new file mode 100644
index 0000000000..d860ebc080
--- /dev/null
+++ b/src/libudev/libudev.c
@@ -0,0 +1,314 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+#include "missing.h"
+
+/**
+ * SECTION:libudev
+ * @short_description: libudev context
+ *
+ * The context contains the default values read from the udev config file,
+ * and is passed to all library operations.
+ */
+
+/**
+ * udev:
+ *
+ * Opaque object representing the library context.
+ */
+struct udev {
+ int refcount;
+ void (*log_fn)(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, va_list args);
+ void *userdata;
+ struct udev_list properties_list;
+ int log_priority;
+};
+
+void udev_log(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ udev->log_fn(udev, priority, file, line, fn, format, args);
+ va_end(args);
+}
+
+static void log_stderr(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, va_list args)
+{
+ fprintf(stderr, "libudev: %s: ", fn);
+ vfprintf(stderr, format, args);
+}
+
+/**
+ * udev_get_userdata:
+ * @udev: udev library context
+ *
+ * Retrieve stored data pointer from library context. This might be useful
+ * to access from callbacks like a custom logging function.
+ *
+ * Returns: stored userdata
+ **/
+_public_ void *udev_get_userdata(struct udev *udev)
+{
+ if (udev == NULL)
+ return NULL;
+ return udev->userdata;
+}
+
+/**
+ * udev_set_userdata:
+ * @udev: udev library context
+ * @userdata: data pointer
+ *
+ * Store custom @userdata in the library context.
+ **/
+_public_ void udev_set_userdata(struct udev *udev, void *userdata)
+{
+ if (udev == NULL)
+ return;
+ udev->userdata = userdata;
+}
+
+/**
+ * udev_new:
+ *
+ * Create udev library context. This reads the udev configuration
+ * file, and fills in the default values.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev library context.
+ *
+ * Returns: a new udev library context
+ **/
+_public_ struct udev *udev_new(void)
+{
+ struct udev *udev;
+ const char *env;
+ FILE *f;
+
+ udev = calloc(1, sizeof(struct udev));
+ if (udev == NULL)
+ return NULL;
+ udev->refcount = 1;
+ udev->log_fn = log_stderr;
+ udev->log_priority = LOG_ERR;
+ udev_list_init(udev, &udev->properties_list, true);
+
+ f = fopen("/etc/udev/udev.conf", "re");
+ if (f != NULL) {
+ char line[UTIL_LINE_SIZE];
+ int line_nr = 0;
+
+ while (fgets(line, sizeof(line), f)) {
+ size_t len;
+ char *key;
+ char *val;
+
+ line_nr++;
+
+ /* find key */
+ key = line;
+ while (isspace(key[0]))
+ key++;
+
+ /* comment or empty line */
+ if (key[0] == '#' || key[0] == '\0')
+ continue;
+
+ /* split key/value */
+ val = strchr(key, '=');
+ if (val == NULL) {
+ udev_err(udev, "missing <key>=<value> in /etc/udev/udev.conf[%i]; skip line\n", line_nr);
+ continue;
+ }
+ val[0] = '\0';
+ val++;
+
+ /* find value */
+ while (isspace(val[0]))
+ val++;
+
+ /* terminate key */
+ len = strlen(key);
+ if (len == 0)
+ continue;
+ while (isspace(key[len-1]))
+ len--;
+ key[len] = '\0';
+
+ /* terminate value */
+ len = strlen(val);
+ if (len == 0)
+ continue;
+ while (isspace(val[len-1]))
+ len--;
+ val[len] = '\0';
+
+ if (len == 0)
+ continue;
+
+ /* unquote */
+ if (val[0] == '"' || val[0] == '\'') {
+ if (val[len-1] != val[0]) {
+ udev_err(udev, "inconsistent quoting in /etc/udev/udev.conf[%i]; skip line\n", line_nr);
+ continue;
+ }
+ val[len-1] = '\0';
+ val++;
+ }
+
+ if (strcmp(key, "udev_log") == 0) {
+ udev_set_log_priority(udev, util_log_priority(val));
+ continue;
+ }
+ }
+ fclose(f);
+ }
+
+ /* environment overrides config */
+ env = secure_getenv("UDEV_LOG");
+ if (env != NULL)
+ udev_set_log_priority(udev, util_log_priority(env));
+
+ return udev;
+}
+
+/**
+ * udev_ref:
+ * @udev: udev library context
+ *
+ * Take a reference of the udev library context.
+ *
+ * Returns: the passed udev library context
+ **/
+_public_ struct udev *udev_ref(struct udev *udev)
+{
+ if (udev == NULL)
+ return NULL;
+ udev->refcount++;
+ return udev;
+}
+
+/**
+ * udev_unref:
+ * @udev: udev library context
+ *
+ * Drop a reference of the udev library context. If the refcount
+ * reaches zero, the resources of the context will be released.
+ *
+ * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise.
+ **/
+_public_ struct udev *udev_unref(struct udev *udev)
+{
+ if (udev == NULL)
+ return NULL;
+ udev->refcount--;
+ if (udev->refcount > 0)
+ return udev;
+ udev_list_cleanup(&udev->properties_list);
+ free(udev);
+ return NULL;
+}
+
+/**
+ * udev_set_log_fn:
+ * @udev: udev library context
+ * @log_fn: function to be called for logging messages
+ *
+ * The built-in logging writes to stderr. It can be
+ * overridden by a custom function, to plug log messages
+ * into the users' logging functionality.
+ *
+ **/
+_public_ void udev_set_log_fn(struct udev *udev,
+ void (*log_fn)(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, va_list args))
+{
+ udev->log_fn = log_fn;
+ udev_dbg(udev, "custom logging function %p registered\n", log_fn);
+}
+
+/**
+ * udev_get_log_priority:
+ * @udev: udev library context
+ *
+ * The initial logging priority is read from the udev config file
+ * at startup.
+ *
+ * Returns: the current logging priority
+ **/
+_public_ int udev_get_log_priority(struct udev *udev)
+{
+ return udev->log_priority;
+}
+
+/**
+ * udev_set_log_priority:
+ * @udev: udev library context
+ * @priority: the new logging priority
+ *
+ * Set the current logging priority. The value controls which messages
+ * are logged.
+ **/
+_public_ void udev_set_log_priority(struct udev *udev, int priority)
+{
+ char num[32];
+
+ udev->log_priority = priority;
+ snprintf(num, sizeof(num), "%u", udev->log_priority);
+ udev_add_property(udev, "UDEV_LOG", num);
+}
+
+struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value)
+{
+ if (value == NULL) {
+ struct udev_list_entry *list_entry;
+
+ list_entry = udev_get_properties_list_entry(udev);
+ list_entry = udev_list_entry_get_by_name(list_entry, key);
+ if (list_entry != NULL)
+ udev_list_entry_delete(list_entry);
+ return NULL;
+ }
+ return udev_list_entry_add(&udev->properties_list, key, value);
+}
+
+struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev)
+{
+ return udev_list_get_entry(&udev->properties_list);
+}
diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h
new file mode 100644
index 0000000000..bb41532a21
--- /dev/null
+++ b/src/libudev/libudev.h
@@ -0,0 +1,204 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifndef _LIBUDEV_H_
+#define _LIBUDEV_H_
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * udev - library context
+ *
+ * reads the udev config and system environment
+ * allows custom logging
+ */
+struct udev;
+struct udev *udev_ref(struct udev *udev);
+struct udev *udev_unref(struct udev *udev);
+struct udev *udev_new(void);
+void udev_set_log_fn(struct udev *udev,
+ void (*log_fn)(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, va_list args));
+int udev_get_log_priority(struct udev *udev);
+void udev_set_log_priority(struct udev *udev, int priority);
+void *udev_get_userdata(struct udev *udev);
+void udev_set_userdata(struct udev *udev, void *userdata);
+
+/*
+ * udev_list
+ *
+ * access to libudev generated lists
+ */
+struct udev_list_entry;
+struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry);
+struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name);
+const char *udev_list_entry_get_name(struct udev_list_entry *list_entry);
+const char *udev_list_entry_get_value(struct udev_list_entry *list_entry);
+/**
+ * udev_list_entry_foreach:
+ * @list_entry: entry to store the current position
+ * @first_entry: first entry to start with
+ *
+ * Helper to iterate over all entries of a list.
+ */
+#define udev_list_entry_foreach(list_entry, first_entry) \
+ for (list_entry = first_entry; \
+ list_entry != NULL; \
+ list_entry = udev_list_entry_get_next(list_entry))
+
+/*
+ * udev_device
+ *
+ * access to sysfs/kernel devices
+ */
+struct udev_device;
+struct udev_device *udev_device_ref(struct udev_device *udev_device);
+struct udev_device *udev_device_unref(struct udev_device *udev_device);
+struct udev *udev_device_get_udev(struct udev_device *udev_device);
+struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath);
+struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum);
+struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname);
+struct udev_device *udev_device_new_from_device_id(struct udev *udev, char *id);
+struct udev_device *udev_device_new_from_environment(struct udev *udev);
+/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */
+struct udev_device *udev_device_get_parent(struct udev_device *udev_device);
+struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device,
+ const char *subsystem, const char *devtype);
+/* retrieve device properties */
+const char *udev_device_get_devpath(struct udev_device *udev_device);
+const char *udev_device_get_subsystem(struct udev_device *udev_device);
+const char *udev_device_get_devtype(struct udev_device *udev_device);
+const char *udev_device_get_syspath(struct udev_device *udev_device);
+const char *udev_device_get_sysname(struct udev_device *udev_device);
+const char *udev_device_get_sysnum(struct udev_device *udev_device);
+const char *udev_device_get_devnode(struct udev_device *udev_device);
+int udev_device_get_is_initialized(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device);
+const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key);
+const char *udev_device_get_driver(struct udev_device *udev_device);
+dev_t udev_device_get_devnum(struct udev_device *udev_device);
+const char *udev_device_get_action(struct udev_device *udev_device);
+unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device);
+unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device);
+const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr);
+int udev_device_has_tag(struct udev_device *udev_device, const char *tag);
+
+/*
+ * udev_monitor
+ *
+ * access to kernel uevents and udev events
+ */
+struct udev_monitor;
+struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor);
+struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor);
+struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor);
+/* kernel and udev generated events over netlink */
+struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);
+/* bind socket */
+int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
+int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
+struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);
+/* 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);
+
+/*
+ * udev_enumerate
+ *
+ * search sysfs for specific devices and provide a sorted list
+ */
+struct udev_enumerate;
+struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate);
+struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate);
+struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate);
+struct udev_enumerate *udev_enumerate_new(struct udev *udev);
+/* device properties filter */
+int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+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_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent);
+int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate);
+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);
+int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate);
+/* return device list */
+struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate);
+
+/*
+ * udev_queue
+ *
+ * access to the currently running udev events
+ */
+struct udev_queue;
+struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue);
+struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue);
+struct udev *udev_queue_get_udev(struct udev_queue *udev_queue);
+struct udev_queue *udev_queue_new(struct udev *udev);
+unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue);
+unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue);
+int udev_queue_get_udev_is_active(struct udev_queue *udev_queue);
+int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue);
+int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum);
+int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+ unsigned long long int start, unsigned long long int end);
+struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue);
+
+/*
+ * udev_hwdb
+ *
+ * access to the static hardware properties database
+ */
+struct udev_hwdb;
+struct udev_hwdb *udev_hwdb_new(struct udev *udev);
+struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb);
+struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb);
+struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags);
+
+/*
+ * udev_util
+ *
+ * udev specific utilities
+ */
+int udev_util_encode_string(const char *str, char *str_enc, size_t len);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in
new file mode 100644
index 0000000000..dad7139c85
--- /dev/null
+++ b/src/libudev/libudev.pc.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libudev
+Description: Library to access udev device information
+Version: @VERSION@
+Libs: -L${libdir} -ludev -lrt
+Libs.private:
+Cflags: -I${includedir}
diff --git a/src/libudev/libudev.sym b/src/libudev/libudev.sym
new file mode 100644
index 0000000000..df6a1aeddf
--- /dev/null
+++ b/src/libudev/libudev.sym
@@ -0,0 +1,110 @@
+/***
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+***/
+
+LIBUDEV_183 {
+global:
+ udev_device_get_action;
+ udev_device_get_devlinks_list_entry;
+ udev_device_get_devnode;
+ udev_device_get_devnum;
+ udev_device_get_devpath;
+ udev_device_get_devtype;
+ udev_device_get_driver;
+ udev_device_get_is_initialized;
+ udev_device_get_parent;
+ udev_device_get_parent_with_subsystem_devtype;
+ udev_device_get_properties_list_entry;
+ udev_device_get_property_value;
+ udev_device_get_seqnum;
+ udev_device_get_subsystem;
+ udev_device_get_sysattr_list_entry;
+ udev_device_get_sysattr_value;
+ udev_device_get_sysname;
+ udev_device_get_sysnum;
+ udev_device_get_syspath;
+ udev_device_get_tags_list_entry;
+ udev_device_get_udev;
+ udev_device_get_usec_since_initialized;
+ udev_device_has_tag;
+ udev_device_new_from_devnum;
+ udev_device_new_from_environment;
+ udev_device_new_from_subsystem_sysname;
+ udev_device_new_from_syspath;
+ udev_device_ref;
+ udev_device_unref;
+ udev_enumerate_add_match_is_initialized;
+ udev_enumerate_add_match_parent;
+ udev_enumerate_add_match_property;
+ udev_enumerate_add_match_subsystem;
+ udev_enumerate_add_match_sysattr;
+ udev_enumerate_add_match_sysname;
+ udev_enumerate_add_match_tag;
+ udev_enumerate_add_nomatch_subsystem;
+ udev_enumerate_add_nomatch_sysattr;
+ udev_enumerate_add_syspath;
+ udev_enumerate_get_list_entry;
+ udev_enumerate_get_udev;
+ udev_enumerate_new;
+ udev_enumerate_ref;
+ udev_enumerate_scan_devices;
+ udev_enumerate_scan_subsystems;
+ udev_enumerate_unref;
+ udev_get_log_priority;
+ udev_get_userdata;
+ udev_list_entry_get_by_name;
+ udev_list_entry_get_name;
+ udev_list_entry_get_next;
+ udev_list_entry_get_value;
+ udev_monitor_enable_receiving;
+ udev_monitor_filter_add_match_subsystem_devtype;
+ udev_monitor_filter_add_match_tag;
+ udev_monitor_filter_remove;
+ udev_monitor_filter_update;
+ udev_monitor_get_fd;
+ udev_monitor_get_udev;
+ udev_monitor_new_from_netlink;
+ udev_monitor_new_from_socket;
+ udev_monitor_receive_device;
+ udev_monitor_ref;
+ udev_monitor_set_receive_buffer_size;
+ udev_monitor_unref;
+ udev_new;
+ udev_queue_get_kernel_seqnum;
+ udev_queue_get_queue_is_empty;
+ udev_queue_get_queued_list_entry;
+ udev_queue_get_seqnum_is_finished;
+ udev_queue_get_seqnum_sequence_is_finished;
+ udev_queue_get_udev;
+ udev_queue_get_udev_is_active;
+ udev_queue_get_udev_seqnum;
+ udev_queue_new;
+ udev_queue_ref;
+ udev_queue_unref;
+ udev_ref;
+ udev_set_log_fn;
+ udev_set_log_priority;
+ udev_set_userdata;
+ udev_unref;
+ udev_util_encode_string;
+local:
+ *;
+};
+
+LIBUDEV_189 {
+global:
+ udev_device_new_from_device_id;
+} LIBUDEV_183;
+
+LIBUDEV_196 {
+global:
+ udev_hwdb_new;
+ udev_hwdb_ref;
+ udev_hwdb_unref;
+ udev_hwdb_get_properties_list_entry;
+} LIBUDEV_189;