diff --git a/libexec/rc/Makefile b/libexec/rc/Makefile --- a/libexec/rc/Makefile +++ b/libexec/rc/Makefile @@ -18,6 +18,12 @@ CONFETCDEFAULTS= rc.conf CONFETCDEFAULTSPACKAGE= rc +FILESGROUPS= LIBEXEC_SCRIPTS +LIBEXEC_SCRIPTS= debug.sh safe_eval.sh +LIBEXEC_SCRIPTSDIR= /libexec +LIBEXEC_SCRIPTSMODE= 755 +LIBEXEC_SCRIPTSPACKAGE= rc + SUBDIR+= rc.d HAS_TESTS= diff --git a/libexec/rc/debug.sh b/libexec/rc/debug.sh new file mode 100755 --- /dev/null +++ b/libexec/rc/debug.sh @@ -0,0 +1,266 @@ +: +# NAME: +# debug.sh - debug scripts +# +# SYNOPSIS: +# $_DEBUG_SH . debug.sh +# DebugOn [-e][-o] "e" ... +# DebugOff "e" ... +# DebugShell "e" ... +# DebugEcho ... +# Debugging +# DebugLog ... +# DebugTrace ... +# Debug "e" ... +# +# $DEBUG_SKIP echo skipped when Debug "e" is true. +# $DEBUG_DO echo only done when Debug "e" is true. +# +# DESCRIPTION: +# DebugOn turns tracing on if any "e" is found in "DEBUG_SH". +# It turns tracing off if "!e" is found in "DEBUG_SH". +# It also sets "DEBUG_ON" to the "e" that caused tracing to be +# enabled, or "DEBUG_OFF" if we matched "!e". +# If '-e' option given returns 1 if no "e" matched. +# If the '-o' flag is given, tracing is turned off unless there +# was a matched "e", useful for functions too noisy to tace. +# +# DebugOff turns tracing on if any "e" matches "DEBUG_OFF" or +# off if any "e" matches "DEBUG_ON". This allows nested +# functions to not interfere with each other. +# +# DebugEcho is just shorthand for: +#.nf +# $DEBUG_DO echo "$@" +#.fi +# +# Debugging returns true if tracing is enabled. +# It is useful for bounding complex debug actions, rather than +# using lots of "DEBUG_DO" lines. +# +# DebugShell runs an interactive shell if any "e" is found in +# "DEBUG_INTERACTIVE", and there is a tty available. +# The shell used is defined by "DEBUG_SHELL" or "SHELL" and +# defaults to '/bin/sh'. +# +# Debug calls DebugOn and if that does not turn tracing on, it +# calls DebugOff to turn it off. +# +# The variables "DEBUG_SKIP" and "DEBUG_DO" are set so as to +# enable/disable code that should be skipped/run when debugging +# is turned on. "DEBUGGING" is the same as "DEBUG_SKIP" for +# backwards compatability and is only set by Debug. +# +# The use of $_DEBUG_SH is to prevent multiple inclusion, though +# it does no harm in this case. +# +# BUGS: +# Does not work with some versions of ksh. +# If a function turns tracing on, ksh turns it off when the +# function returns - useless. +# PD ksh works ok ;-) +# +# AUTHOR: +# Simon J. Gerraty + +# RCSid: +# $Id: debug.sh,v 1.33 2021/12/03 18:22:37 sjg Exp $ +# +# @(#) Copyright (c) 1994-2021 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. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +_DEBUG_SH=: + +Myname=${Myname:-`basename $0 .sh`} + +DEBUGGING= +DEBUG_DO=: +DEBUG_SKIP= +export DEBUGGING DEBUG_DO DEBUG_SKIP + +_debugOn() { + DEBUG_OFF= + DEBUG_DO= + DEBUG_SKIP=: + DEBUG_X=-x + set -x + DEBUG_ON=$1 +} + +_debugOff() { + DEBUG_OFF=$1 + set +x + DEBUG_ON=$2 + DEBUG_DO=: + DEBUG_SKIP= + DEBUG_X= +} + +DebugEcho() { + $DEBUG_DO echo "$@" +} + +Debugging() { + test "$DEBUG_SKIP" +} + +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() { + $DEBUG_SKIP return 0 + set +x + echo "@ ==================== [ $DEBUG_ON ] ====================" + DebugLog "$@" + echo "@ ==================== [ $DEBUG_ON ] ====================" + set -x +} + +# Turn on debugging if appropriate +DebugOn() { + _rc=0 # avoid problems with set -e + _off=: + while : + do + case "$1" in + -e) _rc=1; shift;; # caller ok with return 1 + -o) _off=; shift;; # off unless we have a match + *) break;; + esac + done + case ",${DEBUG_SH:-$DEBUG}," in + ,,) return $_rc;; + *,[Dd]ebug,*) ;; + *) $DEBUG_DO set +x;; # reduce the noise + esac + _match= + # if debugging is off because of a !e + # don't add 'all' to the On list. + case "$_off$DEBUG_OFF" in + :) _e=all;; + *) _e=;; + esac + for _e in ${*:-$Myname} $_e + do + : $_e in ,${DEBUG_SH:-$DEBUG}, + case ",${DEBUG_SH:-$DEBUG}," in + *,!$_e,*|*,!$Myname:$_e,*) + # only turn it off if it was on + _rc=0 + $DEBUG_DO _debugOff $_e $DEBUG_ON + break + ;; + *,$_e,*|*,$Myname:$_e,*) + # only turn it on if it was off + _rc=0 + _match=$_e + $DEBUG_SKIP _debugOn $_e + break + ;; + esac + done + 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 + fi + $DEBUG_DO set -x # back on if needed + $DEBUG_DO set -x # make sure we see it in trace + return $_rc +} + +# 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. +DebugOff() { + case ",${DEBUG_SH:-$DEBUG}," in + *,[Dd]ebug,*) ;; + *) $DEBUG_DO set +x;; # reduce the noise + esac + _rc=0 # always happy + while : + do + case "$1" in + -[eo]) shift;; # ignore it + rc=*) eval "_$1"; shift;; + *) break;; + esac + done + for _e in $* + do + : $_e==$DEBUG_OFF DEBUG_OFF + case "$DEBUG_OFF" in + "") break;; + $_e) _debugOn $DEBUG_ON; return $_rc;; + esac + done + for _e in $* + do + : $_e==$DEBUG_ON DEBUG_ON + case "$DEBUG_ON" in + "") break;; + $_e) _debugOff; return $_rc;; + esac + done + $DEBUG_DO set -x # back on if needed + $DEBUG_DO set -x # make sure we see it in trace + return $_rc +} + +_TTY=${_TTY:-`test -t 0 && tty`}; export _TTY + +# override this if you like +_debugShell() { + { + echo DebugShell "$@" + echo "Type 'exit' to continue..." + } > $_TTY + ${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1 +} + +# Run an interactive shell if appropriate +# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn +DebugShell() { + case "$_TTY%${DEBUG_INTERACTIVE}" in + *%|%*) return 0;; # no tty or no spec + esac + for _e in ${*:-$Myname} all + do + case ",${DEBUG_INTERACTIVE}," in + *,!$_e,*|*,!$Myname:$_e,*) + return 0 + ;; + *,$_e,*|*,$Myname:$_e,*) + # Provide clues as to why/where + _debugShell "$_e: $@" + return $? + ;; + esac + done + return 0 +} + +# For backwards compatability +Debug() { + case "${DEBUG_SH:-$DEBUG}" in + "") ;; + *) DEBUG_ON=${DEBUG_ON:-_Debug} + DebugOn -e $* || DebugOff $DEBUG_LAST + DEBUGGING=$DEBUG_SKIP + ;; + esac +} diff --git a/libexec/rc/rc b/libexec/rc/rc --- a/libexec/rc/rc +++ b/libexec/rc/rc @@ -66,8 +66,11 @@ # to minimize the number of files that are needed on a diskless system, # and to make the configuration file variables available to rc itself. # +# -o verify has no effect if mac_veriexec is not active +set -o verify . /etc/rc.subr -load_rc_config +set +o verify +load_rc_config $rc_config_xtra # If we receive a SIGALRM, re-source /etc/rc.conf; this allows rc.d # scripts to perform "boot-time configuration" including enabling and @@ -93,16 +96,7 @@ unset system_rc find_system_scripts files=`rcorder ${skip} ${skip_firstboot} ${system_rc} 2>/dev/null` - -_rc_elem_done=' ' -for _rc_elem in ${files}; do - run_rc_script ${_rc_elem} ${_boot} - _rc_elem_done="${_rc_elem_done}${_rc_elem} " - - case "$_rc_elem" in - */${early_late_divider}) break ;; - esac -done +run_rc_scripts --break ${early_late_divider} ${rc_early_flags} $files unset files local_rc system_rc @@ -122,13 +116,13 @@ find_system_scripts files=`rcorder ${skip} ${skip_firstboot} ${system_rc} ${local_rc} 2>/dev/null` -for _rc_elem in ${files}; do - case "$_rc_elem_done" in - *" $_rc_elem "*) continue ;; - esac +run_rc_scripts ${rc_late_flags} $files +unset files local_rc system_rc - run_rc_script ${_rc_elem} ${_boot} -done +# allow for more complicated setups +if have run_rc_scripts_final; then + run_rc_scripts_final +fi # Remove the firstboot sentinel, and reboot if it was requested. # Be a bit paranoid about removing it to handle the common failure diff --git a/libexec/rc/rc.subr b/libexec/rc/rc.subr --- a/libexec/rc/rc.subr +++ b/libexec/rc/rc.subr @@ -66,6 +66,121 @@ # functions # --------- +# _rc_verify file +# if VERIEXEC is active check that $file is verified +# +VERIEXEC="/sbin/veriexec" +if test -x $VERIEXEC && $VERIEXEC -i active > /dev/null 2>&1; then + is_verified() { $VERIEXEC -x $1; } +else + is_verified() { return 0; } +fi + +# indicate that we have vdot +_VDOT_SH=: + +# current state of O_VERIFY +o_verify() +{ + set -o | sed -n '/^verify/s,.*[[:space:]],,p' +} + +## +# o_verify_set want [save] +# +# record current state of verify in $save +# and set it to $want if different +# +o_verify_set() { + local x=`o_verify` + + test -z "$x" && return 0 + test -z "$2" || eval $2=$x + test "$x" = "$1" && return 0 + case "$1" in + on) set -o verify;; + off) set +o verify;; + esac +} + +# for unverified files +dotted= +dot() +{ + local f verify + + o_verify_set off verify + for f in "$@" + do + if test -f $f -a -s $f; then + dotted="$dotted $f" + . $f + fi + done + o_verify_set $verify +} + +# try for verified, fallback to safe +sdot() +{ + local f + + for f in "$@" + do + test -f $f -a -s $f || continue + vdot $f || safe_dot $f + done +} + +# convenience function - skip if not verified +vdot() +{ + local f rc=0 verify + + o_verify_set on verify + for f in "$@" + do + test -f $f -a -s $f || continue + if is_verified $f 2> /dev/null; then + dotted="$dotted $f" + . $f + else + rc=80 # EAUTH + fi + done + o_verify_set $verify + return $rc +} + +# do we have $1 (could be a function) +have() +{ + type "$1" > /dev/null 2>&1 +} + +# provide consistent means of logging progress +rc_log() +{ + date "+@ %s [%Y-%m-%d %H:%M:%S %Z] $*" +} + +# only rc_log if tracing enabled +# and $level >= $RC_LEVEL +rc_trace() +{ + local level=$1; shift + local cf=/etc/rc.conf.d/rc_trace + + if test -z "$RC_LEVEL"; then + test -f $cf || return + test -s $cf && + RC_LEVEL=`sed -n '/^RC_LEVEL=/ { s,.*=,,p;q; }' $cf` + RC_LEVEL=${RC_LEVEL:-0} + fi + test ${RC_LEVEL:-0} -ge ${level:-0} || return + rc_log "$@" +} + # list_vars pattern # List variables matching glob pattern. # @@ -924,6 +1039,8 @@ err 3 'run_rc_command: $name is not set.' fi + DebugOn rc:$name rc:$name:$rc_arg $name:$rc_arg + # Don't repeat the first argument when passing additional command- # line arguments to the command subroutines. # @@ -1077,6 +1194,7 @@ _postcmd=\$${rc_arg}_postcmd if [ -n "$_cmd" ]; then + rc_trace 1 "$_cmd" if [ -n "$_env" ]; then eval "export -- $_env" fi @@ -1449,6 +1567,14 @@ required_vars eval unset ${_arg}_cmd ${_arg}_precmd ${_arg}_postcmd + rc_trace 0 "$_file $_arg" + case "$_file" in + *local*.d/*) ;; # allow it ? + *) # don't use it if we don't trust it + is_verified $_file || return + ;; + esac + rc_service="$_file" case "$_file" in /etc/rc.d/*.sh) # no longer allowed in the base @@ -1459,6 +1585,8 @@ ;; *) # run in subshell if [ -x $_file ]; then + DebugOn $_file $_file:$_arg rc:${_file##*/} rc:${_file##*/}:$_arg ${_file##*/} ${_file##*/}:$_arg + if [ -n "$rc_boottrace" ]; then boottrace_fn "$_file" "$_arg" elif [ -n "$rc_fast_and_loose" ]; then @@ -1469,11 +1597,55 @@ trap "echo Script $_file running >&2" 29 set $_arg; . $_file ) fi + DebugOff $_file $_file:$_arg rc:${_file##*/} rc:${_file##*/}:$_arg ${_file##*/} ${_file##*/}:$_arg fi ;; esac } +# +# run_rc_scripts [options] file [...] +# +# Call `run_rc_script' for each "file" unless already listed in +# $_rc_elem_done. +# +# Options: +# +# --arg "arg" +# Pass "arg" to `run_rc_script' default is $_boot. +# +# --break "marker" +# If any "file" matches "marker" stop processing. +# +_rc_elem_done= +run_rc_scripts() +{ + local _arg=${_boot} + local _rc_elem + local _rc_breaks= + + while : + do + case "$1" in + --arg) _arg="$2"; shift 2;; + --break) _rc_breaks="$_rc_breaks $2"; shift 2;; + *) break;; + esac + done + for _rc_elem in "$@" + do + : _rc_elem=$_rc_elem + case " $_rc_elem_done " in + *" $_rc_elem "*) continue;; + esac + run_rc_script ${_rc_elem} ${_arg} + _rc_elem_done="$_rc_elem_done $_rc_elem" + case " $_rc_breaks " in + *" ${_rc_elem##*/} "*) break;; + esac + done +} + boottrace_fn() { local _file _arg @@ -1502,19 +1674,27 @@ # load_rc_config() { - local _name _rcvar_val _var _defval _v _msg _new _d + local _name _rcvar_val _var _defval _v _msg _new _d _dot _name=$1 + _dot=dot + + case "$1" in + -s) _dot=sdot _name=$2; shift;; + -v) _dot=vdot _name=$2; shift;; + esac + + DebugOn rc:$_name $_name if ${_rc_conf_loaded:-false}; then : else if [ -r /etc/defaults/rc.conf ]; then debug "Sourcing /etc/defaults/rc.conf" - . /etc/defaults/rc.conf + $_dot /etc/defaults/rc.conf source_rc_confs elif [ -r /etc/rc.conf ]; then debug "Sourcing /etc/rc.conf (/etc/defaults/rc.conf doesn't exist)." - . /etc/rc.conf + $_dot /etc/rc.conf fi _rc_conf_loaded=true fi @@ -1526,13 +1706,13 @@ _d=${_d%/rc.d} if [ -f ${_d}/rc.conf.d/"$_name" ]; then debug "Sourcing ${_d}/rc.conf.d/$_name" - . ${_d}/rc.conf.d/"$_name" + $_dot ${_d}/rc.conf.d/"$_name" elif [ -d ${_d}/rc.conf.d/"$_name" ] ; then local _rc for _rc in ${_d}/rc.conf.d/"$_name"/* ; do if [ -f "$_rc" ] ; then debug "Sourcing $_rc" - . "$_rc" + $_dot "$_rc" fi done fi @@ -2286,3 +2466,21 @@ if [ -n "$boottrace_cmd" ] && [ "`${SYSCTL_N} -q kern.boottrace.enabled`" = "1" ]; then rc_boottrace=YES fi + +# allow for local additions and overrides for rc.subr +vdot /etc/rc.subr.local + +# safe_dot - for unverified files +$_SAFE_EVAL_SH vdot /libexec/safe_eval.sh +$_DEBUG_SH vdot /libexec/debug.sh + +if have DebugOn; then + # allow DEBUG_SH to be set from loader prompt + DEBUG_SH=${DEBUG_SH:-`kenv -q DEBUG_SH`} +else + DebugOn() { return 0; } + DebugOff() { return 0; } +fi +if ! have save_dot; then + safe_dot() { dot "$@"; } +fi diff --git a/libexec/rc/safe_eval.sh b/libexec/rc/safe_eval.sh new file mode 100644 --- /dev/null +++ b/libexec/rc/safe_eval.sh @@ -0,0 +1,64 @@ +# RCSid: +# $Id: safe_eval.sh,v 1.12 2023/10/12 18:46:53 sjg Exp $ +# +# @(#) Copyright (c) 2023 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. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net + +_SAFE_EVAL_SH=: + +## +# safe_set +# +# return a safe variable setting +# any non-alphanumeric chars are replaced with '_' +# +safe_set() { + sed 's/[ ]*#.*//;/^[A-Za-z_][A-Za-z0-9_]*=/!d;s;[^A-Za-z0-9_. "$,/=-];_;g' +} + +## +# safe_eval [file] +# +# eval variable assignments only from file +# taking care to eliminate any shell meta chars +# +safe_eval() { + eval `cat "$@" | safe_set` +} + +## +# safe_dot file [...] +# +# feed all "file" that exist to safe_eval +# +safe_dot() { + local ef= f + + for f in "$@" + do + test -s $f || continue + ef="${ef:+$ef }$f" + dotted="$dotted $f" + done + test -z "$ef" && return 1 + safe_eval $ef + return 0 +} + +case /$0 in +*/safe_eval*) + case "$1" in + dot|eval|set) op=safe_$1; shift; $op "$@";; + *) safe_dot "$@";; + esac + ;; +esac