2 # SPDX-License-Identifier: LGPL-2.1-or-later
3 # shellcheck disable=SC2235,SC2233
7 # shellcheck source=test/units/generator-utils.sh
8 .
"$(dirname "$0")/generator-utils.sh"
10 GENERATOR_BIN
="/usr/lib/systemd/system-generators/systemd-fstab-generator"
11 NETWORK_FS_RX
="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$"
12 OUT_DIR
="$(mktemp -d /tmp/fstab-generator.XXX)"
16 mountpoint
-q /proc
/cmdline
&& umount
/proc
/cmdline
17 rm -fr "${OUT_DIR:?}" "${FSTAB:?}"
22 test -x "${GENERATOR_BIN:?}"
26 "/dev/test2 /nofail ext4 nofail 0 0"
27 "/dev/test3 /regular btrfs defaults 0 0"
28 "/dev/test4 /x-systemd.requires xfs x-systemd.requires=foo.service 0 0"
29 "/dev/test5 /x-systemd.before-after xfs x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0"
30 "/dev/test6 /x-systemd.wanted-required-by xfs x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0"
31 "/dev/test7 /x-systemd.requires-mounts-for xfs x-systemd.requires-mounts-for=/foo/bar/baz 0 0"
32 "/dev/test8 /x-systemd.automount-idle-timeout vfat x-systemd.automount,x-systemd.idle-timeout=50s 0 0"
33 "/dev/test9 /x-systemd.makefs xfs x-systemd.makefs 0 0"
34 "/dev/test10 /x-systemd.growfs xfs x-systemd.growfs 0 0"
35 "/dev/test11 /_netdev ext4 defaults,_netdev 0 0"
36 "/dev/test12 /_rwonly ext4 x-systemd.rw-only 0 0"
37 "/dev/test13 /chaos1 zfs x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0"
38 "/dev/test14 /chaos2 zfs x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0"
39 "/dev/test15 /fstype/auto auto defaults 0 0"
40 "/dev/test16 /fsck/me ext4 defaults 0 1"
41 "/dev/test17 /also/fsck/me ext4 defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99"
42 "/dev/test18 /swap swap defaults 0 0"
43 "/dev/test19 /swap/makefs swap defaults,x-systemd.makefs 0 0"
44 "/dev/test20 /var xfs defaults,x-systemd.device-timeout=1h 0 0"
45 "/dev/test21 /usr ext4 defaults 0 1"
46 "/dev/test22 /initrd/mount ext2 defaults,x-systemd.rw-only,x-initrd.mount 0 1"
47 "/dev/test23 /initrd/mount/nofail ext3 defaults,nofail,x-initrd.mount 0 1"
48 "/dev/test24 /initrd/mount/deps ext4 x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1"
50 # Incomplete, but valid entries
51 "/dev/incomplete1 /incomplete1"
52 "/dev/incomplete2 /incomplete2 ext4"
53 "/dev/incomplete3 /incomplete3 ext4 defaults"
54 "/dev/incomplete4 /incomplete4 ext4 defaults 0"
57 "/dev/remote1 /nfs nfs bg 0 0"
58 "/dev/remote2 /nfs4 nfs4 bg 0 0"
59 "bar.tld:/store /remote/storage nfs ro,x-systemd.wanted-by=store.service 0 0"
60 "user@host.tld:/remote/dir /remote/top-secret sshfs rw,x-systemd.before=naughty.service 0 0"
61 "foo.tld:/hello /hello/world ceph defaults 0 0"
62 "//192.168.0.1/storage /cifs-storage cifs automount,nofail 0 0"
66 # rootfs with bunch of options we should ignore and fsck enabled
67 "/dev/test1 / ext4 noauto,nofail,x-systemd.automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1"
72 "/dev/loop1 /foo/bar ext3 defaults 0 0"
76 "/dev/dup1 / ext4 defaults 0 1"
77 "/dev/dup2 / ext4 defaults,x-systemd.requires=foo.mount 0 2"
82 "/dev/ignored1 /sys/fs/cgroup/foo ext4 defaults 0 0"
83 "/dev/ignored2 /sys/fs/selinux ext4 defaults 0 0"
84 "/dev/ignored3 /dev/console ext4 defaults 0 0"
85 "/dev/ignored4 /proc/kmsg ext4 defaults 0 0"
86 "/dev/ignored5 /proc/sys ext4 defaults 0 0"
87 "/dev/ignored6 /proc/sys/kernel/random/boot_id ext4 defaults 0 0"
88 "/dev/ignored7 /run/host ext4 defaults 0 0"
89 "/dev/ignored8 /run/host/foo ext4 defaults 0 0"
90 "/dev/ignored9 /autofs autofs defaults 0 0"
91 "/dev/invalid1 not-a-path ext4 defaults 0 0"
99 check_fstab_mount_units
() {
100 local what where fstype opts passno unit
101 local item opt split_options filtered_options supp service device arg
102 local array_name
="${1:?}"
103 local out_dir
="${2:?}/normal"
104 # Get a reference to the array from its name
105 local -n fstab_entries
="$array_name"
107 # Running the checks in a container is pretty much useless, since we don't
108 # generate any mounts, but don't skip the whole test to test the "skip"
110 in_container
&& return 0
112 for item
in "${fstab_entries[@]}"; do
113 # Don't use a pipe here, as it would make the variables out of scope
114 read -r what where fstype opts _ passno
<<< "$item"
116 # Skip non-initrd mounts in initrd
117 if in_initrd_host
&& ! [[ "$opts" =~ x-initrd.mount
]]; then
121 if [[ "$fstype" == swap
]]; then
122 unit
="$(systemd-escape --suffix=swap --path "${what:?}")"
125 grep -qE "^What=$what$" "$out_dir/$unit"
126 if [[ "$opts" != defaults
]]; then
127 grep -qE "^Options=$opts$" "$out_dir/$unit"
130 if [[ "$opts" =~ x-systemd.makefs
]]; then
131 service
="$(systemd-escape --template=systemd-mkswap@.service --path "$what")"
132 test -e "$out_dir/$service"
138 # If we're parsing host's fstab in initrd, prefix all mount targets
140 in_initrd_host
&& where
="/sysroot${where:?}"
141 unit
="$(systemd-escape --suffix=mount --path "${where:?}")"
144 # Check the general stuff
145 grep -qE "^What=$what$" "$out_dir/$unit"
146 grep -qE "^Where=$where$" "$out_dir/$unit"
147 if [[ -n "$fstype" ]] && [[ "$fstype" != auto
]]; then
148 grep -qE "^Type=$fstype$" "$out_dir/$unit"
150 if [[ -n "$opts" ]] && [[ "$opts" != defaults
]]; then
151 # Some options are not propagated to the generated unit
152 if [[ "$where" == / ||
"$where" == /usr
]]; then
153 filtered_options
="$(opt_filter "$opts" "(noauto|nofail|x-systemd.
(wanted-by
=|required-by
=|automount|device-timeout
=))")"
155 filtered_options
="$(opt_filter "$opts" "^x-systemd.device-timeout
=")"
158 if [[ "${filtered_options[*]}" != defaults
]]; then
159 grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit"
163 if ! [[ "$opts" =~
(noauto|x-systemd.
(wanted-by
=|required-by
=|automount
)) ]]; then
164 # We don't create the Requires=/Wants= symlinks for noauto/automount mounts
165 # and for mounts that use x-systemd.wanted-by=/required-by=
166 if in_initrd_host
; then
167 if [[ "$where" == / ]] ||
! [[ "$opts" =~ nofail
]]; then
168 link_eq
"$out_dir/initrd-fs.target.requires/$unit" "../$unit"
170 link_eq
"$out_dir/initrd-fs.target.wants/$unit" "../$unit"
172 elif [[ "$fstype" =~
$NETWORK_FS_RX ||
"$opts" =~ _netdev
]]; then
173 # Units with network filesystems should have a Requires= dependency
174 # on the remote-fs.target, unless they use nofail or are an nfs "bg"
175 # mounts, in which case the dependency is downgraded to Wants=
176 if [[ "$opts" =~ nofail
]] ||
[[ "$fstype" =~ ^
(nfs|nfs4
) && "$opts" =~
bg ]]; then
177 link_eq
"$out_dir/remote-fs.target.wants/$unit" "../$unit"
179 link_eq
"$out_dir/remote-fs.target.requires/$unit" "../$unit"
182 # Similarly, local filesystems should have a Requires= dependency on
183 # the local-fs.target, unless they use nofail, in which case the
184 # dependency is downgraded to Wants=. Rootfs is a special case,
185 # since we always ignore nofail there
186 if [[ "$where" == / ]] ||
! [[ "$opts" =~ nofail
]]; then
187 link_eq
"$out_dir/local-fs.target.requires/$unit" "../$unit"
189 link_eq
"$out_dir/local-fs.target.wants/$unit" "../$unit"
194 if [[ "${passno:=0}" -ne 0 ]]; then
195 # Generate systemd-fsck@.service dependencies, if applicable
196 if in_initrd
&& [[ "$where" == / ||
"$where" == /usr
]]; then
200 if [[ "$where" == / ]]; then
201 link_endswith
"$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service"
203 service
="$(systemd-escape --template=systemd-fsck@.service --path "$what")"
204 grep -qE "^After=$service$" "$out_dir/$unit"
205 if [[ "$where" == /usr
]]; then
206 grep -qE "^Wants=$service$" "$out_dir/$unit"
208 grep -qE "^Requires=$service$" "$out_dir/$unit"
213 # Check various x-systemd options
215 # First, split them into an array to make splitting them even further
217 IFS
="," read -ra split_options
<<< "$opts"
218 # and process them one by one.
220 # Note: the "machinery" below might (and probably does) miss some
221 # combinations of supported options, so tread carefully
222 for opt
in "${split_options[@]}"; do
223 if [[ "$opt" =~ ^x-systemd.requires
= ]]; then
224 service
="$(opt_get_arg "$opt")"
225 grep -qE "^Requires=$service$" "$out_dir/$unit"
226 grep -qE "^After=$service$" "$out_dir/$unit"
227 elif [[ "$opt" =~ ^x-systemd.before
= ]]; then
228 service
="$(opt_get_arg "$opt")"
229 grep -qE "^Before=$service$" "$out_dir/$unit"
230 elif [[ "$opt" =~ ^x-systemd.after
= ]]; then
231 service
="$(opt_get_arg "$opt")"
232 grep -qE "^After=$service$" "$out_dir/$unit"
233 elif [[ "$opt" =~ ^x-systemd.wanted-by
= ]]; then
234 service
="$(opt_get_arg "$opt")"
235 if [[ "$where" == / ]]; then
236 # This option is ignored for rootfs mounts
237 (! link_eq
"$out_dir/$service.wants/$unit" "../$unit")
239 link_eq
"$out_dir/$service.wants/$unit" "../$unit"
241 elif [[ "$opt" =~ ^x-systemd.required-by
= ]]; then
242 service
="$(opt_get_arg "$opt")"
243 if [[ "$where" == / ]]; then
244 # This option is ignored for rootfs mounts
245 (! link_eq
"$out_dir/$service.requires/$unit" "../$unit")
247 link_eq
"$out_dir/$service.requires/$unit" "../$unit"
249 elif [[ "$opt" =~ ^x-systemd.requires-mounts-for
= ]]; then
250 arg
="$(opt_get_arg "$opt")"
251 grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit"
252 elif [[ "$opt" == x-systemd.device-bound
]]; then
253 # This is implied for fstab mounts
255 elif [[ "$opt" == x-systemd.automount
]]; then
256 # The $unit should have an accompanying automount unit
257 supp
="$(systemd-escape --suffix=automount --path "$where")"
258 if [[ "$where" == / ]]; then
259 # This option is ignored for rootfs mounts
260 test ! -e "$out_dir/$supp"
261 (! link_eq
"$out_dir/local-fs.target.requires/$supp" "../$supp")
263 test -e "$out_dir/$supp"
264 link_eq
"$out_dir/local-fs.target.requires/$supp" "../$supp"
266 elif [[ "$opt" =~ ^x-systemd.idle-timeout
= ]]; then
267 # The timeout applies to the automount unit, not the original
269 arg
="$(opt_get_arg "$opt")"
270 supp
="$(systemd-escape --suffix=automount --path "$where")"
271 grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp"
272 elif [[ "$opt" =~ ^x-systemd.device-timeout
= ]]; then
273 arg
="$(opt_get_arg "$opt")"
274 device
="$(systemd-escape --suffix=device --path "$what")"
275 grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf"
276 elif [[ "$opt" == x-systemd.makefs
]]; then
277 service
="$(systemd-escape --template=systemd-makefs@.service --path "$what")"
278 test -e "$out_dir/$service"
279 link_eq
"$out_dir/${unit}.requires/$service" "../$service"
280 elif [[ "$opt" == x-systemd.rw-only
]]; then
281 grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit"
282 elif [[ "$opt" == x-systemd.growfs
]]; then
283 service
="$(systemd-escape --template=systemd-growfs@.service --path "$where")"
284 link_endswith
"$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service"
285 elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^
(nfs|nfs4
)$
]]; then
286 # We "convert" nfs bg mounts to fg, so we can do the job-control
288 grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit"
289 grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit"
290 elif [[ "$opt" =~ ^x-systemd\.
]]; then
291 echo >&2 "Unhandled mount option: $opt"
298 : "fstab-generator: regular"
299 printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
301 SYSTEMD_FSTAB
="$FSTAB" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
302 check_fstab_mount_units FSTAB_GENERAL_ROOT
"$OUT_DIR"
304 # Skip the rest when running in a container, as it makes little sense to check
305 # initrd-related stuff there and fstab-generator might have a bit strange
306 # behavior during certain tests, like https://github.com/systemd/systemd/issues/27156
307 if in_container
; then
308 echo "Running in a container, skipping the rest of the fstab-generator tests..."
312 # In this mode we treat the entries as "regular" ones
313 : "fstab-generator: initrd - initrd fstab"
314 printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
316 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_SYSROOT_FSTAB
=/dev
/null run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
317 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_SYSROOT_FSTAB
=/dev
/null check_fstab_mount_units FSTAB_GENERAL
"$OUT_DIR"
319 # In this mode we prefix the mount target with /sysroot and ignore all mounts
320 # that don't have the x-initrd.mount flag
321 : "fstab-generator: initrd - host fstab"
322 printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
324 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
=/dev
/null SYSTEMD_SYSROOT_FSTAB
="$FSTAB" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
325 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
=/dev
/null SYSTEMD_SYSROOT_FSTAB
="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT
"$OUT_DIR"
327 # Check the default stuff that we (almost) always create in initrd
328 : "fstab-generator: initrd default"
329 SYSTEMD_PROC_CMDLINE
="root=/dev/sda2" SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
=/dev
/null SYSTEMD_SYSROOT_FSTAB
=/dev
/null run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
330 test -e "$OUT_DIR/normal/sysroot.mount"
331 test -e "$OUT_DIR/normal/systemd-fsck-root.service"
332 link_eq
"$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
333 link_eq
"$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
335 : "fstab-generator: run as systemd-sysroot-fstab-check in initrd"
336 ln -svf "$GENERATOR_BIN" /tmp
/systemd-sysroot-fstab-check
337 (! /tmp
/systemd-sysroot-fstab-check foo
)
338 (! SYSTEMD_IN_INITRD
=0 /tmp
/systemd-sysroot-fstab-check
)
339 printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
340 SYSTEMD_IN_INITRD
=1 SYSTEMD_SYSROOT_FSTAB
="$FSTAB" /tmp
/systemd-sysroot-fstab-check
342 : "fstab-generator: duplicate"
343 printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB"
345 (! SYSTEMD_FSTAB
="$FSTAB" run_and_list
"$GENERATOR_BIN" "$OUT_DIR")
347 : "fstab-generator: invalid"
348 printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB"
350 # Don't care about the exit code here
351 SYSTEMD_PROC_CMDLINE
="" SYSTEMD_FSTAB
="$FSTAB" run_and_list
"$GENERATOR_BIN" "$OUT_DIR" ||
:
352 # No mounts should get created here
353 [[ "$(find "$OUT_DIR" -name "*.mount
" | wc -l)" -eq 0 ]]
355 : "fstab-generator: kernel args - fstab=0"
356 printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
357 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_PROC_CMDLINE
="fstab=0" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
358 (! SYSTEMD_FSTAB
="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL
"$OUT_DIR")
359 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_PROC_CMDLINE
="fstab=0" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
360 (! SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL
"$OUT_DIR")
362 : "fstab-generator: kernel args - rd.fstab=0"
363 printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
364 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_PROC_CMDLINE
="rd.fstab=0" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
365 SYSTEMD_FSTAB
="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL
"$OUT_DIR"
366 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_PROC_CMDLINE
="rd.fstab=0" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
367 (! SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL
"$OUT_DIR")
369 : "fstab-generator: kernel args - systemd.swap=0"
370 printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
372 SYSTEMD_FSTAB
="$FSTAB" SYSTEMD_PROC_CMDLINE
="systemd.swap=0" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
373 # No swap units should get created here
374 [[ "$(find "$OUT_DIR" -name "*.swap
" | wc -l)" -eq 0 ]]
377 # - combine the rootfs & usrfs arguments and mix them with fstab entries
378 # - systemd.volatile=
379 : "fstab-generator: kernel args - root= + rootfstype= + rootflags="
380 # shellcheck disable=SC2034
382 "/dev/disk/by-label/rootfs / ext4 noexec,ro 0 1"
384 CMDLINE
="root=LABEL=rootfs rootfstype=ext4 rootflags=noexec"
385 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
=/dev
/null SYSTEMD_SYSROOT_FSTAB
=/dev
/null SYSTEMD_PROC_CMDLINE
="$CMDLINE" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
386 # The /proc/cmdline here is a dummy value to tell the in_initrd_host() function
387 # we're parsing host's fstab, but it's all on the kernel cmdline instead
388 SYSTEMD_IN_INITRD
=1 SYSTEMD_SYSROOT_FSTAB
=/proc
/cmdline check_fstab_mount_units EXPECTED_FSTAB
"$OUT_DIR"
390 # This is a very basic sanity test that involves manual checks, since adding it
391 # to the check_fstab_mount_units() function would make it way too complex
392 # (yet another possible TODO)
393 : "fstab-generator: kernel args - mount.usr= + mount.usrfstype= + mount.usrflags="
394 CMDLINE
="mount.usr=UUID=be780f43-8803-4a76-9732-02ceda6e9808 mount.usrfstype=ext4 mount.usrflags=noexec,nodev"
395 SYSTEMD_IN_INITRD
=1 SYSTEMD_FSTAB
=/dev
/null SYSTEMD_SYSROOT_FSTAB
=/dev
/null SYSTEMD_PROC_CMDLINE
="$CMDLINE" run_and_list
"$GENERATOR_BIN" "$OUT_DIR"
396 cat "$OUT_DIR/normal/sysroot-usr.mount" "$OUT_DIR/normal/sysusr-usr.mount"
397 # The general idea here is to mount the device to /sysusr/usr and then
398 # bind-mount /sysusr/usr to /sysroot/usr
399 grep -qE "^What=/dev/disk/by-uuid/be780f43-8803-4a76-9732-02ceda6e9808$" "$OUT_DIR/normal/sysusr-usr.mount"
400 grep -qE "^Where=/sysusr/usr$" "$OUT_DIR/normal/sysusr-usr.mount"
401 grep -qE "^Type=ext4$" "$OUT_DIR/normal/sysusr-usr.mount"
402 grep -qE "^Options=noexec,nodev,ro$" "$OUT_DIR/normal/sysusr-usr.mount"
403 link_eq
"$OUT_DIR/normal/initrd-usr-fs.target.requires/sysusr-usr.mount" "../sysusr-usr.mount"
404 grep -qE "^What=/sysusr/usr$" "$OUT_DIR/normal/sysroot-usr.mount"
405 grep -qE "^Where=/sysroot/usr$" "$OUT_DIR/normal/sysroot-usr.mount"
406 grep -qE "^Options=bind$" "$OUT_DIR/normal/sysroot-usr.mount"
407 link_eq
"$OUT_DIR/normal/initrd-fs.target.requires/sysroot-usr.mount" "../sysroot-usr.mount"