mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-27 11:29:36 +03:00
aa5a4eb5d0
This effectively reverts4fc411f7a3
(part of #6807) andf6fbe25664
(#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
986 lines
26 KiB
Plaintext
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
|
|
}
|