rc.subr revision 1.142
1#	$OpenBSD: rc.subr,v 1.142 2021/11/07 08:31:24 ajacoutot Exp $
2#
3# Copyright (c) 2010, 2011, 2014-2021 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"
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 \
45		rc_reload_signal rc_stop_signal spamd_black
46
47	[ $# -gt 0 ] || set -- /etc/rc.conf /etc/rc.conf.local
48	for _rcfile; do
49		[[ -f $_rcfile ]] || continue
50		while IFS=' 	' read -r _l; do
51			[[ $_l == [!#=]*=* ]] || continue
52			_key=${_l%%*([[:blank:]])=*}
53			[[ $_key == *_@(flags|logger|rtable|timeout|user) ]] ||
54				[[ " ${_allowed_keys[*]} " == *" $_key "* ]] ||
55				continue
56			[[ $_key == "" ]] && continue
57			_val=${_l##*([!=])=*([[:blank:]])}
58			_val=${_val%%#*}
59			_val=${_val%%*([[:blank:]])}
60			# remove leading and trailing quotes (backwards compat)
61			[[ $_val == @(\"*\"|\'*\') ]] &&
62				_val=${_val#?} _val=${_val%?}
63			eval "${_key}=\${_val}"
64		done < $_rcfile
65	done
66
67	_rc_do _rc_quirks
68}
69
70_rc_quirks() {
71	# special care needed for spamlogd to avoid starting it up and failing
72	# all the time
73	if [ X"${spamd_flags}" = X"NO" -o X"${spamd_black}" != X"NO" ]; then
74		spamlogd_flags=NO
75	fi
76
77	# special care needed for pflogd to avoid starting it up and failing
78	# if pf is not enabled
79	if [ X"${pf}" = X"NO" ]; then
80		pflogd_flags=NO
81	fi
82
83	# special care needed if nfs_server=YES to startup nfsd and mountd with
84	# sane default flags
85	if [ X"${nfs_server}" = X"YES" ]; then
86		[ X"${nfsd_flags}" = X"NO" ] && nfsd_flags="-tun 4"
87		[ X"${mountd_flags}" = X"NO" ] && mountd_flags=
88	fi
89}
90
91# return if we only want internal functions
92[ -n "${FUNCS_ONLY}" ] && return
93
94_rc_not_supported() {
95	local _a _enotsup _what=${1}
96	for _a in ${_rc_actions}; do
97		[ "${_what}" == "restart" ] && _what="stop"
98		if [ "${_what}" == "${_a}" ]; then
99			eval _enotsup=\${rc_${_what}}
100			break
101		fi
102	done
103	[ X"${_enotsup}" == X"NO" ]
104}
105
106_rc_usage() {
107	local _a _allsup
108	for _a in ${_rc_actions}; do
109		_rc_not_supported ${_a} || _allsup="${_allsup:+$_allsup|}${_a}"
110	done
111	_rc_err "usage: $0 [-df] ${_allsup}"
112}
113
114_rc_write_runfile() {
115	[ -d ${_RC_RUNDIR} ] || mkdir -p ${_RC_RUNDIR} &&
116		cat >${_RC_RUNFILE} <<EOF
117daemon_class=${daemon_class}
118daemon_flags=${daemon_flags}
119daemon_logger=${daemon_logger}
120daemon_rtable=${daemon_rtable}
121daemon_timeout=${daemon_timeout}
122daemon_user=${daemon_user}
123pexp=${pexp}
124rc_reload_signal=${rc_reload_signal}
125rc_stop_signal=${rc_stop_signal}
126EOF
127}
128
129_rc_rm_runfile() {
130	rm -f ${_RC_RUNFILE}
131}
132
133_rc_exit() {
134	local _pfix
135	[ -z "${INRC}" -o X"$1" != X"ok" ] && _pfix="($1)"
136	echo ${INRC:+'-n'} "${_pfix}"
137	[ X"$1" = X"ok" ] && exit 0 || exit 1
138}
139
140_rc_alarm()
141{
142	trap - ALRM
143	kill -ALRM ${_TIMERSUB} 2>/dev/null # timer may not be running anymore
144	kill $! 2>/dev/null # kill last job if it's running
145}
146
147_rc_sendsig() {
148	pkill -${1:-TERM} -T "${daemon_rtable}" -xf "${pexp}" 
149}
150
151_rc_wait() {
152	if [ X"$1" = X"start" ]; then # prevent hanging the boot sequence
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	fi
163	while (( SECONDS < daemon_timeout )); do
164		case "$1" in
165		reload)
166			_rc_do rc_check && return 0 ;;
167		stop)
168			# last chance: send a SIGTERM first in case the process
169			# used another signal to stop (e.g. SIGQUIT with nginx)
170			# or a non-default rc_stop() function; do it 2s before
171			# timeout to re-enter the loop one last time which will
172			# give 1s for SIGTERM to terminate the process
173			((SECONDS == daemon_timeout-2)) &&
174				_rc_do _rc_sendsig TERM && sleep .5
175			_rc_do rc_check || return 0 ;;
176		*)
177			break ;;
178		esac
179		sleep .5
180	done
181
182	# KILL the process
183	[ X"$1" = X"stop" ] && _rc_do rc_check && _rc_do _rc_sendsig KILL
184
185	return 1
186}
187
188rc_start() {
189	${rcexec} "${daemon_logger:+set -o pipefail; }${daemon} ${daemon_flags}${daemon_logger:+ 2>&1 |
190		logger -ip ${daemon_logger} -t ${_name}}"
191}
192
193rc_check() {
194	pgrep -T "${daemon_rtable}" -q -xf "${pexp}"
195}
196
197rc_reload() {
198	_rc_sendsig ${rc_reload_signal}
199}
200
201rc_stop() {
202	_rc_sendsig ${rc_stop_signal}
203}
204
205rc_cmd() {
206	local _exit _n _ret
207
208	[ -n "${1}" ] && echo "${_rc_actions}" | grep -qw -- ${1} || _rc_usage
209
210	[ "$(id -u)" -eq 0 ] ||
211		[ X"${rc_usercheck}" != X"NO" -a X"$1" = "Xcheck" ] ||
212		_rc_err "$0: need root privileges"
213
214	if _rc_not_supported $1; then
215		[ -n "${INRC}" ] && exit 1
216		_rc_err "$0: $1 is not supported"
217	fi
218
219	[ -n "${_RC_DEBUG}" ] || _n="-n"
220
221	[[ ${1} == start ]] || _rc_do _rc_parse_conf ${_RC_RUNFILE}
222
223	case "$1" in
224	check)
225		echo $_n "${INRC:+ }${_name}"
226		_rc_do rc_check && _rc_exit ok
227		_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			if type rc_pre >/dev/null; then
237				_rc_do rc_pre || break
238			fi
239			_rc_do _rc_wait start & _TIMERSUB=$!
240			trap "_rc_alarm" ALRM
241			_rc_do rc_start; _ret=$?
242			kill -ALRM ${_TIMERSUB}
243			wait ${_TIMERSUB} 2>/dev/null # don't print Alarm clock
244			[[ "${_ret}" == 142 ]] && [ X"${rc_bg}" != X"YES" ] &&
245				_exit="timeout"
246			# XXX for unknown reason, rc_check can fail (e.g. redis)
247			# while it just succeeded in _rc_wait; the check is
248			# needed to cope with failing daemons returning 0
249			#[[ "${_ret}" == @(0|142) ]] && _rc_do rc_check || break
250			[[ "${_ret}" == @(0|142) ]] || break
251			_rc_do _rc_write_runfile
252			_rc_exit ${_exit:=ok}
253		done
254		# handle failure
255		type rc_post >/dev/null && _rc_do rc_post
256		_rc_do _rc_rm_runfile
257		_rc_exit failed
258		;;
259	stop)
260		_rc_do rc_check || exit 0
261		echo $_n "${INRC:+ }${_name}"
262		_rc_do rc_stop || _rc_exit failed
263		_rc_do _rc_wait stop || _exit=killed
264		if type rc_post >/dev/null; then
265			_rc_do rc_post || _exit=failed
266		fi
267		_rc_do _rc_rm_runfile
268		_rc_exit ${_exit:=ok}
269		;;
270	reload)
271		echo $_n "${INRC:+ }${_name}"
272		_rc_do rc_check && _rc_do rc_reload || _rc_exit failed
273		_rc_do _rc_wait reload || _rc_exit failed
274		_rc_exit ok
275		;;
276	restart)
277		$0 ${_RC_DEBUG} ${_RC_FORCE} stop &&
278			$0 ${_RC_DEBUG} ${_RC_FORCE} start
279		;;
280	*)
281		_rc_usage
282		;;
283	esac
284}
285
286_name=${0##*/}
287_rc_check_name "${_name}" || _rc_err "invalid rc.d script name: ${_name}"
288
289[ -n "${daemon}" ] || _rc_err "$0: daemon is not set"
290
291unset _RC_DEBUG _RC_FORCE
292while getopts "df" c; do
293	case "$c" in
294		d) _RC_DEBUG=-d;;
295		f) _RC_FORCE=-f;;
296		*) _rc_usage;;
297	esac
298done
299shift $((OPTIND-1))
300
301_RC_RUNDIR=/var/run/rc.d
302_RC_RUNFILE=${_RC_RUNDIR}/${_name}
303
304# parse /etc/rc.conf{.local} for the daemon variables
305_rc_do _rc_parse_conf
306
307rc_reload_signal=${rc_reload_signal:=HUP}
308rc_stop_signal=${rc_stop_signal:=TERM}
309
310eval _rcflags=\${${_name}_flags}
311eval _rclogger=\${${_name}_logger}
312eval _rcrtable=\${${_name}_rtable}
313eval _rctimeout=\${${_name}_timeout}
314eval _rcuser=\${${_name}_user}
315
316# set default values; duplicated in rcctl(8)
317getcap -f /etc/login.conf ${_name} 1>/dev/null 2>&1 && daemon_class=${_name} ||
318	daemon_class=daemon
319[ -z "${daemon_rtable}" ] && daemon_rtable=0
320[ -z "${daemon_timeout}" ] && daemon_timeout=30
321[ -z "${daemon_user}" ] && daemon_user=root
322
323# use flags from the rc.d script if daemon is not enabled
324[ -n "${_RC_FORCE}" -o "$1" != "start" ] && [ X"${_rcflags}" = X"NO" ] &&
325	unset _rcflags
326
327[ -n "${_rcflags}" ] && daemon_flags=${_rcflags}
328[ -n "${_rclogger}" ] && daemon_logger=${_rclogger}
329[ -n "${_rcrtable}" ] && daemon_rtable=${_rcrtable}
330[ -n "${_rctimeout}" ] && daemon_timeout=${_rctimeout}
331[ -n "${_rcuser}" ] && daemon_user=${_rcuser}
332
333if [ -n "${_RC_DEBUG}" ]; then
334	echo -n "${_name}_flags "
335	[ -n "${_rcflags}" ] || echo -n "empty, using default "
336	echo ">${daemon_flags}<"
337fi
338
339readonly daemon_class
340unset _rcflags _rclogger _rcrtable _rctimeout _rcuser
341# the shell will strip the quotes from daemon_flags when starting a daemon;
342# make sure pexp matches the process (i.e. doesn't include the quotes)
343pexp="$(eval echo ${daemon}${daemon_flags:+ ${daemon_flags}})"
344rcexec="su -fl -c ${daemon_class} -s /bin/sh ${daemon_user} -c"
345[ "${daemon_rtable}" -eq "$(id -R)" ] ||
346	rcexec="route -T ${daemon_rtable} exec ${rcexec}"
347