Shell bewrap: using boot environments with UFS boot (ZFS root)

Introduction
Hello to all! :)
I wrote this post to share with you a little wrapper for sysutils/beadm that I developed to add (a "little" trivial) support for boot environments on systems with Root-on-ZFS and a UFS boot partition. I hope this could be useful for those that have this setup. :)

Conventions
The conventions to respect are:
  • Having /boot as a symlink to the absolute path /<your_bootfs_mountpoint>/boot.
  • Setting a ${BOOT_FS} environment variable with the full path to the partition, or label, for the boot filesystem (or you can modify the script, of course. ;)); /dev/label/bootfs is the default.
  • Creating a file inside the boot directory named BE, and containing the name of the actual boot environment (on standard installations is "default").

How it works
How I said, it's pretty trivial:
Creating
When creating a new BE, it copies the boot directory to boot.<new_be_name>, and creates in it a BE file with the name of the new boot environment. If instead we are cloning from a snapshot of a BE, it creates a PARENT_SNAP file too, containing the name of the snapshot from which the new BE was made, in order to be able to remove it automatically when instructing beadm(1) to remove the parent snapshot of a BE.​
Activating
To activate a BE, the script does three things:​
  1. Renames the actual boot directory to boot.<actual_be_name>.
  2. Renames the boot directory of the BE to activate to boot.
  3. Temporarily renames zpool.cache to not interfere with beadm(1), then restores it.
Destroying
It removes the boot.<be_name> directory, and the parent snapshot if it's the case (how described in Creating).​
Renaming
Renames the boot.<be_name> directory, and updates its BE file.​
Rollback (bewrap only)
Restores a BE dataset from a snapshot (IOW: zfs rollback <snapshot>), and does the same with the related boot directory.​
For everythings else, it just let beadm(1) do its job.

Conclusions
That's all. I hope this could be useful for those that still have/need this setup. :)
Bash:
#!/bin/sh -e

#--> Variables
BOOT_FS=${BOOT_FS:-/dev/label/bootfs} # The boot UFS filesystem.
BOOT_MP=`fgrep ${BOOT_FS} /etc/fstab | cut -f2 -w` # The boot FS mountpoint

#--> Functions
# Check if the boot partition is mounted or if it's read-only.
__chk_mounted() {
MOUNT_OUTPUT="`mount -t ufs | fgrep ${BOOT_FS}`"

if [ -z "${MOUNT_OUTPUT}" ]; then
        echo "${0##*/}: ERROR: Boot filesystem is not mounted." >&2
        exit 1
else

        if echo ${MOUNT_OUTPUT} | fgrep -q read-only; then
                echo "${0##*/}: ERROR: Boot filesystem is read-only." >&2
                exit 1
        fi

fi

}

# Create a boot dir for the new BE.
__create_bootdir() {
mkdir ${BOOT_MP}/boot.${NEW_BE}
cd ${BOOT_MP}/${BE_BOOTDIR}
tar -cf - * | tar -C ${BOOT_MP}/boot.${NEW_BE} -xf -
echo ${NEW_BE} > ${BOOT_MP}/boot.${NEW_BE}/BE
}

#--> Main
case ${1} in
        activate)
                __chk_mounted

                ACTIVE_BE=`cat ${BOOT_MP}/boot/BE`
                NEW_BE=${2}

                # Rename current boot directory to boot.${ACTIVE_BE},
                # use the new selected BE boot directory,
                # and rename its zpool.cache to not interfere with beadm(1).
                mv ${BOOT_MP}/boot ${BOOT_MP}/boot.${ACTIVE_BE}
                mv ${BOOT_MP}/boot.${NEW_BE} ${BOOT_MP}/boot
                mv ${BOOT_MP}/boot/zfs/zpool.cache ${BOOT_MP}/boot/zfs/zpool.cache.tmp

                # If beadm(1) exists successfully, proceed.
                # If not, restore zpool.cache and the previous directory structure.
                if beadm $@ ; then
                        mv ${BOOT_MP}/boot/zfs/zpool.cache.tmp ${BOOT_MP}/boot/zfs/zpool.cache

                        # Copy zpool.cache if it exists (we handle this instead of beadm(1)).
                        if [ -f /boot/zfs/zpool.cache ]; then
                                cp ${BOOT_MP}/boot.${ACTIVE_BE}/zfs/zpool.cache ${BOOT_MP}/boot/zfs/zpool.cache
                        fi
   
                else
                        mv ${BOOT_MP}/boot/zfs/zpool.cache.tmp ${BOOT_MP}/boot/zfs/zpool.cache
                        mv ${BOOT_MP}/boot ${BOOT_MP}/boot.${NEW_BE}
                        mv ${BOOT_MP}/boot.${ACTIVE_BE} ${BOOT_MP}/boot
                        exit 1
                fi
                ;;
        create) # Create a new BE, copy actual boot directory to create the
                   # new one for the new BE, and store the new BE name into it.
                __chk_mounted

                # Check if there is enough room for the directory of the new BE.
                AVAIL_SPACE=`df ${BOOT_FS} | tail -n1 | cut -f4 -w`
                BOOTDIR_SIZE=`du -d0 ${BOOT_MP}/boot | cut -f1`

                if [ ${BOOTDIR_SIZE} -lt ${AVAIL_SPACE} ]; then

                        if beadm $@; then

                                case ${2} in
                                        -e) # Clone from existing BE or snapshot of a BE.
                                                ACTIVE_BE=`cat ${BOOT_MP}/boot/BE`
                                                BE_TO_CLONE=${3}
                                                NEW_BE=${4}

                                                # Check if we are cloning the active BE.
                                                if [ ${BE_TO_CLONE} == ${ACTIVE_BE} ]; then
                                                        BE_BOOTDIR="boot"
                                                else
                                                        BE_BOOTDIR="boot.${BE_TO_CLONE}"
                                                fi

                                                __create_bootdir

                                                # Keep track of BE snapshot - BE clone relationship.
                                                if echo ${BE_TO_CLONE} | fgrep -q "@"; then
                                                        echo ${BE_TO_CLONE} > ${BOOT_MP}/boot.${NEW_BE}/PARENT_SNAP
                                                fi
                                                ;;
                                        *@*) # Copy the boot dir of a BE to match BE's snapshot.
                                                ACTIVE_BE=`cat ${BOOT_MP}/boot/BE`
                                                BE_TO_SNAP=${2%%@*}
                                                NEW_BE=${2}

                                                # Check if we are taking a snapshot of the active BE.
                                                if [ ${BE_TO_SNAP} == ${ACTIVE_BE} ]; then
                                                        BE_BOOTDIR="boot"
                                                else
                                                        BE_BOOTDIR="boot.${BE_TO_SNAP}"
                                                fi

                                                __create_bootdir
                                                ;;
                                        *)      # Create a new BE from the active one.
                                                BE_BOOTDIR="boot"
                                                NEW_BE=${2}

                                                __create_bootdir
                                                ;;
                                esac

                        fi
           
                else
                        echo "${0##*/}: ERROR: Not enough room for a new boot environment." >&2
                        exit 1
                fi
                ;;
        destroy) # Destroy a BE and the relative boot directory.
                __chk_mounted

                if beadm $@; then

                        # If the "-F" argument was passed bo beadm(1), shift it (for bewrap
                        # it's irrilevant).
                        if [ "${2}" == "-F" ]; then
                                shift
                        fi

                        BE_TO_DESTROY=${2}

                        # If we chose to not destroy the BE, we must not remove
                        # its boot directory.
                        if ! beadm list | fgrep -q ${BE_TO_DESTROY}; then

                                # Check if the BE to destroy is a clone of another's BE snapshot.
                                if [ -f ${BOOT_MP}/boot.${BE_TO_DESTROY}/PARENT_SNAP ]; then
                                        BE_ROOT=`mount -t zfs | fgrep ' / ' | cut -f1,2 -d/`
                                        RELATED_SNAP=`cat ${BOOT_MP}/boot.${BE_TO_DESTROY}/PARENT_SNAP`

                                        # Check if the parent snapshot should be removed.
                                        if ! beadm list -s | fgrep -q ${BE_ROOT}/${RELATED_SNAP}; then
                                                rm -rf ${BOOT_MP}/boot.${RELATED_SNAP}
                                        fi

                                fi

                                # Remove BE's boot
                                rm -rf ${BOOT_MP}/boot.${BE_TO_DESTROY}

                        fi

                fi
                ;;
        rename) # Rename a BE, the relative boot directory, and update its name.
                __chk_mounted

                BE_NEW_NAME=${3}
                BE_OLD_NAME=${2}

                if beadm $@; then
                        mv ${BOOT_MP}/boot.${BE_OLD_NAME} ${BOOT_MP}/boot.${BE_NEW_NAME}
                        echo ${BE_NEW_NAME} > ${BOOT_MP}/boot.${BE_NEW_NAME}/BE
                fi
                ;;
        rollback) # bewrap only: rollback a BE (and its boot dir) from a snapshot.
                __chk_mounted

                ACTIVE_BE=`cat ${BOOT_MP}/boot/BE`
                BE_TO_RB=${1%%@*}
                SNAPSHOT=${1}

                # If the rollback of the BE dataset is successfull, proceed.
                if zfs rollback ${SNAPSHOT}; then

                        # Check if the BE to rollback is the active one.
                        if [ "${BE_TO_RB}" == "${ACTIVE_BE}" ]; then
                                BOOT_TO_RB="boot"
                        else
                                BOOT_TO_RB="boot.${BE_TO_RB}"
                        fi

                        # If the boot directory of the snapshot exists, copy its content to the to-roll-back one.
                        if [ -d ${BOOT_MP}/boot.${SNAPSHOT} ]; then
                                rm -rf ${BOOT_MP}/${BOOT_TO_RB}/*
                                cd ${BOOT_MP}/boot.${SNAPSHOT}
                                tar -cf - * | tar -C ${BOOT_MP}/${BOOT_TO_RB} -xf -
                        else
                                echo "${0##*/}: ERROR: No snapshot-related boot directory." >&2
                                exit 1
                        fi
                else
                        echo "${0##*/}: ERROR: Can't rollback boot environment." >&2
                        exit 1
                fi
                ;;
        *)      # Nothing particular; let beadm(1) do its job.
                beadm $@
                ;;
esac
 

Attachments

  • bewrap.txt
    5.5 KB · Views: 203
Last edited:
UPDATE: Fixed an error in the rollback procedure (to make the wildcard work for tar(1), we must change directory first), and uploaded the correct version.
 
Back
Top