1 if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_SUBR=1
3 # Copyright (c) 2006-2012 Devin Teske
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
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.
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
29 ############################################################ INCLUDES
31 BSDCFG_SHARE="/usr/share/bsdconfig"
32 [ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1
34 BSDCFG_LIBE="/usr/libexec/bsdconfig"
35 if [ ! "$_SYSRC_JAILED" ]; then
36 f_dprintf "%s: loading includes..." sysrc.subr
37 f_include_lang $BSDCFG_LIBE/include/messages.subr
40 ############################################################ CONFIGURATION
43 # Standard pathnames (inherit values from shell if available)
45 : ${RC_DEFAULTS:="/etc/defaults/rc.conf"}
47 ############################################################ GLOBALS
50 # Global exit status variables
56 # Valid characters that can appear in an sh(1) variable name
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
64 VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
66 ############################################################ FUNCTIONS
68 # f_clean_env [ --except $varname ... ]
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.
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 "`...`".
84 # Should we process an exclusion-list?
86 if [ "$1" = "--except" ]; then
92 # Loop over a list of variable names from set(1) built-in.
94 for var in $( set | awk -F= \
95 '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \
99 # In POSIX bourne-shell, attempting to unset(1) OPTIND results
100 # in "unset: Illegal number:" and causes abrupt termination.
102 [ "$var" = OPTIND ] && continue
105 # Process the exclusion-list?
107 if [ "$except" ]; then
108 for arg in "$@" ""; do
109 [ "$var" = "$arg" ] && break
111 [ "$arg" ] && continue
118 # f_sysrc_get $varname
120 # Get a system configuration setting from the collection of system-
121 # configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf and
122 # /etc/rc.conf.local)
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).
132 [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
134 # Taint-check variable name
137 # Don't expand possible positional parameters
140 [ "$1" ] || return $FAILURE
143 ( # Execute within sub-shell to protect parent environment
146 # Clear the environment of all variables, preventing the
147 # expansion of normals such as `PS1', `TERM', etc.
149 f_clean_env --except IFS RC_CONFS RC_DEFAULTS
151 . "$RC_DEFAULTS" > /dev/null 2>&1
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.
162 if [ "$1" = "rc_conf_files" ]; then
163 _rc_conf_files="$rc_conf_files"
167 # If RC_CONFS is defined, set $rc_conf_files to an explicit
168 # value, modifying the default behavior of source_rc_confs().
170 if [ "${RC_CONFS+set}" ]; then
171 rc_conf_files="$RC_CONFS"
175 source_rc_confs > /dev/null 2>&1
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).
185 if [ "$1" = "rc_conf_files" -a \
186 "$_rc_confs_set" -a \
187 "$rc_conf_files" = "$RC_CONFS" \
189 rc_conf_files="$_rc_conf_files"
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).
203 eval echo '"${'"$1"'}"' 2> /dev/null
207 # f_sysrc_get_default $varname
209 # Get a system configuration default setting from the default rc.conf(5) file
210 # (or whatever RC_DEFAULTS points at).
212 f_sysrc_get_default()
215 [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
217 # Taint-check variable name
220 # Don't expand possible positional parameters
223 [ "$1" ] || return $FAILURE
226 ( # Execute within sub-shell to protect parent environment
229 # Clear the environment of all variables, preventing the
230 # expansion of normals such as `PS1', `TERM', etc.
232 f_clean_env --except RC_DEFAULTS
234 . "$RC_DEFAULTS" > /dev/null 2>&1
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).
245 eval echo '"${'"$1"'}"' 2> /dev/null
249 # f_sysrc_find $varname
251 # Find which file holds the effective last-assignment to a given variable
252 # within the rc.conf(5) file(s).
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.
260 local varname="${1%%[!$VALID_VARNAME_CHARS]*}"
261 local regex="^[[:space:]]*$varname="
262 local rc_conf_files="$( f_sysrc_get rc_conf_files )"
268 ""|[0-9]*) return $FAILURE
272 # If RC_CONFS is defined, set $rc_conf_files to an explicit
273 # value, modifying the default behavior of source_rc_confs().
275 [ "${RC_CONFS+set}" ] && rc_conf_files="$RC_CONFS"
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).
282 for file in $rc_conf_files; do
283 conf_files="$file${conf_files:+ }$conf_files"
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
291 conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS"
294 # Find which file matches assignment to the given variable name.
296 for file in $conf_files; do
297 [ -f "$file" -a -r "$file" ] || continue
298 if grep -Eq "$regex" $file; then
304 return $FAILURE # Not found
307 # f_sysrc_desc $varname
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).
312 # Multi-line comments are joined together. Results are NULL if no description
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.
319 # Variables that should be defined on the invocation line:
320 # -v varname="varname"
323 regex = "^[[:space:]]*"varname"="
330 if ( ! match($0, regex) ) next
333 sub(/^[^#]*(#[[:space:]]*)?/, "")
338 if ( !/^[[:space:]]*#/ ||
339 /^[[:space:]]*[[:alpha:]_][[:alnum:]_]*=/ ||
340 /^[[:space:]]*#[[:alpha:]_][[:alnum:]_]*=/ ||
341 /^[[:space:]]*$/ ) exit
343 sub(/(.*#)*[[:space:]]*/, "")
347 # Clean up the buffer
348 sub(/^[[:space:]]*/, "", buffer)
349 sub(/[[:space:]]*$/, "", buffer)
357 awk -v varname="$1" "$f_sysrc_desc_awk" < "$RC_DEFAULTS"
360 # f_sysrc_set $varname $new_value
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'.
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.
371 # Variables that should be defined on the invocation line:
372 # -v varname="varname"
373 # -v new_value="new_value"
376 regex = "^[[:space:]]*"varname"="
380 # If already found... just spew
381 if ( found ) { print; next }
383 # Does this line match an assignment to our variable?
384 if ( ! match($0, regex) ) { print; next }
386 # Save important match information
388 matchlen = RSTART + RLENGTH - 1
390 # Store the value text for later munging
391 value = substr($0, matchlen + 1, length($0) - matchlen)
393 # Store the first character of the value
394 t1 = t2 = substr(value, 0, 1)
396 # Assignment w/ back-ticks, expression, or misc.
397 # We ignore these since we did not generate them
399 if ( t1 ~ /[`$\\]/ ) { retval = 1; print; next }
401 # Assignment w/ single-quoted value
402 else if ( t1 == "'\''" ) {
403 sub(/^'\''[^'\'']*/, "", value)
404 if ( length(value) == 0 ) t2 = ""
405 sub(/^'\''/, "", value)
408 # Assignment w/ double-quoted value
409 else if ( t1 == "\"" ) {
410 sub(/^"(.*\\\\+")*[^"]*/, "", value)
411 if ( length(value) == 0 ) t2 = ""
415 # Assignment w/ non-quoted value
416 else if ( t1 ~ /[^[:space:];]/ ) {
418 sub(/^[^[:space:]]*/, "", value)
422 else if ( t1 ~ /[[:space:];]/ ) { t1 = t2 = "\"" }
424 printf "%s%c%s%c%s\n", substr($0, 0, matchlen), \
425 t1, new_value, t2, value
431 local funcname=f_sysrc_set
432 local varname="$1" new_value="$2"
435 [ "$varname" ] || return $FAILURE
438 # Find which rc.conf(5) file contains the last-assignment
441 local file="$( f_sysrc_find "$varname" )"
442 if [ "$file" = "$RC_DEFAULTS" -o ! "$file" ]; then
444 # We either got a null response (not found) or the variable
445 # was only found in the rc.conf(5) defaults. In either case,
446 # let's instead modify the first file from $rc_conf_files.
452 # If RC_CONFS is defined, use $RC_CONFS
453 # rather than $rc_conf_files.
455 if [ "${RC_CONFS+set}" ]; then
456 file="${RC_CONFS%%[$IFS]*}"
458 file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' )
463 # If not found, append new value to last file and return.
465 if [ "$not_found" ]; then
466 echo "$varname=\"$new_value\"" >> "$file"
471 # Perform sanity checks.
473 if [ ! -w "$file" ]; then
474 f_err "$msg_cannot_create_permission_denied\n" \
480 # Create a new temporary file to write to.
483 if ! f_eval_catch -dk tmpfile $funcname mktemp 'mktemp -t "%s"' "$pgm"
490 # Fixup permissions (else we're in for a surprise, as mktemp(1) creates
491 # the temporary file with 0600 permissions, and if we simply mv(1) the
492 # temporary file over the destination, the destination will inherit the
493 # permissions from the temporary file).
496 f_eval_catch -dk mode $funcname stat 'stat -f "%%#Lp" "%s"' "$file" ||
498 f_eval_catch -d $funcname chmod 'chmod "%s" "%s"' "$mode" "$tmpfile"
501 # Fixup ownership. The destination file _is_ writable (we tested
502 # earlier above). However, this will fail if we don't have sufficient
503 # permissions (so we throw stderr into the bit-bucket).
506 f_eval_catch -dk owner $funcname stat \
507 'stat -f "%%u:%%g" "%s"' "$file" || owner="root:wheel"
508 f_eval_catch -d $funcname chown 'chown "%s" "%s"' "$owner" "$tmpfile"
511 # Operate on the matching file, replacing only the last occurrence.
513 local new_contents retval
514 new_contents=$( tail -r $file 2> /dev/null )
515 new_contents=$( echo "$new_contents" | awk -v varname="$varname" \
516 -v new_value="$new_value" "$f_sysrc_set_awk" )
520 # Write the temporary file contents.
522 echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE
523 if [ $retval -ne $SUCCESS ]; then
524 echo "$varname=\"$new_value\"" >> "$tmpfile"
528 # Taint-check our results.
530 if ! f_eval_catch -d $funcname sh '/bin/sh -n "%s"' "$tmpfile"; then
531 f_err "$msg_previous_syntax_errors\n" "$pgm" "$file"
537 # Finally, move the temporary file into place.
539 f_eval_catch -de $funcname mv 'mv "%s" "%s"' "$tmpfile" "$file"
542 # f_sysrc_delete $varname
544 # Remove a setting from the system configuration files (edits files in-place).
545 # Deletes all assignments to the given variable in all config files. If the
546 # `-f file' option is passed, the removal is restricted to only those files
547 # specified, otherwise the system collection of rc_conf_files is used.
549 # This function is a two-parter. Below is the awk(1) portion of the function,
550 # afterward is the sh(1) function which utilizes the below awk script.
553 # Variables that should be defined on the invocation line:
554 # -v varname="varname"
557 regex = "^[[:space:]]*"varname"="
570 local funcname=f_sysrc_delete
575 [ "$varname" ] || return $FAILURE
578 # Operate on each of the specified files
581 for file in ${RC_CONFS-$( f_sysrc_get rc_conf_files )}; do
582 [ -e "$file" ] || continue
585 # Create a new temporary file to write to.
587 if ! f_eval_catch -dk tmpfile $funcname mktemp \
588 'mktemp -t "%s"' "$pgm"
595 # Fixup permissions and ownership (mktemp(1) defaults to 0600
596 # permissions) to instead match the destination file.
599 f_eval_catch -dk mode $funcname stat \
600 'stat -f "%%#Lp" "%s"' "$file" || mode=0644
601 f_eval_catch -dk owner $funcname stat \
602 'stat -f "%%u:%%g" "%s"' "$file" || owner="root:wheel"
603 f_eval_catch -d $funcname chmod \
604 'chmod "%s" "%s"' "$mode" "$tmpfile"
605 f_eval_catch -d $funcname chown \
606 'chown "%s" "%s"' "$owner" "$tmpfile"
609 # Operate on the file, removing all occurrences, saving the
610 # output in our temporary file.
612 awk -v varname="$varname" "$f_sysrc_delete_awk" "$file" \
614 if [ $? -ne $SUCCESS ]; then
615 # The file didn't contain any assignments
621 # Taint-check our results.
623 if ! f_eval_catch -d $funcname sh '/bin/sh -n "%s"' "$tmpfile"
625 f_err "$msg_previous_syntax_errors\n" \
632 # Perform sanity checks
634 if [ ! -w "$file" ]; then
635 f_err "$msg_permission_denied\n" "$pgm" "$file"
641 # Finally, move the temporary file into place.
643 f_eval_catch -de $funcname mv \
644 'mv "%s" "%s"' "$tmpfile" "$file" || return $FAILURE
648 ############################################################ MAIN
650 f_dprintf "%s: Successfully loaded." sysrc.subr