+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
-
- 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 <errno.h>
if (streq_ptr(d->sysfs, sysfs))
return 0;
- r = hashmap_ensure_allocated(&UNIT(d)->manager->devices_by_sysfs, &string_hash_ops);
+ r = hashmap_ensure_allocated(&UNIT(d)->manager->devices_by_sysfs, &path_hash_ops);
if (r < 0)
return r;
}
static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
- const char *wants, *property, *p;
+ const char *wants, *property;
int r;
assert(u);
assert(dev);
property = MANAGER_IS_USER(u->manager) ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS";
+
wants = udev_device_get_property_value(dev, property);
- for (p = wants;;) {
+ if (!wants)
+ return 0;
+
+ for (;;) {
_cleanup_free_ char *word = NULL, *k = NULL;
- r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+ r = extract_first_word(&wants, &word, NULL, EXTRACT_QUOTES);
if (r == 0)
return 0;
if (r == -ENOMEM)
return log_oom();
if (r < 0)
- return log_unit_error_errno(u, r, "Failed to add parse %s: %m", property);
+ return log_unit_error_errno(u, r, "Failed to parse property %s with value %s: %m", property, wants);
- r = unit_name_mangle(word, UNIT_NAME_NOGLOB, &k);
- if (r < 0)
- return log_unit_error_errno(u, r, "Failed to mangle unit name \"%s\": %m", word);
+ if (unit_name_is_valid(word, UNIT_NAME_TEMPLATE) && DEVICE(u)->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);
+ if (r < 0)
+ return log_unit_error_errno(u, r, "Failed to escape %s: %m", DEVICE(u)->sysfs);
- r = unit_add_dependency_by_name(u, UNIT_WANTS, k, NULL, true);
+ r = unit_name_replace_instance(word, escaped, &k);
+ if (r < 0)
+ return log_unit_error_errno(u, r, "Failed to build %s instance of template %s: %m", escaped, word);
+ } else {
+ /* If this is not a template, then let's mangle it so, that it becomes a valid unit name. */
+
+ r = unit_name_mangle(word, UNIT_NAME_MANGLE_WARN, &k);
+ if (r < 0)
+ return log_unit_error_errno(u, r, "Failed to mangle unit name \"%s\": %m", word);
+ }
+
+ 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");
+ return log_unit_error_errno(u, r, "Failed to add Wants= dependency: %m");
}
}
-static bool device_is_bound_by_mounts(Unit *d, struct udev_device *dev) {
+static bool device_is_bound_by_mounts(Device *d, struct udev_device *dev) {
const char *bound_by;
- int r = false;
+ int r;
assert(d);
assert(dev);
bound_by = udev_device_get_property_value(dev, "SYSTEMD_MOUNT_DEVICE_BOUND");
- if (bound_by)
- r = parse_boolean(bound_by) > 0;
+ if (bound_by) {
+ r = parse_boolean(bound_by);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse SYSTEMD_MOUNT_DEVICE_BOUND='%s' udev property of %s, ignoring: %m", bound_by, strna(d->sysfs));
- DEVICE(d)->bind_mounts = r;
- return r;
+ d->bind_mounts = r > 0;
+ } else
+ d->bind_mounts = false;
+
+ return d->bind_mounts;
}
static int device_upgrade_mount_deps(Unit *u) {
Unit *other;
Iterator i;
+ void *v;
int r;
- SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) {
+ /* Let's upgrade Requires= to BindsTo= on us. (Used when SYSTEMD_MOUNT_DEVICE_BOUND is set) */
+
+ HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRED_BY], i) {
if (other->type != UNIT_MOUNT)
continue;
- r = unit_add_dependency(other, UNIT_BINDS_TO, u, true);
+ r = unit_add_dependency(other, UNIT_BINDS_TO, u, true, UNIT_DEPENDENCY_UDEV);
if (r < 0)
return r;
}
return log_error_errno(r, "Failed to generate unit name from device path: %m");
u = manager_get_unit(m, e);
-
- /* The device unit can still be present even if the device was
- * unplugged: a mount unit can reference it hence preventing
- * the GC to have garbaged it. That's desired since the device
- * unit may have a dependency on the mount unit which was
- * added during the loading of the later. */
- if (dev && u && DEVICE(u)->state == DEVICE_PLUGGED) {
- /* This unit is in plugged state: we're sure it's
- * attached to a device. */
- if (!path_equal(DEVICE(u)->sysfs, sysfs)) {
- log_unit_debug(u, "Dev %s appeared twice with different sysfs paths %s and %s",
- e, DEVICE(u)->sysfs, sysfs);
- return -EEXIST;
+ if (u) {
+ /* The device unit can still be present even if the device was unplugged: a mount unit can reference it hence
+ * preventing the GC to have garbaged it. That's desired since the device unit may have a dependency on the
+ * mount unit which was added during the loading of the later. */
+ if (dev && DEVICE(u)->state == DEVICE_PLUGGED) {
+
+ /* This unit is in plugged state: we're sure it's attached to a device. */
+ if (!path_equal(DEVICE(u)->sysfs, sysfs)) {
+ log_unit_debug(u, "Dev %s appeared twice with different sysfs paths %s and %s",
+ e, DEVICE(u)->sysfs, sysfs);
+ return -EEXIST;
+ }
}
- }
- if (!u) {
+ delete = false;
+
+ /* Let's remove all dependencies generated due to udev properties. We'll readd whatever is configured
+ * now below. */
+ unit_remove_dependencies(u, UNIT_DEPENDENCY_UDEV);
+ } else {
delete = true;
r = unit_new_for_name(m, sizeof(Device), e, &u);
goto fail;
unit_add_to_load_queue(u);
- } else
- delete = false;
+ }
/* If this was created via some dependency and has not
* actually been seen yet ->sysfs will not be
(void) device_add_udev_wants(u, dev);
}
- /* So the user wants the mount units to be bound to the device but a
- * mount unit might has been seen by systemd before the device appears
- * on its radar. In this case the device unit is partially initialized
- * and includes the deps on the mount unit but at that time the "bind
- * mounts" flag wasn't not present. Fix this up now. */
- if (dev && device_is_bound_by_mounts(u, dev))
+ /* So the user wants the mount units to be bound to the device but a mount unit might has been seen by systemd
+ * before the device appears on its radar. In this case the device unit is partially initialized and includes
+ * the deps on the mount unit but at that time the "bind mounts" flag wasn't not present. Fix this up now. */
+ if (dev && device_is_bound_by_mounts(DEVICE(u), dev))
device_upgrade_mount_deps(u);
- /* Note that this won't dispatch the load queue, the caller
- * has to do that if needed and appropriate */
+ /* Note that this won't dispatch the load queue, the caller has to do that if needed and appropriate */
unit_add_to_dbus_queue(u);
return 0;
* now referenced by the kernel, then we assume the
* kernel knows it now, and udev might soon too. */
device_set_state(d, DEVICE_TENTATIVE);
- else
+ else {
/* If nobody sees the device, or if the device was
* previously seen by udev and now is only referenced
* from the kernel, then we consider the device is
* gone, the kernel just hasn't noticed it yet. */
+
device_set_state(d, DEVICE_DEAD);
+ device_unset_sysfs(d);
+ }
+
}
static int device_update_found_by_sysfs(Manager *m, const char *sysfs, bool add, DeviceFound found, bool now) {
- Device *d, *l;
+ Device *d, *l, *n;
assert(m);
assert(sysfs);
return 0;
l = hashmap_get(m->devices_by_sysfs, sysfs);
- LIST_FOREACH(same_sysfs, d, l)
+ LIST_FOREACH_SAFE(same_sysfs, d, n, l)
device_update_found_one(d, add, found, now);
return 0;
assert(m);
m->udev_event_source = sd_event_source_unref(m->udev_event_source);
-
- if (m->udev_monitor) {
- udev_monitor_unref(m->udev_monitor);
- m->udev_monitor = NULL;
- }
-
+ m->udev_monitor = udev_monitor_unref(m->udev_monitor);
m->devices_by_sysfs = hashmap_free(m->devices_by_sysfs);
}
return 0;
}
+ if (streq(action, "change")) {
+ Unit *u;
+ Device *d, *l, *n;
+
+ l = hashmap_get(m->devices_by_sysfs, sysfs);
+ LIST_FOREACH_SAFE(same_sysfs, d, n, l) {
+ u = &d->meta;
+ if (u && UNIT_VTABLE(u)->active_state(u) == UNIT_ACTIVE) {
+ r = manager_propagate_reload(m, u, JOB_REPLACE, NULL);
+ if (r < 0)
+ log_error_errno(r, "Failed to propagate reload: %m");
+ }
+ }
+ }
+
+ /* A change event can signal that a device is becoming ready, in particular if
+ * the device is using the SYSTEMD_READY logic in udev
+ * so we need to reach the else block of the follwing if, even for change events */
if (streq(action, "remove")) {
r = swap_process_device_remove(m, dev);
if (r < 0)