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