Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view sbin/fzfs @ 723:a97ec3f07bdb
farray.sh: REFACTOR: More flexible metadata retrieval.
Using an array or alist variable name or token value (with prefix) is now
supported in every function.
This is possible because the value prefixes contain questin marks (?) which
are not allowed in shell variable names.
This again is a major precondition for recursive data structures
(arrays/alists in arrays/alists).
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 05 Oct 2024 21:55:55 +0200 |
| parents | 6e7c118e7d47 |
| children | 8f1583faf9ea |
line wrap: on
line source
#!/bin/sh # -*- indent-tabs-mode: nil; -*- #: #: A ZFS management helper tool. #: #: :Author: Franz Glasner #: :Copyright: (c) 2022-2024 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 # shellcheck disable=SC2034 # VERSION appears unused 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: clone-tree [-k] [-n] SOURCE-DATASET DEST-DATASET 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] [-k] [-u] [-n] DATASET [MOUNTPOINT] umount [-f] [-k] DATASET unmount [-f] [-k] DATASET ' _p_datadir='@@DATADIR@@' [ "${_p_datadir#@@DATADIR}" = '@@' ] && _p_datadir="$(dirname "$0")"/../share/local-bsdtools . "${_p_datadir}/common.subr" . "${_p_datadir}/farray.sh" # #: 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 _opt_keep local _name _mp _canmount _mounted _rootds_mountpoint _rootds_mountpoint_prefix _relative_mp _real_mp local _mounted_datasets _opt_dry_run="" _opt_keep="" _opt_mount_outside="" _opt_mount_natural="" _opt_mount_children_only="" while getopts "ONPknu" _opt ; do case ${_opt} in O) _opt_mount_outside="yes" ;; N) _opt_mount_natural="yes" ;; P) _opt_mount_children_only="yes" ;; k) _opt_keep="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 if [ "${_rootds_mountpoint}" = "/" ]; then _rootds_mountpoint_prefix="/" else _rootds_mountpoint_prefix="${_rootds_mountpoint}/" fi zfs list -H -o name,mountpoint,canmount,mounted -s mountpoint -t filesystem -r "${_dsname}" \ | { # # _mounted_datasets is an array of ZFS datasets that have been # mounted by this routine and should be unmounted on errors # -- if possible. # farray_create _mounted_datasets 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_prefix}"*) # # Handle only mountpoints that have a mountpoint below # or exactly at the parent datasets mountpoint # # # Determine the mountpoint relative to the parent # mountpoint. Extra effort is needed because the root # filesystem mount is just a single slash. # if [ "${_mp}" = "${_rootds_mountpoint}" ]; then if [ "${_name}" != "${_dsname}" ]; then echo "ERROR: child dataset mounts over root dataset" >&2 if ! checkyes _opt_keep; then _umount_datasets _mounted_datasets || true fi return 1 fi _relative_mp="" _real_mp="${_mountpoint}" else _relative_mp="${_mp#"${_rootds_mountpoint_prefix}"}" # Eventually remove a trailing slash _relative_mp="${_relative_mp%/}" if [ -z "${_relative_mp}" ]; then echo "ERROR: got an empty relative mountpoint in child" >&2 if ! checkyes _opt_keep; then _umount_datasets _mounted_datasets || true fi return 1 fi # The real effective full mountpoint _real_mp="${_mountpoint}/${_relative_mp}" fi # # 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 if ! checkyes _opt_keep; then _umount_datasets _mounted_datasets || true fi 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; _umount_datasets _mounted_datasets || true; return 1; } echo "Mounting ${_name} on ${_real_mp}" if /sbin/mount -t zfs "${_name}" "${_real_mp}"; then farray_append _mounted_datasets "${_name}" else if ! checkyes _opt_keep; then _umount_datasets _mounted_datasets || true fi return 1 fi 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}" if zfs mount "${_name}"; then farray_append _mounted_datasets "${_name}" else if ! checkyes _opt_keep; then _umount_datasets _mounted_datasets || true fi return 1 fi fi else echo "Skipping ${_name} because its configured ZFS mountpoint is not relative to given root dataset" 2>&1 fi ;; esac done farray_destroy _mounted_datasets return 0 } } #: #: Helper to unmount mounted ZFS filesystems #: #: Args: #: $1 (str): The name of the array that contains the datasets to unmount to #: #: Returns: #: 0 if successfully unmounted all given datasets, #: 1 otherwise #: #: Unmounting is done in the reverse order. #: _umount_datasets() { farray_reversed_for_each "$1" _umount_datasets_umount } #: #: Array callback to unmount a single ZFS filesystem #: _umount_datasets_umount() { if ! zfs umount "$3" ; then warn "Dataset \`${3}' cannot unmounted" return 1 fi return 0 } #: #: Implement the "umount" command. #: #: Umount a datasets and recursively all its children datasets. #: command_umount() { local _dsname local _opt_force _opt_dry_run local _opt _name _mp _rest _rootds_mountpoint _opt_force="" _opt_dry_run="" while getopts "fk" _opt ; do case ${_opt} in f) _opt_force="-f" ;; k) _opt_dry_run="yes" ;; \?) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _dsname="${1-}" [ -z "${_dsname}" ] && { err "no dataset given"; return 2; } # Just determine whether the given dataset name exists _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}" 2>/dev/null)" || { err "dataset not found"; return 1; } _get_zfs_mounts_for_dataset_tree -r "${_dsname}" \ | { while IFS=$'\t' read -r _name _mp _rest; do if checkyes _opt_dry_run ; then if [ -z "${_opt_force}" ]; then echo "Would umount ${_name} from ${_mp}" else echo "Would forcefully umount ${_name} from ${_mp}" fi else echo "Umounting ${_name} on ${_mp}" /sbin/umount ${_opt_force} "${_mp}" || return 1 fi done return 0 } } #: #: Implementation of "copy-tree" #: command_copy_tree() { local _ds_source _ds_target local _opt_mountpoint _opt_mount_noauto _opt_nomount _opt_keep _opt_dry_run local _ds_source_base _ds_source_snapshot _snapshot_suffix local _ds_tree _ds _ds_relname _ds_canmount local _arg_canmount _arg_mp1 _arg_mp2 _opt_mountpoint="" _opt_mount_noauto="" _opt_nomount="" _opt_keep="" _opt_dry_run="" while getopts "AM:knu" _opt ; do case ${_opt} in A) _opt_mount_noauto="-o canmount=noauto" ;; M) _opt_mountpoint="${OPTARG}" ;; k) _opt_keep="yes" ;; 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 if zfs get -H name "${_ds_target}" >/dev/null 2>&1; then echo "ERROR: target dataset already exists: ${_ds_target}" 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 _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 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 IFS=$'\n' for _ds in ${_ds_tree}; do # Reset IFS IFS=$' \t\n' # Determine the relative name of the dataset _ds_relname="${_ds#"${_ds_source_base}"}" _ds_canmount=$(zfs get -H -o value canmount "${_ds}") if [ "${_ds_canmount}" = "off" ]; then _arg_canmount="-o canmount=off" else _arg_canmount="${_opt_mount_noauto}" fi if [ -z "${_ds_relname}" ]; then # # Source root to target root # if [ -z "${_opt_mountpoint}" ]; then _arg_mp1=-x _arg_mp2=mountpoint else _arg_mp1=-o _arg_mp2="mountpoint=${_opt_mountpoint}" fi else _arg_mp1=-x _arg_mp2=mountpoint fi if [ -z "${_opt_dry_run}" ]; then zfs send -Lec -p -v "${_ds}${_snapshot_suffix}" | zfs receive -v ${_opt_nomount} ${_arg_canmount} ${_arg_mp1} "${_arg_mp2}" "${_ds_target}${_ds_relname}" else echo "Would execute: zfs send -Lec -p -v '${_ds}${_snapshot_suffix}' | zfs receive -v ${_opt_nomount} ${_arg_canmount} ${_arg_mp1} '${_arg_mp2}' '${_ds_target}${_ds_relname}'" fi # for the loop IFS=$'\n' done # Reset to default IFS=$' \t\n' # Remove received snapshots by default if [ -n "${_ds_source_snapshot}" ]; then if [ -z "${_opt_keep}" ]; then if [ -z "${_opt_dry_run}" ]; then zfs destroy -rv "${_ds_target}${_snapshot_suffix}" else echo "Would execute: zfs destroy -rv '${_ds_target}${_snapshot_suffix}'" fi fi fi } #: #: Implementation of "clone-tree" #: command_clone_tree() { local _ds_source _ds_dest local _opt_keep _opt_dry_run local _ds _snapshot_name _ds_source_base _ds_relname local _ds_canmount _ds_mountpoint local _clone_props _arg_canmount _args_other_clone_props local _opt _idx _idx_lp _prop _propvalue local _ds_tree _cloned_datasets _local_props _opt_dry_run="" _opt_keep="" while getopts "kn" _opt ; do case ${_opt} in k) _opt_keep="yes" ;; n) _opt_dry_run="yes" ;; \?|:) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _ds_source="${1-}" _ds_dest="${2-}" [ -z "${_ds_source}" ] && { err "no source dataset/snapshot given"; return 2; } [ -z "${_ds_dest}" ] && { err "no destination name given"; return 2; } if ! zfs get -H name "${_ds_source}" >/dev/null 2>&1; then err "source dataset does not exist: ${_ds_source}" return 1 fi if zfs get -H name "${_ds_dest}" >/dev/null 2>&1; then err "destination dataset already exists: ${_ds_dest}" return 1 fi _ds_source_base="${_ds_source%@*}" _snapshot_name="${_ds_source##*@}" if [ "${_ds_source_base}" = "${_snapshot_name}" ]; then err "no snapshot given" return 1 fi _ds_tree="" farray_create _ds_tree while IFS=$'\n' read -r _ds; do farray_append _ds_tree "${_ds}" done <<EOF_9ef07253679011efa78174d435fd3892 $(zfs list -H -r -t filesystem -o name -s name "${_ds_source_base}") EOF_9ef07253679011efa78174d435fd3892 # Check the existence of all intermediate datasets and their shapshots _idx=1 while farray_tryget _ds _ds_tree ${_idx}; do if ! zfs get -H name "${_ds}@${_snapshot_name}" >/dev/null 2>&1; then err "child dataset (snapshot) does not exist: ${_ds}@${_snapshot_name}" 1>&2 return 1 fi _idx=$((${_idx} + 1)) done _cloned_datasets="" farray_create _cloned_datasets _local_props="" falist_create _local_props _args_other_clone_props="" farray_create _args_other_clone_props # # 1. Clone with "safe" canmount settings # _idx=1 while farray_tryget _ds _ds_tree ${_idx}; do # Determine the relative name of the dataset _ds_relname="${_ds#"${_ds_source_base}"}" # Need to determine in *every* case (local, default, received, ...) _ds_canmount="$(zfs get -H -o value canmount "${_ds}")" falist_clear _local_props while IFS=$'\t' read -r _prop _propvalue ; do falist_set _local_props "${_prop}" "${_propvalue}" done <<EOF_ce8c76187f33471f8e8c1607ed09c42e $(zfs get -H -o property,value -s local,received all "${_ds}") EOF_ce8c76187f33471f8e8c1607ed09c42e # # - "zfs clone" does NOT copy/clone properties. # # Clones inherit their properties from the target filesystem # context or are set back to their ZFS default. # if [ "${_ds_canmount}" = "off" ]; then _arg_canmount="-o canmount=off" else _arg_canmount="-o canmount=noauto" fi # Copy all local props with the exception of canmount and mountpoint _idx_lp=1 while falist_tryget_item_at_index _prop _propvalue _local_props ${_idx_lp}; do if [ "${_prop}" = "mountpoint" ]; then _idx_lp=$((${_idx_lp} + 1)) continue fi if [ "${_prop}" = "canmount" ]; then _idx_lp=$((${_idx_lp} + 1)) continue fi farray_append _args_other_clone_props "-o" "${_prop}"="${_propvalue}" _idx_lp=$((${_idx_lp} + 1)) done if ! checkyes _opt_dry_run; then echo "Cloning ${_ds}@${_snapshot_name} into ${_ds_dest}${_ds_relname} with ${_arg_canmount} $(farray_print_join_for_eval _args_other_clone_props)" if eval "zfs clone \${_arg_canmount} $(farray_print_join_for_eval _args_other_clone_props) \"\${_ds}@\${_snapshot_name}\" \"\${_ds_dest}\${_ds_relname}\""; then farray_append _cloned_datasets "${_ds_dest}${_ds_relname}" else if ! checkyes _opt_keep; then _destroy_datasets _cloned_datasets || true fi return 1 fi else echo "Would execute: zfs clone ${_arg_canmount} $(farray_print_join_for_eval _args_other_clone_props) '${_ds}@${_snapshot_name}' '${_ds_dest}${_ds_relname}'" fi _idx=$((${_idx} + 1)) done # # 2. Copy property mountpoint for root and inherit for all children; # also handle canmount. # _idx=1 while farray_tryget _ds _ds_tree ${_idx}; do # Determine the relative name of the dataset _ds_relname="${_ds#"${_ds_source_base}"}" # Need to determine in *every* case (default, local, received, ...) _ds_canmount="$(zfs get -H -o value canmount "${_ds}")" # Local mountpoint _ds_mountpoint="$(zfs get -H -o value -s local,received mountpoint "${_ds}")" if [ \( "${_ds_canmount}" = "off" \) -o \( "${_ds_canmount}" = "noauto" \) ]; then # # Already handled above because "nomount" is the default if not # already set to "off". # _arg_canmount="" else _arg_canmount="-o canmount=${_ds_canmount}" fi if [ \( -n "${_arg_canmount}" \) -o \( -n "${_ds_mountpoint}" \) ]; then if ! checkyes _opt_dry_run; then # If a local or received mountpoint is given set it here if [ -n "${_ds_mountpoint}" ]; then echo "Correcting properties for ${_ds_dest}${_ds_relname}: ${_arg_canmount} mountpoint=\"${_ds_mountpoint}\"" zfs set -u ${_arg_canmount} mountpoint="${_ds_mountpoint}" "${_ds_dest}${_ds_relname}" || true else echo "Correcting properties for ${_ds_dest}${_ds_relname}: ${_arg_canmount}" zfs set -u ${_arg_canmount} "${_ds_dest}${_ds_relname}" || true fi else # If a local or received mountpoint is given set it here if [ -n "${_ds_mountpoint}" ]; then echo "Would execute: zfs set -u ${_arg_canmount} mountpoint='${_ds_mountpoint}' '${_ds_dest}${_ds_relname}'" else echo "Would execute: zfs set -u ${_arg_canmount} '${_ds_dest}${_ds_relname}'" fi fi fi _idx=$((${_idx} + 1)) done farray_destroy _cloned_datasets farray_destroy _args_other_clone_props 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_zfs_properties_for_create "${_current_name}" atime exec setuid compression primarycache sync readonly)" if [ -z "${_relative_name}" ]; then # # Root # if [ -z "${_opt_noauto}" ]; then _zfs_canmount="$(_get_local_zfs_properties_for_create "${_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_zfs_properties_for_create "${_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 } #: #: Helper to destroy some created ZFS filesystems #: #: Args: #: $1 (str): The name of the array that contains the datasets to destroy to #: #: Returns: #: 0 if successfully destroyed all given datasets, #: 1 otherwise #: #: Destruction is done in the reverse order. #: _destroy_datasets() { farray_reversed_for_each "$1" _destroy_datasets_destroy } #: #: Array callback to destroy a single ZFS filesystem #: _destroy_datasets_destroy() { if ! zfs destroy -v "$3" ; then warn "Dataset \`${3}' cannot unmounted" return 1 fi 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; ;; *) fatal 2 "option handling failed" 1>&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 || fatal 2 "no command given" command="$1" shift case "${command}" in mount) command_mount "$@" ;; umount|unmount) command_umount "$@" ;; clone-tree) command_clone_tree "$@" ;; copy-tree) command_copy_tree "$@" ;; create-tree) command_create_tree "$@" ;; *) fatal 2 "unknown command \`${command}'" 1>&2 ;; esac
