Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view share/local-bsdtools/common.subr @ 644:0c7917469e04
Put the check for opened files with "procstat" into a subroutine and use it
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Fri, 27 Sep 2024 17:23:01 +0200 |
| parents | de090ff199ff |
| children | aa21ec8b86c5 |
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@@ #: # # "procstat --libxo=json,no-locale -a file | jq -r $'.procstat.files | map(.) | .[] | [ .command, .files.[] | .fd, .fd_type, .path] | @tsv ' # # WORKING (without the command name) # procstat --libxo=json,no-locale -a file | jq -r $'.procstat.files | map(.)| .[] | .files.[] | [.fd, .fd_type, .path] | @tsv ' # # FULL WITH command name # procstat --libxo=json,no-locale -a file | jq -r $'.procstat.files | map(.) | .[] | .command as $cmd | .files[] | [ $cmd, .fd, .fd_type, .vode_type, .path ] | @tsv ' #: #: Dummy function to make the first "shellcheck" directive below non-global #: for this file. #: __dummy_for_shellcheck_must_be_first_function_in_common_subr() { : } # shellcheck disable=SC2223 # quote #: The path to the external jq executable (JSON parser) : ${JQ="/usr/local/bin/jq"} #: #: 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" 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 values are found. #: checkyesno() { local _value eval _value=\$\{"${1}"\} 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 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() { local _value eval _value=\$\{"${1}"\} case "${_value}" 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 } #: #: 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: 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. #: #: 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-}" [ -z "${_location}" ] && { echo "ERROR: no mountpoint given" 1>&2; return 1; } 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 echo "Jail \`${_name}' is currently dying" 1>&2 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 echo "Jail \`${_name}' is currently dying" 1>&2 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 and may not have a trailing slash ``/``. #: 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 "a trailing slash in directory path given" ;; /*) _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 } #: #: 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
