mirror_zfs/contrib/initramfs/scripts/zfs
наб 04344d5bc3 i-t: don't brokenly set the scheduler for root pool vdev's disks
This effectively reverts
  4fc411f7a3 (part of #6807) and
  f6fbe25664 (#9042) ‒
the code itself and latter PR cite symmetry with whole-disk-vdev
behaviour (presumably because rootfs vdevs are rarely whole disks),
but the code is broken for NVME devices (indeed, it'd strip the
controller number instead of the (potential) partition number, turning
"nvme0n1p1" into "nvmen1p1", which would then subsequently fail the
sysfs existence check); it could be fixed to handle those (and any
others) rather easily by dereferencing /sys/class/block/$devname,
but this isn't the place for setting this ‒ as noted in the commit that
removed setting the scheduler by default
(9e17e6f254) ‒ use an udev rule

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Closes #11838
2021-04-07 13:28:56 -07:00

986 lines
26 KiB
Plaintext

# ZFS boot stub for initramfs-tools.
#
# In the initramfs environment, the /init script sources this stub to
# override the default functions in the /scripts/local script.
#
# Enable this by passing boot=zfs on the kernel command line.
#
# Source the common functions
. /etc/zfs/zfs-functions
# Start interactive shell.
# Use debian's panic() if defined, because it allows to prevent shell access
# by setting panic in cmdline (e.g. panic=0 or panic=15).
# See "4.5 Disable root prompt on the initramfs" of Securing Debian Manual:
# https://www.debian.org/doc/manuals/securing-debian-howto/ch4.en.html
shell() {
if command -v panic > /dev/null 2>&1; then
panic
else
/bin/sh
fi
}
# This runs any scripts that should run before we start importing
# pools and mounting any filesystems.
pre_mountroot()
{
if command -v run_scripts > /dev/null 2>&1
then
if [ -f "/scripts/local-top" ] || [ -d "/scripts/local-top" ]
then
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Running /scripts/local-top"
run_scripts /scripts/local-top
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
if [ -f "/scripts/local-premount" ] || [ -d "/scripts/local-premount" ]
then
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Running /scripts/local-premount"
run_scripts /scripts/local-premount
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
fi
}
# If plymouth is available, hide the splash image.
disable_plymouth()
{
if [ -x /bin/plymouth ] && /bin/plymouth --ping
then
/bin/plymouth hide-splash >/dev/null 2>&1
fi
}
# Get a ZFS filesystem property value.
get_fs_value()
{
fs="$1"
value=$2
"${ZFS}" get -H -ovalue "$value" "$fs" 2> /dev/null
}
# Find the 'bootfs' property on pool $1.
# If the property does not contain '/', then ignore this
# pool by exporting it again.
find_rootfs()
{
pool="$1"
# If 'POOL_IMPORTED' isn't set, no pool imported and therefore
# we won't be able to find a root fs.
[ -z "${POOL_IMPORTED}" ] && return 1
# If it's already specified, just keep it mounted and exit
# User (kernel command line) must be correct.
[ -n "${ZFS_BOOTFS}" ] && return 0
# Not set, try to find it in the 'bootfs' property of the pool.
# NOTE: zpool does not support 'get -H -ovalue bootfs'...
ZFS_BOOTFS=$("${ZPOOL}" list -H -obootfs "$pool")
# Make sure it's not '-' and that it starts with /.
if [ "${ZFS_BOOTFS}" != "-" ] && \
get_fs_value "${ZFS_BOOTFS}" mountpoint | grep -q '^/$'
then
# Keep it mounted
POOL_IMPORTED=1
return 0
fi
# Not boot fs here, export it and later try again..
"${ZPOOL}" export "$pool"
POOL_IMPORTED=
ZFS_BOOTFS=
return 1
}
# Support function to get a list of all pools, separated with ';'
find_pools()
{
CMD="$*"
pools=$($CMD 2> /dev/null | \
grep -E "pool:|^[a-zA-Z0-9]" | \
sed 's@.*: @@' | \
while read -r pool; do \
printf "%s" "$pool;"
done)
echo "${pools%%;}" # Return without the last ';'.
}
# Get a list of all available pools
get_pools()
{
if [ -n "${ZFS_POOL_IMPORT}" ]; then
echo "$ZFS_POOL_IMPORT"
return 0
fi
# Get the base list of available pools.
available_pools=$(find_pools "$ZPOOL" import)
# Just in case - seen it happen (that a pool isn't visible/found
# with a simple "zpool import" but only when using the "-d"
# option or setting ZPOOL_IMPORT_PATH).
if [ -d "/dev/disk/by-id" ]
then
npools=$(find_pools "$ZPOOL" import -d /dev/disk/by-id)
if [ -n "$npools" ]
then
# Because we have found extra pool(s) here, which wasn't
# found 'normally', we need to force USE_DISK_BY_ID to
# make sure we're able to actually import it/them later.
USE_DISK_BY_ID='yes'
if [ -n "$available_pools" ]
then
# Filter out duplicates (pools found with the simple
# "zpool import" but which is also found with the
# "zpool import -d ...").
npools=$(echo "$npools" | sed "s,$available_pools,,")
# Add the list to the existing list of
# available pools
available_pools="$available_pools;$npools"
else
available_pools="$npools"
fi
fi
fi
# Filter out any exceptions...
if [ -n "$ZFS_POOL_EXCEPTIONS" ]
then
found=""
apools=""
OLD_IFS="$IFS" ; IFS=";"
for pool in $available_pools
do
for exception in $ZFS_POOL_EXCEPTIONS
do
[ "$pool" = "$exception" ] && continue 2
found="$pool"
done
if [ -n "$found" ]
then
if [ -n "$apools" ]
then
apools="$apools;$pool"
else
apools="$pool"
fi
fi
done
IFS="$OLD_IFS"
available_pools="$apools"
fi
# Return list of available pools.
echo "$available_pools"
}
# Import given pool $1
import_pool()
{
pool="$1"
# Verify that the pool isn't already imported
# Make as sure as we can to not require '-f' to import.
"${ZPOOL}" get name,guid -o value -H 2>/dev/null | grep -Fxq "$pool" && return 0
# For backwards compatibility, make sure that ZPOOL_IMPORT_PATH is set
# to something we can use later with the real import(s). We want to
# make sure we find all by* dirs, BUT by-vdev should be first (if it
# exists).
if [ -n "$USE_DISK_BY_ID" ] && [ -z "$ZPOOL_IMPORT_PATH" ]
then
dirs="$(for dir in $(echo /dev/disk/by-*)
do
# Ignore by-vdev here - we want it first!
echo "$dir" | grep -q /by-vdev && continue
[ ! -d "$dir" ] && continue
printf "%s" "$dir:"
done | sed 's,:$,,g')"
if [ -d "/dev/disk/by-vdev" ]
then
# Add by-vdev at the beginning.
ZPOOL_IMPORT_PATH="/dev/disk/by-vdev:"
fi
# ... and /dev at the very end, just for good measure.
ZPOOL_IMPORT_PATH="$ZPOOL_IMPORT_PATH$dirs:/dev"
fi
# Needs to be exported for "zpool" to catch it.
[ -n "$ZPOOL_IMPORT_PATH" ] && export ZPOOL_IMPORT_PATH
[ "$quiet" != "y" ] && zfs_log_begin_msg \
"Importing pool '${pool}' using defaults"
ZFS_CMD="${ZPOOL} import -N ${ZPOOL_FORCE} ${ZPOOL_IMPORT_OPTS}"
ZFS_STDERR="$($ZFS_CMD "$pool" 2>&1)"
ZFS_ERROR="$?"
if [ "${ZFS_ERROR}" != 0 ]
then
[ "$quiet" != "y" ] && zfs_log_failure_msg "${ZFS_ERROR}"
if [ -f "${ZPOOL_CACHE}" ]
then
[ "$quiet" != "y" ] && zfs_log_begin_msg \
"Importing pool '${pool}' using cachefile."
ZFS_CMD="${ZPOOL} import -c ${ZPOOL_CACHE} -N ${ZPOOL_FORCE} ${ZPOOL_IMPORT_OPTS}"
ZFS_STDERR="$($ZFS_CMD "$pool" 2>&1)"
ZFS_ERROR="$?"
fi
if [ "${ZFS_ERROR}" != 0 ]
then
[ "$quiet" != "y" ] && zfs_log_failure_msg "${ZFS_ERROR}"
disable_plymouth
echo ""
echo "Command: ${ZFS_CMD} '$pool'"
echo "Message: $ZFS_STDERR"
echo "Error: $ZFS_ERROR"
echo ""
echo "Failed to import pool '$pool'."
echo "Manually import the pool and exit."
shell
fi
fi
[ "$quiet" != "y" ] && zfs_log_end_msg
POOL_IMPORTED=1
return 0
}
# Load ZFS modules
# Loading a module in a initrd require a slightly different approach,
# with more logging etc.
load_module_initrd()
{
if [ "$ZFS_INITRD_PRE_MOUNTROOT_SLEEP" -gt 0 ] 2>/dev/null
then
if [ "$quiet" != "y" ]; then
zfs_log_begin_msg "Sleeping for" \
"$ZFS_INITRD_PRE_MOUNTROOT_SLEEP seconds..."
fi
sleep "$ZFS_INITRD_PRE_MOUNTROOT_SLEEP"
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
# Wait for all of the /dev/{hd,sd}[a-z] device nodes to appear.
if command -v wait_for_udev > /dev/null 2>&1 ; then
wait_for_udev 10
elif command -v wait_for_dev > /dev/null 2>&1 ; then
wait_for_dev
fi
# zpool import refuse to import without a valid /proc/self/mounts
[ ! -f /proc/self/mounts ] && mount proc /proc
# Load the module
load_module "zfs" || return 1
if [ "$ZFS_INITRD_POST_MODPROBE_SLEEP" -gt 0 ] 2>/dev/null
then
if [ "$quiet" != "y" ]; then
zfs_log_begin_msg "Sleeping for" \
"$ZFS_INITRD_POST_MODPROBE_SLEEP seconds..."
fi
sleep "$ZFS_INITRD_POST_MODPROBE_SLEEP"
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
return 0
}
# Mount a given filesystem
mount_fs()
{
fs="$1"
# Check that the filesystem exists
"${ZFS}" list -oname -tfilesystem -H "${fs}" > /dev/null 2>&1 || return 1
# Skip filesystems with canmount=off. The root fs should not have
# canmount=off, but ignore it for backwards compatibility just in case.
if [ "$fs" != "${ZFS_BOOTFS}" ]
then
canmount=$(get_fs_value "$fs" canmount)
[ "$canmount" = "off" ] && return 0
fi
# Need the _original_ datasets mountpoint!
mountpoint=$(get_fs_value "$fs" mountpoint)
if [ "$mountpoint" = "legacy" ] || [ "$mountpoint" = "none" ]; then
# Can't use the mountpoint property. Might be one of our
# clones. Check the 'org.zol:mountpoint' property set in
# clone_snap() if that's usable.
mountpoint=$(get_fs_value "$fs" org.zol:mountpoint)
if [ "$mountpoint" = "legacy" ] ||
[ "$mountpoint" = "none" ] ||
[ "$mountpoint" = "-" ]
then
if [ "$fs" != "${ZFS_BOOTFS}" ]; then
# We don't have a proper mountpoint and this
# isn't the root fs.
return 0
else
# Last hail-mary: Hope 'rootmnt' is set!
mountpoint=""
fi
fi
if [ "$mountpoint" = "legacy" ]; then
ZFS_CMD="mount -t zfs"
else
# If it's not a legacy filesystem, it can only be a
# native one...
ZFS_CMD="mount -o zfsutil -t zfs"
fi
else
ZFS_CMD="mount -o zfsutil -t zfs"
fi
# Possibly decrypt a filesystem using native encryption.
decrypt_fs "$fs"
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Mounting '${fs}' on '${rootmnt}/${mountpoint}'"
[ -n "${ZFS_DEBUG}" ] && \
zfs_log_begin_msg "CMD: '$ZFS_CMD ${fs} ${rootmnt}/${mountpoint}'"
ZFS_STDERR=$(${ZFS_CMD} "${fs}" "${rootmnt}/${mountpoint}" 2>&1)
ZFS_ERROR=$?
if [ "${ZFS_ERROR}" != 0 ]
then
[ "$quiet" != "y" ] && zfs_log_failure_msg "${ZFS_ERROR}"
disable_plymouth
echo ""
echo "Command: ${ZFS_CMD} ${fs} ${rootmnt}/${mountpoint}"
echo "Message: $ZFS_STDERR"
echo "Error: $ZFS_ERROR"
echo ""
echo "Failed to mount ${fs} on ${rootmnt}/${mountpoint}."
echo "Manually mount the filesystem and exit."
shell
else
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
return 0
}
# Unlock a ZFS native encrypted filesystem.
decrypt_fs()
{
fs="$1"
# If pool encryption is active and the zfs command understands '-o encryption'
if [ "$(zpool list -H -o feature@encryption "$(echo "${fs}" | awk -F/ '{print $1}')")" = 'active' ]; then
# Determine dataset that holds key for root dataset
ENCRYPTIONROOT="$(get_fs_value "${fs}" encryptionroot)"
KEYLOCATION="$(get_fs_value "${ENCRYPTIONROOT}" keylocation)"
echo "${ENCRYPTIONROOT}" > /run/zfs_fs_name
# If root dataset is encrypted...
if ! [ "${ENCRYPTIONROOT}" = "-" ]; then
KEYSTATUS="$(get_fs_value "${ENCRYPTIONROOT}" keystatus)"
# Continue only if the key needs to be loaded
[ "$KEYSTATUS" = "unavailable" ] || return 0
TRY_COUNT=3
# If key is stored in a file, do not prompt
if ! [ "${KEYLOCATION}" = "prompt" ]; then
$ZFS load-key "${ENCRYPTIONROOT}"
# Prompt with plymouth, if active
elif [ -e /bin/plymouth ] && /bin/plymouth --ping 2>/dev/null; then
echo "plymouth" > /run/zfs_console_askpwd_cmd
while [ $TRY_COUNT -gt 0 ]; do
plymouth ask-for-password --prompt "Encrypted ZFS password for ${ENCRYPTIONROOT}" | \
$ZFS load-key "${ENCRYPTIONROOT}" && break
TRY_COUNT=$((TRY_COUNT - 1))
done
# Prompt with systemd, if active
elif [ -e /run/systemd/system ]; then
echo "systemd-ask-password" > /run/zfs_console_askpwd_cmd
while [ $TRY_COUNT -gt 0 ]; do
systemd-ask-password "Encrypted ZFS password for ${ENCRYPTIONROOT}" --no-tty | \
$ZFS load-key "${ENCRYPTIONROOT}" && break
TRY_COUNT=$((TRY_COUNT - 1))
done
# Prompt with ZFS tty, otherwise
else
# Temporarily setting "printk" to "7" allows the prompt to appear even when the "quiet" kernel option has been used
echo "load-key" > /run/zfs_console_askpwd_cmd
storeprintk="$(awk '{print $1}' /proc/sys/kernel/printk)"
echo 7 > /proc/sys/kernel/printk
$ZFS load-key "${ENCRYPTIONROOT}"
echo "$storeprintk" > /proc/sys/kernel/printk
fi
fi
fi
return 0
}
# Destroy a given filesystem.
destroy_fs()
{
fs="$1"
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Destroying '$fs'"
ZFS_CMD="${ZFS} destroy $fs"
ZFS_STDERR="$(${ZFS_CMD} 2>&1)"
ZFS_ERROR="$?"
if [ "${ZFS_ERROR}" != 0 ]
then
[ "$quiet" != "y" ] && zfs_log_failure_msg "${ZFS_ERROR}"
disable_plymouth
echo ""
echo "Command: $ZFS_CMD"
echo "Message: $ZFS_STDERR"
echo "Error: $ZFS_ERROR"
echo ""
echo "Failed to destroy '$fs'. Please make sure that '$fs' is not available."
echo "Hint: Try: zfs destroy -Rfn $fs"
echo "If this dryrun looks good, then remove the 'n' from '-Rfn' and try again."
shell
else
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
return 0
}
# Clone snapshot $1 to destination filesystem $2
# Set 'canmount=noauto' and 'mountpoint=none' so that we get to keep
# manual control over it's mounting (i.e., make sure it's not automatically
# mounted with a 'zfs mount -a' in the init/systemd scripts).
clone_snap()
{
snap="$1"
destfs="$2"
mountpoint="$3"
[ "$quiet" != "y" ] && zfs_log_begin_msg "Cloning '$snap' to '$destfs'"
# Clone the snapshot into a dataset we can boot from
# + We don't want this filesystem to be automatically mounted, we
# want control over this here and nowhere else.
# + We don't need any mountpoint set for the same reason.
# We use the 'org.zol:mountpoint' property to remember the mountpoint.
ZFS_CMD="${ZFS} clone -o canmount=noauto -o mountpoint=none"
ZFS_CMD="${ZFS_CMD} -o org.zol:mountpoint=${mountpoint}"
ZFS_CMD="${ZFS_CMD} $snap $destfs"
ZFS_STDERR="$(${ZFS_CMD} 2>&1)"
ZFS_ERROR="$?"
if [ "${ZFS_ERROR}" != 0 ]
then
[ "$quiet" != "y" ] && zfs_log_failure_msg "${ZFS_ERROR}"
disable_plymouth
echo ""
echo "Command: $ZFS_CMD"
echo "Message: $ZFS_STDERR"
echo "Error: $ZFS_ERROR"
echo ""
echo "Failed to clone snapshot."
echo "Make sure that the any problems are corrected and then make sure"
echo "that the dataset '$destfs' exists and is bootable."
shell
else
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
return 0
}
# Rollback a given snapshot.
rollback_snap()
{
snap="$1"
[ "$quiet" != "y" ] && zfs_log_begin_msg "Rollback $snap"
ZFS_CMD="${ZFS} rollback -Rf $snap"
ZFS_STDERR="$(${ZFS_CMD} 2>&1)"
ZFS_ERROR="$?"
if [ "${ZFS_ERROR}" != 0 ]
then
[ "$quiet" != "y" ] && zfs_log_failure_msg "${ZFS_ERROR}"
disable_plymouth
echo ""
echo "Command: $ZFS_CMD"
echo "Message: $ZFS_STDERR"
echo "Error: $ZFS_ERROR"
echo ""
echo "Failed to rollback snapshot."
shell
else
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
return 0
}
# Get a list of snapshots, give them as a numbered list
# to the user to choose from.
ask_user_snap()
{
fs="$1"
i=1
# We need to temporarily disable debugging. Set 'debug' so we
# remember to enabled it again.
if [ -n "${ZFS_DEBUG}" ]; then
unset ZFS_DEBUG
set +x
debug=1
fi
# Because we need the resulting snapshot, which is sent on
# stdout to the caller, we use stderr for our questions.
echo "What snapshot do you want to boot from?" > /dev/stderr
while read -r snap; do
echo " $i: ${snap}" > /dev/stderr
eval "$(echo SNAP_$i=$snap)"
i=$((i + 1))
done <<EOT
$("${ZFS}" list -H -oname -tsnapshot -r "${fs}")
EOT
echo "%s" " Snap nr [1-$((i-1))]? " > /dev/stderr
read -r snapnr
# Re-enable debugging.
if [ -n "${debug}" ]; then
ZFS_DEBUG=1
set -x
fi
echo "$(eval echo '$SNAP_'$snapnr)"
}
setup_snapshot_booting()
{
snap="$1"
retval=0
# Make sure that the snapshot specified actually exists.
if [ ! "$(get_fs_value "${snap}" type)" ]
then
# Snapshot does not exist (...@<null> ?)
# ask the user for a snapshot to use.
snap="$(ask_user_snap "${snap%%@*}")"
fi
# Separate the full snapshot ('$snap') into it's filesystem and
# snapshot names. Would have been nice with a split() function..
rootfs="${snap%%@*}"
snapname="${snap##*@}"
ZFS_BOOTFS="${rootfs}_${snapname}"
if ! grep -qiE '(^|[^\\](\\\\)* )(rollback)=(on|yes|1)( |$)' /proc/cmdline
then
# If the destination dataset for the clone
# already exists, destroy it. Recursively
if [ "$(get_fs_value "${rootfs}_${snapname}" type)" ]; then
filesystems=$("${ZFS}" list -oname -tfilesystem -H \
-r -Sname "${ZFS_BOOTFS}")
for fs in $filesystems; do
destroy_fs "${fs}"
done
fi
fi
# Get all snapshots, recursively (might need to clone /usr, /var etc
# as well).
for s in $("${ZFS}" list -H -oname -tsnapshot -r "${rootfs}" | \
grep "${snapname}")
do
if grep -qiE '(^|[^\\](\\\\)* )(rollback)=(on|yes|1)( |$)' /proc/cmdline
then
# Rollback snapshot
rollback_snap "$s" || retval=$((retval + 1))
else
# Setup a destination filesystem name.
# Ex: Called with 'rpool/ROOT/debian@snap2'
# rpool/ROOT/debian@snap2 => rpool/ROOT/debian_snap2
# rpool/ROOT/debian/boot@snap2 => rpool/ROOT/debian_snap2/boot
# rpool/ROOT/debian/usr@snap2 => rpool/ROOT/debian_snap2/usr
# rpool/ROOT/debian/var@snap2 => rpool/ROOT/debian_snap2/var
subfs="${s##$rootfs}"
subfs="${subfs%%@$snapname}"
destfs="${rootfs}_${snapname}" # base fs.
[ -n "$subfs" ] && destfs="${destfs}$subfs" # + sub fs.
# Get the mountpoint of the filesystem, to be used
# with clone_snap(). If legacy or none, then use
# the sub fs value.
mountpoint=$(get_fs_value "${s%%@*}" mountpoint)
if [ "$mountpoint" = "legacy" ] || \
[ "$mountpoint" = "none" ]
then
if [ -n "${subfs}" ]; then
mountpoint="${subfs}"
else
mountpoint="/"
fi
fi
# Clone the snapshot into its own
# filesystem
clone_snap "$s" "${destfs}" "${mountpoint}" || \
retval=$((retval + 1))
fi
done
# If we haven't return yet, we have a problem...
return "${retval}"
}
# ================================================================
# This is the main function.
mountroot()
{
# ----------------------------------------------------------------
# I N I T I A L S E T U P
# ------------
# Run the pre-mount scripts from /scripts/local-top.
pre_mountroot
# ------------
# Source the default setup variables.
[ -r '/etc/default/zfs' ] && . /etc/default/zfs
# ------------
# Support debug option
if grep -qiE '(^|[^\\](\\\\)* )(zfs_debug|zfs\.debug|zfsdebug)=(on|yes|1)( |$)' /proc/cmdline
then
ZFS_DEBUG=1
mkdir /var/log
#exec 2> /var/log/boot.debug
set -x
fi
# ------------
# Load ZFS module etc.
if ! load_module_initrd; then
disable_plymouth
echo ""
echo "Failed to load ZFS modules."
echo "Manually load the modules and exit."
shell
fi
# ------------
# Look for the cache file (if any).
[ ! -f ${ZPOOL_CACHE} ] && unset ZPOOL_CACHE
# ------------
# Compatibility: 'ROOT' is for Debian GNU/Linux (etc),
# 'root' is for Redhat/Fedora (etc),
# 'REAL_ROOT' is for Gentoo
if [ -z "$ROOT" ]
then
[ -n "$root" ] && ROOT=${root}
[ -n "$REAL_ROOT" ] && ROOT=${REAL_ROOT}
fi
# ------------
# Where to mount the root fs in the initrd - set outside this script
# Compatibility: 'rootmnt' is for Debian GNU/Linux (etc),
# 'NEWROOT' is for RedHat/Fedora (etc),
# 'NEW_ROOT' is for Gentoo
if [ -z "$rootmnt" ]
then
[ -n "$NEWROOT" ] && rootmnt=${NEWROOT}
[ -n "$NEW_ROOT" ] && rootmnt=${NEW_ROOT}
fi
# ------------
# No longer set in the defaults file, but it could have been set in
# get_pools() in some circumstances. If it's something, but not 'yes',
# it's no good to us.
[ -n "$USE_DISK_BY_ID" ] && [ "$USE_DISK_BY_ID" != 'yes' ] && \
unset USE_DISK_BY_ID
# ----------------------------------------------------------------
# P A R S E C O M M A N D L I N E O P T I O N S
# This part is the really ugly part - there's so many options and permutations
# 'out there', and if we should make this the 'primary' source for ZFS initrd
# scripting, we need/should support them all.
#
# Supports the following kernel command line argument combinations
# (in this order - first match win):
#
# rpool=<pool> (tries to finds bootfs automatically)
# bootfs=<pool>/<dataset> (uses this for rpool - first part)
# rpool=<pool> bootfs=<pool>/<dataset>
# -B zfs-bootfs=<pool>/<fs> (uses this for rpool - first part)
# rpool=rpool (default if none of the above is used)
# root=<pool>/<dataset> (uses this for rpool - first part)
# root=ZFS=<pool>/<dataset> (uses this for rpool - first part, without 'ZFS=')
# root=zfs:AUTO (tries to detect both pool and rootfs
# root=zfs:<pool>/<dataset> (uses this for rpool - first part, without 'zfs:')
#
# Option <dataset> could also be <snapshot>
# Option <pool> could also be <guid>
# ------------
# Support force option
# In addition, setting one of zfs_force, zfs.force or zfsforce to
# 'yes', 'on' or '1' will make sure we force import the pool.
# This should (almost) never be needed, but it's here for
# completeness.
ZPOOL_FORCE=""
if grep -qiE '(^|[^\\](\\\\)* )(zfs_force|zfs\.force|zfsforce)=(on|yes|1)( |$)' /proc/cmdline
then
ZPOOL_FORCE="-f"
fi
# ------------
# Look for 'rpool' and 'bootfs' parameter
[ -n "$rpool" ] && ZFS_RPOOL="${rpool#rpool=}"
[ -n "$bootfs" ] && ZFS_BOOTFS="${bootfs#bootfs=}"
# ------------
# If we have 'ROOT' (see above), but not 'ZFS_BOOTFS', then use
# 'ROOT'
[ -n "$ROOT" ] && [ -z "${ZFS_BOOTFS}" ] && ZFS_BOOTFS="$ROOT"
# ------------
# Check for the `-B zfs-bootfs=%s/%u,...` kind of parameter.
# NOTE: Only use the pool name and dataset. The rest is not
# supported by OpenZFS (whatever it's for).
if [ -z "$ZFS_RPOOL" ]
then
# The ${zfs-bootfs} variable is set at the kernel command
# line, usually by GRUB, but it cannot be referenced here
# directly because bourne variable names cannot contain a
# hyphen.
#
# Reassign the variable by dumping the environment and
# stripping the zfs-bootfs= prefix. Let the shell handle
# quoting through the eval command.
eval ZFS_RPOOL=$(set | sed -n -e 's,^zfs-bootfs=,,p')
fi
# ------------
# No root fs or pool specified - do auto detect.
if [ -z "$ZFS_RPOOL" ] && [ -z "${ZFS_BOOTFS}" ]
then
# Do auto detect. Do this by 'cheating' - set 'root=zfs:AUTO'
# which will be caught later
ROOT='zfs:AUTO'
fi
# ----------------------------------------------------------------
# F I N D A N D I M P O R T C O R R E C T P O O L
# ------------
if [ "$ROOT" = "zfs:AUTO" ]
then
# Try to detect both pool and root fs.
# If we got here, that means we don't have a hint so as to
# the root dataset, but with root=zfs:AUTO on cmdline,
# this says "zfs:AUTO" here and interferes with checks later
ZFS_BOOTFS=
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Attempting to import additional pools."
# Get a list of pools available for import
if [ -n "$ZFS_RPOOL" ]
then
# We've specified a pool - check only that
POOLS=$ZFS_RPOOL
else
POOLS=$(get_pools)
fi
OLD_IFS="$IFS" ; IFS=";"
for pool in $POOLS
do
[ -z "$pool" ] && continue
IFS="$OLD_IFS" import_pool "$pool"
IFS="$OLD_IFS" find_rootfs "$pool" && break
done
IFS="$OLD_IFS"
[ "$quiet" != "y" ] && zfs_log_end_msg $ZFS_ERROR
else
# No auto - use value from the command line option.
# Strip 'zfs:' and 'ZFS='.
ZFS_BOOTFS="${ROOT#*[:=]}"
# Strip everything after the first slash.
ZFS_RPOOL="${ZFS_BOOTFS%%/*}"
fi
# Import the pool (if not already done so in the AUTO check above).
if [ -n "$ZFS_RPOOL" ] && [ -z "${POOL_IMPORTED}" ]
then
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Importing ZFS root pool '$ZFS_RPOOL'"
import_pool "${ZFS_RPOOL}"
find_rootfs "${ZFS_RPOOL}"
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
if [ -z "${POOL_IMPORTED}" ]
then
# No pool imported, this is serious!
disable_plymouth
echo ""
echo "Command: $ZFS_CMD"
echo "Message: $ZFS_STDERR"
echo "Error: $ZFS_ERROR"
echo ""
echo "No pool imported. Manually import the root pool"
echo "at the command prompt and then exit."
echo "Hint: Try: zpool import -R ${rootmnt} -N ${ZFS_RPOOL}"
shell
fi
# In case the pool was specified as guid, resolve guid to name
pool="$("${ZPOOL}" get name,guid -o name,value -H | \
awk -v pool="${ZFS_RPOOL}" '$2 == pool { print $1 }')"
if [ -n "$pool" ]; then
# If $ZFS_BOOTFS contains guid, replace the guid portion with $pool
ZFS_BOOTFS=$(echo "$ZFS_BOOTFS" | \
sed -e "s/$("${ZPOOL}" get guid -o value "$pool" -H)/$pool/g")
ZFS_RPOOL="${pool}"
fi
# ----------------------------------------------------------------
# P R E P A R E R O O T F I L E S Y S T E M
if [ -n "${ZFS_BOOTFS}" ]
then
# Booting from a snapshot?
# Will overwrite the ZFS_BOOTFS variable like so:
# rpool/ROOT/debian@snap2 => rpool/ROOT/debian_snap2
echo "${ZFS_BOOTFS}" | grep -q '@' && \
setup_snapshot_booting "${ZFS_BOOTFS}"
fi
if [ -z "${ZFS_BOOTFS}" ]
then
# Still nothing! Let the user sort this out.
disable_plymouth
echo ""
echo "Error: Unknown root filesystem - no 'bootfs' pool property and"
echo " not specified on the kernel command line."
echo ""
echo "Manually mount the root filesystem on $rootmnt and then exit."
echo "Hint: Try: mount -o zfsutil -t zfs ${ZFS_RPOOL-rpool}/ROOT/system $rootmnt"
shell
fi
# ----------------------------------------------------------------
# M O U N T F I L E S Y S T E M S
# * Ideally, the root filesystem would be mounted like this:
#
# zpool import -R "$rootmnt" -N "$ZFS_RPOOL"
# zfs mount -o mountpoint=/ "${ZFS_BOOTFS}"
#
# but the MOUNTPOINT prefix is preserved on descendent filesystem
# after the pivot into the regular root, which later breaks things
# like `zfs mount -a` and the /proc/self/mounts refresh.
#
# * Mount additional filesystems required
# Such as /usr, /var, /usr/local etc.
# NOTE: Mounted in the order specified in the
# ZFS_INITRD_ADDITIONAL_DATASETS variable so take care!
# Go through the complete list (recursively) of all filesystems below
# the real root dataset
filesystems=$("${ZFS}" list -oname -tfilesystem -H -r "${ZFS_BOOTFS}")
for fs in $filesystems $ZFS_INITRD_ADDITIONAL_DATASETS
do
mount_fs "$fs"
done
touch /run/zfs_unlock_complete
if [ -e /run/zfs_unlock_complete_notify ]; then
read -r zfs_unlock_complete_notify < /run/zfs_unlock_complete_notify
fi
# ------------
# Debugging information
if [ -n "${ZFS_DEBUG}" ]
then
#exec 2>&1-
echo "DEBUG: imported pools:"
"${ZPOOL}" list -H
echo
echo "DEBUG: mounted ZFS filesystems:"
mount | grep zfs
echo
echo "=> waiting for ENTER before continuing because of 'zfsdebug=1'. "
printf "%s" " 'c' for shell, 'r' for reboot, 'ENTER' to continue. "
read -r b
[ "$b" = "c" ] && /bin/sh
[ "$b" = "r" ] && reboot -f
set +x
fi
# ------------
# Run local bottom script
if command -v run_scripts > /dev/null 2>&1
then
if [ -f "/scripts/local-bottom" ] || [ -d "/scripts/local-bottom" ]
then
[ "$quiet" != "y" ] && \
zfs_log_begin_msg "Running /scripts/local-bottom"
run_scripts /scripts/local-bottom
[ "$quiet" != "y" ] && zfs_log_end_msg
fi
fi
}