diff --git a/beadm b/beadm index f817e40..39a9e72 100755 --- a/beadm +++ b/beadm @@ -1,5 +1,5 @@ #!/bin/sh -e - +# Copyright (c) 2016 Kris Moore (kmoore) # Copyright (c) 2012-2015 Slawomir Wojciech Wojtczak (vermaden) # Copyright (c) 2012-2013 Bryan Drewery (bdrewery) # Copyright (c) 2012-2013 Mike Clarke (rawthey) @@ -44,17 +44,25 @@ fi __usage() { local NAME=${0##*/} echo "usage:" - echo " ${NAME} activate " - echo " ${NAME} create [-e nonActiveBe | -e beName@snapshot] " - echo " ${NAME} create " - echo " ${NAME} destroy [-F] " + echo " ${NAME} activate [-n] " + echo " ${NAME} create [-r] [-e nonActiveBe | -e beName@snapshot] / [nickname]" + echo " ${NAME} destroy [-F] [-n] " + echo " ${NAME} export " + echo " ${NAME} import " echo " ${NAME} list [-a] [-s] [-D] [-H]" - echo " ${NAME} rename " - echo " ${NAME} mount [mountpoint]" - echo " ${NAME} { umount | unmount } [-f] " + echo " ${NAME} rename [-n] " + echo " ${NAME} mount [-n] [mountpoint]" + echo " ${NAME} { umount | unmount } [-f] [-n] " exit 1 } +# Check if we need to run any boot config to activate new BE +__check_boot_updates() { + # check if we need to update grub + __update_grub + +} + # check if system has a grub.cfg file and update it __update_grub() { if [ -e /boot/grub/grub.cfg ] @@ -68,6 +76,38 @@ __update_grub() { fi } +# Init the nicknames +__init_nicks() { + zfs get -d 1 -H -o name,value beadm:nickname ${POOL}/${BEDS} | grep -v '@' | while read nLine + do + # Make sure we have a BE / dataset here + if [ -z "`echo ${nLine} | cut -d '/' -f 3`" ] + then + continue + fi + be="`echo ${nLine} | awk '{print $1}'`" + nick="`echo ${nLine} | awk '{print $2}'`" + benick="`echo ${be} | cut -d '/' -f 3`" + BENICKNAME="`echo ${be} | rev | cut -d "/" -f 1 | rev`" + if [ "$nick" != "-" ] + then + continue + fi + if __be_nickname_exist "${benick}" + then + benick="${benick}-1" + if __be_nickname_exist "${benick}" + then + continue + fi + fi + if ! zfs set beadm:nickname="${benick}" ${be} + then + continue + fi + done +} + # check if boot environment exists __be_exist() { # 1=DATASET if ! zfs list -H -o name ${1} 1> /dev/null 2> /dev/null @@ -77,6 +117,44 @@ __be_exist() { # 1=DATASET fi } +# check if boot environment nickname exists +__be_nickname_exist() { # 1=NICKNAME + if ! zfs get -d 2 -H -o value beadm:nickname ${POOL}/${BEDS} | grep -q "^${1}\$" + then + return 1 + fi + return 0 +} + +__be_get_nickname() { + if ! zfs get -d 1 -H -o value beadm:nickname ${1} | awk '{print $1}' | head -n 1 | sed 's|-||g' 2>/dev/null + then + return 1 + fi + BENICKNAME=`zfs get -d 1 -H -o value beadm:nickname ${1} | head -n 1 | sed 's|-||g'` + if [ -z "$BENICKNAME" ] ; then + return 1 + else + return 0 + fi +} + +# Convert a BE nickname to dataset +__convert_be_nickname() { # 1=NICKNAME + if ! zfs get -d 2 -H -o value,name beadm:nickname ${POOL}/${BEDS} | grep -w "^${1}" | awk '{print $1}' | head -n 1 | sed "s|${POOL}/${BEDS}/||g" > /dev/null 2> /dev/null + then + return 1 + fi + + NICKDATASET=`zfs get -d 2 -H -o value,name beadm:nickname ${POOL}/${BEDS} | grep -v '@' | tr '\t' ' ' | grep "^${1} " | sed "s|^${1} ||g" | awk 'NF<=1' | sed "s|${POOL}/${BEDS}/||g"` + if zfs get -d 2 -H -o value beadm:nickname ${POOL}/${BEDS} | grep -q -w "^$1\$" + then + return 0 + else + return 1 + fi +} + # check if argument is a snapshot __be_snapshot() { # 1=DATASET/SNAPSHOT echo "${1}" | grep -q "@" 2> /dev/null @@ -128,6 +206,18 @@ __be_new() { # 1=SOURCE 2=TARGET unset NAME_NEW unset NAME_SANITY local SOURCE=$( echo ${1} | cut -d '@' -f 1 ) + local NICKNAME="$BENICK" + + # If we are replacing a nickname + if [ ${REPLACEBENICK} -eq 1 ] + then + if ! zfs set beadm:nickname="${OLDBENICK}" ${SOURCE} + then + echo "ERROR: Unable to reset nickname!" + exit 1 + fi + fi + local ENTROPY=0 # secure current /boot/entropy file if [ -f /boot/entropy ] @@ -219,7 +309,7 @@ __be_new() { # 1=SOURCE 2=TARGET local OPTS="-o ${PROPERTY}=${VALUE} ${OPTS}" fi done << EOF -$( zfs get -o name,property,value -s local,received -H all ${FS} | awk '!/[\t ]canmount[\t ]/' ) +$( zfs get -o name,property,value -s local,received -H all ${FS} | awk '!/[\t ]canmount[\t ]/' | awk '!/[\t ]beadm:nickname[\t ]/') EOF DATASET=$( echo ${FS} | awk '{print $1}' | sed -E s/"^${POOL}\/${BEDS}\/${SOURCE##*/}"/"${POOL}\/${BEDS}\/${2##*/}"/g ) if [ "${OPTS}" = "-o = " ] @@ -232,12 +322,16 @@ EOF else eval "zfs clone -o canmount=off ${OPTS} ${FS}@${FMT} ${DATASET}" fi + # Set nickname + if [ -n "${NICKNAME}" ] + then + if ! zfs set beadm:nickname="${NICKNAME}" ${DATASET} + then + echo "WARNING: Unable to set nickname!" + fi + fi done - # check if we need to update grub - if [ "${GRUB}" = YES ] - then - __update_grub - fi + __check_boot_updates } ROOTFS=$( mount | awk '/ \/ / {print $1}' ) @@ -272,17 +366,25 @@ fi # update GRUB bootloader instead of FreeBSD's loader(8) : ${GRUB="NO"} +# Use NICKNAME as the source of truth when referencing BEs +: ${NICKNAME_ONLY="NO"} + # use other prefix then the 'pool/ROOT/bename' default : ${BEDS="$( echo ${ROOTFS} | awk -F '/' '{print $2}' )"} +# Init the nickname system for all BEs +__init_nicks + case ${1} in (list) # -------------------------------------------------------------------- OPTION_a=0 OPTION_D=0 + OPTION_H=0 + OPTION_r=0 OPTION_s=0 shift - while getopts "aDHs" OPT + while getopts "aDHrs" OPT do case ${OPT} in (a) OPTION_a=1 ;; @@ -397,18 +499,27 @@ case ${1} in BE_HEAD = "" else { BE_HEAD = "BE" - printf "%-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", BE_HEAD, "Active", "Mountpoint", "Space", "Created" + printf "%-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\t\t %s\n", BE_HEAD, "Active", "Mountpoint", "Space", "Created", "Nickname" } if(OPTION_s != 1) SNAPSHOT_FILTER = "(/[^@]*)?$" for(I = 1; I <= length(BENAMES); I++) { BENAME = BENAMES[I] + DATASETNAME=BENAME + CMD_ZFS_NICK = "zfs get -d 2 -H -o value beadm:nickname " POOL "/" BEDS "/" BENAME + while(CMD_ZFS_NICK | getline) { + if ($1 != "-") + BENAME=$1 + else + BENAME="" + } + if(OPTION_a == 1) { printf "\n" - print BENAME + print DATASETNAME for(J = 1; J <= length(FSNAMES); J++) { FSNAME = FSNAMES[J] - if(FSNAME ~ "^" BENAME_BEGINS_WITH "/" BENAME SNAPSHOT_FILTER) { + if(FSNAME ~ "^" BENAME_BEGINS_WITH "/" DATASETNAME SNAPSHOT_FILTER) { ACTIVE = "" if(FSNAME == ROOTFS) ACTIVE = ACTIVE "N" @@ -436,7 +547,7 @@ case ${1} in else { SPACE = 0 ACTIVE = "" - NAME = BENAME_BEGINS_WITH "/" BENAME + NAME = BENAME_BEGINS_WITH "/" DATASETNAME if(NAME == ROOTFS) ACTIVE = ACTIVE "N" if(NAME == BOOTFS) @@ -445,8 +556,8 @@ case ${1} in ACTIVE = "-" for(J = 1; J <= length(FSNAMES); J++) { FSNAME = FSNAMES[J] - if(FSNAME ~ "^" BENAME_BEGINS_WITH "/" BENAME "(/[^@]*)?$") { - if((BENAME_BEGINS_WITH "/" BENAME) == FSNAME) { + if(FSNAME ~ "^" BENAME_BEGINS_WITH "/" DATASETNAME "(/[^@]*)?$") { + if((BENAME_BEGINS_WITH "/" DATASETNAME) == FSNAME) { MOUNTPOINT = MOUNTS[FSNAME] if(! MOUNTPOINT) MOUNTPOINT = "-" @@ -464,68 +575,192 @@ case ${1} in } } if(OPTION_H == 1) - printf "%s\t%s\t%s\t%s\t%s\n", BENAME, ACTIVE, MOUNTPOINT, __show_units(SPACE), CREATION + printf "%s\t%s\t%s\t%s\t%s\t%s\n", DATASETNAME, ACTIVE, MOUNTPOINT, __show_units(SPACE), CREATION, BENAME else - printf "%-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", BENAME, ACTIVE, MOUNTPOINT, __show_units(SPACE), CREATION + printf "%-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\t %s\n", DATASETNAME, ACTIVE, MOUNTPOINT, __show_units(SPACE), CREATION, BENAME } } }' ;; (create) # ------------------------------------------------------------------ - case ${#} in - (4) - if ! [ ${2} = "-e" ] - then - __usage - fi - # check if argument for -e option is full path dataset - # argument for -e option must be 'beName' or 'beName@snapshot' - if echo ${3} | grep -q "/" 2> /dev/null - then - __usage - fi - __be_exist ${POOL}/${BEDS}/${3} - if zfs list -H -o name ${POOL}/${BEDS}/${4} 1> /dev/null 2> /dev/null + OPTION_r=0 + OPTION_e=0 + CLONEBE="" + NEWBE="" + BENICK="" + REPLACEBENICK=0 + while [ $# -gt 0 ]; do + case "$1" in + create) ;; + -e) shift + OPTION_e=1 + CLONEBE="$1" + if [ -z "$CLONEBE" ] + then + __usage + fi + # check if argument for -e option is full path dataset + # argument for -e option must be 'beName' or 'beName@snapshot' + if echo ${CLONEBE} | grep -q "/" 2> /dev/null + then + __usage + fi + __be_exist ${POOL}/${BEDS}/${CLONEBE} + ;; + -r) OPTION_r=1 + ;; + *) if [ -z "$NEWBE" ] + then + NEWBE="$1" + shift + continue + fi + if [ -z "$BENICK" ] + then + BENICK="${1}" + if __be_nickname_exist "${BENICK}" + then + if [ ${OPTION_r} -ne 1 ] + then + echo "ERROR: Boot environment nickname '${BENICK}' already exists" + exit 1 + fi + fi + shift + continue + fi + __usage + ;; + esac + shift + done + + if [ -z "$NEWBE" ] ; then + __usage + fi + + if echo "$NEWBE" | grep -q " " + then + echo "ERROR: White-space in dataset name not permitted" + exit 1 + fi + + # Do some sanity checking + if [ ${OPTION_r} -eq 1 ] + then + if [ -z "${NEWBE}" -o -z "${BENICK}" ] + then + echo "ERROR: -r option requires both a and " + exit 1 + fi + fi + if [ "${NEWBE}" = "${BENICK}" ] + then + echo "ERROR: The dataset / nickname should not be identical" + exit 1 + fi + + FROMBE="" + if [ "${OPTION_e}" -eq 1 ] + then + if zfs list -H -o name ${POOL}/${BEDS}/${NEWBE} 1> /dev/null 2> /dev/null + then + echo "ERROR: Boot environment '${NEWBE}' already exists" + exit 1 + fi + FROMBE="${POOL}/${BEDS}/${CLONEBE}" + else + # Creating from the ROOTFS + if __be_snapshot ${NEWBE} + then + if ! zfs snapshot -r ${POOL}/${BEDS}/${NEWBE} 1> /dev/null 2> /dev/null then - echo "ERROR: Boot environment '${4}' already exists" + echo "ERROR: Cannot create '${NEWBE}' recursive snapshot" exit 1 fi - __be_new ${POOL}/${BEDS}/${3} ${POOL}/${BEDS}/${4} - ;; - (2) - if __be_snapshot ${2} + fi + FROMBE="${ROOTFS}" + fi + + if [ "${OPTION_r}" -eq 1 ] + then + if __be_nickname_exist "${BENICK}" + then + echo "ERROR: Boot environment nickname '${BENICK}' already exists" + exit 1 + fi + if ! __be_get_nickname ${FROMBE} 1> /dev/null 2>/dev/null + then + # Try an auto-generated nick-name + BENICKNAME="nick-`echo ${FROMBE} | rev | cut -d "/" -f 1 | rev`" + if __be_nickname_exist "${BENICKNAME}" then - if ! zfs snapshot -r ${POOL}/${BEDS}/${2} 1> /dev/null 2> /dev/null - then - echo "ERROR: Cannot create '${2}' recursive snapshot" - exit 1 - fi - else - __be_new ${ROOTFS} ${POOL}/${BEDS}/${2} + echo "ERROR: Boot environment nickname '${BENICKNAME}' already exists" + exit 1 fi - ;; - (*) - __usage - ;; - esac + fi + if [ "${BENICKNAME}" = "${NEWBE}" ] + then + echo "ERROR: The new nickname cannot be the same as the old BE nickname." + exit 1 + fi + OLDBENICK="${BENICK}" + BENICK="${BENICKNAME}" + REPLACEBENICK=1 + fi + + __be_new ${FROMBE} ${POOL}/${BEDS}/${NEWBE} echo "Created successfully" ;; (activate) # ---------------------------------------------------------------- - if [ ${#} -ne 2 ] + GOTNICK="NO" + if [ ${#} -ne 2 -a ${#} -ne 3 ] then __usage fi - __be_exist ${POOL}/${BEDS}/${2} - if [ "${BOOTFS}" = "${POOL}/${BEDS}/${2}" ] + + if [ ${#} -eq 3 ] + then + if [ "${2}" != "-n" ] + then + __usage + fi + if ! __convert_be_nickname "${3}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + ACTBE="${NICKDATASET}" + GOTNICK="YES" + else + if [ "${2}" = "-n" ] + then + __usage + fi + ACTBE="${2}" + fi + + if [ "${NICKNAME_ONLY}" = YES -a "${GOTNICK}" = "NO" ] + then + if ! __convert_be_nickname "${ACTBE}" + then + echo "ERROR: NICKNAME_ONLY set, but no BE nickname match found" + exit 1 + fi + ACTBE="${NICKDATASET}" + fi + + __be_exist ${POOL}/${BEDS}/${ACTBE} + if [ "${BOOTFS}" = "${POOL}/${BEDS}/${ACTBE}" ] then echo "Already activated" exit 0 else - if __be_mounted ${POOL}/${BEDS}/${2} + if __be_mounted ${POOL}/${BEDS}/${ACTBE} then - MNT=$( mount | grep -E "^${POOL}/${BEDS}/${2} " | awk '{print $3}' ) + MNT=$( mount | grep -E "^${POOL}/${BEDS}/${ACTBE} " | awk '{print $3}' ) if [ "${MNT}" != "/" ] then # boot environment is not current root and its mounted @@ -540,9 +775,9 @@ case ${1} in fi fi # do not change root (/) mounted boot environment mountpoint - if [ "${ROOTFS}" != "${POOL}/${BEDS}/${2}" ] + if [ "${ROOTFS}" != "${POOL}/${BEDS}/${ACTBE}" ] then - TMPMNT=$( mktemp -d -t BE-${2} ) + TMPMNT=$( mktemp -d -t BE-${ACTBE} ) if ! mkdir -p ${TMPMNT} 2> /dev/null then echo "ERROR: Cannot create '${TMPMNT}' directory" @@ -551,7 +786,7 @@ case ${1} in MOUNT=0 while read FS MNT TYPE OPTS DUMP FSCK; do - if [ "${FS}" = "${POOL}/${BEDS}/${2}" ] + if [ "${FS}" = "${POOL}/${BEDS}/${ACTBE}" ] then MOUNT=${MNT} break @@ -561,9 +796,9 @@ $( mount -p ) EOF if [ ${MOUNT} -eq 0 ] then - zfs set canmount=noauto ${POOL}/${BEDS}/${2} - zfs set mountpoint=${TMPMNT} ${POOL}/${BEDS}/${2} - zfs mount ${POOL}/${BEDS}/${2} + zfs set canmount=noauto ${POOL}/${BEDS}/${ACTBE} + zfs set mountpoint=${TMPMNT} ${POOL}/${BEDS}/${ACTBE} + zfs mount ${POOL}/${BEDS}/${ACTBE} else TMPMNT=${MOUNT} fi @@ -578,14 +813,14 @@ EOF then LOADER_CONFIGS="${LOADER_CONFIGS} ${TMPMNT}/boot/loader.conf.local" fi - sed -i '' -E s/"^vfs.root.mountfrom=.*$"/"vfs.root.mountfrom=\"zfs:${POOL}\/${BEDS}\/${2##*/}\""/g ${LOADER_CONFIGS} + sed -i '' -E s/"^vfs.root.mountfrom=.*$"/"vfs.root.mountfrom=\"zfs:${POOL}\/${BEDS}\/${ACTBE##*/}\""/g ${LOADER_CONFIGS} if [ ${MOUNT} -eq 0 ] then - zfs umount ${POOL}/${BEDS}/${2} - zfs set mountpoint=/ ${POOL}/${BEDS}/${2} + zfs umount ${POOL}/${BEDS}/${ACTBE} + zfs set mountpoint=/ ${POOL}/${BEDS}/${ACTBE} fi fi - if ! zpool set bootfs=${POOL}/${BEDS}/${2} ${POOL} 1> /dev/null 2> /dev/null + if ! zpool set bootfs=${POOL}/${BEDS}/${ACTBE} ${POOL} 1> /dev/null 2> /dev/null then echo "ERROR: Failed to activate '${2}' boot environment" exit 1 @@ -596,56 +831,109 @@ EOF # disable automatic mount on all inactive boot environments echo "${ZFS_LIST}" \ | grep -v "^${POOL}/${BEDS}$" \ - | grep -v "^${POOL}/${BEDS}/${2}$" \ - | grep -v "^${POOL}/${BEDS}/${2}/" \ + | grep -v "^${POOL}/${BEDS}/${ACTBE}$" \ + | grep -v "^${POOL}/${BEDS}/${ACTBE}/" \ | while read NAME do zfs set canmount=noauto ${NAME} done # enable automatic mount for active boot environment and promote it echo "${ZFS_LIST}" \ - | grep -E "^${POOL}/${BEDS}/${2}(/|$)" \ + | grep -E "^${POOL}/${BEDS}/${ACTBE}(/|$)" \ | while read NAME do - zfs set canmount=on ${NAME} + # If we are using GRUB, don't set canmount=on, since it clobbers + # what we set in grub.cfg for vfs.root.mountfrom + if [ -e /boot/grub/grub.cfg ] + then + zfs set canmount=noauto ${NAME} + else + zfs set canmount=on ${NAME} + fi while __be_clone ${NAME} do zfs promote ${NAME} done done - # check if we need to update grub - if [ "${GRUB}" = YES ] - then - __update_grub - fi + __check_boot_updates echo "Activated successfully" ;; (destroy) # ----------------------------------------------------------------- - if [ "${2}" != "-F" ] + if [ ${#} -ne 2 -a ${#} -ne 3 -a ${#} -ne 4 ] then - DESTROY=${2} - else - DESTROY=${3} + __usage fi - __be_exist ${POOL}/${BEDS}/${DESTROY} + FORCE="NO" + GOTNICK="NO" + case ${#} in - (2) - echo "Are you sure you want to destroy '${2}'?" + (2) if [ "${2}" = "-n" -o "${2}" = "-F" ] + then + __usage + fi + DESTROY="${2}" + ;; + (3) if [ "${2}" = "-n" ] + then + if ! __convert_be_nickname "${3}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + DESTROY="${NICKDATASET}" + GOTNICK="YES" + elif [ "${2}" = "-F" ] ; then + FORCE="YES" + DESTROY="${3}" + else + __usage + fi + ;; + (4) if [ "${2}" = "-n" -a "${3}" = "-F" ] + then + if ! __convert_be_nickname "${4}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + DESTROY="${NICKDATASET}" + FORCE="YES" + GOTNICK="YES" + elif [ "${2}" = "-F" -a "${3}" = "-n" ] ; then + if ! __convert_be_nickname "${4}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + DESTROY="${NICKDATASET}" + FORCE="YES" + GOTNICK="YES" + else + __usage + fi + ;; + esac + + if [ "${NICKNAME_ONLY}" = YES -a "${GOTNICK}" = "NO" ] + then + if ! __convert_be_nickname "${DESTROY}" + then + echo "ERROR: NICKNAME_ONLY set, but no BE nickname match found" + exit 1 + fi + DESTROY="${NICKDATASET}" + fi + + __be_exist ${POOL}/${BEDS}/${DESTROY} + if [ "${FORCE}" != "YES" ] ; then + echo "Are you sure you want to destroy '${DESTROY}'?" echo -n "This action cannot be undone (y/[n]): " read CHOICE - ;; - (3) - if [ "${2}" != "-F" ] - then - __usage - fi - CHOICE=Y - ;; - (*) - __usage - ;; - esac + else + CHOICE="y" + fi + if [ "${BOOTFS}" = "${POOL}/${BEDS}/${DESTROY}" ] then echo "ERROR: Cannot destroy active boot environment" @@ -738,11 +1026,7 @@ EOF done done fi - # check if we need to update grub - if [ "${GRUB}" = YES ] - then - __update_grub - fi + __check_boot_updates echo "Destroyed successfully" ;; (*) @@ -751,45 +1035,202 @@ EOF esac ;; - (rename) # ------------------------------------------------------------------ + (export) # ------------------------------------------------------------------ + if [ ${#} -ne 3 ] + then + __usage + fi + + beName="$2" + fileOut="$3" + + # Check if this is a nickname + if __convert_be_nickname "${4}" + then + beName="${NICKDATASET}" + fi + + # Save the dataset as a ZFS send dump + echo "Exporting BE: ${beName} -> ${fileOut}" + if ! zfs send ${POOL}/${BEDS}/${beName} > ${fileOut} + then + echo "ERROR: Failed exporting BE to file: ${fileOut}" + exit 1 + fi + + echo "Exported successfully to: ${fileOut}" + ;; + + (import) # ------------------------------------------------------------------ if [ ${#} -ne 3 ] then __usage fi - __be_exist ${POOL}/${BEDS}/${2} - if [ "${BOOTFS}" = "${POOL}/${BEDS}/${2}" ] + + fileIn="$2" + beName="$3" + + if zfs list ${POOL}/${BEDS}/${beName} >/dev/null 2>/dev/null then - echo "ERROR: Renaming active boot environment is not supported" + echo "ERROR: BE ${beName} already exists!" exit 1 fi - if zfs list -H -o name ${POOL}/${BEDS}/${3} 2> /dev/null + + if [ ! -e "${fileIn}" ] then - echo "ERROR: Boot environment '${3}' already exists" + echo "ERROR: File ${fileIn} does not exist!" exit 1 fi - zfs rename ${POOL}/${BEDS}/${2} ${POOL}/${BEDS}/${3} - # check if we need to update grub - if [ "${GRUB}" = YES ] + + # Import the dataset from a ZFS send dump + echo "Importing BE: ${fileIn} -> ${beName}" + if ! cat $fileIn | zfs recv -u ${POOL}/${BEDS}/${beName} + then + echo "ERROR: Failed importing BE from file: ${fileOut}" + exit 1 + fi + + echo "Imported successfully: ${beName}" + ;; + + (rename) # ------------------------------------------------------------------ + if [ ${#} -ne 3 -a ${#} -ne 4 ] + then + __usage + fi + + RENAMENICK="NO" + GOTNICK="NO" + if [ ${#} -eq 4 ] + then + if [ "$2" != "-n" ] + then + __usage + fi + RENAMENICK="YES" + if ! __convert_be_nickname "$3" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + oName="$3" + nName="$4" + GOTNICK="YES" + else + if [ "$2" = "-n" ] + then + __usage + fi + oName="$2" + nName="$3" + fi + + if [ "${NICKNAME_ONLY}" = YES -a "${GOTNICK}" = "NO" ] then - __update_grub + if ! __convert_be_nickname "${oName}" + then + echo "ERROR: NICKNAME_ONLY set, but no BE nickname match found" + exit 1 + fi + RENAMENICK="YES" fi + + if [ "${RENAMENICK}" = "NO" ] + then + # Renaming a dataset + + if echo "${nName}" | grep -q " " + then + echo "ERROR: White-space in dataset name not permitted" + exit 1 + fi + __be_exist ${POOL}/${BEDS}/${oName} + if [ "${BOOTFS}" = "${POOL}/${BEDS}/${oName}" ] + then + echo "ERROR: Renaming active boot environment is not supported" + exit 1 + fi + if zfs list -H -o name ${POOL}/${BEDS}/${nName} 2> /dev/null + then + echo "ERROR: Boot environment '${nName}' already exists" + exit 1 + fi + zfs rename ${POOL}/${BEDS}/${oName} ${POOL}/${BEDS}/${nName} + else + # Renaming a nickname + __be_exist ${POOL}/${BEDS}/${NICKDATASET} + if __be_nickname_exist "${nName}" + then + echo "ERROR: Boot environment nickname '${nName}' already exists" + exit 1 + fi + if ! zfs set beadm:nickname="${nName}" ${POOL}/${BEDS}/${NICKDATASET} + then + echo "ERROR: Unable to set nickname!" + exit 1 + fi + fi + + __check_boot_updates + echo "Renamed successfully" ;; (mount) # ------------------------------------------------------------ - if [ ${#} -eq 2 ] + GOTNICK="NO" + if [ ${#} -eq 3 ] then - TARGET=$( mktemp -d -t BE-${2} ) - elif [ ${#} -eq 3 ] + if [ "$2" = "-n" ] + then + if ! __convert_be_nickname "${3}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + MOUNTBE=${NICKDATASET} + GOTNICK="YES" + TARGET=$( mktemp -d -t BE-${MOUNTBE} ) + else + MOUNTBE="${2}" + TARGET="${3}" + fi + elif [ ${#} -eq 4 ] then - TARGET=${3} + if [ "${2}" != "-n" ] + then + __usage + fi + if ! __convert_be_nickname "${3}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + MOUNTBE=${NICKDATASET} + GOTNICK="YES" + TARGET="${4}" else - __usage + if [ "${2}" = "-n" ] + then + __usage + fi + MOUNTBE="${2}" + TARGET=$( mktemp -d -t BE-${MOUNTBE} ) fi - __be_exist "${POOL}/${BEDS}/${2}" - if __be_mounted "${POOL}/${BEDS}/${2}" + + if [ "${NICKNAME_ONLY}" = YES -a "${GOTNICK}" = "NO" ] then - MNT=$( mount | grep -E "^${POOL}/${BEDS}/${2} " | awk '{print $3}' ) + if ! __convert_be_nickname "${MOUNTBE}" + then + echo "ERROR: NICKNAME_ONLY set, but no BE nickname match found" + exit 1 + fi + MOUNTBE="${NICKDATASET}" + fi + + __be_exist "${POOL}/${BEDS}/${MOUNTBE}" + if __be_mounted "${POOL}/${BEDS}/${MOUNTBE}" + then + MNT=$( mount | grep -E "^${POOL}/${BEDS}/${MOUNTBE} " | awk '{print $3}' ) echo "Boot environment '${2}' is already mounted at '${MNT}'" exit 1 fi @@ -798,18 +1239,18 @@ EOF echo "ERROR: Cannot create '${TARGET}' mountpoint" exit 1 fi - if ! mount -t zfs ${POOL}/${BEDS}/${2} ${TARGET} + if ! mount -t zfs ${POOL}/${BEDS}/${MOUNTBE} ${TARGET} then echo "ERROR: Cannot mount '${2}' at '${TARGET}' mountpoint" exit 1 fi - zfs list -H -o name,mountpoint -r ${POOL}/${BEDS}/${2} \ + zfs list -H -o name,mountpoint -r ${POOL}/${BEDS}/${MOUNTBE} \ | grep -v -E "[[:space:]](legacy|none)$" \ | sort -n \ - | grep -E "^${POOL}/${BEDS}/${2}/" \ + | grep -E "^${POOL}/${BEDS}/${MOUNTBE}/" \ | while read FS MOUNTPOINT do - if [ "{FS}" != "${POOL}/${BEDS}/${2}" ] + if [ "{FS}" != "${POOL}/${BEDS}/${MOUNTBE}" ] then INHERIT=$( zfs get -H -o source mountpoint ${FS} ) if [ "${INHERIT}" = "local" ] @@ -819,7 +1260,7 @@ EOF continue ;; (*) - MOUNTPOINT="/$( echo "${FS}" | sed s^"${POOL}/${BEDS}/${2}/"^^g )" + MOUNTPOINT="/$( echo "${FS}" | sed s^"${POOL}/${BEDS}/${MOUNTBE}/"^^g )" ;; esac fi @@ -839,28 +1280,90 @@ EOF ;; (umount|unmount) # ---------------------------------------------------------- + GOTNICK="NO" if [ ${#} -eq 2 ] then # we need this empty section for argument checking : elif [ ${#} -eq 3 -a "${2}" = "-f" ] then - OPTS="-f" shift else __usage fi - __be_exist "${POOL}/${BEDS}/${2}" - if ! __be_mounted "${POOL}/${BEDS}/${2}" + + if [ ${#} -eq 3 ] + then + if [ "${2}" = "-n" ] + then + if ! __convert_be_nickname "${3}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + UMOUNTBE=${NICKDATASET} + GOTNICK="YES" + elif [ "${2}" = "-f" ] + then + OPTS="-f" + UMOUNTBE="${3}" + else + __usage + fi + elif [ ${#} -eq 4 ] + then + if [ "${2}" = "-n" -a "${3}" = "-f" ] + then + if ! __convert_be_nickname "${4}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + UMOUNTBE=${NICKDATASET} + GOTNICK="YES" + OPTS="-f" + elif [ "${2}" = "-f" -a "${3}" = "-n" ] + then + if ! __convert_be_nickname "${4}" + then + echo "ERROR: -n specified, but no BE nickname match found" + exit 1 + fi + UMOUNTBE=${NICKDATASET} + GOTNICK="YES" + OPTS="-f" + else + __usage + fi + else + if [ "${2}" = "-n" ] + then + __usage + fi + UMOUNTBE="${2}" + fi + + if [ "${NICKNAME_ONLY}" = YES -a "${GOTNICK}" = "NO" ] + then + if ! __convert_be_nickname "${UMOUNTBE}" + then + echo "ERROR: NICKNAME_ONLY set, but no BE nickname match found" + exit 1 + fi + UMOUNTBE="${NICKDATASET}" + fi + + __be_exist "${POOL}/${BEDS}/${UMOUNTBE}" + if ! __be_mounted "${POOL}/${BEDS}/${UMOUNTBE}" then echo "Boot environment '${2}' is not mounted" exit 1 fi MOUNT=$( mount ) - MOUNTPOINT=$( echo "${MOUNT}" | grep -m 1 "^${POOL}/${BEDS}/${2} on " | awk '{print $3}' ) + MOUNTPOINT=$( echo "${MOUNT}" | grep -m 1 "^${POOL}/${BEDS}/${UMOUNTBE} on " | awk '{print $3}' ) echo "${MOUNT}" \ | awk '{print $1}' \ - | grep -E "^${POOL}/${BEDS}/${2}(/|$)" \ + | grep -E "^${POOL}/${BEDS}/${UMOUNTBE}(/|$)" \ | sort -n -r \ | while read FS do @@ -872,7 +1375,7 @@ EOF done echo "Unmounted successfully" # only delete the temporary mountpoint directory - if echo "${MOUNTPOINT}" | grep -q -E "/BE-${2}\.[a-zA-Z0-9]{8}" 1> /dev/null 2> /dev/null + if echo "${MOUNTPOINT}" | grep -q -E "/BE-${UMOUNTBE}\.[a-zA-Z0-9]{8}" 1> /dev/null 2> /dev/null then # delete only when it is an empty directory if [ $( find ${MOUNTPOINT} | head | wc -l | bc ) -eq 1 ] diff --git a/beadm.1 b/beadm.1 index a5ac900..b5d2647 100644 --- a/beadm.1 +++ b/beadm.1 @@ -24,19 +24,29 @@ .Sh SYNOPSIS .Nm activate +.Op Fl n .Ao Ar beName Ac .Nm create -.Op Fl e Ar nonActiveBe | Fl e Ar beName@snapshot +.Op Fl r Fl e Ar nonActiveBe | Fl e Ar beName@snapshot .Ao Ar beName Ac .Nm create -.Ao Ar beName@snapshot Ac +.Ao Fl r Ar beName@snapshot Ac .Nm destroy .Op Fl F +.Op Fl n .Ao Ar beName | beName@snapshot Ac .Nm +export +.Ao Ar beName Ac +.Ao Ar newFile Ac +.Nm +import +.Ao Ar file Ac +.Ao Ar newBeName Ac +.Nm list .Op Fl a .Op Fl D @@ -45,14 +55,17 @@ list .Nm mount .Ao Ar beName Ac +.Op Fl n .Op mountpoint .Nm rename +.Op Fl n .Ao Ar origBeName Ac .Ao Ar newBeName Ac .Nm { umount | unmount } .Op Fl f +.Op Fl n .Ao Ar beName Ac .Sh DESCRIPTION The @@ -80,15 +93,21 @@ Creates a new boot environment named .Ar beName . If the -e param is specified, the new environment will be cloned from the given .Ar nonActiveBe | Ar beName@snapshot . +If the -r param is specified, the current BE nickname will be cloned +and the old BE nickname will be replaced with the specified nickname. .Pp .It Ic create .Ao Ar beName@snapshot Ac .Pp Creates a snapshot of the existing boot environment named .Ar beName . +If the -r param is specified, the current BE nickname will be cloned +and the old BE nickname will be replaced with the specified nickname. .Pp .It -.Ic destroy Op Fl F +.Ic destroy +.Op Fl F +.Op Fl n .Ao Ar beName | beName@snapshot Ac .Pp Destroys the given @@ -99,6 +118,30 @@ snapshot. Specifying .Fl F will automatically unmount without confirmation. +If +.Fl n +used, resolve the BE nickname +.Pp +.It Ic export +.Ao Ar beName Ac Ao Ar fileName Ac +.Pp +Exports the +.Ar beName +to the given +.Ar fileName +.Pp +.It Ic import +.Ao Ar fileName Ac Ao Ar beName Ac +.Pp +Imports the +.Ar fileName +to the given +.Ar beName +.Pp +.It Ic umount +.Op Fl f +.Op Fl n +.Ao Ar beName Ac .Pp .It Ic list .Op Fl a @@ -119,10 +162,12 @@ The .Fl H option is used for scripting. It does not print headers and separate fields by a single tab instead of arbitrary white space. If +If .Fl s is used, display all snapshots as well. .Pp .It Ic mount +.Op Fl n .Ao Ar beName Ac .Op mountpoint .Pp @@ -130,22 +175,34 @@ Temporarily mount the boot environment. Mount at the specified .Ar mountpoint if provided. +If +.Fl n +used, resolve the BE nickname .Pp -.It Ic rename Ao Ar origBeName Ac Ao Ar newBeName Ac +.It Ic rename +.Op Fl n +.Ao Ar origBeName Ac Ao Ar newBeName Ac .Pp Renames the given nonactive .Ar origBeName to the given .Ar newBeName +If +.Fl n +used, rename the nickname property only .Pp .It Ic umount .Op Fl f +.Op Fl n .Ao Ar beName Ac .Pp Unmount the given boot environment, if it is mounted. Specifying .Fl f will force the unmount if busy. +If +.Fl n +used, resolve the BE nickname .Pp .El .Sh EXAMPLES @@ -212,3 +269,8 @@ Wrote fast implementation of .Nm Ar list . .Pp Contributed a lot of fixes and usability changes. +.It +Kris Moore (kmoore134) +.Ar kris@pcbsd.org +.Pp +Added support for nicknames, and exporting / importing BEs diff --git a/beadm.conf b/beadm.conf index 56cc30e..7016f7e 100644 --- a/beadm.conf +++ b/beadm.conf @@ -1,2 +1,5 @@ +# Enable to support re-stamping GRUB boot-loader GRUB=YES +# Enable to have all commands act as if -n was set +NICKNAME_ONLY=NO