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