diff --git a/libexec/rc/Makefile b/libexec/rc/Makefile --- a/libexec/rc/Makefile +++ b/libexec/rc/Makefile @@ -18,7 +18,7 @@ CONFETCDEFAULTSPACKAGE= rc FILESGROUPS= LIBEXEC_SCRIPTS -LIBEXEC_SCRIPTS= debug.sh safe_eval.sh +LIBEXEC_SCRIPTS= debug.sh hooks.sh safe_eval.sh LIBEXEC_SCRIPTSDIR= /libexec LIBEXEC_SCRIPTSMODE= 755 LIBEXEC_SCRIPTSPACKAGE= rc diff --git a/libexec/rc/debug.sh b/libexec/rc/debug.sh --- a/libexec/rc/debug.sh +++ b/libexec/rc/debug.sh @@ -74,7 +74,7 @@ # Simon J. Gerraty # RCSid: -# $Id: debug.sh,v 1.35 2024/02/03 19:04:47 sjg Exp $ +# $Id: debug.sh,v 1.37 2024/09/05 20:00:01 sjg Exp $ # # @(#) Copyright (c) 1994-2024 Simon J. Gerraty # @@ -98,6 +98,18 @@ DEBUG_SKIP= export DEBUGGING DEBUG_DO DEBUG_SKIP +## +# _debugOn match first +# +# Actually turn on tracing, set $DEBUG_ON=$match +# +# If we have included hooks.sh $_HOOKS_SH will be set +# and if $first (the first arg to DebugOn) is suitable as a variable +# name we will run ${first}_debugOn_hooks. +# +# We disable tracing for hooks_run itself but functions can trace +# if they want based on DEBUG_DO +# _debugOn() { DEBUG_OFF= DEBUG_DO= @@ -105,11 +117,36 @@ DEBUG_X=-x set -x DEBUG_ON=$1 + case "$_HOOKS_SH,$2" in + ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; + *) # avoid noise from hooks_run + set +x + hooks_run ${2}_debugOn_hooks + set -x + ;; + esac } +## +# _debugOff match $DEBUG_ON $first +# +# Actually turn off tracing, set $DEBUG_OFF=$match +# +# If we have included hooks.sh $_HOOKS_SH will be set +# and if $first (the first arg to DebugOff) is suitable as a variable +# name we will run ${first}_debugOff_hooks. +# +# We do hooks_run after turning off tracing, but before resetting +# DEBUG_DO so functions can trace if they want +# _debugOff() { DEBUG_OFF=$1 set +x + case "$_HOOKS_SH,$3" in + ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; + *) hooks_run ${3}_debugOff_hooks;; + esac + set +x # just to be sure DEBUG_ON=$2 DEBUG_DO=: DEBUG_SKIP= @@ -120,16 +157,30 @@ $DEBUG_DO echo "$@" } +## +# Debugging +# +# return 0 if we are debugging. +# Debugging() { test "$DEBUG_SKIP" } +## +# DebugLog message +# +# Outout message with timestamp if we are debugging +# DebugLog() { $DEBUG_SKIP return 0 echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@" } -# something hard to miss when wading through huge -x output +## +# DebugTrace message +# +# Something hard to miss when wading through huge -x output +# DebugTrace() { $DEBUG_SKIP return 0 set +x @@ -139,8 +190,13 @@ set -x } -# Turn on debugging if appropriate +## +# DebugOn [-e] [-o] match ... +# +# Turn on debugging if any $match is found in $DEBUG_SH. +# DebugOn() { + eval ${local:-:} _e _match _off _rc _rc=0 # avoid problems with set -e _off=: while : @@ -170,14 +226,14 @@ *,!$_e,*|*,!$Myname:$_e,*) # only turn it off if it was on _rc=0 - $DEBUG_DO _debugOff $_e $DEBUG_ON + $DEBUG_DO _debugOff $_e $DEBUG_ON $1 break ;; *,$_e,*|*,$Myname:$_e,*) # only turn it on if it was off _rc=0 _match=$_e - $DEBUG_SKIP _debugOn $_e + $DEBUG_SKIP _debugOn $_e $1 break ;; esac @@ -185,7 +241,7 @@ if test -z "$_off$_match"; then # off unless explicit match, but # only turn it off if it was on - $DEBUG_DO _debugOff $_e $DEBUG_ON + $DEBUG_DO _debugOff $_e $DEBUG_ON $1 fi DEBUGGING=$DEBUG_SKIP # backwards compatability $DEBUG_DO set -x # back on if needed @@ -193,11 +249,20 @@ return $_rc } +## +# DebugOff [-e] [-o] [rc=$?] match ... +# # Only turn debugging off if one of our args was the reason it # was turned on. +# # We normally return 0, but caller can pass rc=$? as first arg # so that we preserve the status of last statement. +# +# The options '-e' and '-o' are ignored, they just make it easier to +# keep DebugOn and DebugOff lines in sync. +# DebugOff() { + eval ${local:-:} _e _rc case ",${DEBUG_SH:-$DEBUG}," in *,[Dd]ebug,*) ;; *) $DEBUG_DO set +x;; # reduce the noise @@ -216,7 +281,7 @@ : $_e==$DEBUG_OFF DEBUG_OFF case "$DEBUG_OFF" in "") break;; - $_e) _debugOn $DEBUG_ON; return $_rc;; + $_e) _debugOn $DEBUG_ON $1; return $_rc;; esac done for _e in $* @@ -224,7 +289,7 @@ : $_e==$DEBUG_ON DEBUG_ON case "$DEBUG_ON" in "") break;; - $_e) _debugOff; return $_rc;; + $_e) _debugOff "" "" $1; return $_rc;; esac done DEBUGGING=$DEBUG_SKIP # backwards compatability @@ -247,6 +312,7 @@ # Run an interactive shell if appropriate # Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn DebugShell() { + eval ${local:-:} _e case "$_TTY%${DEBUG_INTERACTIVE}" in *%|%*) return 0;; # no tty or no spec esac diff --git a/libexec/rc/hooks.sh b/libexec/rc/hooks.sh new file mode 100755 --- /dev/null +++ b/libexec/rc/hooks.sh @@ -0,0 +1,275 @@ +: +# SPDX-License-Identifier: BSD-2-Clause + +# NAME: +# hooks.sh - provide hooks for customization +# +# SYNOPSIS: +# hooks_add_all HOOKS [--first] func [...] +# hooks_add_once HOOKS [--first] func [...] +# hooks_add_default_set {all,once} +# hooks_add HOOKS func [...] +# hooks_get [--lifo] HOOKS +# hooks_run [--lifo] HOOKS ["args"] +# hooks_run_all [--lifo] HOOKS ["args"] +# hooks_has HOOKS func +# +# add_hooks HOOKS [--first] func [...] +# run_hooks HOOKS [LIFO] ["args"] +# run_hooks_all HOOKS [LIFO] ["args"] +# +# DESCRIPTION: +# The functions add_hooks and run_hooks are retained for +# backwards compatability. They are aliases for hooks_add and +# hooks_run. +# +# hooks_add_all simply adds the "func"s to the list "HOOKS". +# +# If the first arg is '--first' "func"s are added to the start +# of the list. +# +# hooks_add_once does the same but only if "func" is not in "HOOKS". +# hooks_add uses one of the above based on "option", '--all' (default) +# or '--once'. +# +# hooks_add_default_set sets the default behavior of hooks_add +# +# hooks_get simply returns the named list of functions. +# +# hooks_has indicates whether "func" in in "HOOKS". +# +# hooks_run runs each "func" in $HOOKS and stops if any of them +# return a bad status. +# +# hooks_run_all does the same but does not stop on error. +# +# If run_hooks or run_hooks_all is given a flag of '--lifo' or +# 2nd argument of LIFO the hooks are run in the reverse order of +# calls to hooks_add. +# Any "args" specified are passed to each hook function. +# + +# RCSid: +# $Id: hooks.sh,v 1.20 2024/09/05 22:17:44 sjg Exp $ +# +# @(#)Copyright (c) 2000-2024 Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. + +# avoid multiple inclusion +_HOOKS_SH=: + +# We want to use local if we can +# if isposix-shell.sh has been sourced isPOSIX_SHELL will be set +# as will local +case "$isPOSIX_SHELL" in +"") if (echo ${PATH%:*}) > /dev/null 2>&1; then + local=local + else + local=: + fi + ;; +esac + +## +# hooks_add_all list func ... +# +# add "func"s to "list" regardless +# +hooks_add_all() { + eval $local __h + __h=$1; shift + case "$1" in + --first) + shift + eval "$__h=\"$* \$$__h\"" + ;; + *) eval "$__h=\"\$$__h $*\"";; + esac +} + +## +# hooks_add_once list func ... +# +# add "func"s to "list" if not already there +# +hooks_add_once() { + eval $local __h __hh __first + __h=$1; shift + case "$1" in + --first) shift; __first=:;; + *) __first=;; + esac + eval "__hh=\$$__h" + while [ $# -gt 0 ] + do + : __hh="$__hh" 1="$1" + case "$__first $__hh " in + *" $1 "*) ;; # dupe + :*) __hh="$1 $__hh";; + *) __hh="$__hh $1";; + esac + shift + done + eval "$__h=\"$__hh\"" +} + +## +# hooks_add_default_set [--]{all,once} +# +# change the default method of hooks_add +# +hooks_add_default_set() { + case "$1" in + once|--once) HOOKS_ADD_DEFAULT=once;; + *) HOOKS_ADD_DEFAULT=all;; + esac +} + +## +# hooks_add [--{all,once}] list func ... +# +# add "func"s to "list" +# +# If '--once' use hooks_add_once, +# default is hooks_add_all. +# +hooks_add() { + case "$1" in + --all) shift; hooks_add_all "$@";; + --once) shift; hooks_add_once "$@";; + *) hooks_add_${HOOKS_ADD_DEFAULT:-all} "$@";; + esac +} + +## +# hooks_get [--lifo] list [LIFO] +# +# return $list +# +hooks_get() { + eval $local __h __h2 e __l + case "$1" in + --lifo) __l=LIFO; shift;; + esac + eval "__h=\$$1" + case "$__l$2" in + LIFO*) + __h2="$__h" + __h= + for e in $__h2 + do + __h="$e $__h" + done + ;; + esac + echo "$__h" +} + +## +# hooks_has list func +# +# is func in $list ? +# +hooks_has() { + eval $local __h + eval "__h=\$$1" + case " $__h " in + *" $1 "*) return 0;; + esac + return 1 +} + +## +# hooks_run [--all] [--lifo] list [LIFO] [args] +# +# pass "args" to each function in "list" +# Without '--all'; if any return non-zero return that immediately +# +hooks_run() { + eval $local __a e __h __hl __h2 __l + __a=return + __l= + + while : + do + case "$1" in + --all) __a=:; shift;; + --lifo) __l=$1; shift;; + *) break;; + esac + done + __hl=$1; shift + case "$1" in + LIFO) __l=--lifo; shift;; + esac + __h=`hooks_get $__l $__hl` + for e in $__h + do + $e "$@" || $__a $? + done +} + +## +# hooks_run_all [--lifo] list [LIFO] [args] +# +# pass "args" to each function in "list" +# +hooks_run_all() { + hooks_run --all "$@" +} + +## +# add_hooks,run_hooks[_all] aliases +# +add_hooks() { + hooks_add "$@" +} + +run_hooks() { + hooks_run "$@" +} + +run_hooks_all() { + hooks_run --all "$@" +} + + +case /$0 in +*/hooks.sh) + # simple unit-test + list=HOOKS + flags= + while : + do + : 1=$1 + case "$1" in + HOOKS|*hooks) list=$1; shift;; + --*) flags="$flags $1"; shift;; + *) break;; + esac + done + for f in "$@" + do + : f=$f + case "$f" in + LIFO) ;; + false|true) ;; + *) eval "$f() { echo This is $f; }";; + esac + done + echo hooks_add $flags $list "$@" + hooks_add $flags $list "$@" + echo hooks_run $list + hooks_run $list + echo hooks_run --all --lifo $list + hooks_run --all --lifo $list + echo hooks_run $list LIFO + hooks_run $list LIFO + ;; +esac