Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view share/local-bsdtools/common.subr @ 805:e18cc5fe828c
.shellcheckrc: disable SC2214 because is fires for all long options.
Long options are not understood by shellcheck.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 04 Nov 2024 22:45:00 +0100 |
| parents | 2cd233b137ec |
| children | 6cf3e1021862 |
line wrap: on
line source
#!/bin/sh # -*- mode: shell-script; indent-tabs-mode: nil; -*- #: #: :Author: Franz Glasner #: :Copyright: (c) 2017-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@@ #: : # Dummy separator for shellcheck: no module-wide settings below this line # shellcheck disable=SC2223 # quote #: The path to the external jq executable (JSON parser) : ${JQ="/usr/local/bin/jq"} # # Some important definitions from :manpage:`sysexits(3)` # # shellcheck disable=SC2034 # seems unused EX_OK=0 # shellcheck disable=SC2034 EX_BASE=64 # shellcheck disable=SC2034 EX_USAGE=$((EX_BASE + 0)) # shellcheck disable=SC2034 EX_DATAERR=$((EX_BASE + 1)) # shellcheck disable=SC2034 EX_NOINPUT=$((EX_BASE + 2)) # shellcheck disable=SC2034 EX_UNAVAILABLE=$((EX_BASE + 5)) # shellcheck disable=SC2034 EX_SOFTWARE=$((EX_BASE + 6)) # shellcheck disable=SC2034 EX_TEMPFAIL=$((EX_BASE + 12)) # shellcheck disable=SC2034 EX_CONFIG=$((EX_BASE + 14)) #: #: Display an error message to stderr and exit with given exit code #: #: Args: #: $1 (int): The exit code to exit with #: $2 ...: The message to print to #: #: Exit: #: Always exits with $1 #: #: The name of the script is prepended to the error message always. #: fatal() { local _ec if [ $# -ge 1 ]; then _ec="$1" [ -z "$_ec" ] && _ec="${EX_USAGE}" shift else _ec=1 fi printf "%s: ERROR: %s\\n" "$0" "$*" 1>&2 exit "${_ec}" } #: #: Display an error message to stderr #: #: Args: #: $*: The message to print to #: #: Returns: #: int: 0 #: #: The name of the script is prepended to the error message always. #: err() { printf "%s: ERROR: %s\\n" "$0" "$*" 1>&2 return 0 } #: #: Display a warning to stderr #: #: Args: #: $*: the fields to display #: #: Returns: #: int: 0 #: #: The name of the script is prepended to the warning message always. #: warn() { printf "%s: WARNING: %s\\n" "$0" "$*" 1>&2 return 0 } #: #: Test $1 variable, and warn if not set to YES or NO. #: #: Args: #: $1 (str): The name of the variable to test to. #: #: Returns: #: int: 0 (truthy) iff ``yes`` (et al), #: 1 (falsy) otherwise. #: #: This function warns if other than proper YES/NO or their alias #: values are found. #: checkyesno() { # $1 local _value eval _value=\"\$\{"${1}"\}\" # do not delegate to checkyesnovalue() because of the error message proper case "${_value}" in # "yes", "true", "on", or "1" [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;; # "no", "false", "off", or "0" [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;; *) warn "\${${1}} is not set properly" return 1 ;; esac } #: #: Test $1 variable, and warn if not set to YES or NO. #: #: Args: #: $1 (str): The value to test to. #: #: Returns: #: int: 0 (truthy) iff ``yes`` (et al), #: 1 (falsy) otherwise. #: #: This function warns if other than proper YES/NO values are found. #: checkyesnovalue() { # $1 case "${1}" in # "yes", "true", "on", or "1" [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;; # "no", "false", "off", or "0" [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;; *) warn "expected YES/NO but got \`${1}'" return 1 ;; esac } #: #: Test $1 variable whether it is set to YES. #: #: Args: #: $1 (str): The name of the variable to test to. #: #: Returns: #: int: 0 (truthy) iff ``yes`` (et al), #: 1 (falsy) otherwise. #: #: Contrary to `checkyesno` this function does not warn if the variable #: contains other values as YES. #: checkyes() { # $1 local _value eval _value=\"\$\{"${1}"\}\" checkyesvalue "${_value}" } #: #: Test a given value whether it is set to YES. #: #: Args: #: $1 (str): The value to test to. #: #: Returns: #: int: 0 (truthy) iff ``yes`` (et al), #: 1 (falsy) otherwise. #: #: Contrary to `checkyesno` this function does not warn if the variable #: contains other values as YES. #: checkyesvalue() { # $1 case "${1}" in # "yes", "true", "on", or "1" [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;; *) return 1 ;; esac } #: #: Ensure that no command line options are given #: #: Args: #: $@: #: #: Exit: #: 2: If formally `getopts` finds options in "$@" #: #: Return: #: 0 #: _ensure_no_options() { local _opt while getopts ':' _opt ; do [ "${_opt}" = '?' ] && fatal 2 "no option allowed (given: -${OPTARG-<unknown>})" done return 0 } #: #: Helper for `getopts` to process long options also. #: #: Invocation: #: postprocess_getopts_for_long shortopts varname longopt-spec* '' "$@" #: #: The `shortopts` *must* contain ``-:`` to allow a "short" option with #: name ``-`` and further arguments. This is used by this function to #: process them further and augment `OPTARG` and `varname` accordingly. #: #: Input (Globals): #: - The variable with name `varname` #: - OPTARG #: #: Output (Globals): #: - The variable with name `varname` #: - OPTARG #: #: Returns: #: int: 1 if an internal processing error occurs or if the long options #: are not terminated by a null string, #: 0 otherwise #: #: Heavily inspired by #: https://stackoverflow.com/questions/402377/using-getopts-to-process-long-and-short-command-line-options #: #: Error handling is in the line of the POSIX spec for a shell's `getopts`: #: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html. #: In addition to "normal" error handling, if a long options that does not #: accept an option argument has one the same type of error is reported #: as if a required option argument were missing. #: #: A leading ``:`` in `shortopts` is handled also if an alternate error #: handling is to be employed. #: #: Arguments for long options *must* use the form ``--long-option=value``. #: #: There is no default relation between short and long options. The evaluation #: by the caller is responsible for this. #: #: A long option may not contain the ``=`` character. But if a `longopt-spec` #: has a trailing ``=`` the long option requires an argument in the same #: way as the ``:`` for a short option. #: #: If a long option is specified more than once in `longopt-spec` the first #: one is taken into account. #: #: The `shortopts`, the `varname` must be the same as given to `getopts`. #: This is also true for the arguments to be analyzed. #: #: Example: #: #: :: #: while getopts "a:bc-:" opt "$@"; do #: postprocess_getopts_for_long "a:bc-:" opt "long-a=" "long-b" "long-d" "$@" #: case "${opt}" in #: a|long-a) #: a_value="${OPTARG}";; #: b|long-b) #: opt_b=1;; #: c) #: opt_c=1;; #: long-d) #: opt_d=1;; #: \?) #: # handle the error (message already printed to stderr) #: exit 2;; #: *) #: # other inconsistency #: exit 2;; #: esac #: done #: postprocess_getopts_for_long() { # $1 $2 $3... "" $n... local - local __ppgofl_shortopts __ppgofl_varname \ __ppgofl_opt __ppgofl_longspec __ppgofl_longopt __ppgofl_longoptarg \ __ppgofl_caller_reports_error \ __ppgofl_base __ppgofl_shortopts="${1}" __ppgofl_varname="${2}" eval __ppgofl_opt=\"\$\{"${__ppgofl_varname}"\}\" # Check whether it we currently handle a long option [ "${__ppgofl_opt}" = '-' ] || return 0 # # long option: reformulate OPT and OPTARG # case "${__ppgofl_shortopts}" in :*) __ppgofl_caller_reports_error=yes;; *) __ppgofl_caller_reports_error='';; esac __ppgofl_longopt='' case "${OPTARG}" in *=*) __ppgofl_longopt="${OPTARG%%=*}" __ppgofl_longoptarg="${OPTARG#*=}" ;; *) __ppgofl_longopt="${OPTARG}" unset __ppgofl_longoptarg ;; esac shift 2 # # Early check for null termination of long options. # Also check that the `=' character is not part of the name of a long # option. # __ppgofl_base=0 for __ppgofl_longspec in "$@"; do __ppgofl_base=$((__ppgofl_base + 1)) # [ -z "${__ppgofl_longspec}" ] && break case "${__ppgofl_longspec}" in '') break ;; *=*[!=]|*=*=) # Error: option contains a = character if [ -n "${__ppgofl_caller_reports_error}" ]; then setvar "${__ppgofl_varname}" '?' # XXX FIXME this char??? OPTARG="${__ppgofl_longspec}" else setvar "${__ppgofl_varname}" '?' unset OPTARG err "Long option specification contains a forbidden \`=' character: ${__ppgofl_longspec}" fi return 1 ;; *) : ;; esac done if [ -n "${__ppgofl_longspec}" ]; then if [ -n "${__ppgofl_caller_reports_error}" ]; then setvar "${__ppgofl_varname}" ':' OPTARG='' else setvar "${__ppgofl_varname}" '?' unset OPTARG err "Missing null terminator for long option specifications" fi return 1 fi # # Print the "real" arguments that will be analyzed # i=1 # while [ $((i + __ppgofl_base)) -le $# ]; do # eval v=\"\$\{$((i + __ppgofl_base))\}\" # echo "HUHU: ${v}" # i=$((i + 1)) # done # return 0 for __ppgofl_longspec in "$@"; do if [ -z "${__ppgofl_longspec}" ]; then # We did not hit a spec because we return ealy in the hit case if [ -n "${__ppgofl_caller_reports_error}" ]; then setvar "${__ppgofl_varname}" '?' OPTARG="${__ppgofl_longopt}" else setvar "${__ppgofl_varname}" '?' unset OPTARG err "Illegal option --${__ppgofl_longopt}" fi return 0 elif [ "${__ppgofl_longspec}" = "${__ppgofl_longopt}=" ]; then # Need an argument value if [ -z "${__ppgofl_longoptarg+SET}" ]; then # # Error # # Looking for an option like --long-option option-value # would need to change OPTIND. This is unspecified in # POSIX. So we handle only --long-option=option-value. # if [ -n "${__ppgofl_caller_reports_error}" ]; then setvar "${__ppgofl_varname}" ':' OPTARG="${__ppgofl_longopt}" else setvar "${__ppgofl_varname}" '?' unset OPTARG err "option argument required for option --${__ppgofl_longopt}" fi return 0 fi setvar "${__ppgofl_varname}" "${__ppgofl_longopt}" OPTARG="${__ppgofl_longoptarg}" return 0 elif [ "${__ppgofl_longspec}" = "${__ppgofl_longopt}" ]; then # No argument allowed if [ -n "${__ppgofl_longoptarg+SET}" ]; then # Error if [ -n "${__ppgofl_caller_reports_error}" ]; then setvar "${__ppgofl_varname}" ':' OPTARG="${__ppgofl_longopt}" else setvar "${__ppgofl_varname}" '?' unset OPTARG err "no option argument allowed for option --${__ppgofl_longopt}" fi return 0 fi setvar "${__ppgofl_varname}" "${__ppgofl_longopt}" unset OPTARG return 0 fi done # Processing error return 1 } #: #: 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_zfs_properties_for_create() { 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 printf '%s' "${_res}" } #: #: Use `mount -t zfs -p` to determine the ZFS dataset for a given mountpoint #: #: Args: #: $1: the mountpoint #: #: Output (stdout): #: The name of the ZFS dataset that is mounted on `$1` #: #: Return: #: 0: if a mounted dataset is found #: 1: if no ZFS dataset is found that is mounted on `$1` #: #: The dataset has to be mounted. #: _get_zfs_dataset_for_mountpoint() { local _mountpoint local _ds _mount _rest _mountpoint="$1" if [ -x "${JQ}" ]; then /sbin/mount -t zfs -p --libxo=json,no-locale \ | LC_ALL=C.UTF-8 "${JQ}" -r $'.mount.fstab[] | [.device, .mntpoint, .fstype, .opts, .dump, .pass] | @tsv ' \ | { while IFS=$'\t' read -r _ds _mount _rest ; do if [ "$_mount" = "$_mountpoint" ]; then printf '%s' "${_ds}" return 0 fi done return 1 } else # Check for unexpected spaces if ! check_for_proper_fstab; then fatal 1 "Unexpected spaces in fstab. Please install \`${JQ}'." fi /sbin/mount -t zfs -p \ | { while IFS=$' \t' read -r _ds _mount _rest ; do if [ "$_mount" = "$_mountpoint" ]; then printf '%s' "${_ds}" return 0 fi done return 1 } fi } #: #: Determine the ZFS dataset that is mounted at `$1`/var/empty. #: #: Allow special handling for <mountpoint>/var/empty which may be #: mounted read-only. #: #: Args: #: $1: the root mountpoint #: #: Output (stdout): #: The name of the ZFS dataset that is mounted on `$1`/var/empty #: #: Return: #: 0: if a mounted dataset is found #: 1: if no ZFS dataset is found that is mounted on `$1` #: #: The dataset has to be mounted. #: _get_zfs_dataset_for_varempty() { local _mountpoint local _ve_mount _mountpoint="$1" if [ "$_mountpoint" = '/' ]; then _ve_mount='/var/empty' else _ve_mount="${_mountpoint}/var/empty" fi _get_zfs_dataset_for_mountpoint "${_ve_mount}" } #: #: Search for a running jail where it's "path" points to a given location #: #: Args: #: $1 (str): The location to search for. #: #: Output (stdout): #: The name if the jail with a "path" that is equal to the input param. #: Nothing if a jail is not found. #: The name is written when the jail is running and/or a dying. #: #: Return: #: 0: if a running jail is found #: 1: error #: 2: jail found but currently dying #: 3: no running jail found #: _get_jail_from_path() { local _location local _name _path _dying _location="${1-}" case "${_location}" in '') err "no mountpoint given" return 1 ;; /) true ;; *//*) err "given directory must not contain consequtive slashes" return 1 ;; /*/) # Remove trailing slash _location="${_location%/}" ;; /*) true ;; *) err "given directory path must be an absolute path" return 1 ;; esac if [ -x "${JQ}" ]; then /usr/sbin/jls --libxo=json,no-locale -d dying name path \ | LC_ALL=C.UTF-8 "${JQ}" -r $'.["jail-information"].jail[] | [.dying, .name, .path] | @tsv ' \ | { # ' while IFS=$'\t' read -r _dying _name _path ; do if [ "${_path}" = "${_location}" ]; then if [ "${_dying}" != "false" ]; then # Dying printf '%s' "${_name}" return 2 fi printf '%s' "${_name}" return 0 fi done return 3 } else /usr/sbin/jls --libxo=text,no-locale -d name dying path \ | { while IFS=' '$'\t' read -r _name _dying _path ; do if [ "${_path}" = "${_location}" ]; then if [ "${_dying}" != "false" ]; then # Dying printf '%s' "${_name}" return 2 fi printf '%s' "${_name}" return 0 fi done return 3 } fi } #: #: Search for current mounts of a given ZFS dataset and its children. #: #: Only ZFS mounts are considered. Any returned datasets are just #: the given mounted dataset or its mounted children. #: #: The output is sorted by the mountpoint. #: #: Args: #: $1 (str): The dataset the to check for. #: The dataset and its children are searched for. #: #: Other Parameters: #: -r (optional): If given the the output is sorted in reverse order. #: #: Output (stdout): #: The sorted list (lines) of mounts in :manpage:`fstab(5)` format. #: This list may be empty. #: The fields are separated by a single TAB character always. #: #: Exit: #: 1: on fatal errors (usage et al.) #: _get_zfs_mounts_for_dataset_tree() { local _dsname local _opt_reversed local _opt _fstab _opt_reversed="" while getopts "r" _opt ; do case ${_opt} in r) _opt_reversed="--reverse" ;; \?) fatal 2 "invalid option given" ;; esac done shift $((OPTIND-1)) OPTIND=1 _dsname="${1-}" [ -z "${_dsname}" ] && fatal 2 "no dataset given" if [ -x "${JQ}" ]; then /sbin/mount -t zfs -p --libxo=json,no-locale \ | LC_ALL=C.UTF-8 "${JQ}" -r $'.mount.fstab[] | [.device, .mntpoint, .fstype, .opts, .dump, .pass] | @tsv ' \ | LC_ALL=C.UTF-8 /usr/bin/awk -F '\t' -v OFS=$'\t' -v ds1="${_dsname}" -v ds2="${_dsname}/" $'{ if (($1 == ds1) || (index($1, ds2) == 1)) { print $1, $2, $3, $4, $5, $6; } }' \ | LC_ALL=C.UTF-8 /usr/bin/sort --field-separator=$'\t' --key=2 ${_opt_reversed} else # Check for unexpected spaces if ! check_for_proper_fstab; then fatal 1 "Unexpected spaces in fstab. Please install \`${JQ}'." fi _fstab="$(/sbin/mount -t zfs -p | LC_ALL=C awk -v OFS=$'\t' -v ds1="${_dsname}" -v ds2="${_dsname}/" $'{ if (($1 == ds1) || (index($1, ds2) == 1)) { print $1, $2, $3, $4, $5, $6; } }' | LC_ALL=C /usr/bin/sort --field-separator=$'\t' --key=2 ${_opt_reversed})" printf '%s' "${_fstab}" fi } #: #: Search for mounts and sub-mounts at a given directory. #: #: The output is sorted by the mountpoint. #: #: Args: #: $1 (str, optional): The directory where to start for mounts and sub-mounts. #: It must be an absolute path. #: The root directory :file:`/` is allowed. #: A `null` value is treated as if the root directory :file:`/` #: is given as argument. #: #: Other Parameters: #: -r (optional): If given the the output is sorted in reverse order. #: #: Output (stdout): #: The sorted list (lines) of mounts in :manpage:`fstab(5)` format. #: This list may be empty. #: The fields are separated by a single TAB character always. #: #: Exit: #: 1: on fatal errors (usage et al.) #: #: Important: #: The input directory **must** be an absolute path. #: _get_mounts_at_directory() { local _directory local _opt_reversed local _opt _fstab _mp1 _mp2 _opt_reversed="" while getopts "r" _opt ; do case ${_opt} in r) _opt_reversed="--reverse" ;; \?) fatal 2 "invalid option given" ;; esac done shift $((OPTIND-1)) OPTIND=1 _directory="${1-}" case "${_directory}" in '') # OK this matches at the root directory _mp1='/' _mp2='/' ;; /) # OK this matches at the root directory _mp1='/' _mp2='/' ;; *//*) fatal 1 "given directory must not contain consequtive slashes" ;; /*/) # Remove trailing slash for equality check _mp1="${_directory%/}" # Hold the trailing slash as-is for the directory check _mp2="${_directory}" ;; /*) _mp1="${_directory}" _mp2="${_directory}/" ;; *) fatal 1 "given directory must be an absolute path" ;; esac if [ -x "${JQ}" ]; then /sbin/mount -p --libxo=json,no-locale \ | LC_ALL=C.UTF-8 "${JQ}" -r $'.mount.fstab[] | [.device, .mntpoint, .fstype, .opts, .dump, .pass] | @tsv ' \ | LC_ALL=C.UTF-8 /usr/bin/awk -F '\t' -v OFS=$'\t' -v mp1="${_mp1}" -v mp2="${_mp2}" $'{ if (($2 == mp1) || (index($2, mp2) == 1)) { print; } }' \ | LC_ALL=C.UTF-8 /usr/bin/sort --field-separator=$'\t' --key=2 ${_opt_reversed} else # Check for unexpected spaces if ! check_for_proper_fstab; then fatal 1 "Unexpected spaces in fstab. Please install \`${JQ}'." fi _fstab="$(/sbin/mount -p | LC_ALL=C awk -v OFS=$'\t' -v mp1="${_mp1}" -v mp2="${_mp2}" $'{ if (($2 == mp1) || (index($2, mp2) == 1)) { print $1, $2, $3, $4, $5, $6; } }' | LC_ALL=C /usr/bin/sort --field-separator=$'\t' --key=2 ${_opt_reversed})" printf '%s' "${_fstab}" fi } #: #: Use :command:`/usr/bin/procstat` to check if there are opened files or #: VM mappings with a given path prefix. #: #: Args: #: $1 (str, optional): The path prefix to check open files for. #: This must be an absolute path. #: The root directory :file:`/` is allowed. #: A `null` value is treated as if the root directory :file:`/` is given #: as argument. #: #: Returns: #: int: 0 if there are no opened files found, #: 10 if there are opened files found. #: Other error codes may also returned on errors. #: _check_no_open_files_from_all_proc() { local _path_prefix local _command _fd _fd_type _vnode_type _path _pid _kve_type _kve_path _rc _path_prefix="${1-}" case "${_path_prefix}" in '') # OK this matches at the root directory _p1='/' _p2='/' ;; /) # OK this matches at the root directory _p1='/' _p2='/' ;; *//*) fatal 1 "given path prefix must not contain consequtive slashes" ;; /*/) # Remove trailing slash for equality check _p1="${_path_prefix%/}" # Hold the trailing slash as-is for prefix check _p2="${_path_prefix}" ;; /*) _p1="${_path_prefix}" _p2="${_path_prefix}/" ;; *) fatal 1 "given path prefix must be an absolute path" ;; esac if [ -x "${JQ}" ]; then # shellcheck disable=SC2016 # $cmd is really not to expand here LC_ALL=C.UTF-8 /usr/bin/procstat --libxo=json -a file | LC_ALL=C.UTF-8 "${JQ}" -r '.procstat.files | map(.)| .[] | .command as $cmd | .files[] | [ $cmd, .fd, .fd_type, .vode_type, .path ] | @tsv' \ | { _rc=0 while IFS=$'\t' read -r _command _fd _fd_type _vnode_type _path; do case "${_path}" in "${_p1}"|"${_p2}"*) _rc=10 ;; *) ;; esac done [ "${_rc}" -ne 0 ] && return ${_rc} } LC_ALL=C.UTF-8 /usr/bin/procstat --libxo=json -a vm | LC_ALL=C.UTF-8 "${JQ}" -r $'.procstat.vm | map(.) | .[] | .process_id as $pid | .vm[] | [ $pid, .kve_type, .kve_path ] | @tsv' \ | { _rc=0 while IFD=$'\t' read -r _pid _kve_type _kve_path; do case "${_kve_path}" in "${_p1}"|"${_p2}"*) _rc=10 ;; *) ;; esac done return ${_rc} } else /usr/bin/procstat --libxo=text,no-locale -a file \ | LC_ALL=C /usr/bin/awk -v OFS=$'\t' '{ print $2, $3, $4, $10; }' \ | { _rc=0 while IFS=$'\t' read -r _command _fd _fd_type _path; do case "${_path}" in "${_p1}"|"${_p2}"*) _rc=10 ;; *) ;; esac done [ "${_rc}" -ne 0 ] && return ${_rc} } /usr/bin/procstat --libxo=text,no-locale -a vm \ | LC_ALL=C /usr/bin/awk -v OFS=$'\t' '{ print $1, $10, $11; }' \ | { _rc=0 while IFD=$'\t' read -r _pid _kve_type _kve_path; do case "${_kve_path}" in "${_p1}"|"${_p2}"*) _rc=10 ;; *) ;; esac done return ${_rc} } fi } #: #: Use :command:`/usr/bin/fstat` to check if there are opened files or #: VM mappings onr a given file system. #: #: Args: #: $1 (str, optional): The filesystem (aka mountpoint). #: This should be an absolute path. #: The root directory :file:`/` is allowed. #: A `null` value is treated as if the root directory :file:`/` is given #: as argument. #: #: Returns: #: int: 0 if there are no opened files found, #: 10 if there are opened files found. #: Other error codes may also returned on errors. #: _check_no_open_files_on_filesystem() { local _fspath local _count _fspath="${1:-/}" _count="$(LC_ALL=C /usr/bin/fstat -m -f "${_fspath}" | LC_ALL=C /usr/bin/wc -l)" if [ -z "${_count}" ]; then # this is an error return 1 fi # Note that fstat always prints a header: account for the header line if [ "${_count}" -gt 1 ]; then return 10 else return 0 fi } #: #: Check the validity of ZFS dataset names. #: #: See: ZFS Component Naming Requirements #: - https://docs.oracle.com/cd/E26505_01/html/E37384/gbcpt.html #: - https://illumos.org/books/zfs-admin/zfsover-1.html#gbcpt #: #: But it seems that in OpenZFS a space character is also allowed in #: name components; see https://github.com/openzfs/zfs/issues/439: #: It is in zfs_namecheck.c:valid_char() line #55; its the last #: character comparison. #: #: In OpenZFS pool names allow also the colon. #: #: Also there is no difference between "starting with" and other #: "containing" (with the exception of pools starting with a alpha #: character. #: #: Source code: https://iris.cs.tu-dortmund.de/freebsd-lockdoc/lockdoc-v13.0-0.1/source/sys/contrib/openzfs/module/zcommon/zfs_namecheck.c #: #: Args: #: $1 (str): The name of the dataset. #: $2 (bool, optional): If this evals to yes/on/1 then strict checking is #: done, otherwise the OpenZFS implementation is #: followed. #: #: Returns: #: int: 0 (truish) if it is a valid name, #: 1 (falsy) if not. #: #: We never check for special pool names (such as ``mirror``, ``raidz``, #: ``c0`` to ``c9`` et al.) because we do not create any pools. #: #: That also means that proper quoting with double quotes is enough to #: handle all sorts of ZFS names. #: check_zfs_naming() { local _strict _strict="${2-}" if checkyes _strict; then # Oracle docs printf "%s" "$1" | LC_ALL=C /usr/bin/grep -q -E '^[A-Za-z][-A-Za-z0-9_.]*(/[A-Za-z0-9][-A-Za-z0-9:_.]*)*(@[A-Za-z0-9][-A-Za-z0-9:_.]*)?$' else # OpenZFS printf "%s" "$1" | LC_ALL=C /usr/bin/grep -q -E '^[A-Za-z][-A-Za-z0-9:_. ]*(/[-A-Za-z0-9:_. ]+)*(@[-A-Za-z0-9:_. ]+)?$' fi } #: #: Check that the current fstab as returned by :command:`mount -p` does not #: contain any spaces at improper -- and therefore unhandled -- locations. #: #: Returns: #: int: 0 (truish) if :command:`mount -p` contains no spaces, #: 1 (falsy) otherwise #: check_for_proper_fstab() { local _dev _mp _fstype _opts _dump _pass _rest /sbin/mount -p \ | { while IFS=$' \t' read -r _dev _mp _fstype _opts _dump _pass _rest; do if [ -n "${_rest}" ]; then return 1 fi # XXX TBD: Check that _dump and _pass are numbers proper done return 0 } } #: #: Clean the current process environment somewhat #: reset_environment() { unset PATH_FSTAB unset GREP_OPTIONS # XXX: should we do this # export LC_ALL=C } # Automatically reset the environment reset_environment
