xref: /openbsd-src/usr.sbin/rcctl/rcctl.sh (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1#!/bin/ksh
2#
3# $OpenBSD: rcctl.sh,v 1.105 2016/09/07 13:13:13 ajacoutot Exp $
4#
5# Copyright (c) 2014, 2015 Antoine Jacoutot <ajacoutot@openbsd.org>
6# Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
7#
8# Permission to use, copy, modify, and distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20_special_svcs="accounting check_quotas ipsec multicast pf spamd_black"
21readonly _special_svcs
22
23# get local functions from rc.subr(8)
24FUNCS_ONLY=1
25. /etc/rc.d/rc.subr
26_rc_parse_conf
27
28usage()
29{
30	local _a _i
31	for _i in ${_rc_actions}; do _a="$(echo -n ${_i}${_a:+|${_a}})"; done
32
33	_rc_err \
34	"usage:	rcctl get|getdef|set service | daemon [variable [arguments]]
35	rcctl [-df] ${_a} daemon ...
36	rcctl disable|enable|order [daemon ...]
37	rcctl ls all|failed|off|on|started|stopped"
38}
39
40needs_root()
41{
42	[ "$(id -u)" -ne 0 ] && _rc_err "${0##*/}: \"$*\" needs root privileges"
43}
44
45rcctl_err()
46{
47	_rc_err "${0##*/}: ${1}" ${2}
48}
49
50ls_rcscripts()
51{
52	local _s
53
54	cd /etc/rc.d && set -- *
55	for _s; do
56		[[ ${_s} = *.* ]] && continue
57		[ ! -d "${_s}" ] && echo "${_s}"
58	done
59}
60
61pkg_scripts_append()
62{
63	local _svc=$1
64	[ -n "${_svc}" ] || return
65
66	rcconf_edit_begin
67	if [ -z "${pkg_scripts}" ]; then
68		echo pkg_scripts="${_svc}" >>${_TMP_RCCONF}
69	elif ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then
70		grep -v "^pkg_scripts.*=" /etc/rc.conf.local >${_TMP_RCCONF}
71		echo pkg_scripts="${pkg_scripts} ${_svc}" >>${_TMP_RCCONF}
72	fi
73	rcconf_edit_end
74}
75
76pkg_scripts_order()
77{
78	local _svcs="$*"
79	[ -n "${_svcs}" ] || return
80
81	needs_root ${action}
82	local _pkg_scripts _svc
83	for _svc in ${_svcs}; do
84		if svc_is_base ${_svc} || svc_is_special ${_svc}; then
85			rcctl_err "${_svc} is not a pkg script"
86		elif ! svc_get ${_svc} status; then
87			rcctl_err "${_svc} is not enabled"
88		fi
89	done
90	_pkg_scripts=$(echo "${_svcs} ${pkg_scripts}" | tr "[:blank:]" "\n" | \
91		     awk -v ORS=' ' '!x[$0]++')
92	rcconf_edit_begin
93	grep -v "^pkg_scripts.*=" /etc/rc.conf.local >${_TMP_RCCONF}
94	echo pkg_scripts=${_pkg_scripts} >>${_TMP_RCCONF}
95	rcconf_edit_end
96}
97
98pkg_scripts_rm()
99{
100	local _svc=$1
101	[ -n "${_svc}" ] || return
102
103	[ -z "${pkg_scripts}" ] && return
104
105	rcconf_edit_begin
106	sed "/^pkg_scripts[[:>:]]/{s/[[:<:]]${_svc}[[:>:]]//g
107	    s/['\"]//g;s/ *= */=/;s/   */ /g;s/ $//;/=$/d;}" \
108	    /etc/rc.conf.local >${_TMP_RCCONF}
109	rcconf_edit_end
110}
111
112rcconf_edit_begin()
113{
114	_TMP_RCCONF=$(mktemp -p /etc -t rc.conf.local.XXXXXXXXXX) || \
115		rcctl_err "cannot create temporary file under /etc"
116	if [ -f /etc/rc.conf.local ]; then
117		cat /etc/rc.conf.local >${_TMP_RCCONF} || \
118			rcctl_err "cannot append to ${_TMP_RCCONF}"
119	else
120		touch /etc/rc.conf.local || \
121			rcctl_err "cannot create /etc/rc.conf.local"
122	fi
123}
124
125rcconf_edit_end()
126{
127	sort -u -o ${_TMP_RCCONF} ${_TMP_RCCONF} || \
128		rcctl_err "cannot modify ${_TMP_RCCONF}"
129	cat ${_TMP_RCCONF} >/etc/rc.conf.local || \
130		rcctl_err "cannot append to /etc/rc.conf.local"
131	if [ ! -s /etc/rc.conf.local ]; then
132		rm /etc/rc.conf.local || \
133			rcctl_err "cannot remove /etc/rc.conf.local"
134	fi
135	rm -f ${_TMP_RCCONF}
136	_rc_parse_conf # reload new values
137}
138
139svc_is_avail()
140{
141	local _svc=$1
142	_rc_check_name "${_svc}" || return
143
144	[ -x "/etc/rc.d/${_svc}" ] && return
145	svc_is_special ${_svc}
146}
147
148svc_is_base()
149{
150	local _svc=$1
151	[ -n "${_svc}" ] || return
152
153	local _cached _ret
154
155	_cached=$(eval echo \${cached_svc_is_base_${_svc}})
156	[ "${_cached}" ] && return "${_cached}"
157
158	grep -qw "^${_svc}_flags" /etc/rc.conf
159	_ret=$?
160
161	set -A cached_svc_is_base_${_svc} -- ${_ret}
162	return ${_ret}
163}
164
165svc_is_meta()
166{
167	local _svc=$1
168	[ -n "${_svc}" ] || return
169
170	local _cached _ret
171
172	_cached=$(eval echo \${cached_svc_is_meta_${_svc}})
173	[ "${_cached}" ] && return "${_cached}"
174
175	[ -r "/etc/rc.d/${_svc}" ] && ! grep -qw "^rc_cmd" /etc/rc.d/${_svc}
176	_ret=$?
177
178	set -A cached_svc_is_meta_${_svc} -- ${_ret}
179	return ${_ret}
180}
181
182svc_is_special()
183{
184	local _svc=$1
185	[ -n "${_svc}" ] || return
186
187	local _cached _ret
188
189	_cached=$(eval echo \${cached_svc_is_special_${_svc}})
190	[ "${_cached}" ] && return "${_cached}"
191
192	echo ${_special_svcs} | grep -qw -- ${_svc}
193	_ret=$?
194
195	set -A cached_svc_is_special_${_svc} -- ${_ret}
196	return ${_ret}
197}
198
199svc_ls()
200{
201	local _lsarg=$1
202	[ -n "${_lsarg}" ] || return
203
204	# we do not want to return the "status" nor the rc.d(8) script retcode
205	local _ret=0 _on _svc _started
206
207	case ${_lsarg} in
208		all)
209			(
210				ls_rcscripts
211				echo ${_special_svcs} | tr "[:blank:]" "\n"
212			) | sort
213			;;
214		failed)
215			for _svc in $(svc_ls on); do
216				! svc_is_special ${_svc} && \
217					! /etc/rc.d/${_svc} check >/dev/null && \
218					echo ${_svc} && _ret=1
219			done
220			;;
221		off|on)
222			for _svc in $(svc_ls all); do
223				svc_get ${_svc} status && _on=1
224					[ "${_lsarg}" = "on" -a -n "${_on}" ] || \
225						[ "${_lsarg}" = "off" -a -z "${_on}" ] && \
226					echo ${_svc}
227				unset _on
228			done
229			;;
230		started|stopped)
231			for _svc in $(ls_rcscripts); do
232				/etc/rc.d/${_svc} check >/dev/null && _started=1
233				[ "${_lsarg}" = "started" -a -n "${_started}" ] || \
234					[ "${_lsarg}" = "stopped" -a -z "${_started}" ] && \
235					echo ${_svc}
236				unset _started
237			done
238			;;
239		*)
240			_ret=1
241	esac
242
243	return ${_ret}
244}
245
246svc_get()
247{
248	local _svc=$1
249	[ -n "${_svc}" ] || return
250
251	local _status=0 _val _var=$2
252	local daemon_class daemon_flags daemon_rtable daemon_timeout daemon_user
253
254	if svc_is_special ${_svc}; then
255		daemon_flags="$(eval echo \${${_svc}})"
256	else
257		# set pkg daemon_flags to "NO" to match base svc
258		if ! svc_is_base ${_svc}; then
259			if ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then
260				daemon_flags="NO"
261			fi
262		fi
263
264		if ! svc_is_meta ${_svc}; then
265			# these are expensive, make sure they are explicitely requested
266			if [ -z "${_var}" -o "${_var}" = "class" ]; then
267				getcap -f /etc/login.conf ${_svc} 1>/dev/null 2>&1 && \
268					daemon_class=${_svc}
269				[ -z "${daemon_class}" ] && \
270					daemon_class="$(svc_getdef ${_svc} class)"
271			fi
272			if [[ -z ${_var} || ${_var} == @(flags|status) ]]; then
273				[ -z "${daemon_flags}" ] && \
274					daemon_flags="$(eval echo \"\${${_svc}_flags}\")"
275				[ -z "${daemon_flags}" ] && \
276					daemon_flags="$(svc_getdef ${_svc} flags)"
277			fi
278			if [ -z "${_var}" -o "${_var}" = "rtable" ]; then
279				[ -z "${daemon_rtable}" ] && \
280					daemon_rtable="$(eval echo \"\${${_svc}_rtable}\")"
281				[ -z "${daemon_rtable}" ] && \
282					daemon_rtable="$(svc_getdef ${_svc} rtable)"
283			fi
284			if [ -z "${_var}" -o "${_var}" = "timeout" ]; then
285				[ -z "${daemon_timeout}" ] && \
286					daemon_timeout="$(eval echo \"\${${_svc}_timeout}\")"
287				[ -z "${daemon_timeout}" ] && \
288					daemon_timeout="$(svc_getdef ${_svc} timeout)"
289			fi
290			if [ -z "${_var}" -o "${_var}" = "user" ]; then
291				[ -z "${daemon_user}" ] && \
292					daemon_user="$(eval echo \"\${${_svc}_user}\")"
293				[ -z "${daemon_user}" ] && \
294					daemon_user="$(svc_getdef ${_svc} user)"
295			fi
296		fi
297	fi
298
299	[ "${daemon_flags}" = "NO" ] && _status=1
300
301	if [ -n "${_var}" ]; then
302		[ "${_var}" = "status" ] && return ${_status}
303		eval _val=\${daemon_${_var}}
304		[ -z "${_val}" ] || print -r -- "${_val}"
305	else
306		svc_is_meta ${_svc} && return ${_status}
307		if svc_is_special ${_svc}; then
308			echo "${_svc}=${daemon_flags}"
309		else
310			echo "${_svc}_class=${daemon_class}"
311			echo "${_svc}_flags=${daemon_flags}"
312			echo "${_svc}_rtable=${daemon_rtable}"
313			echo "${_svc}_timeout=${daemon_timeout}"
314			echo "${_svc}_user=${daemon_user}"
315		fi
316		return ${_status}
317	fi
318}
319
320# to prevent namespace pollution, only call in a subshell
321svc_getdef()
322{
323	local _svc=$1
324	[ -n "${_svc}" ] || return
325
326	local _status=0 _val _var=$2
327	local daemon_class daemon_flags daemon_rtable daemon_timeout daemon_user
328
329	if svc_is_special ${_svc}; then
330		# unconditionally parse: we always output flags and/or status
331		_rc_parse_conf /etc/rc.conf
332		daemon_flags="$(eval echo \${${_svc}})"
333		[ "${daemon_flags}" = "NO" ] && _status=1
334	else
335		if ! svc_is_base ${_svc}; then
336			_status=1 # all pkg_scripts are off by default
337		else
338			# abuse /etc/rc.conf behavior of only setting flags
339			# to empty or "NO" to get our default status;
340			# we'll get our default flags from the rc.d script
341			[[ -z ${_var} || ${_var} == status ]] && \
342				_rc_parse_conf /etc/rc.conf
343			[ "$(eval echo \${${_svc}_flags})" = "NO" ] && _status=1
344		fi
345
346		if ! svc_is_meta ${_svc}; then
347			rc_cmd() { }
348			. /etc/rc.d/${_svc} >/dev/null 2>&1
349
350			daemon_class=daemon
351			[ -z "${daemon_rtable}" ] && daemon_rtable=0
352			[ -z "${daemon_timeout}" ] && daemon_timeout=30
353			[ -z "${daemon_user}" ] && daemon_user=root
354		fi
355	fi
356
357	if [ -n "${_var}" ]; then
358		[ "${_var}" = "status" ] && return ${_status}
359		eval _val=\${daemon_${_var}}
360		[ -z "${_val}" ] || print -r -- "${_val}"
361	else
362		svc_is_meta ${_svc} && return ${_status}
363		if svc_is_special ${_svc}; then
364			echo "${_svc}=${daemon_flags}"
365		else
366			echo "${_svc}_class=${daemon_class}"
367			echo "${_svc}_flags=${daemon_flags}"
368			echo "${_svc}_rtable=${daemon_rtable}"
369			echo "${_svc}_timeout=${daemon_timeout}"
370			echo "${_svc}_user=${daemon_user}"
371		fi
372		return ${_status}
373	fi
374}
375
376svc_rm()
377{
378	local _svc=$1
379	[ -n "${_svc}" ] || return
380
381	rcconf_edit_begin
382	if svc_is_special ${_svc}; then
383		grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF}
384		( svc_getdef ${_svc} status ) && \
385			echo "${_svc}=NO" >>${_TMP_RCCONF}
386	else
387		grep -Ev "^${_svc}_(flags|rtable|timeout|user).*=" \
388			/etc/rc.conf.local >${_TMP_RCCONF}
389		( svc_getdef ${_svc} status ) && \
390			echo "${_svc}_flags=NO" >>${_TMP_RCCONF}
391	fi
392	rcconf_edit_end
393}
394
395svc_set()
396{
397	local _svc=$1 _var=$2
398	[ -n "${_svc}" -a -n "${_var}" ] || return
399
400	shift 2
401	local _args="$*"
402
403	# don't check if we are already enabled or disabled because rc.conf(8)
404	# defaults may have changed in which case we may have a matching
405	# redundant entry in rc.conf.local that we can drop
406	if [ "${_var}" = "status" ]; then
407		if [ "${_args}" = "on" ]; then
408			_var="flags"
409			# keep our flags if we're already enabled
410			eval "_args=\"\${${_svc}_${_var}}\""
411			[ "${_args}" = "NO" ] && unset _args
412			if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then
413				pkg_scripts_append ${_svc}
414			fi
415		elif [ "${_args}" = "off" ]; then
416			if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then
417				pkg_scripts_rm ${_svc}
418			fi
419			svc_rm ${_svc}
420			return
421		else
422			rcctl_err "invalid status \"${_args}\""
423		fi
424	else
425		svc_get ${_svc} status || \
426			rcctl_err "${svc} is not enabled"
427	fi
428
429	if svc_is_special ${_svc}; then
430		[ "${_var}" = "flags" ] || return
431		rcconf_edit_begin
432		grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF}
433		( svc_getdef ${_svc} status ) || \
434			echo "${_svc}=YES" >>${_TMP_RCCONF}
435		rcconf_edit_end
436		return
437	fi
438
439	if [ -n "${_args}" ]; then
440		if [ "${_var}" = "rtable" ]; then
441			[[ ${_args} != +([[:digit:]]) || ${_args} -lt 0 ]] && \
442				rcctl_err "\"${_args}\" is not an integer"
443		fi
444		if [ "${_var}" = "timeout" ]; then
445			[[ ${_args} != +([[:digit:]]) || ${_args} -le 0 ]] && \
446				rcctl_err "\"${_args}\" is not a positive integer"
447		fi
448		if [ "${_var}" = "user" ]; then
449			getent passwd "${_args}" >/dev/null || \
450				rcctl_err "user \"${_args}\" does not exist"
451		fi
452		# unset flags if they match the default enabled ones
453		[ "${_args}" = "$(svc_getdef ${_svc} ${_var})" ] && \
454			unset _args
455	fi
456
457	# protect leading whitespace
458	[ "${_args}" = "${_args# }" ] || _args="\"${_args}\""
459
460	# reset: value may have changed
461	unset ${_svc}_${_var}
462
463	rcconf_edit_begin
464	grep -v "^${_svc}_${_var}.*=" /etc/rc.conf.local >${_TMP_RCCONF}
465	if [ -n "${_args}" ] || \
466	   ( svc_is_base ${_svc} && ! svc_getdef ${_svc} status && [ "${_var}" == "flags" ] ); then
467		echo "${_svc}_${_var}=${_args}" >>${_TMP_RCCONF}
468	fi
469	rcconf_edit_end
470}
471
472unset _RC_DEBUG _RC_FORCE
473while getopts "df" c; do
474	case "$c" in
475		d) _RC_DEBUG=-d;;
476		f) _RC_FORCE=-f;;
477		*) usage;;
478	esac
479done
480shift $((OPTIND-1))
481[ $# -gt 0 ] || usage
482
483action=$1
484ret=0
485
486case ${action} in
487	ls)
488		lsarg=$2
489		[[ ${lsarg} == @(all|failed|off|on|started|stopped) ]] || usage
490		;;
491	order)
492		shift 1
493		svcs="$*"
494		for svc in ${svcs}; do
495			svc_is_avail ${svc} || \
496				rcctl_err "service ${svc} does not exist" 2
497		done
498		;;
499	disable|enable|start|stop|restart|reload|check)
500		shift 1
501		svcs="$*"
502		[ -z "${svcs}" ] && usage
503		# it's ok to disable a non-existing daemon
504		if [ "${action}" != "disable" ]; then
505			for svc in ${svcs}; do
506				svc_is_avail ${svc} || \
507					rcctl_err "service ${svc} does not exist" 2
508			done
509		fi
510		;;
511	get|getdef)
512		svc=$2
513		var=$3
514		[ -z "${svc}" ] && usage
515		[ "${svc}" = "all" ] || svc_is_avail ${svc} || \
516			rcctl_err "service ${svc} does not exist" 2
517		if [ -n "${var}" ]; then
518			[ "${svc}" = "all" ] && usage
519			[[ ${var} != @(class|flags|status|rtable|timeout|user) ]] && usage
520			if svc_is_meta ${svc}; then
521				[ "${var}" != "status" ] && \
522					rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\""
523			fi
524			if svc_is_special ${svc}; then
525				[[ ${var} == @(class|rtable|timeout|user) ]] && \
526					rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\""
527			fi
528		fi
529		;;
530	set)
531		svc=$2
532		var=$3
533		[ $# -ge 3 ] && shift 3 || shift $#
534		args="$*"
535		[ -z "${svc}" ] && usage
536		# it's ok to disable a non-existing daemon
537		if [ "${action} ${var} ${args}" != "set status off" ]; then
538			svc_is_avail ${svc} || \
539				rcctl_err "service ${svc} does not exist" 2
540		fi
541		[[ ${var} != @(class|flags|rtable|status|timeout|user) ]] && usage
542		svc_is_meta ${svc} && [ "${var}" != "status" ] && \
543			rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\""
544		[[ ${var} = flags && ${args} = NO ]] && \
545			rcctl_err "\"flags NO\" contradicts \"${action}\""
546		if svc_is_special ${svc}; then
547			[[ ${var} != status ]] && \
548				rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\""
549		fi
550		[[ ${var} == class ]] && \
551			rcctl_err "\"${svc}_class\" is a read-only variable set in login.conf(5)"
552		;;
553	*)
554		usage
555		;;
556esac
557
558case ${action} in
559	disable)
560		needs_root ${action}
561		for svc in ${svcs}; do
562			svc_set ${svc} status off || ret=$?;
563		done
564		exit ${ret}
565		;;
566	enable)
567		needs_root ${action}
568		for svc in ${svcs}; do
569			svc_set ${svc} status on || ret=$?;
570		done
571		exit ${ret}
572		;;
573	get|getdef)
574		if [ "${svc}" = "all" ]; then
575			for svc in $(svc_ls all); do
576				( svc_${action} ${svc} "${var}" )
577			done
578			return 0 # we do not want the svc status
579		else
580			( svc_${action} ${svc} "${var}" )
581		fi
582		;;
583	ls)
584		# some rc.d(8) scripts need root for rc_check()
585		[[ ${lsarg} == @(started|stopped|failed) ]] && needs_root ${action} ${lsarg}
586		svc_ls ${lsarg}
587		;;
588	order)
589		if [ -n "${svcs}" ]; then
590			needs_root ${action}
591			pkg_scripts_order ${svcs}
592		else
593			[[ -z ${pkg_scripts} ]] || echo ${pkg_scripts}
594		fi
595		;;
596	set)
597		needs_root ${action}
598		svc_set ${svc} "${var}" "${args}"
599		;;
600	start|stop|restart|reload|check)
601		for svc in ${svcs}; do
602			if svc_is_special ${svc}; then
603				rcctl_err "\"${svc}\" is a special variable, no rc.d(8) script"
604			fi
605			/etc/rc.d/${svc} ${_RC_DEBUG} ${_RC_FORCE} ${action} || ret=$?;
606		done
607		exit ${ret}
608		;;
609	*)
610		usage
611		;;
612esac
613