Date: Tue, 12 Oct 2010 17:01:14 -0700 From: Devin Teske <devin.teske@fisglobal.com> To: freebsd-rc@freebsd.org Subject: Re: sysrc(8) -- a sysctl(8)-like utility for managing rc.conf(5) Message-ID: <1286928074.32724.29.camel@localhost.localdomain> In-Reply-To: <1286925182.32724.18.camel@localhost.localdomain> References: <1286925182.32724.18.camel@localhost.localdomain>
next in thread | previous in thread | raw e-mail | index | archive | help
On Tue, 2010-10-12 at 16:13 -0700, Devin Teske wrote: > [...] > Behold... sysrc(8) v2.0 > > NOTE: just scroll down a little to the INFORMATION section for an easy- > to-read usage statement. > > #!/bin/sh > # -*- tab-width: 4 -*- ;; Emacs > # vi: set tabstop=4 :: Vi/ViM > # > # Revision: 2.0 > # Last Modified: October 12th, 2010 > ############################################################ COPYRIGHT > # > # (c)2010. 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. > # > # AUTHOR DATE DESCRIPTION > # dteske 2010.10.12 Updates per freebsd-hackers thread. > # dteske 2010.09.29 Initial version. > # > ############################################################ INFORMATION > # > # Command Usage: > # > # sysrc [OPTIONS] name[=value] ... > # > # OPTIONS: > # -h Print this message to stderr and exit. > # -f file Operate on the specified file(s) instead of rc_conf_files. > # -a Dump a list of non-default configuration variables. > # -A Dump a list of all configuration variables (incl. defaults). > # -d Print a description of the given variable. > # -e Print query results as `var=value' (useful for producing > # output to be fed back in). Ignored if -n is specified. > # -v Verbose. Print the pathname of the specific rc.conf(5) > # file where the directive was found. > # -i Ignore unknown variables. > # -n Show only variable values, not their names. > # -N Show only variable names, not their values. > # > # ENVIRONMENT: > # RC_DEFAULTS Location of `/etc/defaults/rc.conf' file. > # SYSRC_VERBOSE Default verbosity. Set to non-NULL to enable. > # > ############################################################ CONFIGURATION > > # > # Default verbosity. > # > : ${SYSRC_VERBOSE:=} > > # > # Default location of the rc.conf(5) defaults configuration file. > # > : ${RC_DEFAULTS:="/etc/defaults/rc.conf"} > > ############################################################ GLOBALS > > # > # Global exit status variables > # > SUCCESS=0 > FAILURE=1 > > # > # Program name > # > progname="${0##*/}" > > # > # Options > # > DESCRIBE= > RC_CONFS= > IGNORE_UNKNOWNS= > SHOW_ALL= > SHOW_EQUALS= > SHOW_NAME=1 > SHOW_VALUE=1 > > ############################################################ FUNCTION > > # fprintf $fd $fmt [ $opts ... ] > # > # Like printf, except allows you to print to a specific file-descriptor. Useful > # for printing to stderr (fd=2) or some other known file-descriptor. > # > fprintf() > { > local fd=$1 > [ $# -gt 1 ] || return $FAILURE > shift 1 > printf "$@" >&$fd > } > > # eprintf $fmt [ $opts ... ] > # > # Print a message to stderr (fd=2). > # > eprintf() > { > fprintf 2 "$@" > } > > # die [ $fmt [ $opts ... ]] > # > # Optionally print a message to stderr before exiting with failure status. > # > die() > { > local fmt="$1" > [ $# -gt 0 ] && shift 1 > [ "$fmt" ] && eprintf "$fmt\n" "$@" > > exit $FAILURE > } > > # usage > # > # Prints a short syntax statement and exits. > # > usage() > { > local optfmt="\t%-11s%s\n" > local envfmt="\t%-17s%s\n" > > eprintf "Usage: %s [OPTIONS] name[=value] ...\n" "$progname" > > eprintf "OPTIONS:\n" > eprintf "$optfmt" "-h" \ > "Print this message to stderr and exit." > eprintf "$optfmt" "-f file" \ > "Operate on the specified file(s) instead of rc_conf_files." > eprintf "$optfmt" "-a" \ > "Dump a list of non-default configuration variables." > eprintf "$optfmt" "-A" \ > "Dump a list of all configuration variables (incl. defaults)." > eprintf "$optfmt" "-d" \ > "Print a description of the given variable." > eprintf "$optfmt" "-e" \ > "Print query results as \`var=value' (useful for producing" > eprintf "$optfmt" "" \ > "output to be fed back in). Ignored if -n is specified." > eprintf "$optfmt" "-v" \ > "Verbose. Print the pathname of the specific rc.conf(5)" > eprintf "$optfmt" "" \ > "file where the directive was found." > eprintf "$optfmt" "-i" \ > "Ignore unknown variables." > eprintf "$optfmt" "-n" \ > "Show only variable values, not their names." > eprintf "$optfmt" "-N" \ > "Show only variable names, not their values." > eprintf "\n" > > eprintf "ENVIRONMENT:\n" > eprintf "$envfmt" "RC_DEFAULTS" \ > "Location of \`/etc/defaults/rc.conf' file." > eprintf "$envfmt" "SYSRC_VERBOSE" \ > "Default verbosity. Set to non-NULL to enable." > > die > } > > # 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 enabled the exclusion-list as the remainder of > # positional arguments. > # > # Be careful not to call this in a shell that you still except to perform > # $PATH expansion in, because this will blow $PATH away. This is best used > # within a sub-shell block "(...)" or "$(...)" or "`...`". > # > 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 > } > > # 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). > # > 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. > # > clean_env --except RC_CONFS RC_DEFAULTS > > . "$RC_DEFAULTS" > > # > # If `-f file' was passed, set $rc_conf_files to an explicit > # value, modifying the default behavior of source_rc_confs(). > # > [ "$RC_CONFS" ] && rc_conf_files="$RC_CONFS" > > unset RC_CONFS > # no longer needed > > source_rc_confs > > # > # 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 > ) > } > > # 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. > # > sysrc_find() > { > local varname="$1" > local rc_conf_files="$( sysrc_get rc_conf_files )" > local conf_files= > local file > > # Check parameters > [ "$varname" ] || return $FAILURE > > # > # If `-f file' was passed, set $rc_conf_files to an explicit > # value, modifying the default behavior of source_rc_confs(). > # > [ "$RC_CONFS" ] && 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 > if grep -q "^[[:space:]]*$varname=" $file; then > echo $file > return $SUCCESS > fi > done > > return $FAILURE # Not found > } > > # ... | lrev > # lrev $file ... > # > # Reverse lines of input. Unlike rev(1) which reverses the ordering of > # characters on a single line, this function instead reverses the line > # sequencing. > # > # For example, the following input: > # > # Line 1 > # Line 2 > # Line 3 > # > # Becomes reversed in the following manner: > # > # Line 3 > # Line 2 > # Line 1 > # > lrev() > { > local stdin_rev= > if [ $# -gt 0 ]; then > # > # Reverse lines from files passed as positional arguments. > # > while [ $# -gt 0 ]; do > local file="$1" > [ -f "$file" ] && lrev < "$file" > shift 1 > done > else > # > # Reverse lines from standard input > # > while read -r LINE; do > stdin_rev="$LINE > $stdin_rev" > done > fi > > echo -n "$stdin_rev" > } > > # 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'. > # > 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="$( 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 `-f file' was passed, use $RC_CONFS > # rather than $rc_conf_files. > # > if [ "$RC_CONFS" ]; then > file="${RC_CONFS%%[$IFS]*}" > else > file="$( sysrc_get "rc_conf_files%%[$IFS]*" )" > fi > fi > > # > # Perform sanity checks. > # > if [ ! -w $file ]; then > eprintf "\n%s: cannot create %s: Permission denied\n" \ > "$progname" "$file" > return $FAILURE > fi > > # > # If not found, append new value to last file and return. > # > if [ "$not_found" ]; then > echo "$varname=\"$new_value\"" >> "$file" > return $SUCCESS > fi > > # > # Operate on the matching file, replacing only the last occurrence. > # > local new_contents="`lrev $file 2> /dev/null | \ > ( found= > while read -r LINE; do > if [ ! "$found" ]; then > match="$( echo "$LINE" | grep "$regex" )" > if [ "$match" ]; then > LINE="$varname"'="'"$new_value"'"' > found=1 > fi > fi > echo "$LINE" > done > ) | lrev`" > > [ "$new_contents" ] || return $FAILURE > > # > # Create a new temporary file to write to. > # > local tmpfile="$( mktemp -t "$progname" )" > [ "$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). > # > chmod $( stat -f '%#Lp' "$file" ) "$tmpfile" 2> /dev/null > > # > # Fixup ownerhsip. 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). > # > chown $( stat -f '%u:%g' "$file" ) "$tmpfile" 2> /dev/null > > # > # Write the temporary file contents and move it into place. > # > echo "$new_contents" > "$tmpfile" || return $FAILURE > mv "$tmpfile" "$file" > } > > # 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. > # > sysrc_desc() > { > local varname="$1" > > ( > buffer= > while read LINE; do > case "$LINE" in > $varname=*) > buffer="$LINE" > break > esac > done > > # Return if the variable wasn't found > [ "$buffer" ] || return $FAILURE > > regex='[[:alpha:]_][[:alnum:]_]*=' > while read LINE; do > # > # Stop reading comments if we reach a new assignment > # directive or if the line contains only whitespace > # > echo "$LINE" | grep -q "^[[:space:]]*$regex" && break > echo "$LINE" | grep -q "^[[:space:]]*#$regex" && break > echo "$LINE" | grep -q "^[[:space:]]*$" && break > > # Append new line to buffer > buffer="$buffer > $LINE" > done > > # Return if the buffer is empty > [ "$buffer" ] || return $FAILURE > > # > # Clean up the buffer. > # > regex='^[^#]*\(#[[:space:]]*\)\{0,1\}' > buffer="$( echo "$buffer" | sed -e "s/$regex//" )" > buffer="$( echo "$buffer" | tr '\n' ' ' \ > | sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//' )" > > echo "$buffer" > > ) < "$RC_DEFAULTS" > } > > ############################################################ MAIN SOURCE > > # > # Perform sanity checks > # > [ $# -gt 0 ] || usage > > # > # Process command-line options > # > while getopts hf:aAdevinN flag; do > case "$flag" in > h) usage;; > f) RC_CONFS="$OPTARG";; > a) SHOW_ALL=1;; > A) SHOW_ALL=2;; > d) DESCRIBE=1;; > e) SHOW_EQUALS=1;; > v) SYSRC_VERBOSE=1;; > i) IGNORE_UNKNOWNS=1;; > n) SHOW_NAME=;; > N) SHOW_VALUE=;; > \?) usage;; > esac > done > shift $(( $OPTIND - 1 )) > > # > # Process command-line options > # > SEP=': ' > [ "$SHOW_EQUALS" ] && SEP='="' > [ "$SHOW_NAME" ] || SHOW_EQUALS= > [ "$SYSRC_VERBOSE" = "0" ] && SYSRC_VERBOSE= > if [ ! "$SHOW_VALUE" ]; then > SHOW_NAME=1 > SHOW_EQUALS= > fi > > if [ "$SHOW_ALL" ]; then > # > # Get a list of variables that are currently set in the rc.conf(5) > # files (included `/etc/defaults/rc.conf') by performing a call to > # source_rc_confs() in a clean environment. > # > ( > # > # Set which variables we want to preserve in the environment. > # Append the pipe-character (|) to the list of internal field > # separation (IFS) characters, allowing us to use the below > # list both as an extended grep (-E) pattern and argument list > # (required to first get clean_env() to preserve these in the > # environment and then later to prune them from the list of > # variables produced by set(1)). > # > IFS="$IFS|" > EXCEPT="IFS|EXCEPT|PATH|RC_DEFAULTS|OPTIND|DESCRIBE|SEP" > EXCEPT="$EXCEPT|SHOW_ALL|SHOW_EQUALS|SHOW_NAME|SHOW_VALUE" > EXCEPT="$EXCEPT|SYSRC_VERBOSE|RC_CONFS" > > # > # Clean the environment (except for our required variables) > # and then source the required files. > # > clean_env --except $EXCEPT > if [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ]; then > . "$RC_DEFAULTS" > > # > # If passed `-a' (rather than `-A'), re-purge the > # environment, removing the rc.conf(5) defaults. > # > [ "$SHOW_ALL" = "1" ] \ > && clean_env --except rc_conf_files $EXCEPT > > # > # If `-f file' was passed, set $rc_conf_files to an > # explicit value, modifying the default behavior of > # source_rc_confs(). > # > [ "$RC_CONFS" ] && rc_conf_files="$RC_CONFS" > > source_rc_confs > > # > # If passed `-a' (rather than `-A'), remove > # `rc_conf_files' unless it was defined somewhere > # other than rc.conf(5) defaults. > # > [ "$SHOW_ALL" = "1" -a \ > "$( sysrc_find rc_conf_files )" = "$RC_DEFAULTS" \ > ] \ > && unset rc_conf_files > fi > > for NAME in $( set | awk -F= \ > '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \ > | grep -Ev "^($EXCEPT)$" > ); do > # > # If enabled, describe rather than expand value > # > if [ "$DESCRIBE" ]; then > echo "$NAME: $( sysrc_desc "$NAME" )" > continue > fi > > [ "$SYSRC_VERBOSE" ] && \ > echo -n "$( sysrc_find "$NAME" ): " > > # > # If `-N' is passed, simplify the output > # > if [ ! "$SHOW_VALUE" ]; then > echo "$NAME" > continue > fi > > echo "${SHOW_NAME:+$NAME$SEP}$( > sysrc_get "$NAME" )${SHOW_EQUALS:+\"}" > done > ) > > # > # Ignore the remainder of positional arguments. > # > exit $SUCCESS > fi > > # > # Process command-line arguments > # > while [ $# -gt 0 ]; do > NAME="${1%%=*}" > > [ "$DESCRIBE" ] && \ > echo "$NAME: $( sysrc_desc "$NAME" )" > > case "$1" in > *=*) > # > # Like sysctl(8), if both `-d' AND "name=value" is passed, > # first describe, then attempt to set > # > > if [ "$SYSRC_VERBOSE" ]; then > file="$( sysrc_find "$NAME" )" > [ "$file" = "$RC_DEFAULTS" -o ! "$file" ] && \ > file="$( sysrc_get "rc_conf_files%%[$IFS]*" )" > echo -n "$file: " > fi > > # > # If `-N' is passed, simplify the output > # > if [ ! "$SHOW_VALUE" ]; then > echo "$NAME" > sysrc_set "$NAME" "${1#*}" > else > echo -n "${SHOW_NAME:+$NAME$SEP}$( > sysrc_get "$NAME" )${SHOW_EQUALS:+\"}" > if sysrc_set "$NAME" "${1#*=}"; then > echo " -> $( sysrc_get "$NAME" )" > fi > fi > ;; > *) > if ! IGNORED="$( sysrc_get "$NAME?" )"; then > [ "$IGNORE_UNKNOWNS" ] \ > || echo "$progname: unknown variable '$NAME'" > shift 1 > continue > fi > > # > # Like sysctl(8), when `-d' is passed, > # desribe it rather than expanding it > # > > if [ "$DESCRIBE" ]; then > shift 1 > continue > fi > > [ "$SYSRC_VERBOSE" ] && \ > echo -n "$( sysrc_find "$NAME" ): " > > # > # If `-N' is passed, simplify the output > # > if [ ! "$SHOW_VALUE" ]; then > echo "$NAME" > else > echo "${SHOW_NAME:+$NAME$SEP}$( > sysrc_get "$NAME" )${SHOW_EQUALS:+\"}" > fi > esac > shift 1 > done > > > > -- > Cheers, > Devin Teske > > -> CONTACT INFORMATION <- > Business Solutions Consultant II > FIS - fisglobal.com > 510-735-5650 Mobile > 510-621-2038 Office > 510-621-2020 Office Fax > 909-477-4578 Home/Fax > devin.teske@fisglobal.com > > -> LEGAL DISCLAIMER <- > This message contains confidential and proprietary information > of the sender, and is intended only for the person(s) to whom it > is addressed. Any use, distribution, copying or disclosure by any > other person is strictly prohibited. If you have received this > message in error, please notify the e-mail sender immediately, > and delete the original message without making a copy. > > -> FUN STUFF <- > -----BEGIN GEEK CODE BLOCK----- > Version 3.1 > GAT/CS d(+) s: a- C++(++++) UB++++$ P++(++++) L++(++++) !E--- W++ N? o? K- w O > M+ V- PS+ PE Y+ PGP- t(+) 5? X+(++) R>++ tv(+) b+(++) DI+(++) D(+) G+>++ e>+ h > r>++ y+ > ------END GEEK CODE BLOCK------ > http://www.geekcode.com/ > > -> END TRANSMISSION <- > > _____________ > > The information contained in this message is proprietary and/or confidential. If you are not the intended recipient, please: (i) delete the message and all copies; (ii) do not disclose, distribute or use the message in any manner; and (iii) notify the sender immediately. In addition, please be aware that any message addressed to our domain is subject to archiving and review by persons other than the intended recipient. Thank you. > _____________ > _______________________________________________ > freebsd-rc@freebsd.org mailing list > http://lists.freebsd.org/mailman/listinfo/freebsd-rc > To unsubscribe, send any mail to "freebsd-rc-unsubscribe@freebsd.org" _____________ The information contained in this message is proprietary and/or confidential. If you are not the intended recipient, please: (i) delete the message and all copies; (ii) do not disclose, distribute or use the message in any manner; and (iii) notify the sender immediately. In addition, please be aware that any message addressed to our domain is subject to archiving and review by persons other than the intended recipient. Thank you. _____________
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?1286928074.32724.29.camel>