]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: explicitly trigger changing udev SYSTEMD_WANTS properties 9366/head
authorLennart Poettering <lennart@poettering.net>
Wed, 20 Jun 2018 20:54:55 +0000 (22:54 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 20 Jun 2018 21:59:29 +0000 (23:59 +0200)
This compensates for the unsynchronized reload cycles of systemd and
udev: we manually trigger the deps listed in SYSTEMD_WANTS properties if
they change for device units that are already up. That way all deps
defined that way will be triggered at least once: the first time the
unit goes up by the usual dependency logic, and if it already is up by
the device.c specific logic.

Fixes: #9323
src/core/device.c
src/core/device.h

index 4bffb94726966c6a8658ede562f86302ec5a9f6c..a2d00a0fbe78033394fa3f773be24109b6934fd5 100644 (file)
@@ -6,6 +6,7 @@
 #include "libudev.h"
 
 #include "alloc-util.h"
+#include "bus-error.h"
 #include "dbus-device.h"
 #include "device.h"
 #include "log.h"
@@ -107,6 +108,7 @@ static void device_done(Unit *u) {
         assert(d);
 
         device_unset_sysfs(d);
+        d->wants_property = strv_free(d->wants_property);
 }
 
 static void device_set_state(Device *d, DeviceState state) {
@@ -276,6 +278,14 @@ static void device_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, device_state_to_string(d->state),
                 prefix, strna(d->sysfs),
                 prefix, strna(s));
+
+        if (!strv_isempty(d->wants_property)) {
+                char **i;
+
+                STRV_FOREACH(i, d->wants_property)
+                        fprintf(f, "%sudev SYSTEMD_WANTS: %s\n",
+                                prefix, *i);
+        }
 }
 
 _pure_ static UnitActiveState device_active_state(Unit *u) {
@@ -331,10 +341,12 @@ static int device_update_description(Unit *u, struct udev_device *dev, const cha
 }
 
 static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
+        _cleanup_strv_free_ char **added = NULL;
         const char *wants, *property;
+        Device *d = DEVICE(u);
         int r;
 
-        assert(u);
+        assert(d);
         assert(dev);
 
         property = MANAGER_IS_USER(u->manager) ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS";
@@ -348,21 +360,21 @@ static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
 
                 r = extract_first_word(&wants, &word, NULL, EXTRACT_QUOTES);
                 if (r == 0)
-                        return 0;
+                        break;
                 if (r == -ENOMEM)
                         return log_oom();
                 if (r < 0)
                         return log_unit_error_errno(u, r, "Failed to parse property %s with value %s: %m", property, wants);
 
-                if (unit_name_is_valid(word, UNIT_NAME_TEMPLATE) && DEVICE(u)->sysfs) {
+                if (unit_name_is_valid(word, UNIT_NAME_TEMPLATE) && d->sysfs) {
                         _cleanup_free_ char *escaped = NULL;
 
                         /* If the unit name is specified as template, then automatically fill in the sysfs path of the
                          * device as instance name, properly escaped. */
 
-                        r = unit_name_path_escape(DEVICE(u)->sysfs, &escaped);
+                        r = unit_name_path_escape(d->sysfs, &escaped);
                         if (r < 0)
-                                return log_unit_error_errno(u, r, "Failed to escape %s: %m", DEVICE(u)->sysfs);
+                                return log_unit_error_errno(u, r, "Failed to escape %s: %m", d->sysfs);
 
                         r = unit_name_replace_instance(word, escaped, &k);
                         if (r < 0)
@@ -378,7 +390,43 @@ static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
                 r = unit_add_dependency_by_name(u, UNIT_WANTS, k, NULL, true, UNIT_DEPENDENCY_UDEV);
                 if (r < 0)
                         return log_unit_error_errno(u, r, "Failed to add Wants= dependency: %m");
+
+                r = strv_push(&added, k);
+                if (r < 0)
+                        return log_oom();
+
+                k = NULL;
         }
+
+        if (d->state != DEVICE_DEAD) {
+                char **i;
+
+                /* So here's a special hack, to compensate for the fact that the udev database's reload cycles are not
+                 * synchronized with our own reload cycles: when we detect that the SYSTEMD_WANTS property of a device
+                 * changes while the device unit is already up, let's manually trigger any new units listed in it not
+                 * seen before. This typically appens during the boot-time switch root transition, as udev devices
+                 * will generally already be up in the initrd, but SYSTEMD_WANTS properties get then added through udev
+                 * rules only available on the host system, and thus only when the initial udev coldplug trigger runs.
+                 *
+                 * We do this only if the device has been up already when we parse this, as otherwise the usual
+                 * dependency logic that is run from the dead → plugged transition will trigger these deps. */
+
+                STRV_FOREACH(i, added) {
+                        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+                        if (strv_contains(d->wants_property, *i)) /* Was this unit already listed before? */
+                                continue;
+
+                        r = manager_add_job_by_name(u->manager, JOB_START, *i, JOB_FAIL, &error, NULL);
+                        if (r < 0)
+                                log_unit_warning_errno(u, r, "Failed to enqueue SYSTEMD_WANTS= job, ignoring: %s", bus_error_message(&error, r));
+                }
+        }
+
+        strv_free(d->wants_property);
+        d->wants_property = TAKE_PTR(added);
+
+        return 0;
 }
 
 static bool device_is_bound_by_mounts(Device *d, struct udev_device *dev) {
index a5f9bbe58cab7e2ebabf98aa1aeb8242cf7fe0eb..a119b33e57f34d05c19ae0c6c6c84178e80fa880 100644 (file)
@@ -30,6 +30,9 @@ struct Device {
         DeviceFound found, deserialized_found, enumerated_found;
 
         bool bind_mounts;
+
+        /* The SYSTEMD_WANTS udev property for this device the last time we saw it */
+        char **wants_property;
 };
 
 extern const UnitVTable device_vtable;