634 lines
16 KiB
Plaintext
634 lines
16 KiB
Plaintext
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.local)
|
|
#
|
|
# 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
|