1#!/bin/sh
2#-
3# Copyright (c) 2010-2018 Devin Teske
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27#
28############################################################ INCLUDES
29
30# Prevent `-d' from being interpreted as a debug flag by common.subr
31DEBUG_SELF_INITIALIZE=
32
33BSDCFG_SHARE="/usr/share/bsdconfig"
34[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1
35[ "$_SYSRC_SUBR"  ] || f_include $BSDCFG_SHARE/sysrc.subr
36
37############################################################ GLOBALS
38
39#
40# Version information
41#
42SYSRC_VERSION="7.2 Jun-16,2018"
43
44#
45# Options
46#
47CHECK_ONLY=
48DEFAULT=
49DELETE=
50DESCRIBE=
51EXISTING_ONLY=
52IGNORE_UNKNOWNS=
53JAIL=
54LIST_SERVICE_CONFS=
55LIST_CONFS=
56QUIET=
57ROOTDIR=
58SERVICE=
59SHOW_ALL=
60SHOW_EQUALS=
61SHOW_FILE=
62SHOW_NAME=1
63SHOW_VALUE=1
64VERBOSE=
65
66############################################################ FUNCTIONS
67
68# die [$fmt [$opts ...]]
69#
70# Optionally print a message to stderr before exiting with failure status.
71#
72die()
73{
74	local fmt="$1"
75	[ $# -gt 0 ] && shift 1
76	[  "$fmt"  ] && f_err "$fmt\n" "$@"
77
78	exit $FAILURE
79}
80
81# usage
82#
83# Prints a short syntax statement and exits.
84#
85usage()
86{
87	f_err "Usage: %s [OPTIONS] %s\n" "$pgm" \
88		"{name[[+|-]=value] ... | -a | -A | -l | -L [name ...]}"
89	f_err "Try \`%s --help' for more information.\n" "$pgm"
90	die
91}
92
93# help
94#
95# Prints a full syntax statement and exits.
96#
97help()
98{
99	local optfmt="\t%-11s%s\n"
100	local envfmt="\t%-17s%s\n"
101
102	f_err "Usage: %s [OPTIONS] name[[+|-]=value] ...\n" "$pgm"
103	f_err "Usage: %s [OPTIONS] -a | -A\n" "$pgm"
104	f_err "Usage: %s [OPTIONS] -l | -L [name ...]\n" "$pgm"
105
106	f_err "OPTIONS:\n"
107	f_err "$optfmt" "-a" \
108	      "Dump a list of all non-default configuration variables."
109	f_err "$optfmt" "-A" \
110	      "Dump a list of all configuration variables (incl. defaults)."
111	f_err "$optfmt" "-c" \
112	      "Check. Return success if set or no changes, else error."
113	f_err "$optfmt" "-d" \
114	      "Print a description of the given variable."
115	f_err "$optfmt" "-D" \
116	      "Show default value(s) only (this is the same as setting"
117	f_err "$optfmt" "" \
118	      "RC_CONFS to NULL or passing \`-f' with a NULL file-argument)."
119	f_err "$optfmt" "-e" \
120	      "Print query results as \`var=value' (useful for producing"
121	f_err "$optfmt" "" \
122	      "output to be fed back in). Ignored if \`-n' is specified."
123	f_err "$optfmt" "-E" \
124	      "Existing files only with \`-[lL]' or when changing a setting."
125	f_err "$optfmt" "-f file" \
126	      "Operate on the specified file(s) instead of rc_conf_files."
127	f_err "$optfmt" "" \
128	      "Can be specified multiple times for additional files."
129	f_err "$optfmt" "-F" \
130	      "Show only the last rc.conf(5) file each directive is in."
131	f_err "$optfmt" "-h" \
132	      "Print a short usage statement to stderr and exit."
133	f_err "$optfmt" "--help" \
134	      "Print this message to stderr and exit."
135	f_err "$optfmt" "-i" \
136	      "Ignore unknown variables."
137	f_err "$optfmt" "-j jail" \
138	      "The jid or name of the jail to operate within (overrides"
139	f_err "$optfmt" "" \
140	      "\`-R dir'; requires jexec(8))."
141	f_err "$optfmt" "-l" \
142	      "List configuration files used at startup on stdout and exit."
143	f_err "$optfmt" "-L" \
144	      "List all configuration files including rc.conf.d entries."
145	f_err "$optfmt" "-n" \
146	      "Show only variable values, not their names."
147	f_err "$optfmt" "-N" \
148	      "Show only variable names, not their values."
149	f_err "$optfmt" "-q" \
150	      "Quiet. Disable verbose and hide certain errors."
151	f_err "$optfmt" "-s name" \
152	      "Process additional \`rc.conf.d' entries for service name."
153	f_err "$optfmt" "" \
154	      "Ignored if \`-f file' is given."
155	f_err "$optfmt" "-R dir" \
156	      "Operate within the root directory \`dir' rather than \`/'."
157	f_err "$optfmt" "-v" \
158	      "Verbose. Print the pathname of the specific rc.conf(5)"
159	f_err "$optfmt" "" \
160	      "file where the directive was found."
161	f_err "$optfmt" "--version" \
162	      "Print version information to stdout and exit."
163	f_err "$optfmt" "-x" \
164	      "Remove variable(s) from specified file(s)."
165	f_err "\n"
166
167	f_err "ENVIRONMENT:\n"
168	f_err "$envfmt" "RC_CONFS" \
169	      "Override default rc_conf_files (even if set to NULL)."
170	f_err "$envfmt" "RC_DEFAULTS" \
171	      "Location of \`/etc/defaults/rc.conf' file."
172
173	die
174}
175
176# jail_depend
177#
178# Dump dependencies such as language-file variables and include files to stdout
179# to be piped-into sh(1) running via jexec(8)/chroot(8). As a security measure,
180# this prevents existing language files and library files from being loaded in
181# the jail. This also relaxes the requirement to have these files in every jail
182# before sysrc can be used on said jail.
183#
184jail_depend()
185{
186	#
187	# Indicate that we are jailed
188	#
189	echo export _SYSRC_JAILED=1
190
191	#
192	# Print i18n language variables (their current values are sanitized
193	# and re-printed for interpretation so that the i18n language files
194	# do not need to exist within the jail).
195	#
196	local var val
197	for var in \
198		msg_cannot_create_permission_denied \
199		msg_permission_denied \
200		msg_previous_syntax_errors \
201	; do
202		val=$( eval echo \"\$$var\" |
203			awk '{ gsub(/'\''/, "'\''\\'\'\''"); print }' )
204		echo $var="'$val'"
205	done
206
207	#
208	# Print include dependencies
209	#
210	echo DEBUG_SELF_INITIALIZE=
211	cat $BSDCFG_SHARE/common.subr
212	cat $BSDCFG_SHARE/sysrc.subr
213}
214
215# escape $string [$var_to_set]
216#
217# Escape $string contents so that the contents can be properly encapsulated in
218# single-quotes (making for safe evaluation).
219#
220# NB: See `bsdconfig includes -dF escape' for relevant information/discussion.
221# NB: Abridged version of `f_shell_escape()' from bsdconfig(8) `strings.subr'.
222#
223escape()
224{
225	local __start="$1" __var_to_set="$2" __string=
226	while [ "$__start" ]; do
227		case "$__start" in *\'*)
228			__string="$__string${__start%%\'*}'\\''"
229			__start="${__start#*\'}" continue
230		esac
231		break
232	done
233	__string="$__string$__start"
234	if [ "$__var_to_set" ]; then
235		setvar "$__var_to_set" "$__string"
236	else
237		echo "$__string"
238	fi
239}
240
241############################################################ MAIN SOURCE
242
243#
244# Perform sanity checks
245#
246[ $# -gt 0 ] || usage # NOTREACHED
247
248#
249# Check for `--help' and `--version' command-line option
250#
251for arg in "$@"; do
252	case "$arg" in
253	--) break ;;
254	--help) help ;; # NOTREACHED
255	--version) # see GLOBALS
256		echo "$SYSRC_VERSION"
257		exit $FAILURE ;;
258	esac
259done
260unset arg
261
262#
263# Process command-line flags
264#
265while getopts aAcdDeEf:Fhij:lLnNqR:s:vxX flag; do
266	case "$flag" in
267	a) SHOW_ALL=${SHOW_ALL:-1} ;;
268	A) SHOW_ALL=2 ;;
269	c) CHECK_ONLY=1 ;;
270	d) DESCRIBE=1 ;;
271	D) DEFAULT=1 RC_CONFS= ;;
272	e) SHOW_EQUALS=1 ;;
273	E) EXISTING_ONLY=1 ;;
274	f) DEFAULT= RC_CONFS="$RC_CONFS${RC_CONFS:+ }$OPTARG" ;;
275	F) SHOW_FILE=1 ;;
276	h) usage ;; # NOTREACHED
277	i) IGNORE_UNKNOWNS=1 ;;
278	j) [ "$OPTARG" ] ||
279		die "%s: Missing or null argument to \`-j' flag" "$pgm"
280	   JAIL="$OPTARG" ;;
281	l) LIST_CONFS=1 ;;
282	L) LIST_SERVICE_CONFS=1 ;;
283	n) SHOW_NAME= ;;
284	N) SHOW_VALUE= ;;
285	q) QUIET=1 VERBOSE= ;;
286	R) [ "$OPTARG" ] ||
287		die "%s: Missing or null argument to \`-R' flag" "$pgm"
288	   ROOTDIR="$OPTARG" ;;
289	s) [ "$OPTARG" ] ||
290		die "%s: Missing or null argument to \`-s' flag" "$pgm"
291	   SERVICE="$OPTARG" ;;
292	v) VERBOSE=1 QUIET= ;;
293	x) DELETE=${DELETE:-1} ;;
294	X) DELETE=2 ;;
295	\?) usage ;; # NOTREACHED
296	esac
297done
298shift $(( $OPTIND - 1 ))
299
300#
301# Process `-L' flag
302#
303if [ "$LIST_SERVICE_CONFS" ]; then
304	list= 
305
306	#
307	# List rc_conf_files if no service names given
308	#
309	files=
310	[ $# -eq 0 ] && files=$( f_sysrc_get rc_conf_files )
311	for file in $files; do
312		if [ "$EXISTING_ONLY" ]; then
313			[ -e "$file" -a ! -d "$file" ] || continue
314		fi
315		case "$list" in
316		"$file"|*" $file"|"$file "*|*" $file "*) continue ;;
317		esac
318		list="$list $file"
319	done
320	list="${list# }"
321	if [ $# -eq 0 ]; then
322		if [ "$VERBOSE" ]; then
323			echo rc_conf_files: $list
324		elif [ "$SHOW_EQUALS" ]; then
325			echo "rc_conf_files=\"$list\""
326		fi
327	fi
328
329	#
330	# List rc.conf.d entries
331	#
332	retval=$SUCCESS
333	for service in ${*:-$( service -l )}; do
334		slist=
335		f_sysrc_service_configs $service files || retval=$? continue
336		for file in $files; do
337			if [ "$EXISTING_ONLY" ]; then
338				[ -e "$file" -a ! -d "$file" ] || continue
339			fi
340			if [ ! "$VERBOSE" -a ! "$SHOW_EQUALS" ]; then
341				case "$list" in
342				"$file"|*" $file"|"$file "*|*" $file "*)
343					continue ;;
344				esac
345			fi
346			slist="$slist $file"
347		done
348		slist="${slist# }"
349		if [ $# -gt 0 ]; then
350			[ "$slist" ] || retval=$?
351		fi
352		if [ "$VERBOSE" ]; then
353			[ "$slist" ] && echo "$service: $slist"
354			continue
355		elif [ "$SHOW_EQUALS" ]; then
356			[ "$slist" ] && echo "$service=\"$slist\""
357			continue
358		fi
359		list="$list${slist:+ }$slist"
360	done
361	if [ ! "$VERBOSE" -a ! "$SHOW_EQUALS" ]; then
362		if [ $# -eq 0 -o ! "$QUIET" ]; then
363			list="${list# }"
364			[ "$list" ] && echo $list
365		fi
366	fi
367
368	exit $retval
369fi
370
371#
372# Validate arguments
373#
374for name in "$@"; do
375	# NB: shell expansion syntax removed first
376	name="${name%%:[+=-]*}"
377	name="${name%%[%#+=-]*}"
378	[ "$name" = "${name#*[!$VALID_VARNAME_CHARS]}" ] || die \
379		"%s: %s: name contains characters not allowed in shell" \
380		"$pgm" "$name"
381done
382
383#
384# Process `-s name' argument
385#
386if [ "$SERVICE" -a ! "${RC_CONFS+set}" ]; then
387	if f_sysrc_service_configs "$SERVICE" RC_CONFS; then
388		rc_conf_files=$( f_sysrc_get rc_conf_files )
389		RC_CONFS="$rc_conf_files${RC_CONFS:+ }$RC_CONFS"
390		unset rc_conf_files
391	else
392		unset RC_CONFS
393	fi
394fi
395
396#
397# Process `-E' option flag
398#
399if [ "$EXISTING_ONLY" ]; then
400	#
401	# To get f_sysrc_*() to ignore missing rc_conf_files, we have to use
402	# RC_CONFS to override the unpreened value. If RC_CONFS already has a
403	# value (`-D', `-f file', `-s name', or inherited from parent), use it.
404	# Otherwise, include filtered contents of rc_conf_files.
405	# 
406	RC_CONFS=$(
407		if [ "${RC_CONFS+set}" ]; then
408			set -- $RC_CONFS
409		else
410			set -- $( f_sysrc_get rc_conf_files )
411		fi
412		while [ $# -gt 0 ]; do
413			[ -f "$1" ] && echo -n " $1"
414			shift
415		done
416	)
417	RC_CONFS="${RC_CONFS# }"
418fi
419
420#
421# Process `-l' option flag
422#
423if [ "$LIST_CONFS" ]; then
424	[ $# -eq 0 ] || usage
425	if [ "$DEFAULT" ]; then
426		echo "$RC_DEFAULTS"
427	elif [ "${RC_CONFS+set}" ]; then
428		echo "$RC_CONFS"
429	else
430		f_sysrc_get rc_conf_files
431	fi
432	exit $SUCCESS
433fi
434
435#
436# [More] Sanity checks (e.g., "sysrc --")
437#
438[ $# -eq 0 -a ! "$SHOW_ALL" ] && usage # NOTREACHED
439
440#
441# Taint-check all rc.conf(5) files
442#
443errmsg="$pgm: Exiting due to previous syntax errors"
444if [ "${RC_CONFS+set}" ]; then
445	( for i in $RC_CONFS; do
446	  	[ -e "$i" ] || continue
447	  	/bin/sh -n "$i" || exit $FAILURE
448	  done
449	  exit $SUCCESS
450	) || die "$errmsg"
451else
452	/bin/sh -n "$RC_DEFAULTS" || die "$errmsg"
453	( . "$RC_DEFAULTS"
454	  for i in $rc_conf_files; do
455	  	[ -e "$i" ] || continue
456	  	/bin/sh -n "$i" || exit $FAILURE
457	  done
458	  exit $SUCCESS
459	) || die "$errmsg"
460fi
461
462#
463# Process `-x' (and secret `-X') command-line options
464#
465errmsg="$pgm: \`-x' option incompatible with \`-a'/\`-A' options"
466errmsg="$errmsg (use \`-X' to override)"
467if [ "$DELETE" -a "$SHOW_ALL" ]; then
468	[ "$DELETE" = "2" ] || die "$errmsg"
469fi
470
471#
472# Pre-flight for `-c' command-line option
473#
474[ "$CHECK_ONLY" -a "$SHOW_ALL" ] &&
475	die "$pgm: \`-c' option incompatible with \`-a'/\`-A' options"
476
477#
478# Process `-e', `-n', and `-N' command-line options
479#
480SEP=': '
481[ "$SHOW_FILE" ] && SHOW_EQUALS=
482[ "$SHOW_NAME" ] || SHOW_EQUALS=
483[ "$VERBOSE" = "0" ] && VERBOSE=
484if [ ! "$SHOW_VALUE" ]; then
485	SHOW_NAME=1
486	SHOW_EQUALS=
487fi
488[ "$SHOW_EQUALS" ] && SEP='="'
489
490#
491# Process `-j jail' and `-R dir' command-line options
492#
493if [ "$JAIL" -o "$ROOTDIR" ]; then
494	#
495	# Reconstruct the arguments that we want to carry-over
496	#
497	args="
498		${VERBOSE:+-v}
499		${QUIET:+-q}
500		$( [ "$DELETE" = "1" ] && echo \ -x )
501		$( [ "$DELETE" = "2" ] && echo \ -X )
502		$( [ "$SHOW_ALL" = "1" ] && echo \ -a )
503		$( [ "$SHOW_ALL" = "2" ] && echo \ -A )
504		${CHECK_ONLY:+-c}
505		${DEFAULT:+-D}
506		${EXISTING_ONLY:+-E}
507		${LIST_CONFS:+-l}
508		${LIST_SERVICE_CONFS:+-L}
509		${DESCRIBE:+-d}
510		${SHOW_EQUALS:+-e}
511		${IGNORE_UNKNOWNS:+-i}
512		$( [ "$SHOW_NAME"  ] || echo \ -n )
513		$( [ "$SHOW_VALUE" ] || echo \ -N )
514		$( [ "$SHOW_FILE"  ] && echo \ -F )
515	"
516	if [ "$SERVICE" ]; then
517		escape "$SERVICE" _SERVICE
518		args="$args -s '$_SERVICE'"
519		unset _SERVICE
520	fi
521	if [ "${RC_CONFS+set}" ]; then
522		escape "$RC_CONFS" _RC_CONFS
523		args="$args -f '$_RC_CONFS'"
524		unset _RC_CONFS
525	fi
526	for arg in "$@"; do
527		escape "$arg" arg
528		args="$args '$arg'"
529	done
530
531	#
532	# If both are supplied, `-j jail' supercedes `-R dir'
533	#
534	if [ "$JAIL" ]; then
535		#
536		# Re-execute ourselves with sh(1) via jexec(8)
537		#
538		( echo set -- $args
539		  jail_depend
540		  cat $0
541		) | env - RC_DEFAULTS="$RC_DEFAULTS" \
542		    	/usr/sbin/jexec "$JAIL" /bin/sh
543		exit $?
544	elif [ "$ROOTDIR" ]; then
545		#
546		# Make sure that the root directory specified is not to any
547		# running jails.
548		#
549		# NOTE: To maintain backward compatibility with older jails on
550		# older systems, we will not perform this check if either the
551		# jls(1) or jexec(8) utilities are missing.
552		#
553		if f_have jexec && f_have jls; then
554			jid=$( jls jid path |
555				while read JID JROOT; do
556					[ "$JROOT" = "$ROOTDIR" ] || continue
557					echo $JID
558				done
559			)
560
561			#
562			# If multiple running jails match the specified root
563			# directory, exit with error.
564			#
565			if [ "$jid" -a "${jid%[$IFS]*}" != "$jid" ]; then
566				die "%s: %s: %s" "$pgm" "$ROOTDIR" \
567				    "$( echo "Multiple jails claim this" \
568				             "directory as their root." \
569				             "(use \`-j jail' instead)" )"
570			fi
571
572			#
573			# If only a single running jail matches the specified
574			# root directory, implicitly use `-j jail'.
575			#
576			if [ "$jid" ]; then
577				#
578				# Re-execute outselves with sh(1) via jexec(8)
579				#
580				( echo set -- $args
581				  jail_depend
582				  cat $0
583				) | env - RC_DEFAULTS="$RC_DEFAULTS" \
584					/usr/sbin/jexec "$jid" /bin/sh
585				exit $?
586			fi
587
588			# Otherwise, fall through and allow chroot(8)
589		fi
590
591		#
592		# Re-execute ourselves with sh(1) via chroot(8)
593		#
594		( echo set -- $args
595		  jail_depend
596		  cat $0
597		) | env - RC_DEFAULTS="$RC_DEFAULTS" \
598		    	/usr/sbin/chroot "$ROOTDIR" /bin/sh
599		exit $?
600	fi
601fi
602
603#
604# Process `-a' or `-A' command-line options
605#
606if [ "$SHOW_ALL" ]; then
607	#
608	# Get a list of variables that are currently set in the rc.conf(5)
609	# files (including `/etc/defaults/rc.conf') by performing a call to
610	# source_rc_confs() in a clean environment.
611	#
612	( # Operate in a sub-shell to protect the parent environment
613		#
614		# Set which variables we want to preserve in the environment.
615		# Append the pipe-character (|) to the list of internal field
616		# separation (IFS) characters, allowing us to use the below
617		# list both as an extended grep (-E) pattern and argument list
618		# (required to first get f_clean_env() to preserve these in the
619		# environment and then later to prune them from the list of
620		# variables produced by set(1)).
621		#
622		IFS="$IFS|"
623		EXCEPT="IFS|EXCEPT|PATH|RC_DEFAULTS|OPTIND|DESCRIBE|SEP"
624		EXCEPT="$EXCEPT|DELETE|SHOW_ALL|SHOW_EQUALS|SHOW_NAME|DEFAULT"
625		EXCEPT="$EXCEPT|SHOW_VALUE|SHOW_FILE|VERBOSE|RC_CONFS|SERVICE"
626		EXCEPT="$EXCEPT|pgm|SUCCESS|FAILURE|CHECK_ONLY|EXISTING_ONLY"
627		EXCEPT="$EXCEPT|LIST_CONFS|LIST_SERVICE_CONFS"
628		EXCEPT="$EXCEPT|f_sysrc_desc_awk|f_sysrc_delete_awk"
629
630		#
631		# Clean the environment (except for our required variables)
632		# and then source the required files.
633		#
634		f_clean_env --except $EXCEPT
635		if [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ]; then
636			. "$RC_DEFAULTS"
637
638			#
639			# If passed `-a' (rather than `-A'), re-purge the
640			# environment, removing the rc.conf(5) defaults.
641			#
642			[ "$SHOW_ALL" = "1" ] &&
643				f_clean_env --except rc_conf_files $EXCEPT
644
645			#
646			# If `-f file' was passed, set $rc_conf_files to an
647			# explicit value, modifying the default behavior of
648			# source_rc_confs().
649			#
650			if [ "${RC_CONFS+set}" ]; then
651				[ "$SHOW_ALL" = "1" -a "$SERVICE" -a \
652					! "$DEFAULT" ] || rc_conf_files=
653				rc_conf_files="$rc_conf_files $RC_CONFS"
654				rc_conf_files="${rc_conf_files# }"
655				rc_conf_files="${rc_conf_files% }"
656			fi
657
658			source_rc_confs
659
660			#
661			# If passed `-a' (rather than `-A'), remove
662			# `rc_conf_files' unless it was defined somewhere
663			# other than rc.conf(5) defaults.
664			#
665			[ "$SHOW_ALL" = "1" -a \
666			  "$( f_sysrc_find rc_conf_files )" = "$RC_DEFAULTS" \
667			] && unset rc_conf_files
668		fi
669
670		for NAME in $( set |
671			awk -F= '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' |
672			grep -Ev "^($EXCEPT)$"
673		); do
674			#
675			# If enabled, describe rather than expand value
676			#
677			if [ "$DESCRIBE" ]; then
678				echo "$NAME: $( f_sysrc_desc "$NAME" )"
679				continue
680			fi
681
682			#
683			# If `-F' is passed, find it and move on
684			#
685			if [ "$SHOW_FILE" ]; then
686				[ "$SHOW_NAME" ] && echo -n "$NAME: "
687				f_sysrc_find "$NAME"
688				continue
689			fi
690
691			#
692			# If `-X' is passed, delete the variables
693			#
694			if [ "$DELETE" = "2" ]; then
695				f_sysrc_delete "$NAME"
696				continue
697			fi
698
699			[ "$VERBOSE" ] &&
700				echo -n "$( f_sysrc_find "$NAME" ): "
701
702			#
703			# If `-N' is passed, simplify the output
704			#
705			if [ ! "$SHOW_VALUE" ]; then
706				echo "$NAME"
707				continue
708			fi
709
710			echo "${SHOW_NAME:+$NAME$SEP}$(
711			      f_sysrc_get "$NAME" )${SHOW_EQUALS:+\"}"
712
713		done
714	)
715
716	#
717	# Ignore the remainder of positional arguments.
718	#
719	exit $SUCCESS
720fi
721
722#
723# Process command-line arguments
724#
725status=$SUCCESS
726while [ $# -gt 0 ]; do
727	NAME="${1%%=*}"
728
729	case "$NAME" in
730	*+) mode=APPEND NAME="${NAME%+}" ;;
731	*-) mode=REMOVE NAME="${NAME%-}" ;;
732	 *) mode=ASSIGN
733	esac
734
735	[ "$DESCRIBE" ] &&
736		echo "$NAME: $( f_sysrc_desc "$NAME" )"
737
738	case "$1" in
739	*=*)
740		#
741		# Like sysctl(8), if both `-d' AND "name=value" is passed,
742		# first describe (done above), then attempt to set
743		#
744
745		# If verbose, prefix line with where the directive lives
746		if [ "$VERBOSE" -a ! "$CHECK_ONLY" ]; then
747			file=$( f_sysrc_find "$NAME" )
748			[ "$file" = "$RC_DEFAULTS" -o ! "$file" ] &&
749				file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' )
750			if [ "$SHOW_EQUALS" ]; then
751				echo -n ": $file; "
752			else
753				echo -n "$file: "
754			fi
755		fi
756
757		#
758		# If `-x' or `-X' is passed, delete the variable and ignore the
759		# desire to set some value
760		#
761		if [ "$DELETE" ]; then
762			f_sysrc_delete "$NAME" || status=$FAILURE
763			shift 1
764			continue
765		fi
766
767		#
768		# If `-c' is passed, simply compare and move on
769		#
770		if [ "$CHECK_ONLY" ]; then
771			if ! IGNORED=$( f_sysrc_get "$NAME?" ); then
772				status=$FAILURE
773				[ "$VERBOSE" ] &&
774					echo "$NAME: not currently set"
775				shift 1
776				continue
777			fi
778			value=$( f_sysrc_get "$NAME" )
779			if [ "$value" != "${1#*=}" ]; then
780				status=$FAILURE
781				if [ "$VERBOSE" ]; then
782					echo -n "$( f_sysrc_find "$NAME" ): "
783					echo -n "$NAME: would change from "
784					echo "\`$value' to \`${1#*=}'"
785				fi
786			elif [ "$VERBOSE" ]; then
787				echo -n "$( f_sysrc_find "$NAME" ): "
788				echo "$NAME: already set to \`$value'"
789			fi
790			shift 1
791			continue
792		fi
793
794		#
795		# Determine both `before' value and appropriate `new' value
796		#
797		case "$mode" in
798		APPEND)
799			before=$( f_sysrc_get "$NAME" )
800			add="${1#*=}"
801			delim="${add%"${add#?}"}" # first character
802			oldIFS="$IFS"
803			case "$delim" in
804			""|[$IFS]|[a-zA-Z0-9./]) delim=" " ;;
805			*) IFS="$delim"
806			esac
807			new="$before"
808			for a in $add; do
809				[ "$a" ] || continue
810				skip=
811				for b in $before; do
812					[ "$b" = "$a" ] && skip=1 break
813				done
814				[ "$skip" ] || new="$new$delim$a"
815			done
816			new="${new#"$delim"}" IFS="$oldIFS"
817			unset add delim oldIFS a skip b
818			[ "$SHOW_FILE" ] && before=$( f_sysrc_find "$NAME" )
819			;;
820		REMOVE)
821			before=$( f_sysrc_get "$NAME" )
822			remove="${1#*=}"
823			delim="${remove%"${remove#?}"}" # first character
824			oldIFS="$IFS"
825			case "$delim" in
826			""|[$IFS]|[a-zA-Z0-9./]) delim=" " ;;
827			*) IFS="$delim"
828			esac
829			new=
830			for b in $before; do
831				[ "$b" ] || continue
832				add=1
833				for r in $remove; do
834					[ "$r" = "$b" ] && add= break
835				done
836				[ "$add" ] && new="$new$delim$b"
837			done
838			new="${new#"$delim"}" IFS="$oldIFS"
839			unset remove delim oldIFS b add r
840			[ "$SHOW_FILE" ] && before=$( f_sysrc_find "$NAME" )
841			;;
842		*) # ASSIGN
843			if [ "$SHOW_FILE" ]; then
844				before=$( f_sysrc_find "$NAME" )
845			else
846				before=$( f_sysrc_get "$NAME" )
847			fi
848			new="${1#*=}"
849		esac
850
851		#
852		# If `-N' is passed, simplify the output
853		#
854		if [ ! "$SHOW_VALUE" ]; then
855			echo "$NAME"
856			f_sysrc_set "$NAME" "$new" || status=$FAILURE
857		else
858			if f_sysrc_set "$NAME" "$new"; then
859				if [ "$SHOW_FILE" ]; then
860					after=$( f_sysrc_find "$NAME" )
861				else
862					after=$( f_sysrc_get "$NAME" )
863				fi
864				echo -n "${SHOW_NAME:+$NAME$SEP}"
865				echo -n "$before${SHOW_EQUALS:+\" #}"
866				echo -n " -> ${SHOW_EQUALS:+\"}$after"
867				echo "${SHOW_EQUALS:+\"}"
868			else
869				status=$FAILURE
870			fi
871		fi
872		;;
873	*)
874		if ! IGNORED=$( f_sysrc_get "$NAME?" ); then
875			[ "$IGNORE_UNKNOWNS" -o "$QUIET" ] ||
876				f_err "%s: unknown variable '%s'\n" \
877					"$pgm" "$NAME"
878			shift 1
879			status=$FAILURE
880			continue
881		fi
882
883		# The above check told us what we needed for `-c'
884		if [ "$CHECK_ONLY" ]; then
885			shift 1
886			continue
887		fi
888
889		#
890		# Like sysctl(8), when `-d' is passed, desribe it
891		# (already done above) rather than expanding it
892		#
893
894		if [ "$DESCRIBE" ]; then
895			shift 1
896			continue
897		fi
898
899		#
900		# If `-x' or `-X' is passed, delete the variable
901		#
902		if [ "$DELETE" ]; then
903			f_sysrc_delete "$NAME" || status=$FAILURE
904			shift 1
905			continue
906		fi
907
908		#
909		# If `-F' is passed, find it and move on
910		#
911		if [ "$SHOW_FILE" ]; then
912			[ "$SHOW_NAME" ] && echo -n "$NAME: "
913			f_sysrc_find "$NAME"
914			shift 1
915			continue
916		fi
917
918		if [ "$VERBOSE" ]; then
919			if [ "$SHOW_EQUALS" ]; then
920				echo -n ": $( f_sysrc_find "$NAME" ); "
921			else
922				echo -n "$( f_sysrc_find "$NAME" ): "
923			fi
924		fi
925
926		#
927		# If `-N' is passed, simplify the output
928		#
929		if [ ! "$SHOW_VALUE" ]; then
930			echo "$NAME"
931		else
932			echo "${SHOW_NAME:+$NAME$SEP}$(
933			      f_sysrc_get "$NAME" )${SHOW_EQUALS:+\"}"
934		fi
935	esac
936	shift 1
937done
938
939exit $status # $SUCCESS unless error occurred with either `-c' or `-x'
940
941################################################################################
942# END
943################################################################################
944