1# $OpenBSD: rc.subr,v 1.162 2024/01/17 08:26:06 ajacoutot Exp $ 2# 3# Copyright (c) 2010, 2011, 2014-2022 Antoine Jacoutot <ajacoutot@openbsd.org> 4# Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> 5# Copyright (c) 2010, 2011, 2014 Robert Nagy <robert@openbsd.org> 6# 7# Permission to use, copy, modify, and distribute this software for any 8# purpose with or without fee is hereby granted, provided that the above 9# copyright notice and this permission notice appear in all copies. 10# 11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 19_rc_actions="start stop restart reload check configtest" 20readonly _rc_actions 21 22_rc_check_name() { 23 [[ $1 == +([_[:alpha:]])+(|[_[:alnum:]]) ]] 24} 25 26_rc_do() { 27 if [ -n "${_RC_DEBUG}" ]; then 28 echo "doing $@" && "$@" 29 else 30 "$@" >/dev/null 2>&1 31 fi 32} 33 34_rc_err() { 35 [ -n "${1}" ] && echo "${1}" 1>&2 36 [ -n "${2}" ] && exit "${2}" || exit 1 37} 38 39_rc_parse_conf() { 40 typeset -l _key 41 local _l _rcfile _val 42 set -A _allowed_keys -- \ 43 accounting amd_master check_quotas ipsec library_aslr \ 44 multicast nfs_server pexp pf pkg_scripts shlib_dirs spamd_black 45 46 [ $# -gt 0 ] || set -- /etc/rc.conf /etc/rc.conf.local 47 for _rcfile; do 48 [[ -f $_rcfile ]] || continue 49 while IFS=' ' read -r _l; do 50 [[ $_l == [!#=]*=* ]] || continue 51 _key=${_l%%*([[:blank:]])=*} 52 [[ $_key == *_@(execdir|flags|logger|rtable|timeout|user) ]] || 53 [[ " ${_allowed_keys[*]} " == *" $_key "* ]] || 54 continue 55 [[ $_key == "" ]] && continue 56 _val=${_l##*([!=])=*([[:blank:]])} 57 _val=${_val%%#*} 58 _val=${_val%%*([[:blank:]])} 59 # remove leading and trailing quotes (backwards compat) 60 [[ $_val == @(\"*\"|\'*\') ]] && 61 _val=${_val#?} _val=${_val%?} 62 eval "${_key}=\${_val}" 63 done < $_rcfile 64 done 65 66 # special care needed for spamlogd to avoid starting it up and failing 67 # all the time 68 if [ X"${spamd_flags}" = X"NO" -o X"${spamd_black}" != X"NO" ]; then 69 spamlogd_flags=NO 70 fi 71 72 # special care needed for pflogd to avoid starting it up and failing 73 # if pf is not enabled 74 if [ X"${pf}" = X"NO" ]; then 75 pflogd_flags=NO 76 fi 77 78 # special care needed if nfs_server=YES to startup nfsd and mountd with 79 # sane default flags 80 if [ X"${nfs_server}" = X"YES" ]; then 81 [ X"${nfsd_flags}" = X"NO" ] && nfsd_flags="-tun 4" 82 [ X"${mountd_flags}" = X"NO" ] && mountd_flags= 83 fi 84} 85 86# return if we only want internal functions 87[ -n "${FUNCS_ONLY}" ] && return 88 89_rc_not_supported() { 90 local _a _enotsup _what=${1} 91 for _a in ${_rc_actions}; do 92 [ "${_what}" == "configtest" ] && 93 ! typeset -f rc_configtest >/dev/null && _enotsup=NO && 94 break 95 [ "${_what}" == "restart" ] && _what="stop" 96 if [ "${_what}" == "${_a}" ]; then 97 eval _enotsup=\${rc_${_what}} 98 break 99 fi 100 done 101 [ X"${_enotsup}" == X"NO" ] 102} 103 104_rc_usage() { 105 local _a _allsup 106 for _a in ${_rc_actions}; do 107 _rc_not_supported ${_a} || _allsup="${_allsup:+$_allsup|}${_a}" 108 done 109 _rc_err "usage: $0 [-df] ${_allsup}" 110} 111 112_rc_write_runfile() { 113 [ -d ${_RC_RUNDIR} ] || mkdir -p ${_RC_RUNDIR} && 114 cat >${_RC_RUNFILE} <<EOF 115daemon_class=${daemon_class} 116daemon_execdir=${daemon_execdir} 117daemon_flags=${daemon_flags} 118daemon_logger=${daemon_logger} 119daemon_rtable=${daemon_rtable} 120daemon_timeout=${daemon_timeout} 121daemon_user=${daemon_user} 122pexp=${pexp} 123rc_reload=${rc_reload} 124rc_reload_signal=${rc_reload_signal} 125rc_stop_signal=${rc_stop_signal} 126rc_usercheck=${rc_usercheck} 127EOF 128} 129 130_rc_rm_runfile() { 131 rm -f ${_RC_RUNFILE} 132} 133 134_rc_exit() { 135 local _pfix 136 [ -z "${INRC}" -o X"$1" != X"ok" ] && _pfix="($1)" 137 echo ${INRC:+'-n'} "${_pfix}" 138 [[ $1 == @(ok|killed) ]] && exit 0 || exit 1 139} 140 141_rc_alarm() 142{ 143 trap - ALRM 144 kill -ALRM ${_TIMERSUB} 2>/dev/null # timer may not be running anymore 145 kill $! 2>/dev/null # kill last job if it's running 146} 147 148_rc_sendsig() { 149 pkill -${1:-TERM} -T "${daemon_rtable}" -xf "${pexp}" 150} 151 152_rc_wait_for_start() { 153 trap "_rc_alarm" ALRM 154 while ((SECONDS < daemon_timeout)); do 155 if _rc_do rc_check; then 156 [ X"${rc_bg}" = X"YES" ] || [ -z "$$" ] && break 157 fi 158 sleep .5 159 done & wait 160 pkill -ALRM -P $$ 161 return 162} 163 164rc_exec() { 165 local _rcexec="su -fl -c ${daemon_class} -s /bin/sh ${daemon_user} -c" 166 [ "${daemon_rtable}" -eq "$(id -R)" ] || 167 _rcexec="route -T ${daemon_rtable} exec ${_rcexec}" 168 169 local _set_monitor=":" 170 # Run non-daemons services in a different process group to avoid SIGHUP 171 # at boot. 172 if [ X"${rc_bg}" = X"YES" ]; then 173 _set_monitor="set -o monitor" 174 fi 175 176 ${_rcexec} "${_set_monitor}; \ 177 ${daemon_logger:+set -o pipefail; } \ 178 ${daemon_execdir:+cd ${daemon_execdir} && } \ 179 $@ \ 180 ${daemon_logger:+ 2>&1 | 181 logger -isp ${daemon_logger} -t ${_name}}" 182} 183 184rc_start() { 185 rc_exec "${daemon} ${daemon_flags}" 186} 187 188rc_check() { 189 pgrep -T "${daemon_rtable}" -q -xf "${pexp}" 190} 191 192rc_reload() { 193 _rc_sendsig ${rc_reload_signal} 194} 195 196rc_stop() { 197 _rc_sendsig ${rc_stop_signal} 198} 199 200rc_cmd() { 201 local _exit _n _ret _timer 202 # optim: don't sleep(1) in the first loop 203 _1stloop=true 204 205 [ -n "${1}" ] && echo "${_rc_actions}" | grep -qw -- ${1} || _rc_usage 206 207 [ "$(id -u)" -eq 0 ] || 208 [ X"${rc_usercheck}" != X"NO" -a X"$1" = "Xcheck" ] || 209 _rc_err "$0: need root privileges" 210 211 if _rc_not_supported $1; then 212 [ -n "${INRC}" ] && exit 1 213 _rc_err "$0: $1 is not supported" 214 fi 215 216 [ -n "${_RC_DEBUG}" ] || _n="-n" 217 218 [[ ${1} == start ]] || _rc_do _rc_parse_conf ${_RC_RUNFILE} 219 220 case "$1" in 221 check) 222 echo $_n "${INRC:+ }${_name}" 223 _rc_do rc_check && _rc_exit ok || _rc_exit failed 224 ;; 225 configtest) 226 echo $_n "${INRC:+ }${_name}" 227 _rc_do rc_configtest && _rc_exit ok || _rc_exit failed 228 ;; 229 start) 230 if [ X"${daemon_flags}" = X"NO" ]; then 231 _rc_err "$0: need -f to force $1 since ${_name}_flags=NO" 232 fi 233 [ -z "${INRC}" ] && _rc_do rc_check && exit 0 234 echo $_n "${INRC:+ }${_name}" 235 while true; do # no real loop, only needed to break 236 # running during start is mostly useful for daemons 237 # whose child will not return a config parsing error to 238 # the parent during startup; e.g. bgpd, httpd... 239 if typeset -f rc_configtest >/dev/null; then 240 _rc_do rc_configtest || break 241 fi 242 if typeset -f rc_pre >/dev/null; then 243 _rc_do rc_pre || break 244 fi 245 # prevent hanging the boot sequence 246 _rc_do _rc_wait_for_start & _TIMERSUB=$! 247 trap "_rc_alarm" ALRM 248 _rc_do rc_start; _ret=$? 249 kill -ALRM ${_TIMERSUB} 250 wait ${_TIMERSUB} 2>/dev/null # don't print Alarm clock 251 # XXX for unknown reason, rc_check can fail (e.g. redis) 252 # while it just succeeded in _rc_wait_for_start; 253 # check to cope with failing daemons returning 0 254 #[[ "${_ret}" == @(0|142) ]] && _rc_do rc_check || break 255 [[ "${_ret}" == @(0|142) ]] || break 256 [[ "${_ret}" == 142 ]] && [ X"${rc_bg}" != X"YES" ] && 257 _exit="timeout" 258 _rc_do _rc_write_runfile 259 _rc_exit ${_exit:=ok} 260 done 261 # handle failure 262 _rc_do _rc_rm_runfile 263 typeset -f rc_post >/dev/null && _rc_do rc_post 264 _rc_exit failed 265 ;; 266 stop) 267 _rc_do rc_check || exit 0 268 echo $_n "${INRC:+ }${_name}" 269 _rc_do rc_stop & _timer=$! 270 while ((SECONDS < daemon_timeout)); do 271 # last chance: send a SIGTERM first in case the process 272 # used another signal to stop (e.g. SIGQUIT with nginx) 273 # or a non-default rc_stop() function; do it 2s before 274 # timeout to re-enter the loop one last time which will 275 # give 1s for SIGTERM to terminate the process 276 ((SECONDS == daemon_timeout-2)) && 277 _rc_do _rc_sendsig TERM && sleep .5 278 pkill -0 -P "$$" 2>/dev/null || _rc_do rc_check || 279 break 280 ${_1stloop} && _1stloop=false || sleep .5 281 done 282 kill -ALRM ${_timer} 2>/dev/null 283 wait ${_timer} # don't print Alarm clock 284 [[ $? == 0 ]] || _exit=failed 285 # KILL the process 286 _rc_do rc_check && _rc_do _rc_sendsig KILL && _exit="killed" 287 _rc_do _rc_rm_runfile 288 if typeset -f rc_post >/dev/null; then 289 _rc_do rc_post || _exit=failed 290 fi 291 _rc_exit ${_exit:=ok} 292 ;; 293 reload) 294 echo $_n "${INRC:+ }${_name}" 295 _rc_do rc_check || _rc_exit failed 296 if typeset -f rc_configtest >/dev/null; then 297 _rc_do rc_configtest || _rc_exit failed 298 fi 299 _rc_do rc_reload & _timer=$! 300 while ((SECONDS < daemon_timeout)); do 301 pkill -0 -P "$$" 2>/dev/null || break 302 ${_1stloop} && _1stloop=false || sleep .5 303 done 304 kill -ALRM ${_timer} 2>/dev/null 305 wait ${_timer} # don't print Alarm clock 306 _ret=$? 307 [[ ${_ret} == 142 ]] && _exit=timeout || [[ ${_ret} == 0 ]] || 308 _exit=failed 309 _rc_exit ${_exit:=ok} 310 ;; 311 restart) 312 if typeset -f rc_configtest >/dev/null; then 313 _rc_do rc_configtest || _rc_exit failed 314 fi 315 $0 ${_RC_DEBUG} ${_RC_FORCE} stop && 316 $0 ${_RC_DEBUG} ${_RC_FORCE} start 317 ;; 318 *) 319 _rc_usage 320 ;; 321 esac 322} 323 324_name=${0##*/} 325_rc_check_name "${_name}" || _rc_err "invalid rc.d script name: ${_name}" 326 327[ -n "${KSH_VERSION}" ] || _rc_err "$0: wrong shell, use /bin/ksh" 328[ -n "${daemon}" ] || _rc_err "$0: daemon is not set" 329 330unset _RC_DEBUG _RC_FORCE 331while getopts "df" c; do 332 case "$c" in 333 d) _RC_DEBUG=-d;; 334 f) _RC_FORCE=-f;; 335 *) _rc_usage;; 336 esac 337done 338shift $((OPTIND-1)) 339 340_RC_RUNDIR=/var/run/rc.d 341_RC_RUNFILE=${_RC_RUNDIR}/${_name} 342 343# parse /etc/rc.conf{.local} for the daemon variables 344_rc_do _rc_parse_conf 345 346rc_reload_signal=${rc_reload_signal:=HUP} 347rc_stop_signal=${rc_stop_signal:=TERM} 348 349eval _rcexecdir=\${${_name}_execdir} 350eval _rcflags=\${${_name}_flags} 351eval _rclogger=\${${_name}_logger} 352eval _rcrtable=\${${_name}_rtable} 353eval _rctimeout=\${${_name}_timeout} 354eval _rcuser=\${${_name}_user} 355 356# set default values; duplicated in rcctl(8) 357getcap -f /etc/login.conf.d/${_name}:/etc/login.conf ${_name} 1>/dev/null 2>&1 && \ 358 daemon_class=${_name} || daemon_class=daemon 359[ -z "${daemon_rtable}" ] && daemon_rtable=0 360[ -z "${daemon_timeout}" ] && daemon_timeout=30 361[ -z "${daemon_user}" ] && daemon_user=root 362 363# use flags from the rc.d script if daemon is not enabled 364[ -n "${_RC_FORCE}" -o "$1" != "start" ] && [ X"${_rcflags}" = X"NO" ] && 365 unset _rcflags 366 367[ -n "${_rcexecdir}" ] && daemon_execdir=${_rcexecdir} 368[ -n "${_rcflags}" ] && daemon_flags=${_rcflags} 369[ -n "${_rclogger}" ] && daemon_logger=${_rclogger} 370[ -n "${_rcrtable}" ] && daemon_rtable=${_rcrtable} 371[ -n "${_rctimeout}" ] && daemon_timeout=${_rctimeout} 372[ -n "${_rcuser}" ] && daemon_user=${_rcuser} 373 374if [ -n "${_RC_DEBUG}" ]; then 375 echo -n "${_name}_flags " 376 [ -n "${_rcflags}" ] || echo -n "empty, using default " 377 echo ">${daemon_flags}<" 378fi 379 380readonly daemon_class 381unset _rcexecdir _rcflags _rclogger _rcrtable _rctimeout _rcuser 382# the shell will strip the quotes from daemon_flags when starting a daemon; 383# make sure pexp matches the process (i.e. doesn't include the quotes) 384pexp="$(eval echo ${daemon}${daemon_flags:+ ${daemon_flags}})" 385