]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/udev/udev-node.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / udev / udev-node.c
index 79d050cce68551bf157915740c2c66dbf192d62d..1990282c75427e6678836f6485f8f7a2a1b3d6c2 100644 (file)
@@ -1,11 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include <errno.h>
-#include <fcntl.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include <sys/file.h>
 
 #include "sd-id128.h"
 
 #include "format-util.h"
 #include "fs-util.h"
 #include "hexdecoct.h"
+#include "label.h"
 #include "mkdir-label.h"
 #include "parse-util.h"
 #include "path-util.h"
-#include "random-util.h"
 #include "selinux-util.h"
 #include "smack-util.h"
 #include "stat-util.h"
-#include "stdio-util.h"
 #include "string-util.h"
-#include "strxcpyx.h"
-#include "time-util.h"
 #include "udev-node.h"
 #include "user-util.h"
 
-#define CREATE_LINK_MAX_RETRIES        128
-#define LINK_UPDATE_MAX_RETRIES        128
-#define CREATE_STACK_LINK_MAX_RETRIES  128
-#define UPDATE_TIMESTAMP_MAX_RETRIES   128
-#define MAX_RANDOM_DELAY (250 * USEC_PER_MSEC)
-#define MIN_RANDOM_DELAY ( 50 * USEC_PER_MSEC)
 #define UDEV_NODE_HASH_KEY SD_ID128_MAKE(b9,6a,f1,ce,40,31,44,1a,9e,19,ec,8b,ae,f3,e3,2f)
 
-static int create_symlink(const char *target, const char *slink) {
-        int r;
+int udev_node_cleanup(void) {
+        _cleanup_closedir_ DIR *dir = NULL;
 
-        assert(target);
-        assert(slink);
+        /* This must not be called when any workers exist. It would cause a race between mkdir() called
+         * by stack_directory_lock() and unlinkat() called by this. */
+
+        dir = opendir("/run/udev/links");
+        if (!dir) {
+                if (errno == ENOENT)
+                        return 0;
 
-        for (unsigned i = 0; i < CREATE_LINK_MAX_RETRIES; i++) {
-                r = mkdir_parents_label(slink, 0755);
-                if (r == -ENOENT)
+                return log_debug_errno(errno, "Failed to open directory '/run/udev/links', ignoring: %m");
+        }
+
+        FOREACH_DIRENT_ALL(de, dir, break) {
+                _cleanup_free_ char *lockfile = NULL;
+
+                if (de->d_name[0] == '.')
                         continue;
-                if (r < 0)
-                        return r;
 
-                mac_selinux_create_file_prepare(slink, S_IFLNK);
-                r = RET_NERRNO(symlink(target, slink));
-                mac_selinux_create_file_clear();
-                if (r != -ENOENT)
-                        return r;
+                if (de->d_type != DT_DIR)
+                        continue;
+
+                /* As commented in the above, this is called when no worker exists, hence the file is not
+                 * locked. On a later uevent, the lock file will be created if necessary. So, we can safely
+                 * remove the file now. */
+                lockfile = path_join(de->d_name, ".lock");
+                if (!lockfile)
+                        return log_oom_debug();
+
+                if (unlinkat(dirfd(dir), lockfile, 0) < 0 && errno != ENOENT) {
+                        log_debug_errno(errno, "Failed to remove '/run/udev/links/%s', ignoring: %m", lockfile);
+                        continue;
+                }
+
+                if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0 && errno != ENOTEMPTY)
+                        log_debug_errno(errno, "Failed to remove '/run/udev/links/%s', ignoring: %m", de->d_name);
         }
 
-        return r;
+        return 0;
 }
 
-static int node_symlink(sd_device *dev, const char *node, const char *slink) {
-        _cleanup_free_ char *slink_dirname = NULL, *target = NULL;
-        const char *id, *slink_tmp;
-        struct stat stats;
+static int node_symlink(sd_device *dev, const char *devnode, const char *slink) {
+        struct stat st;
         int r;
 
         assert(dev);
-        assert(node);
         assert(slink);
 
-        if (lstat(slink, &stats) >= 0) {
-                if (!S_ISLNK(stats.st_mode))
+        if (!devnode) {
+                r = sd_device_get_devname(dev, &devnode);
+                if (r < 0)
+                        return log_device_debug_errno(dev, r, "Failed to get device node: %m");
+        }
+
+        if (lstat(slink, &st) >= 0) {
+                if (!S_ISLNK(st.st_mode))
                         return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST),
-                                                      "Conflicting inode '%s' found, link to '%s' will not be created.", slink, node);
+                                                      "Conflicting inode '%s' found, symlink to '%s' will not be created.",
+                                                      slink, devnode);
         } else if (errno != ENOENT)
                 return log_device_debug_errno(dev, errno, "Failed to lstat() '%s': %m", slink);
 
-        r = path_extract_directory(slink, &slink_dirname);
+        r = mkdir_parents_label(slink, 0755);
         if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get parent directory of '%s': %m", slink);
+                return log_device_debug_errno(dev, r, "Failed to create parent directory of '%s': %m", slink);
 
         /* use relative link */
-        r = path_make_relative(slink_dirname, node, &target);
+        r = symlink_atomic_full_label(devnode, slink, /* make_relative = */ true);
         if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get relative path from '%s' to '%s': %m", slink, node);
+                return log_device_debug_errno(dev, r, "Failed to create symlink '%s' to '%s': %m", slink, devnode);
 
-        r = device_get_device_id(dev, &id);
-        if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get device id: %m");
+        log_device_debug(dev, "Successfully created symlink '%s' to '%s'", slink, devnode);
+        return 0;
+}
 
-        slink_tmp = strjoina(slink, ".tmp-", id);
-        (void) unlink(slink_tmp);
+static int stack_directory_read_one(int dirfd, const char *id, bool is_symlink, char **devnode, int *priority) {
+        int tmp_prio, r;
 
-        r = create_symlink(target, slink_tmp);
-        if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to create symlink '%s' to '%s': %m", slink_tmp, target);
+        assert(dirfd >= 0);
+        assert(id);
+        assert(devnode);
+        assert(priority);
 
-        if (rename(slink_tmp, slink) < 0) {
-                r = log_device_debug_errno(dev, errno, "Failed to rename '%s' to '%s': %m", slink_tmp, slink);
-                (void) unlink(slink_tmp);
-                return r;
+        if (is_symlink) {
+                _cleanup_free_ char *buf = NULL;
+                char *colon;
+
+                /* New format. The devnode and priority can be obtained from symlink. */
+
+                r = readlinkat_malloc(dirfd, id, &buf);
+                if (r < 0)
+                        return r;
+
+                colon = strchr(buf, ':');
+                if (!colon || colon == buf)
+                        return -EINVAL;
+
+                *colon = '\0';
+
+                /* Of course, this check is racy, but it is not necessary to be perfect. Even if the device
+                 * node will be removed after this check, we will receive 'remove' uevent, and the invalid
+                 * symlink will be removed during processing the event. The check is just for shortening the
+                 * timespan that the symlink points to a non-existing device node. */
+                if (access(colon + 1, F_OK) < 0)
+                        return -errno;
+
+                r = safe_atoi(buf, &tmp_prio);
+                if (r < 0)
+                        return r;
+
+                if (*devnode && tmp_prio <= *priority)
+                        return 0; /* Unchanged */
+
+                r = free_and_strdup(devnode, colon + 1);
+                if (r < 0)
+                        return r;
+
+        } else {
+                _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+                const char *val;
+
+                /* Old format. The devnode and priority must be obtained from uevent and udev database. */
+
+                r = sd_device_new_from_device_id(&dev, id);
+                if (r < 0)
+                        return r;
+
+                r = device_get_devlink_priority(dev, &tmp_prio);
+                if (r < 0)
+                        return r;
+
+                if (*devnode && tmp_prio <= *priority)
+                        return 0; /* Unchanged */
+
+                r = sd_device_get_devname(dev, &val);
+                if (r < 0)
+                        return r;
+
+                r = free_and_strdup(devnode, val);
+                if (r < 0)
+                        return r;
         }
 
-        return 0;
+        *priority = tmp_prio;
+        return 1; /* Updated */
 }
 
-static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir, char **ret) {
+static int stack_directory_find_prioritized_devnode(sd_device *dev, const char *dirname, bool add, char **ret) {
         _cleanup_closedir_ DIR *dir = NULL;
-        _cleanup_free_ char *target = NULL;
+        _cleanup_free_ char *devnode = NULL;
         int r, priority = 0;
         const char *id;
 
         assert(dev);
-        assert(stackdir);
+        assert(dirname);
         assert(ret);
 
         /* Find device node of device with highest priority. This returns 1 if a device found, 0 if no
-         * device found, or a negative errno. */
+         * device found, or a negative errno on error. */
 
         if (add) {
-                const char *devnode;
+                const char *n;
 
                 r = device_get_devlink_priority(dev, &priority);
                 if (r < 0)
                         return r;
 
-                r = sd_device_get_devname(dev, &devnode);
+                r = sd_device_get_devname(dev, &n);
                 if (r < 0)
                         return r;
 
-                target = strdup(devnode);
-                if (!target)
+                devnode = strdup(n);
+                if (!devnode)
                         return -ENOMEM;
         }
 
-        dir = opendir(stackdir);
-        if (!dir) {
-                if (add) /* The stack directory must exist. */
-                        return -errno;
-                if (errno != ENOENT)
-                        return -errno;
-
-                *ret = NULL;
-                return 0;
-        }
+        dir = opendir(dirname);
+        if (!dir)
+                return -errno;
 
         r = device_get_device_id(dev, &id);
         if (r < 0)
                 return r;
 
         FOREACH_DIRENT_ALL(de, dir, break) {
-                _cleanup_free_ char *path = NULL, *buf = NULL;
-                int tmp_prio;
-
                 if (de->d_name[0] == '.')
                         continue;
 
@@ -165,211 +220,96 @@ static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir,
                 if (streq(de->d_name, id))
                         continue;
 
-                path = path_join(stackdir, de->d_name);
-                if (!path)
-                        return -ENOMEM;
-
-                if (readlink_malloc(path, &buf) >= 0) {
-                        char *devnode;
-
-                        /* New format. The devnode and priority can be obtained from symlink. */
-
-                        devnode = strchr(buf, ':');
-                        if (!devnode || devnode == buf)
-                                continue;
-
-                        *(devnode++) = '\0';
-                        if (!path_startswith(devnode, "/dev"))
-                                continue;
-
-                        if (safe_atoi(buf, &tmp_prio) < 0)
-                                continue;
-
-                        if (target && tmp_prio <= priority)
-                                continue;
-
-                        r = free_and_strdup(&target, devnode);
-                        if (r < 0)
-                                return r;
-                } else {
-                        _cleanup_(sd_device_unrefp) sd_device *tmp_dev = NULL;
-                        const char *devnode;
-
-                        /* Old format. The devnode and priority must be obtained from uevent and
-                         * udev database files. */
-
-                        if (sd_device_new_from_device_id(&tmp_dev, de->d_name) < 0)
-                                continue;
-
-                        if (device_get_devlink_priority(tmp_dev, &tmp_prio) < 0)
-                                continue;
-
-                        if (target && tmp_prio <= priority)
-                                continue;
-
-                        if (sd_device_get_devname(tmp_dev, &devnode) < 0)
-                                continue;
+                if (!IN_SET(de->d_type, DT_LNK, DT_REG))
+                        continue;
 
-                        r = free_and_strdup(&target, devnode);
-                        if (r < 0)
-                                return r;
+                r = stack_directory_read_one(dirfd(dir), de->d_name, /* is_symlink = */ de->d_type == DT_LNK, &devnode, &priority);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to read '%s/%s', ignoring: %m", dirname, de->d_name);
+                        continue;
                 }
-
-                priority = tmp_prio;
         }
 
-        *ret = TAKE_PTR(target);
+        *ret = TAKE_PTR(devnode);
         return !!*ret;
 }
 
-static int update_timestamp(sd_device *dev, const char *path, struct stat *prev) {
-        assert(path);
-        assert(prev);
-
-        /* Even if a symlink in the stack directory is created/removed, the mtime of the directory may
-         * not be changed. Why? Let's consider the following situation. For simplicity, let's assume
-         * there exist two udev workers (A and B) and all of them calls link_update() for the same
-         * devlink simultaneously.
-         *
-         * 1. A creates/removes a symlink in the stack directory.
-         * 2. A calls the first stat() in the loop of link_update().
-         * 3. A calls link_find_prioritized().
-         * 4. B creates/removes another symlink in the stack directory, so the result of the step 3 is outdated.
-         * 5. B finishes link_update().
-         * 6. A creates/removes devlink according to the outdated result in the step 3.
-         * 7. A calls the second stat() in the loop of link_update().
-         *
-         * If these 7 steps are processed in this order within a short time period that kernel's timer
-         * does not increase, then even if the contents in the stack directory is changed, the results
-         * of two stat() called by A shows the same timestamp, and A cannot detect the change.
-         *
-         * By calling this function after creating/removing symlinks in the stack directory, the
-         * timestamp of the stack directory is always increased at least in the above step 5, so A can
-         * detect the update. */
-
-        if ((prev->st_mode & S_IFMT) == 0)
-                return 0; /* Does not exist, or previous stat() failed. */
-
-        for (unsigned i = 0; i < UPDATE_TIMESTAMP_MAX_RETRIES; i++) {
-                struct stat st;
-
-                if (stat(path, &st) < 0)
-                        return -errno;
-
-                if (!stat_inode_unmodified(prev, &st))
-                        return 0;
-
-                log_device_debug(dev,
-                                 "%s is modified, but its timestamp is not changed, "
-                                 "updating timestamp after 10ms.",
-                                 path);
-
-                (void) usleep(10 * USEC_PER_MSEC);
-                if (utimensat(AT_FDCWD, path, NULL, 0) < 0)
-                        return -errno;
-        }
-
-        return -ELOOP;
-}
-
-static int update_stack_directory(sd_device *dev, const char *dirname, bool add) {
-        _cleanup_free_ char *filename = NULL, *data = NULL, *buf = NULL;
-        const char *devname, *id;
-        struct stat st = {};
-        int priority, r;
+static int stack_directory_update(sd_device *dev, int fd, bool add) {
+        const char *id;
+        int r;
 
         assert(dev);
-        assert(dirname);
+        assert(fd >= 0);
 
         r = device_get_device_id(dev, &id);
         if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get device id: %m");
-
-        filename = path_join(dirname, id);
-        if (!filename)
-                return log_oom_debug();
-
-        if (!add) {
-                int unlink_error = 0, stat_error = 0;
+                return r;
 
-                if (stat(dirname, &st) < 0) {
-                        if (errno == ENOENT)
-                                return 0; /* The stack directory is already removed. That's OK. */
-                        stat_error = -errno;
-                }
+        if (add) {
+                _cleanup_free_ char *data = NULL, *buf = NULL;
+                const char *devname;
+                int priority;
 
-                if (unlink(filename) < 0)
-                        unlink_error = -errno;
+                r = sd_device_get_devname(dev, &devname);
+                if (r < 0)
+                        return r;
 
-                if (rmdir(dirname) >= 0 || errno == ENOENT)
-                        return 0;
+                r = device_get_devlink_priority(dev, &priority);
+                if (r < 0)
+                        return r;
 
-                if (unlink_error < 0) {
-                        if (unlink_error == -ENOENT)
-                                return 0;
+                if (asprintf(&data, "%i:%s", priority, devname) < 0)
+                        return -ENOMEM;
 
-                        /* If we failed to remove the symlink, then there is almost nothing we can do. */
-                        return log_device_debug_errno(dev, unlink_error, "Failed to remove %s: %m", filename);
-                }
+                if (readlinkat_malloc(fd, id, &buf) >= 0 && streq(buf, data))
+                        return 0; /* Unchanged. */
 
-                if (stat_error < 0)
-                        return log_device_debug_errno(dev, stat_error, "Failed to stat %s: %m", dirname);
+                (void) unlinkat(fd, id, 0);
 
-                /* The symlink was removed. Check if the timestamp of directory is changed. */
-                r = update_timestamp(dev, dirname, &st);
-                if (r < 0 && r != -ENOENT)
-                        return log_device_debug_errno(dev, r, "Failed to update timestamp of %s: %m", dirname);
+                if (symlinkat(data, fd, id) < 0)
+                        return -errno;
 
-                return 0;
+        } else {
+                if (unlinkat(fd, id, 0) < 0) {
+                        if (errno == ENOENT)
+                                return 0; /* Unchanged. */
+                        return -errno;
+                }
         }
 
-        r = sd_device_get_devname(dev, &devname);
-        if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get device node: %m");
+        return 1; /* Updated. */
+}
 
-        r = device_get_devlink_priority(dev, &priority);
-        if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get priority of device node symlink: %m");
+static int stack_directory_open(const char *dirname) {
+        _cleanup_close_ int fd = -EBADF;
+        int r;
 
-        if (asprintf(&data, "%i:%s", priority, devname) < 0)
-                return log_oom_debug();
+        assert(dirname);
 
-        if (readlink_malloc(filename, &buf) >= 0 && streq(buf, data))
-                return 0;
+        r = mkdir_parents(dirname, 0755);
+        if (r < 0)
+                return r;
 
-        if (unlink(filename) < 0 && errno != ENOENT)
-                log_device_debug_errno(dev, errno, "Failed to remove %s, ignoring: %m", filename);
+        fd = open_mkdir_at(AT_FDCWD, dirname, O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW | O_RDONLY, 0755);
+        if (fd < 0)
+                return fd;
 
-        for (unsigned j = 0; j < CREATE_STACK_LINK_MAX_RETRIES; j++) {
-                /* This may fail with -ENOENT when the parent directory is removed during
-                 * creating the file by another udevd worker. */
-                r = mkdir_p(dirname, 0755);
-                if (r == -ENOENT)
-                        continue;
-                if (r < 0)
-                        return log_device_debug_errno(dev, r, "Failed to create directory %s: %m", dirname);
+        return TAKE_FD(fd);
+}
 
-                if (stat(dirname, &st) < 0) {
-                        if (errno == ENOENT)
-                                continue;
-                        return log_device_debug_errno(dev, errno, "Failed to stat %s: %m", dirname);
-                }
+static int stack_directory_lock(int dirfd) {
+        _cleanup_close_ int fd = -EBADF;
 
-                if (symlink(data, filename) < 0) {
-                        if (errno == ENOENT)
-                                continue;
-                        return log_device_debug_errno(dev, errno, "Failed to create symbolic link %s: %m", filename);
-                }
+        assert(dirfd >= 0);
 
-                /* The symlink was created. Check if the timestamp of directory is changed. */
-                r = update_timestamp(dev, dirname, &st);
-                if (r < 0)
-                        return log_device_debug_errno(dev, r, "Failed to update timestamp of %s: %m", dirname);
+        fd = openat(dirfd, ".lock", O_CLOEXEC | O_NOFOLLOW | O_RDONLY | O_CREAT, 0600);
+        if (fd < 0)
+                return -errno;
 
-                return 0;
-        }
+        if (flock(fd, LOCK_EX) < 0)
+                return -errno;
 
-        return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ELOOP), "Failed to create symbolic link %s: %m", filename);
+        return TAKE_FD(fd);
 }
 
 size_t udev_node_escape_path(const char *src, char *dest, size_t size) {
@@ -414,79 +354,75 @@ toolong:
         return size - 1;
 }
 
-/* manage "stack of names" with possibly specified device priorities */
-static int link_update(sd_device *dev, const char *slink_in, bool add) {
-        _cleanup_free_ char *slink = NULL, *dirname = NULL;
-        const char *slink_name;
+static int stack_directory_get_name(const char *slink, char **ret) {
+        _cleanup_free_ char *s = NULL, *dirname = NULL;
         char name_enc[NAME_MAX+1];
-        int r;
+        const char *name;
 
-        assert(dev);
-        assert(slink_in);
+        assert(slink);
+        assert(ret);
+
+        s = strdup(slink);
+        if (!s)
+                return -ENOMEM;
 
-        slink = strdup(slink_in);
-        if (!slink)
-                return log_oom_debug();
+        path_simplify(s);
 
-        path_simplify(slink);
+        if (!path_is_normalized(s))
+                return -EINVAL;
 
-        slink_name = path_startswith(slink, "/dev");
-        if (!slink_name ||
-            empty_or_root(slink_name) ||
-            !path_is_normalized(slink_name))
-                return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL),
-                                              "Invalid symbolic link of device node: %s", slink);
+        name = path_startswith(s, "/dev");
+        if (empty_or_root(name))
+                return -EINVAL;
+
+        udev_node_escape_path(name, name_enc, sizeof(name_enc));
 
-        (void) udev_node_escape_path(slink_name, name_enc, sizeof(name_enc));
         dirname = path_join("/run/udev/links", name_enc);
         if (!dirname)
-                return log_oom_debug();
+                return -ENOMEM;
 
-        r = update_stack_directory(dev, dirname, add);
-        if (r < 0)
-                return r;
+        *ret = TAKE_PTR(dirname);
+        return 0;
+}
 
-        for (unsigned i = 0; i < LINK_UPDATE_MAX_RETRIES; i++) {
-                _cleanup_free_ char *target = NULL;
-                struct stat st1 = {}, st2 = {};
+static int link_update(sd_device *dev, const char *slink, bool add) {
+        _cleanup_free_ char *dirname = NULL, *devnode = NULL;
+        _cleanup_close_ int dirfd = -EBADF, lockfd = -EBADF;
+        int r;
 
-                if (i > 0) {
-                        usec_t delay = MIN_RANDOM_DELAY + random_u64_range(MAX_RANDOM_DELAY - MIN_RANDOM_DELAY);
+        assert(dev);
+        assert(slink);
 
-                        log_device_debug(dev, "Directory %s was updated, retrying to update devlink %s after %s.",
-                                         dirname, slink, FORMAT_TIMESPAN(delay, USEC_PER_MSEC));
-                        (void) usleep(delay);
-                }
+        r = stack_directory_get_name(slink, &dirname);
+        if (r < 0)
+                return log_device_debug_errno(dev, r, "Failed to build stack directory name for '%s': %m", slink);
 
-                if (stat(dirname, &st1) < 0 && errno != ENOENT)
-                        return log_device_debug_errno(dev, errno, "Failed to stat %s: %m", dirname);
+        dirfd = stack_directory_open(dirname);
+        if (dirfd < 0)
+                return log_device_debug_errno(dev, dirfd, "Failed to open stack directory '%s': %m", dirname);
 
-                r = link_find_prioritized(dev, add, dirname, &target);
-                if (r < 0)
-                        return log_device_debug_errno(dev, r, "Failed to determine device node with the highest priority for '%s': %m", slink);
-                if (r == 0) {
-                        log_device_debug(dev, "No reference left for '%s', removing", slink);
+        lockfd = stack_directory_lock(dirfd);
+        if (lockfd < 0)
+                return log_device_debug_errno(dev, lockfd, "Failed to lock stack directory '%s': %m", dirname);
 
-                        if (unlink(slink) < 0 && errno != ENOENT)
-                                log_device_debug_errno(dev, errno, "Failed to remove '%s', ignoring: %m", slink);
+        r = stack_directory_update(dev, dirfd, add);
+        if (r < 0)
+                return log_device_debug_errno(dev, r, "Failed to update stack directory '%s': %m", dirname);
 
-                        (void) rmdir_parents(slink, "/dev");
-                        return 0;
-                }
+        r = stack_directory_find_prioritized_devnode(dev, dirname, add, &devnode);
+        if (r < 0)
+                return log_device_debug_errno(dev, r, "Failed to determine device node with the highest priority for '%s': %m", slink);
+        if (r > 0)
+                return node_symlink(dev, devnode, slink);
 
-                r = node_symlink(dev, target, slink);
-                if (r < 0)
-                        return r;
+        log_device_debug(dev, "No reference left for '%s', removing", slink);
 
-                if (stat(dirname, &st2) < 0 && errno != ENOENT)
-                        return log_device_debug_errno(dev, errno, "Failed to stat %s: %m", dirname);
+        if (unlink(slink) < 0 && errno != ENOENT)
+                log_device_debug_errno(dev, errno, "Failed to remove '%s', ignoring: %m", slink);
 
-                if (((st1.st_mode & S_IFMT) == 0 && (st2.st_mode & S_IFMT) == 0) ||
-                    stat_inode_unmodified(&st1, &st2))
-                        return 0;
-        }
+        (void) rmdir_parents(slink, "/dev");
 
-        return -ELOOP;
+        return 0;
 }
 
 static int device_get_devpath_by_devnum(sd_device *dev, char **ret) {
@@ -510,23 +446,12 @@ static int device_get_devpath_by_devnum(sd_device *dev, char **ret) {
 
 int udev_node_update(sd_device *dev, sd_device *dev_old) {
         _cleanup_free_ char *filename = NULL;
-        const char *devnode, *devlink;
+        const char *devlink;
         int r;
 
         assert(dev);
         assert(dev_old);
 
-        r = sd_device_get_devname(dev, &devnode);
-        if (r < 0)
-                return log_device_debug_errno(dev, r, "Failed to get devnode: %m");
-
-        if (DEBUG_LOGGING) {
-                const char *id = NULL;
-
-                (void) device_get_device_id(dev, &id);
-                log_device_debug(dev, "Handling device node '%s', devnum=%s", devnode, strna(id));
-        }
-
         /* update possible left-over symlinks */
         FOREACH_DEVICE_DEVLINK(dev_old, devlink) {
                 /* check if old link name still belongs to this device */
@@ -558,7 +483,7 @@ int udev_node_update(sd_device *dev, sd_device *dev_old) {
                 return log_device_debug_errno(dev, r, "Failed to get device path: %m");
 
         /* always add /dev/{block,char}/$major:$minor */
-        r = node_symlink(dev, devnode, filename);
+        r = node_symlink(dev, NULL, filename);
         if (r < 0)
                 return log_device_warning_errno(dev, r, "Failed to create device symlink '%s': %m", filename);
 
@@ -699,7 +624,7 @@ int udev_node_apply_permissions(
                 OrderedHashmap *seclabel_list) {
 
         const char *devnode;
-        _cleanup_close_ int node_fd = -1;
+        _cleanup_close_ int node_fd = -EBADF;
         int r;
 
         assert(dev);
@@ -729,7 +654,7 @@ int static_node_apply_permissions(
                 char **tags) {
 
         _cleanup_free_ char *unescaped_filename = NULL;
-        _cleanup_close_ int node_fd = -1;
+        _cleanup_close_ int node_fd = -EBADF;
         const char *devnode;
         struct stat stats;
         int r;