Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view sbin/fzfs @ 427:7056e5b7712d
Improvenents in handling names with spaces: quoting fixes
| author | Franz Glasner <hg@dom66.de> |
|---|---|
| date | Thu, 21 Sep 2023 12:03:09 +0200 |
| parents | 2cd4777821fc |
| children | 824e88618376 |
line wrap: on
line source
#!/bin/sh # -*- indent-tabs-mode: nil; -*- #: #: A ZFS management helper tool. #: #: :Author: Franz Glasner #: :Copyright: (c) 2022-2023 Franz Glasner. #: All rights reserved. #: :License: BSD 3-Clause "New" or "Revised" License. #: See LICENSE for details. #: If you cannot find LICENSE see #: <https://opensource.org/licenses/BSD-3-Clause> #: :ID: @(#)@@SIMPLEVERSIONTAG@@ #: set -eu VERSION="@@VERSION@@" USAGE=' USAGE: fzfs [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ] OPTIONS: -V Print the program name and version number to stdout and exit -h Print this help message to stdout and exit COMMANDS: copy-tree [-A] [-M MOUNTPOINT] [-n] [-u] SOURCE-DATASET DEST-DATASET create-tree [-A] [-M MOUNTPOINT] [-n] [-p] [-u] SOURCE-DATASET DEST-DATASET mount [-O] [-N] [-P] [-u] [-n] DATASET [MOUNTPOINT] umount DATASET unmount ' #: #: Determine some important dataset properties that are set locally #: #: Args: #: $1: the dataset #: $2 ...: the properties to check for #: #: Output (stdout): #: An option string suited for use in "zfs create" #: _get_local_properties() { local ds local _res _prop _value _source ds="${1}" shift _res="" for _prop in "$@" ; do IFS=$'\t' read -r _value _source <<EOF73GHASGJKKJ354 $(zfs get -H -p -o value,source "${_prop}" "${ds}") EOF73GHASGJKKJ354 case "${_source}" in local) if [ -z "${_res}" ]; then _res="-o ${_prop}=${_value}" else _res="${_res} -o ${_prop}=${_value}" fi ;; *) # VOID ;; esac done echo "${_res}" } # #: Implementation of the "mount" command. #: #: Mount a dataset and recursively all its children datasets. #: command_mount() { local _dsname _mountpoint local _opt_dry_run _opt_mount_outside _opt_mount_natural local _opt_mount_children_only local _name _mp _canmount _mounted _rootds_mountpoint _relative_mp _real_mp _opt_dry_run="" _opt_mount_outside="" _opt_mount_natural="" _opt_mount_children_only="" while getopts "ONPnu" _opt ; do case ${_opt} in O) _opt_mount_outside="yes" ;; N) _opt_mount_natural="yes" ;; P) _opt_mount_children_only="yes" ;; n|u) _opt_dry_run="yes" ;; \?|:) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _dsname="${1-}" _mountpoint="${2-}" if [ -z "${_dsname}" ]; then echo "ERROR: no dataset given" >&2 return 2 fi _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || \ { echo "ERROR: root dataset does not exist" >&2; return 1; } if [ -z "${_mountpoint}" ]; then if [ "${_opt_mount_natural}" = "yes" ]; then _mountpoint="${_rootds_mountpoint}" else echo "ERROR: no mountpoint given" >&2 return 2 fi else if [ "${_opt_mount_natural}" = "yes" ]; then echo "ERROR: Cannot have a custom mountpoint when \"-O\" is given" >&2 return 2 fi fi # Eventually remove a trailing slash _mountpoint="${_mountpoint%/}" if [ -z "${_mountpoint}" ]; then echo "ERROR: would mount over the root filesystem" >&2 return 1 fi zfs list -H -o name,mountpoint,canmount,mounted -s mountpoint -t filesystem -r "${_dsname}" \ | { while IFS=$'\t' read -r _name _mp _canmount _mounted ; do # Skip filesystems that are already mounted [ "${_mounted}" = "yes" ] && continue # Skip filesystems that must not be mounted [ "${_canmount}" = "off" ] && continue # # Mount only the children and skip the given parent dataset # if required # [ \( "${_opt_mount_children_only}" = "yes" \) -a \( "${_name}" = "${_dsname}" \) ] && continue case "${_mp}" in "none"|"legacy") # Do nothing for filesystem with unset or legacy mountpoints ;; "${_rootds_mountpoint}"|"${_rootds_mountpoint}/"*) # # Handle only mountpoints that have a mountpoint below # the parent datasets mountpoint # # Determine the mountpoint relative to the parent mountpoint _relative_mp="${_mp#${_rootds_mountpoint}}" # Eventually remove a trailing slash _relative_mp="${_relative_mp%/}" # The real effective full mountpoint _real_mp="${_mountpoint}${_relative_mp}" # # Consistency and sanity check: computed real mountpoint must # be equal to the configured mountpoint when no custom mountpoint # is given. # if [ "${_opt_mount_natural}" = "yes" ]; then if [ "${_real_mp}" != "${_mp}" ]; then echo "ERROR: mountpoint mismatch" >&2 return 1 fi fi if [ "${_opt_dry_run}" = "yes" ]; then echo "Would mount ${_name} on ${_real_mp}" else mkdir -p "${_real_mp}" 1> /dev/null 2> /dev/null || \ { echo "ERROR: cannot create mountpoint ${_real_mp}" >&2; return 1; } echo "Mounting ${_name} on ${_real_mp}" mount -t zfs "${_name}" "${_real_mp}" || return 1 fi ;; *) if [ "${_opt_mount_outside}" = "yes" ]; then if [ "${_opt_dry_run}" = "yes" ]; then echo "Would mount ${_name} on configured ZFS dataset mountpoint ${_mp}" else echo "Mounting ${_name} on configured ZFS dataset mountpoint ${_mp}" zfs mount "${_name}" || return 1 fi else echo "Skipping ${_name} because its configured ZFS mountpoint is not relative to given root dataset" 2>&1 fi ;; esac done return 0 } } #: #: Implement the "umount" command. #: #: Umount a datasets and recursively all its children datasets. #: command_umount() { local _dsname local _name _mp _rest _rootds_mountpoint _dsname="${1-}" [ -z "${_dsname}" ] && { echo "ERROR: no dataset given" 1>&2; return 2; } # Just determine whether the given dataset name exists _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || { echo "ERROR: dataset not found" 1>&2; return 1; } mount -t zfs -p \ | grep -E "^${_dsname}(/|\s)" \ | sort -n -r \ | { while IFS=' '$'\t' read -r _name _mp _rest ; do echo "Umounting ${_name} on ${_mp}" umount "${_mp}" || return 1 done } return 0 } #: #: Implementation of "copy-tree" #: command_copy_tree() { local _ds_source _ds_target local _opt_mountpoint _opt_mount_noauto _opt_nomount _opt_dry_run local _ds_source_base _ds_source_snapshot _snapshot_suffix local _ds_tree _ds _ds_relname _ds_canmount local _rflags _opt_mountpoint="" _opt_mount_noauto="" _opt_nomount="" _opt_dry_run="" while getopts "AM:nu" _opt ; do case ${_opt} in A) _opt_mount_noauto="-o canmount=noauto" ;; M) _opt_mountpoint="${OPTARG}" ;; n) _opt_dry_run="-n" ;; u) _opt_nomount="-u" ;; \?) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _ds_source="${1-}" _ds_target="${2-}" [ -z "${_ds_source}" ] && { echo "ERROR: no source given" 1>&2; return 2; } [ -z "${_ds_target}" ] && { echo "ERROR: no target name given" 1>&2; return 2; } if ! zfs get -H name "${_ds_source}" >/dev/null 2>&1; then echo "ERROR: source dataset does not exist: ${_ds_source}" 1>&2; return 1; fi _ds_source_base="${_ds_source%@*}" _ds_source_snapshot="${_ds_source##*@}" _snapshot_suffix="@${_ds_source_snapshot}" if [ "${_ds_source_snapshot}" = "${_ds_source_base}" ]; then # No snapshot given [ "${_ds_source_base}" = "${_ds_source}" ] || { echo "ERROR:" 1>&2; return 1; } _ds_source_snapshot="" _snapshot_suffix="" fi echo $_ds_source_base echo $_ds_source_snapshot $_snapshot_suffix _ds_tree="" while IFS=$'\n' read -r _ds; do if [ -z "${_ds_tree}" ]; then _ds_tree="${_ds}" else _ds_tree="${_ds_tree}"$'\n'"${_ds}" fi echo "X: $_ds" done <<EOF20ee7ea0781414fab8c305d3875d15e $(zfs list -H -r -t filesystem -o name -s name "${_ds_source_base}") EOF20ee7ea0781414fab8c305d3875d15e # Check the existence of all intermediate datasets and their shapshots IFS=$'\n' for _ds in ${_ds_tree}; do if ! zfs get -H name "${_ds}${_snapshot_suffix}" >/dev/null 2>&1; then echo "ERROR: child dataset does not exist: ${_ds}${_snapshot_suffix}" 1>&2 return 1 fi done for _ds in ${_ds_tree}; do # Determine the relative name of the dataset _ds_relname="${_ds#${_ds_source_base}}" _ds_canmount=$(zfs get -H -o value canmount "${_ds}") echo "REL: $_ds --- $_ds_relname --- $_ds_canmount" _rflags="" if [ "${_ds_canmount}" = "off" ]; then _rflags="-o canmount=off" else _rflags="${_opt_mount_noauto}" fi if [ -z "${_ds_relname}" ]; then # # Source root to target root # if [ -z "${_opt_mountpoint}" ]; then _rflags="${_rflags} -x mountpoint" else _rflags="${_rflags} -o mountpoint='${_opt_mountpoint}'" fi else _rflags="${_rflags} -x mountpoint" fi echo "zfs send -Lec -p -v '${_ds}${_ds_source_snapshot}' | zfs receive -v ${_opt_nomount} ${_rflags} '${_ds_target}${_ds_relname}'" done } #: #: Implement the "create-tree" command #: #: Create a ZFS dataset tree from a source tree tree including important properties #: command_create_tree() { local _source_ds _target_ds local _opt_dry_run _opt_mountpoint _opt_noauto _opt_nomount _opt_canmount_parent_only local _opt _source_name _snapshot_name _current_name _current_type _relative_name _zfs_opts _zfs_canmount _zfs_mountpoint _opt_noauto="" _opt_dry_run="" _opt_mountpoint="" _opt_nomount="" _opt_canmount_parent_only="" while getopts "AM:npu" _opt ; do case ${_opt} in A) _opt_noauto="-o canmount=noauto" ;; M) _opt_mountpoint="${OPTARG}" ;; n) _opt_dry_run="yes" ;; p) _opt_canmount_parent_only="yes" ;; u) _opt_nomount="-u" ;; \?|:) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _source_ds="${1-}" _target_ds="${2-}" [ -z "${_source_ds}" ] && { echo "ERROR: no source dataset given" 1>&2; return 2; } [ -z "${_target_ds}" ] && { echo "ERROR: no destination dataset given" 1>&2; return 2; } _source_name="${_source_ds%@*}" if [ "${_source_name}" != "${_source_ds}" ]; then _snapshot_name="${_source_ds##*@}" else _snapshot_name="" fi [ -n "${_snapshot_name}" ] && { echo "ERROR: No snapshot sources are supported yet" 1>&2; return 2; } zfs list -H -r -t filesystem -o name,type "${_source_ds}" \ | { while IFS=$'\t' read -r _current_name _current_type ; do # Determine the relative name of the dataset _relative_name="${_current_name#${_source_name}}" _relative_name="${_relative_name%@*}" if [ "${_current_type}" != "filesystem" ]; then echo "ERROR: Got a snapshot but expected a filesystem" 1>&2 return 1 fi _zfs_opts="$(_get_local_properties "${_current_name}" atime exec setuid compression primarycache sync readonly)" if [ -z "${_relative_name}" ]; then # # Root # if [ -z "${_opt_noauto}" ]; then _zfs_canmount="$(_get_local_properties "${_current_name}" canmount)" else _zfs_canmount="${_opt_noauto}" fi if [ -n "${_opt_mountpoint}" ]; then _zfs_mountpoint="${_opt_mountpoint}" else _zfs_mountpoint="" fi if [ "${_opt_dry_run}" = "yes" ]; then if [ -z "${_zfs_mountpoint}" ]; then echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} ${_target_ds}${_relative_name}" else echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} -o mountpoint=${_zfs_mountpoint} ${_target_ds}${_relative_name}" fi else if [ -z "${_zfs_mountpoint}" ]; then zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} "${_target_ds}${_relative_name}" else zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} -o "mountpoint=${_zfs_mountpoint}" "${_target_ds}${_relative_name}" fi fi else # # Children # if [ -z "${_opt_noauto}" ]; then if [ "${_opt_canmount_parent_only}" = "yes" ]; then _zfs_canmount="" else _zfs_canmount="$(_get_local_properties "${_current_name}" canmount)" fi else _zfs_canmount="${_opt_noauto}" fi if [ "${_opt_dry_run}" = "yes" ]; then echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} ${_target_ds}${_relative_name}" else zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} "${_target_ds}${_relative_name}" fi fi done } return 0 } # # Global option handling # while getopts "Vh" _opt ; do case ${_opt} in V) printf 'fzfs %s\n' '@@SIMPLEVERSIONSTR@@' exit 0 ;; h) echo "${USAGE}" exit 0 ;; \?) exit 2; ;; *) echo "ERROR: option handling failed" 1>&2 exit 2 ;; esac done # # Reset the Shell's option handling system to prepare for handling # command-local options. # shift $((OPTIND-1)) OPTIND=1 test $# -gt 0 || { echo "ERROR: no command given" 1>&2; exit 2; } command="$1" shift case "${command}" in mount) command_mount "$@" ;; umount|unmount) command_umount "$@" ;; copy-tree) command_copy_tree "$@" ;; create-tree) command_create_tree "$@" ;; *) echo "ERROR: unknown command \`${command}'" 1>&2 exit 2 ;; esac
