xref: /freebsd/usr.sbin/bsdconfig/share/sysrc.subr (revision 2642f6b2c65b1a21f54c1c729a72ff96a0f59c37)
1if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_SUBR=1
2#
3# Copyright (c) 2006-2012 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# $FreeBSD$
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1
33
34BSDCFG_LIBE="/usr/libexec/bsdconfig"
35if [ ! "$_SYSRC_JAILED" ]; then
36	f_dprintf "%s: loading includes..." sysrc.subr
37	f_include_lang $BSDCFG_LIBE/include/messages.subr
38fi
39
40############################################################ CONFIGURATION
41
42#
43# Standard pathnames (inherit values from shell if available)
44#
45: ${RC_DEFAULTS:="/etc/defaults/rc.conf"}
46
47############################################################ GLOBALS
48
49#
50# Global exit status variables
51#
52SUCCESS=0
53FAILURE=1
54
55#
56# Valid characters that can appear in an sh(1) variable name
57#
58# Please note that the character ranges A-Z and a-z should be avoided because
59# these can include accent characters (which are not valid in a variable name).
60# For example, A-Z matches any character that sorts after A but before Z,
61# including A and Z. Although ASCII order would make more sense, that is not
62# how it works.
63#
64VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
65
66############################################################ FUNCTIONS
67
68# f_clean_env [ --except $varname ... ]
69#
70# Unset all environment variables in the current scope. An optional list of
71# arguments can be passed, indicating which variables to avoid unsetting; the
72# `--except' is required to enable the exclusion-list as the remainder of
73# positional arguments.
74#
75# Be careful not to call this in a shell that you still expect to perform
76# $PATH expansion in, because this will blow $PATH away. This is best used
77# within a sub-shell block "(...)" or "$(...)" or "`...`".
78#
79f_clean_env()
80{
81	local var arg except=
82
83	#
84	# Should we process an exclusion-list?
85	#
86	if [ "$1" = "--except" ]; then
87		except=1
88		shift 1
89	fi
90
91	#
92	# Loop over a list of variable names from set(1) built-in.
93	#
94	for var in $( set | awk -F= \
95		'/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \
96		| grep -v '^except$'
97	); do
98		#
99		# In POSIX bourne-shell, attempting to unset(1) OPTIND results
100		# in "unset: Illegal number:" and causes abrupt termination.
101		#
102		[ "$var" = OPTIND ] && continue
103
104		#
105		# Process the exclusion-list?
106		#
107		if [ "$except" ]; then
108			for arg in "$@" ""; do
109				[ "$var" = "$arg" ] && break
110			done
111			[ "$arg" ] && continue
112		fi
113
114		unset "$var"
115	done
116}
117
118# f_sysrc_get $varname
119#
120# Get a system configuration setting from the collection of system-
121# configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf
122# and /etc/rc.conf).
123#
124# NOTE: Additional shell parameter-expansion formats are supported. For
125# example, passing an argument of "hostname%%.*" (properly quoted) will
126# return the hostname up to (but not including) the first `.' (see sh(1),
127# "Parameter Expansion" for more information on additional formats).
128#
129f_sysrc_get()
130{
131	# Sanity check
132	[ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
133
134	# Taint-check variable name
135	case "$1" in
136	[0-9]*)
137		# Don't expand possible positional parameters
138		return $FAILURE ;;
139	*)
140		[ "$1" ] || return $FAILURE
141	esac
142
143	( # Execute within sub-shell to protect parent environment
144
145		#
146		# Clear the environment of all variables, preventing the
147		# expansion of normals such as `PS1', `TERM', etc.
148		#
149		f_clean_env --except IFS RC_CONFS RC_DEFAULTS
150
151		. "$RC_DEFAULTS" > /dev/null 2>&1
152
153		unset RC_DEFAULTS
154			# no longer needed
155
156		#
157		# If the query is for `rc_conf_files' then store the value that
158		# we inherited from sourcing RC_DEFAULTS (above) so that we may
159		# conditionally restore this value after source_rc_confs in the
160		# event that RC_CONFS does not customize the value.
161		#
162		if [ "$1" = "rc_conf_files" ]; then
163			_rc_conf_files="$rc_conf_files"
164		fi
165
166		#
167		# If RC_CONFS is defined, set $rc_conf_files to an explicit
168		# value, modifying the default behavior of source_rc_confs().
169		#
170		if [ "${RC_CONFS+set}" ]; then
171			rc_conf_files="$RC_CONFS"
172			_rc_confs_set=1
173		fi
174
175		source_rc_confs > /dev/null 2>&1
176
177		#
178		# If the query was for `rc_conf_files' AND after calling
179		# source_rc_confs the value has not changed, then we should
180		# restore the value to the one inherited from RC_DEFAULTS
181		# before performing the final query (preventing us from
182		# returning what was set via RC_CONFS when the intent was
183		# instead to query the value from the file(s) specified).
184		#
185		if [ "$1" = "rc_conf_files" -a \
186		     "$_rc_confs_set" -a \
187		     "$rc_conf_files" = "$RC_CONFS" \
188		]; then
189			rc_conf_files="$_rc_conf_files"
190			unset _rc_conf_files
191			unset _rc_confs_set
192		fi
193
194		unset RC_CONFS
195			# no longer needed
196
197		#
198		# This must be the last functional line for both the sub-shell
199		# and the function to preserve the return status from formats
200		# such as "${varname?}" and "${varname:?}" (see "Parameter
201		# Expansion" in sh(1) for more information).
202		#
203		eval echo '"${'"$1"'}"' 2> /dev/null
204	)
205}
206
207# f_sysrc_get_default $varname
208#
209# Get a system configuration default setting from the default rc.conf(5) file
210# (or whatever RC_DEFAULTS points at).
211#
212f_sysrc_get_default()
213{
214	# Sanity check
215	[ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
216
217	# Taint-check variable name
218	case "$1" in
219	[0-9]*)
220		# Don't expand possible positional parameters
221		return $FAILURE ;;
222	*)
223		[ "$1" ] || return $FAILURE
224	esac
225
226	( # Execute within sub-shell to protect parent environment
227
228		#
229		# Clear the environment of all variables, preventing the
230		# expansion of normals such as `PS1', `TERM', etc.
231		#
232		f_clean_env --except RC_DEFAULTS
233
234		. "$RC_DEFAULTS" > /dev/null 2>&1
235
236		unset RC_DEFAULTS
237			# no longer needed
238
239		#
240		# This must be the last functional line for both the sub-shell
241		# and the function to preserve the return status from formats
242		# such as "${varname?}" and "${varname:?}" (see "Parameter
243		# Expansion" in sh(1) for more information).
244		#
245		eval echo '"${'"$1"'}"' 2> /dev/null
246	)
247}
248
249# f_sysrc_find $varname
250#
251# Find which file holds the effective last-assignment to a given variable
252# within the rc.conf(5) file(s).
253#
254# If the variable is found in any of the rc.conf(5) files, the function prints
255# the filename it was found in and then returns success. Otherwise output is
256# NULL and the function returns with error status.
257#
258f_sysrc_find()
259{
260	local varname="${1%%[!$VALID_VARNAME_CHARS]*}"
261	local regex="^[[:space:]]*$varname="
262	local rc_conf_files="$( f_sysrc_get rc_conf_files )"
263	local conf_files=
264	local file
265
266	# Check parameters
267	case "$varname" in
268	""|[0-9]*) return $FAILURE
269	esac
270
271	#
272	# If RC_CONFS is defined, set $rc_conf_files to an explicit
273	# value, modifying the default behavior of source_rc_confs().
274	#
275	[ "${RC_CONFS+set}" ] && rc_conf_files="$RC_CONFS"
276
277	#
278	# Reverse the order of files in rc_conf_files (the boot process sources
279	# these in order, so we will search them in reverse-order to find the
280	# last-assignment -- the one that ultimately effects the environment).
281	#
282	for file in $rc_conf_files; do
283		conf_files="$file${conf_files:+ }$conf_files"
284	done
285
286	#
287	# Append the defaults file (since directives in the defaults file
288	# indeed affect the boot process, we'll want to know when a directive
289	# is found there).
290	#
291	conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS"
292
293	#
294	# Find which file matches assignment to the given variable name.
295	#
296	for file in $conf_files; do
297		[ -f "$file" -a -r "$file" ] || continue
298		if grep -Eq "$regex" $file; then
299			echo $file
300			return $SUCCESS
301		fi
302	done
303
304	return $FAILURE # Not found
305}
306
307# f_sysrc_desc $varname
308#
309# Attempts to return the comments associated with varname from the rc.conf(5)
310# defaults file `/etc/defaults/rc.conf' (or whatever RC_DEFAULTS points to).
311#
312# Multi-line comments are joined together. Results are NULL if no description
313# could be found.
314#
315# This function is a two-parter. Below is the awk(1) portion of the function,
316# afterward is the sh(1) function which utilizes the below awk script.
317#
318f_sysrc_desc_awk='
319# Variables that should be defined on the invocation line:
320# 	-v varname="varname"
321#
322BEGIN {
323	regex = "^[[:space:]]*"varname"="
324	found = 0
325	buffer = ""
326}
327{
328	if ( ! found )
329	{
330		if ( ! match($0, regex) ) next
331
332		found = 1
333		sub(/^[^#]*(#[[:space:]]*)?/, "")
334		buffer = $0
335		next
336	}
337
338	if ( !/^[[:space:]]*#/ ||
339	      /^[[:space:]]*[[:alpha:]_][[:alnum:]_]*=/ ||
340	      /^[[:space:]]*#[[:alpha:]_][[:alnum:]_]*=/ ||
341	      /^[[:space:]]*$/ ) exit
342
343	sub(/(.*#)*[[:space:]]*/, "")
344	buffer = buffer" "$0
345}
346END {
347	# Clean up the buffer
348	sub(/^[[:space:]]*/, "", buffer)
349	sub(/[[:space:]]*$/, "", buffer)
350
351	print buffer
352	exit ! found
353}
354'
355f_sysrc_desc()
356{
357	awk -v varname="$1" "$f_sysrc_desc_awk" < "$RC_DEFAULTS"
358}
359
360# f_sysrc_set $varname $new_value
361#
362# Change a setting in the system configuration files (edits the files in-place
363# to change the value in the last assignment to the variable). If the variable
364# does not appear in the source file, it is appended to the end of the primary
365# system configuration file `/etc/rc.conf'.
366#
367# This function is a two-parter. Below is the awk(1) portion of the function,
368# afterward is the sh(1) function which utilizes the below awk script.
369#
370f_sysrc_set_awk='
371# Variables that should be defined on the invocation line:
372# 	-v varname="varname"
373# 	-v new_value="new_value"
374#
375BEGIN {
376	regex = "^[[:space:]]*"varname"="
377	found = retval = 0
378}
379{
380	# If already found... just spew
381	if ( found ) { print; next }
382
383	# Does this line match an assignment to our variable?
384	if ( ! match($0, regex) ) { print; next }
385
386	# Save important match information
387	found = 1
388	matchlen = RSTART + RLENGTH - 1
389
390	# Store the value text for later munging
391	value = substr($0, matchlen + 1, length($0) - matchlen)
392
393	# Store the first character of the value
394	t1 = t2 = substr(value, 0, 1)
395
396	# Assignment w/ back-ticks, expression, or misc.
397	# We ignore these since we did not generate them
398	#
399	if ( t1 ~ /[`$\\]/ ) { retval = 1; print; next }
400
401	# Assignment w/ single-quoted value
402	else if ( t1 == "'\''" ) {
403		sub(/^'\''[^'\'']*/, "", value)
404		if ( length(value) == 0 ) t2 = ""
405		sub(/^'\''/, "", value)
406	}
407
408	# Assignment w/ double-quoted value
409	else if ( t1 == "\"" ) {
410		sub(/^"(.*\\\\+")*[^"]*/, "", value)
411		if ( length(value) == 0 ) t2 = ""
412		sub(/^"/, "", value)
413	}
414
415	# Assignment w/ non-quoted value
416	else if ( t1 ~ /[^[:space:];]/ ) {
417		t1 = t2 = "\""
418		sub(/^[^[:space:]]*/, "", value)
419	}
420
421	# Null-assignment
422	else if ( t1 ~ /[[:space:];]/ ) { t1 = t2 = "\"" }
423
424	printf "%s%c%s%c%s\n", substr($0, 0, matchlen), \
425		t1, new_value, t2, value
426}
427END { exit retval }
428'
429f_sysrc_set()
430{
431	local varname="$1" new_value="$2"
432
433	# Check arguments
434	[ "$varname" ] || return $FAILURE
435
436	#
437	# Find which rc.conf(5) file contains the last-assignment
438	#
439	local not_found=
440	local file="$( f_sysrc_find "$varname" )"
441	if [ "$file" = "$RC_DEFAULTS" -o ! "$file" ]; then
442		#
443		# We either got a null response (not found) or the variable
444		# was only found in the rc.conf(5) defaults. In either case,
445		# let's instead modify the first file from $rc_conf_files.
446		#
447
448		not_found=1
449
450		#
451		# If RC_CONFS is defined, use $RC_CONFS
452		# rather than $rc_conf_files.
453		#
454		if [ "${RC_CONFS+set}" ]; then
455			file="${RC_CONFS%%[$IFS]*}"
456		else
457			file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' )
458		fi
459	fi
460
461	#
462	# If not found, append new value to last file and return.
463	#
464	if [ "$not_found" ]; then
465		echo "$varname=\"$new_value\"" >> "$file"
466		return $?
467	fi
468
469	#
470	# Perform sanity checks.
471	#
472	if [ ! -w "$file" ]; then
473		f_err "$msg_cannot_create_permission_denied\n" \
474		      "$pgm" "$file"
475		return $FAILURE
476	fi
477
478	#
479	# Create a new temporary file to write to.
480	#
481	local tmpfile="$( mktemp -t "$pgm" )"
482	[ "$tmpfile" ] || return $FAILURE
483
484	#
485	# Fixup permissions (else we're in for a surprise, as mktemp(1) creates
486	# the temporary file with 0600 permissions, and if we simply mv(1) the
487	# temporary file over the destination, the destination will inherit the
488	# permissions from the temporary file).
489	#
490	local mode
491	mode=$( stat -f '%#Lp' "$file" 2> /dev/null )
492	f_quietly chmod "${mode:-0644}" "$tmpfile"
493
494	#
495	# Fixup ownership. The destination file _is_ writable (we tested
496	# earlier above). However, this will fail if we don't have sufficient
497	# permissions (so we throw stderr into the bit-bucket).
498	#
499	local owner
500	owner=$( stat -f '%u:%g' "$file" 2> /dev/null )
501	f_quietly chown "${owner:-root:wheel}" "$tmpfile"
502
503	#
504	# Operate on the matching file, replacing only the last occurrence.
505	#
506	local new_contents retval
507	new_contents=$( tail -r $file 2> /dev/null )
508	new_contents=$( echo "$new_contents" | awk -v varname="$varname" \
509		-v new_value="$new_value" "$f_sysrc_set_awk" )
510	retval=$?
511
512	#
513	# Write the temporary file contents.
514	#
515	echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE
516	if [ $retval -ne $SUCCESS ]; then
517		echo "$varname=\"$new_value\"" >> "$tmpfile"
518	fi
519
520	#
521	# Taint-check our results.
522	#
523	if ! /bin/sh -n "$tmpfile"; then
524		f_err "$msg_previous_syntax_errors\n" "$pgm" "$file"
525		rm -f "$tmpfile"
526		return $FAILURE
527	fi
528
529	#
530	# Finally, move the temporary file into place.
531	#
532	mv "$tmpfile" "$file"
533}
534
535# f_sysrc_delete $varname
536#
537# Remove a setting from the system configuration files (edits files in-place).
538# Deletes all assignments to the given variable in all config files. If the
539# `-f file' option is passed, the removal is restricted to only those files
540# specified, otherwise the system collection of rc_conf_files is used.
541#
542# This function is a two-parter. Below is the awk(1) portion of the function,
543# afterward is the sh(1) function which utilizes the below awk script.
544#
545f_sysrc_delete_awk='
546# Variables that should be defined on the invocation line:
547# 	-v varname="varname"
548#
549BEGIN {
550	regex = "^[[:space:]]*"varname"="
551	found = 0
552}
553{
554	if ( $0 ~ regex )
555		found = 1
556	else
557		print
558}
559END { exit ! found }
560'
561f_sysrc_delete()
562{
563	local varname="$1"
564	local file
565
566	# Check arguments
567	[ "$varname" ] || return $FAILURE
568
569	#
570	# Operate on each of the specified files
571	#
572	for file in ${RC_CONFS-$( f_sysrc_get rc_conf_files )}; do
573		[ -e "$file" ] || continue
574
575		#
576		# Create a new temporary file to write to.
577		#
578		local tmpfile="$( mktemp -t "$pgm" )"
579		[ "$tmpfile" ] || return $FAILURE
580
581		#
582		# Fixup permissions and ownership (mktemp(1) defaults to 0600
583		# permissions) to instead match the destination file.
584		#
585		local mode owner
586		mode=$( stat -f '%#Lp' "$file" 2> /dev/null )
587		owner=$( stat -f '%u:%g' "$file" 2> /dev/null )
588		f_quietly chmod "${mode:-0644}" "$tmpfile"
589		f_quietly chown "${owner:-root:wheel}" "$tmpfile"
590
591		#
592		# Operate on the file, removing all occurrences, saving the
593		# output in our temporary file.
594		#
595		awk -v varname="$varname" "$f_sysrc_delete_awk" "$file" \
596			> "$tmpfile"
597		if [ $? -ne $SUCCESS ]; then
598			# The file didn't contain any assignments
599			rm -f "$tmpfile"
600			continue
601		fi
602
603		#
604		# Taint-check our results.
605		#
606		if ! /bin/sh -n "$tmpfile"; then
607			f_err "$msg_previous_syntax_errors\n" \
608			      "$pgm" "$file"
609			rm -f "$tmpfile"
610			return $FAILURE
611		fi
612
613		#
614		# Perform sanity checks
615		#
616		if [ ! -w "$file" ]; then
617			f_err "$msg_permission_denied\n" "$pgm" "$file"
618			rm -f "$tmpfile"
619			return $FAILURE
620		fi
621
622		#
623		# Finally, move the temporary file into place.
624		#
625		mv "$tmpfile" "$file"
626	done
627}
628
629############################################################ MAIN
630
631f_dprintf "%s: Successfully loaded." sysrc.subr
632
633fi # ! $_SYSRC_SUBR
634