view sbin/fzfs @ 286:258a1dfd52eb

Separate manual page documentation and "normal" HTML documentation. No "orphaned" source files should be there now. Also the HTML theme for the is changed from "alabaster" to "agogo".
author Franz Glasner <fzglas.hg@dom66.de>
date Sun, 18 Sep 2022 10:25:55 +0200
parents 1fc3b04b39fa
children 6be930eb7490
line wrap: on
line source

#!/bin/sh
# -*- indent-tabs-mode: nil; -*-
#:
#: A ZFS management helper tool.
#:
#: :Author:    Franz Glasner
#: :Copyright: (c) 2022 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:        @(#)@@PKGORIGIN@@ $HGid$
#:

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:

  mount [-O] [-N] [-P] [-u] [-n] DATASET [MOUNTPOINT]

  umount DATASET

  unmount

'

#
#: 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
}

#
# Global option handling
#
while getopts "Vh" _opt ; do
    case ${_opt} in
        V)
            printf 'ftjail v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@'
            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 "$@"
        ;;
    *)
        echo "ERROR: unknown command \`${command}'" 1>&2
        exit 2
        ;;
esac