#!/bin/sh # Shebang line included so editors and shellcheck/shfmt know this contains # shell code # Common helper functions for NUT server initscript # In recent (relevant) versions of shellcheck busybox is a valid shell type # shellcheck shell=busybox # Pre-requisite sourcing for functions this script uses # * /lib/functions.sh has been sourced # We use explicit return 0 at the end of functions throughout, except where they # would be dead code, for clarity # Preserve current umask in CUR_UMASK, unless a umask was previously captured CUR_UMASK="${CUR_UMASK:-$(umask)}" restore_umask() { umask "$CUR_UMASK" } restore_umask_on_exit() { # Restore umask on exit # shellcheck disable=SC3047 trap restore_umask EXIT } # Check string to contain only characters allowed in an UCI section name check_safe_uci_name() { local val="$1" [ -n "$val" ] || return 1 case "$val" in *[!a-zA-Z0-9_-]*) return 1 ;; esac return 0 } # Check string to contain only characters valid in a RFC822 Date header check_safe_date() { local in_date="$1" [ -n "$in_date" ] || return 1 # Some AI code reviewers get confused by \(\) and \space in the case # pattern, but this is correct for busybox ash in modern OpenWrt # and does not allow \ through the validation. # We want to allow space, comma, and hyphen as they can all appear in an # RFC822 Date: header case "$in_date" in *[!a-zA-Z0-9_:+\(\),\ -]*) return 1 ;; esac return 0 } # Check if input is a valid user or group name check_valid_user_group_name() { local in_name="$1" case "$in_name" in [a-zA-Z_][a-zA-Z0-9_-]*) ;; # Includes the case of an empty string *) return 1 ;; esac return 0 } # shellcheck disable=SC2329,SC2317 check_valid_hex_number() { local number="$1" local max_length="$2" [ -n "$max_length" ] || max_length=4 case "$number" in '') # Empty string is not valid return 1 ;; 0x*) case "$number" in 0x[!0-9a-fA-F]*) return 1 ;; 0x[0-9a-fA-F]*) # Minimum of 1 hex digit # Has a max of 4 hex digits (1-max_length hex digits allowed) and min of 1 if [ "${#number}" -gt "$((max_length + 2))" ] || [ "${#number}" -lt 1 ]; then return 1 fi return 0 ;; *) # Redundant, but add clarity for reviewers, especially automated # ones that can get confused without it. return 1 ;; esac ;; *[!0-9a-fA-F]*) return 1 ;; *) # Has a maximum of 4 hex digits (1-max_length hex digits allowed) if [ "${#number}" -gt "$max_length" ]; then return 1 fi return 0 ;; esac # We do not return 0 here as it is unnecessary (all case branches, including # catch-all have a return) and shellcheck will complain if we do. } # Wrap logging, in case we decide to update the logging, everywhere log_msg() { local reason="$1" local current_script="$2" local syslog_id="$3" local level="${4:-notice}" local facility="${5:-daemon}" logger -s -t "$syslog_id" -p "${facility}.${level}" "'$reason' in '$current_script'" || { # We use 'tr' as changing case via variable parameter substitution is not available in ash on OpenWrt # shellcheck disable=SC2018,SC2019 level="$(echo "$level" | tr 'a-z' 'A-Z')" printf "'%s': Logging failed during $level: '%s' in '%s'\n" "$syslog_id" "$reason" "$current_script" >&2 } case "$level" in crit) exit 1 ;; err | error | warn) return 1 ;; *) return 0 ;; esac } log_source_error() { local sourced_script="$1" local current_script="$2" local syslog_id="$3" log_msg "Unable to source '$sourced_script'" "$current_script" "$syslog_id" "crit" } log_config_load_error() { local config="$1" local current_script="$2" local syslog_id="$3" log_msg "Unable to load configuration '$config'" "$current_script" "$syslog_id" "crit" } # Wrap logging of errors, in case we decide to update the logging, everywhere # we do error logging with script exit log_error_exit() { local reason="$1" local current_script="$2" local syslog_id="$3" log_msg "$reason" "$current_script" "$syslog_id" "crit" } # Wrap logging of errors, in case we decide to update the logging, everywhere # we do error logging without hard exit log_error() { local reason="$1" local current_script="$2" local syslog_id="$3" log_msg "$reason" "$current_script" "$syslog_id" "error" } note_section_of_type() { have_expected_section="true" } have_section_of_type() { local section_type="$1" # 'pseudo-global' to capture result from config_foreach local have_expected_section="false" config_foreach note_section_of_type "$section_type" if [ "$have_expected_section" = "true" ]; then return 0 fi return 1 }