Merge pull request #28518 from yuwata/fstab-generator-fixes

fstab-generator: fixes for systemd.mount-extra=
This commit is contained in:
Luca Boccassi
2023-07-27 18:47:41 +01:00
committed by GitHub
66 changed files with 566 additions and 124 deletions

View File

@@ -242,11 +242,15 @@
<varlistentry>
<term><varname>systemd.mount-extra=<replaceable>WHAT</replaceable>:<replaceable>WHERE</replaceable>[:<replaceable>FSTYPE</replaceable>[:<replaceable>OPTIONS</replaceable>]]</varname></term>
<term><varname>rd.systemd.mount-extra=<replaceable>WHAT</replaceable>:<replaceable>WHERE</replaceable>[:<replaceable>FSTYPE</replaceable>[:<replaceable>OPTIONS</replaceable>]]</varname></term>
<listitem>
<para>Specifies the mount unit. Takes at least two and at most four fields separated with a colon
(<literal>:</literal>). Each field is handled as the corresponding fstab field. This option can be
specified multiple times.</para>
specified multiple times. <varname>rd.systemd.mount-extra=</varname> is honored only in the initrd,
while <varname>systemd.mount-extra=</varname> is honored by both the main system and the initrd.
In the initrd, the mount point (and also source path if the mount is bind mount) specified in
<varname>systemd.mount-extra=</varname> is prefixed with <filename>/sysroot/</filename>.</para>
<para>Example:
<programlisting>
systemd.mount-extra=/dev/sda1:/mount-point:ext4:rw,noatime</programlisting>
@@ -256,10 +260,13 @@ systemd.mount-extra=/dev/sda1:/mount-point:ext4:rw,noatime</programlisting>
<varlistentry>
<term><varname>systemd.swap-extra=<replaceable>WHAT</replaceable>[:<replaceable>OPTIONS</replaceable>]</varname></term>
<term><varname>rd.systemd.swap-extra=<replaceable>WHAT</replaceable>[:<replaceable>OPTIONS</replaceable>]</varname></term>
<listitem>
<para>Specifies the swap unit. Takes the block device to be used as a swap device, and optionally
takes mount options followed by a colon (<literal>:</literal>).</para>
takes mount options followed by a colon (<literal>:</literal>). This option can be specified
multiple times. <varname>rd.systemd.swap-extra=</varname> is honored only in the initrd, while
<varname>systemd.swap-extra=</varname> is honored by both the main system and the initrd.</para>
<para>Example:
<programlisting>
systemd.swap=/dev/sda2:x-systemd.makefs</programlisting>

View File

@@ -49,6 +49,7 @@ typedef enum MountPointFlags {
} MountPointFlags;
typedef struct Mount {
bool for_initrd;
char *what;
char *where;
char *fstype;
@@ -102,7 +103,13 @@ static void mount_array_free(Mount *mounts, size_t n) {
free(mounts);
}
static int mount_array_add_internal(char *in_what, char *in_where, const char *in_fstype, const char *in_options) {
static int mount_array_add_internal(
bool for_initrd,
char *in_what,
char *in_where,
const char *in_fstype,
const char *in_options) {
_cleanup_free_ char *what = NULL, *where = NULL, *fstype = NULL, *options = NULL;
int r;
@@ -135,6 +142,7 @@ static int mount_array_add_internal(char *in_what, char *in_where, const char *i
return -ENOMEM;
arg_mounts[arg_n_mounts++] = (Mount) {
.for_initrd = for_initrd,
.what = TAKE_PTR(what),
.where = TAKE_PTR(where),
.fstype = TAKE_PTR(fstype),
@@ -144,7 +152,7 @@ static int mount_array_add_internal(char *in_what, char *in_where, const char *i
return 0;
}
static int mount_array_add(const char *str) {
static int mount_array_add(bool for_initrd, const char *str) {
_cleanup_free_ char *what = NULL, *where = NULL, *fstype = NULL, *options = NULL;
int r;
@@ -159,10 +167,10 @@ static int mount_array_add(const char *str) {
if (!isempty(str))
return -EINVAL;
return mount_array_add_internal(TAKE_PTR(what), TAKE_PTR(where), fstype, options);
return mount_array_add_internal(for_initrd, TAKE_PTR(what), TAKE_PTR(where), fstype, options);
}
static int mount_array_add_swap(const char *str) {
static int mount_array_add_swap(bool for_initrd, const char *str) {
_cleanup_free_ char *what = NULL, *options = NULL;
int r;
@@ -177,7 +185,7 @@ static int mount_array_add_swap(const char *str) {
if (!isempty(str))
return -EINVAL;
return mount_array_add_internal(TAKE_PTR(what), NULL, "swap", options);
return mount_array_add_internal(for_initrd, TAKE_PTR(what), NULL, "swap", options);
}
static int write_options(FILE *f, const char *options) {
@@ -306,9 +314,9 @@ static bool mount_is_network(const char *fstype, const char *options) {
(fstype && fstype_is_network(fstype));
}
static bool mount_in_initrd(const char *where, const char *options) {
static bool mount_in_initrd(const char *where, const char *options, bool accept_root) {
return fstab_test_option(options, "x-initrd.mount\0") ||
(where && path_equal(where, "/usr"));
(where && PATH_IN_SET(where, "/usr", accept_root ? "/" : NULL));
}
static int write_timeout(
@@ -801,7 +809,7 @@ static MountPointFlags fstab_options_to_flags(const char *options, bool is_swap)
return flags;
}
static int canonicalize_mount_path(const char *path, const char *type, bool initrd, char **ret) {
static int canonicalize_mount_path(const char *path, const char *type, bool prefix_sysroot, char **ret) {
_cleanup_free_ char *p = NULL;
bool changed;
int r;
@@ -813,11 +821,11 @@ static int canonicalize_mount_path(const char *path, const char *type, bool init
// FIXME: when chase() learns to chase non-existent paths, use this here and drop the prefixing with
// /sysroot on error below.
r = chase(path, initrd ? "/sysroot" : NULL, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &p, NULL);
r = chase(path, prefix_sysroot ? "/sysroot" : NULL, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &p, NULL);
if (r < 0) {
log_debug_errno(r, "Failed to chase '%s', using as-is: %m", path);
if (initrd)
if (prefix_sysroot)
p = path_join("/sysroot", path);
else
p = strdup(path);
@@ -842,7 +850,8 @@ static int parse_fstab_one(
const char *fstype,
const char *options,
int passno,
bool initrd,
bool prefix_sysroot,
bool accept_root, /* This takes an effect only when prefix_sysroot is true. */
bool use_swap_enabled) {
_cleanup_free_ char *what = NULL, *where = NULL;
@@ -854,7 +863,7 @@ static int parse_fstab_one(
assert(fstype);
assert(options);
if (initrd && !mount_in_initrd(where_original, options))
if (prefix_sysroot && !mount_in_initrd(where_original, options, accept_root))
return 0;
is_swap = streq_ptr(fstype, "swap");
@@ -891,16 +900,16 @@ static int parse_fstab_one(
* /etc/fstab. So we canonicalize here. Note that we use CHASE_NONEXISTENT to handle the case
* where a symlink refers to another mount target; this works assuming the sub-mountpoint
* target is the final directory. */
r = canonicalize_mount_path(where_original, "where", initrd, &where);
r = canonicalize_mount_path(where_original, "where", prefix_sysroot, &where);
if (r < 0)
return r;
where_changed = r > 0;
if (initrd && fstab_is_bind(options, fstype)) {
if (prefix_sysroot && fstab_is_bind(options, fstype)) {
/* When in initrd, the source of bind mount needs to be prepended with /sysroot as well. */
_cleanup_free_ char *p = NULL;
r = canonicalize_mount_path(what, "what", initrd, &p);
r = canonicalize_mount_path(what, "what", prefix_sysroot, &p);
if (r < 0)
return r;
@@ -919,9 +928,9 @@ static int parse_fstab_one(
bool is_sysroot_usr = in_initrd() && path_equal(where, "/sysroot/usr");
const char *target_unit =
initrd ? SPECIAL_INITRD_FS_TARGET :
is_sysroot ? SPECIAL_INITRD_ROOT_FS_TARGET :
is_sysroot_usr ? SPECIAL_INITRD_USR_FS_TARGET :
prefix_sysroot ? SPECIAL_INITRD_FS_TARGET :
mount_is_network(fstype, options) ? SPECIAL_REMOTE_FS_TARGET :
SPECIAL_LOCAL_FS_TARGET;
@@ -948,13 +957,13 @@ static int parse_fstab_one(
return true;
}
static int parse_fstab(bool initrd) {
static int parse_fstab(bool prefix_sysroot) {
_cleanup_endmntent_ FILE *f = NULL;
const char *fstab;
struct mntent *me;
int r, ret = 0;
if (initrd)
if (prefix_sysroot)
fstab = sysroot_fstab_path();
else {
fstab = fstab_path();
@@ -974,7 +983,9 @@ static int parse_fstab(bool initrd) {
while ((me = getmntent(f))) {
r = parse_fstab_one(fstab,
me->mnt_fsname, me->mnt_dir, me->mnt_type, me->mnt_opts, me->mnt_passno,
initrd, /* use_swap_enabled = */ true);
prefix_sysroot,
/* accept_root = */ false,
/* use_swap_enabled = */ true);
if (r < 0 && ret >= 0)
ret = r;
if (arg_sysroot_check && r > 0)
@@ -1282,6 +1293,9 @@ static int add_mounts_from_cmdline(void) {
/* Handle each entries found in cmdline as a fstab entry. */
FOREACH_ARRAY(m, arg_mounts, arg_n_mounts) {
if (m->for_initrd && !in_initrd())
continue;
r = parse_fstab_one(
"/proc/cmdline",
m->what,
@@ -1289,7 +1303,8 @@ static int add_mounts_from_cmdline(void) {
m->fstype,
m->options,
/* passno = */ 0,
/* initrd = */ false,
/* prefix_sysroot = */ !m->for_initrd && in_initrd(),
/* accept_root = */ true,
/* use_swap_enabled = */ false);
if (r < 0 && ret >= 0)
ret = r;
@@ -1298,14 +1313,16 @@ static int add_mounts_from_cmdline(void) {
return ret;
}
static int add_mounts_from_creds(void) {
static int add_mounts_from_creds(bool prefix_sysroot) {
_cleanup_free_ void *b = NULL;
struct mntent *me;
int r, ret = 0;
size_t bs;
assert(in_initrd() || !prefix_sysroot);
r = read_credential_with_decryption(
in_initrd() ? "fstab.extra.initrd" : "fstab.extra",
in_initrd() && !prefix_sysroot ? "fstab.extra.initrd" : "fstab.extra",
&b, &bs);
if (r <= 0)
return r;
@@ -1323,7 +1340,8 @@ static int add_mounts_from_creds(void) {
me->mnt_type,
me->mnt_opts,
me->mnt_passno,
/* initrd = */ false,
/* prefix_sysroot = */ prefix_sysroot,
/* accept_root = */ true,
/* use_swap_enabled = */ true);
if (r < 0 && ret >= 0)
ret = r;
@@ -1437,21 +1455,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
else
arg_verity = r;
} else if (streq(key, "systemd.mount-extra")) {
} else if (STR_IN_SET(key, "systemd.mount-extra", "rd.systemd.mount-extra")) {
if (proc_cmdline_value_missing(key, value))
return 0;
r = mount_array_add(value);
r = mount_array_add(startswith(key, "rd."), value);
if (r < 0)
log_warning("Failed to parse systemd.mount-extra= option, ignoring: %s", value);
} else if (streq(key, "systemd.swap-extra")) {
} else if (STR_IN_SET(key, "systemd.swap-extra", "rd.systemd.swap-extra")) {
if (proc_cmdline_value_missing(key, value))
return 0;
r = mount_array_add_swap(value);
r = mount_array_add_swap(startswith(key, "rd."), value);
if (r < 0)
log_warning("Failed to parse systemd.swap-extra= option, ignoring: %s", value);
}
@@ -1517,7 +1535,7 @@ static int run_generator(void) {
(void) determine_usr();
if (arg_sysroot_check) {
r = parse_fstab(/* initrd= */ true);
r = parse_fstab(/* prefix_sysroot = */ true);
if (r == 0)
log_debug("Nothing interesting found, not doing daemon-reload.");
if (r > 0)
@@ -1547,13 +1565,13 @@ static int run_generator(void) {
/* Honour /etc/fstab only when that's enabled */
if (arg_fstab_enabled) {
/* Parse the local /etc/fstab, possibly from the initrd */
r = parse_fstab(/* initrd= */ false);
r = parse_fstab(/* prefix_sysroot = */ false);
if (r < 0 && ret >= 0)
ret = r;
/* If running in the initrd also parse the /etc/fstab from the host */
if (in_initrd())
r = parse_fstab(/* initrd= */ true);
r = parse_fstab(/* prefix_sysroot = */ true);
else
r = generator_enable_remount_fs_service(arg_dest);
if (r < 0 && ret >= 0)
@@ -1564,10 +1582,16 @@ static int run_generator(void) {
if (r < 0 && ret >= 0)
ret = r;
r = add_mounts_from_creds();
r = add_mounts_from_creds(/* prefix_sysroot = */ false);
if (r < 0 && ret >= 0)
ret = r;
if (in_initrd()) {
r = add_mounts_from_creds(/* prefix_sysroot = */ true);
if (r < 0 && ret >= 0)
ret = r;
}
return ret;
}

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
set -eux
shopt -s nullglob
shopt -s globstar
if [[ -n "$1" ]]; then
if [[ -n "${1:-}" ]]; then
generator=$1
elif [[ -x /usr/lib/systemd/system-generators/systemd-fstab-generator ]]; then
generator=/usr/lib/systemd/system-generators/systemd-fstab-generator
@@ -23,97 +23,116 @@ PATH=$PATH:/usr/sbin
# of the host system. Override the measurement to avoid the issue.
export SYSTEMD_FORCE_MEASURE=0
for f in "$src"/test-*.input; do
echo "*** Running $f"
test_one() (
local initrd input out exp i j k dir fname expf
(
out=$(mktemp --tmpdir --directory "test-fstab-generator.XXXXXXXXXX")
# shellcheck disable=SC2064
trap "rm -rf '$out'" EXIT INT QUIT PIPE
input=${1?}
initrd=${2?}
exp="${f%.input}.expected"
if [[ "${f##*/}" =~ swap ]] && systemd-detect-virt --container >/dev/null; then
exp="${exp}.container"
fi
: "*** Running $input (initrd=$initrd)"
if [[ "${f##*/}" =~ \.fstab\.input ]]; then
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD=yes SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=yes root=fstab" SYSTEMD_FSTAB="$f" SYSTEMD_SYSROOT_FSTAB="/dev/null" $generator "$out" "$out" "$out"
else
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD=yes SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=no $(cat "$f")" $generator "$out" "$out" "$out"
fi
out=$(mktemp --tmpdir --directory "test-fstab-generator.XXXXXXXXXX")
# shellcheck disable=SC2064
trap "rm -rf '$out'" EXIT INT QUIT PIPE
# The option x-systemd.growfs creates symlink to system's systemd-growfs@.service in .mount.wants directory.
# The system that the test is currently running on may not have or may have outdated unit file.
# Let's replace the symlink with an empty file.
for i in "$out"/*/systemd-growfs@*.service; do
[[ -L "$i" ]] || continue
rm "$i"
touch "$i"
exp="${input%.input}.expected"
if [[ "${input##*/}" =~ swap ]] && systemd-detect-virt --container >/dev/null; then
exp="${exp}.container"
fi
if [[ "$initrd" == no ]]; then
exp="${exp}.sysroot"
fi
if [[ "${input##*/}" =~ \.fstab\.input ]]; then
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD="$initrd" SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=yes root=fstab" SYSTEMD_FSTAB="$input" SYSTEMD_SYSROOT_FSTAB="/dev/null" $generator "$out" "$out" "$out"
else
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD="$initrd" SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=no $(cat "$input")" $generator "$out" "$out" "$out"
fi
# The option x-systemd.growfs creates symlink to system's systemd-growfs@.service in .mount.wants directory.
# Also, when $initrd is no, symlink to systemd-remount-fs.service is created.
# The system that the test is currently running on may not have or may have outdated unit file.
# Let's replace the symlink with an empty file.
for i in "$out"/*/systemd-growfs@*.service "$out"/local-fs.target.wants/systemd-remount-fs.service; do
[[ -L "$i" ]] || continue
rm "$i"
touch "$i"
done
# For split-usr system
for i in "$out"/systemd-*.service; do
sed -i -e 's:ExecStart=/lib/systemd/:ExecStart=/usr/lib/systemd/:' "$i"
done
if [[ "${input##*/}" =~ \.fstab\.input ]]; then
for i in "$out"/*.{automount,mount,swap}; do
sed -i -e 's:SourcePath=.*$:SourcePath=/etc/fstab:' "$i"
done
fi
# For split-usr system
for i in "$out"/systemd-*.service; do
sed -i -e 's:ExecStart=/lib/systemd/:ExecStart=/usr/lib/systemd/:' "$i"
done
if [[ "${f##*/}" =~ \.fstab\.input ]]; then
for i in "$out"/*.{automount,mount,swap}; do
sed -i -e 's:SourcePath=.*$:SourcePath=/etc/fstab:' "$i"
done
# .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
k="${i//\\/}"
if [[ "$i" != "$k" ]]; then
if [[ -f "$i" ]]; then
mv "$i" "$k"
elif [[ -L "$i" ]]; then
dest=$(readlink "$i")
rm "$i"
ln -s "${dest//\\/}" "$k"
fi
fi
done
# .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
k="${i//\\/}"
if [[ "$i" != "$k" ]]; then
if [[ -f "$i" ]]; then
mv "$i" "$k"
elif [[ -L "$i" ]]; then
dest=$(readlink "$i")
rm "$i"
ln -s "${dest//\\/}" "$k"
# We do not store empty directory.
if [[ -z "$(ls -A "$out")" && ! -d "$exp" ]]; then
return 0
fi
# We store empty files rather than dead symlinks, so that they don't get pruned when packaged up, so compare
# the list of filenames rather than their content
if ! diff -u <(find "$out" -printf '%P\n' | sort) <(find "$exp" -printf '%P\n' | sort); then
: "**** Unexpected output for $input (initrd=$initrd)"
return 1
fi
# Check the main units.
if ! diff -u "$out" "$exp"; then
: "**** Unexpected output for $input (initrd=$initrd)"
return 1
fi
# Also check drop-ins.
for i in "$out"/*; do
[[ -d "$i" ]] || continue
dir="${i##*/}"
for j in "$i"/*; do
fname="${j##*/}"
expf="$exp/$dir/$fname"
if [[ -L "$j" && ! -e "$j" ]]; then
# For dead symlink, we store an empty file.
if [[ ! -e "$expf" || -n "$(cat "$expf")" ]]; then
: "**** Unexpected symlink $j created by $input (initrd=$initrd)"
return 1
fi
continue
fi
if ! diff -u "$j" "$expf"; then
: "**** Unexpected output in $j for $input (initrd=$initrd)"
return 1
fi
done
done
# We store empty files rather than dead symlinks, so that they don't get pruned when packaged up, so compare
# the list of filenames rather than their content
if ! diff -u <(find "$out" -printf '%P\n' | sort) <(find "$exp" -printf '%P\n' | sort); then
echo "**** Unexpected output for $f"
exit 1
fi
return 0
)
# Check the main units.
if ! diff -u "$out" "$exp"; then
echo "**** Unexpected output for $f"
exit 1
fi
# Also check drop-ins.
for i in "$out"/*; do
[[ -d "$i" ]] || continue
dir="${i##*/}"
for j in "$i"/*; do
fname="${j##*/}"
expf="$exp/$dir/$fname"
if [[ -L "$j" && ! -e "$j" ]]; then
# For dead symlink, we store an empty file.
if [[ ! -e "$expf" || -n "$(cat "$expf")" ]]; then
echo "**** Unexpected symlink $j created by $f"
exit 1
fi
continue
fi
if ! diff -u "$j" "$expf"; then
echo "**** Unexpected output in $j for $f"
exit 1
fi
done
done
) || exit 1
for f in "$src"/test-*.input; do
test_one "$f" yes
test_one "$f" no
done

View File

@@ -0,0 +1,11 @@
# Automatically generated by systemd-fstab-generator
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
Before=local-fs.target
After=blockdev@dev-sdx2.target
[Mount]
What=/dev/sdx2
Where=/sysroot/usr

View File

@@ -0,0 +1,13 @@
# Automatically generated by systemd-fstab-generator
[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
Before=local-fs.target
Requires=systemd-fsck@dev-sdx1.service
After=systemd-fsck@dev-sdx1.service
After=blockdev@dev-sdx1.target
[Mount]
What=/dev/sdx1
Where=/sysroot

View File

@@ -0,0 +1,4 @@
# Automatically generated by systemd-fstab-generator
[Unit]
After=systemd-growfs@mnt-growfs.service

Some files were not shown because too many files have changed in this diff Show More