automount/beadm

490 lines
14 KiB
Bash
Executable File

#!/bin/sh -e
# Copyright 2012 Slawomir Wojciech Wojtczak (vermaden). All rights reserved.
# Copyright 2012 Bryan Drewery (bdrewery). All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 'AS IS' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
unset LC_ALL
unset LANG
PATH=${PATH}:/bin:/usr/bin:/sbin:/usr/sbin
if [ $( uname -r | cut -d '.' -f1 ) -lt 8 ]
then
echo "ERROR: beadm works on FreeBSD 8.0 or later"
exit 1
fi
__usage() {
local NAME=${0##*/}
echo "usage:"
echo " ${NAME} subcommand cmd_options"
echo
echo " subcommands:"
echo
echo " ${NAME} activate beName"
echo " ${NAME} create [-e nonActiveBe | -e beName@snapshot] beName"
echo " ${NAME} create beName@snapshot"
echo " ${NAME} destroy [-F] beName | beName@snapshot"
echo " ${NAME} list"
echo " ${NAME} mount [-p]"
echo " ${NAME} mount beName [mountpoint]"
echo " ${NAME} umount [-f] beName"
echo " ${NAME} rename origBeName newBeName"
exit 1
}
# check if BE exists
__be_exist() { # 1=DATASET
if ! zfs list -H -o name ${1} 1> /dev/null 2> /dev/null
then
echo "ERROR: Boot environment '${1##*/}' does not exist"
exit 1
fi
}
# check if argument is a snapshot
__be_snapshot() { # 1=DATASET/SNAPSHOT
echo "${1}" | grep -q "@"
}
# create new BE
__be_new() { # 1=SOURCE 2=TARGET
local SOURCE=$( echo ${1} | cut -d '@' -f 1 )
if __be_snapshot ${1}
then
local SNAPSHOT=$( echo ${1} | cut -d '@' -f 2 )
zfs list -r -H -t filesystem -o name ${SOURCE} \
| while read FS
do
if ! zfs list -H -o name ${FS}@${SNAPSHOT} 1> /dev/null 2> /dev/null
then
echo "ERROR: Child snapshot '${FS}@${SNAPSHOT}' does not exists"
exit 1
fi
done
else
if zfs list -H -o name ${1}@${2##*/} 1> /dev/null 2> /dev/null
then
echo "ERROR: Snapshot '${1}@${2##*/}' already exists"
exit 1
fi
if ! zfs snapshot -r ${1}@${2##*/} 1> /dev/null 2> /dev/null
then
echo "ERROR: Cannot create snapshot '${1}@${2##*/}'"
exit 1
fi
fi
zfs list -H -o name -r ${SOURCE} \
| while read FS
do
local OPTS=""
while read NAME PROPERTY VALUE
do
local OPTS="-o ${PROPERTY}=${VALUE} ${OPTS}"
done << EOF
$( zfs get -o name,property,value -s local -H all ${FS} | grep -v -E "(canmount)" )
EOF
DATASET=$( echo ${FS} | awk '{print $1}' | sed -E s/"^${POOL}\/ROOT\/${SOURCE##*/}"/"${POOL}\/ROOT\/${2##*/}"/g )
if [ "${OPTS}" = "-o = " ]
then
local OPTS=""
fi
if __be_snapshot ${1}
then
zfs clone -o canmount=off ${OPTS} ${FS}@${1##*@} ${DATASET}
else
zfs clone -o canmount=off ${OPTS} ${FS}@${2##*/} ${DATASET}
fi
done
echo "Created successfully"
}
ROOTFS=$( mount | awk '/ \/ / {print $1}' )
if echo ${ROOTFS} | grep -q -E "^/dev/"
then
echo "ERROR: This system does not boot from ZFS pool"
exit 1
fi
POOL=$( echo ${ROOTFS} | awk -F '/' '{print $1}' )
if ! zfs list ${POOL}/ROOT 1> /dev/null 2> /dev/null
then
echo "ERROR: This system is not configured for boot environments"
exit 1
fi
BOOTFS=$( zpool list -H -o bootfs ${POOL} )
case ${1} in
(list) # --------------------------------------------------------------------
BENAME_STARTS_WITH="${POOL}/ROOT"
LIST=$( zfs list -o name,used,mountpoint,creation -s creation -H -d 1 -r ${POOL}/ROOT | grep -E "^${POOL}/ROOT/" )
WIDTH_CREATION=$( echo "${LIST}" | awk '{print $5}' | wc -L )
WIDTH_NAME=$( echo "${LIST}" | awk '{print $1}' | wc -L )
WIDTH_NAME=$(( ${WIDTH_NAME} - ${#BENAME_STARTS_WITH} - 1 ))
printf "%-${WIDTH_NAME}s %-6s %-10s %5s %6s %s\n" \
BE Active Mountpoint Space Policy Created
echo "${LIST}" \
| while read NAME USED MOUNTPOINT Y m d H M
do
NAME=${NAME##*/}
unset ACTIVE
if [ "${BENAME_STARTS_WITH}/${NAME}" = "${ROOTFS}" ]
then
ACTIVE="${ACTIVE}N"
fi
if [ "${BENAME_STARTS_WITH}/${NAME}" = "${BOOTFS}" ]
then
ACTIVE="${ACTIVE}R"
fi
if [ -z "${ACTIVE}" ]
then
ACTIVE="-"
fi
printf "%-${WIDTH_NAME}s %-6s " ${NAME} ${ACTIVE}
case ${ACTIVE} in
(N|NR) MOUNT="/" ;;
(*) MOUNT="-" ;;
esac
printf "%-10s %5s %-6s " ${MOUNT} ${USED} "static"
date -j -f "%a %b %d %H:%M %Y" "${Y} ${m} ${d} ${H} ${M}" +"%Y-%m-%d %H:%M"
done
;;
(create) # ------------------------------------------------------------------
case ${#} in
(4)
if ! [ ${2} = "-e" ]
then
__usage
fi
__be_exist ${POOL}/ROOT/${3}
if zfs list -H -o name ${POOL}/ROOT/${4} 1> /dev/null 2> /dev/null
then
echo "ERROR: Boot environment '${4}' already exists"
exit 1
fi
__be_new ${POOL}/ROOT/${3} ${POOL}/ROOT/${4}
;;
(2)
if __be_snapshot ${2}
then
if ! zfs snapshot -r ${POOL}/ROOT/${2} 1> /dev/null 2> /dev/null
then
echo "ERROR: Cannot create '${2}' recursive snapshot"
exit 1
fi
echo "Created successfully"
else
__be_new ${ROOTFS} ${POOL}/ROOT/${2}
fi
;;
(*)
__usage
;;
esac
;;
(activate) # ----------------------------------------------------------------
if [ ${#} -ne 2 ]
then
__usage
fi
__be_exist ${POOL}/ROOT/${2}
if [ "${BOOTFS}" = "${POOL}/ROOT/${2}" ]
then
echo "Already activated"
exit 0
else
if [ "${ROOTFS}" != "${POOL}/ROOT/${2}" ]
then
TMPMNT="/tmp/BE"
if ! mkdir -p ${TMPMNT}
then
echo "ERROR: Cannot create '${TMPMNT}' directory"
exit 1
fi
MOUNT=0
while read FS MNT
do
if [ "${FS}" = "${POOL}/ROOT/${2}" ]
then
MOUNT=${MNT}
fi
done << EOF
$( mount -p | awk '{print $1 " " $2}' )
EOF
if [ ${MOUNT} -eq 0 ]
then
zfs set canmount=noauto ${POOL}/ROOT/${2}
zfs set mountpoint=${TMPMNT} ${POOL}/ROOT/${2}
zfs mount ${POOL}/ROOT/${2} 1> /dev/null 2> /dev/null
else
TMPMNT=${MOUNT}
fi
cp /boot/zfs/zpool.cache ${TMPMNT}/boot/zfs/zpool.cache
LOADER_CONFIGS=${TMPMNT}/boot/loader.conf
if [ -f ${TMPMNT}/boot/loader.conf.local ]
then
LOADER_CONFIGS="${LOADER_CONFIGS} ${TMPMNT}/boot/loader.conf.local"
fi
sed -i '' -E s/"^vfs.root.mountfrom=.*$"/"vfs.root.mountfrom=\"zfs:${POOL}\/ROOT\/${2##*/}\""/g ${LOADER_CONFIGS} 2> /dev/null
if [ ${MOUNT} -eq 0 ]
then
zfs umount ${POOL}/ROOT/${2}
zfs set mountpoint=legacy ${POOL}/ROOT/${2}
fi
fi
if ! zpool set bootfs=${POOL}/ROOT/${2} ${POOL} 2> /dev/null
then
echo "ERROR: Failed to activate '${POOL}/ROOT/${2}'"
exit 1
fi
fi
# disable automatic mount on all inactive datasets
zfs list -H -o name -r ${POOL}/ROOT \
| grep -v "${POOL}/ROOT/${2}" \
| while read I
do
zfs set canmount=noauto ${I}
done
# enable automatic mount for active BE and promote it
zfs list -H -o name,origin -t filesystem -r ${POOL}/ROOT/${2} \
| while read I ORIGIN
do
zfs set canmount=on ${I}
if [ ${ORIGIN} != "-" ]
then
zfs promote ${I}
fi
done
echo "Activated successfully"
;;
(destroy) # -----------------------------------------------------------------
case ${#} in
(2)
echo "Are you sure you want to destroy '${2}'?"
echo -n "This action cannot be undone (y/[n]): "
read CHOICE
DESTROY=${2}
;;
(3)
if [ "${2}" != "-F" ]
then
__usage
fi
CHOICE=Y
DESTROY=${3}
;;
(*)
__usage
;;
esac
__be_exist ${POOL}/ROOT/${DESTROY}
if [ "${BOOTFS}" = "${POOL}/ROOT/${DESTROY}" ]
then
echo "ERROR: '${POOL}/ROOT/${2}' is current active boot environment"
exit 1
fi
case ${CHOICE} in
(Y|y|[Yy][Ee][Ss])
if __be_snapshot ${POOL}/ROOT/${DESTROY}
then
if ! zfs destroy -r ${POOL}/ROOT/${DESTROY} 1> /dev/null 2> /dev/null
then
echo "ERROR: Snapshot '${2}' is origin for other boot environment(s)"
exit 1
fi
else
zfs list -r -H -o name ${POOL}/ROOT/${DESTROY} \
| sort -n -r \
| while read DATASET
do
zfs umount ${DATASET} 1> /dev/null 2> /dev/null || true
done
ORIGINS=$( zfs list -r -H -o origin ${POOL}/ROOT/${DESTROY} )
if ! zfs destroy ${POOL}/ROOT/${DESTROY} 1> /dev/null 2> /dev/null
then
zfs destroy -r ${POOL}/ROOT/${DESTROY} 2>&1 \
| grep "${POOL}/ROOT/" \
| grep -v "@" \
| while read I
do
zfs promote ${I} 2> /dev/null
done
fi
if [ "${ORIGINS}" != "-" ]
then
echo "${ORIGINS}" \
| while read I
do
zfs destroy -r ${I} 2> /dev/null || true
done
fi
fi
echo "Destroyed successfully"
;;
(*)
echo "'${DESTROY}' has not been destroyed"
;;
esac
;;
(rename) # ------------------------------------------------------------------
if [ ${#} -ne 3 ]
then
__usage
fi
__be_exist ${POOL}/ROOT/${2}
if [ "${BOOTFS}" = "${POOL}/ROOT/${2}" ]
then
echo "ERROR: Renaming the active BE is not supported"
exit 1
fi
if zfs list -H -o name ${POOL}/ROOT/${3} 2> /dev/null
then
echo "ERROR: Boot environment '${3}' already exists"
exit 1
fi
zfs rename ${POOL}/ROOT/${2} ${POOL}/ROOT/${3}
echo "Renamed successfully"
;;
(mount) # -------------------------------------------------------------------
COMPLETE=1
if [ ${#} -eq 1 -o ${#} -eq 2 ]
then
if [ ${2} ]
then
if [ "${2}" = "-p" ]
then
OPTS="-p"
else
BE=${2}
fi
fi
if ! [ ${BE} ]
then
mount ${OPTS} | grep -E "^${POOL}/ROOT/"
exit 0
fi
elif [ ${#} -eq 3 ]
then
BE=${2}
TARGET=${3}
else
__usage
fi
__be_exist "${POOL}/ROOT/${BE}"
if mount | grep -E "^${POOL}/ROOT/${BE} " 1> /dev/null 2> /dev/null
then
MNT=$( mount | grep -E "^${POOL}/ROOT/${BE} " | awk '{print $3}' )
echo "The '${BE}' is already mounted at '${MNT}'"
exit 1
fi
if ! [ ${3} ]
then
TARGET="/tmp/tmp.$( tr -cd 'A-Za-z' < /dev/random|head -c 6 )"
mkdir -p ${TARGET}
fi
if ! mount -t zfs ${POOL}/ROOT/${BE} ${TARGET}
then
echo "ERROR: Cannot mount '${POOL}/ROOT/${BE}' at '${TARGET}' mountpoint"
exit 1
fi
zfs list -H -o name,mountpoint -r ${POOL}/ROOT/${BE} \
| grep -v "legacy$" \
| sort -n \
| grep -E "^${POOL}/ROOT/${BE}/" \
| while read FS MOUNTPOINT
do
if ! mkdir -p ${TARGET}${MOUNTPOINT} 1> /dev/null 2> /dev/null
then
echo "ERROR: Cannot create '${TARGET}${MOUNTPOINT}' mountpoint"
exit 1
fi
if ! mount -t zfs ${FS} ${TARGET}${MOUNTPOINT} 1> /dev/null 2> /dev/null
then
echo "ERROR: Cannot mount '${FS}' at '${TARGET}${MOUNTPOINT}' mountpoint"
COMPLETE=0
fi
done
if [ ${COMPLETE} -eq 0 ]
then
echo "Partially mounted"
else
echo "Mounted successfully on '${TARGET}'"
fi
;;
(umount) # ------------------------------------------------------------------
COMPLETE=1
if [ ${#} -eq 2 ]
then
BE=${2}
elif [ ${#} -eq 3 ]
then
BE=${3}
if [ "${2}" = "-f" ]
then
OPTS="-f"
else
__usage
fi
else
__usage
fi
__be_exist "$POOL/ROOT/$BE"
if ! mount | grep -E "^${POOL}/ROOT/${BE} " 1> /dev/null 2> /dev/null
then
echo "The '${BE}' is not mounted"
exit 1
fi
mount \
| grep "^${POOL}/ROOT/${BE}" \
| awk '{print $1}' \
| sort -n -r \
| while read FS
do
if ! umount ${OPTS} ${FS} 1> /dev/null 2> /dev/null
then
echo "ERROR: Cannot umount '${FS}' dataset"
COMPLETE=0
fi
done
if [ ${COMPLETE} -eq 0 ]
then
echo "Partially unmounted"
else
echo "Unmounted successfully"
fi
;;
(*) # -----------------------------------------------------------------------
__usage
;;
esac