b677e7
From 897b4d1e19c706d9198b9308125df57a5d469a6b Mon Sep 17 00:00:00 2001
b677e7
From: Michal Sekletar <msekleta@redhat.com>
b677e7
Date: Mon, 17 May 2021 15:50:31 +0200
b677e7
Subject: [PATCH] Revert "udev: make algorithm that selects highest priority
b677e7
 devlink less susceptible to race conditions"
b677e7
b677e7
This reverts commit 1d5f966c1758eb620755fcae54abd07a1ac36d3d.
b677e7
b677e7
Related: #1942299
b677e7
---
b677e7
 src/udev/udev-event.c |  71 +++++-------
b677e7
 src/udev/udev-node.c  | 244 ++++++++++++------------------------------
b677e7
 2 files changed, 99 insertions(+), 216 deletions(-)
b677e7
b677e7
diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c
b677e7
index 9004634f65..fd8406d959 100644
b677e7
--- a/src/udev/udev-event.c
b677e7
+++ b/src/udev/udev-event.c
b677e7
@@ -833,41 +833,6 @@ static int rename_netif(struct udev_event *event) {
b677e7
         return 0;
b677e7
 }
b677e7
 
b677e7
-static void update_devnode(struct udev_event *event) {
b677e7
-        struct udev_device *dev = event->dev;
b677e7
-
b677e7
-        if (major(udev_device_get_devnum(dev)) > 0) {
b677e7
-                bool apply;
b677e7
-
b677e7
-                /* remove/update possible left-over symlinks from old database entry */
b677e7
-                if (event->dev_db != NULL)
b677e7
-                        udev_node_update_old_links(dev, event->dev_db);
b677e7
-
b677e7
-                if (!event->owner_set)
b677e7
-                        event->uid = udev_device_get_devnode_uid(dev);
b677e7
-
b677e7
-                if (!event->group_set)
b677e7
-                        event->gid = udev_device_get_devnode_gid(dev);
b677e7
-
b677e7
-                if (!event->mode_set) {
b677e7
-                        if (udev_device_get_devnode_mode(dev) > 0) {
b677e7
-                                /* kernel supplied value */
b677e7
-                                event->mode = udev_device_get_devnode_mode(dev);
b677e7
-                        } else if (event->gid > 0) {
b677e7
-                                /* default 0660 if a group is assigned */
b677e7
-                                event->mode = 0660;
b677e7
-                        }
b677e7
-                        else {
b677e7
-                                /* default 0600 */
b677e7
-                                event->mode = 0600;
b677e7
-                        }
b677e7
-                }
b677e7
-
b677e7
-                apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set;
b677e7
-                udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list);
b677e7
-        }
b677e7
-}
b677e7
-
b677e7
 void udev_event_execute_rules(struct udev_event *event,
b677e7
                               usec_t timeout_usec, usec_t timeout_warn_usec,
b677e7
                               struct udev_list *properties_list,
b677e7
@@ -926,7 +891,35 @@ void udev_event_execute_rules(struct udev_event *event,
b677e7
                         }
b677e7
                 }
b677e7
 
b677e7
-                update_devnode(event);
b677e7
+                if (major(udev_device_get_devnum(dev)) > 0) {
b677e7
+                        bool apply;
b677e7
+
b677e7
+                        /* remove/update possible left-over symlinks from old database entry */
b677e7
+                        if (event->dev_db != NULL)
b677e7
+                                udev_node_update_old_links(dev, event->dev_db);
b677e7
+
b677e7
+                        if (!event->owner_set)
b677e7
+                                event->uid = udev_device_get_devnode_uid(dev);
b677e7
+
b677e7
+                        if (!event->group_set)
b677e7
+                                event->gid = udev_device_get_devnode_gid(dev);
b677e7
+
b677e7
+                        if (!event->mode_set) {
b677e7
+                                if (udev_device_get_devnode_mode(dev) > 0) {
b677e7
+                                        /* kernel supplied value */
b677e7
+                                        event->mode = udev_device_get_devnode_mode(dev);
b677e7
+                                } else if (event->gid > 0) {
b677e7
+                                        /* default 0660 if a group is assigned */
b677e7
+                                        event->mode = 0660;
b677e7
+                                } else {
b677e7
+                                        /* default 0600 */
b677e7
+                                        event->mode = 0600;
b677e7
+                                }
b677e7
+                        }
b677e7
+
b677e7
+                        apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set;
b677e7
+                        udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list);
b677e7
+                }
b677e7
 
b677e7
                 /* preserve old, or get new initialization timestamp */
b677e7
                 udev_device_ensure_usec_initialized(event->dev, event->dev_db);
b677e7
@@ -934,12 +927,6 @@ void udev_event_execute_rules(struct udev_event *event,
b677e7
                 /* (re)write database file */
b677e7
                 udev_device_tag_index(dev, event->dev_db, true);
b677e7
                 udev_device_update_db(dev);
b677e7
-
b677e7
-                /* Yes, we run update_devnode() twice, because in the first invocation, that is before update of udev database,
b677e7
-                 * it could happen that two contenders are replacing each other's symlink. Hence we run it again to make sure
b677e7
-                 * symlinks point to devices that claim them with the highest priority. */
b677e7
-                update_devnode(event);
b677e7
-
b677e7
                 udev_device_set_is_initialized(dev);
b677e7
 
b677e7
                 event->dev_db = udev_device_unref(event->dev_db);
b677e7
diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c
b677e7
index 2eeeccdd3a..333dcae6b9 100644
b677e7
--- a/src/udev/udev-node.c
b677e7
+++ b/src/udev/udev-node.c
b677e7
@@ -13,27 +13,19 @@
b677e7
 #include <unistd.h>
b677e7
 
b677e7
 #include "device-nodes.h"
b677e7
-#include "device-private.h"
b677e7
 #include "dirent-util.h"
b677e7
-#include "fd-util.h"
b677e7
 #include "format-util.h"
b677e7
 #include "fs-util.h"
b677e7
-#include "sd-device.h"
b677e7
 #include "selinux-util.h"
b677e7
 #include "smack-util.h"
b677e7
-#include "stat-util.h"
b677e7
 #include "stdio-util.h"
b677e7
 #include "string-util.h"
b677e7
 #include "udev.h"
b677e7
-#include "libudev-device-internal.h"
b677e7
 
b677e7
-#define LINK_UPDATE_MAX_RETRIES 128
b677e7
-
b677e7
-static int node_symlink(sd_device *dev, const char *node, const char *slink) {
b677e7
+static int node_symlink(struct udev_device *dev, const char *node, const char *slink) {
b677e7
         struct stat stats;
b677e7
         char target[UTIL_PATH_SIZE];
b677e7
         char *s;
b677e7
-        const char *id_filename;
b677e7
         size_t l;
b677e7
         char slink_tmp[UTIL_PATH_SIZE + 32];
b677e7
         int i = 0;
b677e7
@@ -97,10 +89,7 @@ static int node_symlink(sd_device *dev, const char *node, const char *slink) {
b677e7
         }
b677e7
 
b677e7
         log_debug("atomically replace '%s'", slink);
b677e7
-        err = device_get_id_filename(dev, &id_filename);
b677e7
-        if (err < 0)
b677e7
-                return log_error_errno(err, "Failed to get id_filename: %m");
b677e7
-        strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", id_filename, NULL);
b677e7
+        strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL);
b677e7
         unlink(slink_tmp);
b677e7
         do {
b677e7
                 err = mkdir_parents_label(slink_tmp, 0755);
b677e7
@@ -120,187 +109,104 @@ static int node_symlink(sd_device *dev, const char *node, const char *slink) {
b677e7
         if (err != 0) {
b677e7
                 log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink);
b677e7
                 unlink(slink_tmp);
b677e7
-        } else
b677e7
-                /* Tell caller that we replaced already existing symlink. */
b677e7
-                return 1;
b677e7
+        }
b677e7
 exit:
b677e7
         return err;
b677e7
 }
b677e7
 
b677e7
 /* find device node of device with highest priority */
b677e7
-static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir, char **ret) {
b677e7
-        _cleanup_closedir_ DIR *dir = NULL;
b677e7
-        _cleanup_free_ char *target = NULL;
b677e7
+static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) {
b677e7
+        struct udev *udev = udev_device_get_udev(dev);
b677e7
+        DIR *dir;
b677e7
         struct dirent *dent;
b677e7
-        int r, priority = 0;
b677e7
-
b677e7
-        assert(!add || dev);
b677e7
-        assert(stackdir);
b677e7
-        assert(ret);
b677e7
+        int priority = 0;
b677e7
+        const char *target = NULL;
b677e7
 
b677e7
         if (add) {
b677e7
-                const char *devnode;
b677e7
-
b677e7
-                r = device_get_devlink_priority(dev, &priority);
b677e7
-                if (r < 0)
b677e7
-                        return r;
b677e7
-
b677e7
-                r = sd_device_get_devname(dev, &devnode);
b677e7
-                if (r < 0)
b677e7
-                        return r;
b677e7
-
b677e7
-                target = strdup(devnode);
b677e7
-                if (!target)
b677e7
-                        return -ENOMEM;
b677e7
+                priority = udev_device_get_devlink_priority(dev);
b677e7
+                strscpy(buf, bufsize, udev_device_get_devnode(dev));
b677e7
+                target = buf;
b677e7
         }
b677e7
 
b677e7
         dir = opendir(stackdir);
b677e7
-        if (!dir) {
b677e7
-                if (target) {
b677e7
-                        *ret = TAKE_PTR(target);
b677e7
-                        return 0;
b677e7
-                }
b677e7
-
b677e7
-                return -errno;
b677e7
-        }
b677e7
-
b677e7
+        if (dir == NULL)
b677e7
+                return target;
b677e7
         FOREACH_DIRENT_ALL(dent, dir, break) {
b677e7
-                _cleanup_(sd_device_unrefp) sd_device *dev_db = NULL;
b677e7
-                const char *devnode, *id_filename;
b677e7
-                int db_prio = 0;
b677e7
+                struct udev_device *dev_db;
b677e7
 
b677e7
                 if (dent->d_name[0] == '\0')
b677e7
                         break;
b677e7
                 if (dent->d_name[0] == '.')
b677e7
                         continue;
b677e7
 
b677e7
-                log_debug("Found '%s' claiming '%s'", dent->d_name, stackdir);
b677e7
-
b677e7
-                if (device_get_id_filename(dev, &id_filename) < 0)
b677e7
-                        continue;
b677e7
+                log_debug("found '%s' claiming '%s'", dent->d_name, stackdir);
b677e7
 
b677e7
                 /* did we find ourself? */
b677e7
-                if (streq(dent->d_name, id_filename))
b677e7
-                        continue;
b677e7
-
b677e7
-                if (sd_device_new_from_device_id(&dev_db, dent->d_name) < 0)
b677e7
+                if (streq(dent->d_name, udev_device_get_id_filename(dev)))
b677e7
                         continue;
b677e7
 
b677e7
-                if (sd_device_get_devname(dev_db, &devnode) < 0)
b677e7
-                        continue;
b677e7
-
b677e7
-                if (device_get_devlink_priority(dev_db, &db_prio) < 0)
b677e7
-                        continue;
b677e7
-
b677e7
-                if (target && db_prio <= priority)
b677e7
-                        continue;
b677e7
-
b677e7
-                if (DEBUG_LOGGING) {
b677e7
-                        const char *syspath = NULL;
b677e7
-
b677e7
-                        (void) sd_device_get_syspath(dev_db, &syspath);
b677e7
-                        log_debug("Device '%s' claims priority %i for '%s'", strnull(syspath), db_prio, stackdir);
b677e7
+                dev_db = udev_device_new_from_device_id(udev, dent->d_name);
b677e7
+                if (dev_db != NULL) {
b677e7
+                        const char *devnode;
b677e7
+
b677e7
+                        devnode = udev_device_get_devnode(dev_db);
b677e7
+                        if (devnode != NULL) {
b677e7
+                                if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) {
b677e7
+                                        log_debug("'%s' claims priority %i for '%s'",
b677e7
+                                                  udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir);
b677e7
+                                        priority = udev_device_get_devlink_priority(dev_db);
b677e7
+                                        strscpy(buf, bufsize, devnode);
b677e7
+                                        target = buf;
b677e7
+                                }
b677e7
+                        }
b677e7
+                        udev_device_unref(dev_db);
b677e7
                 }
b677e7
-
b677e7
-                r = free_and_strdup(&target, devnode);
b677e7
-                if (r < 0)
b677e7
-                        return r;
b677e7
-                priority = db_prio;
b677e7
         }
b677e7
-
b677e7
-        if (!target)
b677e7
-                return -ENOENT;
b677e7
-
b677e7
-        *ret = TAKE_PTR(target);
b677e7
-        return 0;
b677e7
+        closedir(dir);
b677e7
+        return target;
b677e7
 }
b677e7
 
b677e7
-
b677e7
 /* manage "stack of names" with possibly specified device priorities */
b677e7
-static int link_update(sd_device *dev, const char *slink, bool add) {
b677e7
-        _cleanup_free_ char *filename = NULL, *dirname = NULL;
b677e7
-        char name_enc[PATH_MAX];
b677e7
-        const char *id_filename;
b677e7
-        int i, r, retries;
b677e7
-
b677e7
-        assert(dev);
b677e7
-        assert(slink);
b677e7
-
b677e7
-        r = device_get_id_filename(dev, &id_filename);
b677e7
-        if (r < 0)
b677e7
-                return log_debug_errno(r, "Failed to get id_filename: %m");
b677e7
+static void link_update(struct udev_device *dev, const char *slink, bool add) {
b677e7
+        char name_enc[UTIL_PATH_SIZE];
b677e7
+        char filename[UTIL_PATH_SIZE * 2];
b677e7
+        char dirname[UTIL_PATH_SIZE];
b677e7
+        const char *target;
b677e7
+        char buf[UTIL_PATH_SIZE];
b677e7
 
b677e7
         util_path_encode(slink + STRLEN("/dev"), name_enc, sizeof(name_enc));
b677e7
-        dirname = path_join(NULL, "/run/udev/links/", name_enc);
b677e7
-        if (!dirname)
b677e7
-                return log_oom();
b677e7
-        filename = path_join(NULL, dirname, id_filename);
b677e7
-        if (!filename)
b677e7
-                return log_oom();
b677e7
-
b677e7
-        if (!add) {
b677e7
-                if (unlink(filename) == 0)
b677e7
-                        (void) rmdir(dirname);
b677e7
-        } else
b677e7
-                for (;;) {
b677e7
-                        _cleanup_close_ int fd = -1;
b677e7
-
b677e7
-                        r = mkdir_parents(filename, 0755);
b677e7
-                        if (!IN_SET(r, 0, -ENOENT))
b677e7
-                                return r;
b677e7
-
b677e7
-                        fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
b677e7
-                        if (fd >= 0)
b677e7
-                                break;
b677e7
-                        if (errno != ENOENT)
b677e7
-                                return -errno;
b677e7
-                }
b677e7
-
b677e7
-        /* If the database entry is not written yet we will just do one iteration and possibly wrong symlink
b677e7
-         * will be fixed in the second invocation. */
b677e7
-        (void) sd_device_get_is_initialized(dev, &r);
b677e7
-        retries = r > 0 ? LINK_UPDATE_MAX_RETRIES : 1;
b677e7
+        strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL);
b677e7
+        strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL);
b677e7
 
b677e7
-        for (i = 0; i < retries; i++) {
b677e7
-                _cleanup_free_ char *target = NULL;
b677e7
-                struct stat st1 = {}, st2 = {};
b677e7
+        if (!add && unlink(filename) == 0)
b677e7
+                rmdir(dirname);
b677e7
 
b677e7
-                r = stat(dirname, &st1;;
b677e7
-                if (r < 0 && errno != ENOENT)
b677e7
-                        return -errno;
b677e7
-
b677e7
-                r = link_find_prioritized(dev, add, dirname, &target);
b677e7
-                if (r == -ENOENT) {
b677e7
-                        log_debug("No reference left, removing '%s'", slink);
b677e7
-                        if (unlink(slink) == 0)
b677e7
-                                (void) rmdir_parents(slink, "/");
b677e7
-
b677e7
-                        break;
b677e7
-                } else if (r < 0)
b677e7
-                        return log_error_errno(r, "Failed to determine highest priority symlink: %m");
b677e7
+        target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf));
b677e7
+        if (target == NULL) {
b677e7
+                log_debug("no reference left, remove '%s'", slink);
b677e7
+                if (unlink(slink) == 0)
b677e7
+                        rmdir_parents(slink, "/");
b677e7
+        } else {
b677e7
+                log_debug("creating link '%s' to '%s'", slink, target);
b677e7
+                node_symlink(dev, target, slink);
b677e7
+        }
b677e7
 
b677e7
-                r = node_symlink(dev, target, slink);
b677e7
-                if (r < 0) {
b677e7
-                        (void) unlink(filename);
b677e7
-                        break;
b677e7
-                } else if (r == 1)
b677e7
-                        /* We have replaced already existing symlink, possibly there is some other device trying
b677e7
-                         * to claim the same symlink. Let's do one more iteration to give us a chance to fix
b677e7
-                         * the error if other device actually claims the symlink with higher priority. */
b677e7
-                        continue;
b677e7
+        if (add) {
b677e7
+                int err;
b677e7
 
b677e7
-               /* Skip the second stat() if the first failed, stat_inode_unmodified() would return false regardless. */
b677e7
-                if ((st1.st_mode & S_IFMT) != 0) {
b677e7
-                        r = stat(dirname, &st2;;
b677e7
-                        if (r < 0 && errno != ENOENT)
b677e7
-                                return -errno;
b677e7
+                do {
b677e7
+                        int fd;
b677e7
 
b677e7
-                        if (stat_inode_unmodified(&st1, &st2))
b677e7
+                        err = mkdir_parents(filename, 0755);
b677e7
+                        if (!IN_SET(err, 0, -ENOENT))
b677e7
                                 break;
b677e7
-                }
b677e7
+                        fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
b677e7
+                        if (fd >= 0)
b677e7
+                                close(fd);
b677e7
+                        else
b677e7
+                                err = -errno;
b677e7
+                } while (err == -ENOENT);
b677e7
         }
b677e7
-
b677e7
-        return i < LINK_UPDATE_MAX_RETRIES ? 0 : -ELOOP;
b677e7
 }
b677e7
 
b677e7
 void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) {
b677e7
@@ -327,7 +233,7 @@ void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev
b677e7
 
b677e7
                 log_debug("update old name, '%s' no longer belonging to '%s'",
b677e7
                      name, udev_device_get_devpath(dev));
b677e7
-                link_update(dev->device, name, false);
b677e7
+                link_update(dev, name, false);
b677e7
         }
b677e7
 }
b677e7
 
b677e7
@@ -432,16 +338,11 @@ void udev_node_add(struct udev_device *dev, bool apply,
b677e7
         xsprintf_dev_num_path(filename,
b677e7
                               streq(udev_device_get_subsystem(dev), "block") ? "block" : "char",
b677e7
                               udev_device_get_devnum(dev));
b677e7
-        node_symlink(dev->device, udev_device_get_devnode(dev), filename);
b677e7
+        node_symlink(dev, udev_device_get_devnode(dev), filename);
b677e7
 
b677e7
         /* create/update symlinks, add symlinks to name index */
b677e7
-        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) {
b677e7
-                int r;
b677e7
-
b677e7
-                r = link_update(dev->device, udev_list_entry_get_name(list_entry), true);
b677e7
-                if (r < 0)
b677e7
-                        log_info_errno(r, "Failed to update device symlinks: %m");
b677e7
-        }
b677e7
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
b677e7
+                        link_update(dev, udev_list_entry_get_name(list_entry), true);
b677e7
 }
b677e7
 
b677e7
 void udev_node_remove(struct udev_device *dev) {
b677e7
@@ -449,13 +350,8 @@ void udev_node_remove(struct udev_device *dev) {
b677e7
         char filename[DEV_NUM_PATH_MAX];
b677e7
 
b677e7
         /* remove/update symlinks, remove symlinks from name index */
b677e7
-        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) {
b677e7
-                int r;
b677e7
-
b677e7
-                r = link_update(dev->device, udev_list_entry_get_name(list_entry), false);
b677e7
-                if (r < 0)
b677e7
-                        log_info_errno(r, "Failed to update device symlinks: %m");
b677e7
-        }
b677e7
+        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
b677e7
+                link_update(dev, udev_list_entry_get_name(list_entry), false);
b677e7
 
b677e7
         /* remove /dev/{block,char}/$major:$minor */
b677e7
         xsprintf_dev_num_path(filename,