diff --git a/man/udevadm.xml b/man/udevadm.xml
index 4e1255bf4d..5636d102b7 100644
--- a/man/udevadm.xml
+++ b/man/udevadm.xml
@@ -769,6 +769,16 @@
+
+
+
+ Revert settings previously set with udevadm control command. When
+ specified, settings set with , ,
+ , and will be cleared.
+
+
+
+
diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm
index d3c07e81dd..ee463902b5 100644
--- a/shell-completion/bash/udevadm
+++ b/shell-completion/bash/udevadm
@@ -93,7 +93,7 @@ _udevadm() {
-g --tag-match -y --sysname-match --name-match -b --parent-match
--prioritized-subsystem'
[SETTLE]='-t --timeout -E --exit-if-exists'
- [CONTROL_STANDALONE]='-e --exit -s --stop-exec-queue -S --start-exec-queue -R --reload --ping
+ [CONTROL_STANDALONE]='-e --exit -s --stop-exec-queue -S --start-exec-queue -R --reload --ping --revert
--load-credentials'
[CONTROL_ARG]='-l --log-priority -p --property -m --children-max -t --timeout --trace'
[MONITOR_STANDALONE]='-k --kernel -u --udev -p --property'
diff --git a/shell-completion/zsh/_udevadm b/shell-completion/zsh/_udevadm
index d5cda3977e..0f4b8aff2c 100644
--- a/shell-completion/zsh/_udevadm
+++ b/shell-completion/zsh/_udevadm
@@ -67,6 +67,7 @@ _udevadm_control(){
'(-p --property)'{-p,--property=}'[Set a global property for all events.]:KEY=VALUE' \
'(-m --children-max=)'{-m,--children-max=}'[Set the maximum number of events.]:N' \
'--trace=[Enable/disable trace logging.]:BOOL' \
+ '--revert[Revert previously set configurations.]' \
'(-t --timeout=)'{-t,--timeout=}'[The maximum number of seconds to wait for a reply from systemd-udevd.]:SECONDS'
}
diff --git a/src/shared/daemon-util.c b/src/shared/daemon-util.c
index d8aa259ee9..e83ca8f663 100644
--- a/src/shared/daemon-util.c
+++ b/src/shared/daemon-util.c
@@ -6,7 +6,7 @@
#include "string-util.h"
#include "time-util.h"
-static int notify_remove_fd_warn(const char *name) {
+int notify_remove_fd_warn(const char *name) {
int r;
assert(name);
diff --git a/src/shared/daemon-util.h b/src/shared/daemon-util.h
index cbb175cbe1..ff5e60e9be 100644
--- a/src/shared/daemon-util.h
+++ b/src/shared/daemon-util.h
@@ -23,6 +23,7 @@ static inline void notify_on_cleanup(const char **p) {
(void) sd_notify(false, *p);
}
+int notify_remove_fd_warn(const char *name);
int notify_remove_fd_warnf(const char *format, ...) _printf_(1, 2);
int close_and_notify_warn(int fd, const char *name);
int notify_push_fd(int fd, const char *name);
diff --git a/src/shared/varlink-io.systemd.Udev.c b/src/shared/varlink-io.systemd.Udev.c
index 09c1ca70ce..b6cfbd3bf3 100644
--- a/src/shared/varlink-io.systemd.Udev.c
+++ b/src/shared/varlink-io.systemd.Udev.c
@@ -17,6 +17,8 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("An array of global udev property assignments. Each string must be in KEY=VALUE style."),
SD_VARLINK_DEFINE_INPUT(assignments, SD_VARLINK_STRING, SD_VARLINK_ARRAY));
+static SD_VARLINK_DEFINE_METHOD(Revert);
+
static SD_VARLINK_DEFINE_METHOD(StartExecQueue);
static SD_VARLINK_DEFINE_METHOD(StopExecQueue);
@@ -34,6 +36,8 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_SYMBOL_COMMENT("Sets the global udev properties."),
&vl_method_SetEnvironment,
SD_VARLINK_SYMBOL_COMMENT("Starts processing of queued events."),
+ &vl_method_Revert,
+ SD_VARLINK_SYMBOL_COMMENT("Revert previously set configurations."),
&vl_method_StartExecQueue,
SD_VARLINK_SYMBOL_COMMENT("Stops processing of queued events."),
&vl_method_StopExecQueue,
diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c
index 142f19e1b1..52855145da 100644
--- a/src/udev/udev-config.c
+++ b/src/udev/udev-config.c
@@ -5,10 +5,14 @@
#include "conf-parser.h"
#include "cpu-set-util.h"
+#include "daemon-util.h"
+#include "fd-util.h"
+#include "fileio.h"
#include "limits-util.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "proc-cmdline.h"
+#include "serialize.h"
#include "signal-util.h"
#include "syslog-util.h"
#include "udev-config.h"
@@ -463,6 +467,24 @@ int manager_load(Manager *manager, int argc, char *argv[]) {
return 1;
}
+static UdevReloadFlags manager_needs_reload(Manager *manager, const UdevConfig *old) {
+ assert(manager);
+ assert(old);
+
+ if (manager->config.resolve_name_timing != old->resolve_name_timing)
+ return UDEV_RELOAD_RULES | UDEV_RELOAD_KILL_WORKERS;
+
+ if (manager->config.log_level != old->log_level ||
+ manager->config.exec_delay_usec != old->exec_delay_usec ||
+ manager->config.timeout_usec != old->timeout_usec ||
+ manager->config.timeout_signal != old->timeout_signal ||
+ manager->config.blockdev_read_only != old->blockdev_read_only ||
+ manager->config.trace != old->trace)
+ return UDEV_RELOAD_KILL_WORKERS;
+
+ return 0;
+}
+
UdevReloadFlags manager_reload_config(Manager *manager) {
assert(manager);
@@ -473,17 +495,133 @@ UdevReloadFlags manager_reload_config(Manager *manager) {
manager_merge_config(manager);
manager_adjust_config(&manager->config);
- if (manager->config.resolve_name_timing != old.resolve_name_timing)
- return UDEV_RELOAD_RULES | UDEV_RELOAD_KILL_WORKERS;
+ return manager_needs_reload(manager, &old);
+}
- if (manager->config.log_level != old.log_level ||
- manager->config.exec_delay_usec != old.exec_delay_usec ||
- manager->config.timeout_usec != old.timeout_usec ||
- manager->config.timeout_signal != old.timeout_signal ||
- manager->config.blockdev_read_only != old.blockdev_read_only ||
- manager->config.trace != old.trace)
- return UDEV_RELOAD_KILL_WORKERS;
+UdevReloadFlags manager_revert_config(Manager *manager) {
+ assert(manager);
+ UdevReloadFlags flags = 0;
+ if (!hashmap_isempty(manager->properties)) {
+ flags |= UDEV_RELOAD_KILL_WORKERS;
+ manager->properties = hashmap_free(manager->properties);
+ }
+
+ UdevConfig old = manager->config;
+
+ manager->config_by_control = UDEV_CONFIG_INIT;
+ manager_merge_config(manager);
+ manager_adjust_config(&manager->config);
+
+ return flags | manager_needs_reload(manager, &old);
+}
+
+int manager_serialize_config(Manager *manager) {
+ int r;
+
+ assert(manager);
+
+ _cleanup_fclose_ FILE *f = NULL;
+ r = open_serialization_file("systemd-udevd", &f);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to open new serialization file: %m");
+
+ if (manager->config_by_control.log_level >= 0) {
+ r = serialize_item_format(f, "log-level", "%i", manager->config_by_control.log_level);
+ if (r < 0)
+ return r;
+ }
+
+ if (manager->config_by_control.children_max > 0) {
+ r = serialize_item_format(f, "children-max", "%u", manager->config_by_control.children_max);
+ if (r < 0)
+ return r;
+ }
+
+ r = serialize_bool_elide(f, "trace", manager->config_by_control.trace);
+ if (r < 0)
+ return r;
+
+ const char *k, *v;
+ HASHMAP_FOREACH_KEY(v, k, manager->properties) {
+ r = serialize_item_format(f, "property", "%s=%s", k, v);
+ if (r < 0)
+ return r;
+ }
+
+ r = finish_serialization_file(f);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to finalize serialization file: %m");
+
+ /* Remove the previous serialization to make it replaced with the new one. */
+ (void) notify_remove_fd_warn("config-serialization");
+
+ r = notify_push_fd(fileno(f), "config-serialization");
+ if (r < 0)
+ return log_warning_errno(r, "Failed to push serialization fd to service manager: %m");
+
+ log_debug("Serialized configurations.");
+ return 0;
+}
+
+int manager_deserialize_config(Manager *manager, int *fd) {
+ int r;
+
+ assert(manager);
+ assert(fd);
+ assert(*fd >= 0);
+
+ /* This may invalidate passed file descriptor even on failure. */
+
+ _cleanup_fclose_ FILE *f = take_fdopen(fd, "r");
+ if (!f)
+ return log_warning_errno(errno, "Failed to open serialization fd: %m");
+
+ for (;;) {
+ _cleanup_free_ char *l = NULL;
+ const char *val;
+
+ r = deserialize_read_line(f, &l);
+ if (r < 0)
+ return r;
+ if (r == 0) /* eof or end marker */
+ break;
+
+ if ((val = startswith(l, "log-level="))) {
+ r = log_level_from_string(val);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse serialized log level (%s), ignoring: %m", val);
+ else
+ manager->config_by_control.log_level = r;
+
+ } else if ((val = startswith(l, "children-max="))) {
+ r = safe_atou(val, &manager->config_by_control.children_max);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse serialized children max (%s), ignoring: %m", val);
+
+ } else if ((val = startswith(l, "trace="))) {
+ r = parse_boolean(val);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse serialized trace (%s), ignoring: %m", val);
+ else
+ manager->config_by_control.trace = r;
+
+ } else if ((val = startswith(l, "property="))) {
+ if (!udev_property_assignment_is_valid(val))
+ r = -EINVAL;
+ else
+ r = manager_set_environment_one(manager, val);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize property (%s), ignoring: %m", val);
+
+ } else
+ log_debug("Unknown serialization item, ignoring: %s", l);
+ }
+
+ manager_merge_config(manager);
+ manager_adjust_config(&manager->config);
+
+ log_debug("Deserialized configurations.");
return 0;
}
diff --git a/src/udev/udev-config.h b/src/udev/udev-config.h
index 339d426a0f..127b3b9a1b 100644
--- a/src/udev/udev-config.h
+++ b/src/udev/udev-config.h
@@ -34,5 +34,9 @@ void manager_set_environment(Manager *manager, char * const *v);
int manager_load(Manager *manager, int argc, char *argv[]);
UdevReloadFlags manager_reload_config(Manager *manager);
+UdevReloadFlags manager_revert_config(Manager *manager);
+
+int manager_serialize_config(Manager *manager);
+int manager_deserialize_config(Manager *manager, int *fd);
usec_t manager_kill_worker_timeout(Manager *manager);
diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c
index 8d27373b45..d35100198b 100644
--- a/src/udev/udev-manager.c
+++ b/src/udev/udev-manager.c
@@ -237,6 +237,7 @@ void manager_exit(Manager *manager) {
/* close sources of new events and discard buffered events */
manager->ctrl = udev_ctrl_unref(manager->ctrl);
manager->varlink_server = sd_varlink_server_unref(manager->varlink_server);
+ (void) manager_serialize_config(manager);
/* Disable the event source, but does not close the inotify fd here, as we may still receive
* notification messages about requests to add or remove inotify watches. */
@@ -311,6 +312,17 @@ void manager_reload(Manager *manager, bool force) {
notify_ready(manager);
}
+void manager_revert(Manager *manager) {
+ assert(manager);
+
+ UdevReloadFlags flags = manager_revert_config(manager);
+ if (flags == 0)
+ return;
+
+ assert(flags == UDEV_RELOAD_KILL_WORKERS);
+ manager_kill_workers(manager, SIGTERM);
+}
+
static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(worker_freep) Worker *worker = ASSERT_PTR(userdata);
sd_device *dev = worker->event ? ASSERT_PTR(worker->event->dev) : NULL;
@@ -1167,6 +1179,8 @@ static int manager_listen_fds(Manager *manager) {
r = manager_init_device_monitor(manager, fd);
else if (streq(names[i], "inotify"))
r = manager_init_inotify(manager, fd);
+ else if (streq(names[i], "config-serialization"))
+ r = manager_deserialize_config(manager, &fd);
else
r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Received unexpected fd (%s), ignoring.", names[i]);
diff --git a/src/udev/udev-manager.h b/src/udev/udev-manager.h
index fc2df313e2..806adb3ef8 100644
--- a/src/udev/udev-manager.h
+++ b/src/udev/udev-manager.h
@@ -70,6 +70,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
int manager_main(Manager *manager);
void manager_reload(Manager *manager, bool force);
+void manager_revert(Manager *manager);
void manager_exit(Manager *manager);
void notify_ready(Manager *manager);
diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c
index 41f7fe08f9..d0e85c2633 100644
--- a/src/udev/udev-varlink.c
+++ b/src/udev/udev-varlink.c
@@ -103,6 +103,21 @@ static int vl_method_set_environment(sd_varlink *link, sd_json_variant *paramete
return sd_varlink_reply(link, NULL);
}
+static int vl_method_revert(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+ Manager *manager = ASSERT_PTR(userdata);
+ int r;
+
+ assert(link);
+
+ r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL);
+ if (r != 0)
+ return r;
+
+ log_debug("Received io.systemd.Udev.Revert()");
+ manager_revert(manager);
+ return sd_varlink_reply(link, NULL);
+}
+
static int vl_method_start_stop_exec_queue(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
const char *method;
@@ -183,6 +198,7 @@ int manager_start_varlink_server(Manager *manager) {
"io.systemd.Udev.SetTrace", vl_method_set_trace,
"io.systemd.Udev.SetChildrenMax", vl_method_set_children_max,
"io.systemd.Udev.SetEnvironment", vl_method_set_environment,
+ "io.systemd.Udev.Revert", vl_method_revert,
"io.systemd.Udev.StartExecQueue", vl_method_start_stop_exec_queue,
"io.systemd.Udev.StopExecQueue", vl_method_start_stop_exec_queue,
"io.systemd.Udev.Exit", vl_method_exit);
diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c
index 1fa5a42018..f9cc4c34cf 100644
--- a/src/udev/udevadm-control.c
+++ b/src/udev/udevadm-control.c
@@ -32,6 +32,7 @@ static int arg_max_children = -1;
static int arg_log_level = -1;
static int arg_start_exec_queue = -1;
static int arg_trace = -1;
+static bool arg_revert = false;
static bool arg_load_credentials = false;
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
@@ -45,7 +46,8 @@ static bool arg_has_control_commands(void) {
!strv_isempty(arg_env) ||
arg_max_children >= 0 ||
arg_ping ||
- arg_trace >= 0;
+ arg_trace >= 0 ||
+ arg_revert;
}
static int help(void) {
@@ -62,6 +64,7 @@ static int help(void) {
" -m --children-max=N Maximum number of children\n"
" --ping Wait for udev to respond to a ping message\n"
" --trace=BOOL Enable/disable trace logging\n"
+ " --revert Revert previously set configurations\n"
" -t --timeout=SECONDS Maximum time to block for a reply\n"
" --load-credentials Load udev rules from credentials\n",
program_invocation_short_name);
@@ -73,6 +76,7 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_PING = 0x100,
ARG_TRACE,
+ ARG_REVERT,
ARG_LOAD_CREDENTIALS,
};
@@ -89,6 +93,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "children-max", required_argument, NULL, 'm' },
{ "ping", no_argument, NULL, ARG_PING },
{ "trace", required_argument, NULL, ARG_TRACE },
+ { "revert", no_argument, NULL, ARG_REVERT },
{ "timeout", required_argument, NULL, 't' },
{ "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS },
{ "version", no_argument, NULL, 'V' },
@@ -157,6 +162,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_trace = r;
break;
+ case ARG_REVERT:
+ arg_revert = true;
+ break;
+
case 't':
r = parse_sec(optarg, &arg_timeout);
if (r < 0)
@@ -270,6 +279,12 @@ static int send_control_commands(void) {
if (arg_exit)
return varlink_call_and_log(link, "io.systemd.Udev.Exit", /* parameters = */ NULL, /* reply = */ NULL);
+ if (arg_revert) {
+ r = varlink_call_and_log(link, "io.systemd.Udev.Revert", /* parameters = */ NULL, /* reply = */ NULL);
+ if (r < 0)
+ return r;
+ }
+
if (arg_log_level >= 0) {
r = varlink_callbo_and_log(link, "io.systemd.service.SetLogLevel", /* reply = */ NULL,
SD_JSON_BUILD_PAIR_INTEGER("level", arg_log_level));
diff --git a/test/units/TEST-17-UDEV.global-property.sh b/test/units/TEST-17-UDEV.global-property.sh
index 152922b364..c9b070e203 100755
--- a/test/units/TEST-17-UDEV.global-property.sh
+++ b/test/units/TEST-17-UDEV.global-property.sh
@@ -86,4 +86,33 @@ udevadm trigger --action change --settle /dev/null
test_property /dev/null PROP_FOO foo
test_not_property /dev/null PROP_BAR
+: revert
+
+udevadm control --revert
+udevadm trigger --action change --settle /dev/null
+test_not_property /dev/null PROP_FOO
+test_not_property /dev/null PROP_BAR
+
+: set again, and restart
+
+udevadm control -p FOO=foo -p BAR=bar
+udevadm trigger --action change --settle /dev/null
+test_property /dev/null PROP_FOO foo
+test_property /dev/null PROP_BAR bar
+systemctl restart systemd-udevd.service
+udevadm trigger --action change --settle /dev/null
+test_property /dev/null PROP_FOO foo
+test_property /dev/null PROP_BAR bar
+
+: revert again, and restart
+
+udevadm control --revert
+udevadm trigger --action change --settle /dev/null
+test_not_property /dev/null PROP_FOO
+test_not_property /dev/null PROP_BAR
+systemctl restart systemd-udevd.service
+udevadm trigger --action change --settle /dev/null
+test_not_property /dev/null PROP_FOO
+test_not_property /dev/null PROP_BAR
+
exit 0
diff --git a/test/units/TEST-17-UDEV.sanity-check.sh b/test/units/TEST-17-UDEV.sanity-check.sh
index 5bbf42a5cf..125a5946f7 100755
--- a/test/units/TEST-17-UDEV.sanity-check.sh
+++ b/test/units/TEST-17-UDEV.sanity-check.sh
@@ -61,10 +61,13 @@ udevadm control -R
udevadm control -p HELLO=world
udevadm control -m 42
udevadm control --ping -t 5
-udevadm control --trace yes
udevadm control --trace no
+udevadm control --trace yes
udevadm control --load-credentials
udevadm control -h
+# Sanity check for serialization and deserialization
+systemctl restart systemd-udevd.service
+udevadm control --revert
udevadm info /dev/null
udevadm info /sys/class/net/$netdev