diff options
Diffstat (limited to 'usr.sbin/bsdconfig/share/sysrc.subr')
-rw-r--r-- | usr.sbin/bsdconfig/share/sysrc.subr | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/usr.sbin/bsdconfig/share/sysrc.subr b/usr.sbin/bsdconfig/share/sysrc.subr new file mode 100644 index 0000000..36a550e --- /dev/null +++ b/usr.sbin/bsdconfig/share/sysrc.subr @@ -0,0 +1,633 @@ +if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_SUBR=1 +# +# Copyright (c) 2006-2012 Devin Teske +# All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# +############################################################ INCLUDES + +BSDCFG_SHARE="/usr/share/bsdconfig" +[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1 + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +if [ ! "$_SYSRC_JAILED" ]; then + f_dprintf "%s: loading includes..." sysrc.subr + f_include_lang $BSDCFG_LIBE/include/messages.subr +fi + +############################################################ CONFIGURATION + +# +# Standard pathnames (inherit values from shell if available) +# +: ${RC_DEFAULTS:="/etc/defaults/rc.conf"} + +############################################################ GLOBALS + +# +# Global exit status variables +# +SUCCESS=0 +FAILURE=1 + +# +# Valid characters that can appear in an sh(1) variable name +# +# Please note that the character ranges A-Z and a-z should be avoided because +# these can include accent characters (which are not valid in a variable name). +# For example, A-Z matches any character that sorts after A but before Z, +# including A and Z. Although ASCII order would make more sense, that is not +# how it works. +# +VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" + +############################################################ FUNCTIONS + +# f_clean_env [ --except $varname ... ] +# +# Unset all environment variables in the current scope. An optional list of +# arguments can be passed, indicating which variables to avoid unsetting; the +# `--except' is required to enable the exclusion-list as the remainder of +# positional arguments. +# +# Be careful not to call this in a shell that you still expect to perform +# $PATH expansion in, because this will blow $PATH away. This is best used +# within a sub-shell block "(...)" or "$(...)" or "`...`". +# +f_clean_env() +{ + local var arg except= + + # + # Should we process an exclusion-list? + # + if [ "$1" = "--except" ]; then + except=1 + shift 1 + fi + + # + # Loop over a list of variable names from set(1) built-in. + # + for var in $( set | awk -F= \ + '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \ + | grep -v '^except$' + ); do + # + # In POSIX bourne-shell, attempting to unset(1) OPTIND results + # in "unset: Illegal number:" and causes abrupt termination. + # + [ "$var" = OPTIND ] && continue + + # + # Process the exclusion-list? + # + if [ "$except" ]; then + for arg in "$@" ""; do + [ "$var" = "$arg" ] && break + done + [ "$arg" ] && continue + fi + + unset "$var" + done +} + +# f_sysrc_get $varname +# +# Get a system configuration setting from the collection of system- +# configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf +# and /etc/rc.conf). +# +# NOTE: Additional shell parameter-expansion formats are supported. For +# example, passing an argument of "hostname%%.*" (properly quoted) will +# return the hostname up to (but not including) the first `.' (see sh(1), +# "Parameter Expansion" for more information on additional formats). +# +f_sysrc_get() +{ + # Sanity check + [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE + + # Taint-check variable name + case "$1" in + [0-9]*) + # Don't expand possible positional parameters + return $FAILURE;; + *) + [ "$1" ] || return $FAILURE + esac + + ( # Execute within sub-shell to protect parent environment + + # + # Clear the environment of all variables, preventing the + # expansion of normals such as `PS1', `TERM', etc. + # + f_clean_env --except IFS RC_CONFS RC_DEFAULTS + + . "$RC_DEFAULTS" > /dev/null 2>&1 + + unset RC_DEFAULTS + # no longer needed + + # + # If the query is for `rc_conf_files' then store the value that + # we inherited from sourcing RC_DEFAULTS (above) so that we may + # conditionally restore this value after source_rc_confs in the + # event that RC_CONFS does not customize the value. + # + if [ "$1" = "rc_conf_files" ]; then + _rc_conf_files="$rc_conf_files" + fi + + # + # If RC_CONFS is defined, set $rc_conf_files to an explicit + # value, modifying the default behavior of source_rc_confs(). + # + if [ "${RC_CONFS+set}" ]; then + rc_conf_files="$RC_CONFS" + _rc_confs_set=1 + fi + + source_rc_confs > /dev/null 2>&1 + + # + # If the query was for `rc_conf_files' AND after calling + # source_rc_confs the value has not changed, then we should + # restore the value to the one inherited from RC_DEFAULTS + # before performing the final query (preventing us from + # returning what was set via RC_CONFS when the intent was + # instead to query the value from the file(s) specified). + # + if [ "$1" = "rc_conf_files" -a \ + "$_rc_confs_set" -a \ + "$rc_conf_files" = "$RC_CONFS" \ + ]; then + rc_conf_files="$_rc_conf_files" + unset _rc_conf_files + unset _rc_confs_set + fi + + unset RC_CONFS + # no longer needed + + # + # This must be the last functional line for both the sub-shell + # and the function to preserve the return status from formats + # such as "${varname?}" and "${varname:?}" (see "Parameter + # Expansion" in sh(1) for more information). + # + eval echo '"${'"$1"'}"' 2> /dev/null + ) +} + +# f_sysrc_get_default $varname +# +# Get a system configuration default setting from the default rc.conf(5) file +# (or whatever RC_DEFAULTS points at). +# +f_sysrc_get_default() +{ + # Sanity check + [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE + + # Taint-check variable name + case "$1" in + [0-9]*) + # Don't expand possible positional parameters + return $FAILURE;; + *) + [ "$1" ] || return $FAILURE + esac + + ( # Execute within sub-shell to protect parent environment + + # + # Clear the environment of all variables, preventing the + # expansion of normals such as `PS1', `TERM', etc. + # + f_clean_env --except RC_DEFAULTS + + . "$RC_DEFAULTS" > /dev/null 2>&1 + + unset RC_DEFAULTS + # no longer needed + + # + # This must be the last functional line for both the sub-shell + # and the function to preserve the return status from formats + # such as "${varname?}" and "${varname:?}" (see "Parameter + # Expansion" in sh(1) for more information). + # + eval echo '"${'"$1"'}"' 2> /dev/null + ) +} + +# f_sysrc_find $varname +# +# Find which file holds the effective last-assignment to a given variable +# within the rc.conf(5) file(s). +# +# If the variable is found in any of the rc.conf(5) files, the function prints +# the filename it was found in and then returns success. Otherwise output is +# NULL and the function returns with error status. +# +f_sysrc_find() +{ + local varname="${1%%[!$VALID_VARNAME_CHARS]*}" + local regex="^[[:space:]]*$varname=" + local rc_conf_files="$( f_sysrc_get rc_conf_files )" + local conf_files= + local file + + # Check parameters + case "$varname" in + ""|[0-9]*) return $FAILURE + esac + + # + # If RC_CONFS is defined, set $rc_conf_files to an explicit + # value, modifying the default behavior of source_rc_confs(). + # + [ "${RC_CONFS+set}" ] && rc_conf_files="$RC_CONFS" + + # + # Reverse the order of files in rc_conf_files (the boot process sources + # these in order, so we will search them in reverse-order to find the + # last-assignment -- the one that ultimately effects the environment). + # + for file in $rc_conf_files; do + conf_files="$file${conf_files:+ }$conf_files" + done + + # + # Append the defaults file (since directives in the defaults file + # indeed affect the boot process, we'll want to know when a directive + # is found there). + # + conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS" + + # + # Find which file matches assignment to the given variable name. + # + for file in $conf_files; do + [ -f "$file" -a -r "$file" ] || continue + if grep -Eq "$regex" $file; then + echo $file + return $SUCCESS + fi + done + + return $FAILURE # Not found +} + +# f_sysrc_desc $varname +# +# Attempts to return the comments associated with varname from the rc.conf(5) +# defaults file `/etc/defaults/rc.conf' (or whatever RC_DEFAULTS points to). +# +# Multi-line comments are joined together. Results are NULL if no description +# could be found. +# +# This function is a two-parter. Below is the awk(1) portion of the function, +# afterward is the sh(1) function which utilizes the below awk script. +# +f_sysrc_desc_awk=' +# Variables that should be defined on the invocation line: +# -v varname="varname" +# +BEGIN { + regex = "^[[:space:]]*"varname"=" + found = 0 + buffer = "" +} +{ + if ( ! found ) + { + if ( ! match($0, regex) ) next + + found = 1 + sub(/^[^#]*(#[[:space:]]*)?/, "") + buffer = $0 + next + } + + if ( !/^[[:space:]]*#/ || + /^[[:space:]]*[[:alpha:]_][[:alnum:]_]*=/ || + /^[[:space:]]*#[[:alpha:]_][[:alnum:]_]*=/ || + /^[[:space:]]*$/ ) exit + + sub(/(.*#)*[[:space:]]*/, "") + buffer = buffer" "$0 +} +END { + # Clean up the buffer + sub(/^[[:space:]]*/, "", buffer) + sub(/[[:space:]]*$/, "", buffer) + + print buffer + exit ! found +} +' +f_sysrc_desc() +{ + awk -v varname="$1" "$f_sysrc_desc_awk" < "$RC_DEFAULTS" +} + +# f_sysrc_set $varname $new_value +# +# Change a setting in the system configuration files (edits the files in-place +# to change the value in the last assignment to the variable). If the variable +# does not appear in the source file, it is appended to the end of the primary +# system configuration file `/etc/rc.conf'. +# +# This function is a two-parter. Below is the awk(1) portion of the function, +# afterward is the sh(1) function which utilizes the below awk script. +# +f_sysrc_set_awk=' +# Variables that should be defined on the invocation line: +# -v varname="varname" +# -v new_value="new_value" +# +BEGIN { + regex = "^[[:space:]]*"varname"=" + found = retval = 0 +} +{ + # If already found... just spew + if ( found ) { print; next } + + # Does this line match an assignment to our variable? + if ( ! match($0, regex) ) { print; next } + + # Save important match information + found = 1 + matchlen = RSTART + RLENGTH - 1 + + # Store the value text for later munging + value = substr($0, matchlen + 1, length($0) - matchlen) + + # Store the first character of the value + t1 = t2 = substr(value, 0, 1) + + # Assignment w/ back-ticks, expression, or misc. + # We ignore these since we did not generate them + # + if ( t1 ~ /[`$\\]/ ) { retval = 1; print; next } + + # Assignment w/ single-quoted value + else if ( t1 == "'\''" ) { + sub(/^'\''[^'\'']*/, "", value) + if ( length(value) == 0 ) t2 = "" + sub(/^'\''/, "", value) + } + + # Assignment w/ double-quoted value + else if ( t1 == "\"" ) { + sub(/^"(.*\\\\+")*[^"]*/, "", value) + if ( length(value) == 0 ) t2 = "" + sub(/^"/, "", value) + } + + # Assignment w/ non-quoted value + else if ( t1 ~ /[^[:space:];]/ ) { + t1 = t2 = "\"" + sub(/^[^[:space:]]*/, "", value) + } + + # Null-assignment + else if ( t1 ~ /[[:space:];]/ ) { t1 = t2 = "\"" } + + printf "%s%c%s%c%s\n", substr($0, 0, matchlen), \ + t1, new_value, t2, value +} +END { exit retval } +' +f_sysrc_set() +{ + local varname="$1" new_value="$2" + + # Check arguments + [ "$varname" ] || return $FAILURE + + # + # Find which rc.conf(5) file contains the last-assignment + # + local not_found= + local file="$( f_sysrc_find "$varname" )" + if [ "$file" = "$RC_DEFAULTS" -o ! "$file" ]; then + # + # We either got a null response (not found) or the variable + # was only found in the rc.conf(5) defaults. In either case, + # let's instead modify the first file from $rc_conf_files. + # + + not_found=1 + + # + # If RC_CONFS is defined, use $RC_CONFS + # rather than $rc_conf_files. + # + if [ "${RC_CONFS+set}" ]; then + file="${RC_CONFS%%[$IFS]*}" + else + file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' ) + fi + fi + + # + # If not found, append new value to last file and return. + # + if [ "$not_found" ]; then + echo "$varname=\"$new_value\"" >> "$file" + return $? + fi + + # + # Perform sanity checks. + # + if [ ! -w "$file" ]; then + f_err "$msg_cannot_create_permission_denied\n" \ + "$pgm" "$file" + return $FAILURE + fi + + # + # Create a new temporary file to write to. + # + local tmpfile="$( mktemp -t "$pgm" )" + [ "$tmpfile" ] || return $FAILURE + + # + # Fixup permissions (else we're in for a surprise, as mktemp(1) creates + # the temporary file with 0600 permissions, and if we simply mv(1) the + # temporary file over the destination, the destination will inherit the + # permissions from the temporary file). + # + local mode + mode=$( stat -f '%#Lp' "$file" 2> /dev/null ) + f_quietly chmod "${mode:-0644}" "$tmpfile" + + # + # Fixup ownership. The destination file _is_ writable (we tested + # earlier above). However, this will fail if we don't have sufficient + # permissions (so we throw stderr into the bit-bucket). + # + local owner + owner=$( stat -f '%u:%g' "$file" 2> /dev/null ) + f_quietly chown "${owner:-root:wheel}" "$tmpfile" + + # + # Operate on the matching file, replacing only the last occurrence. + # + local new_contents retval + new_contents=$( tail -r $file 2> /dev/null ) + new_contents=$( echo "$new_contents" | awk -v varname="$varname" \ + -v new_value="$new_value" "$f_sysrc_set_awk" ) + retval=$? + + # + # Write the temporary file contents. + # + echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE + if [ $retval -ne $SUCCESS ]; then + echo "$varname=\"$new_value\"" >> "$tmpfile" + fi + + # + # Taint-check our results. + # + if ! /bin/sh -n "$tmpfile"; then + f_err "$msg_previous_syntax_errors\n" "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Finally, move the temporary file into place. + # + mv "$tmpfile" "$file" +} + +# f_sysrc_delete $varname +# +# Remove a setting from the system configuration files (edits files in-place). +# Deletes all assignments to the given variable in all config files. If the +# `-f file' option is passed, the removal is restricted to only those files +# specified, otherwise the system collection of rc_conf_files is used. +# +# This function is a two-parter. Below is the awk(1) portion of the function, +# afterward is the sh(1) function which utilizes the below awk script. +# +f_sysrc_delete_awk=' +# Variables that should be defined on the invocation line: +# -v varname="varname" +# +BEGIN { + regex = "^[[:space:]]*"varname"=" + found = 0 +} +{ + if ( $0 ~ regex ) + found = 1 + else + print +} +END { exit ! found } +' +f_sysrc_delete() +{ + local varname="$1" + local file + + # Check arguments + [ "$varname" ] || return $FAILURE + + # + # Operate on each of the specified files + # + for file in ${RC_CONFS-$( f_sysrc_get rc_conf_files )}; do + [ -e "$file" ] || continue + + # + # Create a new temporary file to write to. + # + local tmpfile="$( mktemp -t "$pgm" )" + [ "$tmpfile" ] || return $FAILURE + + # + # Fixup permissions and ownership (mktemp(1) defaults to 0600 + # permissions) to instead match the destination file. + # + local mode owner + mode=$( stat -f '%#Lp' "$file" 2> /dev/null ) + owner=$( stat -f '%u:%g' "$file" 2> /dev/null ) + f_quietly chmod "${mode:-0644}" "$tmpfile" + f_quietly chown "${owner:-root:wheel}" "$tmpfile" + + # + # Operate on the file, removing all occurrences, saving the + # output in our temporary file. + # + awk -v varname="$varname" "$f_sysrc_delete_awk" "$file" \ + > "$tmpfile" + if [ $? -ne $SUCCESS ]; then + # The file didn't contain any assignments + rm -f "$tmpfile" + continue + fi + + # + # Taint-check our results. + # + if ! /bin/sh -n "$tmpfile"; then + f_err "$msg_previous_syntax_errors\n" \ + "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Perform sanity checks + # + if [ ! -w "$file" ]; then + f_err "$msg_permission_denied\n" "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Finally, move the temporary file into place. + # + mv "$tmpfile" "$file" + done +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." sysrc.subr + +fi # ! $_SYSRC_SUBR |