From 006a60e079fb49d7cdebec3d566a83c6a370e3d4 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 5 Apr 2023 20:14:53 +0200 Subject: fstab-generator: don't propagate ignored errno With certain fstabs we may propagate ENXIO from the $SYSTEMD_SYSFS_CHECK check all the way up, making fstab-generator exit with a non-zero EC and without any helpful message, which is really confusing. --- src/fstab-generator/fstab-generator.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 69effacaa2..cc2c5512dd 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -698,10 +698,10 @@ static int parse_fstab(bool initrd) { } if (sysfs_check < 0) { - r = getenv_bool_secure("SYSTEMD_SYSFS_CHECK"); - if (r < 0 && r != -ENXIO) - log_debug_errno(r, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m"); - sysfs_check = r != 0; + k = getenv_bool_secure("SYSTEMD_SYSFS_CHECK"); + if (k < 0 && k != -ENXIO) + log_debug_errno(k, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m"); + sysfs_check = k != 0; } if (sysfs_check && is_device_path(what)) { -- cgit v1.2.1 From 9948a169c0b2826b4c7512f5ec09a723d8eea05a Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 3 Apr 2023 18:32:58 +0200 Subject: test: add a couple of tests for systemd-modules-load --- test/units/testsuite-74.modules-load.sh | 88 +++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100755 test/units/testsuite-74.modules-load.sh diff --git a/test/units/testsuite-74.modules-load.sh b/test/units/testsuite-74.modules-load.sh new file mode 100755 index 0000000000..3d00e07f07 --- /dev/null +++ b/test/units/testsuite-74.modules-load.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +MODULES_LOAD_BIN="/usr/lib/systemd/systemd-modules-load" +CONFIG_FILE="/run/modules-load.d/99-test.conf" + +at_exit() { + rm -rfv "${CONFIG_FILE:?}" +} + +trap at_exit EXIT + +if systemd-detect-virt -cq; then + echo "Running in a container, skipping the systemd-modules-load test..." + exit 0 +fi + +# Check if we have required kernel modules +modprobe --all --resolve-alias loop dummy + +mkdir -p /run/modules-load.d/ + +"$MODULES_LOAD_BIN" +"$MODULES_LOAD_BIN" --help +"$MODULES_LOAD_BIN" --version + +# Explicit config file +modprobe -v --all --remove loop dummy +printf "loop\ndummy" >"$CONFIG_FILE" +"$MODULES_LOAD_BIN" "$CONFIG_FILE" |& tee /tmp/out.log +grep -E "Inserted module .*loop" /tmp/out.log +grep -E "Inserted module .*dummy" /tmp/out.log + +# Implicit config file +modprobe -v --all --remove loop dummy +printf "loop\ndummy" >"$CONFIG_FILE" +"$MODULES_LOAD_BIN" |& tee /tmp/out.log +grep -E "Inserted module .*loop" /tmp/out.log +grep -E "Inserted module .*dummy" /tmp/out.log + +# Valid & invalid data mixed together +modprobe -v --all --remove loop dummy +cat >"$CONFIG_FILE" < Date: Mon, 3 Apr 2023 22:38:37 +0200 Subject: test: introduce TEST-81-GENERATORS Add some explicit tests for various generators we ship, e.g.: - systemd-debug-generator - systemd-environment-d-generator - systemd-fstab-generator --- test/TEST-81-GENERATORS/Makefile | 1 + test/TEST-81-GENERATORS/test.sh | 10 + test/units/generator-utils.sh | 61 ++++ test/units/testsuite-81.debug-generator.sh | 105 ++++++ test/units/testsuite-81.environment-d-generator.sh | 80 +++++ test/units/testsuite-81.fstab-generator.sh | 360 +++++++++++++++++++++ test/units/testsuite-81.service | 8 + test/units/testsuite-81.sh | 16 + 8 files changed, 641 insertions(+) create mode 120000 test/TEST-81-GENERATORS/Makefile create mode 100755 test/TEST-81-GENERATORS/test.sh create mode 100644 test/units/generator-utils.sh create mode 100755 test/units/testsuite-81.debug-generator.sh create mode 100755 test/units/testsuite-81.environment-d-generator.sh create mode 100755 test/units/testsuite-81.fstab-generator.sh create mode 100644 test/units/testsuite-81.service create mode 100755 test/units/testsuite-81.sh diff --git a/test/TEST-81-GENERATORS/Makefile b/test/TEST-81-GENERATORS/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-81-GENERATORS/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-81-GENERATORS/test.sh b/test/TEST-81-GENERATORS/test.sh new file mode 100755 index 0000000000..6c2f0d34aa --- /dev/null +++ b/test/TEST-81-GENERATORS/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="Test systemd generators" + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +do_test "$@" diff --git a/test/units/generator-utils.sh b/test/units/generator-utils.sh new file mode 100644 index 0000000000..1973efefba --- /dev/null +++ b/test/units/generator-utils.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +link_endswith() { + [[ -h "${1:?}" && "$(readlink "${1:?}")" =~ ${2:?}$ ]] +} + +link_eq() { + [[ -h "${1:?}" && "$(readlink "${1:?}")" == "${2:?}" ]] +} + +# Get the value from a 'key=value' assignment +opt_get_arg() { + local arg + + IFS="=" read -r _ arg <<< "${1:?}" + test -n "$arg" + echo "$arg" +} + +in_initrd() { + [[ "${SYSTEMD_IN_INITRD:-0}" -ne 0 ]] +} + +# Check if we're parsing host's fstab in initrd +in_initrd_host() { + in_initrd && [[ "${SYSTEMD_SYSROOT_FSTAB:-/dev/null}" != /dev/null ]] +} + +in_container() { + systemd-detect-virt -qc +} + +# Filter out "unwanted" options, i.e. options that the fstab-generator doesn't +# propagate to the final mount unit +opt_filter_consumed() {( + set +x + local opt split_options filtered_options + + IFS="," read -ra split_options <<< "${1:?}" + for opt in "${split_options[@]}"; do + if [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then + continue + fi + + filtered_options+=("$opt") + done + + IFS=","; printf "%s" "${filtered_options[*]}" +)} + +# Run the given generator $1 with target directory $2 - clean the target +# directory beforehand +run_and_list() { + local generator="${1:?}" + local out_dir="${2:?}" + + rm -fr "${out_dir:?}"/* + "$generator" "$out_dir" + ls -lR "$out_dir" +} diff --git a/test/units/testsuite-81.debug-generator.sh b/test/units/testsuite-81.debug-generator.sh new file mode 100755 index 0000000000..d2b2bf1111 --- /dev/null +++ b/test/units/testsuite-81.debug-generator.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-debug-generator" +OUT_DIR="$(mktemp -d /tmp/debug-generator.XXX)" + +at_exit() { + rm -frv "${OUT_DIR:?}" +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +# Potential FIXME: +# - debug-generator should gracefully handle duplicated mask/wants +# - also, handle gracefully empty mask/wants +ARGS=( + "systemd.mask=masked-no-suffix" + "systemd.mask=masked.service" + "systemd.mask=masked.socket" + "systemd.wants=wanted-no-suffix" + "systemd.wants=wanted.service" + "systemd.wants=wanted.mount" + "rd.systemd.mask=masked-initrd.service" + "rd.systemd.wants=wanted-initrd.service" +) + +# Regular (non-initrd) scenario +# +: "debug-shell: regular" +CMDLINE="ro root=/ ${ARGS[*]} rd.systemd.debug_shell" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null +link_eq "$OUT_DIR/masked.service" /dev/null +link_eq "$OUT_DIR/masked.socket" /dev/null +link_endswith "$OUT_DIR/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service +link_endswith "$OUT_DIR/default.target.wants/wanted.service" /lib/systemd/system/wanted.service +link_endswith "$OUT_DIR/default.target.wants/wanted.mount" /lib/systemd/system/wanted.mount +# Following stuff should be ignored, as it's prefixed with rd. +test ! -h "$OUT_DIR/masked-initrd.service" +test ! -h "$OUT_DIR/default.target.wants/wants-initrd.service" +test ! -h "$OUT_DIR/default.target.wants/debug-shell.service" +test ! -d "$OUT_DIR/initrd.target.wants" + +# Let's re-run the generator with systemd.debug_shell that should be honored +: "debug-shell: regular + systemd.debug_shell" +CMDLINE="$CMDLINE systemd.debug_shell" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service + +# Same thing, but with custom tty +: "debug-shell: regular + systemd.debug_shell=/dev/tty666" +CMDLINE="$CMDLINE systemd.debug_shell=/dev/tty666" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service +grep -F "/dev/tty666" "$OUT_DIR/debug-shell.service.d/50-tty.conf" + +# Now override the default target via systemd.unit= +: "debug-shell: regular + systemd.unit=" +CMDLINE="$CMDLINE systemd.unit=my-fancy.target" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null +link_eq "$OUT_DIR/masked.service" /dev/null +link_eq "$OUT_DIR/masked.socket" /dev/null +link_endswith "$OUT_DIR/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service +link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service +link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount +link_endswith "$OUT_DIR/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service +test ! -d "$OUT_DIR/default.target.wants" + + +# Initrd scenario +: "debug-shell: initrd" +CMDLINE="ro root=/ ${ARGS[*]} systemd.debug_shell" +SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/masked-initrd.service" /dev/null +link_endswith "$OUT_DIR/initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service +# The non-initrd stuff (i.e. without the rd. suffix) should be ignored in +# this case +test ! -h "$OUT_DIR/masked-no-suffix.service" +test ! -h "$OUT_DIR/masked.service" +test ! -h "$OUT_DIR/masked.socket" +test ! -h "$OUT_DIR/initrd.target.wants/debug-shell.service" +test ! -d "$OUT_DIR/default.target.wants" + +# Again, but with rd.systemd.debug_shell +: "debug-shell: initrd + rd.systemd.debug_shell" +CMDLINE="$CMDLINE rd.systemd.debug_shell" +SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/initrd.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service + +# Override the default target +: "debug-shell: initrd + rd.systemd.unit" +CMDLINE="$CMDLINE rd.systemd.unit=my-fancy-initrd.target" +SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/masked-initrd.service" /dev/null +link_endswith "$OUT_DIR/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service +test ! -d "$OUT_DIR/initrd.target.wants" diff --git a/test/units/testsuite-81.environment-d-generator.sh b/test/units/testsuite-81.environment-d-generator.sh new file mode 100755 index 0000000000..5bc3978951 --- /dev/null +++ b/test/units/testsuite-81.environment-d-generator.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator" +CONFIG_FILE="/run/environment.d/99-test.conf" +OUT_FILE="$(mktemp)" + +at_exit() { + set +e + rm -frv "${CONFIG_FILE:?}" "${OUT_FILE:?}" + systemctl -M testuser@.host --user daemon-reload +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" +mkdir -p /run/environment.d/ + +cat >"$CONFIG_FILE" <"$OUT_FILE" + +# Check if the generator is correctly called in a user session +systemctl -M testuser@.host --user daemon-reload +systemctl -M testuser@.host --user show-environment | tee "$OUT_FILE" +check_environment "$OUT_FILE" + +(! "$GENERATOR_BIN" foo) diff --git a/test/units/testsuite-81.fstab-generator.sh b/test/units/testsuite-81.fstab-generator.sh new file mode 100755 index 0000000000..a9efc0bc7a --- /dev/null +++ b/test/units/testsuite-81.fstab-generator.sh @@ -0,0 +1,360 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235,SC2233 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +export SYSTEMD_LOG_LEVEL=debug + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-fstab-generator" +NETWORK_FS_RX="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$" +OUT_DIR="$(mktemp -d /tmp/fstab-generator.XXX)" +FSTAB="$(mktemp)" + +at_exit() { + rm -fr "${OUT_DIR:?}" "${FSTAB:?}" +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +FSTAB_GENERAL=( + # Valid entries + "/dev/test2 /nofail ext4 nofail 0 0" + "/dev/test3 /regular btrfs defaults 0 0" + "/dev/test4 /x-systemd.requires xfs x-systemd.requires=foo.service 0 0" + "/dev/test5 /x-systemd.before-after xfs x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0" + "/dev/test6 /x-systemd.wanted-required-by xfs x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0" + "/dev/test7 /x-systemd.requires-mounts-for xfs x-systemd.requires-mounts-for=/foo/bar/baz 0 0" + "/dev/test8 /x-systemd.automount-idle-timeout vfat x-systemd.automount,x-systemd.idle-timeout=50s 0 0" + "/dev/test9 /x-systemd.makefs xfs x-systemd.makefs 0 0" + "/dev/test10 /x-systemd.growfs xfs x-systemd.growfs 0 0" + "/dev/test11 /_netdev ext4 defaults,_netdev 0 0" + "/dev/test12 /_rwonly ext4 x-systemd.rw-only 0 0" + "/dev/test13 /chaos1 zfs x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0" + "/dev/test14 /chaos2 zfs x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0" + "/dev/test15 /fstype/auto auto defaults 0 0" + "/dev/test16 /fsck/me ext4 defaults 0 1" + "/dev/test17 /also/fsck/me ext4 defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99" + "/dev/test18 /swap swap defaults 0 0" + "/dev/test19 /swap/makefs swap defaults,x-systemd.makefs 0 0" + "/dev/test20 /var xfs defaults,x-systemd.device-timeout=1h 0 0" + "/dev/test21 /usr ext4 defaults 0 1" + "/dev/test22 /initrd/mount ext2 defaults,x-systemd.rw-only,x-initrd.mount 0 1" + "/dev/test23 /initrd/mount/nofail ext3 defaults,nofail,x-initrd.mount 0 1" + "/dev/test24 /initrd/mount/deps ext4 x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1" + + # Incomplete, but valid entries + "/dev/incomplete1 /incomplete1" + "/dev/incomplete2 /incomplete2 ext4" + "/dev/incomplete3 /incomplete3 ext4 defaults" + "/dev/incomplete4 /incomplete4 ext4 defaults 0" + + # Remote filesystems + "/dev/remote1 /nfs nfs bg 0 0" + "/dev/remote2 /nfs4 nfs4 bg 0 0" + "bar.tld:/store /remote/storage nfs ro,x-systemd.wanted-by=store.service 0 0" + "user@host.tld:/remote/dir /remote/top-secret sshfs rw,x-systemd.before=naughty.service 0 0" + "foo.tld:/hello /hello/world ceph defaults 0 0" + "//192.168.0.1/storage /cifs-storage cifs automount,nofail 0 0" +) + +FSTAB_GENERAL_ROOT=( + # rootfs with bunch of options we should ignore and fsck enabled + "/dev/test1 / ext4 noauto,nofail,automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1" + "${FSTAB_GENERAL[@]}" +) + +FSTAB_MINIMAL=( + "/dev/loop1 /foo/bar ext3 defaults 0 0" +) + +FSTAB_DUPLICATE=( + "/dev/dup1 / ext4 defaults 0 1" + "/dev/dup2 / ext4 defaults,x-systemd.requires=foo.mount 0 2" +) + +FSTAB_INVALID=( + # Ignored entries + "/dev/ignored1 /sys/fs/cgroup/foo ext4 defaults 0 0" + "/dev/ignored2 /sys/fs/selinux ext4 defaults 0 0" + "/dev/ignored3 /dev/console ext4 defaults 0 0" + "/dev/ignored4 /proc/kmsg ext4 defaults 0 0" + "/dev/ignored5 /proc/sys ext4 defaults 0 0" + "/dev/ignored6 /proc/sys/kernel/random/boot_id ext4 defaults 0 0" + "/dev/ignored7 /run/host ext4 defaults 0 0" + "/dev/ignored8 /run/host/foo ext4 defaults 0 0" + "/dev/ignored9 /autofs autofs defaults 0 0" + "/dev/invalid1 not-a-path ext4 defaults 0 0" + "" + "/dev/invalid1" + " " + "\\" + "$" +) + +check_fstab_mount_units() { + local what where fstype opts passno unit + local item opt split_options filtered_options supp service device arg + local array_name="${1:?}" + local out_dir="${2:?}" + # Get a reference to the array from its name + local -n fstab_entries="$array_name" + + # Running the checks in a container is pretty much useless, since we don't + # generate any mounts, but don't skip the whole test to test the "skip" + # paths as well + in_container && return 0 + + for item in "${fstab_entries[@]}"; do + # Don't use a pipe here, as it would make the variables out of scope + read -r what where fstype opts _ passno <<< "$item" + + # Skip non-initrd mounts in initrd + if in_initrd_host && ! [[ "$opts" =~ x-initrd.mount ]]; then + continue + fi + + if [[ "$fstype" == swap ]]; then + unit="$(systemd-escape --suffix=swap --path "${what:?}")" + cat "$out_dir/$unit" + + grep -qE "^What=$what$" "$out_dir/$unit" + if [[ "$opts" != defaults ]]; then + grep -qE "^Options=$opts$" "$out_dir/$unit" + fi + + if [[ "$opts" =~ x-systemd.makefs ]]; then + service="$(systemd-escape --template=systemd-mkswap@.service --path "$what")" + test -e "$out_dir/$service" + fi + + continue + fi + + # If we're parsing host's fstab in initrd, prefix all mount targets + # with /sysroot + in_initrd_host && where="/sysroot${where:?}" + unit="$(systemd-escape --suffix=mount --path "${where:?}")" + cat "$out_dir/$unit" + + # Check the general stuff + grep -qE "^What=$what$" "$out_dir/$unit" + grep -qE "^Where=$where$" "$out_dir/$unit" + if [[ -n "$fstype" ]] && [[ "$fstype" != auto ]]; then + grep -qE "^Type=$fstype$" "$out_dir/$unit" + fi + if [[ -n "$opts" ]] && [[ "$opts" != defaults ]]; then + # Some options are not propagated to the generated unit + filtered_options="$(opt_filter_consumed "$opts")" + if [[ "${filtered_options[*]}" != defaults ]]; then + grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit" + fi + fi + + if ! [[ "$opts" =~ (noauto|x-systemd.(wanted-by=|required-by=|automount)) ]]; then + # We don't create the Requires=/Wants= symlinks for noauto/automount mounts + # and for mounts that use x-systemd.wanted-by=/required-by= + if in_initrd_host; then + if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then + link_eq "$out_dir/initrd-fs.target.requires/$unit" "../$unit" + else + link_eq "$out_dir/initrd-fs.target.wants/$unit" "../$unit" + fi + elif [[ "$fstype" =~ $NETWORK_FS_RX || "$opts" =~ _netdev ]]; then + # Units with network filesystems should have a Requires= dependency + # on the remote-fs.target, unless they use nofail or are an nfs "bg" + # mounts, in which case the dependency is downgraded to Wants= + if [[ "$opts" =~ nofail ]] || [[ "$fstype" =~ ^(nfs|nfs4) && "$opts" =~ bg ]]; then + link_eq "$out_dir/remote-fs.target.wants/$unit" "../$unit" + else + link_eq "$out_dir/remote-fs.target.requires/$unit" "../$unit" + fi + else + # Similarly, local filesystems should have a Requires= dependency on + # the local-fs.target, unless they use nofail, in which case the + # dependency is downgraded to Wants=. Rootfs is a special case, + # since we always ignore nofail there + if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then + link_eq "$out_dir/local-fs.target.requires/$unit" "../$unit" + else + link_eq "$out_dir/local-fs.target.wants/$unit" "../$unit" + fi + fi + fi + + if [[ "${passno:=0}" -ne 0 ]]; then + # Generate systemd-fsck@.service dependencies, if applicable + if in_initrd && [[ "$where" == / || "$where" == /usr ]]; then + continue + fi + + if [[ "$where" == / ]]; then + link_endswith "$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service" + else + service="$(systemd-escape --template=systemd-fsck@.service --path "$what")" + grep -qE "^After=$service$" "$out_dir/$unit" + if [[ "$where" == /usr ]]; then + grep -qE "^Wants=$service$" "$out_dir/$unit" + else + grep -qE "^Requires=$service$" "$out_dir/$unit" + fi + fi + fi + + # Check various x-systemd options + # + # First, split them into an array to make splitting them even further + # easier + IFS="," read -ra split_options <<< "$opts" + # and process them one by one. + # + # Note: the "machinery" below might (and probably does) miss some + # combinations of supported options, so tread carefully + for opt in "${split_options[@]}"; do + if [[ "$opt" =~ ^x-systemd.requires= ]]; then + service="$(opt_get_arg "$opt")" + grep -qE "^Requires=$service$" "$out_dir/$unit" + grep -qE "^After=$service$" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd.before= ]]; then + service="$(opt_get_arg "$opt")" + grep -qE "^Before=$service$" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd.after= ]]; then + service="$(opt_get_arg "$opt")" + grep -qE "^After=$service$" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd.wanted-by= ]]; then + service="$(opt_get_arg "$opt")" + if [[ "$where" == / ]]; then + # This option is ignored for rootfs mounts + (! link_eq "$out_dir/$service.wants/$unit" "../$unit") + else + link_eq "$out_dir/$service.wants/$unit" "../$unit" + fi + elif [[ "$opt" =~ ^x-systemd.required-by= ]]; then + service="$(opt_get_arg "$opt")" + if [[ "$where" == / ]]; then + # This option is ignored for rootfs mounts + (! link_eq "$out_dir/$service.requires/$unit" "../$unit") + else + link_eq "$out_dir/$service.requires/$unit" "../$unit" + fi + elif [[ "$opt" =~ ^x-systemd.requires-mounts-for= ]]; then + arg="$(opt_get_arg "$opt")" + grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit" + elif [[ "$opt" == x-systemd.device-bound ]]; then + # This is implied for fstab mounts + : + elif [[ "$opt" == x-systemd.automount ]]; then + # The $unit should have an accompanying automount unit + supp="$(systemd-escape --suffix=automount --path "$where")" + test -e "$out_dir/$supp" + link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp" + elif [[ "$opt" =~ ^x-systemd.idle-timeout= ]]; then + # The timeout applies to the automount unit, not the original + # mount one + arg="$(opt_get_arg "$opt")" + supp="$(systemd-escape --suffix=automount --path "$where")" + grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp" + elif [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then + arg="$(opt_get_arg "$opt")" + device="$(systemd-escape --suffix=device --path "$what")" + grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf" + elif [[ "$opt" == x-systemd.makefs ]]; then + service="$(systemd-escape --template=systemd-makefs@.service --path "$what")" + test -e "$out_dir/$service" + link_eq "$out_dir/${unit}.requires/$service" "../$service" + elif [[ "$opt" == x-systemd.rw-only ]]; then + grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit" + elif [[ "$opt" == x-systemd.growfs ]]; then + service="$(systemd-escape --template=systemd-growfs@.service --path "$where")" + link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service" + elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then + # We "convert" nfs bg mounts to fg, so we can do the job-control + # ourselves + grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit" + grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd\. ]]; then + echo >&2 "Unhandled mount option: $opt" + exit 1 + fi + done + done +} + +# TODO +# - kernel arguments + +: "fstab-generator: regular" +printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR" + +# Skip the rest when running in a container, as it makes little sense to check +# initrd-related stuff there and fstab-generator might have a bit strange +# behavior during certain tests, like https://github.com/systemd/systemd/issues/27156 +if in_container; then + echo "Running in a container, skipping the rest of the fstab-generator tests..." + exit 0 +fi + +# In this mode we treat the entries as "regular" ones +: "fstab-generator: initrd - initrd fstab" +printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null check_fstab_mount_units FSTAB_GENERAL "$OUT_DIR" + +# In this mode we prefix the mount target with /sysroot and ignore all mounts +# that don't have the x-initrd.mount flag +: "fstab-generator: initrd - host fstab" +printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR" + +# Check the default stuff that we (almost) always create in initrd +: "fstab-generator: initrd default" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR" +test -e "$OUT_DIR/sysroot.mount" +test -e "$OUT_DIR/systemd-fsck-root.service" +link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount" +link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount" + +: "fstab-generator: run as systemd-sysroot-fstab-check in initrd" +ln -svf "$GENERATOR_BIN" /tmp/systemd-sysroot-fstab-check +(! /tmp/systemd-sysroot-fstab-check foo) +(! SYSTEMD_IN_INITRD=0 /tmp/systemd-sysroot-fstab-check) +printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB" +SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB="$FSTAB" /tmp/systemd-sysroot-fstab-check + +: "fstab-generator: duplicate" +printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB" +cat "$FSTAB" +(! SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR") + +: "fstab-generator: invalid" +printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB" +cat "$FSTAB" +# Don't care about the exit code here +SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" || : +# No mounts should get created here +[[ "$(find "$OUT_DIR" -name "*.mount" | wc -l)" -eq 0 ]] + +: "fstab-generator: kernel args - fstab=0" +printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB" +SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +(! SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR") +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR") + +: "fstab-generator: kernel args - rd.fstab=0" +printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB" +SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR") diff --git a/test/units/testsuite-81.service b/test/units/testsuite-81.service new file mode 100644 index 0000000000..3b697b3cff --- /dev/null +++ b/test/units/testsuite-81.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-81-GENERATORS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-81.sh b/test/units/testsuite-81.sh new file mode 100755 index 0000000000..63f4cb6430 --- /dev/null +++ b/test/units/testsuite-81.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +: >/failed + +systemctl log-level debug + +for script in "${0%.sh}".*.sh; do + echo "Running $script" + "./$script" +done + +touch /testok +rm /failed -- cgit v1.2.1