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:
How it works
How I said, it's pretty trivial:
Conclusions
That's all. I hope this could be useful for those that still have/need this setup.
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:
- Renames the actual boot directory to boot.<actual_be_name>.
- Renames the boot directory of the BE to activate to boot.
- 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
Last edited: