Compare commits

...

10 Commits

Author SHA1 Message Date
Yu Watanabe 500356301d
Merge 91ca5afdb8 into 6fd3496cfd 2024-11-25 06:34:23 +00:00
Yu Watanabe 91ca5afdb8 TEST-17: check if udevd can be restarted within reasonably short time
Even if there are long running spawned processes.
2024-11-25 15:33:48 +09:00
Yu Watanabe f84fc84928 test-udev-spawn: add test cases for SIGTERM handling 2024-11-25 15:33:48 +09:00
Yu Watanabe 61fff36b55 udev-spawn: kill spawned process when worker received SIGTERM and skip remaining executions.
Otherwise, if long running program is spawned (of course, that's bad
configuration, but anyway), restarting udevd also takes longer, and
may be killed forcibly.
2024-11-25 15:33:48 +09:00
Yu Watanabe 9e52fac93d test-udev-spawn: migrate to use ASSERT_XYZ() 2024-11-25 15:33:48 +09:00
Yu Watanabe 675feaf521 TEST-17: add reproducer for issue #35329
Without the previous commit, the test case will fail.
2024-11-25 15:33:48 +09:00
Yu Watanabe c4fc22c4de core/device: ignore ID_PROCESSING udev property on enumerate
This partially reverts the commit 405be62f05
"tree-wide: refuse enumerated device with ID_PROCESSING=1".

Otherwise, when systemd-udev-trigger.service is (re)started just before
daemon-reexec, which can be easily happen on systemd package update, then
udev database files for many devices may have ID_PROCESSING=1 property,
thus devices may not be enumerated on daemon-reexec. That causes many
units especially mount units being deactivated after daemon-reexec.

Fixes #35329.
2024-11-25 15:33:48 +09:00
Luca Boccassi 6fd3496cfd test: mask tmpfiles.d file shipped by selinux policy package in containers
This tmpfiles.d wants to write to sysfs, which is read-only in containers,
so systemd-tmpfiles --create fails in TEST-22-TMPFILES when ran in nspawn
if the selinux policy package is instealled. Mask it, as it's not our
config file, we don't need it in the test.
2024-11-25 15:25:55 +09:00
Daan De Meyer bb486fe9df mkosi: Use shared extra tree between initrd and main image
Let's share more between initrd and main system and use a shared
extra tree to achieve that.
2024-11-25 15:09:58 +09:00
Daan De Meyer 0e44a351ea mkosi: Make sure mkosi.clangd always runs on the host
If the editor that invokes mkosi.clangd is a flatpak, let's make sure
that mkosi is run on the host and not in the flatpak sandbox since it
won't be installed there.
2024-11-25 00:21:10 +01:00
14 changed files with 168 additions and 30 deletions

View File

@ -1,12 +1,18 @@
#!/bin/bash
# SPDX-License-Identifier: LGPL-2.1-or-later
MKOSI_CONFIG="$(mkosi --json summary | jq -r .Images[-1])"
if command -v flatpak-spawn >/dev/null; then
SPAWN=(flatpak-spawn --host)
else
SPAWN=()
fi
MKOSI_CONFIG="$("${SPAWN[@]}" --host mkosi --json summary | jq -r .Images[-1])"
DISTRIBUTION="$(jq -r .Distribution <<< "$MKOSI_CONFIG")"
RELEASE="$(jq -r .Release <<< "$MKOSI_CONFIG")"
ARCH="$(jq -r .Architecture <<< "$MKOSI_CONFIG")"
exec mkosi \
exec "${SPAWN[@]}" mkosi \
--incremental=strict \
--build-sources-ephemeral=no \
--format=none \

View File

@ -38,9 +38,8 @@ SignExpectedPcr=yes
[Content]
ExtraTrees=
mkosi.extra.common
mkosi.crt:/usr/lib/verity.d/mkosi.crt # sysext verification key
mkosi.leak-sanitizer-suppressions:/usr/lib/systemd/leak-sanitizer-suppressions
mkosi.coredump-journal-storage.conf:/usr/lib/systemd/coredump.conf.d/10-coredump-journal-storage.conf
%O/minimal-0.root-%a.raw:/usr/share/minimal_0.raw
%O/minimal-0.root-%a-verity.raw:/usr/share/minimal_0.verity
%O/minimal-0.root-%a-verity-sig.raw:/usr/share/minimal_0.verity.sig

View File

@ -6,9 +6,7 @@ Include=
%D/mkosi.sanitizers
[Content]
ExtraTrees=
%D/mkosi.leak-sanitizer-suppressions:/usr/lib/systemd/leak-sanitizer-suppressions
%D/mkosi.coredump-journal-storage.conf:/usr/lib/systemd/coredump.conf.d/10-coredump-journal-storage.conf
ExtraTrees=%D/mkosi.extra.common
Packages=
findutils

View File

@ -1048,9 +1048,6 @@ static void device_enumerate(Manager *m) {
_cleanup_set_free_ Set *ready_units = NULL, *not_ready_units = NULL;
Device *d;
if (device_is_processed(dev) <= 0)
continue;
if (device_setup_units(m, dev, &ready_units, &not_ready_units) < 0)
continue;

View File

@ -10,17 +10,44 @@
#define BUF_SIZE 1024
static void test_event_spawn_core(bool with_pidfd, const char *cmd, char *result_buf, size_t buf_size) {
static void test_event_spawn_core(bool with_pidfd, const char *cmd, char *result_buf, size_t buf_size, int expected) {
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
_cleanup_(udev_event_freep) UdevEvent *event = NULL;
assert_se(setenv("SYSTEMD_PIDFD", yes_no(with_pidfd), 1) >= 0);
ASSERT_OK_ERRNO(setenv("SYSTEMD_PIDFD", yes_no(with_pidfd), 1));
assert_se(sd_device_new_from_syspath(&dev, "/sys/class/net/lo") >= 0);
assert_se(event = udev_event_new(dev, NULL, EVENT_TEST_SPAWN));
assert_se(udev_event_spawn(event, false, cmd, result_buf, buf_size, NULL) == 0);
ASSERT_OK(sd_device_new_from_syspath(&dev, "/sys/class/net/lo"));
ASSERT_NOT_NULL(event = udev_event_new(dev, NULL, EVENT_TEST_SPAWN));
ASSERT_EQ(udev_event_spawn(event, false, cmd, result_buf, buf_size, NULL), expected);
assert_se(unsetenv("SYSTEMD_PIDFD") >= 0);
ASSERT_OK_ERRNO(unsetenv("SYSTEMD_PIDFD"));
}
static void test_event_spawn_sleep_child(bool with_pidfd) {
_cleanup_free_ char *cmd = NULL;
log_debug("/* %s(%s) */", __func__, yes_no(with_pidfd));
ASSERT_OK(find_executable("sleep", &cmd));
ASSERT_NOT_NULL(strextend_with_separator(&cmd, " ", "1h"));
test_event_spawn_core(with_pidfd, cmd, NULL, 0, -EIO);
}
static void test_event_spawn_sleep(bool with_pidfd) {
PidRef pidref;
int r;
r = pidref_safe_fork("(test-worker)", FORK_LOG, &pidref);
ASSERT_OK(r);
if (r == 0) {
test_event_spawn_sleep_child(with_pidfd);
_exit(EXIT_SUCCESS);
}
ASSERT_OK(usleep_safe(USEC_PER_SEC));
ASSERT_OK(pidref_kill(&pidref, SIGTERM));
ASSERT_OK(wait_for_terminate_with_timeout(pidref.pid, 10 * USEC_PER_SEC));
}
static void test_event_spawn_cat(bool with_pidfd, size_t buf_size) {
@ -30,18 +57,18 @@ static void test_event_spawn_cat(bool with_pidfd, size_t buf_size) {
log_debug("/* %s(%s) */", __func__, yes_no(with_pidfd));
assert_se(find_executable("cat", &cmd) >= 0);
assert_se(strextend_with_separator(&cmd, " ", "/sys/class/net/lo/uevent"));
ASSERT_OK(find_executable("cat", &cmd));
ASSERT_NOT_NULL(strextend_with_separator(&cmd, " ", "/sys/class/net/lo/uevent"));
test_event_spawn_core(with_pidfd, cmd, result_buf,
buf_size >= BUF_SIZE ? BUF_SIZE : buf_size);
buf_size >= BUF_SIZE ? BUF_SIZE : buf_size, 0);
assert_se(lines = strv_split_newlines(result_buf));
ASSERT_NOT_NULL(lines = strv_split_newlines(result_buf));
strv_print(lines);
if (buf_size >= BUF_SIZE) {
assert_se(strv_contains(lines, "INTERFACE=lo"));
assert_se(strv_contains(lines, "IFINDEX=1"));
ASSERT_TRUE(strv_contains(lines, "INTERFACE=lo"));
ASSERT_TRUE(strv_contains(lines, "IFINDEX=1"));
}
}
@ -53,15 +80,15 @@ static void test_event_spawn_self(const char *self, const char *arg, bool with_p
log_debug("/* %s(%s, %s) */", __func__, arg, yes_no(with_pidfd));
/* 'self' may contain spaces, hence needs to be quoted. */
assert_se(cmd = strjoin("'", self, "' ", arg));
ASSERT_NOT_NULL(cmd = strjoin("'", self, "' ", arg));
test_event_spawn_core(with_pidfd, cmd, result_buf, BUF_SIZE);
test_event_spawn_core(with_pidfd, cmd, result_buf, BUF_SIZE, 0);
assert_se(lines = strv_split_newlines(result_buf));
ASSERT_NOT_NULL(lines = strv_split_newlines(result_buf));
strv_print(lines);
assert_se(strv_contains(lines, "aaa"));
assert_se(strv_contains(lines, "bbb"));
ASSERT_TRUE(strv_contains(lines, "aaa"));
ASSERT_TRUE(strv_contains(lines, "bbb"));
}
static void test1(void) {
@ -114,5 +141,7 @@ int main(int argc, char *argv[]) {
test_event_spawn_self(self, "test2", true);
test_event_spawn_self(self, "test2", false);
test_event_spawn_sleep(true);
test_event_spawn_sleep(false);
return 0;
}

View File

@ -16,6 +16,7 @@
#include "udev-trace.h"
typedef struct Spawn {
UdevWorker *worker;
sd_device *device;
const char *cmd;
pid_t pid;
@ -151,11 +152,27 @@ static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userd
return 1;
}
static int on_spawn_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
Spawn *spawn = ASSERT_PTR(userdata);
DEVICE_TRACE_POINT(spawn_sigterm, spawn->device, spawn->cmd);
log_device_error(spawn->device, "Worker process received SIGTERM, killing spawned process '%s' ["PID_FMT"].",
spawn->cmd, spawn->pid);
(void) kill_and_sigcont(spawn->pid, SIGTERM);
/* terminate the main event source of the worker. Note, the spawn->worker may be NULL in tests. */
if (spawn->worker)
(void) sd_event_exit(spawn->worker->event, 0);
return 1;
}
static int spawn_wait(Spawn *spawn) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *sigchld_source = NULL;
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *stdout_source = NULL;
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *stderr_source = NULL;
_cleanup_(sd_event_source_disable_unrefp) sd_event_source
*sigchld_source = NULL, *stdout_source = NULL, *stderr_source = NULL, *sigterm_source = NULL;
int r;
assert(spawn);
@ -201,11 +218,21 @@ static int spawn_wait(Spawn *spawn) {
r = sd_event_add_child(e, &sigchld_source, spawn->pid, WEXITED, on_spawn_sigchld, spawn);
if (r < 0)
return log_device_debug_errno(spawn->device, r, "Failed to create sigchild event source: %m");
/* SIGCHLD should be processed after IO is complete */
r = sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1);
if (r < 0)
return log_device_debug_errno(spawn->device, r, "Failed to set priority to sigchild event source: %m");
r = sd_event_add_signal(e, &sigterm_source, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_spawn_sigterm, spawn);
if (r < 0)
return log_device_debug_errno(spawn->device, r, "Failed to set SIGTERM event: %m");
/* SIGTERM should be processed with higher priorities with the others. */
r = sd_event_source_set_priority(sigterm_source, SD_EVENT_PRIORITY_NORMAL - 1);
if (r < 0)
return log_device_debug_errno(spawn->device, r, "Failed to set priority to sigchild event source: %m");
return sd_event_loop(e);
}
@ -239,6 +266,10 @@ int udev_event_spawn(
return 0;
}
if (event->worker && sd_event_get_exit_code(event->worker->event, NULL) >= 0)
return log_device_debug_errno(event->dev, SYNTHETIC_ERRNO(EIO),
"The main event loop of the worker process already terminating, skipping execution of '%s'.", cmd);
int timeout_signal = event->worker ? event->worker->timeout_signal : SIGKILL;
usec_t timeout_usec = event->worker ? event->worker->timeout_usec : DEFAULT_WORKER_TIMEOUT_USEC;
usec_t now_usec = now(CLOCK_MONOTONIC);
@ -304,6 +335,7 @@ int udev_event_spawn(
errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]);
spawn = (Spawn) {
.worker = event->worker,
.device = event->dev,
.cmd = cmd,
.pid = pid,

View File

@ -0,0 +1,69 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2317
set -ex
set -o pipefail
# This is a reproducer of issue #35329,
# which is a regression caused by 405be62f05d76f1845f347737b5972158c79dd3e.
IFNAME=udevtestnetif
at_exit() {
set +e
systemctl stop testsleep.service
rm -f /run/udev/udev.conf.d/timeout.conf
rm -f /run/udev/rules.d/99-testsuite.rules
# Check if udevd can be restarted within a reasonably short time.
timeout 10 systemctl restart systemd-udevd.service
ip link del "$IFNAME"
}
trap at_exit EXIT
udevadm settle
mkdir -p /run/udev/udev.conf.d/
cat >/run/udev/udev.conf.d/timeout.conf <<EOF
event_timeout=1h
EOF
mkdir -p /run/udev/rules.d/
cat >/run/udev/rules.d/99-testsuite.rules <<EOF
SUBSYSTEM=="net", ACTION=="change", KERNEL=="${IFNAME}", OPTIONS="log_level=debug", RUN+="/usr/bin/sleep 1000"
EOF
systemctl restart systemd-udevd.service
ip link add "$IFNAME" type dummy
IFINDEX=$(ip -json link show "$IFNAME" | jq '.[].ifindex')
udevadm wait --timeout 10 "/sys/class/net/${IFNAME}"
# Check if the database file is created.
[[ -e "/run/udev/data/n${IFINDEX}" ]]
systemd-run \
-p After="sys-subsystem-net-devices-${IFNAME}.device" \
-p BindsTo="sys-subsystem-net-devices-${IFNAME}.device" \
-u testsleep.service \
sleep 1h
timeout 10 bash -c 'until systemctl is-active testsleep.service; do sleep .5; done'
udevadm trigger "/sys/class/net/${IFNAME}"
timeout 30 bash -c "until grep -F 'ID_PROCESSING=1' /run/udev/data/n${IFINDEX}; do sleep .5; done"
for _ in {1..3}; do
systemctl daemon-reexec
systemctl is-active testsleep.service
done
for _ in {1..3}; do
systemctl daemon-reload
systemctl is-active testsleep.service
done
# Check if the reexec and reload have finished during processing the event.
grep -F 'ID_PROCESSING=1' "/run/udev/data/n${IFINDEX}"
exit 0

View File

@ -6,6 +6,14 @@ set -o pipefail
# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
if systemd-detect-virt --quiet --container; then
# This comes from the selinux package and tries to write
# some files under sysfs, which will be read-only in a container,
# so mask it. It's not our tmpfiles.d file anyway.
mkdir -p /run/tmpfiles.d/
ln -s /dev/null /run/tmpfiles.d/selinux-policy.conf
fi
run_subtests
touch /testok