[systemd-devel] [RFC][PATCH 1/2] libsystemd: add sd-device library

Tom Gundersen teg at jklm.no
Wed Apr 1 06:00:40 PDT 2015


This provides equivalent functionality to libudev-device, but in the
systemd style. The public API only caters to creating sd_device objects
from for devices that already exist in /sys, there is no support for
listening for monitoring uevents or creating devices received over
the udev netlink protocol.

The private API contains the necessary functionality to make sd-device
a drop-in replacement for libudev-device, but which we will not export
publicly.
---

Hi guys,

This is another step on the way of ripping out the bits of libudev that will
still be useful once the udev netlink transport has been replaced by kdbus.

Feedback welcome.

Cheers,

Tom

 Makefile.am                                |    9 +-
 src/libsystemd/sd-device/device-internal.h |  125 ++
 src/libsystemd/sd-device/device-private.c  | 1101 +++++++++++++++++
 src/libsystemd/sd-device/device-private.h  |   63 +
 src/libsystemd/sd-device/device-util.h     |   48 +
 src/libsystemd/sd-device/sd-device.c       | 1812 ++++++++++++++++++++++++++++
 src/systemd/sd-device.h                    |   77 ++
 7 files changed, 3234 insertions(+), 1 deletion(-)
 create mode 100644 src/libsystemd/sd-device/device-internal.h
 create mode 100644 src/libsystemd/sd-device/device-private.c
 create mode 100644 src/libsystemd/sd-device/device-private.h
 create mode 100644 src/libsystemd/sd-device/device-util.h
 create mode 100644 src/libsystemd/sd-device/sd-device.c
 create mode 100644 src/systemd/sd-device.h

diff --git a/Makefile.am b/Makefile.am
index 93fdbc2..9509247 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -231,6 +231,7 @@ AM_CPPFLAGS = \
 	-I $(top_srcdir)/src/libsystemd/sd-rtnl \
 	-I $(top_srcdir)/src/libsystemd/sd-network \
 	-I $(top_srcdir)/src/libsystemd/sd-hwdb \
+	-I $(top_srcdir)/src/libsystemd/sd-device \
 	-I $(top_srcdir)/src/libsystemd-network \
 	-I $(top_srcdir)/src/libsystemd-terminal \
 	$(OUR_CPPFLAGS)
@@ -2918,6 +2919,7 @@ libsystemd_internal_la_SOURCES = \
 	src/systemd/sd-path.h \
 	src/systemd/sd-network.h \
 	src/systemd/sd-hwdb.h \
+	src/systemd/sd-device.h \
 	src/libsystemd/sd-bus/sd-bus.c \
 	src/libsystemd/sd-bus/bus-control.c \
 	src/libsystemd/sd-bus/bus-control.h \
@@ -2981,7 +2983,12 @@ libsystemd_internal_la_SOURCES = \
 	src/libsystemd/sd-network/network-util.c \
 	src/libsystemd/sd-hwdb/sd-hwdb.c \
 	src/libsystemd/sd-hwdb/hwdb-util.h \
-	src/libsystemd/sd-hwdb/hwdb-internal.h
+	src/libsystemd/sd-hwdb/hwdb-intenal.h \
+	src/libsystemd/sd-device/device-internal.h \
+	src/libsystemd/sd-device/device-util.h \
+	src/libsystemd/sd-device/sd-device.c \
+	src/libsystemd/sd-device/device-private.c \
+	src/libsystemd/sd-device/device-private.h
 
 nodist_libsystemd_internal_la_SOURCES = \
 	src/libsystemd/libsystemd.sym
diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h
new file mode 100644
index 0000000..59ec1a6
--- /dev/null
+++ b/src/libsystemd/sd-device/device-internal.h
@@ -0,0 +1,125 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2008-2012 Kay Sievers <kay at vrfy.org>
+  Copyright 2014 Tom Gundersen <teg at jklm.no>
+
+  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/>.
+***/
+
+#pragma once
+
+#include "hashmap.h"
+#include "set.h"
+
+struct sd_device {
+        uint64_t n_ref;
+
+        sd_device *parent;
+        bool parent_set; /* no need to try to reload parent */
+
+        OrderedHashmap *properties;
+        Iterator properties_iterator;
+        uint64_t properties_generation; /* changes whenever the properties are changed */
+        uint64_t properties_iterator_generation; /* generation when iteration was started */
+
+        /* the subset of the properties that should be written to the db*/
+        OrderedHashmap *properties_db;
+
+        Hashmap *sysattr_values; /* cached sysattr values */
+
+        Set *sysattrs; /* names of sysattrs */
+        Iterator sysattrs_iterator;
+        bool sysattrs_read; /* don't try to re-read sysattrs once read */
+
+        Set *tags;
+        Iterator tags_iterator;
+        uint64_t tags_generation; /* changes whenever the tags are changed */
+        uint64_t tags_iterator_generation; /* generation when iteration was started */
+        bool property_tags_outdated; /* need to update TAGS= property */
+
+        Set *devlinks;
+        Iterator devlinks_iterator;
+        uint64_t devlinks_generation; /* changes whenever the devlinks are changed */
+        uint64_t devlinks_iterator_generation; /* generation when iteration was started */
+        bool property_devlinks_outdated; /* need to update DEVLINKS= property */
+        int devlink_priority;
+
+        char **properties_strv; /* the properties hashmap as a strv */
+        uint8_t *properties_nulstr; /* the same as a nulstr */
+        size_t properties_nulstr_len;
+        bool properties_buf_outdated; /* need to reread hashmap */
+
+        int watch_handle;
+
+        char *syspath;
+        const char *devpath;
+        const char *sysnum;
+        char *sysname;
+        bool sysname_set; /* don't reread sysname */
+
+        char *devtype;
+        int ifindex;
+        char *devname;
+        dev_t devnum;
+
+        char *subsystem;
+        bool subsystem_set; /* don't reread subsystem */
+        char *driver;
+        bool driver_set; /* don't reread driver */
+
+        char *id_filename;
+
+        bool is_initialized;
+        uint64_t usec_initialized;
+
+        mode_t devmode;
+        uid_t devuid;
+        gid_t devgid;
+
+        bool uevent_loaded; /* don't reread uevent */
+        bool db_loaded; /* don't reread db */
+
+        bool sealed; /* don't read more information from uevent/db */
+        bool db_persist; /* don't clean up the db when switching from initrd to real root */
+};
+
+typedef enum DeviceAction {
+        DEVICE_ACTION_ADD,
+        DEVICE_ACTION_REMOVE,
+        DEVICE_ACTION_CHANGE,
+        DEVICE_ACTION_MOVE,
+        DEVICE_ACTION_ONLINE,
+        DEVICE_ACTION_OFFLINE,
+        _DEVICE_ACTION_MAX,
+        _DEVICE_ACTION_INVALID = -1,
+} DeviceAction;
+
+int device_new_aux(sd_device **ret);
+int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db);
+int device_add_property_internal(sd_device *device, const char *key, const char *value);
+int device_read_uevent_file(sd_device *device);
+
+int device_set_syspath(sd_device *device, const char *_syspath, bool verify);
+int device_set_ifindex(sd_device *device, const char *ifindex);
+int device_set_devmode(sd_device *device, const char *devmode);
+int device_set_devname(sd_device *device, const char *_devname);
+int device_set_devtype(sd_device *device, const char *_devtype);
+int device_set_devnum(sd_device *device, const char *major, const char *minor);
+int device_set_subsystem(sd_device *device, const char *_subsystem);
+int device_set_driver(sd_device *device, const char *_driver);
+int device_set_usec_initialized(sd_device *device, const char *initialized);
+
+DeviceAction device_action_from_string(const char *s) _pure_;
+const char *device_action_to_string(DeviceAction a) _const_;
diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c
new file mode 100644
index 0000000..56f6dd2
--- /dev/null
+++ b/src/libsystemd/sd-device/device-private.c
@@ -0,0 +1,1101 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2008-2012 Kay Sievers <kay at vrfy.org>
+  Copyright 2014 Tom Gundersen <teg at jklm.no>
+
+  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 <ctype.h>
+#include <sys/types.h>
+#include <net/if.h>
+
+#include "util.h"
+#include "macro.h"
+#include "refcnt.h"
+#include "path-util.h"
+#include "strxcpyx.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "set.h"
+#include "strv.h"
+#include "mkdir.h"
+
+#include "sd-device.h"
+
+#include "device-util.h"
+#include "device-internal.h"
+#include "device-private.h"
+
+int device_add_property(sd_device *device, const char *key, const char *value) {
+        int r;
+
+        assert(device);
+        assert(key);
+
+        r = device_add_property_aux(device, key, value, false);
+        if (r < 0)
+                return r;
+
+        if (key[0] != '.') {
+                r = device_add_property_aux(device, key, value, true);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int device_add_property_internal_from_string(sd_device *device, const char *str) {
+        _cleanup_free_ char *key = NULL;
+        char *value;
+
+        assert(device);
+        assert(str);
+
+        key = strdup(str);
+        if (!key)
+                return -ENOMEM;
+
+        value = strchr(key, '=');
+        if (!value)
+                return -EINVAL;
+
+        *value = '\0';
+
+        if (isempty(++value))
+                value = NULL;
+
+        return device_add_property_internal(device, key, value);
+}
+
+static int handle_db_line(sd_device *device, char key, const char *value) {
+        char *path;
+        int r;
+
+        assert(device);
+        assert(value);
+
+        switch (key) {
+        case 'S':
+                path = strjoina("/dev/", value);
+                r = device_add_devlink(device, path);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'L':
+                r = safe_atoi(value, &device->devlink_priority);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'E':
+                r = device_add_property_internal_from_string(device, value);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'G':
+                r = device_add_tag(device, value);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'W':
+                r = safe_atoi(value, &device->watch_handle);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'I':
+                r = device_set_usec_initialized(device, value);
+                if (r < 0)
+                        return r;
+
+                break;
+        default:
+                log_debug("device db: unknown key '%c'", key);
+        }
+
+        return 0;
+}
+
+void device_set_devlink_priority(sd_device *device, int priority) {
+        assert(device);
+
+        device->devlink_priority = priority;
+}
+
+void device_set_is_initialized(sd_device *device) {
+        assert(device);
+
+        device->is_initialized = true;
+}
+
+int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) {
+        char num[DECIMAL_STR_MAX(usec_t)];
+        usec_t usec_initialized;
+        int r;
+
+        assert(device);
+
+        if (device_old && device_old->usec_initialized > 0)
+                usec_initialized = device_old->usec_initialized;
+        else
+                usec_initialized = now(CLOCK_MONOTONIC);
+
+        r = snprintf(num, sizeof(num), USEC_FMT, usec_initialized);
+        if (r < 0)
+                return -errno;
+
+        r = device_set_usec_initialized(device, num);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int device_read_db(sd_device *device) {
+        _cleanup_free_ char *db = NULL;
+        char *path;
+        const char *id, *value;
+        char key;
+        size_t db_len;
+        unsigned i;
+        int r;
+
+        enum {
+                PRE_KEY,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                INVALID_LINE,
+        } state = PRE_KEY;
+
+        assert(device);
+
+        if (device->db_loaded || device->sealed)
+                return 0;
+
+        r = device_get_id_filename(device, &id);
+        if (r < 0)
+                return r;
+
+        path = strjoina("/run/udev/data/", id);
+
+        r = read_full_file(path, &db, &db_len);
+        if (r < 0) {
+                if (r == -ENOENT)
+                        return 0;
+                else {
+                        log_debug("sd-device: failed to read db '%s': %s", path, strerror(-r));
+                        return r;
+                }
+        }
+
+        /* devices with a database entry are initialized */
+        device_set_is_initialized(device);
+
+        for (i = 0; i < db_len; i++) {
+                switch (state) {
+                case PRE_KEY:
+                        if (!strchr(NEWLINE, db[i])) {
+                                key = db[i];
+
+                                state = KEY;
+                        }
+
+                        break;
+                case KEY:
+                        if (db[i] != ':') {
+                                log_debug("sd-device: ignoring invalid db entry with key '%c'", key);
+
+                                state = INVALID_LINE;
+                        } else {
+                                db[i] = '\0';
+
+                                state = PRE_VALUE;
+                        }
+
+                        break;
+                case PRE_VALUE:
+                        value = &db[i];
+
+                        state = VALUE;
+
+                        break;
+                case INVALID_LINE:
+                        if (strchr(NEWLINE, db[i]))
+                                state = PRE_KEY;
+
+                        break;
+                case VALUE:
+                        if (strchr(NEWLINE, db[i])) {
+                                db[i] = '\0';
+                                r = handle_db_line(device, key, value);
+                                if (r < 0)
+                                        log_debug("sd-device: failed to handle db entry '%c:%s': %s", key, value, strerror(-r));
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                default:
+                        assert_not_reached("invalid state when parsing db");
+                }
+        }
+
+        device->db_loaded = true;
+
+        return 0;
+}
+
+uint64_t device_get_properties_generation(sd_device *device) {
+        assert(device);
+
+        return device->properties_generation;
+}
+
+uint64_t device_get_tags_generation(sd_device *device) {
+        assert(device);
+
+        return device->tags_generation;
+}
+
+uint64_t device_get_devlinks_generation(sd_device *device) {
+        assert(device);
+
+        return device->devlinks_generation;
+}
+
+int device_get_devnode_mode(sd_device *device, mode_t *mode) {
+        int r;
+
+        assert(device);
+        assert(mode);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        *mode = device->devmode;
+
+        return 0;
+}
+
+int device_get_devnode_uid(sd_device *device, uid_t *uid) {
+        int r;
+
+        assert(device);
+        assert(uid);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        *uid = device->devuid;
+
+        return 0;
+}
+
+static int device_set_devuid(sd_device *device, const char *uid) {
+        unsigned u;
+        int r;
+
+        assert(device);
+        assert(uid);
+
+        r = safe_atou(uid, &u);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(device, "DEVUID", uid);
+        if (r < 0)
+                return r;
+
+        device->devuid = u;
+
+        return 0;
+}
+
+int device_get_devnode_gid(sd_device *device, gid_t *gid) {
+        int r;
+
+        assert(device);
+        assert(gid);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        *gid = device->devgid;
+
+        return 0;
+}
+
+static int device_set_devgid(sd_device *device, const char *gid) {
+        unsigned g;
+        int r;
+
+        assert(device);
+        assert(gid);
+
+        r = safe_atou(gid, &g);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(device, "DEVGID", gid);
+        if (r < 0)
+                return r;
+
+        device->devgid = g;
+
+        return 0;
+}
+
+static int device_ammend(sd_device *device, const char *key, const char *value) {
+        int r;
+
+        assert(device);
+        assert(key);
+        assert(value);
+
+        if (streq(key, "DEVPATH")) {
+                char *path;
+
+                path = strjoina("/sys", value);
+
+                /* the caller must verify or trust this data (e.g., if it comes from the kernel) */
+                r = device_set_syspath(device, path, false);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set syspath to '%s': %m", path);
+        } else if (streq(key, "SUBSYSTEM")) {
+                r = device_set_subsystem(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set subsystem to '%s': %m", value);
+        } else if (streq(key, "DEVTYPE")) {
+                r = device_set_devtype(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devtype to '%s': %m", value);
+        } else if (streq(key, "DEVNAME")) {
+                r = device_set_devname(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devname to '%s': %m", value);
+        } else if (streq(key, "USEC_INITIALIZED")) {
+                r = device_set_usec_initialized(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set usec-initialized to '%s': %m", value);
+        } else if (streq(key, "DRIVER")) {
+                r = device_set_driver(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set driver to '%s': %m", value);
+        } else if (streq(key, "IFINDEX")) {
+                r = device_set_ifindex(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set ifindex to '%s': %m", value);
+        } else if (streq(key, "DEVMODE")) {
+                r = device_set_devmode(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devmode to '%s': %m", value);
+        } else if (streq(key, "DEVUID")) {
+                r = device_set_devuid(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devuid to '%s': %m", value);
+        } else if (streq(key, "DEVGID")) {
+                r = device_set_devgid(device, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devgid to '%s': %m", value);
+        } else if (streq(key, "DEVLINKS")) {
+                char *devlinks, *next;
+
+                devlinks = strdupa(value);
+
+                while ((next = strchr(devlinks, ' '))) {
+                        next[0] = '\0';
+
+                        r = device_add_devlink(device, devlinks);
+                        if (r < 0)
+                                return log_debug_errno(r, "sd-device: could not add devlink '%s': %m", devlinks);
+
+                        devlinks = next + 1;
+                }
+        } else if (streq(key, "TAGS")) {
+                char *tags, *next;
+
+                tags = strdupa(value);
+
+                while ((next = strchr(tags, ':'))) {
+                        next[0] = '\0';
+
+                        r = device_add_tag(device, tags);
+                        if (r < 0)
+                                return log_debug_errno(r, "sd-device: could not add tag '%s': %m", tags);
+
+                        tags = next + 1;
+                }
+        } else {
+                r = device_add_property_internal(device, key, value);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not add property '%s=%s': %m", key, value);
+        }
+
+        return 0;
+}
+
+static const char* const device_action_table[_DEVICE_ACTION_MAX] = {
+        [DEVICE_ACTION_ADD] = "add",
+        [DEVICE_ACTION_REMOVE] = "remove",
+        [DEVICE_ACTION_CHANGE] = "change",
+        [DEVICE_ACTION_MOVE] = "move",
+        [DEVICE_ACTION_ONLINE] = "online",
+        [DEVICE_ACTION_OFFLINE] = "offline",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(device_action, DeviceAction);
+
+static int device_append(sd_device *device, char *key, const char **_major, const char **_minor, uint64_t *_seqnum,
+                         DeviceAction *_action) {
+        DeviceAction action = _DEVICE_ACTION_INVALID;
+        uint64_t seqnum;
+        const char *major = NULL, *minor = NULL;
+        char *value;
+        int r;
+
+        assert(device);
+        assert(key);
+        assert(_major);
+        assert(_minor);
+        assert(_seqnum);
+        assert(_action);
+
+        value = strchr(key, '=');
+        if (!value) {
+                log_debug("sd-device: not a key-value pair: '%s'", key);
+                return -EINVAL;
+        }
+
+        *value = '\0';
+
+        value++;
+
+        if (streq(key, "MAJOR"))
+                major = value;
+        else if (streq(key, "MINOR"))
+                minor = value;
+        else {
+                if (streq(key, "ACTION")) {
+                        action = device_action_from_string(value);
+                        if (action == _DEVICE_ACTION_INVALID)
+                                return -EINVAL;
+                } else if (streq(key, "SEQNUM")) {
+                        r = safe_atou64(value, &seqnum);
+                        if (r < 0)
+                                return r;
+                        else if (seqnum == 0)
+                                 /* kernel only sends seqnum > 0 */
+                                return -EINVAL;
+                }
+
+                r = device_ammend(device, key, value);
+                if (r < 0)
+                        return r;
+        }
+
+        if (major != 0)
+                *_major = major;
+
+        if (minor != 0)
+                *_minor = minor;
+
+        if (action != _DEVICE_ACTION_INVALID)
+                *_action = action;
+
+        if (seqnum > 0)
+                *_seqnum = seqnum;
+
+        return 0;
+}
+
+void device_seal(sd_device *device) {
+        assert(device);
+
+        device->sealed = true;
+}
+
+static int device_verify(sd_device *device, DeviceAction action, uint64_t seqnum) {
+        assert(device);
+
+        if (!device->devpath || !device->subsystem || action == _DEVICE_ACTION_INVALID || seqnum == 0) {
+                log_debug("sd-device: device created from strv lacks devpath, subsystem, action or seqnum");
+                return -EINVAL;
+        }
+
+        device->sealed = true;
+
+        return 0;
+}
+
+int device_new_from_strv(sd_device **ret, char **strv) {
+        _cleanup_device_unref_ sd_device *device = NULL;
+        char **key;
+        const char *major = NULL, *minor = NULL;
+        DeviceAction action = _DEVICE_ACTION_INVALID;
+        uint64_t seqnum;
+        int r;
+
+        assert(ret);
+        assert(strv);
+
+        r = device_new_aux(&device);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(key, strv) {
+                r = device_append(device, *key, &major, &minor, &seqnum, &action);
+                if (r < 0)
+                        return r;
+        }
+
+        if (major) {
+                r = device_set_devnum(device, major, minor);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor);
+        }
+
+        r = device_verify(device, action, seqnum);
+        if (r < 0)
+                return r;
+
+        *ret = device;
+        device = NULL;
+
+        return 0;
+}
+
+int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) {
+        _cleanup_device_unref_ sd_device *device = NULL;
+        const char *major = NULL, *minor = NULL;
+        DeviceAction action = _DEVICE_ACTION_INVALID;
+        uint64_t seqnum;
+        unsigned i = 0;
+        int r;
+
+        assert(ret);
+        assert(nulstr);
+        assert(len);
+
+        r = device_new_aux(&device);
+        if (r < 0)
+                return r;
+
+        while (i < len) {
+                char *key;
+                const char *end;
+
+                key = (char*)&nulstr[i];
+                end = memchr(key, '\0', len - i);
+                if (!end) {
+                        log_debug("sd-device: failed to parse nulstr");
+                        return -EINVAL;
+                }
+                i += end - key + 1;
+
+                r = device_append(device, key, &major, &minor, &seqnum, &action);
+                if (r < 0)
+                        return r;
+        }
+
+        if (major) {
+                r = device_set_devnum(device, major, minor);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor);
+        }
+
+        r = device_verify(device, action, seqnum);
+        if (r < 0)
+                return r;
+
+        *ret = device;
+        device = NULL;
+
+        return 0;
+}
+
+static int device_update_properties_bufs(sd_device *device) {
+        const char *val, *prop;
+        char **buf_strv = NULL;
+        uint8_t *buf_nulstr = NULL;
+        size_t allocated_nulstr = 0, allocated_strv = 0;
+        size_t nulstr_len = 0, strv_size = 0;
+
+        assert(device);
+
+        FOREACH_DEVICE_PROPERTY(device, prop, val) {
+                size_t len = 0;
+
+                len = strlen(prop) + 1 + strlen(val);
+
+                buf_nulstr = GREEDY_REALLOC0(buf_nulstr, allocated_nulstr, nulstr_len + len + 2);
+                if (!buf_nulstr)
+                        return -ENOMEM;
+
+                buf_strv = GREEDY_REALLOC0(buf_strv, allocated_strv, strv_size + 2);
+                if (!buf_strv)
+                        return -ENOMEM;
+
+                buf_strv[++ strv_size] = (char *)&buf_nulstr[nulstr_len];
+                strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL);
+                nulstr_len += len + 1;
+        }
+
+        free(device->properties_nulstr);
+        free(device->properties_strv);
+        device->properties_nulstr = buf_nulstr;
+        device->properties_nulstr_len = nulstr_len;
+        device->properties_strv = buf_strv;
+
+        device->properties_buf_outdated = false;
+
+        return 0;
+}
+
+int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) {
+        int r;
+
+        assert(device);
+        assert(nulstr);
+        assert(len);
+
+        if (device->properties_buf_outdated) {
+                r = device_update_properties_bufs(device);
+                if (r < 0)
+                        return r;
+        }
+
+        *nulstr = device->properties_nulstr;
+        *len = device->properties_nulstr_len;
+
+        return 0;
+}
+
+int device_get_properties_strv(sd_device *device, char ***strv) {
+        int r;
+
+        assert(device);
+        assert(strv);
+
+        r = device_update_properties_bufs(device);
+        if (r < 0)
+                return r;
+
+        *strv = device->properties_strv;
+
+        return 0;
+}
+
+int device_get_devlink_priority(sd_device *device, int *priority) {
+        int r;
+
+        assert(device);
+        assert(priority);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        *priority = device->devlink_priority;
+
+        return 0;
+}
+
+int device_get_watch_handle(sd_device *device, int *handle) {
+        int r;
+
+        assert(device);
+        assert(handle);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        *handle = device->watch_handle;
+
+        return 0;
+}
+
+void device_set_watch_handle(sd_device *device, int handle) {
+        assert(device);
+
+        device->watch_handle = handle;
+}
+
+int device_rename(sd_device *device, const char *name) {
+        _cleanup_free_ char *dirname = NULL;
+        char *new_syspath;
+        const char *interface;
+        int r;
+
+        assert(device);
+        assert(name);
+
+        dirname = dirname_malloc(device->syspath);
+        if (!dirname)
+                return -ENOMEM;
+
+        new_syspath = strjoina(dirname, "/", name);
+
+        /* the user must trust that the new name is correct */
+        r = device_set_syspath(device, new_syspath, false);
+        if (r < 0)
+                return r;
+
+        r = sd_device_get_property_value(device, "INTERFACE", &interface);
+        if (r >= 0) {
+                r = device_add_property_internal(device, "INTERFACE", name);
+                if (r < 0)
+                        return r;
+
+                /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */
+                r = device_add_property_internal(device, "INTERFACE_OLD", interface);
+                if (r < 0)
+                        return r;
+        } else if (r != -ENOENT)
+                return r;
+
+        return 0;
+}
+
+int device_shallow_clone(sd_device *old_device, sd_device **new_device) {
+        _cleanup_device_unref_ sd_device *ret = NULL;
+        int r;
+
+        assert(old_device);
+        assert(new_device);
+
+        r = device_new_aux(&ret);
+        if (r < 0)
+                return r;
+
+        r = device_set_syspath(ret, old_device->syspath, false);
+        if (r < 0)
+                return r;
+
+        r = device_set_subsystem(ret, old_device->subsystem);
+        if (r < 0)
+                return r;
+
+        ret->devnum = old_device->devnum;
+
+        *new_device = ret;
+        ret = NULL;
+
+        return 0;
+}
+
+int device_clone_with_db(sd_device *old_device, sd_device **new_device) {
+        _cleanup_device_unref_ sd_device *ret = NULL;
+        int r;
+
+        assert(old_device);
+        assert(new_device);
+
+        r = device_shallow_clone(old_device, &ret);
+        if (r < 0)
+                return r;
+
+        r = device_read_db(ret);
+        if (r < 0)
+                return r;
+
+        ret->sealed = true;
+
+        *new_device = ret;
+        ret = NULL;
+
+        return 0;
+}
+
+int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action) {
+        _cleanup_device_unref_ sd_device *ret = NULL;
+        int r;
+
+        assert(new_device);
+        assert(syspath);
+        assert(action);
+
+        r = sd_device_new_from_syspath(&ret, syspath);
+        if (r < 0)
+                return r;
+
+        r = device_read_uevent_file(ret);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(ret, "ACTION", action);
+        if (r < 0)
+                return r;
+
+        *new_device = ret;
+        ret = NULL;
+
+        return 0;
+}
+
+int device_copy_properties(sd_device *device_dst, sd_device *device_src) {
+        const char *property, *value;
+        int r;
+
+        assert(device_dst);
+        assert(device_src);
+
+        FOREACH_DEVICE_PROPERTY(device_src, property, value) {
+                r = device_add_property(device_dst, property, value);
+                if (r < 0)
+                        return r;
+        }
+
+        return r;
+}
+
+void device_cleanup_tags(sd_device *device) {
+        assert(device);
+
+        set_free_free(device->tags);
+        device->tags = NULL;
+        device->property_tags_outdated = true;
+        device->tags_generation ++;
+}
+
+void device_cleanup_devlinks(sd_device *device) {
+        assert(device);
+
+        set_free_free(device->devlinks);
+        device->devlinks = NULL;
+        device->property_devlinks_outdated = true;
+        device->devlinks_generation ++;
+}
+
+void device_remove_tag(sd_device *device, const char *tag) {
+        assert(device);
+        assert(tag);
+
+        free(set_remove(device->tags, tag));
+        device->property_tags_outdated = true;
+        device->tags_generation ++;
+}
+
+static int device_tag(sd_device *device, const char *tag, bool add) {
+        const char *id;
+        char *path;
+        int r;
+
+        assert(device);
+        assert(tag);
+
+        r = device_get_id_filename(device, &id);
+        if (r < 0)
+                return r;
+
+        path = strjoina("/run/udev/tags/", tag, "/", id);
+
+        if (add) {
+                r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444);
+                if (r < 0)
+                        return r;
+        } else {
+                r = unlink(path);
+                if (r < 0 && errno != ENOENT)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+int device_tag_index(sd_device *device, sd_device *device_old, bool add) {
+        const char *tag;
+        int r = 0, k;
+
+        if (add && device_old) {
+                /* delete possible left-over tags */
+                FOREACH_DEVICE_TAG(device_old, tag) {
+                        if (!sd_device_has_tag(device, tag)) {
+                                k = device_tag(device_old, tag, false);
+                                if (r >= 0 && k < 0)
+                                        r = k;
+                        }
+                }
+        }
+
+        FOREACH_DEVICE_TAG(device, tag) {
+                k = device_tag(device, tag, add);
+                if (r >= 0 && k < 0)
+                        r = k;
+        }
+
+        return r;
+}
+
+static bool device_has_info(sd_device *device) {
+        assert(device);
+
+        if (!set_isempty(device->devlinks))
+                return true;
+
+        if (device->devlink_priority != 0)
+                return true;
+
+        if (!ordered_hashmap_isempty(device->properties_db))
+                return true;
+
+        if (!set_isempty(device->tags))
+                return true;
+
+        if (device->watch_handle >= 0)
+                return true;
+
+        return false;
+}
+
+void device_set_db_persist(sd_device *device) {
+        assert(device);
+
+        device->db_persist = true;
+}
+
+int device_update_db(sd_device *device) {
+        const char *id;
+        char *path;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *path_tmp = NULL;
+        bool has_info;
+        int r;
+
+        assert(device);
+
+        has_info = device_has_info(device);
+
+        r = device_get_id_filename(device, &id);
+        if (r < 0)
+                return r;
+
+        path = strjoina("/run/udev/data/", id);
+
+        /* do not store anything for otherwise empty devices */
+        if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) {
+                r = unlink(path);
+                if (r < 0 && errno != ENOENT)
+                        return -errno;
+
+                return 0;
+        }
+
+        /* write a database file */
+        r = mkdir_parents(path, 0755);
+        if (r < 0)
+                return r;
+
+        r = fopen_temporary(path, &f, &path_tmp);
+        if (r < 0)
+                return r;
+
+        /*
+         * set 'sticky' bit to indicate that we should not clean the
+         * database when we transition from initramfs to the real root
+         */
+        if (device->db_persist) {
+                r = fchmod(fileno(f), 01644);
+                if (r < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+        } else {
+                r = fchmod(fileno(f), 0644);
+                if (r < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+        }
+
+        if (has_info) {
+                const char *property, *value, *tag;
+                Iterator i;
+
+                if (major(device->devnum) > 0) {
+                        const char *devlink;
+
+                        FOREACH_DEVICE_DEVLINK(device, devlink)
+                                fprintf(f, "S:%s\n", devlink + strlen("/dev/"));
+
+                        if (device->devlink_priority != 0)
+                                fprintf(f, "L:%i\n", device->devlink_priority);
+
+                        if (device->watch_handle >= 0)
+                                fprintf(f, "W:%i\n", device->watch_handle);
+                }
+
+                if (device->usec_initialized > 0)
+                        fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized);
+
+                ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db, i)
+                        fprintf(f, "E:%s=%s\n", property, value);
+
+                FOREACH_DEVICE_TAG(device, tag)
+                        fprintf(f, "G:%s\n", tag);
+        }
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                goto fail;
+
+        r = rename(path_tmp, path);
+        if (r < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        log_debug("created %s file '%s' for '%s'", has_info ? "db" : "empty",
+                  path, device->devpath);
+
+        return 0;
+
+fail:
+        log_error_errno(r, "failed to create %s file '%s' for '%s'", has_info ? "db" : "empty",
+                        path, device->devpath);
+        unlink(path);
+        unlink(path_tmp);
+
+        return r;
+}
+
+int device_delete_db(sd_device *device) {
+        const char *id;
+        char *path;
+        int r;
+
+        assert(device);
+
+        r = device_get_id_filename(device, &id);
+        if (r < 0)
+                return r;
+
+        path = strjoina("/run/udev/data/", id);
+
+        r = unlink(path);
+        if (r < 0 && errno != ENOENT)
+                return -errno;
+
+        return 0;
+}
diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h
new file mode 100644
index 0000000..7c6219c
--- /dev/null
+++ b/src/libsystemd/sd-device/device-private.h
@@ -0,0 +1,63 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Tom Gundersen <teg at jklm.no>
+
+  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/>.
+***/
+
+int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len);
+int device_new_from_strv(sd_device **ret, char **strv);
+
+int device_get_id_filename(sd_device *device, const char **ret);
+
+int device_get_devlink_priority(sd_device *device, int *priority);
+int device_get_watch_handle(sd_device *device, int *handle);
+int device_get_devnode_mode(sd_device *device, mode_t *mode);
+int device_get_devnode_uid(sd_device *device, uid_t *uid);
+int device_get_devnode_gid(sd_device *device, gid_t *gid);
+
+void device_seal(sd_device *device);
+void device_set_is_initialized(sd_device *device);
+void device_set_watch_handle(sd_device *device, int fd);
+void device_set_db_persist(sd_device *device);
+void device_set_devlink_priority(sd_device *device, int priority);
+int device_ensure_usec_initialized(sd_device *devcie, sd_device *device_old);
+int device_add_devlink(sd_device *device, const char *devlink);
+int device_add_property(sd_device *device, const char *property, const char *value);
+int device_add_tag(sd_device *device, const char *tag);
+void device_remove_tag(sd_device *device, const char *tag);
+void device_cleanup_tags(sd_device *device);
+void device_cleanup_devlinks(sd_device *device);
+
+uint64_t device_get_properties_generation(sd_device *device);
+uint64_t device_get_tags_generation(sd_device *device);
+uint64_t device_get_devlinks_generation(sd_device *device);
+
+int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len);
+int device_get_properties_strv(sd_device *device, char ***strv);
+
+int device_rename(sd_device *device, const char *name);
+int device_shallow_clone(sd_device *old_device, sd_device **new_device);
+int device_clone_with_db(sd_device *old_device, sd_device **new_device);
+int device_copy_properties(sd_device *device_dst, sd_device *device_src);
+int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action);
+
+int device_tag_index(sd_device *dev, sd_device *dev_old, bool add);
+int device_update_db(sd_device *device);
+int device_delete_db(sd_device *device);
diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h
new file mode 100644
index 0000000..bfbb328
--- /dev/null
+++ b/src/libsystemd/sd-device/device-util.h
@@ -0,0 +1,48 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Tom Gundersen <teg at jklm.no>
+
+  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 "util.h"
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_device*, sd_device_unref);
+
+#define _cleanup_device_unref_ _cleanup_(sd_device_unrefp)
+
+#define FOREACH_DEVICE_PROPERTY(device, key, value)                \
+        for (key = sd_device_get_property_first(device, &(value)); \
+             key;                                                  \
+             key = sd_device_get_property_next(device, &(value)))
+
+#define FOREACH_DEVICE_TAG(device, tag)             \
+        for (tag = sd_device_get_tag_first(device); \
+             tag;                                   \
+             tag = sd_device_get_tag_next(device))
+
+#define FOREACH_DEVICE_SYSATTR(device, attr)             \
+        for (attr = sd_device_get_sysattr_first(device); \
+             attr;                                       \
+             attr = sd_device_get_sysattr_next(device))
+
+#define FOREACH_DEVICE_DEVLINK(device, devlink)             \
+        for (devlink = sd_device_get_devlink_first(device); \
+             devlink;                                   \
+             devlink = sd_device_get_devlink_next(device))
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
new file mode 100644
index 0000000..921adfd
--- /dev/null
+++ b/src/libsystemd/sd-device/sd-device.c
@@ -0,0 +1,1812 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2008-2012 Kay Sievers <kay at vrfy.org>
+  Copyright 2014 Tom Gundersen <teg at jklm.no>
+
+  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 <ctype.h>
+#include <sys/types.h>
+#include <net/if.h>
+
+#include "util.h"
+#include "macro.h"
+#include "path-util.h"
+#include "strxcpyx.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "set.h"
+#include "strv.h"
+
+#include "sd-device.h"
+
+#include "device-util.h"
+#include "device-private.h"
+#include "device-internal.h"
+
+int device_new_aux(sd_device **ret) {
+        _cleanup_device_unref_ sd_device *device = NULL;
+
+        assert(ret);
+
+        device = new0(sd_device, 1);
+        if (!device)
+                return -ENOMEM;
+
+        device->n_ref = 1;
+        device->watch_handle = -1;
+
+        *ret = device;
+        device = NULL;
+
+        return 0;
+}
+
+_public_ sd_device *sd_device_ref(sd_device *device) {
+        if (device)
+                assert_se(++ device->n_ref >= 2);
+
+        return device;
+}
+
+_public_ sd_device *sd_device_unref(sd_device *device) {
+        if (device && -- device->n_ref == 0) {
+                sd_device_unref(device->parent);
+                free(device->syspath);
+                free(device->sysname);
+                free(device->devtype);
+                free(device->devname);
+                free(device->subsystem);
+                free(device->driver);
+                free(device->id_filename);
+                free(device->properties_strv);
+                free(device->properties_nulstr);
+
+                ordered_hashmap_free_free_free(device->properties);
+                ordered_hashmap_free_free_free(device->properties_db);
+                hashmap_free_free_free(device->sysattr_values);
+                set_free_free(device->sysattrs);
+                set_free_free(device->tags);
+                set_free_free(device->devlinks);
+
+                free(device);
+        }
+
+        return NULL;
+}
+
+int device_add_property_aux(sd_device *device, const char *_key, const char *_value, bool db) {
+        OrderedHashmap **properties;
+
+        assert(device);
+        assert(_key);
+
+        if (db)
+                properties = &device->properties_db;
+        else
+                properties = &device->properties;
+
+        if (_value) {
+                _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL;
+                int r;
+
+                r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops);
+                if (r < 0)
+                        return r;
+
+                key = strdup(_key);
+                if (!key)
+                        return -ENOMEM;
+
+                value = strdup(_value);
+                if (!value)
+                        return -ENOMEM;
+
+                old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key);
+
+                r = ordered_hashmap_replace(*properties, key, value);
+                if (r < 0)
+                        return r;
+
+                key = NULL;
+                value = NULL;
+        } else {
+                _cleanup_free_ char *key = NULL;
+                _cleanup_free_ char *value = NULL;
+
+                value = ordered_hashmap_remove2(*properties, _key, (void**) &key);
+        }
+
+        if (!db) {
+                device->properties_generation ++;
+                device->properties_buf_outdated = true;
+        }
+
+        return 0;
+}
+
+int device_add_property_internal(sd_device *device, const char *key, const char *value) {
+        return device_add_property_aux(device, key, value, false);
+}
+
+int device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
+        _cleanup_free_ char *syspath = NULL;
+        const char *devpath;
+        int r;
+
+        assert(device);
+        assert(_syspath);
+
+        /* must be a subdirectory of /sys */
+        if (!path_startswith(_syspath, "/sys/")) {
+                log_debug("sd-device: syspath '%s' is not a subdirectory of /sys", _syspath);
+                return -EINVAL;
+        }
+
+        if (verify) {
+                r = readlink_and_canonicalize(_syspath, &syspath);
+                if (r == -EINVAL) {
+                        /* not a symlink */
+                        syspath = canonicalize_file_name(_syspath);
+                        if (!syspath) {
+                                log_debug("sd-device: could not canonicalize '%s': %m", _syspath);
+                                return -errno;
+                        }
+                /* ignore errors due to the link not being a symlink */
+                } else if (r < 0 && r != -EINVAL) {
+                        log_debug("sd-device: could not get target of '%s': %s", _syspath, strerror(-r));
+                        return r;
+                }
+
+                if (path_startswith(syspath,  "/sys/devices/")) {
+                        char *path;
+
+                        /* all 'devices' require an 'uevent' file */
+                        path = strjoina(syspath, "/uevent");
+                        r = access(path, F_OK);
+                        if (r < 0) {
+                                log_debug("sd-device: %s does not have an uevent file: %m", syspath);
+                                return -errno;
+                        }
+                } else {
+                        /* everything else just just needs to be a directory */
+                        if (!is_dir(syspath, false)) {
+                                log_debug("sd-device: %s is not a directory", syspath);
+                                return -EINVAL;
+                        }
+                }
+        } else {
+                syspath = strdup(_syspath);
+                if (!syspath)
+                        return -ENOMEM;
+        }
+
+        devpath = syspath + strlen("/sys");
+
+        r = device_add_property_internal(device, "DEVPATH", devpath);
+        if (r < 0)
+                return r;
+
+        free(device->syspath);
+        device->syspath = syspath;
+        syspath = NULL;
+
+        device->devpath = devpath;
+
+        return 0;
+}
+
+_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) {
+        _cleanup_device_unref_ sd_device *device = NULL;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(syspath, -EINVAL);
+
+        r = device_new_aux(&device);
+        if (r < 0)
+                return r;
+
+        r = device_set_syspath(device, syspath, true);
+        if (r < 0)
+                return r;
+
+        *ret = device;
+        device = NULL;
+
+        return 0;
+}
+
+_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) {
+        char *syspath;
+        char id[DECIMAL_STR_MAX(unsigned) * 2 + 1];
+
+        assert_return(ret, -EINVAL);
+        assert_return(type == 'b' || type == 'c', -EINVAL);
+
+        /* use /sys/dev/{block,char}/<maj>:<min> link */
+        snprintf(id, sizeof(id), "%u:%u", major(devnum), minor(devnum));
+
+        syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id);
+
+        return sd_device_new_from_syspath(ret, syspath);
+}
+
+_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) {
+        char *syspath;
+
+        assert_return(ret, -EINVAL);
+        assert_return(subsystem, -EINVAL);
+        assert_return(sysname, -EINVAL);
+
+        if (streq(subsystem, "subsystem")) {
+                syspath = strjoina("/sys/subsystem/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+
+                syspath = strjoina("/sys/bus/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+
+                syspath = strjoina("/sys/class/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+        } else  if (streq(subsystem, "module")) {
+                syspath = strjoina("/sys/module/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+        } else if (streq(subsystem, "drivers")) {
+                char subsys[PATH_MAX];
+                char *driver;
+
+                strscpy(subsys, sizeof(subsys), sysname);
+                driver = strchr(subsys, ':');
+                if (driver) {
+                        driver[0] = '\0';
+                        driver++;
+
+                        syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver);
+                        if (access(syspath, F_OK) >= 0)
+                                return sd_device_new_from_syspath(ret, syspath);
+
+                        syspath = strjoina("/sys/bus/", subsys, "/drivers/", driver);
+                        if (access(syspath, F_OK) >= 0)
+                                return sd_device_new_from_syspath(ret, syspath);
+                } else
+                        return -EINVAL;
+        } else {
+                syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+
+                syspath = strjoina("/sys/bus/", subsystem, "/devices/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+
+                syspath = strjoina("/sys/class/", subsystem, "/", sysname);
+                if (access(syspath, F_OK) >= 0)
+                        return sd_device_new_from_syspath(ret, syspath);
+        }
+
+        return -ENOENT;
+}
+
+int device_set_devtype(sd_device *device, const char *_devtype) {
+        _cleanup_free_ char *devtype = NULL;
+        int r;
+
+        assert(device);
+        assert(_devtype);
+
+        devtype = strdup(_devtype);
+        if (!devtype)
+                return -ENOMEM;
+
+        r = device_add_property_internal(device, "DEVTYPE", devtype);
+        if (r < 0)
+                return r;
+
+        free(device->devtype);
+        device->devtype = devtype;
+        devtype = NULL;
+
+        return 0;
+}
+
+int device_set_ifindex(sd_device *device, const char *_ifindex) {
+        int ifindex, r;
+
+        assert(device);
+        assert(_ifindex);
+
+        r = safe_atoi(_ifindex, &ifindex);
+        if (r < 0)
+                return r;
+
+        if (ifindex <= 0)
+                return -EINVAL;
+
+        r = device_add_property_internal(device, "IFINDEX", _ifindex);
+        if (r < 0)
+                return r;
+
+        device->ifindex = ifindex;
+
+        return 0;
+}
+
+int device_set_devname(sd_device *device, const char *_devname) {
+        _cleanup_free_ char *devname = NULL;
+        int r;
+
+        assert(device);
+        assert(_devname);
+
+        if (_devname[0] != '/') {
+                r = asprintf(&devname, "/dev/%s", _devname);
+                if (r < 0)
+                        return -ENOMEM;
+        } else {
+                devname = strdup(_devname);
+                if (!devname)
+                        return -ENOMEM;
+        }
+
+        r = device_add_property_internal(device, "DEVNAME", devname);
+        if (r < 0)
+                return r;
+
+        free(device->devname);
+        device->devname = devname;
+        devname = NULL;
+
+        return 0;
+}
+
+int device_set_devmode(sd_device *device, const char *_devmode) {
+        unsigned devmode;
+        int r;
+
+        assert(device);
+        assert(_devmode);
+
+        r = safe_atou(_devmode, &devmode);
+        if (r < 0)
+                return r;
+
+        if (devmode > 07777)
+                return -EINVAL;
+
+        r = device_add_property_internal(device, "DEVMODE", _devmode);
+        if (r < 0)
+                return r;
+
+        device->devmode = devmode;
+
+        return 0;
+}
+
+int device_set_devnum(sd_device *device, const char *major, const char *minor) {
+        unsigned maj = 0, min = 0;
+        int r;
+
+        assert(device);
+        assert(major);
+
+        r = safe_atou(major, &maj);
+        if (r < 0)
+                return r;
+        if (!maj)
+                return 0;
+
+        if (minor) {
+                r = safe_atou(minor, &min);
+                if (r < 0)
+                        return r;
+        }
+
+        r = device_add_property_internal(device, "MAJOR", major);
+        if (r < 0)
+                return r;
+
+        if (minor) {
+                r = device_add_property_internal(device, "MINOR", minor);
+                if (r < 0)
+                        return r;
+        }
+
+        device->devnum = makedev(maj, min);
+
+        return 0;
+}
+
+static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) {
+        int r;
+
+        assert(device);
+        assert(key);
+        assert(value);
+        assert(major);
+        assert(minor);
+
+        if (streq(key, "DEVTYPE")) {
+                r = device_set_devtype(device, value);
+                if (r < 0)
+                        return r;
+        } else if (streq(key, "IFINDEX")) {
+                r = device_set_ifindex(device, value);
+                if (r < 0)
+                        return r;
+        } else if (streq(key, "DEVNAME")) {
+                r = device_set_devname(device, value);
+                if (r < 0)
+                        return r;
+        } else if (streq(key, "DEVMODE")) {
+                r = device_set_devmode(device, value);
+                if (r < 0)
+                        return r;
+        } else if (streq(key, "MAJOR"))
+                *major = value;
+        else if (streq(key, "MINOR"))
+                *minor = value;
+        else {
+                r = device_add_property_internal(device, key, value);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int device_read_uevent_file(sd_device *device) {
+        _cleanup_free_ char *uevent = NULL;
+        const char *syspath, *key, *value, *major = NULL, *minor = NULL;
+        char *path;
+        size_t uevent_len;
+        unsigned i;
+        int r;
+
+        enum {
+                PRE_KEY,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                INVALID_LINE,
+        } state = PRE_KEY;
+
+        assert(device);
+
+        if (device->uevent_loaded || device->sealed)
+                return 0;
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        path = strjoina(syspath, "/uevent");
+
+        r = read_full_file(path, &uevent, &uevent_len);
+        if (r < 0) {
+                log_debug("sd-device: failed to read uevent file '%s': %s", path, strerror(-r));
+                return r;
+        }
+
+        for (i = 0; i < uevent_len; i++) {
+                switch (state) {
+                case PRE_KEY:
+                        if (!strchr(NEWLINE, uevent[i])) {
+                                key = &uevent[i];
+
+                                state = KEY;
+                        }
+
+                        break;
+                case KEY:
+                        if (uevent[i] == '=') {
+                                uevent[i] = '\0';
+
+                                state = PRE_VALUE;
+                        } else if (strchr(NEWLINE, uevent[i])) {
+                                uevent[i] = '\0';
+                                log_debug("sd-device: ignoring invalid uevent line '%s'", key);
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                case PRE_VALUE:
+                        value = &uevent[i];
+
+                        state = VALUE;
+
+                        break;
+                case VALUE:
+                        if (strchr(NEWLINE, uevent[i])) {
+                                uevent[i] = '\0';
+
+                                r = handle_uevent_line(device, key, value, &major, &minor);
+                                if (r < 0)
+                                        log_debug("sd-device: failed to handle uevent entry '%s=%s': %s", key, value, strerror(-r));
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                default:
+                        assert_not_reached("invalid state when parsing uevent file");
+                }
+        }
+
+        if (major) {
+                r = device_set_devnum(device, major, minor);
+                if (r < 0)
+                        log_debug("sd-device: could not set 'MAJOR=%s' or 'MINOR=%s' from '%s': %s", major, minor, path, strerror(-r));
+        }
+
+        device->uevent_loaded = true;
+
+        return 0;
+}
+
+_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(ifindex, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        *ifindex = device->ifindex;
+
+        return 0;
+}
+
+_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) {
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(id, -EINVAL);
+
+        switch (id[0]) {
+        case 'b':
+        case 'c':
+        {
+                char type;
+                int maj, min;
+
+                r = sscanf(id, "%c%i:%i", &type, &maj, &min);
+                if (r != 3)
+                        return -EINVAL;
+
+                return sd_device_new_from_devnum(ret, type, makedev(maj, min));
+        }
+        case 'n':
+        {
+                _cleanup_device_unref_ sd_device *device = NULL;
+                _cleanup_close_ int sk = -1;
+                struct ifreq ifr = {};
+                int ifindex;
+
+                r = safe_atoi(&id[1], &ifr.ifr_ifindex);
+                if (r < 0)
+                        return r;
+                else if (ifr.ifr_ifindex <= 0)
+                        return -EINVAL;
+
+                sk = socket(PF_INET, SOCK_DGRAM, 0);
+                if (sk < 0)
+                        return -errno;
+
+                r = ioctl(sk, SIOCGIFNAME, &ifr);
+                if (r < 0)
+                        return -errno;
+
+                r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name);
+                if (r < 0)
+                        return r;
+
+                r = sd_device_get_ifindex(device, &ifindex);
+                if (r < 0)
+                        return r;
+
+                /* this si racey, so we might end up with the wrong device */
+                if (ifr.ifr_ifindex != ifindex)
+                        return -ENODEV;
+
+                *ret = device;
+                device = NULL;
+
+                return 0;
+        }
+        case '+':
+        {
+                char subsys[PATH_MAX];
+                char *sysname;
+
+                (void)strscpy(subsys, sizeof(subsys), id + 1);
+                sysname = strchr(subsys, ':');
+                if (!sysname)
+                        return -EINVAL;
+
+                sysname[0] = '\0';
+                sysname ++;
+
+                return sd_device_new_from_subsystem_sysname(ret, subsys, sysname);
+        }
+        default:
+                return -EINVAL;
+        }
+}
+
+_public_ int sd_device_get_syspath(sd_device *device, const char **ret) {
+        assert_return(device, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        assert(path_startswith(device->syspath, "/sys/"));
+
+        *ret = device->syspath;
+
+        return 0;
+}
+
+static int device_new_from_child(sd_device **ret, sd_device *child) {
+        _cleanup_free_ char *path = NULL;
+        const char *subdir, *syspath;
+        int r;
+
+        assert(ret);
+        assert(child);
+
+        r = sd_device_get_syspath(child, &syspath);
+        if (r < 0)
+                return r;
+
+        path = strdup(syspath);
+        if (!path)
+                return -ENOMEM;
+        subdir = path + strlen("/sys");
+
+        for (;;) {
+                char *pos;
+
+                pos = strrchr(subdir, '/');
+                if (!pos || pos < subdir + 2)
+                        break;
+
+                *pos = '\0';
+
+                r = sd_device_new_from_syspath(ret, path);
+                if (r < 0)
+                        continue;
+
+                return 0;
+        }
+
+        return -ENOENT;
+}
+
+_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) {
+
+        assert_return(ret, -EINVAL);
+        assert_return(child, -EINVAL);
+
+        if (!child->parent_set) {
+                child->parent_set = true;
+
+                (void)device_new_from_child(&child->parent, child);
+        }
+
+        if (!child->parent)
+                return -ENOENT;
+
+        *ret = child->parent;
+
+        return 0;
+}
+
+int device_set_subsystem(sd_device *device, const char *_subsystem) {
+        _cleanup_free_ char *subsystem = NULL;
+        int r;
+
+        assert(device);
+        assert(_subsystem);
+
+        subsystem = strdup(_subsystem);
+        if (!subsystem)
+                return -ENOMEM;
+
+        r = device_add_property_internal(device, "SUBSYSTEM", subsystem);
+        if (r < 0)
+                return r;
+
+        free(device->subsystem);
+        device->subsystem = subsystem;
+        subsystem = NULL;
+
+        device->subsystem_set = true;
+
+        return 0;
+}
+
+_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) {
+        assert_return(ret, -EINVAL);
+        assert_return(device, -EINVAL);
+
+        if (!device->subsystem_set) {
+                _cleanup_free_ char *subsystem = NULL;
+                const char *syspath;
+                char *path;
+                int r;
+
+                /* read 'subsystem' link */
+                r = sd_device_get_syspath(device, &syspath);
+                if (r < 0)
+                        return r;
+
+                path = strjoina(syspath, "/subsystem");
+                r = readlink_value(path, &subsystem);
+                if (r >= 0)
+                        r = device_set_subsystem(device, subsystem);
+                /* use implicit names */
+                else if (path_startswith(device->devpath, "/module/"))
+                        r = device_set_subsystem(device, "module");
+                else if (strstr(device->devpath, "/drivers/"))
+                        r = device_set_subsystem(device, "drivers");
+                else if (path_startswith(device->devpath, "/subsystem/") ||
+                         path_startswith(device->devpath, "/class/") ||
+                         path_startswith(device->devpath, "/buss/"))
+                        r = device_set_subsystem(device, "subsystem");
+                if (r < 0)
+                        return r;
+
+                device->subsystem_set = true;
+        }
+
+        *ret = device->subsystem;
+
+        return 0;
+}
+
+_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) {
+        int r;
+
+        assert(devtype);
+        assert(device);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        *devtype = device->devtype;
+
+        return 0;
+}
+
+_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) {
+        sd_device *parent = NULL;
+        int r;
+
+        assert_return(child, -EINVAL);
+        assert_return(subsystem, -EINVAL);
+
+        r = sd_device_get_parent(child, &parent);
+        while (r >= 0) {
+                const char *parent_subsystem;
+                const char *parent_devtype;
+
+                (void)sd_device_get_subsystem(parent, &parent_subsystem);
+                if (streq_ptr(parent_subsystem, subsystem)) {
+                        if (!devtype)
+                                break;
+
+                        (void)sd_device_get_devtype(parent, &parent_devtype);
+                        if (streq_ptr(parent_devtype, devtype))
+                                break;
+                }
+                r = sd_device_get_parent(parent, &parent);
+        }
+
+        if (r < 0)
+                return r;
+
+        *ret = parent;
+
+        return 0;
+}
+
+_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(devnum, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        *devnum = device->devnum;
+
+        return 0;
+}
+
+int device_set_driver(sd_device *device, const char *_driver) {
+        char *driver;
+        int r;
+
+        assert(device);
+        assert(_driver);
+
+        driver = strdup(_driver);
+        if (!driver)
+                return -ENOMEM;
+
+        r = device_add_property_internal(device, "DRIVER", driver);
+        if (r < 0)
+                return r;
+
+        free(device->driver);
+        device->driver = driver;
+
+        device->driver_set = true;
+
+        return 0;
+}
+
+_public_ int sd_device_get_driver(sd_device *device, const char **ret) {
+        assert_return(device, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!device->driver_set) {
+                _cleanup_free_ char *driver = NULL;
+                const char *syspath;
+                char *path;
+                int r;
+
+                r = sd_device_get_syspath(device, &syspath);
+                if (r < 0)
+                        return r;
+
+                path = strjoina(syspath, "/driver");
+                r = readlink_value(path, &driver);
+                if (r >= 0) {
+                        r = device_set_driver(device, driver);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        *ret = device->driver;
+
+        return 0;
+}
+
+_public_ int sd_device_get_devpath(sd_device *device, const char **devpath) {
+        assert_return(device, -EINVAL);
+        assert_return(devpath, -EINVAL);
+
+        assert(device->devpath);
+        assert(device->devpath[0] == '/');
+
+        *devpath = device->devpath;
+
+        return 0;
+}
+
+_public_ int sd_device_get_devname(sd_device *device, const char **devname) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(devname, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        if (!device->devname)
+                return -ENOENT;
+
+        assert(path_startswith(device->devname, "/dev/"));
+
+        *devname = device->devname;
+
+        return 0;
+}
+
+static int device_set_sysname(sd_device *device) {
+        _cleanup_free_ char *sysname = NULL;
+        const char *sysnum;
+        const char *pos;
+        size_t len = 0;
+
+        pos = strrchr(device->devpath, '/');
+        if (!pos)
+                return -EINVAL;
+        pos ++;
+
+        /* devpath is not a root directory */
+        if (*pos == '\0' || pos <= device->devpath)
+                return -EINVAL;
+
+        sysname = strdup(pos);
+        if (!sysname)
+                return -ENOMEM;
+
+        /* some devices have '!' in their name, change that to '/' */
+        while (sysname[len] != '\0') {
+                if (sysname[len] == '!')
+                        sysname[len] = '/';
+
+                len ++;
+        }
+
+        /* trailing number */
+        while (len > 0 && isdigit(sysname[--len]))
+                sysnum = &sysname[len];
+
+        if (len == 0)
+                sysnum = NULL;
+
+        free(device->sysname);
+        device->sysname = sysname;
+        sysname = NULL;
+
+        device->sysnum = sysnum;
+
+        device->sysname_set = true;
+
+        return 0;
+}
+
+_public_ int sd_device_get_sysname(sd_device *device, const char **ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!device->sysname_set) {
+                r = device_set_sysname(device);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = device->sysname;
+
+        return 0;
+}
+
+_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!device->sysname_set) {
+                r = device_set_sysname(device);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = device->sysnum;
+
+        return 0;
+}
+
+static bool is_valid_tag(const char *tag) {
+        assert(tag);
+
+        return !strchr(tag, ':') && !strchr(tag, ' ');
+}
+
+int device_add_tag(sd_device *device, const char *tag) {
+        int r;
+
+        assert(device);
+        assert(tag);
+
+        if (!is_valid_tag(tag))
+                return -EINVAL;
+
+        r = set_ensure_allocated(&device->tags, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = set_put_strdup(device->tags, tag);
+        if (r < 0)
+                return r;
+
+        device->tags_generation ++;
+        device->property_tags_outdated = true;
+
+        return 0;
+}
+
+int device_add_devlink(sd_device *device, const char *devlink) {
+        int r;
+
+        assert(device);
+        assert(devlink);
+
+        r = set_ensure_allocated(&device->devlinks, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = set_put_strdup(device->devlinks, devlink);
+        if (r < 0)
+                return r;
+
+        device->devlinks_generation ++;
+        device->property_devlinks_outdated = true;
+
+        return 0;
+}
+
+static int device_add_property_internal_from_string(sd_device *device, const char *str) {
+        _cleanup_free_ char *key = NULL;
+        char *value;
+
+        assert(device);
+        assert(str);
+
+        key = strdup(str);
+        if (!key)
+                return -ENOMEM;
+
+        value = strchr(key, '=');
+        if (!value)
+                return -EINVAL;
+
+        *value = '\0';
+
+        if (isempty(++value))
+                value = NULL;
+
+        return device_add_property_internal(device, key, value);
+}
+
+int device_set_usec_initialized(sd_device *device, const char *initialized) {
+        uint64_t usec_initialized;
+        int r;
+
+        assert(device);
+        assert(initialized);
+
+        r = safe_atou64(initialized, &usec_initialized);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(device, "USEC_INITIALIZED", initialized);
+        if (r < 0)
+                return r;
+
+        device->usec_initialized = usec_initialized;
+
+        return 0;
+}
+
+static int handle_db_line(sd_device *device, char key, const char *value) {
+        char *path;
+        int r;
+
+        assert(device);
+        assert(value);
+
+        switch (key) {
+        case 'G':
+                r = device_add_tag(device, value);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'S':
+                path = strjoina("/dev/", value);
+                r = device_add_devlink(device, path);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'E':
+                r = device_add_property_internal_from_string(device, value);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'I':
+                r = device_set_usec_initialized(device, value);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'L':
+                r = safe_atoi(value, &device->devlink_priority);
+                if (r < 0)
+                        return r;
+
+                break;
+        case 'W':
+                r = safe_atoi(value, &device->watch_handle);
+                if (r < 0)
+                        return r;
+
+                break;
+        default:
+                log_debug("device db: unknown key '%c'", key);
+        }
+
+        return 0;
+}
+
+int device_get_id_filename(sd_device *device, const char **ret) {
+        assert(device);
+        assert(ret);
+
+        if (!device->id_filename) {
+                _cleanup_free_ char *id = NULL;
+                const char *subsystem;
+                dev_t devnum;
+                int ifindex, r;
+
+                r = sd_device_get_subsystem(device, &subsystem);
+                if (r < 0)
+                        return r;
+
+                r = sd_device_get_devnum(device, &devnum);
+                if (r < 0)
+                        return r;
+
+                r = sd_device_get_ifindex(device, &ifindex);
+                if (r < 0)
+                        return r;
+
+                if (major(devnum) > 0) {
+                        /* use dev_t -- b259:131072, c254:0 */
+                        r = asprintf(&id, "%c%u:%u",
+                                     streq(subsystem, "block") ? 'b' : 'c',
+                                     major(devnum), minor(devnum));
+                        if (r < 0)
+                                return -errno;
+                } else if (ifindex > 0) {
+                        /* use netdev ifindex -- n3 */
+                        r = asprintf(&id, "n%u", ifindex);
+                        if (r < 0)
+                                return -errno;
+                } else {
+                        /* use $subsys:$sysname -- pci:0000:00:1f.2
+                         * sysname() has '!' translated, get it from devpath
+                         */
+                        const char *sysname;
+
+                        sysname = basename(device->devpath);
+                        if (!sysname)
+                                return -EINVAL;
+
+                        r = asprintf(&id, "+%s:%s", subsystem, sysname);
+                        if (r < 0)
+                                return -errno;
+                }
+
+                device->id_filename = id;
+                id = NULL;
+        }
+
+        *ret = device->id_filename;
+
+        return 0;
+}
+
+static int device_read_db(sd_device *device) {
+        _cleanup_free_ char *db = NULL;
+        char *path;
+        const char *id, *value;
+        char key;
+        size_t db_len;
+        unsigned i;
+        int r;
+
+        enum {
+                PRE_KEY,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                INVALID_LINE,
+        } state = PRE_KEY;
+
+        if (device->db_loaded || device->sealed)
+                return 0;
+
+        r = device_get_id_filename(device, &id);
+        if (r < 0)
+                return r;
+
+        path = strjoina("/run/udev/data/", id);
+
+        r = read_full_file(path, &db, &db_len);
+        if (r < 0) {
+                if (r == -ENOENT)
+                        return 0;
+                else {
+                        log_debug("sd-device: failed to read db '%s': %s", path, strerror(-r));
+                        return r;
+                }
+        }
+
+        /* devices with a database entry are initialized */
+        device->is_initialized = true;;
+
+        for (i = 0; i < db_len; i++) {
+                switch (state) {
+                case PRE_KEY:
+                        if (!strchr(NEWLINE, db[i])) {
+                                key = db[i];
+
+                                state = KEY;
+                        }
+
+                        break;
+                case KEY:
+                        if (db[i] != ':') {
+                                log_debug("sd-device: ignoring invalid db entry with key '%c'", key);
+
+                                state = INVALID_LINE;
+                        } else {
+                                db[i] = '\0';
+
+                                state = PRE_VALUE;
+                        }
+
+                        break;
+                case PRE_VALUE:
+                        value = &db[i];
+
+                        state = VALUE;
+
+                        break;
+                case INVALID_LINE:
+                        if (strchr(NEWLINE, db[i]))
+                                state = PRE_KEY;
+
+                        break;
+                case VALUE:
+                        if (strchr(NEWLINE, db[i])) {
+                                db[i] = '\0';
+                                r = handle_db_line(device, key, value);
+                                if (r < 0)
+                                        log_debug("sd-device: failed to handle db entry '%c:%s': %s", key, value, strerror(-r));
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                default:
+                        assert_not_reached("invalid state when parsing db");
+                }
+        }
+
+        device->db_loaded = true;
+
+        return 0;
+}
+
+_public_ int sd_device_get_is_initialized(sd_device *device, int *initialized) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(initialized, -EINVAL);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        *initialized = device->is_initialized;
+
+        return 0;
+}
+
+_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) {
+        usec_t now_ts;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(usec, -EINVAL);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (!device->is_initialized)
+                return -EBUSY;
+
+        if (!device->usec_initialized)
+                return -ENODATA;
+
+        now_ts = now(clock_boottime_or_monotonic());
+
+        if (now_ts < device->usec_initialized)
+                return -EIO;
+
+        *usec = now_ts - device->usec_initialized;
+
+        return 0;
+}
+
+_public_ const char *sd_device_get_tag_first(sd_device *device) {
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        device->tags_iterator_generation = device->tags_generation;
+        device->tags_iterator = ITERATOR_FIRST;
+
+        return set_iterate(device->tags, &device->tags_iterator);
+}
+
+_public_ const char *sd_device_get_tag_next(sd_device *device) {
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        if (device->tags_iterator_generation != device->tags_generation)
+                return NULL;
+
+        return set_iterate(device->tags, &device->tags_iterator);
+}
+
+_public_ const char *sd_device_get_devlink_first(sd_device *device) {
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        device->devlinks_iterator_generation = device->devlinks_generation;
+        device->devlinks_iterator = ITERATOR_FIRST;
+
+        return set_iterate(device->devlinks, &device->devlinks_iterator);
+}
+
+_public_ const char *sd_device_get_devlink_next(sd_device *device) {
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        if (device->devlinks_iterator_generation != device->devlinks_generation)
+                return NULL;
+
+        return set_iterate(device->devlinks, &device->devlinks_iterator);
+}
+
+static int device_properties_prepare(sd_device *device) {
+        int r;
+
+        assert(device);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (device->property_devlinks_outdated) {
+                char *devlinks = NULL;
+                const char *devlink;
+
+                devlink = sd_device_get_devlink_first(device);
+                if (devlink)
+                        devlinks = strdupa(devlink);
+
+                while ((devlink = sd_device_get_devlink_next(device)))
+                        devlinks = strjoina(devlinks, " ", devlink);
+
+                r = device_add_property_internal(device, "DEVLINKS", devlinks);
+                if (r < 0)
+                        return r;
+
+                device->property_devlinks_outdated = false;
+        }
+
+        if (device->property_tags_outdated) {
+                char *tags = NULL;
+                const char *tag;
+
+                tag = sd_device_get_tag_first(device);
+                if (tag)
+                        tags = strjoina(":", tag);
+
+                while ((tag = sd_device_get_tag_next(device)))
+                        tags = strjoina(tags, ":", tag);
+
+                tags = strjoina(tags, ":");
+
+                r = device_add_property_internal(device, "TAGS", tags);
+                if (r < 0)
+                        return r;
+
+                device->property_tags_outdated = false;
+        }
+
+        return 0;
+}
+
+_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) {
+        const char *key;
+        const char *value;
+        int r;
+
+        assert_return(device, NULL);
+
+        r = device_properties_prepare(device);
+        if (r < 0)
+                return NULL;
+
+        device->properties_iterator_generation = device->properties_generation;
+        device->properties_iterator = ITERATOR_FIRST;
+
+        value = ordered_hashmap_iterate(device->properties, &device->properties_iterator, (const void**)&key);
+
+        if (_value)
+                *_value = value;
+
+        return key;
+}
+
+_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) {
+        const char *key;
+        const char *value;
+        int r;
+
+        assert_return(device, NULL);
+
+        r = device_properties_prepare(device);
+        if (r < 0)
+                return NULL;
+
+        if (device->properties_iterator_generation != device->properties_generation)
+                return NULL;
+
+        value = ordered_hashmap_iterate(device->properties, &device->properties_iterator, (const void**)&key);
+
+        if (_value)
+                *_value = value;
+
+        return key;
+}
+
+static int device_sysattrs_read_all(sd_device *device) {
+        _cleanup_closedir_ DIR *dir = NULL;
+        const char *syspath;
+        struct dirent *dent;
+        int r;
+
+        assert(device);
+
+        if (device->sysattrs_read)
+                return 0;
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        dir = opendir(syspath);
+        if (!dir)
+                return -errno;
+
+        r = set_ensure_allocated(&device->sysattrs, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+                _cleanup_free_ char *sysattr = NULL;
+                char *path;
+                struct stat statbuf;
+
+                /* only handle symlinks and regular files */
+                if (dent->d_type != DT_LNK && dent->d_type != DT_REG)
+                        continue;
+
+                path = strjoina(syspath, "/", dent->d_name);
+
+                if (lstat(path, &statbuf) != 0)
+                        continue;
+
+                if (!(statbuf.st_mode & S_IRUSR))
+                        continue;
+
+                r = set_put_strdup(device->sysattrs, dent->d_name);
+                if (r < 0)
+                        return r;
+        }
+
+        device->sysattrs_read = true;
+
+        return 0;
+}
+
+_public_ const char *sd_device_get_sysattr_first(sd_device *device) {
+        int r;
+
+        assert_return(device, NULL);
+
+        if (!device->sysattrs_read) {
+                r = device_sysattrs_read_all(device);
+                if (r < 0) {
+                        errno = -r;
+                        return NULL;
+                }
+        }
+
+        device->sysattrs_iterator = ITERATOR_FIRST;
+
+        return set_iterate(device->sysattrs, &device->sysattrs_iterator);
+}
+
+_public_ const char *sd_device_get_sysattr_next(sd_device *device) {
+        assert_return(device, NULL);
+
+        if (!device->sysattrs_read)
+                return NULL;
+
+        return set_iterate(device->sysattrs, &device->sysattrs_iterator);
+}
+
+_public_ int sd_device_has_tag(sd_device *device, const char *tag) {
+        assert_return(device, -EINVAL);
+        assert_return(tag, -EINVAL);
+
+        (void) device_read_db(device);
+
+        return !!set_contains(device->tags, tag);
+}
+
+_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) {
+        char *value;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(key, -EINVAL);
+        assert_return(_value, -EINVAL);
+
+        r = device_properties_prepare(device);
+        if (r < 0)
+                return r;
+
+        value = ordered_hashmap_get(device->properties, key);
+        if (!value)
+                return -ENOENT;
+
+        *_value = value;
+
+        return 0;
+}
+
+/* replaces the value if it already exists */
+static int device_add_sysattr_value(sd_device *device, const char *_key, const char *_value) {
+        _cleanup_free_ char *key = NULL;
+        _cleanup_free_ char *value = NULL;
+        int r;
+
+        assert(device);
+        assert(_key);
+
+        r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        value = hashmap_remove2(device->sysattr_values, _key, (void **)&key);
+        if (!key) {
+                key = strdup(_key);
+                if (!key)
+                        return -ENOMEM;
+        }
+
+        free(value);
+        value = NULL;
+
+        if (_value) {
+                value = strdup(_value);
+                if (!value)
+                        return -ENOMEM;
+        }
+
+        r = hashmap_put(device->sysattr_values, key, value);
+        if (r < 0)
+                return r;
+
+        key = NULL;
+        value = NULL;
+
+        return 0;
+}
+
+static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) {
+        const char *key = NULL, *value;
+
+        assert(device);
+        assert(_key);
+
+        value = hashmap_get2(device->sysattr_values, _key, (void **) &key);
+        if (!key)
+                return -ENOENT;
+
+        if (_value)
+                *_value = value;
+
+        return 0;
+}
+
+/* We cache all sysattr lookups. If an attribute does not exist, it is stored
+ * with a NULL value in the cache, otherwise the returned string is stored */
+_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value) {
+        _cleanup_free_ char *value = NULL;
+        const char *syspath, *cached_value = NULL;
+        char *path;
+        struct stat statbuf;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(sysattr, -EINVAL);
+
+        /* look for possibly already cached result */
+        r = device_get_sysattr_value(device, sysattr, &cached_value);
+        if (r != -ENOENT) {
+                if (r < 0)
+                        return r;
+
+                if (!cached_value)
+                        /* we looked up the sysattr before and it did not exist */
+                        return -ENOENT;
+
+                if (_value)
+                        *_value = cached_value;
+
+                return 0;
+        }
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        path = strjoina(syspath, "/", sysattr);
+        r = lstat(path, &statbuf);
+        if (r < 0) {
+                /* remember that we could not access the sysattr */
+                r = device_add_sysattr_value(device, sysattr, NULL);
+                if (r < 0)
+                        return r;
+
+                return -ENOENT;
+        } else if (S_ISLNK(statbuf.st_mode)) {
+                /* Some core links return only the last element of the target path,
+                 * these are just values, the paths should not be exposed. */
+                if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) {
+                        r = readlink_value(path, &value);
+                        if (r < 0)
+                                return r;
+                } else
+                        return -EINVAL;
+        } else if (S_ISDIR(statbuf.st_mode)) {
+                /* skip directories */
+                return -EINVAL;
+        } else if (!(statbuf.st_mode & S_IRUSR)) {
+                /* skip non-readable files */
+                return -EPERM;
+        } else {
+                size_t size;
+
+                /* read attribute value */
+                r = read_full_file(path, &value, &size);
+                if (r < 0)
+                        return r;
+
+                /* drop trailing newlines */
+                while (size > 0 && value[--size] == '\n')
+                        value[size] = '\0';
+        }
+
+        r = device_add_sysattr_value(device, sysattr, value);
+        if (r < 0)
+                return r;
+
+        *_value = value;
+        value = NULL;
+
+        return 0;
+}
+
+static void device_remove_sysattr_value(sd_device *device, const char *_key) {
+        _cleanup_free_ char *key = NULL;
+        _cleanup_free_ char *value = NULL;
+
+        assert(device);
+        assert(_key);
+
+        value = hashmap_remove2(device->sysattr_values, _key, (void **) &key);
+
+        return;
+}
+
+/* set the attribute and save it in the cache. If a NULL value is passed the
+ * attribute is cleared from the cache */
+_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *value) {
+        _cleanup_close_ int fd = -1;
+        const char *syspath;
+        char *path;
+        struct stat statbuf;
+        size_t value_len = 0;
+        ssize_t size;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(sysattr, -EINVAL);
+
+        if (!value) {
+                device_remove_sysattr_value(device, sysattr);
+
+                return 0;
+        }
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        path = strjoina(syspath, "/", sysattr);
+        r = lstat(path, &statbuf);
+        if (r < 0) {
+                r = device_add_sysattr_value(device, sysattr, "");
+                if (r < 0)
+                        return r;
+
+                return -ENXIO;
+        }
+
+        if (S_ISLNK(statbuf.st_mode))
+                return -EINVAL;
+
+        /* skip directories */
+        if (S_ISDIR(statbuf.st_mode))
+                return -EISDIR;
+
+        /* skip non-readable files */
+        if ((statbuf.st_mode & S_IRUSR) == 0)
+                return -EACCES;
+
+        value_len = strlen(value);
+
+        /* drop trailing newlines */
+        while (value_len > 0 && value[--value_len] == '\n')
+                value[value_len] = '\0';
+
+        /* value length is limited to 4k */
+        if (value_len > 4096)
+                return -EINVAL;
+
+        fd = open(path, O_WRONLY | O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        size = write(fd, value, value_len);
+        if (size < 0)
+                return -errno;
+
+        if ((size_t)size != value_len)
+                return -EIO;
+
+        r = device_add_sysattr_value(device, sysattr, value);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h
new file mode 100644
index 0000000..d737753
--- /dev/null
+++ b/src/systemd/sd-device.h
@@ -0,0 +1,77 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddevicehfoo
+#define foosddevicehfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2008-2012 Kay Sievers <kay at vrfy.org>
+  Copyright 2014 Tom Gundersen <teg at jklm.no>
+
+  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 <sys/types.h>
+#include <stdint.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_device sd_device;
+
+sd_device *sd_device_ref(sd_device *device);
+sd_device *sd_device_unref(sd_device *device);
+
+int sd_device_new_from_syspath(sd_device **ret, const char *syspath);
+int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum);
+int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname);
+int sd_device_new_from_device_id(sd_device **ret, const char *id);
+
+int sd_device_get_parent(sd_device *child, sd_device **ret);
+int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret);
+
+int sd_device_get_syspath(sd_device *device, const char **ret);
+int sd_device_get_subsystem(sd_device *device, const char **ret);
+int sd_device_get_devtype(sd_device *device, const char **ret);
+int sd_device_get_devnum(sd_device *device, dev_t *devnum);
+int sd_device_get_ifindex(sd_device *device, int *ifindex);
+int sd_device_get_driver(sd_device *device, const char **ret);
+int sd_device_get_devpath(sd_device *device, const char **ret);
+int sd_device_get_devname(sd_device *device, const char **ret);
+int sd_device_get_sysname(sd_device *device, const char **ret);
+int sd_device_get_sysnum(sd_device *device, const char **ret);
+
+int sd_device_get_is_initialized(sd_device *device, int *initialized);
+int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec);
+
+const char *sd_device_get_tag_first(sd_device *device);
+const char *sd_device_get_tag_next(sd_device *device);
+const char *sd_device_get_devlink_first(sd_device *device);
+const char *sd_device_get_devlink_next(sd_device *device);
+const char *sd_device_get_property_first(sd_device *device, const char **value);
+const char *sd_device_get_property_next(sd_device *device, const char **value);
+const char *sd_device_get_sysattr_first(sd_device *device);
+const char *sd_device_get_sysattr_next(sd_device *device);
+
+int sd_device_has_tag(sd_device *device, const char *tag);
+int sd_device_get_property_value(sd_device *device, const char *key, const char **value);
+int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value);
+
+int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *value);
+
+_SD_END_DECLARATIONS;
+
+#endif
-- 
2.3.4



More information about the systemd-devel mailing list