if [ ! "$_STARTUP_RCCONF_SUBR" ]; then _STARTUP_RCCONF_SUBR=1 # # Copyright (c) 2006-2013 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. # # ############################################################ INCLUDES BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." startup/rcconf.subr f_include $BSDCFG_SHARE/sysrc.subr BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup" f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr ############################################################ GLOBALS # # Initialize in-memory cache variables # STARTUP_RCCONF_MAP= _STARTUP_RCCONF_MAP= # # Define what a variable looks like # STARTUP_RCCONF_REGEX="^[[:alpha:]_][[:alnum:]_]*=" # # Default path to on-disk cache file(s) # STARTUP_RCCONF_MAP_CACHEFILE="/var/run/bsdconfig/startup_rcconf_map.cache" ############################################################ FUNCTIONS # f_startup_rcconf_list # # Produce a list of non-default configuration variables configured in the # rc.conf(5) collection of files. # f_startup_rcconf_list() { ( # Operate within a sub-shell to protect the parent environment . "$RC_DEFAULTS" > /dev/null f_clean_env --except PATH STARTUP_RCCONF_REGEX rc_conf_files source_rc_confs > /dev/null export _rc_conf_files_file="$( f_sysrc_find rc_conf_files )" export RC_DEFAULTS set | awk -F= " function test_print(var) { if ( var == \"OPTIND\" ) return if ( var == \"PATH\" ) return if ( var == \"RC_DEFAULTS\" ) return if ( var == \"STARTUP_RCCONF_REGEX\" ) return if ( var == \"_rc_conf_files_file\" ) return if ( var == \"rc_conf_files\" ) { if ( ENVIRON[\"_rc_conf_files_file\"] == \ ENVIRON[\"RC_DEFAULTS\"] ) return } print var } /$STARTUP_RCCONF_REGEX/ { test_print(\$1) }" ) } # f_startup_rcconf_map [$var_to_set] # # Produce a map (beit from in-memory cache or on-disk cache) of rc.conf(5) # variables and their descriptions. The map returned has the following format: # # var description # # With each as follows: # # var the rc.conf(5) variable # description description of the variable # # If $var_to_set is missing or NULL, the map is printed to standard output for # capturing in a sub-shell (which is less-recommended because of performance # degredation; for example, when called in a loop). # f_startup_rcconf_map() { local __funcname=f_startup_rcconf_map local __var_to_set="$1" # If the in-memory cached value is available, return it immediately if [ "$_STARTUP_RCCONF_MAP" ]; then if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$STARTUP_RCCONF_MAP" else echo "$STARTUP_RCCONF_MAP" fi return $SUCCESS fi # # Create the in-memory cache (potentially from validated on-disk cache) # # # Calculate digest used to determine if the on-disk global persistent # cache file (containing this digest on the first line) is valid and # can be used to quickly populate the cache value for immediate return. # local __rc_defaults_digest __rc_defaults_digest=$( exec 2> /dev/null; md5 < "$RC_DEFAULTS" ) # # Check to see if the global persistent cache file exists # if [ -f "$STARTUP_RCCONF_MAP_CACHEFILE" ]; then # # Attempt to populate the in-memory cache with the (soon to be) # validated on-disk cache. If validation fails, fall-back to # the current value and provide error exit status. # STARTUP_RCCONF_MAP=$( ( # Get digest as the first word on first line read digest rest_ignored # # If the stored digest matches the calculated- # one populate the in-memory cache from the on- # disk cache and provide success exit status. # if [ "$digest" = "$__rc_defaults_digest" ] then cat exit $SUCCESS else # Otherwise, return the current value echo "$STARTUP_RCCONF_MAP" exit $FAILURE fi ) < "$STARTUP_RCCONF_MAP_CACHEFILE" ) local __retval=$? export STARTUP_RCCONF_MAP # Make children faster (export cache) if [ $__retval -eq $SUCCESS ]; then export _STARTUP_RCCONF_MAP=1 if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$STARTUP_RCCONF_MAP" else echo "$STARTUP_RCCONF_MAP" fi return $SUCCESS fi # Otherwise, fall-thru to create in-memory cache from scratch fi # # If we reach this point, we need to generate the data from scratch # (and after we do, we'll attempt to create the global persistent # cache file to speed up future executions). # STARTUP_RCCONF_MAP=$( f_clean_env --except \ PATH \ RC_DEFAULTS \ STARTUP_RCCONF_REGEX \ f_sysrc_desc_awk . "$RC_DEFAULTS" # Unset variables we don't want reported unset source_rc_confs_defined for var in $( set | awk -F= " function test_print(var) { if ( var == \"OPTIND\" ) return if ( var == \"PATH\" ) return if ( var == \"RC_DEFAULTS\" ) return if ( var == \"STARTUP_RCCONF_REGEX\" ) return if ( var == \"f_sysrc_desc_awk\" ) return print var } /$STARTUP_RCCONF_REGEX/ { test_print(\$1) } " ); do echo $var "$( f_sysrc_desc $var )" done ) export STARTUP_RCCONF_MAP export _STARTUP_RCCONF_MAP=1 if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$STARTUP_RCCONF_MAP" else echo "$STARTUP_RCCONF_MAP" fi # # Attempt to create the persistent global cache # # Create a new temporary file to write to local __tmpfile f_eval_catch -dk __tmpfile $__funcname mktemp \ 'mktemp -t "%s"' "$pgm" || return $FAILURE # Write the temporary file contents echo "$__rc_defaults_digest" > "$__tmpfile" echo "$STARTUP_RCCONF_MAP" >> "$__tmpfile" # Finally, move the temporary file into place case "$STARTUP_RCCONF_MAP_CACHEFILE" in */*) f_eval_catch -d $__funcname mkdir \ 'mkdir -p "%s"' "${STARTUP_RCCONF_MAP_CACHEFILE%/*}" esac f_eval_catch -d $__funcname mv \ 'mv "%s" "%s"' "$__tmpfile" "$STARTUP_RCCONF_MAP_CACHEFILE" } # f_startup_rcconf_map_expand $var_to_get # # Expands the map ($var_to_get) into the shell environment namespace by # creating _${var}_desc variables containing the description of each variable # encountered. # # NOTE: Variables are exported for later-required awk(1) ENVIRON visibility. # f_startup_rcconf_map_expand() { local var_to_get="$1" eval "$( debug= f_getvar "$var_to_get" | awk ' BEGIN { rword = "^[[:space:]]*[^[:space:]]*[[:space:]]*" } { var = $1 desc = $0 sub(rword, "", desc) gsub(/'\''/, "'\''\\'\'\''", desc) printf "_%s_desc='\''%s'\''\n", var, desc printf "export _%s_desc\n", var }' )" } # f_dialog_input_view_details # # Display a menu for selecting which details are to be displayed. The following # variables are tracked/modified by the menu/user's selection: # # SHOW_DESC Show or hide descriptions # # Mutually exclusive options: # # SHOW_VALUE Show the value (default; override only) # SHOW_DEFAULT_VALUE Show both value and default # SHOW_CONFIGURED Show rc.conf(5) file variable is configured in # # Each variable is treated as a boolean (NULL for false, non-NULL for true). # # Variables are exported for later-required awk(1) ENVIRON visibility. Returns # success unless the user chose `Cancel' or pressed Escape. # f_dialog_input_view_details() { local prompt= local menu_list # calculated below local defaultitem= # calculated below local hline="$hline_arrows_tab_enter" # Calculate marks for checkboxes and radio buttons local md=" " if [ "$SHOW_DESC" ]; then md="X" fi local m1=" " m2=" " m3=" " if [ "$SHOW_VALUE" ]; then m1="*" defaultitem="1 ($m1) $msg_show_value" elif [ "$SHOW_DEFAULT_VALUE" ]; then m2="*" defaultitem="2 ($m2) $msg_show_default_value" elif [ "$SHOW_CONFIGURED" ]; then m3="*" defaultitem="3 ($m3) $msg_show_configured" fi # Create the menu list with the above-calculated marks menu_list=" 'R $msg_reset' '$msg_reset_desc' 'D [$md] $msg_desc' '$msg_desc_desc' '1 ($m1) $msg_show_value' '$msg_show_value_desc' '2 ($m2) $msg_show_default_value' '$msg_show_default_value_desc' '3 ($m3) $msg_show_configured' '$msg_show_configured_desc' " # END-QUOTE local height width rows eval f_dialog_menu_size height width rows \ \"\$DIALOG_TITLE\" \ \"\$DIALOG_BACKTITLE\" \ \"\$prompt\" \ \"\$hline\" \ $menu_list f_dialog_title "$msg_choose_view_details" local mtag mtag=$( eval $DIALOG \ --title \"\$DIALOG_TITLE\" \ --backtitle \"\$DIALOG_BACKTITLE\" \ --hline \"\$hline\" \ --ok-label \"\$msg_ok\" \ --cancel-label \"\$msg_cancel\" \ --default-item \"\$defaultitem\" \ --menu \"\$prompt\" \ $height $width $rows \ $menu_list \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) local retval=$? f_dialog_data_sanitize mtag f_dialog_title_restore [ $retval -eq $DIALOG_OK ] || return $DIALOG_CANCEL case "$mtag" in "R $msg_reset") SHOW_VALUE=1 SHOW_DESC=1 SHOW_DEFAULT_VALUE= SHOW_CONFIGURED= ;; "D [X] $msg_desc") SHOW_DESC= ;; "D [ ] $msg_desc") SHOW_DESC=1 ;; "1 ("?") $msg_show_value") SHOW_VALUE=1 SHOW_DEFAULT_VALUE= SHOW_CONFIGURED= ;; "2 ("?") $msg_show_default_value") SHOW_VALUE= SHOW_DEFAULT_VALUE=1 SHOW_CONFIGURED= ;; "3 ("?") $msg_show_configured") SHOW_VALUE= SHOW_DEFAULT_VALUE= SHOW_CONFIGURED=1 ;; esac } # f_dialog_input_rclist [$default] # # Presents a menu of rc.conf(5) defaults (with, or without descriptions). This # function should be treated like a call to dialog(1) (the exit status should # be captured and f_dialog_menutag_fetch() should be used to get the user's # response). Optionally if present and non-null, highlight $default rcvar. # f_dialog_input_rclist() { local prompt="$msg_please_select_an_rcconf_directive" local menu_list=" 'X $msg_exit' '' ${SHOW_DESC:+'$msg_exit_this_menu'} " # END-QUOTE local defaultitem="$1" local hline="$hline_arrows_tab_enter" if [ ! "$_RCCONF_MAP" ]; then # Generate RCCONF_MAP of `var desc ...' per-line f_dialog_info "$msg_creating_rcconf_map" RCCONF_MAP=$( f_startup_rcconf_map ) export RCCONF_MAP # Generate _${var}_desc variables from $RCCONF_MAP f_startup_rcconf_map_expand export _RCCONF_MAP=1 fi menu_list="$menu_list $( export SHOW_DESC echo "$RCCONF_MAP" | awk ' BEGIN { prefix = "" rword = "^[[:space:]]*[^[:space:]]*[[:space:]]*" } { cur_prefix = tolower(substr($1, 1, 1)) printf "'\''" if ( prefix != cur_prefix ) prefix = cur_prefix else printf " " rcvar = $1 printf "%s'\'' '\'\''", rcvar if ( ENVIRON["SHOW_DESC"] ) { desc = $0 sub(rword, "", desc) gsub(/'\''/, "'\''\\'\'\''", desc) printf " '\''%s'\''", desc } printf "\n" }' )" set -f # set noglob because descriptions in the $menu_list may contain # `*' and get expanded by dialog(1) (doesn't affect Xdialog(1)). # This prevents dialog(1) from expanding wildcards in help line. local height width rows eval f_dialog_menu${SHOW_DESC:+_with_help}_size \ height width rows \ \"\$DIALOG_TITLE\" \ \"\$DIALOG_BACKTITLE\" \ \"\$prompt\" \ \"\$hline\" \ $menu_list local menu_choice menu_choice=$( eval $DIALOG \ --title \"\$DIALOG_TITLE\" \ --backtitle \"\$DIALOG_BACKTITLE\" \ --hline \"\$hline\" \ --default-item \"\$defaultitem\" \ --ok-label \"\$msg_ok\" \ --cancel-label \"\$msg_cancel\" \ ${SHOW_DESC:+--item-help} \ --menu \"\$prompt\" \ $height $width $rows \ $menu_list \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) local retval=$? f_dialog_menutag_store -s "$menu_choice" return $retval } # f_dialog_input_rcvar [$init] # # Allows the user to enter the name for a new rc.conf(5) variable. If the user # does not cancel or press ESC, the $rcvar variable will hold the newly- # configured value upon return. # f_dialog_input_rcvar() { # # Loop until the user provides taint-free/valid input # local _input="$1" while :; do # Return if user either pressed ESC or chosen Cancel/No f_dialog_input _input "$msg_please_enter_rcvar_name" \ "$_input" "$hline_alnum_tab_enter" || return $? # Check for invalid entry (1of2) if ! echo "$_input" | grep -q "^[[:alpha:]_]"; then f_show_msg "$msg_rcvar_must_start_with" continue fi # Check for invalid entry (2of2) if ! echo "$_input" | grep -q "^[[:alpha:]_][[:alnum:]_]*$" then f_show_msg "$msg_rcvar_contains_invalid_chars" continue fi rcvar="$_input" break done f_dprintf "f_dialog_input_rcvar: rcvar->[%s]" "$rcvar" return $DIALOG_OK } ############################################################ MAIN f_dprintf "%s: Successfully loaded." startup/rcconf.subr fi # ! $_STARTUP_RCCONF_SUBR