udev: support reverting/serializing/deserializing configurations set by 'udevadm control' (#37067)

Previously, log level, properties, maximum number of worker processes,
and so on set by 'udevadm control' are discarded on restart. This makes
the configuration serialized on stop and deserialized in next
invocation. Also, this introduces 'udevadm control --revert' to clear
previous configurations.
This commit is contained in:
Yu Watanabe
2025-04-12 06:47:16 +09:00
committed by GitHub
14 changed files with 249 additions and 13 deletions
+10
View File
@@ -769,6 +769,16 @@
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--revert</option></term>
<listitem>
<para>Revert settings previously set with <command>udevadm control</command> command. When
specified, settings set with <option>-l/--log-level=</option>, <option>--trace</option>,
<option>-m/--children-max=</option>, and <option>-p/--property=</option> will be cleared.</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-t</option></term>
<term><option>--timeout=<replaceable>seconds</replaceable></option></term>
+1 -1
View File
@@ -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'
+1
View File
@@ -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'
}
+1 -1
View File
@@ -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);
+1
View File
@@ -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);
+4
View File
@@ -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,
+147 -9
View File
@@ -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;
}
+4
View File
@@ -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);
+14
View File
@@ -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]);
+1
View File
@@ -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);
+16
View File
@@ -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);
+16 -1
View File
@@ -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));
@@ -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
+4 -1
View File
@@ -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