fstab-generator: support for x-systemd.onfailure (#42440)

1. test-fstab-generator: fix inverted /mnt symlink guard

   The test-18-options.fstab.input scenario was guarded by a check
   that was always true on a normal system, so it was silently
   skipped and its fixtures had drifted out of sync with actual
   generator output. This commit fixes the guard and repairs the
   stale fixtures it exposed.

2. fstab-generator: support for x-systemd.onfailure

   Add support for the x-systemd.onfailure= fstab option in
   systemd-fstab-generator. This allows configuring OnFailure=
   dependencies for generated mount units directly through /etc/fstab.

   For example:
/dev/sda1 /data ext4 defaults,x-systemd.onfailure=emergency.target 0 2

   results in the generated mount unit containing:
   [Unit]
   OnFailure=emergency.target

   This makes it possible to attach recovery targets or services to
   mount failures directly from fstab configuration.

   Add documentation and generator tests.
This commit is contained in:
Yu Watanabe
2026-09-23 09:08:24 +09:00
committed by GitHub
22 changed files with 107 additions and 17 deletions
+19
View File
@@ -284,6 +284,25 @@
<xi:include href="version-info.xml" xpointer="v220"/></listitem>
</varlistentry>
<varlistentry>
<term><option>x-systemd.onfailure=</option></term>
<listitem><para>In the created mount unit, configures an
<varname>OnFailure=</varname> dependency. The argument is a unit name, see
<option>x-systemd.requires=</option> above. This option may be specified more than
once. See <varname>OnFailure=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details.</para>
<para>Note that this option always applies to the created mount
unit only, regardless of whether <option>x-systemd.automount</option>
has been specified.</para>
<para>Note that this option is silently ignored on swap entries.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>x-systemd.graceful-option=</option></term>
+19 -5
View File
@@ -341,6 +341,7 @@ static int write_dependency(
const char *where,
const char *opts,
const char *filter,
const char *suffix,
const char* const *unit_settings) {
_cleanup_strv_free_ char **unit_names = NULL;
@@ -360,7 +361,9 @@ static int write_dependency(
STRV_FOREACH(s, unit_names) {
_cleanup_free_ char *mangled = NULL;
r = unit_name_mangle_with_suffix(*s, "as dependency", 0, ".mount", &mangled);
r = suffix ?
unit_name_mangle_with_suffix(*s, "as dependency", 0, suffix, &mangled) :
unit_name_mangle(*s, 0, &mangled);
if (r < 0)
return log_error_errno(r, "Failed to generate dependency unit name for '%s': %m", where);
@@ -376,22 +379,24 @@ static int write_dependency(
static int write_after(FILE *f, const char *where, const char *opts) {
return write_dependency(f, where, opts,
"x-systemd.after\0", STRV_MAKE_CONST("After"));
"x-systemd.after\0", /* suffix= */ ".mount", STRV_MAKE_CONST("After"));
}
static int write_requires_after(FILE *f, const char *where, const char *opts) {
return write_dependency(f, where, opts,
"x-systemd.requires\0", STRV_MAKE_CONST("Requires", "After"));
"x-systemd.requires\0", /* suffix= */ ".mount",
STRV_MAKE_CONST("Requires", "After"));
}
static int write_wants_after(FILE *f, const char *where, const char *opts) {
return write_dependency(f, where, opts,
"x-systemd.wants\0", STRV_MAKE_CONST("Wants", "After"));
"x-systemd.wants\0", /* suffix= */ ".mount",
STRV_MAKE_CONST("Wants", "After"));
}
static int write_before(FILE *f, const char *where, const char *opts) {
return write_dependency(f, where, opts,
"x-systemd.before\0", STRV_MAKE_CONST("Before"));
"x-systemd.before\0", /* suffix= */ ".mount", STRV_MAKE_CONST("Before"));
}
static int write_mounts_for(
@@ -426,6 +431,11 @@ static int write_mounts_for(
return 0;
}
static int write_on_failure(FILE *f, const char *where, const char *opts) {
return write_dependency(f, where, opts,
"x-systemd.onfailure\0", /* suffix= */ NULL, STRV_MAKE_CONST("OnFailure"));
}
static int write_extra_dependencies(FILE *f, const char *where, const char *opts) {
int r;
@@ -460,6 +470,10 @@ static int write_extra_dependencies(FILE *f, const char *where, const char *opts
if (r < 0)
return r;
r = write_on_failure(f, where, opts);
if (r < 0)
return r;
return 0;
}
+5 -6
View File
@@ -67,15 +67,15 @@ test_one() (
# .deb packager seems to dislike files named with backslash. So, as a workaround, we store files
# without backslash in .expected.
for i in "$out"/**/*\\*.{mount,swap}; do
for i in "$out"/**/*\\*.{mount,swap,automount}; do
k="${i//\\/}"
if [[ "$i" != "$k" ]]; then
if [[ -f "$i" ]]; then
mv "$i" "$k"
elif [[ -L "$i" ]]; then
if [[ -L "$i" ]]; then
dest=$(readlink "$i")
rm "$i"
ln -s "${dest//\\/}" "$k"
elif [[ -f "$i" ]]; then
mv "$i" "$k"
fi
fi
done
@@ -130,8 +130,7 @@ test_one() (
for f in "$src"/test-*.input; do
# If /mnt is a symlink, then the expected output from this
# test scenario will not match the actual output
if test "$f" = "$src/test-18-options.fstab.input" -a "$(readlink /mnt)" != "/mnt"
then
if [[ "$f" == "$src/test-18-options.fstab.input" && -L /mnt ]]; then
echo "Skip $f because /mnt is a symlink"
continue
fi
@@ -1 +0,0 @@
../mnt-wantedby-automount.automount
@@ -0,0 +1 @@
../mnt-wantedbyx2dautomount.automount
@@ -0,0 +1 @@
../mnt-onfailuremulti.mount
@@ -0,0 +1,13 @@
# Automatically generated by systemd-fstab-generator
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
OnFailure=foo.service
Before=local-fs.target
After=blockdev@dev-sdx18.target
[Mount]
What=/dev/sdx18
Where=/mnt/onfailure
Options=x-systemd.onfailure=foo.service
@@ -0,0 +1,13 @@
# Automatically generated by systemd-fstab-generator
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
OnFailure=foo.service bar.service
Before=local-fs.target
After=blockdev@dev-sdx19.target
[Mount]
What=/dev/sdx19
Where=/mnt/onfailuremulti
Options=x-systemd.onfailure=foo.service,x-systemd.onfailure=bar.service
@@ -3,8 +3,8 @@
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
After=foo.service
Requires=foo.service
After=foo.service
Before=local-fs.target
After=blockdev@dev-sdx5.target
@@ -10,5 +10,5 @@ After=blockdev@dev-sdx17.target
[Mount]
What=/dev/sdx17
Where=/mnt/wantedby
Where=/mnt/wantedby-automount
Options=x-systemd.wanted-by=foo.service,x-systemd.automount
@@ -1 +0,0 @@
../mnt-wantedby-automount.automount
@@ -0,0 +1 @@
../mnt-wantedbyx2dautomount.automount
@@ -0,0 +1 @@
../mnt-onfailure.mount
@@ -0,0 +1 @@
../mnt-onfailuremulti.mount
@@ -0,0 +1,13 @@
# Automatically generated by systemd-fstab-generator
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
OnFailure=foo.service
Before=local-fs.target
After=blockdev@dev-sdx18.target
[Mount]
What=/dev/sdx18
Where=/mnt/onfailure
Options=x-systemd.onfailure=foo.service
@@ -0,0 +1,13 @@
# Automatically generated by systemd-fstab-generator
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
OnFailure=foo.service bar.service
Before=local-fs.target
After=blockdev@dev-sdx19.target
[Mount]
What=/dev/sdx19
Where=/mnt/onfailuremulti
Options=x-systemd.onfailure=foo.service,x-systemd.onfailure=bar.service
@@ -3,8 +3,8 @@
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
After=foo.service
Requires=foo.service
After=foo.service
Before=local-fs.target
After=blockdev@dev-sdx5.target
@@ -10,5 +10,5 @@ After=blockdev@dev-sdx17.target
[Mount]
What=/dev/sdx17
Where=/mnt/wantedby
Where=/mnt/wantedby-automount
Options=x-systemd.wanted-by=foo.service,x-systemd.automount
@@ -15,3 +15,5 @@
/dev/sdx15 /mnt/noauto auto noauto 0 0
/dev/sdx16 /mnt/nofail auto nofail 0 0
/dev/sdx17 /mnt/wantedby-automount auto x-systemd.wanted-by=foo.service,x-systemd.automount 0 0
/dev/sdx18 /mnt/onfailure auto x-systemd.onfailure=foo.service 0 0
/dev/sdx19 /mnt/onfailuremulti auto x-systemd.onfailure=foo.service,x-systemd.onfailure=bar.service 0 0