From owner-freebsd-rc@FreeBSD.ORG Tue Oct 12 23:13:04 2010 Return-Path: Delivered-To: freebsd-rc@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id E7A4C1065672 for ; Tue, 12 Oct 2010 23:13:04 +0000 (UTC) (envelope-from Devin.Teske@fisglobal.com) Received: from mailoutltc.fnis.com (mailoutltc.fnis.com [199.200.24.190]) by mx1.freebsd.org (Postfix) with ESMTP id AF7248FC12 for ; Tue, 12 Oct 2010 23:13:04 +0000 (UTC) Received: from sbhfislrext02.fnfis.com ([192.168.249.140]) by SCSFISLTC01 (8.14.3/8.14.3) with ESMTP id o9CND4pp012526 for ; Tue, 12 Oct 2010 18:13:04 -0500 Received: from sbhfisltcgw02.FNFIS.COM (Not Verified[10.132.248.122]) by sbhfislrext02.fnfis.com with MailMarshal (v6, 5, 4, 7535) id ; Tue, 12 Oct 2010 18:12:58 -0500 Received: from sbhfisltcgw02.FNFIS.COM ([10.132.248.122]) by sbhfisltcgw02.FNFIS.COM with Microsoft SMTPSVC(6.0.3790.4675); Tue, 12 Oct 2010 18:13:03 -0500 Received: from dt.vicor.com ([10.14.152.1]) by sbhfisltcgw02.FNFIS.COM over TLS secured channel with Microsoft SMTPSVC(6.0.3790.4675); Tue, 12 Oct 2010 18:13:03 -0500 From: Devin Teske To: freebsd-rc@freebsd.org Content-Type: text/plain Date: Tue, 12 Oct 2010 16:13:02 -0700 Message-Id: <1286925182.32724.18.camel@localhost.localdomain> Mime-Version: 1.0 X-Mailer: Evolution 2.0.2 (2.0.2-41.el4) Content-Transfer-Encoding: 7bit X-OriginalArrivalTime: 12 Oct 2010 23:13:03.0460 (UTC) FILETIME=[055EDA40:01CB6A63] Subject: sysrc(8) -- a sysctl(8)-like utility for managing rc.conf(5) X-BeenThere: freebsd-rc@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "Discussion related to /etc/rc.d design and implementation." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 12 Oct 2010 23:13:05 -0000 Hey all, Long-time user, first-time poster (to this list at least). Over on the -hackers@ mailing-list, I posted the first version of a script I've written named sysrc(8), designed to ease management of the rc.conf(5) files. There were a lot of great discussions about this over on -hackers@: http://lists.freebsd.org/pipermail/freebsd-hackers/2010- October/thread.html I'm now happy to post the second version of this script which attempts to address all of the concerns that -hackers@ brought-up. 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. _____________