Skip to content

Commit 3399a30

Browse files
contrib: dracut: fix race with root=zfs:dset when necessities required
This had always worked in my testing, but a user on hardware reported this to happen 100%, and I reproduced it once with cold VM host caches. dracut-zfs-generator runs as a systemd generator, i.e. at Some Relatively Early Time; if root= is a fixed dataset, it tries to "solve [necessities] statically at generation time". If by that point zfs-import.target hasn't popped (because the import is taking a non-negligible amount of time for whatever reason), it'll see no children for the root datase, and as such generate no mounts. This has never had any right to work. No-one caught this earlier because it's just that much more convenient to have root=zfs:AUTO, which orders itself properly. To fix this, always run zfs-nonroot-necessities.service; this additionally simplifies the implementation by: * making BOOTFS from zfs-env-bootfs.service be the real, canonical, root dataset name, not just "whatever the first bootfs is", and only set it if we're ZFS-booting * zfs-{rollback,snapshot}-bootfs.service can use this instead of re-implementing it * having zfs-env-bootfs.service also set BOOTFSFLAGS * this means the sysroot.mount drop-in can be fixed text * zfs-nonroot-necessities.service can also be constant and always enabled, because it's conditioned on BOOTFS being set There is no longer any code generated at run-time (the sysroot.mount drop-in is an unavoidable gratuitous cp). The flow of BOOTFS{,FLAGS} from zfs-env-bootfs.service to sysroot.mount is not noted explicitly in dracut.zfs(7), because (a) at some point it's just visual noise and (b) it's already ordered via d-p-m.s from z-i.t. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Ahelenia Ziemiańska <[email protected]> Closes #14690
1 parent c5431f1 commit 3399a30

9 files changed

+54
-76
lines changed

contrib/dracut/90zfs/module-setup.sh.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ install() {
8181
inst_simple "${moddir}/zfs-env-bootfs.service" "${systemdsystemunitdir}/zfs-env-bootfs.service"
8282
systemctl -q --root "${initdir}" add-wants zfs-import.target zfs-env-bootfs.service
8383

84+
inst_simple "${moddir}/zfs-nonroot-necessities.service" "${systemdsystemunitdir}/zfs-nonroot-necessities.service"
85+
systemctl -q --root "${initdir}" add-requires initrd-root-fs.target zfs-nonroot-necessities.service
86+
8487
# Add user-provided unit overrides:
8588
# - /etc/systemd/system/${_service}
8689
# - /etc/systemd/system/${_service}.d/overrides.conf
Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
[Unit]
2-
Description=Set BOOTFS environment for dracut
3-
Documentation=man:zpool(8)
2+
Description=Set BOOTFS and BOOTFSFLAGS environment variables for dracut
43
DefaultDependencies=no
54
After=zfs-import-cache.service
65
After=zfs-import-scan.service
76
Before=zfs-import.target
87

98
[Service]
109
Type=oneshot
11-
ExecStart=/bin/sh -c "exec systemctl set-environment BOOTFS=$(@sbindir@/zpool list -H -o bootfs | grep -m1 -vFx -)"
10+
ExecStart=/bin/sh -c ' \
11+
. /lib/dracut-zfs-lib.sh; \
12+
decode_root_args || exit 0; \
13+
[ "$root" = "zfs:AUTO" ] && root="$(@sbindir@/zpool list -H -o bootfs | grep -m1 -vFx -)"; \
14+
rootflags="$(getarg rootflags=)"; \
15+
case ",$rootflags," in \
16+
*,zfsutil,*) ;; \
17+
,,) rootflags=zfsutil ;; \
18+
*) rootflags="zfsutil,$rootflags" ;; \
19+
esac; \
20+
exec systemctl set-environment BOOTFS="$root" BOOTFSFLAGS="$rootflags"'
1221

1322
[Install]
1423
WantedBy=zfs-import.target

contrib/dracut/90zfs/zfs-generator.sh.in

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,81 +14,24 @@ GENERATOR_DIR="$1"
1414
. /lib/dracut-zfs-lib.sh
1515
decode_root_args || exit 0
1616

17-
[ -z "${rootflags}" ] && rootflags=$(getarg rootflags=)
18-
case ",${rootflags}," in
19-
*,zfsutil,*) ;;
20-
,,) rootflags=zfsutil ;;
21-
*) rootflags="zfsutil,${rootflags}" ;;
22-
esac
23-
2417
[ -n "$debug" ] && echo "zfs-generator: writing extension for sysroot.mount to $GENERATOR_DIR/sysroot.mount.d/zfs-enhancement.conf" >> /dev/kmsg
2518

2619

27-
mkdir -p "$GENERATOR_DIR"/sysroot.mount.d "$GENERATOR_DIR"/initrd-root-fs.target.requires "$GENERATOR_DIR"/dracut-pre-mount.service.d
20+
mkdir -p "$GENERATOR_DIR"/sysroot.mount.d "$GENERATOR_DIR"/dracut-pre-mount.service.d
21+
2822
{
2923
echo "[Unit]"
3024
echo "Before=initrd-root-fs.target"
3125
echo "After=zfs-import.target"
3226
echo
3327
echo "[Mount]"
34-
if [ "${root}" = "zfs:AUTO" ]; then
35-
echo "PassEnvironment=BOOTFS"
36-
echo 'What=${BOOTFS}'
37-
else
38-
echo "What=${root}"
39-
fi
28+
echo "PassEnvironment=BOOTFS BOOTFSFLAGS"
29+
echo 'What=${BOOTFS}'
4030
echo "Type=zfs"
41-
echo "Options=${rootflags}"
31+
echo 'Options=${BOOTFSFLAGS}'
4232
} > "$GENERATOR_DIR"/sysroot.mount.d/zfs-enhancement.conf
4333
ln -fs ../sysroot.mount "$GENERATOR_DIR"/initrd-root-fs.target.requires/sysroot.mount
4434

45-
46-
if [ "${root}" = "zfs:AUTO" ]; then
47-
{
48-
echo "[Unit]"
49-
echo "Before=initrd-root-fs.target"
50-
echo "After=sysroot.mount"
51-
echo "DefaultDependencies=no"
52-
echo
53-
echo "[Service]"
54-
echo "Type=oneshot"
55-
echo "PassEnvironment=BOOTFS"
56-
echo "ExecStart=/bin/sh -c '" ' \
57-
. /lib/dracut-zfs-lib.sh; \
58-
_zfs_nonroot_necessities_cb() { \
59-
zfs mount | grep -m1 -q "^$1 " && return 0; \
60-
echo "Mounting $1 on /sysroot$2"; \
61-
mount -o zfsutil -t zfs "$1" "/sysroot$2"; \
62-
}; \
63-
for_relevant_root_children "${BOOTFS}" _zfs_nonroot_necessities_cb;' \
64-
"'"
65-
} > "$GENERATOR_DIR"/zfs-nonroot-necessities.service
66-
ln -fs ../zfs-nonroot-necessities.service "$GENERATOR_DIR"/initrd-root-fs.target.requires/zfs-nonroot-necessities.service
67-
else
68-
# We can solve this statically at generation time, so do!
69-
_zfs_generator_cb() {
70-
dset="${1}"
71-
mpnt="${2}"
72-
unit="$(systemd-escape --suffix=mount -p "/sysroot${mpnt}")"
73-
74-
{
75-
echo "[Unit]"
76-
echo "Before=initrd-root-fs.target"
77-
echo "After=sysroot.mount"
78-
echo
79-
echo "[Mount]"
80-
echo "Where=/sysroot${mpnt}"
81-
echo "What=${dset}"
82-
echo "Type=zfs"
83-
echo "Options=zfsutil"
84-
} > "$GENERATOR_DIR/${unit}"
85-
ln -fs ../"${unit}" "$GENERATOR_DIR"/initrd-root-fs.target.requires/"${unit}"
86-
}
87-
88-
for_relevant_root_children "${root}" _zfs_generator_cb
89-
fi
90-
91-
9235
{
9336
echo "[Unit]"
9437
echo "After=zfs-import.target"

contrib/dracut/90zfs/zfs-lib.sh.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ mount_dataset() {
3939

4040
# for_relevant_root_children DATASET EXEC
4141
# Runs "EXEC dataset mountpoint" for all children of DATASET that are needed for system bringup
42-
# Used by zfs-generator.sh and friends, too!
42+
# Used by zfs-nonroot-necessities.service and friends, too!
4343
for_relevant_root_children() {
4444
dataset="${1}"
4545
exec="${2}"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[Unit]
2+
Before=initrd-root-fs.target
3+
After=sysroot.mount
4+
DefaultDependencies=no
5+
ConditionEnvironment=BOOTFS
6+
7+
[Service]
8+
Type=oneshot
9+
PassEnvironment=BOOTFS
10+
ExecStart=/bin/sh -c ' \
11+
. /lib/dracut-zfs-lib.sh; \
12+
_zfs_nonroot_necessities_cb() { \
13+
@sbindir@/zfs mount | grep -m1 -q "^$1 " && return 0; \
14+
echo "Mounting $1 on /sysroot$2"; \
15+
mount -o zfsutil -t zfs "$1" "/sysroot$2"; \
16+
}; \
17+
for_relevant_root_children "${BOOTFS}" _zfs_nonroot_necessities_cb'
18+
19+
[Install]
20+
RequiredBy=initrd-root-fs.target

contrib/dracut/90zfs/zfs-rollback-bootfs.service.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ After=zfs-import.target dracut-pre-mount.service zfs-snapshot-bootfs.service
55
Before=dracut-mount.service
66
DefaultDependencies=no
77
ConditionKernelCommandLine=bootfs.rollback
8+
ConditionEnvironment=BOOTFS
89

910
[Service]
1011
Type=oneshot
11-
ExecStart=/bin/sh -c '. /lib/dracut-zfs-lib.sh; decode_root_args || exit; [ "$root" = "zfs:AUTO" ] && root="$BOOTFS"; SNAPNAME="$(getarg bootfs.rollback)"; exec @sbindir@/zfs rollback -Rf "$root@${SNAPNAME:-%v}"'
12+
ExecStart=/bin/sh -c '. /lib/dracut-lib.sh; SNAPNAME="$(getarg bootfs.rollback)"; exec @sbindir@/zfs rollback -Rf "$BOOTFS@${SNAPNAME:-%v}"'
1213
RemainAfterExit=yes

contrib/dracut/90zfs/zfs-snapshot-bootfs.service.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ After=zfs-import.target dracut-pre-mount.service
55
Before=dracut-mount.service
66
DefaultDependencies=no
77
ConditionKernelCommandLine=bootfs.snapshot
8+
ConditionEnvironment=BOOTFS
89

910
[Service]
1011
Type=oneshot
11-
ExecStart=-/bin/sh -c '. /lib/dracut-zfs-lib.sh; decode_root_args || exit; [ "$root" = "zfs:AUTO" ] && root="$BOOTFS"; SNAPNAME="$(getarg bootfs.snapshot)"; exec @sbindir@/zfs snapshot "$root@${SNAPNAME:-%v}"'
12+
ExecStart=-/bin/sh -c '. /lib/dracut-lib.sh; SNAPNAME="$(getarg bootfs.snapshot)"; exec @sbindir@/zfs snapshot "$BOOTFS@${SNAPNAME:-%v}"'
1213
RemainAfterExit=yes

contrib/dracut/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pkgdracut_90_SCRIPTS = \
1616

1717
pkgdracut_90_DATA = \
1818
%D%/90zfs/zfs-env-bootfs.service \
19+
%D%/90zfs/zfs-nonroot-necessities.service \
1920
%D%/90zfs/zfs-rollback-bootfs.service \
2021
%D%/90zfs/zfs-snapshot-bootfs.service
2122

man/man7/dracut.zfs.7

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.\" SPDX-License-Identifier: 0BSD
22
.\"
3-
.Dd April 4, 2022
3+
.Dd March 28, 2023
44
.Dt DRACUT.ZFS 7
55
.Os
66
.
@@ -28,21 +28,21 @@ zfs-import-scan.service \(da \(da | zfs-import-c
2828
zfs-import.target \(-> dracut-pre-mount.service
2929
| \(ua |
3030
| dracut-zfs-generator |
31-
| ____________________/|
31+
| _____________________/|
3232
|/ \(da
33-
| sysroot.mount \(<-\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em dracut-zfs-generator
34-
| | \(da |
35-
| \(da sysroot-{usr,etc,lib,&c.}.mount |
36-
| initrd-root-fs.target \(<-\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em or \(da
37-
| | zfs-nonroot-necessities.service
33+
| sysroot.mount \(<-\(em\(em\(em dracut-zfs-generator
34+
| |
35+
| \(da
36+
| initrd-root-fs.target \(<-\(em zfs-nonroot-necessities.service
37+
| | |
3838
| \(da |
3939
\(da dracut-mount.service |
4040
zfs-snapshot-bootfs.service | |
4141
| \(da |
4242
\(da … |
4343
zfs-rollback-bootfs.service | |
4444
| \(da |
45-
| sysroot-usr.mount \(<-\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em/
45+
| /sysroot/{usr,etc,lib,&c.} \(<-\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em\(em/
4646
| |
4747
| \(da
4848
| initrd-fs.target

0 commit comments

Comments
 (0)