From 42b01560a92f4bfad494710dd73f162e193a1483 Mon Sep 17 00:00:00 2001 From: Turbo Fredriksson Date: Mon, 6 Jul 2015 00:24:11 +0200 Subject: [PATCH] Add functionality that exists in initramfs to dracut. * Support 'zfs:' import and mounting. This was for some reason removed in eda3d4e1de94cd3a9247bb90a64413a89e736847. * Support booting from snapshots. * Support mounting recursive filesystems. * Support mounting of additional filesystems. * Support mounting nativly encrypted filesystems. * Move code snippet that fetches and mounts recursive filesystems into recursive_mount_filesystems(). * zfs_set_ifs() needs to be a global func. We need it in a number of places! * Use this wherever we use CMD="somecmd someopt someopt" ; OUT="$($CMD someopt)" * Dracut have the func emergency_shell() which does a little more. For those that don't have it, just run '/bin/sh -i -l'. --- contrib/dracut/90zfs/mount-zfs.sh.in | 45 ++++- contrib/dracut/README.dracut.markdown | 25 +++ contrib/initramfs/scripts/zfs | 9 +- contrib/shell-common/zfs-functions.in | 227 +++++++++++++++++--------- contrib/shell-common/zfs.in | 3 + 5 files changed, 220 insertions(+), 89 deletions(-) diff --git a/contrib/dracut/90zfs/mount-zfs.sh.in b/contrib/dracut/90zfs/mount-zfs.sh.in index 4ee3bc300547..634301e8e77b 100755 --- a/contrib/dracut/90zfs/mount-zfs.sh.in +++ b/contrib/dracut/90zfs/mount-zfs.sh.in @@ -15,29 +15,58 @@ esac udev_trigger if [ "${root}" = "zfs:AUTO" ] ; then - ZFS_BOOTFS="" POOLS=$(get_pools) IFS=";" + for pool in $POOLS do [ -z "$pool" ] && continue + ZFS_BOOTFS="" # No 'else' or return below - if there's more pools, we try # them. If not, we'll drop through and go to the end # where we need a reboot because we couldn't find a root fs. - if import_pool "$pool"; then - if find_rootfs "$pool"; then - if mount_fs "${ZFS_BOOTFS}"; then - info "ZFS: Using ${ZFS_BOOTFS} as root." - - rootok=1 + if zfs_action "Importing pool ${pool}" import_pool "${pool}" + then + if find_rootfs "${pool}"; then + if recursive_mount_filesystems \ + "Using '${ZFS_BOOTFS}' as root." \ + "${ZFS_BOOTFS}" + then IFS="$OLD_IFS" - + rootok=1 return 0 fi fi fi done +else + # Because of the case at the top, it's either 'zfs:AUTO' or + # 'zfs:'. Import the pool from rootfs and mount it. + root="${root#zfs:}" # Remove leading 'zfs:' + pool="${root%%/*}" # Remove everything after first slash. + ZFS_BOOTFS="${root}" # For zfs-functions. + if zfs_action "Importing pool ${pool}" import_pool "${pool}"; then + info "ZFS: Using ${pool} as root pool." + + # Will be overwritten in clone_snap() if we're booting from + # a snapshot. + ZFS_BOOTFS_ORIG="$ZFS_BOOTFS" + + # Booting from a snapshot? Updates ZFS_BOOTFS_ORIG and ZFS_BOOTFS... + echo "${root}" | grep -q '@' && setup_snapshot_booting + root="$ZFS_BOOTFS" + + if recursive_mount_filesystems "Using '${root}' as root." \ + "${root}" + then + rootok=1 + return 0 + fi + else + warn "ZFS: Can't import '${pool}'." + # Fallthrough + fi fi rootok=0 diff --git a/contrib/dracut/README.dracut.markdown b/contrib/dracut/README.dracut.markdown index d7b4f76adb86..a6593257ce9a 100644 --- a/contrib/dracut/README.dracut.markdown +++ b/contrib/dracut/README.dracut.markdown @@ -42,6 +42,31 @@ parameters passed in from the boot loader: * `root=...`: If not set, importable pools are searched for a bootfs attribute. If an explicitly set root is desired, you may use `root=ZFS:pool/dataset` + * It is also possible to boot from a snapshot (technically, a clone of + the snapshot) using `root=ZFS:pool/dataset@snapshot`. + * If the actual snapshot isn't specified, only the at (@) character, + the user will be asked for a snapshot to boot from. + * With the extra parameter `rollback=(on,yes,1)`, instead of doing a + clone and boot from that, the code will rollback the filesystem up + to the snapshot specified. + * Even though there is no freely availible native encryption for any + opensource ZFS implementation, the hope is that one day have one. + This code supports the native encryption that Solaris have, using + the `zfs key` option. + * Supports both 'native' and 'legacy' filesystems. + * Supports 'recursive filesystems'. That is, if the root file system + is something like `rpool/ROOT/fedora17`, it is possible to have + `rpool/ROOT/fedora17/var`, `rpool/ROOT/fedora17/usr` etc. + It is not important that the 'mountpoint' property is set or correct, + the 'base filesystem' will be removed from the full path of the filesystem. + This means, that with the root fs as `rpool/ROOT/fedora17` and the sub- + filesystem as `rpool/ROOT/fedora17/var`, the result would be `/var`, + where it will be mounted. + * In addition to 'recursive filesystems', it is possible to have other + filesystems mounted by setting the ZFS_INITRD_ADDITIONAL_DATASETS variable + in /etc/sysconfig/zfs. Here, it is imparative that the 'mountpoint' + property is correct for each of these filesystems, since each filesystem + will use this to correctly mount it. * `zfs_force=0`: If set to 1, the initramfs will run `zpool import -f` when attempting to import pools if the required pool isn't automatically imported diff --git a/contrib/initramfs/scripts/zfs b/contrib/initramfs/scripts/zfs index 1bb23277e67e..0755392eb945 100644 --- a/contrib/initramfs/scripts/zfs +++ b/contrib/initramfs/scripts/zfs @@ -335,13 +335,8 @@ mountroot() # NOTE: Mounted in the order specified in the # ZFS_INITRD_ADDITIONAL_DATASETS variable so take care! - # Go through the complete list (recursivly) 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 + recursive_mount_filesystems "Using ${ZFS_BOOTFS} as root." \ + "${ZFS_BOOTFS}" # ------------ # Debugging information diff --git a/contrib/shell-common/zfs-functions.in b/contrib/shell-common/zfs-functions.in index 529ae5bff5bc..23e32875f671 100644 --- a/contrib/shell-common/zfs-functions.in +++ b/contrib/shell-common/zfs-functions.in @@ -41,17 +41,6 @@ if type log_failure_msg > /dev/null 2>&1 ; then zfs_log_progress_msg() { log_progress_msg "$1"; } elif type success > /dev/null 2>&1 ; then # Fedora/RedHat functions - zfs_set_ifs() { - # For some reason, the init function library have a problem - # with a changed IFS, so this function goes around that. - local tIFS="$1" - if [ -n "$tIFS" ] - then - TMP_IFS="$IFS" - IFS="$tIFS" - fi - } - zfs_log_begin_msg() { echo -n "$1 "; } zfs_log_end_msg() { zfs_set_ifs "$OLD_IFS" @@ -334,6 +323,35 @@ check_module_loaded() [ -r /sys/module/zfs/version ] && return 0 || return 1 } +# Recursivly mount all filesystems, including any additional filesystems, +# below the specified filesystem. +recursive_mount_filesystems() +{ + local msg="$1" + local fs="$2" + local filesystems f + + # Only fail here if the first fs fails to mount. + if zfs_action "$msg" mount_fs "${fs}"; then + # Go through the complete list (recursivly) of all filesystems + # below the first (root) dataset. + filesystems=$("${ZFS}" list -oname -tfilesystem -H -r "${fs}") + for f in $filesystems $ZFS_INITRD_ADDITIONAL_DATASETS + do + # Ignore the first (root) fs. Should already be + # mounted, but is still included in the 'zfs list' + # above. + [ "${f}" = "${fs}" ] && continue + + zfs_action "Mounting filesystem $f" mount_fs "${f}" + done + else + return 1 + fi + + return 0 +} + load_module() { # Load the zfs module stack @@ -483,7 +501,7 @@ find_pools() local CMD="$*" local pools - pools=$($CMD 2> /dev/null | \ + pools=$(${CMD} 2> /dev/null | \ grep -E "pool:|^[a-zA-Z0-9]" | \ sed 's@.*: @@' | \ sort | \ @@ -609,7 +627,9 @@ import_pool() "Importing pool '${pool}' using defaults" ZFS_CMD="${ZPOOL} import -N ${ZPOOL_FORCE} ${ZPOOL_IMPORT_OPTS}" + zfs_set_ifs "$OLD_IFS" ZFS_STDERR="$($ZFS_CMD "$pool" 2>&1)" + zfs_set_ifs "$TMP_IFS" ZFS_ERROR="$?" if [ "${ZFS_ERROR}" != 0 ] then @@ -619,21 +639,28 @@ import_pool() "Importing pool '${pool}' using cachefile." ZFS_CMD="${ZPOOL} import -c ${ZPOOL_CACHE} -N ${ZPOOL_FORCE} ${ZPOOL_IMPORT_OPTS}" + zfs_set_ifs "$OLD_IFS" ZFS_STDERR="$($ZFS_CMD "$pool" 2>&1)" + zfs_set_ifs "$TMP_IFS" ZFS_ERROR="$?" 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." - /bin/sh + if [ "$?" != 999 ]; then + 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." + emergency_shell + else + [ -n "$ORIG_IFS" ] && IFS=$ORIG_IFS + return 1 + fi fi fi @@ -689,21 +716,27 @@ mount_fs() [ -n "${ZFS_DEBUG}" ] && \ zfs_log_begin_msg "CMD: '$ZFS_CMD ${fs} ${rootmnt}/${mountpoint}'" + zfs_set_ifs "$OLD_IFS" ZFS_STDERR=$(${ZFS_CMD} "${fs}" "${rootmnt}/${mountpoint}" 2>&1) + zfs_set_ifs "$TMP_IFS" 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." - /bin/sh + if [ "$?" != 999 ]; then + 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." + emergency_shell + else + return 1 + fi else [ "$quiet" != "y" ] && zfs_log_end_msg fi @@ -741,24 +774,28 @@ decrypt_fs() [ "$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 load $mod module." - echo "Please verify that it is availible on the initrd image" - echo "(without it it won't be possible to unlock the filesystem)" - echo "and rerun: $ZFS_CMD" - /bin/sh - else - [ "$quiet" != "y" ] && zfs_log_end_msg + if [ "$?" != 999 ]; then + echo "" + echo "Command: $ZFS_CMD" + echo "Message: $ZFS_STDERR" + echo "Error: $ZFS_ERROR" + echo "" + echo "Failed to load $mod module." + echo "Please verify that it is availible on the initrd image" + echo "(without it it won't be possible to unlock the filesystem)" + echo "and rerun: $ZFS_CMD" + emergency_shell + else + return 1 + fi fi done # If the key isn't availible, then this will fail! ZFS_CMD="${ZFS} key -l -r $fs" + zfs_set_ifs "$OLD_IFS" ZFS_STDERR="$(${ZFS_CMD} 2>&1)" + zfs_set_ifs "$TMP_IFS" ZFS_ERROR="$?" if [ "${ZFS_ERROR}" != 0 ] @@ -766,15 +803,19 @@ decrypt_fs() [ "$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 load zfs encryption wrapper key (s)." - echo "Please verify dataset property 'keysource' for datasets" - echo "and rerun: $ZFS_CMD" - /bin/sh + if [ "$?" != 999 ]; then + echo "" + echo "Command: $ZFS_CMD" + echo "Message: $ZFS_STDERR" + echo "Error: $ZFS_ERROR" + echo "" + echo "Failed to load zfs encryption wrapper key (s)." + echo "Please verify dataset property 'keysource' for datasets" + echo "and rerun: $ZFS_CMD" + emergency_shell + else + return 1 + fi else [ "$quiet" != "y" ] && zfs_log_end_msg fi @@ -792,22 +833,28 @@ destroy_fs() zfs_log_begin_msg "Destroying '$fs'" ZFS_CMD="${ZFS} destroy $fs" + zfs_set_ifs "$OLD_IFS" ZFS_STDERR="$(${ZFS_CMD} 2>&1)" + zfs_set_ifs "$TMP_IFS" 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 availible." - echo "Hint: Try: zfs destroy -Rfn $fs" - echo "If this dryrun looks good, then remove the 'n' from '-Rfn' and try again." - /bin/sh + if [ "$?" != 999 ]; then + 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 availible." + echo "Hint: Try: zfs destroy -Rfn $fs" + echo "If this dryrun looks good, then remove the 'n' from '-Rfn' and try again." + emergency_shell + else + return 1 + fi else [ "$quiet" != "y" ] && zfs_log_end_msg fi @@ -836,22 +883,28 @@ clone_snap() 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_set_ifs "$OLD_IFS" ZFS_STDERR="$(${ZFS_CMD} 2>&1)" + zfs_set_ifs "$TMP_IFS" 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." - /bin/sh + if [ "$?" != 999 ]; then + 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 '$fs' exists and is bootable." + emergency_shell + else + return 1 + fi else [ "$quiet" != "y" ] && zfs_log_end_msg fi @@ -868,20 +921,26 @@ rollback_snap() [ "$quiet" != "y" ] && zfs_log_begin_msg "Rollback $snap" ZFS_CMD="${ZFS} rollback -Rf $snap" + zfs_set_ifs "$OLD_IFS" ZFS_STDERR="$(${ZFS_CMD} 2>&1)" + zfs_set_ifs "$TMP_IFS" 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." - /bin/sh + if [ "$?" != 999 ]; then + echo "" + echo "Command: $ZFS_CMD" + echo "Message: $ZFS_STDERR" + echo "Error: $ZFS_ERROR" + echo "" + echo "Failed to rollback snapshot." + emergency_shell + else + return 1 + fi else [ "$quiet" != "y" ] && zfs_log_end_msg fi @@ -1040,6 +1099,26 @@ disable_plymouth() then /bin/plymouth hide-splash >/dev/null 2>&1 else + # No plymouth. Return a high number that shouldn't + # mean anything to anyone but us. return 999 fi } + +# This is defined in dracut, but nowhere +# else. So create a wrapper. +if ! type emergency_shell > /dev/null 2>&1 ; then + emergency_shell() { /bin/sh -i -l; } +fi + +# For some reason, some functions in the library have a problem +# with a changed IFS, so this function goes around that. +zfs_set_ifs() { + local tIFS="$1" + if [ -n "$tIFS" ] + then + TMP_IFS="$IFS" + IFS="$tIFS" + fi +} + diff --git a/contrib/shell-common/zfs.in b/contrib/shell-common/zfs.in index 588bb3f90450..74a30d4d8850 100644 --- a/contrib/shell-common/zfs.in +++ b/contrib/shell-common/zfs.in @@ -31,6 +31,9 @@ DO_OVERLAY_MOUNTS='no' # Any additional option to the 'zfs import' commandline? # Include '-o' for each option wanted. +# You don't need to put '-f' in here, unless you want it ALL the time. +# Using the option 'zfsforce=1' on the grub/kernel command line will +# do the same, but on a case-to-case basis. ZPOOL_IMPORT_OPTS="" # Any additional option to the 'zfs mount' command line?