From 245529d85fb807bfc4525b3b1858896d2860995b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Mon, 4 Apr 2022 22:45:58 +0200 Subject: [PATCH] contrib; dracut: centralise root= parsing, actually support root=s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far, everything parsed root= manually, which meant that while zfs-parse.sh was updated, and supposedly supported + -> ' ' conversion, it meant nothing Instead, centralise parsing, and allow: root= root=zfs root=zfs: root=zfs:AUTO root=ZFS=data/set root=zfs:data/set root=zfs:ZFS=data/set (as a side-effect; allowed but undocumented) rootfstype=zfs AND root=data/set <=> root=data/set rootfstype=zfs AND root= <=> root=zfs:AUTO So rootfstype=zfs /also/ behaves as expected, and + decoding works Reviewed-by: Brian Behlendorf Signed-off-by: Ahelenia Ziemiańska Closes #13291 --- contrib/dracut/90zfs/mount-zfs.sh.in | 33 +++++-------- contrib/dracut/90zfs/parse-zfs.sh.in | 59 +++++++---------------- contrib/dracut/90zfs/zfs-generator.sh.in | 30 ++---------- contrib/dracut/90zfs/zfs-lib.sh.in | 61 +++++++++++++++++++++++- contrib/dracut/90zfs/zfs-load-key.sh.in | 28 ++++------- contrib/dracut/README.md | 14 +++--- 6 files changed, 107 insertions(+), 118 deletions(-) diff --git a/contrib/dracut/90zfs/mount-zfs.sh.in b/contrib/dracut/90zfs/mount-zfs.sh.in index 7e11c9afd..6bb06a7ff 100755 --- a/contrib/dracut/90zfs/mount-zfs.sh.in +++ b/contrib/dracut/90zfs/mount-zfs.sh.in @@ -3,34 +3,20 @@ . /lib/dracut-zfs-lib.sh -ZFS_DATASET="" -ZFS_POOL="" - -case "${root}" in - zfs:*) ;; - *) return ;; -esac +decode_root_args || return 0 GENERATOR_FILE=/run/systemd/generator/sysroot.mount GENERATOR_EXTENSION=/run/systemd/generator/sysroot.mount.d/zfs-enhancement.conf -if [ -e "$GENERATOR_FILE" ] && [ -e "$GENERATOR_EXTENSION" ] ; then - # If the ZFS sysroot.mount flag exists, the initial RAM disk configured - # it to mount ZFS on root. In that case, we bail early. This flag - # file gets created by the zfs-generator program upon successful run. - info "ZFS: There is a sysroot.mount and zfs-generator has extended it." - info "ZFS: Delegating root mount to sysroot.mount." - # Let us tell the initrd to run on shutdown. - # We have a shutdown hook to run - # because we imported the pool. +if [ -e "$GENERATOR_FILE" ] && [ -e "$GENERATOR_EXTENSION" ]; then + # We're under systemd and dracut-zfs-generator ran to completion. + info "ZFS: Delegating root mount to sysroot.mount at al." + # We now prevent Dracut from running this thing again. - for zfsmounthook in "$hookdir"/mount/*zfs* ; do - if [ -f "$zfsmounthook" ] ; then - rm -f "$zfsmounthook" - fi - done + rm -f "$hookdir"/mount/*zfs* return fi + info "ZFS: No sysroot.mount exists or zfs-generator did not extend it." info "ZFS: Mounting root with the traditional mount-zfs.sh instead." @@ -38,6 +24,9 @@ info "ZFS: Mounting root with the traditional mount-zfs.sh instead." modprobe zfs 2>/dev/null udevadm settle +ZFS_DATASET= +ZFS_POOL= + if [ "${root}" = "zfs:AUTO" ] ; then if ! ZFS_DATASET="$(find_bootfs)" ; then # shellcheck disable=SC2086 @@ -53,7 +42,7 @@ if [ "${root}" = "zfs:AUTO" ] ; then info "ZFS: Using ${ZFS_DATASET} as root." fi -ZFS_DATASET="${ZFS_DATASET:-${root#zfs:}}" +ZFS_DATASET="${ZFS_DATASET:-${root}}" ZFS_POOL="${ZFS_DATASET%%/*}" if import_pool "${ZFS_POOL}" ; then diff --git a/contrib/dracut/90zfs/parse-zfs.sh.in b/contrib/dracut/90zfs/parse-zfs.sh.in index 77595bcdb..f7d1f1c5d 100755 --- a/contrib/dracut/90zfs/parse-zfs.sh.in +++ b/contrib/dracut/90zfs/parse-zfs.sh.in @@ -1,7 +1,8 @@ #!/bin/sh # shellcheck disable=SC2034,SC2154 -. /lib/dracut-lib.sh +# shellcheck source=zfs-lib.sh.in +. /lib/dracut-zfs-lib.sh # Let the command line override our host id. spl_hostid=$(getarg spl_hostid=) @@ -15,46 +16,20 @@ else warn "ZFS: Pools may not import correctly." fi -wait_for_zfs=0 -case "${root}" in - ""|zfs|zfs:) - # We'll take root unset, root=zfs, or root=zfs: - # No root set, so we want to read the bootfs attribute. We - # can't do that until udev settles so we'll set dummy values - # and hope for the best later on. - root="zfs:AUTO" - rootok=1 - wait_for_zfs=1 +if decode_root_args; then + if [ "$root" = "zfs:AUTO" ]; then + info "ZFS: Boot dataset autodetected from bootfs=." + else + info "ZFS: Boot dataset is ${root}." + fi - info "ZFS: Enabling autodetection of bootfs after udev settles." - ;; - - ZFS=*|zfs:*) - # root is explicit ZFS root. Parse it now. We can handle - # a root=... param in any of the following formats: - # root=ZFS=rpool/ROOT - # root=zfs:rpool/ROOT - # root=ZFS=pool+with+space/ROOT+WITH+SPACE (translates to root=ZFS=pool with space/ROOT WITH SPACE) - - # Strip down to just the pool/fs - root="${root#zfs:}" - root="zfs:${root#ZFS=}" - # switch + with spaces because kernel cmdline does not allow us to quote parameters - root=$(echo "$root" | tr '+' ' ') - rootok=1 - wait_for_zfs=1 - - info "ZFS: Set ${root} as bootfs." - ;; - - *) - info "ZFS: no ZFS-on-root" -esac - -# Make sure Dracut is happy that we have a root and will wait for ZFS -# modules to settle before mounting. -if [ "${wait_for_zfs}" -eq 1 ]; then - ln -s /dev/null /dev/root 2>/dev/null - initqueuedir="${hookdir}/initqueue/finished" - echo '[ -e /dev/zfs ]' > "${initqueuedir}/zfs.sh" + rootok=1 + # Make sure Dracut is happy that we have a root and will wait for ZFS + # modules to settle before mounting. + if [ -n "${wait_for_zfs}" ]; then + ln -s null /dev/root + echo '[ -e /dev/zfs ]' > "${hookdir}/initqueue/finished/zfs.sh" + fi +else + info "ZFS: no ZFS-on-root." fi diff --git a/contrib/dracut/90zfs/zfs-generator.sh.in b/contrib/dracut/90zfs/zfs-generator.sh.in index e50b9530c..56f7ca978 100755 --- a/contrib/dracut/90zfs/zfs-generator.sh.in +++ b/contrib/dracut/90zfs/zfs-generator.sh.in @@ -1,5 +1,5 @@ #!/bin/sh -# shellcheck disable=SC2016,SC1004 +# shellcheck disable=SC2016,SC1004,SC2154 grep -wq debug /proc/cmdline && debug=1 [ -n "$debug" ] && echo "zfs-generator: starting" >> /dev/kmsg @@ -10,37 +10,17 @@ GENERATOR_DIR="$1" exit 1 } -[ -f /lib/dracut-lib.sh ] && dracutlib=/lib/dracut-lib.sh -[ -f /usr/lib/dracut/modules.d/99base/dracut-lib.sh ] && dracutlib=/usr/lib/dracut/modules.d/99base/dracut-lib.sh -command -v getarg >/dev/null 2>&1 || { - [ -n "$debug" ] && echo "zfs-generator: loading Dracut library from $dracutlib" >> /dev/kmsg - . "$dracutlib" -} - +# shellcheck source=zfs-lib.sh.in . /lib/dracut-zfs-lib.sh +decode_root_args || exit 0 -[ -z "$root" ] && root=$(getarg root=) -[ -z "$rootfstype" ] && rootfstype=$(getarg rootfstype=) -[ -z "$rootflags" ] && rootflags=$(getarg rootflags=) - -# If root is not ZFS= or zfs: or rootfstype is not zfs -# then we are not supposed to handle it. -[ "${root##zfs:}" = "${root}" ] && - [ "${root##ZFS=}" = "${root}" ] && - [ "$rootfstype" != "zfs" ] && - exit 0 - +[ -z "${rootflags}" ] && rootflags=$(getarg rootflags=) case ",${rootflags}," in *,zfsutil,*) ;; ,,) rootflags=zfsutil ;; *) rootflags="zfsutil,${rootflags}" ;; esac -if [ "${root}" != "zfs:AUTO" ]; then - root="${root##zfs:}" - root="${root##ZFS=}" -fi - [ -n "$debug" ] && echo "zfs-generator: writing extension for sysroot.mount to $GENERATOR_DIR/sysroot.mount.d/zfs-enhancement.conf" >> /dev/kmsg @@ -89,7 +69,7 @@ else _zfs_generator_cb() { dset="${1}" mpnt="${2}" - unit="sysroot$(echo "$mpnt" | tr '/' '-').mount" + unit="$(systemd-escape --suffix=mount -p "/sysroot${mpnt}")" { echo "[Unit]" diff --git a/contrib/dracut/90zfs/zfs-lib.sh.in b/contrib/dracut/90zfs/zfs-lib.sh.in index afd872d69..b48c97034 100755 --- a/contrib/dracut/90zfs/zfs-lib.sh.in +++ b/contrib/dracut/90zfs/zfs-lib.sh.in @@ -1,6 +1,6 @@ #!/bin/sh -command -v getarg >/dev/null || . /lib/dracut-lib.sh +command -v getarg >/dev/null || . /lib/dracut-lib.sh || . /usr/lib/dracut/modules.d/99base/dracut-lib.sh command -v getargbool >/dev/null || { # Compatibility with older Dracut versions. # With apologies to the Dracut developers. @@ -161,7 +161,9 @@ ask_for_password() { shift done - { flock -s 9; + { + flock -s 9 + # Prompt for password with plymouth, if installed and running. if plymouth --ping 2>/dev/null; then plymouth ask-for-password \ @@ -191,3 +193,58 @@ ask_for_password() { [ "$ret" -ne 0 ] && echo "Wrong password" >&2 return "$ret" } + +# Parse root=, rootfstype=, return them decoded and normalised to zfs:AUTO for auto, plain dset for explicit +# +# True if ZFS-on-root, false if we shouldn't +# +# Supported values: +# root= +# root=zfs +# root=zfs: +# root=zfs:AUTO +# +# root=ZFS=data/set +# root=zfs:data/set +# root=zfs:ZFS=data/set (as a side-effect; allowed but undocumented) +# +# rootfstype=zfs AND root=data/set <=> root=data/set +# rootfstype=zfs AND root= <=> root=zfs:AUTO +# +# '+'es in explicit dataset decoded to ' 's. +decode_root_args() { + if [ -n "$rootfstype" ]; then + [ "$rootfstype" = zfs ] + return + fi + + root=$(getarg root=) + rootfstype=$(getarg rootfstype=) + + # shellcheck disable=SC2249 + case "$root" in + ""|zfs|zfs:|zfs:AUTO) + root=zfs:AUTO + rootfstype=zfs + return 0 + ;; + + ZFS=*|zfs:*) + root="${root#zfs:}" + root="${root#ZFS=}" + root=$(echo "$root" | tr '+' ' ') + rootfstype=zfs + return 0 + ;; + esac + + if [ "$rootfstype" = "zfs" ]; then + case "$root" in + "") root=zfs:AUTO ;; + *) root=$(echo "$root" | tr '+' ' ') ;; + esac + return 0 + fi + + return 1 +} diff --git a/contrib/dracut/90zfs/zfs-load-key.sh.in b/contrib/dracut/90zfs/zfs-load-key.sh.in index c974b3d9e..9fbef8f68 100755 --- a/contrib/dracut/90zfs/zfs-load-key.sh.in +++ b/contrib/dracut/90zfs/zfs-load-key.sh.in @@ -4,32 +4,20 @@ # only run this on systemd systems, we handle the decrypt in mount-zfs.sh in the mount hook otherwise [ -e /bin/systemctl ] || [ -e /usr/bin/systemctl ] || return 0 -# This script only gets executed on systemd systems, see mount-zfs.sh for non-systemd systems +# shellcheck source=zfs-lib.sh.in +. /lib/dracut-zfs-lib.sh -# import the libs now that we know the pool imported -[ -f /lib/dracut-lib.sh ] && dracutlib=/lib/dracut-lib.sh -[ -f /usr/lib/dracut/modules.d/99base/dracut-lib.sh ] && dracutlib=/usr/lib/dracut/modules.d/99base/dracut-lib.sh -# shellcheck source=./lib-zfs.sh.in -. "$dracutlib" - -# load the kernel command line vars -[ -z "$root" ] && root="$(getarg root=)" -# If root is not ZFS= or zfs: or rootfstype is not zfs then we are not supposed to handle it. -[ "${root##zfs:}" = "${root}" ] && [ "${root##ZFS=}" = "${root}" ] && [ "$rootfstype" != "zfs" ] && exit 0 +decode_root_args || return 0 # There is a race between the zpool import and the pre-mount hooks, so we wait for a pool to be imported -while [ "$(zpool list -H)" = "" ]; do - systemctl is-failed --quiet zfs-import-cache.service zfs-import-scan.service && exit 1 +while ! systemctl is-active --quiet zfs-import.target; do + systemctl is-failed --quiet zfs-import-cache.service zfs-import-scan.service && return 1 sleep 0.1s done -# run this after import as zfs-import-cache/scan service is confirmed good -# we do not overwrite the ${root} variable, but create a new one, BOOTFS, to hold the dataset -if [ "${root}" = "zfs:AUTO" ] ; then - BOOTFS="$(zpool list -H -o bootfs | awk '$1 != "-" {print; exit}')" -else - BOOTFS="${root##zfs:}" - BOOTFS="${BOOTFS##ZFS=}" +BOOTFS="$root" +if [ "$BOOTFS" = "zfs:AUTO" ]; then + BOOTFS="$(zpool get -Ho value bootfs | grep -m1 -vFx -)" fi # if pool encryption is active and the zfs command understands '-o encryption' diff --git a/contrib/dracut/README.md b/contrib/dracut/README.md index fc3d504ef..522f2ce3c 100644 --- a/contrib/dracut/README.md +++ b/contrib/dracut/README.md @@ -16,18 +16,18 @@ Encrypted datasets have keys loaded automatically or prompted for. If the root dataset contains children with `mountpoint=`s of `/etc`, `/bin`, `/lib*`, or `/usr`, they're mounted too. ## cmdline -1. `root=` | Root dataset is… | Pools imported | - -------------------|----------------------------------------------------------|----------------| - *(empty)* | the first `bootfs=` after `zpool import -aN` | all | - `zfs:AUTO` | *(as above, but overriding other autoselection methods)* | all | - `ZFS=pool/dataset` | `pool/dataset` | `pool` | - `zfs:pool/dataset` | *(as above)* | `pool` | +1. `root=` | Root dataset is… | + ---------------------------|----------------------------------------------------------| + *(empty)* | the first `bootfs=` after `zpool import -aN` | + `zfs:AUTO`, `zfs:`, `zfs` | *(as above, but overriding other autoselection methods)* | + `ZFS=pool/dataset` | `pool/dataset` | + `zfs:pool/dataset` | *(as above)* | All `+`es are replaced with spaces (i.e. to boot from `root pool/data set`, pass `root=zfs:root+pool/data+set`). The dataset can be at any depth, including being the pool's root dataset (i.e. `root=zfs:pool`). - `rootfstype=zfs` is mostly equivalent to `root=zfs:AUTO`. + `rootfstype=zfs` is equivalent to `root=zfs:AUTO`, `rootfstype=zfs root=pool/dataset` is equivalent to `root=zfs:pool/dataset`. 2. `spl_hostid`: passed to `zgenhostid -f`, useful to override the `/etc/hostid` file baked into the initrd.