Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view sbin/fzfs @ 386:84d2735fe7f6
Simplified version tagging a lot: it is also faster now.
While being there: adjusting copyright year to end in 2023 now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Thu, 23 Feb 2023 01:08:31 +0100 |
| parents | 6be930eb7490 |
| children | bb0a0384b5da |
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: 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 } #: #: 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 'ftjail %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 "$@" ;; create-tree) command_create_tree "$@" ;; *) echo "ERROR: unknown command \`${command}'" 1>&2 exit 2 ;; esac
