Index: head/usr.sbin/bsdconfig/share/common.subr =================================================================== --- head/usr.sbin/bsdconfig/share/common.subr (revision 298883) +++ head/usr.sbin/bsdconfig/share/common.subr (revision 298884) @@ -1,1046 +1,1046 @@ if [ ! "$_COMMON_SUBR" ]; then _COMMON_SUBR=1 # # Copyright (c) 2012 Ron McDowell # Copyright (c) 2012-2016 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$ # ############################################################ CONFIGURATION # # Default file descriptors to link to stdout/stderr for passthru allowing # redirection within a sub-shell to bypass directly to the terminal. # : ${TERMINAL_STDOUT_PASSTHRU:=3} : ${TERMINAL_STDERR_PASSTHRU:=4} ############################################################ GLOBALS # # Program name # pgm="${0##*/}" # # Program arguments # ARGC="$#" ARGV="$@" # # Global exit status variables # SUCCESS=0 FAILURE=1 # # Operating environment details # export UNAME_S="$( uname -s )" # Operating System (i.e. FreeBSD) export UNAME_P="$( uname -p )" # Processor Architecture (i.e. i386) export UNAME_M="$( uname -m )" # Machine platform (i.e. i386) export UNAME_R="$( uname -r )" # Release Level (i.e. X.Y-RELEASE) # # Default behavior is to call f_debug_init() automatically when loaded. # : ${DEBUG_SELF_INITIALIZE=1} # # Default behavior of f_debug_init() is to truncate $debugFile (set to NULL to # disable truncating the debug file when initializing). To get child processes # to append to the same log file, export this variarable (with a NULL value) # and also export debugFile with the desired value. # : ${DEBUG_INITIALIZE_FILE=1} # # Define standard optstring arguments that should be supported by all programs # using this include (unless DEBUG_SELF_INITIALIZE is set to NULL to prevent # f_debug_init() from autamatically processing "$@" for the below arguments): # # d Sets $debug to 1 # D: Sets $debugFile to $OPTARG # GETOPTS_STDARGS="dD:" # # The getopts builtin will return 1 either when the end of "$@" or the first # invalid flag is reached. This makes it impossible to determine if you've # processed all the arguments or simply have hit an invalid flag. In the cases # where we want to tolerate invalid flags (f_debug_init() for example), the # following variable can be appended to your optstring argument to getopts, # preventing it from prematurely returning 1 before the end of the arguments. # # NOTE: This assumes that all unknown flags are argument-less. # GETOPTS_ALLFLAGS="abcdefghijklmnopqrstuvwxyz" GETOPTS_ALLFLAGS="${GETOPTS_ALLFLAGS}ABCDEFGHIJKLMNOPQRSTUVWXYZ" GETOPTS_ALLFLAGS="${GETOPTS_ALLFLAGS}0123456789" # # When we get included, f_debug_init() will fire (unless $DEBUG_SELF_INITIALIZE # is set to disable automatic initialization) and process "$@" for a few global # options such as `-d' and/or `-D file'. However, if your program takes custom # flags that take arguments, this automatic processing may fail unexpectedly. # # The solution to this problem is to pre-define (before including this file) # the following variable (which defaults to NULL) to indicate that there are # extra flags that should be considered when performing automatic processing of # globally persistent flags. # : ${GETOPTS_EXTRA:=} ############################################################ FUNCTIONS # f_dprintf $format [$arguments ...] # # Sensible debug function. Override in ~/.bsdconfigrc if desired. # See /usr/share/examples/bsdconfig/bsdconfigrc for example. # # If $debug is set and non-NULL, prints DEBUG info using printf(1) syntax: # + To $debugFile, if set and non-NULL # + To standard output if $debugFile is either NULL or unset # + To both if $debugFile begins with a single plus-sign (`+') # f_dprintf() { [ "$debug" ] || return $SUCCESS local fmt="$1"; shift case "$debugFile" in ""|+*) printf "DEBUG: $fmt${fmt:+\n}" "$@" >&${TERMINAL_STDOUT_PASSTHRU:-1} esac [ "${debugFile#+}" ] && printf "DEBUG: $fmt${fmt:+\n}" "$@" >> "${debugFile#+}" return $SUCCESS } # f_debug_init # # Initialize debugging. Truncates $debugFile to zero bytes if set. # f_debug_init() { # # Process stored command-line arguments # set -- $ARGV local OPTIND OPTARG flag f_dprintf "f_debug_init: ARGV=[%s] GETOPTS_STDARGS=[%s]" \ "$ARGV" "$GETOPTS_STDARGS" while getopts "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" flag \ > /dev/null; do case "$flag" in d) debug=1 ;; D) debugFile="$OPTARG" ;; esac done shift $(( $OPTIND - 1 )) f_dprintf "f_debug_init: debug=[%s] debugFile=[%s]" \ "$debug" "$debugFile" # # Automagically enable debugging if debugFile is set (and non-NULL) # [ "$debugFile" ] && { [ "${debug+set}" ] || debug=1; } # - # Make debugging persistant if set + # Make debugging persistent if set # [ "$debug" ] && export debug [ "$debugFile" ] && export debugFile # # Truncate debug file unless requested otherwise. Note that we will # trim a leading plus (`+') from the value of debugFile to support - # persistant meaning that f_dprintf() should print both to standard + # persistent meaning that f_dprintf() should print both to standard # output and $debugFile (minus the leading plus, of course). # local _debug_file="${debugFile#+}" if [ "$_debug_file" -a "$DEBUG_INITIALIZE_FILE" ]; then if ( umask 022 && :> "$_debug_file" ); then f_dprintf "Successfully initialized debugFile \`%s'" \ "$_debug_file" f_isset debug || debug=1 # turn debugging on if not set else unset debugFile f_dprintf "Unable to initialize debugFile \`%s'" \ "$_debug_file" fi fi } # f_err $format [$arguments ...] # # Print a message to stderr (fd=2). # f_err() { printf "$@" >&2 } # f_quietly $command [$arguments ...] # # Run a command quietly (quell any output to stdout or stderr) # f_quietly() { "$@" > /dev/null 2>&1 } # f_have $anything ... # # A wrapper to the `type' built-in. Returns true if argument is a valid shell # built-in, keyword, or externally-tracked binary, otherwise false. # f_have() { f_quietly type "$@" } # setvar $var_to_set [$value] # # Implement setvar for shells unlike FreeBSD sh(1). # if ! f_have setvar; then setvar() { [ $# -gt 0 ] || return $SUCCESS local __setvar_var_to_set="$1" __setvar_right="$2" __setvar_left= case $# in 1) unset "$__setvar_var_to_set" return $? ;; 2) : fall through ;; *) f_err "setvar: too many arguments\n" return $FAILURE esac case "$__setvar_var_to_set" in *[!0-9A-Za-z_]*) f_err "setvar: %s: bad variable name\n" "$__setvar_var_to_set" return 2 esac while case "$__setvar_r" in *\'*) : ;; *) false ; esac do __setvar_left="$__setvar_left${__setvar_right%%\'*}'\\''" __setvar_right="${__setvar_right#*\'}" done __setvar_left="$__setvar_left${__setvar_right#*\'}" eval "$__setvar_var_to_set='$__setvar_left'" } fi # f_which $anything [$var_to_set] # # A fast built-in replacement for syntaxes such as foo=$( which bar ). In a # comparison of 10,000 runs of this function versus which, this function # completed in under 3 seconds, while `which' took almost a full minute. # # If $var_to_set is missing or NULL, output is (like which) to standard out. # Returns success if a match was found, failure otherwise. # f_which() { local __name="$1" __var_to_set="$2" case "$__name" in */*|'') return $FAILURE; esac local __p __exec IFS=":" __found= for __p in $PATH; do __exec="$__p/$__name" [ -f "$__exec" -a -x "$__exec" ] && __found=1 break done if [ "$__found" ]; then if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__exec" else echo "$__exec" fi return $SUCCESS fi return $FAILURE } # f_getvar $var_to_get [$var_to_set] # # Utility function designed to go along with the already-builtin setvar. # Allows clean variable name indirection without forking or sub-shells. # # Returns error status if the requested variable ($var_to_get) is not set. # # If $var_to_set is missing or NULL, the value of $var_to_get 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_getvar() { local __var_to_get="$1" __var_to_set="$2" [ "$__var_to_set" ] || local value eval [ \"\${$__var_to_get+set}\" ] local __retval=$? eval ${__var_to_set:-value}=\"\${$__var_to_get}\" eval f_dprintf '"f_getvar: var=[%s] value=[%s] r=%u"' \ \"\$__var_to_get\" \"\$${__var_to_set:-value}\" \$__retval [ "$__var_to_set" ] || { [ "$value" ] && echo "$value"; } return $__retval } # f_isset $var # # Check if variable $var is set. Returns success if variable is set, otherwise # returns failure. # f_isset() { eval [ \"\${${1%%[$IFS]*}+set}\" ] } # f_die [$status [$format [$arguments ...]]] # # Abruptly terminate due to an error optionally displaying a message in a # dialog box using printf(1) syntax. # f_die() { local status=$FAILURE # If there is at least one argument, take it as the status if [ $# -gt 0 ]; then status=$1 shift 1 # status fi # If there are still arguments left, pass them to f_show_msg [ $# -gt 0 ] && f_show_msg "$@" # Optionally call f_clean_up() function if it exists f_have f_clean_up && f_clean_up exit $status } # f_interrupt # # Interrupt handler. # f_interrupt() { exec 2>&1 # fix sh(1) bug where stderr gets lost within async-trap f_die } # f_show_info $format [$arguments ...] # # Display a message in a dialog infobox using printf(1) syntax. # f_show_info() { local msg msg=$( printf "$@" ) # # Use f_dialog_infobox from dialog.subr if possible, otherwise fall # back to dialog(1) (without options, making it obvious when using # un-aided system dialog). # if f_have f_dialog_info; then f_dialog_info "$msg" else dialog --infobox "$msg" 0 0 fi } # f_show_msg $format [$arguments ...] # # Display a message in a dialog box using printf(1) syntax. # f_show_msg() { local msg msg=$( printf "$@" ) # # Use f_dialog_msgbox from dialog.subr if possible, otherwise fall # back to dialog(1) (without options, making it obvious when using # un-aided system dialog). # if f_have f_dialog_msgbox; then f_dialog_msgbox "$msg" else dialog --msgbox "$msg" 0 0 fi } # f_show_err $format [$arguments ...] # # Display a message in a dialog box with ``Error'' i18n title (overridden by # setting msg_error) using printf(1) syntax. # f_show_err() { local msg msg=$( printf "$@" ) : ${msg:=${msg_an_unknown_error_occurred:-An unknown error occurred}} if [ "$_DIALOG_SUBR" ]; then f_dialog_title "${msg_error:-Error}" f_dialog_msgbox "$msg" f_dialog_title_restore else dialog --title "${msg_error:-Error}" --msgbox "$msg" 0 0 fi return $SUCCESS } # f_yesno $format [$arguments ...] # # Display a message in a dialog yes/no box using printf(1) syntax. # f_yesno() { local msg msg=$( printf "$@" ) # # Use f_dialog_yesno from dialog.subr if possible, otherwise fall # back to dialog(1) (without options, making it obvious when using # un-aided system dialog). # if f_have f_dialog_yesno; then f_dialog_yesno "$msg" else dialog --yesno "$msg" 0 0 fi } # f_noyes $format [$arguments ...] # # Display a message in a dialog yes/no box using printf(1) syntax. # NOTE: THis is just like the f_yesno function except "No" is default. # f_noyes() { local msg msg=$( printf "$@" ) # # Use f_dialog_noyes from dialog.subr if possible, otherwise fall # back to dialog(1) (without options, making it obvious when using # un-aided system dialog). # if f_have f_dialog_noyes; then f_dialog_noyes "$msg" else dialog --defaultno --yesno "$msg" 0 0 fi } # f_show_help $file # # Display a language help-file. Automatically takes $LANG and $LC_ALL into # consideration when displaying $file (suffix ".$LC_ALL" or ".$LANG" will # automatically be added prior to loading the language help-file). # # If a language has been requested by setting either $LANG or $LC_ALL in the # environment and the language-specific help-file does not exist we will fall # back to $file without-suffix. # # If the language help-file does not exist, an error is displayed instead. # f_show_help() { local file="$1" local lang="${LANG:-$LC_ALL}" [ -f "$file.$lang" ] && file="$file.$lang" # # Use f_dialog_textbox from dialog.subr if possible, otherwise fall # back to dialog(1) (without options, making it obvious when using # un-aided system dialog). # if f_have f_dialog_textbox; then f_dialog_textbox "$file" else dialog --msgbox "$( cat "$file" 2>&1 )" 0 0 fi } # f_include $file # # Include a shell subroutine file. # # If the subroutine file exists but returns error status during loading, exit # is called and execution is prematurely terminated with the same error status. # f_include() { local file="$1" f_dprintf "f_include: file=[%s]" "$file" . "$file" || exit $? } # f_include_lang $file # # Include a language file. Automatically takes $LANG and $LC_ALL into # consideration when including $file (suffix ".$LC_ALL" or ".$LANG" will # automatically by added prior to loading the language file). # # No error is produced if (a) a language has been requested (by setting either # $LANG or $LC_ALL in the environment) and (b) the language file does not # exist -- in which case we will fall back to loading $file without-suffix. # # If the language file exists but returns error status during loading, exit # is called and execution is prematurely terminated with the same error status. # f_include_lang() { local file="$1" local lang="${LANG:-$LC_ALL}" f_dprintf "f_include_lang: file=[%s] lang=[%s]" "$file" "$lang" if [ -f "$file.$lang" ]; then . "$file.$lang" || exit $? else . "$file" || exit $? fi } # f_usage $file [$key1 $value1 ...] # # Display USAGE file with optional pre-processor macro definitions. The first # argument is the template file containing the usage text to be displayed. If # $LANG or $LC_ALL (in order of preference, respectively) is set, ".encoding" # will automatically be appended as a suffix to the provided $file pathname. # # When processing $file, output begins at the first line containing that is # (a) not a comment, (b) not empty, and (c) is not pure-whitespace. All lines # appearing after this first-line are output, including (a) comments (b) empty # lines, and (c) lines that are purely whitespace-only. # # If additional arguments appear after $file, substitutions are made while # printing the contents of the USAGE file. The pre-processor macro syntax is in # the style of autoconf(1), for example: # # f_usage $file "FOO" "BAR" # # Will cause instances of "@FOO@" appearing in $file to be replaced with the # text "BAR" before being printed to the screen. # # 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_usage_awk=' BEGIN { found = 0 } { if ( !found && $0 ~ /^[[:space:]]*($|#)/ ) next found = 1 print } ' f_usage() { local file="$1" local lang="${LANG:-$LC_ALL}" f_dprintf "f_usage: file=[%s] lang=[%s]" "$file" "$lang" shift 1 # file local usage if [ -f "$file.$lang" ]; then usage=$( awk "$f_usage_awk" "$file.$lang" ) || exit $FAILURE else usage=$( awk "$f_usage_awk" "$file" ) || exit $FAILURE fi while [ $# -gt 0 ]; do local key="$1" export value="$2" usage=$( echo "$usage" | awk \ "{ gsub(/@$key@/, ENVIRON[\"value\"]); print }" ) shift 2 done f_err "%s\n" "$usage" exit $FAILURE } # f_index_file $keyword [$var_to_set] # # Process all INDEX files known to bsdconfig and return the path to first file # containing a menu_selection line with a keyword portion matching $keyword. # # If $LANG or $LC_ALL (in order of preference, respectively) is set, # "INDEX.encoding" files will be searched first. # # If no file is found, error status is returned along with the NULL string. # # If $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # # 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_index_file_awk=' # Variables that should be defined on the invocation line: # -v keyword="keyword" BEGIN { found = 0 } ( $0 ~ "^menu_selection=\"" keyword "\\|" ) { print FILENAME found++ exit } END { exit ! found } ' f_index_file() { local __keyword="$1" __var_to_set="$2" local __lang="${LANG:-$LC_ALL}" local __indexes="$BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX" f_dprintf "f_index_file: keyword=[%s] lang=[%s]" "$__keyword" "$__lang" if [ "$__lang" ]; then if [ "$__var_to_set" ]; then eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \ "$f_index_file_awk" $__indexes.$__lang )"' && return $SUCCESS else awk -v keyword="$__keyword" "$f_index_file_awk" \ $__indexes.$__lang && return $SUCCESS fi # No match, fall-thru to non-i18n sources fi if [ "$__var_to_set" ]; then eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \ "$f_index_file_awk" $__indexes )"' && return $SUCCESS else awk -v keyword="$__keyword" "$f_index_file_awk" $__indexes && return $SUCCESS fi # No match? Fall-thru to `local' libexec sources (add-on modules) [ "$BSDCFG_LOCAL_LIBE" ] || return $FAILURE __indexes="$BSDCFG_LOCAL_LIBE/*/INDEX" if [ "$__lang" ]; then if [ "$__var_to_set" ]; then eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \ "$f_index_file_awk" $__indexes.$__lang )"' && return $SUCCESS else awk -v keyword="$__keyword" "$f_index_file_awk" \ $__indexes.$__lang && return $SUCCESS fi # No match, fall-thru to non-i18n sources fi if [ "$__var_to_set" ]; then eval "$__var_to_set"='$( awk -v keyword="$__keyword" \ "$f_index_file_awk" $__indexes )"' else awk -v keyword="$__keyword" "$f_index_file_awk" $__indexes fi } # f_index_menusel_keyword $indexfile $pgm [$var_to_set] # # Process $indexfile and return only the keyword portion of the menu_selection # line with a command portion matching $pgm. # # This function is for internationalization (i18n) mapping of the on-disk # scriptname ($pgm) into the localized language (given language-specific # $indexfile). If $LANG or $LC_ALL (in orderder of preference, respectively) is # set, ".encoding" will automatically be appended as a suffix to the provided # $indexfile pathname. # # If, within $indexfile, multiple $menu_selection values map to $pgm, only the # first one will be returned. If no mapping can be made, the NULL string is # returned. # # If $indexfile does not exist, error status is returned with NULL. # # If $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # # 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_index_menusel_keyword_awk=' # Variables that should be defined on the invocation line: # -v pgm="program_name" # BEGIN { prefix = "menu_selection=\"" plen = length(prefix) found = 0 } { if (!match($0, "^" prefix ".*\\|.*\"")) next keyword = command = substr($0, plen + 1, RLENGTH - plen - 1) sub(/^.*\|/, "", command) sub(/\|.*$/, "", keyword) if ( command == pgm ) { print keyword found++ exit } } END { exit ! found } ' f_index_menusel_keyword() { local __indexfile="$1" __pgm="$2" __var_to_set="$3" local __lang="${LANG:-$LC_ALL}" __file="$__indexfile" [ -f "$__indexfile.$__lang" ] && __file="$__indexfile.$__lang" f_dprintf "f_index_menusel_keyword: index=[%s] pgm=[%s] lang=[%s]" \ "$__file" "$__pgm" "$__lang" if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$( awk \ -v pgm="$__pgm" "$f_index_menusel_keyword_awk" "$__file" )" else awk -v pgm="$__pgm" "$f_index_menusel_keyword_awk" "$__file" fi } # f_index_menusel_command $indexfile $keyword [$var_to_set] # # Process $indexfile and return only the command portion of the menu_selection # line with a keyword portion matching $keyword. # # This function is for mapping [possibly international] keywords into the # command to be executed. If $LANG or $LC_ALL (order of preference) is set, # ".encoding" will automatically be appended as a suffix to the provided # $indexfile pathname. # # If, within $indexfile, multiple $menu_selection values map to $keyword, only # the first one will be returned. If no mapping can be made, the NULL string is # returned. # # If $indexfile doesn't exist, error status is returned with NULL. # # If $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # # 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_index_menusel_command_awk=' # Variables that should be defined on the invocation line: # -v key="keyword" # BEGIN { prefix = "menu_selection=\"" plen = length(prefix) found = 0 } { if (!match($0, "^" prefix ".*\\|.*\"")) next keyword = command = substr($0, plen + 1, RLENGTH - plen - 1) sub(/^.*\|/, "", command) sub(/\|.*$/, "", keyword) if ( keyword == key ) { print command found++ exit } } END { exit ! found } ' f_index_menusel_command() { local __indexfile="$1" __keyword="$2" __var_to_set="$3" __command local __lang="${LANG:-$LC_ALL}" __file="$__indexfile" [ -f "$__indexfile.$__lang" ] && __file="$__indexfile.$__lang" f_dprintf "f_index_menusel_command: index=[%s] key=[%s] lang=[%s]" \ "$__file" "$__keyword" "$__lang" [ -f "$__file" ] || return $FAILURE __command=$( awk -v key="$__keyword" \ "$f_index_menusel_command_awk" "$__file" ) || return $FAILURE # # If the command pathname is not fully qualified fix-up/force to be # relative to the $indexfile directory. # case "$__command" in /*) : already fully qualified ;; *) local __indexdir="${__indexfile%/*}" [ "$__indexdir" != "$__indexfile" ] || __indexdir="." __command="$__indexdir/$__command" esac if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__command" else echo "$__command" fi } # f_running_as_init # # Returns true if running as init(1). # f_running_as_init() { # # When a custom init(8) performs an exec(3) to invoke a shell script, # PID 1 becomes sh(1) and $PPID is set to 1 in the executed script. # [ ${PPID:-0} -eq 1 ] # Return status } # f_mounted $local_directory # f_mounted -b $device # # Return success if a filesystem is mounted on a particular directory. If `-b' # is present, instead check that the block device (or a partition thereof) is # mounted. # f_mounted() { local OPTIND OPTARG flag use_device= while getopts b flag; do case "$flag" in b) use_device=1 ;; esac done shift $(( $OPTIND - 1 )) if [ "$use_device" ]; then local device="$1" mount | grep -Eq \ "^$device([[:space:]]|p[0-9]|s[0-9]|\.nop|\.eli)" else [ -d "$dir" ] || return $FAILURE mount | grep -Eq " on $dir \([^)]+\)$" fi # Return status is that of last grep(1) } # f_eval_catch [-de] [-k $var_to_set] $funcname $utility \ # $format [$arguments ...] # # Silently evaluate a command in a sub-shell and test for error. If debugging # is enabled a copy of the command and its output is sent to debug (either # stdout or file depending on environment). If an error occurs, output of the # command is displayed in a dialog(1) msgbox using the [above] f_show_err() # function (unless optional `-d' flag is given, then no dialog). # # The $funcname argument is sent to debugging while the $utility argument is # used in the title of the dialog box. The command that is executed as well as # sent to debugging with $funcname is the product of the printf(1) syntax # produced by $format with optional $arguments. # # The following options are supported: # # -d Do not use dialog(1). # -e Produce error text from failed command on stderr. # -k var Save output from the command in var. # # Example 1: # # debug=1 # f_eval_catch myfunc echo 'echo "%s"' "Hello, World!" # # Produces the following debug output: # # DEBUG: myfunc: echo "Hello, World!" # DEBUG: myfunc: retval=0 # Hello, World! # # Example 2: # # debug=1 # f_eval_catch -k contents myfunc cat 'cat "%s"' /some/file # # dialog(1) Error ``cat: /some/file: No such file or directory'' # # contents=[cat: /some/file: No such file or directory] # # Produces the following debug output: # # DEBUG: myfunc: cat "/some/file" # DEBUG: myfunc: retval=1 # cat: /some/file: No such file or directory # # Example 3: # # debug=1 # echo 123 | f_eval_catch myfunc rev rev # # Produces the following debug output: # # DEBUG: myfunc: rev # DEBUG: myfunc: retval=0 # 321 # # Example 4: # # debug=1 # f_eval_catch myfunc true true # # Produces the following debug output: # # DEBUG: myfunc: true # DEBUG: myfunc: retval=0 # # Example 5: # # f_eval_catch -de myfunc ls 'ls "%s"' /some/dir # # Output on stderr ``ls: /some/dir: No such file or directory'' # # Example 6: # # f_eval_catch -dek contents myfunc ls 'ls "%s"' /etc # # Output from `ls' sent to stderr and also saved in $contents # f_eval_catch() { local __no_dialog= __show_err= __var_to_set= # # Process local function arguments # local OPTIND OPTARG __flag while getopts "dek:" __flag > /dev/null; do case "$__flag" in d) __no_dialog=1 ;; e) __show_err=1 ;; k) __var_to_set="$OPTARG" ;; esac done shift $(( $OPTIND - 1 )) local __funcname="$1" __utility="$2"; shift 2 local __cmd __output __retval __cmd=$( printf -- "$@" ) f_dprintf "%s: %s" "$__funcname" "$__cmd" # Log command *before* eval __output=$( exec 2>&1; eval "$__cmd" ) __retval=$? if [ "$__output" ]; then [ "$__show_err" ] && echo "$__output" >&2 f_dprintf "%s: retval=%i \n%s" "$__funcname" \ $__retval "$__output" else f_dprintf "%s: retval=%i " "$__funcname" $__retval fi ! [ "$__no_dialog" -o "$nonInteractive" -o $__retval -eq $SUCCESS ] && msg_error="${msg_error:-Error}${__utility:+: $__utility}" \ f_show_err "%s" "$__output" # NB: f_show_err will handle NULL output appropriately [ "$__var_to_set" ] && setvar "$__var_to_set" "$__output" return $__retval } # f_count $var_to_set arguments ... # # Sets $var_to_set to the number of arguments minus one (the effective number # of arguments following $var_to_set). # # Example: # f_count count dog house # count=[2] # f_count() { setvar "$1" $(( $# - 1 )) } # f_count_ifs $var_to_set string ... # # Sets $var_to_set to the number of words (split by the internal field # separator, IFS) following $var_to_set. # # Example 1: # # string="word1 word2 word3" # f_count_ifs count "$string" # count=[3] # f_count_ifs count $string # count=[3] # # Example 2: # # IFS=. f_count_ifs count www.freebsd.org # count=[3] # # NB: Make sure to use double-quotes if you are using a custom value for IFS # and you don't want the current value to effect the result. See example 3. # # Example 3: # # string="a-b c-d" # IFS=- f_count_ifs count "$string" # count=[3] # IFS=- f_count_ifs count $string # count=[4] # f_count_ifs() { local __var_to_set="$1" shift 1 set -- $* setvar "$__var_to_set" $# } ############################################################ MAIN # # Trap signals so we can recover gracefully # trap 'f_interrupt' INT trap 'f_die' TERM PIPE XCPU XFSZ FPE TRAP ABRT SEGV trap '' ALRM PROF USR1 USR2 HUP VTALRM # # Clone terminal stdout/stderr so we can redirect to it from within sub-shells # eval exec $TERMINAL_STDOUT_PASSTHRU\>\&1 eval exec $TERMINAL_STDERR_PASSTHRU\>\&2 # # Self-initialize unless requested otherwise # f_dprintf "%s: DEBUG_SELF_INITIALIZE=[%s]" \ dialog.subr "$DEBUG_SELF_INITIALIZE" case "$DEBUG_SELF_INITIALIZE" in ""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;; *) f_debug_init esac # # Log our operating environment for debugging purposes # f_dprintf "UNAME_S=[%s] UNAME_P=[%s] UNAME_R=[%s]" \ "$UNAME_S" "$UNAME_P" "$UNAME_R" f_dprintf "%s: Successfully loaded." common.subr fi # ! $_COMMON_SUBR Index: head/usr.sbin/bsdconfig/share/dialog.subr =================================================================== --- head/usr.sbin/bsdconfig/share/dialog.subr (revision 298883) +++ head/usr.sbin/bsdconfig/share/dialog.subr (revision 298884) @@ -1,2340 +1,2340 @@ if [ ! "$_DIALOG_SUBR" ]; then _DIALOG_SUBR=1 # # Copyright (c) 2006-2015 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" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." dialog.subr f_include $BSDCFG_SHARE/strings.subr f_include $BSDCFG_SHARE/variable.subr BSDCFG_LIBE="/usr/libexec/bsdconfig" f_include_lang $BSDCFG_LIBE/include/messages.subr ############################################################ CONFIGURATION # # Default file descriptor to link to stdout for dialog(1) passthru allowing # execution of dialog from within a sub-shell (so-long as its standard output # is explicitly redirected to this file descriptor). # : ${DIALOG_TERMINAL_PASSTHRU_FD:=${TERMINAL_STDOUT_PASSTHRU:-3}} ############################################################ GLOBALS # # Default name of dialog(1) utility # NOTE: This is changed to "Xdialog" by the optional `-X' argument # DIALOG="dialog" # # Default dialog(1) title and backtitle text # DIALOG_TITLE="$pgm" DIALOG_BACKTITLE="bsdconfig" # # Settings used while interacting with dialog(1) # DIALOG_MENU_TAGS="123456789ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyz" # # Declare that we are fully-compliant with Xdialog(1) by unset'ing all # compatibility settings. # unset XDIALOG_HIGH_DIALOG_COMPAT unset XDIALOG_FORCE_AUTOSIZE unset XDIALOG_INFOBOX_TIMEOUT # # Exit codes for [X]dialog(1) # DIALOG_OK=${SUCCESS:-0} DIALOG_CANCEL=${FAILURE:-1} DIALOG_HELP=2 DIALOG_ITEM_HELP=2 DIALOG_EXTRA=3 DIALOG_ITEM_HELP=4 export DIALOG_ERROR=254 # sh(1) can't handle the default of `-1' DIALOG_ESC=255 # # Default behavior is to call f_dialog_init() automatically when loaded. # : ${DIALOG_SELF_INITIALIZE=1} # # Default terminal size (used if/when running without a controlling terminal) # : ${DEFAULT_TERMINAL_SIZE:=24 80} # # Minimum width(s) for various dialog(1) implementations (sensible global # default(s) for all widgets of a given variant) # : ${DIALOG_MIN_WIDTH:=24} : ${XDIALOG_MIN_WIDTH:=35} # # When manually sizing Xdialog(1) widgets such as calendar and timebox, you'll # need to know the size of the embedded GUI objects because the height passed -# to Xdialog(1) for these widgets has to be tall enough to accomodate them. +# to Xdialog(1) for these widgets has to be tall enough to accommodate them. # # These values are helpful when manually sizing with dialog(1) too, but in a -# different way. dialog(1) does not make you accomodate the custom items in the +# different way. dialog(1) does not make you accommodate the custom items in the # height (but does for width) -- a height of 3 will display three lines and a # full calendar, for example (whereas Xdialog will truncate the calendar if # given a height of 3). For dialog(1), use these values for making sure that # the height does not exceed max_height (obtained by f_dialog_max_size()). # DIALOG_CALENDAR_HEIGHT=15 DIALOG_TIMEBOX_HEIGHT=6 ############################################################ GENERIC FUNCTIONS # f_dialog_data_sanitize $var_to_edit ... # # When using dialog(1) or Xdialog(1) sometimes unintended warnings or errors # are generated from underlying libraries. For example, if $LANG is set to an # invalid or unknown locale, the warnings from the Xdialog(1) libraries will # clutter the output. This function helps by providing a centralied function # that removes spurious warnings from the dialog(1) (or Xdialog(1)) response. # # Simply pass the name of one or more variables that need to be sanitized. # After execution, the variables will hold their newly-sanitized data. # f_dialog_data_sanitize() { if [ "$#" -eq 0 ]; then f_dprintf "%s: called with zero arguments" \ f_dialog_response_sanitize return $FAILURE fi local __var_to_edit for __var_to_edit in $*; do # Skip warnings and trim leading/trailing whitespace setvar $__var_to_edit "$( f_getvar $__var_to_edit | awk ' BEGIN { data = 0 } { if ( ! data ) { if ( $0 ~ /^$/ ) next if ( $0 ~ /^Gdk-WARNING \*\*:/ ) next data = 1 } print } ' )" done } # f_dialog_line_sanitize $var_to_edit ... # # When using dialog(1) or Xdialog(1) sometimes unintended warnings or errors # are generated from underlying libraries. For example, if $LANG is set to an # invalid or unknown locale, the warnings from the Xdialog(1) libraries will # clutter the output. This function helps by providing a centralied function # that removes spurious warnings from the dialog(1) (or Xdialog(1)) response. # # Simply pass the name of one or more variables that need to be sanitized. # After execution, the variables will hold their newly-sanitized data. # # This function, unlike f_dialog_data_sanitize(), also removes leading/trailing # whitespace from each line. # f_dialog_line_sanitize() { if [ "$#" -eq 0 ]; then f_dprintf "%s: called with zero arguments" \ f_dialog_response_sanitize return $FAILURE fi local __var_to_edit for __var_to_edit in $*; do # Skip warnings and trim leading/trailing whitespace setvar $__var_to_edit "$( f_getvar $__var_to_edit | awk ' BEGIN { data = 0 } { if ( ! data ) { if ( $0 ~ /^$/ ) next if ( $0 ~ /^Gdk-WARNING \*\*:/ ) next data = 1 } sub(/^[[:space:]]*/, "") sub(/[[:space:]]*$/, "") print } ' )" done } ############################################################ TITLE FUNCTIONS # f_dialog_title [$new_title] # # Set the title of future dialog(1) ($DIALOG_TITLE) or backtitle of Xdialog(1) # ($DIALOG_BACKTITLE) invocations. If no arguments are given or the first # argument is NULL, the current title is returned. # # Each time this function is called, a backup of the current values is made # allowing a one-time (single-level) restoration of the previous title using # the f_dialog_title_restore() function (below). # f_dialog_title() { local new_title="$1" if [ "${1+set}" ]; then if [ "$USE_XDIALOG" ]; then _DIALOG_BACKTITLE="$DIALOG_BACKTITLE" DIALOG_BACKTITLE="$new_title" else _DIALOG_TITLE="$DIALOG_TITLE" DIALOG_TITLE="$new_title" fi else if [ "$USE_XDIALOG" ]; then echo "$DIALOG_BACKTITLE" else echo "$DIALOG_TITLE" fi fi } # f_dialog_title_restore # # Restore the previous title set by the last call to f_dialog_title(). # Restoration is non-recursive and only works to restore the most-recent title. # f_dialog_title_restore() { if [ "$USE_XDIALOG" ]; then DIALOG_BACKTITLE="$_DIALOG_BACKTITLE" else DIALOG_TITLE="$_DIALOG_TITLE" fi } # f_dialog_backtitle [$new_backtitle] # # Set the backtitle of future dialog(1) ($DIALOG_BACKTITLE) or title of # Xdialog(1) ($DIALOG_TITLE) invocations. If no arguments are given or the # first argument is NULL, the current backtitle is returned. # f_dialog_backtitle() { local new_backtitle="$1" if [ "${1+set}" ]; then if [ "$USE_XDIALOG" ]; then _DIALOG_TITLE="$DIALOG_TITLE" DIALOG_TITLE="$new_backtitle" else _DIALOG_BACKTITLE="$DIALOG_BACKTITLE" DIALOG_BACKTITLE="$new_backtitle" fi else if [ "$USE_XDIALOG" ]; then echo "$DIALOG_TITLE" else echo "$DIALOG_BACKTITLE" fi fi } # f_dialog_backtitle_restore # # Restore the previous backtitle set by the last call to f_dialog_backtitle(). # Restoration is non-recursive and only works to restore the most-recent # backtitle. # f_dialog_backtitle_restore() { if [ "$USE_XDIALOG" ]; then DIALOG_TITLE="$_DIALOG_TITLE" else DIALOG_BACKTITLE="$_DIALOG_BACKTITLE" fi } ############################################################ SIZE FUNCTIONS # f_dialog_max_size $var_height $var_width # # Get the maximum height and width for a dialog widget and store the values in # $var_height and $var_width (respectively). # f_dialog_max_size() { local funcname=f_dialog_max_size local __var_height="$1" __var_width="$2" __max_size [ "$__var_height" -o "$__var_width" ] || return $FAILURE if [ "$USE_XDIALOG" ]; then __max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION else if __max_size=$( $DIALOG --print-maxsize \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) then f_dprintf "$funcname: %s --print-maxsize = [%s]" \ "$DIALOG" "$__max_size" # usually "MaxSize: 24, 80" __max_size="${__max_size#*: }" f_replaceall "$__max_size" "," "" __max_size else f_eval_catch -dk __max_size $funcname stty \ 'stty size' || __max_size= # usually "24 80" fi : ${__max_size:=$DEFAULT_TERMINAL_SIZE} fi if [ "$__var_height" ]; then local __height="${__max_size%%[$IFS]*}" # # If we're not using Xdialog(1), we should assume that $DIALOG # will render --backtitle behind the widget. In such a case, we # should prevent a widget from obscuring the backtitle (unless # $NO_BACKTITLE is set and non-NULL, allowing a trap-door). # if [ ! "$USE_XDIALOG" ] && [ ! "$NO_BACKTITLE" ]; then # # If use_shadow (in ~/.dialogrc) is OFF, we need to # subtract 4, otherwise 5. However, don't check this # every time, rely on an initialization variable set # by f_dialog_init(). # local __adjust=5 [ "$NO_SHADOW" ] && __adjust=4 # Don't adjust height if already too small (allowing # obscured backtitle for small values of __height). [ ${__height:-0} -gt 11 ] && __height=$(( $__height - $__adjust )) fi setvar "$__var_height" "$__height" fi [ "$__var_width" ] && setvar "$__var_width" "${__max_size##*[$IFS]}" } # f_dialog_size_constrain $var_height $var_width [$min_height [$min_width]] # # Modify $var_height to be no-less-than $min_height (if given; zero otherwise) # and no-greater-than terminal height (or screen height if $USE_XDIALOG is # set). # # Also modify $var_width to be no-less-than $XDIALOG_MIN_WIDTH (or # $XDIALOG_MIN_WIDTH if $_USE_XDIALOG is set) and no-greater-than terminal # or screen width. The use of $[X]DIALOG_MIN_WIDTH can be overridden by # passing $min_width. # # Return status is success unless one of the passed arguments is invalid # or all of the $var_* arguments are either NULL or missing. # f_dialog_size_constrain() { local __var_height="$1" __var_width="$2" local __min_height="$3" __min_width="$4" local __retval=$SUCCESS # Return failure unless at least one var_* argument is passed [ "$__var_height" -o "$__var_width" ] || return $FAILURE # # Print debug warnings if any given (non-NULL) argument are invalid # NOTE: Don't change the name of $__{var,min,}{height,width} # local __height __width local __arg __cp __fname=f_dialog_size_constrain for __arg in height width; do debug= f_getvar __var_$__arg __cp [ "$__cp" ] || continue if ! debug= f_getvar "$__cp" __$__arg; then f_dprintf "%s: var_%s variable \`%s' not set" \ $__fname $__arg "$__cp" __retval=$FAILURE elif ! eval f_isinteger \$__$__arg; then f_dprintf "%s: var_%s variable value not a number" \ $__fname $__arg __retval=$FAILURE fi done for __arg in height width; do debug= f_getvar __min_$__arg __cp [ "$__cp" ] || continue f_isinteger "$__cp" && continue f_dprintf "%s: min_%s value not a number" $__fname $__arg __retval=$FAILURE setvar __min_$__arg "" done # Obtain maximum height and width values # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __max_height_size_constain __max_width_size_constrain f_dialog_max_size \ __max_height_size_constrain __max_width_size_constrain # Adjust height if desired if [ "$__var_height" ]; then if [ $__height -lt ${__min_height:-0} ]; then setvar "$__var_height" $__min_height elif [ $__height -gt $__max_height_size_constrain ]; then setvar "$__var_height" $__max_height_size_constrain fi fi # Adjust width if desired if [ "$__var_width" ]; then if [ "$USE_XDIALOG" ]; then : ${__min_width:=${XDIALOG_MIN_WIDTH:-35}} else : ${__min_width:=${DIALOG_MIN_WIDTH:-24}} fi if [ $__width -lt $__min_width ]; then setvar "$__var_width" $__min_width elif [ $__width -gt $__max_width_size_constrain ]; then setvar "$__var_width" $__max_width_size_constrain fi fi if [ "$debug" ]; then # Print final constrained values to debugging [ "$__var_height" ] && f_quietly f_getvar "$__var_height" [ "$__var_width" ] && f_quietly f_getvar "$__var_width" fi return $__retval # success if no debug warnings were printed } # f_dialog_menu_constrain $var_height $var_width $var_rows "$prompt" \ # [$min_height [$min_width [$min_rows]]] # # Modify $var_height to be no-less-than $min_height (if given; zero otherwise) # and no-greater-than terminal height (or screen height if $USE_XDIALOG is # set). # # Also modify $var_width to be no-less-than $XDIALOG_MIN_WIDTH (or # $XDIALOG_MIN_WIDTH if $_USE_XDIALOG is set) and no-greater-than terminal # or screen width. The use of $[X]DIALOG_MIN_WIDTH can be overridden by # passing $min_width. # # Last, modify $var_rows to be no-less-than $min_rows (if specified; zero # otherwise) and no-greater-than (max_height - 8) where max_height is the # terminal height (or screen height if $USE_XDIALOG is set). If $prompt is NULL # or missing, dialog(1) allows $var_rows to be (max_height - 7), maximizing the # number of visible rows. # # Return status is success unless one of the passed arguments is invalid # or all of the $var_* arguments are either NULL or missing. # f_dialog_menu_constrain() { local __var_height="$1" __var_width="$2" __var_rows="$3" __prompt="$4" local __min_height="$5" __min_width="$6" __min_rows="$7" # Return failure unless at least one var_* argument is passed [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] || return $FAILURE # # Print debug warnings if any given (non-NULL) argument are invalid # NOTE: Don't change the name of $__{var,min,}{height,width,rows} # local __height_menu_constrain __width_menu_constrain local __rows_menu_constrain local __arg __cp __fname=f_dialog_menu_constrain for __arg in height width rows; do debug= f_getvar __var_$__arg __cp [ "$__cp" ] || continue if ! debug= f_getvar "$__cp" __${__arg}_menu_constrain; then f_dprintf "%s: var_%s variable \`%s' not set" \ $__fname $__arg "$__cp" __retval=$FAILURE elif ! eval f_isinteger \$__${__arg}_menu_constrain; then f_dprintf "%s: var_%s variable value not a number" \ $__fname $__arg __retval=$FAILURE fi done for __arg in height width rows; do debug= f_getvar __min_$__arg __cp [ "$__cp" ] || continue f_isinteger "$__cp" && continue f_dprintf "%s: min_%s value not a number" $__fname $__arg __retval=$FAILURE setvar __min_$__arg "" done # Obtain maximum height and width values # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __max_height_menu_constrain __max_width_menu_constrain f_dialog_max_size \ __max_height_menu_constrain __max_width_menu_constrain # Adjust height if desired if [ "$__var_height" ]; then if [ $__height_menu_constrain -lt ${__min_height:-0} ]; then setvar "$__var_height" $__min_height elif [ $__height_menu_constrain -gt \ $__max_height_menu_constrain ] then setvar "$__var_height" $__max_height_menu_constrain fi fi # Adjust width if desired if [ "$__var_width" ]; then if [ "$USE_XDIALOG" ]; then : ${__min_width:=${XDIALOG_MIN_WIDTH:-35}} else : ${__min_width:=${DIALOG_MIN_WIDTH:-24}} fi if [ $__width_menu_constrain -lt $__min_width ]; then setvar "$__var_width" $__min_width elif [ $__width_menu_constrain -gt \ $__max_width_menu_constrain ] then setvar "$__var_width" $__max_width_menu_constrain fi fi # Adjust rows if desired if [ "$__var_rows" ]; then if [ "$USE_XDIALOG" ]; then : ${__min_rows:=1} else : ${__min_rows:=0} fi local __max_rows_menu_constrain=$(( $__max_height_menu_constrain - 7 )) # If prompt_len is zero (no prompt), bump the max-rows by 1 # Default assumption is (if no argument) that there's no prompt [ ${__prompt_len:-0} -gt 0 ] || __max_rows_menu_constrain=$(( $__max_rows_menu_constrain + 1 )) if [ $__rows_menu_constrain -lt $__min_rows ]; then setvar "$__var_rows" $__min_rows elif [ $__rows_menu_constrain -gt $__max_rows_menu_constrain ] then setvar "$__var_rows" $__max_rows_menu_constrain fi fi if [ "$debug" ]; then # Print final constrained values to debugging [ "$__var_height" ] && f_quietly f_getvar "$__var_height" [ "$__var_width" ] && f_quietly f_getvar "$__var_width" [ "$__var_rows" ] && f_quietly f_getvar "$__var_rows" fi return $__retval # success if no debug warnings were printed } # f_dialog_infobox_size [-n] $var_height $var_width \ # $title $backtitle $prompt [$hline] # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--infobox' boxes sensibly. # # This function helps solve this issue by taking two sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height and width. The second set of arguments are the # title, backtitle, prompt, and [optionally] hline. The optimal height and # width for the described widget (not exceeding the actual terminal height or # width) is stored in $var_height and $var_width (respectively). # # If the first argument is `-n', the calculated sizes ($var_height and # $var_width) are not constrained to minimum/maximum values. # # Newline character sequences (``\n'') in $prompt are expanded as-is done by # dialog(1). # f_dialog_infobox_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" local __title="$3" __btitle="$4" __prompt="$5" __hline="$6" # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" ] || return $FAILURE # Default height/width of zero for auto-sizing local __height=0 __width=0 __n # Adjust height if desired if [ "$__var_height" ]; then # # Set height based on number of rows in prompt # __n=$( echo -n "$__prompt" | f_number_of_lines ) __n=$(( $__n + 2 )) [ $__n -gt $__height ] && __height=$__n # # For Xdialog(1) bump height if backtitle is enabled (displayed # in the X11 window with a separator line between the backtitle # and msg text). # if [ "$USE_XDIALOG" -a "$__btitle" ]; then __n=$( echo "$__btitle" | f_number_of_lines ) __height=$(( $__height + $__n + 2 )) fi setvar "$__var_height" $__height fi # Adjust width if desired if [ "$__var_width" ]; then # # Bump width for long titles # __n=$(( ${#__title} + 4 )) [ $__n -gt $__width ] && __width=$__n # # If using Xdialog(1), bump width for long backtitles (which # appear within the window). # if [ "$USE_XDIALOG" ]; then __n=$(( ${#__btitle} + 4 )) [ $__n -gt $__width ] && __width=$__n fi # # Bump width for long prompts # __n=$( echo "$__prompt" | f_longest_line_length ) __n=$(( $__n + 4 )) # add width for border [ $__n -gt $__width ] && __width=$__n # # Bump width for long hlines. Xdialog(1) supports `--hline' but # it's currently not used (so don't do anything here if using # Xdialog(1)). # if [ ! "$USE_XDIALOG" ]; then __n=$(( ${#__hline} + 10 )) [ $__n -gt $__width ] && __width=$__n fi # Bump width by 16.6% if using Xdialog(1) [ "$USE_XDIALOG" ] && __width=$(( $__width + $__width / 6 )) setvar "$__var_width" $__width fi # Constrain values to sensible minimums/maximums unless `-n' was passed # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_size_constrain "$__var_height" "$__var_width" } # f_dialog_buttonbox_size [-n] $var_height $var_width \ # $title $backtitle $prompt [$hline] # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--msgbox' and `--yesno' boxes sensibly. # # This function helps solve this issue by taking two sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height and width. The second set of arguments are the # title, backtitle, prompt, and [optionally] hline. The optimal height and # width for the described widget (not exceeding the actual terminal height or # width) is stored in $var_height and $var_width (respectively). # # If the first argument is `-n', the calculated sizes ($var_height and # $var_width) are not constrained to minimum/maximum values. # # Newline character sequences (``\n'') in $prompt are expanded as-is done by # dialog(1). # f_dialog_buttonbox_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" local __title="$3" __btitle="$4" __prompt="$5" __hline="$6" # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" ] || return $FAILURE # Calculate height/width of infobox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_bbox_size __width_bbox_size f_dialog_infobox_size -n \ "${__var_height:+__height_bbox_size}" \ "${__var_width:+__width_bbox_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" # Adjust height if desired if [ "$__var_height" ]; then - # Add height to accomodate the buttons + # Add height to accommodate the buttons __height_bbox_size=$(( $__height_bbox_size + 2 )) # Adjust for clipping with Xdialog(1) on Linux/GTK2 [ "$USE_XDIALOG" ] && __height_bbox_size=$(( $__height_bbox_size + 3 )) setvar "$__var_height" $__height_bbox_size fi # No adjustemnts to width, just pass-thru the infobox width if [ "$__var_width" ]; then setvar "$__var_width" $__width_bbox_size fi # Constrain values to sensible minimums/maximums unless `-n' was passed # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_size_constrain "$__var_height" "$__var_width" } # f_dialog_inputbox_size [-n] $var_height $var_width \ # $title $backtitle $prompt $init [$hline] # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--inputbox' boxes sensibly. # # This function helps solve this issue by taking two sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height and width. The second set of arguments are the # title, backtitle, prompt, and [optionally] hline. The optimal height and # width for the described widget (not exceeding the actual terminal height or # width) is stored in $var_height and $var_width (respectively). # # If the first argument is `-n', the calculated sizes ($var_height and # $var_width) are not constrained to minimum/maximum values. # # Newline character sequences (``\n'') in $prompt are expanded as-is done by # dialog(1). # f_dialog_inputbox_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" local __title="$3" __btitle="$4" __prompt="$5" __init="$6" __hline="$7" # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" ] || return $FAILURE # Calculate height/width of buttonbox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_ibox_size __width_ibox_size f_dialog_buttonbox_size -n \ "${__var_height:+__height_ibox_size}" \ "${__var_width:+__width_ibox_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" # Adjust height if desired if [ "$__var_height" ]; then # Add height for input box (not needed for Xdialog(1)) [ ! "$USE_XDIALOG" ] && __height_ibox_size=$(( $__height_ibox_size + 3 )) setvar "$__var_height" $__height_ibox_size fi # Adjust width if desired if [ "$__var_width" ]; then # Bump width for initial text (something neither dialog(1) nor # Xdialog(1) do, but worth it!; add 16.6% if using Xdialog(1)) local __n=$(( ${#__init} + 7 )) [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) [ $__n -gt $__width_ibox_size ] && __width_ibox_size=$__n setvar "$__var_width" $__width_ibox_size fi # Constrain values to sensible minimums/maximums unless `-n' was passed # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_size_constrain "$__var_height" "$__var_width" } # f_xdialog_2inputsbox_size [-n] $var_height $var_width \ # $title $backtitle $prompt \ # $label1 $init1 $label2 $init2 # # Xdialog(1) does not perform auto-sizing of the width and height of # `--2inputsbox' boxes sensibly. # # This function helps solve this issue by taking two sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height and width. The second set of arguments are the # title, backtitle, prompt, label for the first field, initial text for said # field, label for the second field, and initial text for said field. The # optimal height and width for the described widget (not exceeding the actual # terminal height or width) is stored in $var_height and $var_width # (respectively). # # If the first argument is `-n', the calculated sizes ($var_height and # $var_width) are not constrained to minimum/maximum values. # # Newline character sequences (``\n'') in $prompt are expanded as-is done by # Xdialog(1). # f_xdialog_2inputsbox_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" local __title="$3" __btitle="$4" __prompt="$5" local __label1="$6" __init1="$7" __label2="$8" __init2="$9" # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" ] || return $FAILURE # Calculate height/width of inputbox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_2ibox_size __width_2ibox_size f_dialog_inputbox_size -n \ "${__var_height:+__height_2ibox_size}" \ "${__var_width:+__width_2ibox_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" "$__init1" # Adjust height if desired if [ "$__var_height" ]; then # Add height for 1st label, 2nd label, and 2nd input box __height_2ibox_size=$(( $__height_2ibox_size + 2 + 2 + 2 )) setvar "$__var_height" $__height_2ibox_size fi # Adjust width if desired if [ "$__var_width" ]; then local __n # Bump width for first label text (+16.6% since Xdialog(1)) __n=$(( ${#__label1} + 7 )) __n=$(( $__n + $__n / 6 )) [ $__n -gt $__width_2ibox_size ] && __width_2ibox_size=$__n # Bump width for second label text (+16.6% since Xdialog(1)) __n=$(( ${#__label2} + 7 )) __n=$(( $__n + $__n / 6 )) [ $__n -gt $__width_2ibox_size ] && __width_2ibox_size=$__n # Bump width for 2nd initial text (something neither dialog(1) # nor Xdialog(1) do, but worth it!; +16.6% since Xdialog(1)) __n=$(( ${#__init2} + 7 )) __n=$(( $__n + $__n / 6 )) [ $__n -gt $__width_2ibox_size ] && __width_2ibox_size=$__n setvar "$__var_width" $__width_2ibox_size fi # Constrain values to sensible minimums/maximums unless `-n' was passed # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_size_constrain "$__var_height" "$__var_width" } # f_dialog_menu_size [-n] $var_height $var_width $var_rows \ # $title $backtitle $prompt $hline \ # $tag1 $item1 $tag2 $item2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--menu' boxes sensibly. # # This function helps solve this issue by taking three sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height, width, and rows. The second set of arguments # are the title, backtitle, prompt, and hline. The [optional] third set of # arguments are the menu list itself (comprised of tag/item couplets). The # optimal height, width, and rows for the described widget (not exceeding the # actual terminal height or width) is stored in $var_height, $var_width, and # $var_rows (respectively). # # If the first argument is `-n', the calculated sizes ($var_height, $var_width, # and $var_rows) are not constrained to minimum/maximum values. # f_dialog_menu_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" __var_rows="$3" local __title="$4" __btitle="$5" __prompt="$6" __hline="$7" shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] || return $FAILURE # Calculate height/width of infobox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_menu_size __width_menu_size f_dialog_infobox_size -n \ "${__var_height:+__height_menu_size}" \ "${__var_width:+__width_menu_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" # # Always process the menu-item arguments to get the longest tag-length, # longest item-length (both used to bump the width), and the number of # rows (used to bump the height). # local __longest_tag=0 __longest_item=0 __rows=0 while [ $# -ge 2 ]; do local __tag="$1" __item="$2" shift 2 # tag/item [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag} [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item} __rows=$(( $__rows + 1 )) done # Adjust rows early (for up-comning height calculation) if [ "$__var_height" -o "$__var_rows" ]; then # Add a row for visual aid if using Xdialog(1) [ "$USE_XDIALOG" ] && __rows=$(( $__rows + 1 )) fi # Adjust height if desired if [ "$__var_height" ]; then # Add rows to height if [ "$USE_XDIALOG" ]; then __height_menu_size=$(( $__height_menu_size + $__rows + 7 )) else __height_menu_size=$(( $__height_menu_size + $__rows + 4 )) fi setvar "$__var_height" $__height_menu_size fi # Adjust width if desired if [ "$__var_width" ]; then # The sum total between the longest tag-length and the # longest item-length should be used to bump menu width local __n=$(( $__longest_tag + $__longest_item + 10 )) [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6% [ $__n -gt $__width_menu_size ] && __width_menu_size=$__n setvar "$__var_width" $__width_menu_size fi # Store adjusted rows if desired [ "$__var_rows" ] && setvar "$__var_rows" $__rows # Constrain height, width, and rows to sensible minimum/maximum values # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_menu_constrain \ "$__var_height" "$__var_width" "$__var_rows" "$__prompt" } # f_dialog_menu_with_help_size [-n] $var_height $var_width $var_rows \ # $title $backtitle $prompt $hline \ # $tag1 $item1 $help1 $tag2 $item2 $help2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--menu' boxes sensibly. # # This function helps solve this issue by taking three sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height, width, and rows. The second set of arguments # are the title, backtitle, prompt, and hline. The [optional] third set of # arguments are the menu list itself (comprised of tag/item/help triplets). The # optimal height, width, and rows for the described widget (not exceeding the # actual terminal height or width) is stored in $var_height, $var_width, and # $var_rows (respectively). # # If the first argument is `-n', the calculated sizes ($var_height, $var_width, # and $var_rows) are not constrained to minimum/maximum values. # f_dialog_menu_with_help_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" __var_rows="$3" local __title="$4" __btitle="$5" __prompt="$6" __hline="$7" shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] || return $FAILURE # Calculate height/width of infobox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_menu_with_help_size __width_menu_with_help_size f_dialog_infobox_size -n \ "${__var_height:+__height_menu_with_help_size}" \ "${__var_width:+__width_menu_with_help_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" # # Always process the menu-item arguments to get the longest tag-length, # longest item-length, longest help-length (help-length only considered # if using Xdialog(1), as it places the help string in the widget) -- # all used to bump the width -- and the number of rows (used to bump # the height). # local __longest_tag=0 __longest_item=0 __longest_help=0 __rows=0 while [ $# -ge 3 ]; do local __tag="$1" __item="$2" __help="$3" shift 3 # tag/item/help [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag} [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item} [ ${#__help} -gt $__longest_help ] && __longest_help=${#__help} __rows=$(( $__rows + 1 )) done # Adjust rows early (for up-coming height calculation) if [ "$__var_height" -o "$__var_rows" ]; then # Add a row for visual aid if using Xdialog(1) [ "$USE_XDIALOG" ] && __rows=$(( $__rows + 1 )) fi # Adjust height if desired if [ "$__var_height" ]; then # Add rows to height if [ "$USE_XDIALOG" ]; then __height_menu_with_help_size=$(( $__height_menu_with_help_size + $__rows + 8 )) else __height_menu_with_help_size=$(( $__height_menu_with_help_size + $__rows + 4 )) fi setvar "$__var_height" $__height_menu_with_help_size fi # Adjust width if desired if [ "$__var_width" ]; then # The sum total between the longest tag-length and the # longest item-length should be used to bump menu width local __n=$(( $__longest_tag + $__longest_item + 10 )) [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6% [ $__n -gt $__width_menu_with_help_size ] && __width_menu_with_help_size=$__n # Update width for help text if using Xdialog(1) if [ "$USE_XDIALOG" ]; then __n=$(( $__longest_help + 10 )) __n=$(( $__n + $__n / 6 )) # plus 16.6% [ $__n -gt $__width_menu_with_help_size ] && __width_menu_with_help_size=$__n fi setvar "$__var_width" $__width_menu_with_help_size fi # Store adjusted rows if desired [ "$__var_rows" ] && setvar "$__var_rows" $__rows # Constrain height, width, and rows to sensible minimum/maximum values # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_menu_constrain \ "$__var_height" "$__var_width" "$__var_rows" "$__prompt" } # f_dialog_radiolist_size [-n] $var_height $var_width $var_rows \ # $title $backtitle $prompt $hline \ # $tag1 $item1 $status1 $tag2 $item2 $status2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--radiolist' boxes sensibly. # # This function helps solve this issue by taking three sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height, width, and rows. The second set of arguments # are the title, backtitle, prompt, and hline. The [optional] third set of # arguments are the radio list itself (comprised of tag/item/status triplets). # The optimal height, width, and rows for the described widget (not exceeding # the actual terminal height or width) is stored in $var_height, $var_width, # and $var_rows (respectively). # # If the first argument is `-n', the calculated sizes ($var_height, $var_width, # and $var_rows) are not constrained to minimum/maximum values. # f_dialog_radiolist_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" __var_rows="$3" local __title="$4" __btitle="$5" __prompt="$6" __hline="$7" shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] || return $FAILURE # Calculate height/width of infobox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_rlist_size __width_rlist_size f_dialog_infobox_size -n \ "${__var_height:+__height_rlist_size}" \ "${__var_width:+__width_rlist_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" # # Always process the menu-item arguments to get the longest tag-length, # longest item-length (both used to bump the width), and the number of # rows (used to bump the height). # local __longest_tag=0 __longest_item=0 __rows_rlist_size=0 while [ $# -ge 3 ]; do local __tag="$1" __item="$2" shift 3 # tag/item/status [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag} [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item} __rows_rlist_size=$(( $__rows_rlist_size + 1 )) done # Adjust rows early (for up-coming height calculation) if [ "$__var_height" -o "$__var_rows" ]; then # Add a row for visual aid if using Xdialog(1) [ "$USE_XDIALOG" ] && __rows_rlist_size=$(( $__rows_rlist_size + 1 )) fi # Adjust height if desired if [ "$__var_height" ]; then # Add rows to height if [ "$USE_XDIALOG" ]; then __height_rlist_size=$(( $__height_rlist_size + $__rows_rlist_size + 7 )) else __height_rlist_size=$(( $__height_rlist_size + $__rows_rlist_size + 4 )) fi setvar "$__var_height" $__height_rlist_size fi # Adjust width if desired if [ "$__var_width" ]; then # Sum total between longest tag-length, longest item-length, # and radio-button width should be used to bump menu width local __n=$(( $__longest_tag + $__longest_item + 13 )) [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6% [ $__n -gt $__width_rlist_size ] && __width_rlist_size=$__n setvar "$__var_width" $__width_rlist_size fi # Store adjusted rows if desired [ "$__var_rows" ] && setvar "$__var_rows" $__rows_rlist_size # Constrain height, width, and rows to sensible minimum/maximum values # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_menu_constrain \ "$__var_height" "$__var_width" "$__var_rows" "$__prompt" } # f_dialog_checklist_size [-n] $var_height $var_width $var_rows \ # $title $backtitle $prompt $hline \ # $tag1 $item1 $status1 $tag2 $item2 $status2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--checklist' boxes sensibly. # # This function helps solve this issue by taking three sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height, width, and rows. The second set of arguments # are the title, backtitle, prompt, and hline. The [optional] third set of # arguments are the check list itself (comprised of tag/item/status triplets). # The optimal height, width, and rows for the described widget (not exceeding # the actual terminal height or width) is stored in $var_height, $var_width, # and $var_rows (respectively). # # If the first argument is `-n', the calculated sizes ($var_height, $var_width, # and $var_rows) are not constrained to minimum/maximum values. # f_dialog_checklist_size() { f_dialog_radiolist_size "$@" } # f_dialog_radiolist_with_help_size [-n] $var_height $var_width $var_rows \ # $title $backtitle $prompt $hline \ # $tag1 $item1 $status1 $help1 \ # $tag2 $item2 $status2 $help2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--radiolist' boxes sensibly. # # This function helps solve this issue by taking three sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height, width, and rows. The second set of arguments # are the title, backtitle, prompt, and hline. The [optional] third set of # arguments are the radio list itself (comprised of tag/item/status/help # quadruplets). The optimal height, width, and rows for the described widget # (not exceeding the actual terminal height or width) is stored in $var_height, # $var_width, and $var_rows (respectively). # # If the first argument is `-n', the calculated sizes ($var_height, $var_width, # and $var_rows) are not constrained to minimum/maximum values. # f_dialog_radiolist_with_help_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" __var_rows="$3" local __title="$4" __btitle="$5" __prompt="$6" __hline="$7" shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] || return $FAILURE # Calculate height/width of infobox (adjusted/constrained below) # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_rlist_with_help_size __width_rlist_with_help_size f_dialog_infobox_size -n \ "${__var_height:+__height_rlist_with_help_size}" \ "${__var_width:+__width_rlist_with_help_size}" \ "$__title" "$__btitle" "$__prompt" "$__hline" # # Always process the menu-item arguments to get the longest tag-length, # longest item-length, longest help-length (help-length only considered # if using Xdialog(1), as it places the help string in the widget) -- # all used to bump the width -- and the number of rows (used to bump # the height). # local __longest_tag=0 __longest_item=0 __longest_help=0 local __rows_rlist_with_help_size=0 while [ $# -ge 4 ]; do local __tag="$1" __item="$2" __status="$3" __help="$4" shift 4 # tag/item/status/help [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag} [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item} [ ${#__help} -gt $__longest_help ] && __longest_help=${#__help} __rows_rlist_with_help_size=$(( $__rows_rlist_with_help_size + 1 )) done # Adjust rows early (for up-coming height calculation) if [ "$__var_height" -o "$__var_rows" ]; then # Add a row for visual aid if using Xdialog(1) [ "$USE_XDIALOG" ] && __rows_rlist_with_help_size=$(( $__rows_rlist_with_help_size + 1 )) fi # Adjust height if desired if [ "$__var_height" ]; then # Add rows to height if [ "$USE_XDIALOG" ]; then __height_rlist_with_help_size=$(( $__height_rlist_with_help_size + $__rows_rlist_with_help_size + 7 )) else __height_rlist_with_help_size=$(( $__height_rlist_with_help_size + $__rows_rlist_with_help_size + 4 )) fi setvar "$__var_height" $__height fi # Adjust width if desired if [ "$__var_width" ]; then # Sum total between longest tag-length, longest item-length, # and radio-button width should be used to bump menu width local __n=$(( $__longest_tag + $__longest_item + 13 )) [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6% [ $__n -gt $__width_rlist_with_help_size ] && __width_rlist_with_help_size=$__n # Update width for help text if using Xdialog(1) if [ "$USE_XDIALOG" ]; then __n=$(( $__longest_help + 10 )) __n=$(( $__n + $__n / 6 )) # plus 16.6% [ $__n -gt $__width_rlist_with_help_size ] && __width_rlist_with_help_size=$__n fi setvar "$__var_width" $__width_rlist_with_help_size fi # Store adjusted rows if desired [ "$__var_rows" ] && setvar "$__var_rows" $__rows_rlist_with_help_size # Constrain height, width, and rows to sensible minimum/maximum values # Return success if no-constrain, else return status from constrain [ ! "$__constrain" ] || f_dialog_menu_constrain \ "$__var_height" "$__var_width" "$__var_rows" "$__prompt" } # f_dialog_checklist_with_help_size [-n] $var_height $var_width $var_rows \ # $title $backtitle $prompt $hline \ # $tag1 $item1 $status1 $help1 \ # $tag2 $item2 $status2 $help2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--checklist' boxes sensibly. # # This function helps solve this issue by taking three sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height, width, and rows. The second set of arguments # are the title, backtitle, prompt, and hline. The [optional] third set of # arguments are the check list itself (comprised of tag/item/status/help # quadruplets). The optimal height, width, and rows for the described widget # (not exceeding the actual terminal height or width) is stored in $var_height, # $var_width, and $var_rows (respectively). # # If the first argument is `-n', the calculated sizes ($var_height, $var_width, # and $var_rows) are not constrained to minimum/maximum values. # f_dialog_checklist_with_help_size() { f_dialog_radiolist_with_help_size "$@" } # f_dialog_calendar_size [-n] $var_height $var_width \ # $title $backtitle $prompt [$hline] # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--calendar' boxes sensibly. # # This function helps solve this issue by taking two sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height and width. The second set of arguments are the # title, backtitle, prompt, and [optionally] hline. The optimal height and # width for the described widget (not exceeding the actual terminal height or # width) is stored in $var_height and $var_width (respectively). # # If the first argument is `-n', the calculated sizes ($var_height and # $var_width) are not constrained to minimum/maximum values. # # Newline character sequences (``\n'') in $prompt are expanded as-is done by # dialog(1). # f_dialog_calendar_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" local __title="$3" __btitle="$4" __prompt="$5" __hline="$6" # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" ] || return $FAILURE # # Obtain/Adjust minimum and maximum thresholds # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). # local __max_height_cal_size __max_width_cal_size f_dialog_max_size __max_height_cal_size __max_width_cal_size __max_width_cal_size=$(( $__max_width_cal_size - 2 )) # the calendar box will refuse to display if too wide local __min_width if [ "$USE_XDIALOG" ]; then __min_width=55 else __min_width=40 __max_height_cal_size=$(( $__max_height_cal_size - $DIALOG_CALENDAR_HEIGHT )) # When using dialog(1), we can't predict whether the user has # disabled shadow's in their `$HOME/.dialogrc' file, so we'll # subtract one for the potential shadow around the widget __max_height_cal_size=$(( $__max_height_cal_size - 1 )) fi # Calculate height if desired if [ "$__var_height" ]; then local __height __height=$( echo "$__prompt" | f_number_of_lines ) if [ "$USE_XDIALOG" ]; then - # Add height to accomodate for embedded calendar widget + # Add height to accommodate for embedded calendar widget __height=$(( $__height + $DIALOG_CALENDAR_HEIGHT - 1 )) # Also, bump height if backtitle is enabled if [ "$__btitle" ]; then local __n __n=$( echo "$__btitle" | f_number_of_lines ) __height=$(( $__height + $__n + 2 )) fi else [ "$__prompt" ] && __height=$(( $__height + 1 )) fi # Enforce maximum height, unless `-n' was passed [ "$__constrain" -a $__height -gt $__max_height_cal_size ] && __height=$__max_height_cal_size setvar "$__var_height" $__height fi # Calculate width if desired if [ "$__var_width" ]; then # NOTE: Function name appended to prevent __var_{height,width} # values from becoming local (and thus preventing setvar # from working). local __width_cal_size f_dialog_infobox_size -n "" __width_cal_size \ "$__title" "$__btitle" "$__prompt" "$__hline" # Enforce minimum/maximum width, unless `-n' was passed if [ "$__constrain" ]; then if [ $__width_cal_size -lt $__min_width ]; then __width_cal_size=$__min_width elif [ $__width_cal_size -gt $__max_width_cal_size ] then __width_cal_size=$__max_width_size fi fi setvar "$__var_width" $__width_cal_size fi return $SUCCESS } # f_dialog_timebox_size [-n] $var_height $var_width \ # $title $backtitle $prompt [$hline] # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--timebox' boxes sensibly. # # This function helps solve this issue by taking two sets of sequential # arguments. The first set of arguments are the variable names to use when # storing the calculated height and width. The second set of arguments are the # title, backtitle, prompt, and [optionally] hline. The optional height and # width for the described widget (not exceeding the actual terminal height or # width) is stored in $var_height and $var_width (respectively). # # If the first argument is `-n', the calculated sizes ($var_height and # $var_width) are not constrained to minimum/maximum values. # # Newline character sequences (``\n'') in $prompt are expanded as-is done by # dialog(1). # f_dialog_timebox_size() { local __constrain=1 [ "$1" = "-n" ] && __constrain= && shift 1 # -n local __var_height="$1" __var_width="$2" local __title="$3" __btitle="$4" __prompt="$5" __hline="$6" # Return unless at least one size aspect has been requested [ "$__var_height" -o "$__var_width" ] || return $FAILURE # # Obtain/Adjust minimum and maximum thresholds # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). # local __max_height_tbox_size __max_width_tbox_size f_dialog_max_size __max_height_tbox_size __max_width_tbox_size __max_width_tbox_size=$(( $__max_width_tbox_size - 2 )) # the timebox widget refuses to display if too wide local __min_width if [ "$USE_XDIALOG" ]; then __min_width=40 else __min_width=20 __max_height_tbox_size=$(( \ $__max_height_tbox_size - $DIALOG_TIMEBOX_HEIGHT )) # When using dialog(1), we can't predict whether the user has # disabled shadow's in their `$HOME/.dialogrc' file, so we'll # subtract one for the potential shadow around the widget __max_height_tbox_size=$(( $__max_height_tbox_size - 1 )) fi # Calculate height if desired if [ "$__var_height" -a "$USE_XDIALOG" ]; then # When using Xdialog(1), the height seems to have # no effect. All values provide the same results. setvar "$__var_height" 0 # autosize elif [ "$__var_height" ]; then local __height __height=$( echo "$__prompt" | f_number_of_lines ) __height=$(( $__height ${__prompt:++1} + 1 )) # Enforce maximum height, unless `-n' was passed [ "$__constrain" -a $__height -gt $__max_height_tbox_size ] && __height=$__max_height_tbox_size setvar "$__var_height" $__height fi # Calculate width if desired if [ "$__var_width" ]; then # NOTE: Function name appended to prevent __var_{height,width} # values from becoming local (and thus preventing setvar # from working). local __width_tbox_size f_dialog_infobox_size -n "" __width_tbox_size \ "$__title" "$__btitle" "$__prompt" "$__hline" # Enforce the minimum width for displaying the timebox if [ "$__constrain" ]; then if [ $__width_tbox_size -lt $__min_width ]; then __width_tbox_size=$__min_width elif [ $__width_tbox_size -ge $__max_width_tbox_size ] then __width_tbox_size=$__max_width_tbox_size fi fi setvar "$__var_width" $__width_tbox_size fi return $SUCCESS } ############################################################ CLEAR FUNCTIONS # f_dialog_clear # # Clears any/all previous dialog(1) displays. # f_dialog_clear() { $DIALOG --clear } ############################################################ INFO FUNCTIONS # f_dialog_info $info_text ... # # Throw up a dialog(1) infobox. The infobox remains until another dialog is # displayed or `dialog --clear' (or f_dialog_clear) is called. # f_dialog_info() { local info_text="$*" height width f_dialog_infobox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$info_text" $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ ${USE_XDIALOG:+--ignore-eof} \ ${USE_XDIALOG:+--no-buttons} \ --infobox "$info_text" $height $width } # f_xdialog_info $info_text ... # # Throw up an Xdialog(1) infobox and do not dismiss it until stdin produces # EOF. This implies that you must execute this either as an rvalue to a pipe, # lvalue to indirection or in a sub-shell that provides data on stdin. # # To open an Xdialog(1) infobox that does not disappear until expeclitly dis- # missed, use the following: # # f_xdialog_info "$info_text" < /dev/tty & # pid=$! # # Perform some lengthy actions # kill $pid # # NB: Check $USE_XDIALOG if you need to support both dialog(1) and Xdialog(1). # f_xdialog_info() { local info_text="$*" height width f_dialog_infobox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$info_text" exec $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --no-close --no-buttons \ --infobox "$info_text" $height $width \ -1 # timeout of -1 means abort when EOF on stdin } ############################################################ PAUSE FUNCTIONS # f_dialog_pause $msg_text $duration [$hline] # # Display a message in a widget with a progress bar that runs backward for # $duration seconds. # f_dialog_pause() { local pause_text="$1" duration="$2" hline="$3" height width f_isinteger "$duration" || return $FAILURE f_dialog_buttonbox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$pause_text" "$hline" if [ "$USE_XDIALOG" ]; then $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --ok-label "$msg_skip" \ --cancel-label "$msg_cancel" \ ${noCancel:+--no-cancel} \ --timeout "$duration" \ --yesno "$pause_text" \ $height $width else [ $duration -gt 0 ] && duration=$(( $duration - 1 )) height=$(( $height + 3 )) # Add height for progress bar $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --ok-label "$msg_skip" \ --cancel-label "$msg_cancel" \ ${noCancel:+--no-cancel} \ --pause "$pause_text" \ $height $width "$duration" fi } # f_dialog_pause_no_cancel $msg_text $duration [$hline] # # Display a message in a widget with a progress bar that runs backward for # $duration seconds. No cancel button is provided. Always returns success. # f_dialog_pause_no_cancel() { noCancel=1 f_dialog_pause "$@" return $SUCCESS } ############################################################ MSGBOX FUNCTIONS # f_dialog_msgbox $msg_text [$hline] # # Throw up a dialog(1) msgbox. The msgbox remains until the user presses ENTER # or ESC, acknowledging the modal dialog. # # If the user presses ENTER, the exit status is zero (success), otherwise if # the user presses ESC the exit status is 255. # f_dialog_msgbox() { local msg_text="$1" hline="$2" height width f_dialog_buttonbox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_text" "$hline" $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --ok-label "$msg_ok" \ --msgbox "$msg_text" $height $width } ############################################################ TEXTBOX FUNCTIONS # f_dialog_textbox $file # # Display the contents of $file (or an error if $file does not exist, etc.) in # a dialog(1) textbox (which has a scrollable region for the text). The textbox # remains until the user presses ENTER or ESC, acknowledging the modal dialog. # # If the user presses ENTER, the exit status is zero (success), otherwise if # the user presses ESC the exit status is 255. # f_dialog_textbox() { local file="$1" local contents height width retval contents=$( cat "$file" 2>&1 ) retval=$? f_dialog_buttonbox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$contents" if [ $retval -eq $SUCCESS ]; then $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --exit-label "$msg_ok" \ --no-cancel \ --textbox "$file" $height $width else $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --ok-label "$msg_ok" \ --msgbox "$contents" $height $width fi } ############################################################ YESNO FUNCTIONS # f_dialog_yesno $msg_text [$hline] # # Display a dialog(1) Yes/No prompt to allow the user to make some decision. # The yesno prompt remains until the user presses ENTER or ESC, acknowledging # the modal dialog. # # If the user chooses YES the exit status is zero, or chooses NO the exit # status is one, or presses ESC the exit status is 255. # f_dialog_yesno() { local msg_text="$1" height width local hline="${2-$hline_arrows_tab_enter}" f_interactive || return 0 # If non-interactive, return YES all the time f_dialog_buttonbox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_text" "$hline" if [ "$USE_XDIALOG" ]; then $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --ok-label "$msg_yes" \ --cancel-label "$msg_no" \ --yesno "$msg_text" $height $width else $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --yes-label "$msg_yes" \ --no-label "$msg_no" \ --yesno "$msg_text" $height $width fi } # f_dialog_noyes $msg_text [$hline] # # Display a dialog(1) No/Yes prompt to allow the user to make some decision. # The noyes prompt remains until the user presses ENTER or ESC, acknowledging # the modal dialog. # # If the user chooses YES the exit status is zero, or chooses NO the exit # status is one, or presses ESC the exit status is 255. # # NOTE: This is just like the f_dialog_yesno function except "No" is default. # f_dialog_noyes() { local msg_text="$1" height width local hline="${2-$hline_arrows_tab_enter}" f_interactive || return 1 # If non-interactive, return NO all the time f_dialog_buttonbox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_text" "$hline" if [ "$USE_XDIALOG" ]; then $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --default-no \ --ok-label "$msg_yes" \ --cancel-label "$msg_no" \ --yesno "$msg_text" $height $width else $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --defaultno \ --yes-label "$msg_yes" \ --no-label "$msg_no" \ --yesno "$msg_text" $height $width fi } ############################################################ INPUT FUNCTIONS # f_dialog_inputstr_store [-s] $text # # Store some text from a dialog(1) inputbox to be retrieved later by # f_dialog_inputstr_fetch(). If the first argument is `-s', the text is # sanitized before being stored. # f_dialog_inputstr_store() { local sanitize= [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s local text="$1" # Sanitize the line before storing it if desired [ "$sanitize" ] && f_dialog_line_sanitize text setvar DIALOG_INPUTBOX_$$ "$text" } # f_dialog_inputstr_fetch [$var_to_set] # # Obtain the inputstr entered by the user from the most recently displayed # dialog(1) inputbox (previously stored with f_dialog_inputstr_store() above). # If $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # f_dialog_inputstr_fetch() { local __var_to_set="$1" __cp debug= f_getvar DIALOG_INPUTBOX_$$ "${__var_to_set:-__cp}" # get data setvar DIALOG_INPUTBOX_$$ "" # scrub memory in case data was sensitive # Return the line on standard-out if desired [ "$__var_to_set" ] || echo "$__cp" return $SUCCESS } # f_dialog_input $var_to_set $prompt [$init [$hline]] # # Prompt the user with a dialog(1) inputbox to enter some value. The inputbox # remains until the the user presses ENTER or ESC, or otherwise ends the # editing session (by selecting `Cancel' for example). # # If the user presses ENTER, the exit status is zero (success), otherwise if # the user presses ESC the exit status is 255, or if the user chose Cancel, the # exit status is instead 1. # # NOTE: The hline should correspond to the type of data you want from the user. # NOTE: Should not be used to edit multiline values. # f_dialog_input() { local __var_to_set="$1" __prompt="$2" __init="$3" __hline="$4" # NOTE: Function name appended to prevent __var_{height,width} values # from becoming local (and thus preventing setvar from working). local __height_input __width_input f_dialog_inputbox_size __height_input __width_input \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" \ "$__prompt" "$__init" "$__hline" local __opterm="--" [ "$USE_XDIALOG" ] && __opterm= local __dialog_input __dialog_input=$( $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$__hline" \ --ok-label "$msg_ok" \ --cancel-label "$msg_cancel" \ --inputbox "$__prompt" \ $__height_input $__width_input \ $__opterm "$__init" \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) local __retval=$? # Remove warnings and leading/trailing whitespace from user input f_dialog_line_sanitize __dialog_input setvar "$__var_to_set" "$__dialog_input" return $__retval } ############################################################ MENU FUNCTIONS # f_dialog_menutag_store [-s] $text # # Store some text from a dialog(1) menu to be retrieved later by # f_dialog_menutag_fetch(). If the first argument is `-s', the text is # sanitized before being stored. # f_dialog_menutag_store() { local sanitize= [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s local text="$1" # Sanitize the menutag before storing it if desired [ "$sanitize" ] && f_dialog_data_sanitize text setvar DIALOG_MENU_$$ "$text" } # f_dialog_menutag_fetch [$var_to_set] # # Obtain the menutag chosen by the user from the most recently displayed # dialog(1) menu (previously stored with f_dialog_menutag_store() above). If # $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # f_dialog_menutag_fetch() { local __var_to_set="$1" __cp debug= f_getvar DIALOG_MENU_$$ "${__var_to_set:-__cp}" # get the data setvar DIALOG_MENU_$$ "" # scrub memory in case data was sensitive # Return the data on standard-out if desired [ "$__var_to_set" ] || echo "$__cp" return $SUCCESS } # f_dialog_menuitem_store [-s] $text # # Store the item from a dialog(1) menu (see f_dialog_menutag2item()) to be # retrieved later by f_dialog_menuitem_fetch(). If the first argument is `-s', # the text is sanitized before being stored. # f_dialog_menuitem_store() { local sanitize= [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s local text="$1" # Sanitize the menuitem before storing it if desired [ "$sanitize" ] && f_dialog_data_sanitize text setvar DIALOG_MENUITEM_$$ "$text" } # f_dialog_menuitem_fetch [$var_to_set] # # Obtain the menuitem chosen by the user from the most recently displayed # dialog(1) menu (previously stored with f_dialog_menuitem_store() above). If # $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # f_dialog_menuitem_fetch() { local __var_to_set="$1" __cp debug= f_getvar DIALOG_MENUITEM_$$ "${__var_to_set:-__cp}" # get data setvar DIALOG_MENUITEM_$$ "" # scrub memory in case data was sensitive # Return the data on standard-out if desired [ "$__var_to_set" ] || echo "$__cp" return $SUCCESS } # f_dialog_default_store [-s] $text # # Store some text to be used later as the --default-item argument to dialog(1) # (or Xdialog(1)) for --menu, --checklist, and --radiolist widgets. Retrieve # the text later with f_dialog_menutag_fetch(). If the first argument is `-s', # the text is sanitized before being stored. # f_dialog_default_store() { local sanitize= [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s local text="$1" # Sanitize the defaulitem before storing it if desired [ "$sanitize" ] && f_dialog_data_sanitize text setvar DEFAULTITEM_$$ "$text" } # f_dialog_default_fetch [$var_to_set] # # Obtain text to be used with the --default-item argument of dialog(1) (or # Xdialog(1)) (previously stored with f_dialog_default_store() above). If # $var_to_set is NULL or missing, output is printed to stdout (which is less # recommended due to performance degradation; in a loop for example). # f_dialog_default_fetch() { local __var_to_set="$1" __cp debug= f_getvar DEFAULTITEM_$$ "${__var_to_set:-__cp}" # get the data setvar DEFAULTITEM_$$ "" # scrub memory in case data was sensitive # Return the data on standard-out if desired [ "$__var_to_set" ] || echo "$__cp" return $SUCCESS } # f_dialog_menutag2item $tag_chosen $tag1 $item1 $tag2 $item2 ... # # To use the `--menu' option of dialog(1) you must pass an ordered list of # tag/item pairs on the command-line. When the user selects a menu option the # tag for that item is printed to stderr. # # This function allows you to dereference the tag chosen by the user back into # the item associated with said tag. # # Pass the tag chosen by the user as the first argument, followed by the # ordered list of tag/item pairs (HINT: use the same tag/item list as was # passed to dialog(1) for consistency). # # If the tag cannot be found, NULL is returned. # f_dialog_menutag2item() { local tag="$1" tagn item shift 1 # tag while [ $# -gt 0 ]; do tagn="$1" item="$2" shift 2 # tagn/item if [ "$tag" = "$tagn" ]; then echo "$item" return $SUCCESS fi done return $FAILURE } # f_dialog_menutag2item_with_help $tag_chosen $tag1 $item1 $help1 \ # $tag2 $item2 $help2 ... # # To use the `--menu' option of dialog(1) with the `--item-help' option, you # must pass an ordered list of tag/item/help triplets on the command-line. When # the user selects a menu option the tag for that item is printed to stderr. # # This function allows you to dereference the tag chosen by the user back into # the item associated with said tag (help is discarded/ignored). # # Pass the tag chosen by the user as the first argument, followed by the # ordered list of tag/item/help triplets (HINT: use the same tag/item/help list # as was passed to dialog(1) for consistency). # # If the tag cannot be found, NULL is returned. # f_dialog_menutag2item_with_help() { local tag="$1" tagn item shift 1 # tag while [ $# -gt 0 ]; do tagn="$1" item="$2" shift 3 # tagn/item/help if [ "$tag" = "$tagn" ]; then echo "$item" return $SUCCESS fi done return $FAILURE } # f_dialog_menutag2index $tag_chosen $tag1 $item1 $tag2 $item2 ... # # To use the `--menu' option of dialog(1) you must pass an ordered list of # tag/item pairs on the command-line. When the user selects a menu option the # tag for that item is printed to stderr. # # This function allows you to dereference the tag chosen by the user back into # the index associated with said tag. The index is the one-based tag/item pair # array position within the ordered list of tag/item pairs passed to dialog(1). # # Pass the tag chosen by the user as the first argument, followed by the # ordered list of tag/item pairs (HINT: use the same tag/item list as was # passed to dialog(1) for consistency). # # If the tag cannot be found, NULL is returned. # f_dialog_menutag2index() { local tag="$1" tagn n=1 shift 1 # tag while [ $# -gt 0 ]; do tagn="$1" shift 2 # tagn/item if [ "$tag" = "$tagn" ]; then echo $n return $SUCCESS fi n=$(( $n + 1 )) done return $FAILURE } # f_dialog_menutag2index_with_help $tag_chosen $tag1 $item1 $help1 \ # $tag2 $item2 $help2 ... # # To use the `--menu' option of dialog(1) with the `--item-help' option, you # must pass an ordered list of tag/item/help triplets on the command-line. When # the user selects a menu option the tag for that item is printed to stderr. # # This function allows you to dereference the tag chosen by the user back into # the index associated with said tag. The index is the one-based tag/item/help # triplet array position within the ordered list of tag/item/help triplets # passed to dialog(1). # # Pass the tag chosen by the user as the first argument, followed by the # ordered list of tag/item/help triplets (HINT: use the same tag/item/help list # as was passed to dialog(1) for consistency). # # If the tag cannot be found, NULL is returned. # f_dialog_menutag2index_with_help() { local tag="$1" tagn n=1 shift 1 # tag while [ $# -gt 0 ]; do tagn="$1" shift 3 # tagn/item/help if [ "$tag" = "$tagn" ]; then echo $n return $SUCCESS fi n=$(( $n + 1 )) done return $FAILURE } # f_dialog_menutag2help $tag_chosen $tag1 $item1 $help1 $tag2 $item2 $help2 ... # # To use the `--menu' option of dialog(1) with the `--item-help' option, you # must pass an ordered list of tag/item/help triplets on the command-line. When # the user selects a menu option the tag for that item is printed to stderr. # # This function allows you to dereference the tag chosen by the user back into # the help associated with said tag (item is discarded/ignored). # # Pass the tag chosen by the user as the first argument, followed by the # ordered list of tag/item/help triplets (HINT: use the same tag/item/help list # as was passed to dialog(1) for consistency). # # If the tag cannot be found, NULL is returned. # f_dialog_menutag2help() { local tag="$1" tagn help shift 1 # tag while [ $# -gt 0 ]; do tagn="$1" help="$3" shift 3 # tagn/item/help if [ "$tag" = "$tagn" ]; then echo "$help" return $SUCCESS fi done return $FAILURE } ############################################################ INIT FUNCTIONS # f_dialog_init # # Initialize (or re-initialize) the dialog module after setting/changing any # of the following environment variables: # # USE_XDIALOG Either NULL or Non-NULL. If given a value will indicate # that Xdialog(1) should be used instead of dialog(1). # # SECURE Either NULL or Non-NULL. If given a value will indicate # that (while running as root) sudo(8) authentication is # required to proceed. # # Also reads ~/.dialogrc for the following information: # # NO_SHADOW Either NULL or Non-NULL. If use_shadow is OFF (case- # insensitive) in ~/.dialogrc this is set to "1" (otherwise # unset). # f_dialog_init() { local funcname=f_dialog_init DIALOG_SELF_INITIALIZE= USE_DIALOG=1 # # Clone terminal stdout so we can redirect to it from within sub-shells # eval exec $DIALOG_TERMINAL_PASSTHRU_FD\>\&1 # # Add `-S' and `-X' to the list of standard arguments supported by all # case "$GETOPTS_STDARGS" in *SX*) : good ;; # already present *) GETOPTS_STDARGS="${GETOPTS_STDARGS}SX" esac # # Process stored command-line arguments # # NB: Using backticks instead of $(...) for portability since Linux # bash(1) balks at the right parentheses encountered in the case- # statement (incorrectly interpreting it as the close of $(...)). # f_dprintf "f_dialog_init: ARGV=[%s] GETOPTS_STDARGS=[%s]" \ "$ARGV" "$GETOPTS_STDARGS" SECURE=`set -- $ARGV OPTIND=1 while getopts \ "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" \ flag > /dev/null; do case "$flag" in S) echo 1 ;; esac done ` # END-BACKTICK USE_XDIALOG=`set -- $ARGV OPTIND=1 while getopts \ "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" \ flag > /dev/null; do case "$flag" in S|X) echo 1 ;; esac done ` # END-BACKTICK f_dprintf "f_dialog_init: SECURE=[%s] USE_XDIALOG=[%s]" \ "$SECURE" "$USE_XDIALOG" # # Process `-X' command-line option # [ "$USE_XDIALOG" ] && DIALOG=Xdialog USE_DIALOG= # # Sanity check, or die gracefully # if ! f_have $DIALOG; then unset USE_XDIALOG local failed_dialog="$DIALOG" DIALOG=dialog f_die 1 "$msg_no_such_file_or_directory" "$pgm" "$failed_dialog" fi # # Read ~/.dialogrc (unless using Xdialog(1)) for properties # if [ -f ~/.dialogrc -a ! "$USE_XDIALOG" ]; then eval "$( awk -v param=use_shadow -v expect=OFF \ -v set="NO_SHADOW=1" ' !/^[[:space:]]*(#|$)/ && \ tolower($1) ~ "^"param"(=|$)" && \ /[^#]*=/ { sub(/^[^=]*=[[:space:]]*/, "") if ( toupper($1) == expect ) print set";" }' ~/.dialogrc )" fi # # If we're already running as root but we got there by way of sudo(8) # and we have X11, we should merge the xauth(1) credentials from our # original user. # if [ "$USE_XDIALOG" ] && [ "$( id -u )" = "0" ] && [ "$SUDO_USER" -a "$DISPLAY" ] then if ! f_have xauth; then # Die gracefully, as we [likely] can't use Xdialog(1) unset USE_XDIALOG DIALOG=dialog f_die 1 "$msg_no_such_file_or_directory" "$pgm" "xauth" fi HOSTNAME=$( hostname ) local displaynum="${DISPLAY#*:}" eval xauth -if \~$SUDO_USER/.Xauthority extract - \ \"\$HOSTNAME/unix:\$displaynum\" \ \"\$HOSTNAME:\$displaynum\" | sudo sh -c 'xauth -ivf \ ~root/.Xauthority merge - > /dev/null 2>&1' fi # # Probe Xdialog(1) for maximum height/width constraints, or die # gracefully # if [ "$USE_XDIALOG" ]; then local maxsize if ! f_eval_catch -dk maxsize $funcname "$DIALOG" \ 'LANG= LC_ALL= %s --print-maxsize' "$DIALOG" then # Xdialog(1) failed, fall back to dialog(1) unset USE_XDIALOG # Display the error message produced by Xdialog(1) local height width f_dialog_buttonbox_size height width \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$maxsize" dialog \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --ok-label "$msg_ok" \ --msgbox "$maxsize" $height $width exit $FAILURE fi XDIALOG_MAXSIZE=$( set -- ${maxsize##*:} height=${1%,} width=$2 echo $height $width ) fi # # If using Xdialog(1), swap DIALOG_TITLE with DIALOG_BACKTITLE. # The reason for this is because many dialog(1) applications use # --backtitle for the program name (which is better suited as # --title with Xdialog(1)). # if [ "$USE_XDIALOG" ]; then local _DIALOG_TITLE="$DIALOG_TITLE" DIALOG_TITLE="$DIALOG_BACKTITLE" DIALOG_BACKTITLE="$_DIALOG_TITLE" fi f_dprintf "f_dialog_init: dialog(1) API initialized." } ############################################################ MAIN # # Self-initialize unless requested otherwise # f_dprintf "%s: DIALOG_SELF_INITIALIZE=[%s]" \ dialog.subr "$DIALOG_SELF_INITIALIZE" case "$DIALOG_SELF_INITIALIZE" in ""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;; *) f_dialog_init esac f_dprintf "%s: Successfully loaded." dialog.subr fi # ! $_DIALOG_SUBR Index: head/usr.sbin/bsdconfig/share/geom.subr =================================================================== --- head/usr.sbin/bsdconfig/share/geom.subr (revision 298883) +++ head/usr.sbin/bsdconfig/share/geom.subr (revision 298884) @@ -1,430 +1,430 @@ if [ ! "$_GEOM_SUBR" ]; then _GEOM_SUBR=1 # # Copyright (c) 2012-2014 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" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." geom.subr f_include $BSDCFG_SHARE/strings.subr f_include $BSDCFG_SHARE/struct.subr ############################################################ GLOBALS NGEOM_CLASSES=0 # Set by f_geom_get_all()/f_geom_reset() # # GEOM classes for use with f_geom_find() # # NB: Since $GEOM_CLASS_ANY is the NULL string, make sure you quote it whenever # you put arguments after it. # setvar GEOM_CLASS_ANY "any" setvar GEOM_CLASS_DEV "DEV" setvar GEOM_CLASS_DISK "DISK" setvar GEOM_CLASS_ELI "ELI" setvar GEOM_CLASS_FD "FD" setvar GEOM_CLASS_LABEL "LABEL" setvar GEOM_CLASS_MD "MD" setvar GEOM_CLASS_NOP "NOP" setvar GEOM_CLASS_PART "PART" setvar GEOM_CLASS_RAID "RAID" setvar GEOM_CLASS_SWAP "SWAP" setvar GEOM_CLASS_VFS "VFS" setvar GEOM_CLASS_ZFS_VDEV "ZFS::VDEV" setvar GEOM_CLASS_ZFS_ZVOL "ZFS::ZVOL" # # GEOM structure definitions # f_struct_define GEOM_CLASS \ id name ngeoms f_struct_define GEOM_GEOM \ id class_ref config name nconsumers nproviders rank # Also consumerN where N is 1 through nconsumers # Also providerN where N is 1 through nproviders f_struct_define GEOM_CONSUMER \ id geom_ref config mode provider_ref f_struct_define GEOM_PROVIDER \ id geom_ref config mode name mediasize # The config property of GEOM_GEOM struct is defined as this f_struct_define GEOM_GEOM_CONFIG \ entries first fwheads fwsectors last modified scheme state # The config property of GEOM_PROVIDER struct is defined as this f_struct_define GEOM_PROVIDER_CONFIG \ descr file fwheads fwsectors ident length type unit # # Default behavior is to call f_geom_get_all() automatically when loaded. # : ${GEOM_SELF_SCAN_ALL=1} ############################################################ FUNCTIONS # f_geom_get_all # # Parse sysctl(8) `kern.geom.confxml' data into a series of structs. GEOM -# classes are at the top of the heirarchy and are stored as numbered structs +# classes are at the top of the hierarchy and are stored as numbered structs # from 1 to $NGEOM_CLASSES (set by this function) named `geom_class_C'. GEOM # objects within each class are stored as numbered structs from 1 to `ngeoms' # (a property of the GEOM class struct) named `geom_class_C_geom_N' (where C # is the class number and N is the geom number). # # Use the function f_geom_find() to get a list of geoms (execute without # arguments) or find specific geoms by class or name. # f_geom_get_all() { eval "$( sysctl -n kern.geom.confxml | awk ' BEGIN { struct_count["class"] = 0 struct_count["geom"] = 0 struct_count["consumer"] = 0 struct_count["provider"] = 0 } ############################################### FUNCTIONS function set_value(prop, value) { if (!struct_stack[cur_struct]) return printf "%s set %s \"%s\"\n", struct_stack[cur_struct], prop, value } function create(type, id) { if (struct = created[type "_" id]) print "f_struct_free", struct else { struct = struct_stack[cur_struct] struct = struct ( struct ? "" : "geom" ) struct = struct "_" type "_" ++struct_count[type] created[type "_" id] = struct } print "debug= f_struct_new GEOM_" toupper(type), struct cur_struct++ struct_stack[cur_struct] = struct type_stack[cur_struct] = type set_value("id", id) } function create_config() { struct = struct_stack[cur_struct] struct = struct ( struct ? "" : "geom" ) struct = struct "_config" set_value("config", struct) type = type_stack[cur_struct] print "debug= f_struct_new GEOM_" toupper(type) "_CONFIG", \ struct cur_struct++ struct_stack[cur_struct] = struct type_stack[cur_struct] = type "_config" } function extract_attr(field, attr) { if (match(field, attr "=\"0x[[:xdigit:]]+\"")) { len = length(attr) return substr($2, len + 3, RLENGTH - len - 3) } } function extract_data(type) { data = $0 sub("^[[:space:]]*<" type ">", "", data) sub(".*$", "", data) return data } ############################################### OPENING PATTERNS $1 == "" { mesh = 1 } $1 ~ /^<(class|geom)$/ && mesh { prop = substr($1, 2) if ((ref = extract_attr($2, "ref")) != "") set_value(prop "_ref", ref) else if ((id = extract_attr($2, "id")) != "") create(prop, id) } $1 ~ /^<(consumer|provider)$/ && mesh { prop = substr($1, 2) if ((ref = extract_attr($2, "ref")) != "") set_value(prop "_ref", ref) else if ((id = extract_attr($2, "id")) != "") { create(prop, id) cur_struct-- propn = struct_count[prop] set_value(prop propn, struct_stack[cur_struct+1]) cur_struct++ } } $1 == "" && mesh { create_config() } ############################################### PROPERTIES $1 ~ /^<[[:alnum:]]+>/ { prop = $1 sub(/^.*/, "", prop) set_value(prop, extract_data(prop)) } ############################################### CLOSING PATTERNS $1 ~ "^$" { cur_struct-- } $1 == "" { set_value("nconsumers", struct_count["consumer"]) set_value("nproviders", struct_count["provider"]) cur_struct-- struct_count["consumer"] = 0 struct_count["provider"] = 0 } $1 == "" { set_value("ngeoms", struct_count["geom"]) cur_struct-- struct_count["consumer"] = 0 struct_count["provider"] = 0 struct_count["geom"] = 0 } $1 == "" { printf "NGEOM_CLASSES=%u\n", struct_count["class"] delete struct_count mesh = 0 }' )" } # f_geom_reset # # Reset the registered GEOM chain. # f_geom_reset() { local classn=1 class ngeoms geomn geom while [ $classn -le ${NGEOM_CLASSES:-0} ]; do class=geom_class_$classn $class get ngeoms ngeoms geomn=1 while [ $geomn -le $ngeoms ]; do f_struct_free ${class}_geom_$geomn geomn=$(( $geomn + 1 )) done classn=$(( $classn + 1 )) done NGEOM_CLASSES=0 } # f_geom_rescan # # Rescan all GEOMs - convenience function. # f_geom_rescan() { f_geom_reset f_geom_get_all } # f_geom_find $name [$type [$var_to_set]] # # Find one or more registered GEOMs by name, type, or both. Returns a space- # separated list of GEOMs matching the search criterion. The $type argument # should be the GEOM class (see $GEOM_CLASS_* variables in GLOBALS above). # # If $var_to_set is missing or NULL, the GEOM name(s) are printed to standard # out for capturing in a sub-shell (which is less-recommended because of # performance degredation; for example, when called in a loop). # f_geom_find() { local __name="$1" __type="${2:-$GEOM_CLASS_ANY}" __var_to_set="$3" local __classn=1 __class __class_name __ngeoms local __geomn __geom __geom_name __found= while [ $__classn -le ${NGEOM_CLASSES:-0} ]; do __class=geom_class_$__classn $__class get name __class_name if [ "$__type" != "$GEOM_CLASS_ANY" -a \ "$__type" != "$__class_name" ] then __classn=$(( $__classn + 1 )) continue fi __geomn=1 $__class get ngeoms __ngeoms || __ngeoms=0 while [ $__geomn -le $__ngeoms ]; do __geom=${__class}_geom_$__geomn $__geom get name __geom_name [ "$__name" = "$__geom_name" -o ! "$__name" ] && __found="$__found $__geom" __geomn=$(( $__geomn + 1 )) done __classn=$(( $__classn + 1 )) done if [ "$__var_to_set" ]; then setvar "$__var_to_set" "${__found# }" else echo $__found fi [ "$__found" ] # Return status } # f_geom_find_by $prop $find [$type [$var_to_set]] # # Find GEOM-related struct where $prop of the struct is equal to $find. Returns # NULL or the name of the first GEOM struct to match. The $type argument should # be one of the following: # # NULL Find any of the below # class Find GEOM_CLASS struct # geom Find GEOM_GEOM struct # consumer Find GEOM_CONSUMER struct # provider Find GEOM_PROVIDER struct # # The $prop argument can be any property of the given type of struct. Some # properties are common to all types (such as id) so the $type argument is # optional (allowing you to return any struct whose property matches $find). # # If $var_to_set is missing or NULL, the GEOM struct name is printed to # standard out for capturing in a sub-shell (which is less-recommended because # of performance degredation; for example when called in a loop). # f_geom_find_by() { local __prop="$1" __find="$2" __type="$3" __var_to_set="$4" local __classn=1 __class __ngeoms local __geomn __geom __nitems local __itype __itemn __item local __value __found= if [ ! "$__prop" ]; then [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi case "$__type" in "") : OK ;; class|GEOM_CLASS) __type=class ;; geom|GEOM_GEOM) __type=geom ;; consumer|GEOM_CONSUMER) __type=consumer ;; provider|GEOM_PROVIDER) __type=provider ;; *) [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE esac while [ $__classn -le ${NGEOM_CLASSES:-0} ]; do __class=geom_class_$__classn if [ "${__type:-class}" = "class" ]; then $__class get "$__prop" __value || __value= [ "$__value" = "$__find" ] && __found="$__class" break [ "$__type" ] && __classn=$(( $__classn + 1 )) continue fi __geomn=1 $__class get ngeoms __ngeoms || __ngeoms=0 while [ $__geomn -le $__ngeoms ]; do __geom=${__class}_geom_$__geomn if [ "${__type:-geom}" = "geom" ]; then $__geom get "$__prop" __value || __value= [ "$__value" = "$__find" ] && __found="$__geom" break [ "$__type" ] && __geomn=$(( $__geomn + 1 )) continue fi for __itype in ${__type:-consumer provider}; do $__geom get n${__itype}s __nitems || continue __itemn=1 while [ $__itemn -le $__nitems ]; do __item=${__geom}_${__itype}_$__itemn $__item get "$__prop" __value || __value= [ "$__value" = "$__find" ] && __found="$__item" break __itemn=$(( $__itemn + 1 )) done [ "$__found" ] && break done [ "$__found" ] && break __geomn=$(( $__geomn + 1 )) done [ "$__found" ] && break __classn=$(( $__classn + 1 )) done if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__found" else [ "$__found" ] && echo "$__found" fi [ "$__found" ] # Return status } # f_geom_parent $geom|$consumer|$provider|$config [$var_to_set] # # Get the GEOM class associated with one of $geom, $consumer, $provider or # $config. # # If $var_to_set is missing or NULL, the GEOM class name is printed to standard # out for capturing in a sub-shell (which is less-recommended because of # performance degredation; for example when called in a loop). # f_geom_parent() { local __struct="$1" __var_to_set="$2" # NB: Order of pattern matches below is important case "$__struct" in *_config*) __struct="${__struct%_config*}" ;; *_consumer_*) __struct="${__struct%_consumer_[0-9]*}" ;; *_provider_*) __struct="${__struct%_provider_[0-9]*}" ;; *_geom_*) __struct="${__struct%_geom_[0-9]*}" ;; *) __struct= esac if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__struct" else echo "$__struct" fi f_struct "$__struct" # Return status } ############################################################ MAIN # # Parse GEOM configuration unless requested otherwise # f_dprintf "%s: GEOM_SELF_SCAN_ALL=[%s]" geom.subr "$GEOM_SELF_SCAN_ALL" case "$GEOM_SELF_SCAN_ALL" in ""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;; *) f_geom_get_all if [ "$debug" ]; then debug= f_geom_find "" "$GEOM_CLASS_ANY" geoms f_count ngeoms $geoms f_dprintf "%s: Initialized %u geom devices in %u classes." \ geom.subr "$ngeoms" "$NGEOM_CLASSES" unset geoms ngeoms fi esac f_dprintf "%s: Successfully loaded." geom.subr fi # ! $_GEOM_SUBR Index: head/usr.sbin/bsdconfig/share/media/tcpip.subr =================================================================== --- head/usr.sbin/bsdconfig/share/media/tcpip.subr (revision 298883) +++ head/usr.sbin/bsdconfig/share/media/tcpip.subr (revision 298884) @@ -1,1713 +1,1713 @@ if [ ! "$_MEDIA_TCPIP_SUBR" ]; then _MEDIA_TCPIP_SUBR=1 # # Copyright (c) 2012-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. # # $FreeBSD$ # ############################################################ INCLUDES BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." media/tcpip.subr f_include $BSDCFG_SHARE/device.subr f_include $BSDCFG_SHARE/dialog.subr f_include $BSDCFG_SHARE/strings.subr f_include $BSDCFG_SHARE/struct.subr f_include $BSDCFG_SHARE/variable.subr BSDCFG_LIBE="/usr/libexec/bsdconfig" f_include_lang $BSDCFG_LIBE/include/messages.subr TCP_HELPFILE=$BSDCFG_LIBE/include/tcp.hlp NETWORK_DEVICE_HELPFILE=$BSDCFG_LIBE/include/network_device.hlp ############################################################ GLOBALS # # Path to resolv.conf(5). # : ${RESOLV_CONF:="/etc/resolv.conf"} # # Path to nsswitch.conf(5). # : ${NSSWITCH_CONF:="/etc/nsswitch.conf"} # # Path to hosts(5) # : ${ETC_HOSTS:="/etc/hosts"} # # Structure of dhclient.leases(5) lease { ... } entry # f_struct_define DHCP_LEASE \ interface \ fixed_address \ filename \ server_name \ script \ medium \ host_name \ subnet_mask \ routers \ domain_name_servers \ domain_name \ broadcast_address \ dhcp_lease_time \ dhcp_message_type \ dhcp_server_identifier \ dhcp_renewal_time \ dhcp_rebinding_time \ renew \ rebind \ expire ############################################################ FUNCTIONS # f_validate_hostname $hostname # # Returns zero if the given argument (a fully-qualified hostname) is compliant # with standards set-forth in RFC's 952 and 1123 of the Network Working Group: # # RFC 952 - DoD Internet host table specification # http://tools.ietf.org/html/rfc952 # # RFC 1123 - Requirements for Internet Hosts - Application and Support # http://tools.ietf.org/html/rfc1123 # # See http://en.wikipedia.org/wiki/Hostname for a brief overview. # # The return status for invalid hostnames is one of: # 255 Entire hostname exceeds the maximum length of 255 characters. # 63 One or more individual labels within the hostname (separated by # dots) exceeds the maximum of 63 characters. # 1 One or more individual labels within the hostname contains one # or more invalid characters. # 2 One or more individual labels within the hostname starts or # ends with a hyphen (hyphens are allowed, but a label cannot # begin or end with a hyphen). # 3 One or more individual labels within the hostname are null. # # To call this function and display an appropriate error message to the user # based on the above error codes, use the following function defined in # dialog.subr: # # f_dialog_validate_hostname $hostname # f_validate_hostname() { local fqhn="$1" # Return error if the hostname exceeds 255 characters [ ${#fqhn} -gt 255 ] && return 255 local IFS="." # Split on `dot' for label in $fqhn; do # Return error if the label exceeds 63 characters [ ${#label} -gt 63 ] && return 63 # Return error if the label is null [ "$label" ] || return 3 # Return error if label begins/ends with dash case "$label" in -*|*-) return 2; esac # Return error if the label contains any invalid chars case "$label" in *[!0-9a-zA-Z-]*) return 1; esac done return $SUCCESS } # f_inet_atoi $ipv4_address [$var_to_set] # # Convert an IPv4 address or mask from dotted-quad notation (e.g., `127.0.0.1' # or `255.255.255.0') to a 32-bit unsigned integer for the purpose of network # and broadcast calculations. For example, one can validate that two addresses # are on the same network: # # f_inet_atoi 1.2.3.4 ip1num # f_inet_atoi 1.2.4.5 ip2num # f_inet_atoi 255.255.0.0 masknum # if [ $(( $ip1num & $masknum )) -eq \ # $(( $ip2num & $masknum )) ] # then # : IP addresses are on same network # fi # # See f_validate_ipaddr() below for an additional example usage, on calculating # network and broadcast addresses. # # If $var_to_set is missing or NULL, the converted IP address 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_inet_atoi() { local __addr="$1" __var_to_set="$2" __num=0 if f_validate_ipaddr "$__addr"; then local IFS=. set -- $__addr __num=$(( ($1 << 24) + ($2 << 16) + ($3 << 8) + $4 )) fi if [ "$__var_to_set" ]; then setvar "$__var_to_set" $__num else echo $__num fi } # f_validate_ipaddr $ipaddr [$netmask] # # Returns zero if the given argument (an IP address) is of the proper format. # # The return status for invalid IP address is one of: # 1 One or more individual octets within the IP address (separated # by dots) contains one or more invalid characters. # 2 One or more individual octets within the IP address are null # and/or missing. # 3 One or more individual octets within the IP address exceeds the # maximum of 255 (or 2^8, being an octet comprised of 8 bits). # 4 The IP address has either too few or too many octets. # # If a netmask is provided, the IP address is checked further: # # 5 The IP address must not be the network or broadcast address. # f_validate_ipaddr() { local ip="$1" mask="$2" # Track number of octets for error checking local noctets=0 local oldIFS="$IFS" IFS="." # Split on `dot' for octet in $ip; do # Return error if the octet is null [ "$octet" ] || return 2 # Return error if not a whole integer f_isinteger "$octet" || return 1 # Return error if not a positive integer [ $octet -ge 0 ] || return 1 # Return error if the octet exceeds 255 [ $octet -gt 255 ] && return 3 noctets=$(( $noctets + 1 )) done IFS="$oldIFS" [ $noctets -eq 4 ] || return 4 # # The IP address must not be network or broadcast address. # if [ "$mask" ]; then local ipnum masknum netnum bcastnum local max_addr=4294967295 # 255.255.255.255 f_inet_atoi $ip ipnum f_inet_atoi $mask masknum netnum=$(( $ipnum & $masknum )) bcastnum=$(( ($ipnum & $masknum)+$max_addr-$masknum )) if [ "$masknum" ] && [ $ipnum -eq $netnum -o $ipnum -eq $bcastnum ] then return 5 fi fi return $SUCCESS } # f_validate_ipaddr6 $ipv6_addr # # Returns zero if the given argument (an IPv6 address) is of the proper format. # # The return status for invalid IP address is one of: # 1 One or more individual segments within the IP address # (separated by colons) contains one or more invalid characters. # Segments must contain only combinations of the characters 0-9, # A-F, or a-f. # 2 Too many/incorrect null segments. A single null segment is # allowed within the IP address (separated by colons) but not # allowed at the beginning or end (unless a double-null segment; # i.e., "::*" or "*::"). # 3 One or more individual segments within the IP address # (separated by colons) exceeds the length of 4 hex-digits. # 4 The IP address entered has either too few (less than 3), too # many (more than 8), or not enough segments, separated by # colons. # 5* The IPv4 address at the end of the IPv6 address is invalid. # * When there is an error with the dotted-quad IPv4 address at the # end of the IPv6 address, the return value of 5 is OR'd with a # bit-shifted (<< 4) return of f_validate_ipaddr. # f_validate_ipaddr6() { local ip="${1%\%*}" # removing the interface specification if-present local IFS=":" # Split on `colon' set -- $ip: # Return error if too many or too few segments # Using 9 as max in case of leading or trailing null spanner [ $# -gt 9 -o $# -lt 3 ] && return 4 local h="[0-9A-Fa-f]" local nulls=0 nsegments=$# contains_ipv4_segment= while [ $# -gt 0 ]; do segment="${1%:}" shift # # Return error if this segment makes one null too-many. A # single null segment is allowed anywhere in the middle as well # as double null segments are allowed at the beginning or end # (but not both). # if [ ! "$segment" ]; then nulls=$(( $nulls + 1 )) if [ $nulls -eq 3 ]; then # Only valid syntax for 3 nulls is `::' [ "$ip" = "::" ] || return 2 elif [ $nulls -eq 2 ]; then # Only valid if begins/ends with `::' case "$ip" in ::*|*::) : fall thru ;; *) return 2 esac fi continue fi # # Return error if not a valid hexadecimal short # case "$segment" in $h|$h$h|$h$h$h|$h$h$h$h) : valid segment of 1-4 hexadecimal digits ;; *[!0-9A-Fa-f]*) # Segment contains at least one invalid char # Return error immediately if not last segment [ $# -eq 0 ] || return 1 # Otherwise, check for legacy IPv4 notation case "$segment" in *[!0-9.]*) # Segment contains at least one invalid # character even for an IPv4 address return 1 esac # Return error if not enough segments if [ $nulls -eq 0 ]; then [ $nsegments -eq 7 ] || return 4 fi contains_ipv4_segment=1 # Validate the IPv4 address f_validate_ipaddr "$segment" || return $(( 5 | $? << 4 )) ;; *) # Segment characters are all valid but too many return 3 esac done if [ $nulls -eq 1 ]; then # Single null segment cannot be at beginning/end case "$ip" in :*|*:) return 2 esac fi # # A legacy IPv4 address can span the last two 16-bit segments, # reducing the amount of maximum allowable segments by-one. # maxsegments=8 if [ "$contains_ipv4_segment" ]; then maxsegments=7 fi case $nulls in # Return error if missing segments with no null spanner 0) [ $nsegments -eq $maxsegments ] || return 4 ;; # Return error if null spanner with too many segments 1) [ $nsegments -le $maxsegments ] || return 4 ;; # Return error if leading/trailing `::' with too many segments 2) [ $nsegments -le $(( $maxsegments + 1 )) ] || return 4 ;; esac return $SUCCESS } # f_validate_netmask $netmask # # Returns zero if the given argument (a subnet mask) is of the proper format. # # The return status for invalid netmask is one of: # 1 One or more individual fields within the subnet mask (separated # by dots) contains one or more invalid characters. # 2 One or more individual fields within the subnet mask are null # and/or missing. # 3 One or more individual fields within the subnet mask exceeds # the maximum of 255 (a full 8-bit register). # 4 The subnet mask has either too few or too many fields. # 5 One or more individual fields within the subnet mask is an # invalid integer (only 0,128,192,224,240,248,252,254,255 are # valid integers). # f_validate_netmask() { local mask="$1" # Track number of fields for error checking local nfields=0 local IFS="." # Split on `dot' for field in $mask; do # Return error if the field is null [ "$field" ] || return 2 # Return error if not a whole positive integer f_isinteger "$field" || return 1 # Return error if the field exceeds 255 [ $field -gt 255 ] && return 3 # Return error if the field is an invalid integer case "$field" in 0|128|192|224|240|248|252|254|255) : ;; *) return 5 ;; esac nfields=$(( $nfields + 1 )) done [ $nfields -eq 4 ] || return 4 } # f_validate_gateway $gateway $ipaddr $netmask # # Validate an IPv4 default gateway (aka router) address for a given IP address # making sure the two are in the same network (able to ``talk'' to each other). # Returns success if $ipaddr and $gateway are in the same network given subnet # mask $netmask. # f_validate_gateway() { local gateway="$1" ipaddr="$2" netmask="$3" local gwnum ipnum masknum f_validate_ipaddr "$gateway" "$netmask" || return $FAILURE f_inet_atoi "$netmask" masknum f_inet_atoi "$ipaddr" ipnum f_inet_atoi "$gateway" gwnum # Gateway must be within set of IPs reachable through interface [ $(( $ipnum & $masknum )) -eq \ $(( $gwnum & $masknum )) ] # Return status } # f_dialog_validate_tcpip $hostname $gateway $nameserver $ipaddr $netmask # # Returns success if the arguments provided are valid for accessing a TCP/IP # network, otherwise returns failure. # f_dialog_validate_tcpip() { local hostname="$1" gateway="$2" nameserver="$3" local ipaddr="$4" netmask="$5" local ipnum masknum if [ ! "$hostname" ]; then f_show_msg "$msg_must_specify_a_host_name_of_some_sort" elif ! f_validate_hostname "$hostname"; then f_show_msg "$msg_invalid_hostname_value" elif [ "$netmask" ] && ! f_validate_netmask "$netmask"; then f_show_msg "$msg_invalid_netmask_value" elif [ "$nameserver" ] && ! f_validate_ipaddr "$nameserver" && ! f_validate_ipaddr6 "$nameserver"; then f_show_msg "$msg_invalid_name_server_ip_address_specified" elif [ "$ipaddr" ] && ! f_validate_ipaddr "$ipaddr" "$netmask"; then f_show_msg "$msg_invalid_ipv4_address" elif [ "$gateway" -a "$gateway" != "NO" ] && ! f_validate_gateway "$gateway" "$ipaddr" "$netmask"; then f_show_msg "$msg_invalid_gateway_ipv4_address_specified" else return $DIALOG_OK fi return $DIALOG_CANCEL } # f_ifconfig_inet $interface [$var_to_set] # # Returns the IPv4 address associated with $interface. If $var_to_set is # missing or NULL, the IP address 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). # # 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_ifconfig_inet_awk=' BEGIN { found = 0 } ( $1 == "inet" ) \ { print $2 found = 1 exit } END { exit ! found } ' f_ifconfig_inet() { local __interface="$1" __var_to_set="$2" if [ "$__var_to_set" ]; then local __ip __ip=$( ifconfig "$__interface" 2> /dev/null | awk "$f_ifconfig_inet_awk" ) setvar "$__var_to_set" "$__ip" else ifconfig "$__interface" 2> /dev/null | awk "$f_ifconfig_inet_awk" fi } # f_ifconfig_inet6 $interface [$var_to_set] # # Returns the IPv6 address associated with $interface. If $var_to_set is # missing or NULL, the IP address 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). # # 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_ifconfig_inet6_awk=' BEGIN { found = 0 } ( $1 == "inet6" ) \ { print $2 found = 1 exit } END { exit ! found } ' f_ifconfig_inet6() { local __interface="$1" __var_to_set="$2" if [ "$__var_to_set" ]; then local __ip6 __ip6=$( ifconfig "$__interface" 2> /dev/null | awk "$f_ifconfig_inet6_awk" ) setvar "$__var_to_set" "$__ip6" else ifconfig "$__interface" 2> /dev/null | awk "$f_ifconfig_inet6_awk" fi } # f_ifconfig_netmask $interface [$var_to_set] # # Returns the IPv4 subnet mask associated with $interface. If $var_to_set is # missing or NULL, the netmask 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_ifconfig_netmask() { local __interface="$1" __var_to_set="$2" __octets __octets=$( ifconfig "$__interface" 2> /dev/null | awk \ ' BEGIN { found = 0 } ( $1 == "inet" ) \ { printf "%s %s %s %s\n", substr($4,3,2), substr($4,5,2), substr($4,7,2), substr($4,9,2) found = 1 exit } END { exit ! found } ' ) || return $FAILURE local __octet __netmask= for __octet in $__octets; do f_sprintf __netmask "%s.%u" "$__netmask" "0x$__octet" done __netmask="${__netmask#.}" if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__netmask" else echo $__netmask fi } # f_route_get_default [$var_to_set] # # Returns the IP address of the currently active default router. If $var_to_set # is missing or NULL, the IP address 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). # # 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_route_get_default_awk=' BEGIN { found = 0 } ( $1 == "gateway:" ) \ { print $2 found = 1 exit } END { exit ! found } ' f_route_get_default() { local __var_to_set="$1" if [ "$__var_to_set" ]; then local __ip __ip=$( route -n get default 2> /dev/null | awk "$f_route_get_default_awk" ) setvar "$__var_to_set" "$__ip" else route -n get default 2> /dev/null | awk "$f_route_get_default_awk" fi } # f_resolv_conf_nameservers [$var_to_set] # # Returns nameserver(s) configured in resolv.conf(5). If $var_to_set is missing # or NULL, the list of nameservers 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). # # 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_resolv_conf_nameservers_awk=' BEGIN { found = 0 } ( $1 == "nameserver" ) \ { print $2 found = 1 } END { exit ! found } ' f_resolv_conf_nameservers() { local __var_to_set="$1" if [ "$__var_to_set" ]; then local __ns __ns=$( awk "$f_resolv_conf_nameservers_awk" "$RESOLV_CONF" \ 2> /dev/null ) setvar "$__var_to_set" "$__ns" else awk "$f_resolv_conf_nameservers_awk" "$RESOLV_CONF" \ 2> /dev/null fi } # f_config_resolv # # Attempts to configure resolv.conf(5) and ilk. Returns success if able to # write the file(s), otherwise returns error status. # # Variables from variable.subr that are used in configuring resolv.conf(5) are # as follows (all of which can be configured automatically through functions # like f_dhcp_get_info() or manually): # # VAR_NAMESERVER # The nameserver to add in resolv.conf(5). # VAR_DOMAINNAME # The domain to configure in resolv.conf(5). Also used in the # configuration of hosts(5). # VAR_IPADDR # The IPv4 address to configure in hosts(5). # VAR_IPV6ADDR # The IPv6 address to configure in hosts(5). # VAR_HOSTNAME # The hostname to associate with the IPv4 and/or IPv6 address in # hosts(5). # f_config_resolv() { local cp c6p dp hp f_getvar $VAR_NAMESERVER cp if [ "$cp" ]; then case "$RESOLV_CONF" in */*) f_quietly mkdir -p "${RESOLV_CONF%/*}" ;; esac # Attempt to create/truncate the file ( :> "$RESOLV_CONF" ) 2> /dev/null || return $FAILURE f_getvar $VAR_DOMAINNAME dp && printf "domain\t%s\n" "$dp" >> "$RESOLV_CONF" printf "nameserver\t%s\n" "$cp" >> "$RESOLV_CONF" f_dprintf "Wrote out %s" "$RESOLV_CONF" fi f_getvar $VAR_DOMAINNAME dp f_getvar $VAR_IPADDR cp f_getvar $VAR_IPV6ADDR c6p f_getvar $VAR_HOSTNAME hp # Attempt to create the file if it doesn't already exist if [ ! -e "$ETC_HOSTS" ]; then case "$ETC_HOSTS" in */*) f_quietly mkdir -p "${ETC_HOSTS%/*}" ;; esac ( :> "$ETC_HOSTS" ) 2> /dev/null || return $FAILURE fi # Scan the file and add ourselves if not already configured awk -v dn="$dp" -v ip4="$cp" -v ip6="$c6p" -v hn="$hp" ' BEGIN { local4found = local6found = 0 hn4found = hn6found = h4found = h6found = 0 h = ( match(hn, /\./) ? substr(hn, 0, RSTART-1) : "" ) } ($1 == "127.0.0.1") { local4found = 1 } ($1 == "::1") { local6found = 1 } { for (n = 2; n <= NF; n++) { if ( $1 == ip4 ) { if ( $n == h ) h4found = 1 if ( $n == hn ) hn4found = 1 if ( $n == hn "." ) hn4found = 1 } if ( $1 == ip6 ) { if ( $n == h ) h6found = 1 if ( $n == hn ) hn6found = 1 if ( $n == hn "." ) hn6found = 1 } } } END { hosts = FILENAME if ( ! local6found ) printf "::1\t\t\tlocalhost%s\n", ( dn ? " localhost." dn : "" ) >> hosts if ( ! local4found ) printf "127.0.0.1\t\tlocalhost%s\n", ( dn ? " localhost." dn : "" ) >> hosts if ( ip6 && ! (h6found && hn6found)) { printf "%s\t%s %s\n", ip6, hn, h >> hosts printf "%s\t%s.\n", ip6, hn >> hosts } else if ( ip6 ) { if ( ! h6found ) printf "%s\t%s.\n", ip6, h >> hosts if ( ! hn6found ) printf "%s\t%s\n", ip6, hn >> hosts } if ( ip4 && ! (h4found && hn4found)) { printf "%s\t\t%s %s\n", ip4, hn, h >> hosts printf "%s\t\t%s.\n", ip4, hn >> hosts } else if ( ip4 ) { if ( ! h4found ) printf "%s\t\t%s.\n", ip4, h >> hosts if ( ! hn4found ) printf "%s\t\t%s\n", ip4, hn >> hosts } } ' "$ETC_HOSTS" 2> /dev/null || return $FAILURE f_dprintf "Wrote out %s" "$ETC_HOSTS" return $SUCCESS } # f_dhcp_parse_leases $leasefile struct_name # # Parse $leasefile and store the information for the most recent lease in a # struct (see struct.subr for additional details) named `struct_name'. See # DHCP_LEASE struct definition in the GLOBALS section above. # f_dhcp_parse_leases() { local leasefile="$1" struct_name="$2" [ "$struct_name" ] || return $FAILURE if [ ! -e "$leasefile" ]; then f_dprintf "%s: No such file or directory" "$leasefile" return $FAILURE fi f_struct "$struct_name" && f_struct_free "$struct_name" f_struct_new DHCP_LEASE "$struct_name" eval "$( awk -v struct="$struct_name" ' BEGIN { lease_found = 0 keyword_list = " \ interface \ fixed-address \ filename \ server-name \ script \ medium \ " split(keyword_list, keywords, FS) time_list = "renew rebind expire" split(time_list, times, FS) option_list = " \ host-name \ subnet-mask \ routers \ domain-name-servers \ domain-name \ broadcast-address \ dhcp-lease-time \ dhcp-message-type \ dhcp-server-identifier \ dhcp-renewal-time \ dhcp-rebinding-time \ " split(option_list, options, FS) } function set_value(prop,value) { lease_found = 1 gsub(/[^[:alnum:]_]/, "_", prop) sub(/;$/, "", value) sub(/^"/, "", value) sub(/"$/, "", value) sub(/,.*/, "", value) printf "%s set %s \"%s\"\n", struct, prop, value } /^lease {$/, /^}$/ \ { if ( $0 ~ /^lease {$/ ) next if ( $0 ~ /^}$/ ) exit for (k in keywords) { keyword = keywords[k] if ( $1 == keyword ) { set_value(keyword, $2) next } } for (t in times) { time = times[t] if ( $1 == time ) { set_value(time, $2 " " $3 " " $4) next } } if ( $1 != "option" ) next for (o in options) { option = options[o] if ( $2 == option ) { set_value(option, $3) next } } } EXIT { if ( ! lease_found ) { printf "f_struct_free \"%s\"\n", struct print "return $FAILURE" } } ' "$leasefile" )" } # f_dhcp_get_info $interface # # Parse the dhclient(8) lease database for $interface to obtain all the # necessary IPv4 details necessary to communicate on the network. The retrieved # information is stored in VAR_IPADDR, VAR_NETMASK, VAR_GATEWAY, and # VAR_NAMESERVER. # # If reading the lease database fails, values are obtained from ifconfig(8) and # route(8). If the DHCP lease did not provide a nameserver (or likewise, we # were unable to parse the lease database), fall-back to resolv.conf(5) for # obtaining the nameserver. Always returns success. # f_dhcp_get_info() { local interface="$1" cp local leasefile="/var/db/dhclient.leases.$interface" # If it fails, do it the old-fashioned way if f_dhcp_parse_leases "$leasefile" lease; then lease get fixed_address $VAR_IPADDR lease get subnet_mask $VAR_NETMASK lease get routers cp setvar $VAR_GATEWAY "${cp%%,*}" lease get domain_name_servers cp setvar $VAR_NAMESERVER "${cp%%,*}" lease get host_name cp && setvar $VAR_HOSTNAME "$cp" f_struct_free lease else # Bah, now we have to get the information from ifconfig if f_debugging; then f_dprintf "DHCP configured interface returns %s" \ "$( ifconfig "$interface" )" fi f_ifconfig_inet "$interface" $VAR_IPADDR f_ifconfig_netmask "$interface" $VAR_NETMASK f_route_get_default $VAR_GATEWAY fi # If we didn't get a name server value, hunt for it in resolv.conf local ns if [ -r "$RESOLV_CONF" ] && ! { f_getvar $VAR_NAMESERVER ns || [ "$ns" ] }; then f_resolv_conf_nameservers cp && setvar $VAR_NAMESERVER ${cp%%[$IFS]*} fi return $SUCCESS } # f_rtsol_get_info $interface # # Returns the rtsol-provided IPv6 address associated with $interface. The # retrieved IP address is stored in VAR_IPV6ADDR. Always returns success. # f_rtsol_get_info() { local interface="$1" cp cp=$( ifconfig "$interface" 2> /dev/null | awk \ ' BEGIN { found = 0 } ( $1 == "inet6" ) && ( $2 ~ /^fe80:/ ) \ { print $2 found = 1 exit } END { exit ! found } ' ) && setvar $VAR_IPV6ADDR "$cp" } # f_host_lookup $host [$var_to_set] # # Use host(1) to lookup (or reverse) an Internet number from (or to) a name. # Multiple answers are returned separated by a single space. If host(1) does # not exit cleanly, its full output is provided and the return status is 1. # # If nsswitch.conf(5) has been configured to query local access first for the # `hosts' database, we'll manually check hosts(5) first (preventing host(1) # from hanging in the event that DNS goes awry). # # If $var_to_set is missing or NULL, the list of IP addresses 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). # # The variables from variable.subr used in looking up the host are as follows # (which are set manually): # # VAR_IPV6_ENABLE [Optional] # If set to "YES", enables the lookup of IPv6 addresses and IPv4 # address. IPv6 addresses, if any, will come before IPv4. Note # that if nsswitch.conf(5) shows an affinity for "files" for the # "host" database and there is a valid entry in hosts(5) for # $host, this setting currently has no effect (an IPv4 address # can supersede an IPv6 address). By design, hosts(5) overrides # any preferential treatment. Otherwise, if this variable is not # set, IPv6 addresses will not be used (IPv4 addresses will # specifically be requested from DNS). # # 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_host_lookup_awk=' BEGIN{ addrs = "" } !/^[[:space:]]*(#|$)/ \ { for (n=1; n++ < NF;) if ($n == name) addrs = addrs (addrs ? " " : "") $1 } END { if (addrs) print addrs exit !addrs } ' f_host_lookup() { local __host="$1" __var_to_set="$2" f_dprintf "f_host_lookup: host=[%s]" "$__host" # If we're configured to look at local files first, do that if awk '/^hosts:/{exit !($2=="files")}' "$NSSWITCH_CONF"; then if [ "$__var_to_set" ]; then local __cp if __cp=$( awk -v name="$__host" \ "$f_host_lookup_awk" "$ETC_HOSTS" ) then setvar "$__var_to_set" "$__cp" return $SUCCESS fi else awk -v name="$__host" \ "$f_host_lookup_awk" "$ETC_HOSTS" && return $SUCCESS fi fi # # Fall back to host(1) -- which is further governed by nsswitch.conf(5) # local __output __ip6 __addrs= f_getvar $VAR_IPV6_ENABLE __ip6 # If we have a TCP media type configured, check for an SRV record local __srvtypes= { f_quietly f_getvar $VAR_HTTP_PATH || f_quietly f_getvar $VAR_HTTP_PROXY_PATH } && __srvtypes="$__srvtypes _http._tcp" f_quietly f_getvar $VAR_FTP_PATH && __srvtypes="$__srvtypes _ftp._tcp" f_quietly f_getvar $VAR_NFS_PATH && __srvtypes="$__srvtypes _nfs._tcp _nfs._udp" # Calculate wait time as dividend of total time and host(1) invocations local __host_runs __wait f_count __host_runs $__srvtypes if [ "$__ip6" = "YES" ]; then __host_runs=$(( $__host_runs + 2 )) else __host_runs=$(( $__host_runs + 1 )) fi f_getvar $VAR_MEDIA_TIMEOUT __wait [ "$__wait" ] && __wait="-W $(( $__wait / $__host_runs ))" # Query SRV types first (1st host response taken as new host to query) for __type in $__srvtypes; do if __output=$( host -t SRV $__wait -- "$__type.$__host" \ 2> /dev/null ); then __host=$( echo "$__output" | awk '/ SRV /{print $NF;exit}' ) break fi done # Try IPv6 first (if enabled) if [ "$__ip6" = "YES" ]; then if ! __output=$( host -t AAAA $__wait -- "$__host" 2>&1 ); then # An error occurred, display in-full and return error [ "$__var_to_set" ] && setvar "$__var_to_set" "$__output" return $FAILURE fi # Add the IPv6 addresses and fall-through to collect IPv4 too __addrs=$( echo "$__output" | awk '/ address /{print $NF}' ) fi # Good ol' IPv4 if ! __output=$( host -t A $__wait -- "$__host" 2>&1 ); then # An error occurred, display it in-full and return error [ "$__var_to_set" ] && setvar "$__var_to_set" "$__output" return $FAILURE fi __addrs="$__addrs${__addrs:+ }$( echo "$__output" | awk '/ address /{print $NF}' )" if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__addrs" else echo $__addrs fi } # f_device_dialog_tcp $device # # This is it - how to get TCP setup values. Prompt the user to edit/confirm the # interface, gateway, nameserver, and hostname settings -- all required for # general TCP/IP access. # # Variables from variable.subr that can be used to sript user input: # # VAR_NO_INET6 # If set, prevents asking the user if they would like to use # rtsol(8) to check for an IPv6 router. # VAR_TRY_RTSOL # If set to "YES" (and VAR_NONINTERACTIVE is unset), asks the # user if they would like to try the IPv6 RouTer SOLicitation # utility (rtsol(8)) to get IPv6 information. Ignored if # VAR_NO_INET6 is set. # VAR_TRY_DHCP # If set to "YES" (and VAR_NONINTERACTIVE is unset), asks the # user if they would like to try to acquire IPv4 connection # settings from a DHCP server using dhclient(8). # # VAR_GATEWAY Default gateway to use. # VAR_IPADDR Interface address to assign. # VAR_NETMASK Interface subnet mask. # VAR_EXTRAS Extra interface options to ifconfig(8). # VAR_HOSTNAME Hostname to set. # VAR_DOMAINNAME Domain name to use. # VAR_NAMESERVER DNS nameserver to use when making lookups. # VAR_IPV6ADDR IPv6 interface address. # # In addition, the following variables are used in acquiring network settings # from the user: # # VAR_NONINTERACTIVE # If set (such as when running in a script), prevents asking the # user questions or displaying the usual prompts, etc. # VAR_NETINTERACTIVE # The one exception to VAR_NONINTERACTIVE is VAR_NETINTERACTIVE, # which if set will prompt the user to try RTSOL (unless # VAR_TRY_RTSOL has been set), try DHCP (unless VAR_TRY_DHCP has # been set), and display the network verification dialog. This # allows you to have a mostly non-interactive script that still # prompts for network setup/confirmation. # -# After successfull execution, the following variables are set: +# After successful execution, the following variables are set: # # VAR_IFCONFIG + $device (e.g., `ifconfig_em0') # Defines the ifconfig(8) properties specific to $device. # f_device_dialog_tcp() { local dev="$1" devname cp n local use_dhcp="" use_rtsol="" local _ipaddr _netmask _extras [ "$dev" ] || return $DIALOG_CANCEL f_struct "$dev" get name devname || return $DIALOG_CANCEL # Initialize vars from previous device values local private $dev get private private if [ "$private" ] && f_struct "$private"; then $private get ipaddr _ipaddr $private get netmask _netmask $private get extras _extras $private get use_dhcp use_dhcp $private get use_rtsol use_rtsol else # See if there are any defaults # # This is a hack so that the dialogs below are interactive in a # script if we have requested interactive behavior. # local old_interactive= if ! f_interactive && f_netinteractive; then f_getvar $VAR_NONINTERACTIVE old_interactive unset $VAR_NONINTERACTIVE fi # # Try a RTSOL scan if such behavior is desired. # If the variable was configured and is YES, do it. # If it was configured to anything else, treat it as NO. # Otherwise, ask the question interactively. # local try6 if ! f_isset $VAR_NO_INET6 && { { f_getvar $VAR_TRY_RTSOL try6 && [ "$try6" = "YES" ]; } || { # Only prompt the user when VAR_TRY_RTSOL is unset ! f_isset $VAR_TRY_RTSOL && f_dialog_noyes "$msg_try_ipv6_configuration" } }; then local i f_quietly sysctl net.inet6.ip6.forwarding=0 f_quietly sysctl net.inet6.ip6.accept_rtadv=1 f_quietly ifconfig $devname up i=$( sysctl -n net.inet6.ip6.dad_count ) sleep $(( $i + 1 )) f_quietly mkdir -p /var/run f_dialog_info "$msg_scanning_for_ra_servers" if f_quietly rtsol $devname; then i=$( sysctl -n net.inet6.ip6.dad_count ) sleep $(( $i + 1 )) f_rtsol_get_info $devname use_rtsol=1 else use_rtsol= fi fi # # Try a DHCP scan if such behavior is desired. # If the variable was configured and is YES, do it. # If it was configured to anything else, treat it as NO. # Otherwise, ask the question interactively. # local try4 if { f_getvar $VAR_TRY_DHCP try4 && [ "$try4" = "YES" ]; } || { # Only prompt the user when VAR_TRY_DHCP is unset ! f_isset $VAR_TRY_DHCP && f_dialog_noyes "$msg_try_dhcp_configuration" }; then f_quietly ifconfig $devname delete f_quietly mkdir -p /var/db f_quietly mkdir -p /var/run f_quietly mkdir -p /tmp local msg="$msg_scanning_for_dhcp_servers" trap - SIGINT ( # Execute in sub-shell to allow/catch Ctrl-C trap 'exit $FAILURE' SIGINT if [ "$USE_XDIALOG" ]; then f_quietly dhclient $devname | f_xdialog_info "$msg" else f_dialog_info "$msg" f_quietly dhclient $devname fi ) local retval=$? trap 'f_interrupt' SIGINT if [ $retval -eq $SUCCESS ]; then f_dhcp_get_info $devname use_dhcp=1 else use_dhcp= fi fi # Restore old VAR_NONINTERACTIVE if needed. [ "$old_interactive" ] && setvar $VAR_NONINTERACTIVE "$old_interactive" # Special hack so it doesn't show up oddly in the menu local gw if f_getvar $VAR_GATEWAY gw && [ "$gw" = "NO" ]; then setvar $VAR_GATEWAY "" fi # Get old IP address from variable space, if available if [ ! "$_ipaddr" ]; then if f_getvar $VAR_IPADDR cp; then _ipaddr="$cp" elif f_getvar ${devname}_$VAR_IPADDR cp; then _ipaddr="$cp" fi fi # Get old netmask from variable space, if available if [ ! "$_netmask" ]; then if f_getvar $VAR_NETMASK cp; then _netmask="$cp" elif f_getvar ${devname}_$VAR_NETMASK cp; then _netmask="$cp" fi fi # Get old extras string from variable space, if available if [ ! "$_extras" ]; then if f_getvar $VAR_EXTRAS cp; then _extras="$cp" elif f_getvar ${devname}_$VAR_EXTRAS cp; then _extras="$cp" fi fi fi # Look up values already recorded with the system, or blank the string # variables ready to accept some new data local _hostname _gateway _nameserver f_getvar $VAR_HOSTNAME _hostname case "$_hostname" in *.*) : do nothing ;; # Already fully-qualified *) f_getvar $VAR_DOMAINNAME cp [ "$cp" ] && _hostname="$_hostname.$cp" esac f_getvar $VAR_GATEWAY _gateway f_getvar $VAR_NAMESERVER _nameserver # Re-check variables for initial inheritance before heading into dialog [ "$_hostname" ] || _hostname="${HOSTNAME:-$( hostname )}" [ "$_gateway" ] || f_route_get_default _gateway [ ! "$_nameserver" ] && f_resolv_conf_nameservers cp && _nameserver=${cp%%[$IFS]*} [ "$_ipaddr" ] || f_ifconfig_inet $devname _ipaddr [ "$_netmask" ] || f_ifconfig_netmask $devname _netmask # If non-interactive, jump over dialog section and into config section if f_netinteractive || f_interactive || [ ! "$_hostname" ] then [ ! "$_hostname" ] && f_interactive && f_show_msg "$msg_hostname_variable_not_set" local title=" $msg_network_configuration " local hline="$hline_alnum_arrows_punc_tab_enter" local extras_help="$tcplayout_extras_help" # Modify the help line for PLIP config [ "${devname#plip}" != "$devname" ] && extras_help="$tcplayout_extras_help_for_plip" f_getvar $VAR_IPV6ADDR cp && [ "$cp" ] && title="$title($msg_ipv6_ready) " if [ ! "$USE_XDIALOG" ]; then local prompt="$msg_dialog_mixedform_navigation_help" # Calculate center position for displaying device label local devlabel="$msg_configuration_for_interface" devlabel="$devlabel $devname" local width=54 local n=$(( $width/2 - (${#devlabel} + 4)/2 - 2 )) while :; do cp=$( $DIALOG \ --title "$title" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --item-help \ --ok-label "$msg_ok" \ --cancel-label "$msg_cancel" \ --help-button \ --help-label "$msg_help" \ --mixedform "$prompt" 16 $width 9 \ "$msg_host_name_including_domain:" 1 2 \ "$_hostname" 2 3 45 255 0 \ "$tcplayout_hostname_help" \ "$msg_ipv4_gateway:" 3 2 \ "$_gateway" 4 3 16 15 0 \ "$tcplayout_gateway_help" \ "$msg_name_server:" 3 31 \ "$_nameserver" 4 32 16 15 0 \ "$tcplayout_nameserver_help" \ "- $devlabel -" 5 $n "" 0 0 0 0 3 "" \ "$msg_ipv4_address:" 6 6 \ "$_ipaddr" 7 7 16 15 0 \ "$tcplayout_ipaddr_help" \ "$msg_netmask:" 6 31 \ "$_netmask" 7 32 16 15 0 \ "$tcplayout_netmask_help" \ "$msg_extra_options_to_ifconfig" 8 6 \ "$_extras" 9 7 41 2048 0 \ "$extras_help" \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) # --mixed-form always returns 0, we have to # use the returned data to determine button if [ ! "$cp" ]; then # User either chose "Cancel", pressed # ESC, or blanked every form field return $DIALOG_CANCEL else n=$( echo "$cp" | f_number_of_lines ) [ $n -eq 1 ] && case "$cp" in HELP*) # User chose "Help" f_show_help "$TCP_HELPFILE" continue esac fi # Turn mixed-form results into env variables eval "$( echo "$cp" | awk ' BEGIN { n = 0 field[++n] = "_hostname" field[++n] = "_gateway" field[++n] = "_nameserver" field[++n] = "_ipaddr" field[++n] = "_netmask" field[++n] = "_extras" nfields = n n = 0 } { gsub(/'\''/, "'\'\\\\\'\''") sub(/[[:space:]]*$/, "") value[field[++n]] = $0 } END { for ( n = 1; n <= nfields; n++ ) { printf "%s='\''%s'\'';\n", field[n], value[field[n]] } }' )" f_dialog_validate_tcpip \ "$_hostname" \ "$_gateway" \ "$_nameserver" \ "$_ipaddr" \ "$_netmask" \ && break done else # Xdialog(1) does not support --mixed-form # Create a persistent menu instead f_dialog_title "$msg_network_configuration" local prompt= while :; do cp=$( $DIALOG \ --title "$DIALOG_TITLE" \ --backtitle "$DIALOG_BACKTITLE" \ --hline "$hline" \ --item-help \ --ok-label "$msg_ok" \ --cancel-label "$msg_cancel" \ --help "" \ --menu "$prompt" 21 60 8 \ "$msg_accept_continue" "" \ "$tcplayout_accept_cont_help" \ "$msg_host_name_including_domain:" \ "$_hostname" \ "$tcplayout_hostname_help" \ "$msg_ipv4_gateway:" "$_gateway" \ "$tcplayout_gateway_help" \ "$msg_name_server:" "$_nameserver" \ "$tcplayout_nameserver_help" \ "$msg_ipv4_address:" "$_ipaddr" \ "$tcplayout_ipaddr_help" \ "$msg_netmask:" "$_netmask" \ "$tcplayout_netmask_help" \ "$msg_extra_options_to_ifconfig" \ "$_extras" "$extras_help" \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) local retval=$? f_dialog_data_sanitize cp f_dprintf "retval=%u mtag=[%s]" $retval "$cp" if [ $retval -eq $DIALOG_HELP ]; then f_show_help "$TCP_HELPFILE" continue elif [ $retval -ne $DIALOG_OK ]; then f_dialog_title_restore return $DIALOG_CANCEL fi case "$cp" in "$msg_accept_continue") f_dialog_validate_tcpip \ "$_hostname" \ "$_gateway" \ "$_nameserver" \ "$_ipaddr" \ "$_netmask" \ && break ;; "$msg_host_name_including_domain:") f_dialog_input cp "$cp" "$_hostname" \ && _hostname="$cp" ;; "$msg_ipv4_gateway:") f_dialog_input cp "$cp" "$_gateway" \ && _gateway="$cp" ;; "$msg_name_server:") f_dialog_input cp "$cp" "$_nameserver" \ && _nameserver="$cp" ;; "$msg_ipv4_address:") f_dialog_input cp "$cp" "$_ipaddr" \ && _ipaddr="$cp" ;; "$msg_netmask:") f_dialog_input cp "$cp" "$_netmask" \ && _netmask="$cp" ;; "$msg_extra_options_to_ifconfig") f_dialog_input cp "$cp" "$_extras" \ && _extras="$cp" ;; esac done f_dialog_title_restore fi # XDIALOG fi # interactive # We actually need to inform the rest of bsdconfig about this # data now if the user hasn't selected cancel. if [ "$_hostname" ]; then setvar $VAR_HOSTNAME "$_hostname" f_quietly hostname "$_hostname" case "$_hostname" in *.*) setvar $VAR_DOMAINNAME "${_hostname#*.}" ;; esac fi [ "$_gateway" ] && setvar $VAR_GATEWAY "$_gateway" [ "$_nameserver" ] && setvar $VAR_NAMESERVER "$_nameserver" [ "$_ipaddr" ] && setvar $VAR_IPADDR "$_ipaddr" [ "$_netmask" ] && setvar $VAR_NETMASK "$_netmask" [ "$_extras" ] && setvar $VAR_EXTRAS "$_extras" f_dprintf "Creating struct DEVICE_INFO devinfo_%s" "$dev" f_struct_new DEVICE_INFO devinfo_$dev $dev set private devinfo_$dev devinfo_$dev set ipaddr $_ipaddr devinfo_$dev set netmask $_netmask devinfo_$dev set extras $_extras devinfo_$dev set use_rtsol $use_rtsol devinfo_$dev set use_dhcp $use_dhcp if [ "$use_dhcp" -o "$_ipaddr" ]; then if [ "$use_dhcp" ]; then cp="DHCP${extras:+ $extras}" else cp="inet $_ipaddr netmask $_netmask${extras:+ $extras}" fi setvar $VAR_IFCONFIG$devname "$cp" fi [ "$use_rtsol" ] && setvar $VAR_IPV6_ENABLE "YES" [ "$use_dhcp" ] || f_config_resolv # XXX this will do it on the MFS copy return $DIALOG_OK } # f_device_scan_tcp [$var_to_set] # # Scan for the first active/configured TCP/IP device. The name of the interface # is printed to stderr like other dialog(1)-based functions (stdout is reserved # for dialog(1) interaction) if $var_to_set is missing or NULL. Returns failure # if no active/configured interface # f_device_scan_tcp() { local __var_to_set="$1" __iface for __iface in $( ifconfig -l ); do if ifconfig $__iface | awk ' BEGIN { has_inet = has_inet6 = is_ethernet = 0 is_usable = 1 } ( $1 == "status:" && $2 != "active" ) { is_usable = 0; exit } ( $1 == "inet" ) { if ($2 == "0.0.0.0") { is_usable = 0; exit } has_inet++ } ( $1 == "inet6") { has_inet6++ } ( $1 == "media:" ) { if ($2 != "Ethernet") { is_usable = 0; exit } is_ethernet = 1 } END { if (!(is_ethernet && (has_inet || has_inet6))) is_usable = 0 exit ! is_usable }'; then f_interactive && f_show_msg "$msg_using_interface" "$__iface" f_dprintf "f_device_scan_tcp found %s" "$__iface" if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__iface" else echo "$__iface" >&2 fi return $SUCCESS fi done return $FAILURE } # f_device_select_tcp # # Prompt the user to select network interface to use for TCP/IP access. # Variables from variable.subr that can be used to script user input: # # VAR_NETWORK_DEVICE [Optional] # Either a comma-separated list of network interfaces to try when # setting up network access (e.g., "fxp0,em0") or "ANY" (case- # sensitive) to indicate that the first active and configured # interface is acceptable. If unset, the user is presented with a # menu of all available network interfaces. # # Returns success if a valid network interface has been selected. # f_device_select_tcp() { local devs dev cnt if network_dev f_getvar $VAR_NETWORK_DEVICE network_dev f_dprintf "f_device_select_tcp: %s=[%s]" \ VAR_NETWORK_DEVICE "$network_dev" if [ "$network_dev" ]; then # # This can be set to several types of values. If set to ANY, # scan all network devices looking for a valid link, and go # with the first device found. Can also be specified as a # comma delimited list, with each network device tried in # order. Can also be set to a single network device. # [ "$network_dev" = "ANY" ] && f_device_scan_tcp network_dev while [ "$network_dev" ]; do case "$network_dev" in *,*) if="${network_dev%%,*}" network_dev="${network_dev#*,}" ;; *) if="$network_dev" network_dev= esac f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev f_device_dialog_tcp $dev if [ $? -eq $DIALOG_OK ]; then setvar $VAR_NETWORK_DEVICE $if return $DIALOG_OK fi done f_interactive && f_show_msg "$msg_no_network_devices" return $DIALOG_CANCEL fi # $network_dev f_device_find "" $DEVICE_TYPE_NETWORK devs f_count cnt $devs dev="${devs%%[$IFS]*}" $dev get name if f_quietly f_getvar NETWORK_CONFIGURED # for debugging info if ! f_running_as_init && ! [ "${NETWORK_CONFIGURED+set}" -a "$NETWORK_CONFIGURED" = "NO" ] then trap 'f_interrupt' SIGINT if f_dialog_yesno "$msg_assume_network_is_already_configured" then setvar $VAR_NETWORK_DEVICE $if return $DIALOG_OK fi fi local retval=$SUCCESS if [ ${cnt:=0} -eq 0 ]; then f_show_msg "$msg_no_network_devices" retval=$DIALOG_CANCEL elif [ $cnt -eq 1 ]; then f_device_dialog_tcp $dev retval=$? [ $retval -eq $DIALOG_OK ] && setvar $VAR_NETWORK_DEVICE $if else local title="$msg_network_interface_information_required" local prompt="$msg_please_select_ethernet_device_to_configure" local hline="$hline_arrows_tab_enter" dev=$( f_device_menu \ "$title" "$prompt" "$hline" $DEVICE_TYPE_NETWORK \ "$NETWORK_DEVICE_HELPFILE" \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || return $DIALOG_CANCEL f_device_dialog_tcp $dev retval=$? if [ $retval -eq $DIALOG_OK ]; then f_struct_copy "$dev" device_network setvar $VAR_NETWORK_DEVICE device_network else f_struct_free device_network fi fi return $retval } # f_dialog_menu_select_tcp # # Like f_dialog_select_tcp() above, but do it from a menu that doesn't care # about status. In other words, where f_dialog_select_tcp() will not display a # menu if scripted, this function will always display the menu of available # network interfaces. # f_dialog_menu_select_tcp() { local private use_dhcp name NETWORK_CONFIGURED=NO f_device_select_tcp if f_struct device_network && device_network get private private && f_struct_copy "$private" di && di get use_dhcp use_dhcp && [ ! "$use_dhcp" ] && device_network get name name && f_yesno "$msg_would_you_like_to_bring_interface_up" "$name" then if ! f_device_init device_network; then f_show_msg "$msg_initialization_of_device_failed" \ "$name" fi fi return $DIALOG_OK } ############################################################ MAIN f_dprintf "%s: Successfully loaded." media/tcpip.subr fi # ! $_MEDIA_TCPIP_SUBR Index: head/usr.sbin/bsdconfig/share/packages/index.subr =================================================================== --- head/usr.sbin/bsdconfig/share/packages/index.subr (revision 298883) +++ head/usr.sbin/bsdconfig/share/packages/index.subr (revision 298884) @@ -1,416 +1,416 @@ if [ ! "$_PACKAGES_INDEX_SUBR" ]; then _PACKAGES_INDEX_SUBR=1 # # Copyright (c) 2013-2016 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" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." packages/index.subr f_include $BSDCFG_SHARE/device.subr f_include $BSDCFG_SHARE/media/common.subr f_include $BSDCFG_SHARE/packages/musthavepkg.subr f_include $BSDCFG_SHARE/strings.subr BSDCFG_LIBE="/usr/libexec/bsdconfig" f_include_lang $BSDCFG_LIBE/include/messages.subr ############################################################ GLOBALS PACKAGE_INDEX= _INDEX_INITTED= # # Default path to pkg(8) repo-packagesite.sqlite database # SQLITE_REPO="/var/db/pkg/repo-FreeBSD.sqlite" # # Default path to on-disk cache INDEX file # PACKAGES_INDEX_CACHEFILE="/var/run/bsdconfig/packages_INDEX.cache" ############################################################ FUNCTIONS # f_index_initialize [$var_to_set] # # Read and initialize the global index. Returns success unless media cannot be # initialized for any reason (e.g. user cancels media selection dialog or an # error occurs). The index is sorted before being loaded into $var_to_set. # # NOTE: The index is processed with f_index_read() [below] after being loaded. # f_index_initialize() { local __funcname=f_index_initialize local __var_to_set="${1:-PACKAGE_INDEX}" [ "$_INDEX_INITTED" ] && return $SUCCESS # Got any media? f_media_verify || return $FAILURE # Make sure we have a usable pkg(8) with $PKG_ABI f_musthavepkg_init # Does it move when you kick it? f_device_init device_media || return $FAILURE f_show_info "$msg_attempting_to_update_repository_catalogue" # # Generate $PACKAGESITE variable for pkg(8) based on media type # local __type __data __site device_media get type __type device_media get private __data case "$__type" in $DEVICE_TYPE_DIRECTORY) __site="file://$__data/packages/$PKG_ABI" ;; $DEVICE_TYPE_FLOPPY) __site="file://${__data:-$MOUNTPOINT}/packages/$PKG_ABI" ;; $DEVICE_TYPE_FTP) f_getvar $VAR_FTP_PATH __site __site="$__site/packages/$PKG_ABI" ;; $DEVICE_TYPE_HTTP) f_getvar $VAR_HTTP_PATH __site __site="$__site/$PKG_ABI/latest" ;; $DEVICE_TYPE_HTTP_PROXY) f_getvar $VAR_HTTP_PROXY_PATH __site __site="$__site/packages/$PKG_ABI" ;; $DEVICE_TYPE_CDROM) __site="file://$MOUNTPOINT/packages/$PKG_ABI" export REPOS_DIR="$MOUNTPOINT/packages/repos" ;; *) # UFS, DISK, CDROM, USB, DOS, NFS, etc. __site="file://$MOUNTPOINT/packages/$PKG_ABI" esac f_dprintf "PACKAGESITE=[%s]" "$__site" if ! f_eval_catch $__funcname pkg \ 'PACKAGESITE="%s" pkg update' "$__site" then f_show_err "$msg_unable_to_update_pkg_from_selected_media" f_device_shutdown device_media return $FAILURE fi # # Try to get contents from validated on-disk cache # # - # Calculate digest used to determine if the on-disk persistant cache + # Calculate digest used to determine if the on-disk persistent cache # INDEX (containing this digest on the first line) is valid and can be # used to quickly populate the environment. # local __sqlite_digest if ! __sqlite_digest=$( md5 < "$SQLITE_REPO" 2> /dev/null ); then f_show_err "$msg_no_pkg_database_found" f_device_shutdown device_media return $FAILURE fi # - # Check to see if the persistant cache INDEX file exists + # Check to see if the persistent cache INDEX file exists # if [ -f "$PACKAGES_INDEX_CACHEFILE" ]; then # # Attempt to populate the environment with the (soon to be) # validated on-disk cache. If validation fails, fall-back to # generating a fresh cache. # if eval $__var_to_set='$( ( # Get digest as the first word on first line read digest rest_ignored # # If the stored digest matches the calculated- # one populate the environment from the on-disk # cache and provide success exit status. # if [ "$digest" = "$__sqlite_digest" ]; then cat exit $SUCCESS else # Otherwise, return the current value eval echo \"\$__var_to_set\" exit $FAILURE fi ) < "$PACKAGES_INDEX_CACHEFILE" 2> /dev/null )'; then if ! f_index_read "$__var_to_set"; then f_show_err \ "$msg_io_or_format_error_on_index_file" return $FAILURE fi _INDEX_INITTED=1 return $SUCCESS fi # Otherwise, fall-thru to create a fresh cache from scratch fi # # If we reach this point, we need to generate the data from scratch # eval "$__var_to_set"='$( pkg rquery -I | ( exec 2<&1; dpv -ko /dev/stderr >&$TERMINAL_STDOUT_PASSTHRU \ -b "$DIALOG_BACKTITLE" \ -- "$msg_generating_index_from_pkg_database" ) | sort )' # - # Attempt to create the persistant on-disk cache + # Attempt to create the persistent on-disk cache # # Create a new temporary file to write to local __tmpfile if f_eval_catch -dk __tmpfile $__funcname mktemp \ 'mktemp -t "%s"' "$pgm" then # Write the temporary file contents echo "$__sqlite_digest" > "$__tmpfile" debug= f_getvar "$__var_to_set" >> "$__tmpfile" # Finally, move the temporary file into place case "$PACKAGES_INDEX_CACHEFILE" in */*) f_eval_catch -d $__funcname mkdir \ 'mkdir -p "%s"' "${PACKAGES_INDEX_CACHEFILE%/*}" esac f_eval_catch -d $__funcname mv 'mv -f "%s" "%s"' \ "$__tmpfile" "$PACKAGES_INDEX_CACHEFILE" fi if ! f_index_read "$__var_to_set"; then f_show_err "$msg_io_or_format_error_on_index_file" return $FAILURE fi _INDEX_INITTED=1 return $SUCCESS } # f_index_read [$var_to_get] # # Process the INDEX file (contents contained in $var_to_get) and... # # 1. create a list ($CATEGORY_MENU_LIST) of categories with package counts # 2. For convenience, create $_npkgs holding the total number of all packages # 3. extract associative categories for each package into $_categories_$varpkg # 4. extract runtime dependencies for each package into $_rundeps_$varpkg # 5. extract a [sorted] list of categories into $PACKAGE_CATEGORIES # 6. create $_npkgs_$varcat holding the total number of packages in category # # NOTE: $varpkg is the product of f_str2varname $package varpkg # NOTE: $package is the name as it appears in the INDEX (no archive suffix) # NOTE: We only show categories for which there are at least one package. # NOTE: $varcat is the product of f_str2varname $category varcat # f_index_read() { local var_to_get="${1:-PACKAGE_INDEX}" # Export variables required by awk(1) below export msg_no_description_provided export msg_all msg_all_desc export VALID_VARNAME_CHARS export msg_packages eval "$( debug= f_getvar "$var_to_get" | awk -F'|' ' function _asorti(src, dest) { k = nitems = 0 # Copy src indices to dest and calculate array length for (i in src) dest[++nitems] = i # Sort the array of indices (dest) using insertion sort method for (i = 1; i <= nitems; k = i++) { idx = dest[i] while ((k > 0) && (dest[k] > idx)) { dest[k+1] = dest[k] k-- } dest[k+1] = idx } return nitems } function print_category(category, npkgs, desc) { cat = category # Accent the category if the first page has been # cached (also acting as a visitation indicator) if ( ENVIRON["_index_page_" varcat "_1"] ) cat = cat "*" printf "'\''%s'\'' '\''%s " packages "'\'' '\''%s'\''\n", cat, npkgs, desc } BEGIN { valid_chars = ENVIRON["VALID_VARNAME_CHARS"] default_desc = ENVIRON["msg_no_description_provided"] packages = ENVIRON["msg_packages"] tpkgs = 0 prefix = "" } { tpkgs++ varpkg = $1 gsub("[^" valid_chars "]", "_", varpkg) print "_categories_" varpkg "=\"" $7 "\"" split($7, pkg_categories, /[[:space:]]+/) for (pkg_category in pkg_categories) categories[pkg_categories[pkg_category]]++ print "_rundeps_" varpkg "=\"" $9 "\"" } END { print "_npkgs=" tpkgs # For convenience, total package count n = _asorti(categories, categories_sorted) # Produce package counts for each category for (i = 1; i <= n; i++) { cat = varcat = categories_sorted[i] npkgs = categories[cat] gsub("[^" valid_chars "]", "_", varcat) print "_npkgs_" varcat "=\"" npkgs "\"" } # Create menu list and generate list of categories at same time print "CATEGORY_MENU_LIST=\"" print_category(ENVIRON["msg_all"], tpkgs, ENVIRON["msg_all_desc"]) category_list = "" for (i = 1; i <= n; i++) { cat = varcat = categories_sorted[i] npkgs = categories[cat] cur_prefix = tolower(substr(cat, 1, 1)) if ( prefix != cur_prefix ) prefix = cur_prefix else cat = " " cat gsub("[^" valid_chars "]", "_", varcat) desc = ENVIRON["_category_" varcat] if ( ! desc ) desc = default_desc print_category(cat, npkgs, desc) category_list = category_list " " cat } print "\"" # Produce the list of categories (calculated in above block) sub(/^ /, "", category_list) print "PACKAGE_CATEGORIES=\"" category_list "\"" }' | ( exec 2<&1; dpv -ko /dev/stderr >&$TERMINAL_STDOUT_PASSTHRU \ -b "$DIALOG_BACKTITLE" -- "$msg_reading_package_index_data" ) )" # End-Quote } # f_index_extract_pages $var_to_get $var_basename $pagesize [$category] # # Extracts the package INDEX ($PACKAGE_INDEX by default if/when $var_to_get is # NULL; but should not be missing) into a series of sequential variables # corresponding to "pages" containing up to $pagesize packages. The package # INDEX data must be contained in the variable $var_to_get. The extracted pages # are stored in variables ${var_basename}_# -- where "#" is a the page number. # If $category is set, only packages for that category are extracted. # Otherwise, if $category is "All", missing, or NULL, all packages are # extracted and no filtering is done. # f_index_extract_pages() { local var_to_get="${1:-PACKAGE_INDEX}" var_basename="$2" pagesize="$3" local category="$4" # Optional eval "$( debug= f_getvar "$var_to_get" | awk -F'|' \ -v cat="$category" \ -v pagesize="$pagesize" \ -v var_basename="$var_basename" \ -v i18n_all="$msg_all" ' BEGIN { n = page = 0 } /'\''/{ gsub(/'\''/, "'\''\\'\'\''") } { if ( cat !~ "(^$|^" i18n_all "$)" && $7 !~ \ "(^|[[:space:]])" cat "([[:space:]]|$)" ) next starting_new_page = (n++ == (pagesize * page)) if ( starting_new_page ) printf "%s%s", ( n > 1 ? "'\''\n" : "" ), var_basename "_" ++page "='\''" printf "%s%s", ( starting_new_page ? "" : "\n" ), $0 } END { if ( n > 0 ) print "'\''" }' )" } # f_index_search $var_to_get $name [$var_to_set] # # Search the package INDEX ($PACKAGE_INDEX by default if/when $var_to_get is # NULL; but should not be missing) for $name, returning the first match. # Matches are strict (not regular expressions) and must match the beginning # portion of the package name to be considered a match. If $var_to_set is # missing or NULL, output is sent to standard output. If a match is found, # returns success; otherwise failure. # f_index_search() { local __var_to_get="${1:-PACKAGE_INDEX}" __pkg_basename="$2" local __var_to_set="$3" f_dprintf "f_index_search: Searching package data (in %s) for %s" \ "$__var_to_get" "$__pkg_basename" local __pkg= __pkg=$( debug= f_getvar "$__var_to_get" | awk -F'|' -v basename="$__pkg_basename" ' BEGIN { n = length(basename) } substr($1, 0, n) == basename { print $1; exit } ' ) if [ ! "$__pkg" ]; then f_dprintf "f_index_search: No packages matching %s found" \ "$__pkg_basename" return $FAILURE fi f_dprintf "f_index_search: Found package %s" "$__pkg" if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__pkg" else echo "$__pkg" fi return $SUCCESS } ############################################################ MAIN f_dprintf "%s: Successfully loaded." packages/index.subr fi # ! $_PACKAGES_INDEX_SUBR Index: head/usr.sbin/bsdconfig/share/packages/packages.subr =================================================================== --- head/usr.sbin/bsdconfig/share/packages/packages.subr (revision 298883) +++ head/usr.sbin/bsdconfig/share/packages/packages.subr (revision 298884) @@ -1,1194 +1,1194 @@ if [ ! "$_PACKAGES_PACKAGES_SUBR" ]; then _PACKAGES_PACKAGES_SUBR=1 # # Copyright (c) 2013-2016 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" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." "$0" f_include $BSDCFG_SHARE/dialog.subr f_include $BSDCFG_SHARE/device.subr f_include $BSDCFG_SHARE/media/common.subr f_include $BSDCFG_SHARE/packages/categories.subr f_include $BSDCFG_SHARE/packages/index.subr f_include $BSDCFG_SHARE/packages/musthavepkg.subr f_include $BSDCFG_SHARE/strings.subr BSDCFG_LIBE="/usr/libexec/bsdconfig" f_include_lang $BSDCFG_LIBE/include/messages.subr ############################################################ CONFIGURATION # # How many packages to display (maximum) per dialog menubox. # : ${PACKAGE_MENU_PAGESIZE:=2000} ############################################################ GLOBALS # # Package extensions to try # PACKAGE_EXTENSIONS=".txz .tbz .tbz2 .tgz" # # Variables used to track runtime states # PACKAGES_DETECTED= # Boolean (NULL/non-NULL); detected installed packages? PACKAGE_CATEGORIES= # List of package categories parsed from INDEX SELECTED_PACKAGES= # Packages selected by user in [X]dialog(1) interface # # Options # [ "${SHOW_DESC+set}" ] || SHOW_DESC=1 ############################################################ FUNCTIONS # eval f_package_accent_category_menu $var_to_set $CATEGORY_MENU_LIST # # Accent the CATEGORY_MENU_LIST produced by f_index_read() (see # packages/index.subr). Accented information includes adding an asterisk to the # category name if its index has been cached, adding the number of installed # packages for each category, and adding the number _selected_ packages for # each category. # # NOTE: The reason `eval' is recommended/shown for the syntax above is because # the $CATEGORY_MENU_LIST generated by f_index_read() is meant to be expanded # prior to execution (it contains a series of pre-quoted strings which act as # the interpolated command arguments). # f_package_accent_category_menu() { local var_to_set="$1" category cat desc help varcat menu_buf n shift 1 # var_to_set while [ $# -gt 0 ]; do category="${1%\*}" desc="${2%%; *}" help="$3" shift 3 # cat/desc/help cat="${category# }" # Trim lead space inserted by sort-method f_str2varname "$cat" varcat # Add number of installed packages for this category (if any) n=0 case "$cat" in "$msg_all") debug= f_getvar "_All_ninstalled" n ;; *) debug= f_getvar "_${varcat}_ninstalled" n ;; esac && [ $n -ge 1 ] && desc="$desc; $n $msg_installed_lc" # Add number of selected packages for this category (if any) n=0 case "$cat" in "$msg_all") debug= f_getvar "_All_nselected" n ;; *) debug= f_getvar "_${varcat}_nselected" n ;; esac && [ $n -ge 1 ] && desc="$desc; $n $msg_selected" # Re-Add asterisk to the category if its index has been cached f_isset _index_page_${varcat}_1 && category="$category*" # Update buffer with modified elements menu_buf="$menu_buf '$category' '$desc' '$help'" # End-Quote done setvar "$var_to_set" "$menu_buf" # return our buffer } # f_package_select $package ... # # Add $package to the list of tracked/selected packages. If $package is already # being tracked (already apears in $SELECTED_PACKAGES), this function amounts # to having no effect. # f_package_select() { local package pkgsel while [ $# -gt 0 ]; do package="$1" shift 1 # package for pkgsel in $SELECTED_PACKAGES; do [ "$package" = "$pkgsel" ] && return $SUCCESS done SELECTED_PACKAGES="$SELECTED_PACKAGES $package" f_dprintf "Added %s to selection list" "$package" done SELECTED_PACKAGES="${SELECTED_PACKAGES# }" # Trim leading space } # f_package_deselect $package ... # -# Remove $package from teh list of tracked/selected packages. If $package is +# Remove $package from the list of tracked/selected packages. If $package is # not being tracked (doesn't appear in $SELECTED_PACKAGES), this function # amounts to having no effet. # f_package_deselect() { local package pkgsel while [ $# -gt 1 ]; do local new_list="" package="$1" shift 1 # package for pkgsel in $SELECTED_PACKAGES; do [ "$pkgsel" = "$package" ] && continue new_list="$new_list${new_list:+ }$pkgsel" done SELECTED_PACKAGES="$new_list" f_dprintf "Removed %s from selection list" "$package" done } # f_package_detect_installed # # Detect installed packages. Currently this uses pkg-query(8) for querying # entries and marks each entry as an installed/selected package. # f_package_detect_installed() { local package varpkg for package in $( pkg query "%n-%v" ); do f_str2varname $package varpkg export _mark_$varpkg=X # exported for awk(1) ENVIRON[] f_package_select $package done } # f_package_calculate_totals # # Calculate number of installed/selected packages for each category listed in # $PACKAGE_CATEGORIES (the number of installed packages for $category is stored # as $_${varcat}_ninstalled -- where $varcat is the product of `f_str2varname # $category varcat' -- and number selected packages as $_${varcat}_nselected). # Also calculates the total number of installed/selected packages stored as # $_All_ninstalled and $_All_nselected. # -# Calculations are peformed by checking "marks". A "mark" is stored as +# Calculations are performed by checking "marks". A "mark" is stored as # $_mark_$varpkg -- where $varpkg is the product of `f_str2varname $package # varpkg'. A mark can be "X" for an installed package, `I' for a package that # is marked for installation, "R" for a package that is marked for re-install, # and "U" for a package that is marked for uninstallation. If a package mark is # NULL or a single space (e.g., " "), the package is considered to be NOT # selected (and therefore does not increment the counts calculated herein). # f_package_calculate_totals() { local pkg varpkg mark cat varcat pkgcat n tselected=0 tinstalled=0 for cat in $PACKAGE_CATEGORIES; do f_str2varname $cat varcat setvar _${varcat}_ninstalled=0 setvar _${varcat}_nselected=0 done for pkg in $SELECTED_PACKAGES; do f_str2varname $pkg varpkg mark= f_getvar _mark_$varpkg mark case "$mark" in ""|" ") : ;; X) tinstalled=$(( $tinstalled + 1 )) ;; *) tselected=$(( $tselected + 1 )) esac f_getvar _categories_$varpkg pkgcat for cat in $pkgcat; do f_str2varname $cat varcat case "$mark" in ""|" ") : ;; X) debug= f_getvar _${varcat}_ninstalled n setvar _${varcat}_ninstalled $(( $n + 1 )) ;; *) debug= f_getvar _${varcat}_nselected n setvar _${varcat}_nselected $(( $n + 1 )) esac done done _All_nselected=$tselected _All_ninstalled=$tinstalled } # f_package_calculate_rundeps # # Update package dependencies by first unmarking all dependencies and then # re-marking all dependencies of packages marked for either install ("I") or # re-install ("R"). # f_package_calculate_rundeps() { local pkg varpkg mark rundeps dep vardep # # First unmark all the existing run-dependencies # f_dprintf "Unselecting package run-dependencies..." for pkg in $SELECTED_PACKAGES; do f_str2varname $pkg varpkg mark= debug= f_getvar _mark_$varpkg mark # Only unmark if it's marked as a Dependency if [ "$mark" = "D" ]; then f_dprintf "%s unselected" $pkg unset _mark_$varpkg f_package_deselect $pkg fi done # # Processes selected packages, adding dependencies # f_dprintf "Re-selecting package run-dependencies..." for pkg in $SELECTED_PACKAGES; do f_str2varname $pkg varpkg mark= debug= f_getvar _mark_$varpkg mark # Skip pkg unless marked for [Re-]Install [ "$mark" = "I" -o "$mark" = "R" ] || continue f_getvar _rundeps_$varpkg rundeps for dep in $rundeps; do f_str2varname $dep vardep mark= debug= f_getvar _mark_$vardep mark # Skip dep if already marked [ "${mark:- }" = " " ] || continue export _mark_$vardep="D" f_package_select $dep done done f_dprintf "Finished recalculating dependencies." } # f_package_menu_categories $var_to_set $defaultitem # # Dislay the menu of package categories, complete with package counts for each # category, accents, and other miscellany. If $defaultitem is non-NULL and # matches one of the existing menu-items, it will be pre-highlighted in the # menu dialog (HINT: Use f_dialog_menutag_fetch() to populate a local variable # that is passed as $defaultitem to highlight the user's last selection). # f_package_menu_categories() { local var_to_get="$1" defaultitem="$2" local prompt="$msg_please_select_a_category_to_display" local menu_list=" '> $msg_review' '$msg_review_desc' '$msg_review_help' " # End-Quote local hline= f_package_calculate_rundeps # updates package mark variables and SELECTED_PACKAGES f_package_calculate_totals # creates _{varcat}_ninstalled and _{varcat}_nselected local category_list debug= f_getvar "$var_to_get" category_list || return $DIALOG_CANCEL # Accent the category menu list with ninstalled/nselected eval f_package_accent_category_menu category_list $category_list # Add list of categories to menu list menu_list="$menu_list $category_list" local height width rows eval f_dialog_menu_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\" \ --item-help \ --default-item \"\$defaultitem\" \ --ok-label \"$msg_select\" \ --cancel-label \"$msg_cancel\" \ --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_package_index_get_page $category $page [$var_to_set [$var_to_get]] # # Obtain a [potentially cached] page of the INDEX file for a given $category. # If $page is 1 and the cache has not yet been generated, the cache-generating # function f_index_extract_pages() (above) is called to generate all pages # (not just the requested page) in cache before returning the requested page. # If $page is not 1 and there is no cached page, failure status is returned. # f_package_index_get_page() { local category="$1" page="$2" var_to_set="$3" var_to_get="$4" varcat f_str2varname "$category" varcat if ! debug= f_getvar "_index_page_${varcat}_$page" $var_to_set && [ "$page" = "1" ] then f_show_info "$msg_building_package_menus" local pagesize="$PACKAGE_MENU_PAGESIZE" f_index_extract_pages "${var_to_get:-PACKAGE_INDEX}" \ _index_page_${varcat} "$pagesize" "$category" debug= f_getvar _index_page_${varcat}_$page $var_to_set # Update category default-item because now we're cached [ $page -eq 1 ] && category_defaultitem="${category_defaultitem%\*}*" else return $FAILURE fi } # f_package_menu_select $category [$page [$defaultitem]] # # Display list of packages for $category, optionally $page N and with a default # item selected. If $page is omitted, the first page is displayed (but this # only matters if there are multiple pages; which is determined by the global # maximum $PACKAGE_MENU_PAGESIZE). # # On success, if the user doesn't press ESC or choose Cancel, use # f_dialog_menuitem_fetch() to populate a local variable with the item (not # tag) corresponding to the user's selection. The tag portion of the user's # selection is available through f_dialog_menutag_fetch(). # f_package_menu_select() { local category="$1" page="${2:-1}" local prompt= # Calculated below local menu_list # Calculated below local defaultitem="$3" local hline="$hline_arrows_tab_punc_enter" f_isinteger "$page" || return $DIALOG_CANCEL local varcat f_str2varname "$category" varcat # Get number of packages for this category local npkgs=0 case "$category" in "$msg_all"|"") npkgs="${_npkgs:-0}" ;; *) f_getvar _npkgs_$varcat npkgs esac # Calculate number of pages local npages=$(( ${npkgs:=0} / $PACKAGE_MENU_PAGESIZE )) # Add a page to the pagecount if not evenly divisible [ $(( $npages * $PACKAGE_MENU_PAGESIZE )) -lt $npkgs ] && npages=$(( $npages + 1 )) # Print some debugging information f_dprintf "f_package_menu_select: category=[%s] npkgs=%u npages=%u" \ "$category" "$npkgs" "$npages" local add_prev="" add_next="" local previous_page="$msg_previous_page" next_page="$msg_next_page" if [ $page -gt 1 ]; then add_prev=1 # Accent the `Previous Page' item with an asterisk # if the page-before-previous is loaded/cached f_isset _index_page_${varcat}_$(( $page - 1 )) && previous_page="$previous_page*" fi if [ $page -lt $npages ]; then add_next=1 # Accent the `Next Page' item with an asterisk # if the page-after-next is loaded/cached f_isset _index_page_${varcat}_$(( $page + 1 )) && next_page="$next_page*" fi local index_page f_package_index_get_page "$category" $page index_page menu_list=" ${add_prev:+'> $previous_page' '' ${SHOW_DESC:+''}} ${add_next:+'> $next_page' '' ${SHOW_DESC:+''}} $( export SHOW_DESC export VALID_VARNAME_CHARS echo "$index_page" | awk -F'|' -v view="port" ' BEGIN { valid_chars = ENVIRON["VALID_VARNAME_CHARS"] prefix = "" } { cur_prefix = tolower(substr($1, 1, 1)) printf "'\''" if ( prefix != cur_prefix ) prefix = cur_prefix else printf " " package = $1 if ( view == "port" ) desc = $2 varpkg = package gsub("[^" valid_chars "]", "_", varpkg) mark = ENVIRON["_mark_" varpkg] if ( ! mark ) mark = " " printf "%s'\'' '\''[%c] %s'\''", package, mark, desc if ( ENVIRON["SHOW_DESC"] ) { help = $4 gsub(/'\''/, "'\''\\'\'\''", help) printf " '\''%s'\''", help } printf "\n" }' ) ${add_prev:+'> $previous_page' '' ${SHOW_DESC:+''}} ${add_next:+'> $next_page' '' ${SHOW_DESC:+''}} " # End-Quote # Accept/Translate i18n "All" but other category names must # match tree definitions from INDEX, ports, FTP, etc. case "$category" in "$msg_all"|"") f_category_desc_get "All" prompt ;; *) f_category_desc_get "$category" prompt ;; esac f_sprintf prompt "%s $msg_page_of_npages" "$prompt" "$page" "$npages" local mheight mwidth mrows eval f_dialog_menu${SHOW_DESC:+_with_help}_size mheight mwidth mrows \ \"\$DIALOG_TITLE\" \"\$DIALOG_BACKTITLE\" \ \"\$prompt\" \"\$hline\" $menu_list local iheight iwidth f_dialog_infobox_size iheight iwidth \ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" \ "$msg_processing_selection" local menu_choice menu_choice=$( eval $DIALOG \ --title \"\$DIALOG_TITLE\" \ --backtitle \"\$DIALOG_BACKTITLE\" \ --hline \"\$hline\" \ --keep-tite \ --ok-label \"$msg_select\" \ --cancel-label \"$msg_back\" \ ${SHOW_DESC:+--item-help} \ --default-item \"\$defaultitem\" \ --menu \"\$prompt\" \ $mheight $mwidth $mrows \ $menu_list \ --and-widget \ ${USE_XDIALOG:+--no-buttons} \ --infobox \"\$msg_processing_selection\" \ $iheight $iwidth \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) local retval=$? f_dialog_data_sanitize menu_choice f_dialog_menutag_store "$menu_choice" if [ $retval -eq $DIALOG_OK ]; then local item item=$( eval f_dialog_menutag2item${SHOW_DESC:+_with_help} \ \"\$menu_choice\" $menu_list ) f_dialog_menuitem_store "$item" fi return $retval } # f_package_menu_deselect $package # # Display a menu, asking the user what they would like to do with $package # with regard to "deselecting" an already installed package. Choices include # uninstall, re-install, or cancel (leave $package marked as installed). # Returns success if the user does not press ESC or choose Cnacel. Use the # f_dialog_menutag_fetch() function upon success to retrieve the user's choice. # f_package_menu_deselect() { local package="$1" local prompt # Calculated below local menu_list=" 'X $msg_installed' '$msg_installed_desc' 'R $msg_reinstall' '$msg_reinstall_desc' 'U $msg_uninstall' '$msg_uninstall_desc' " # End-Quote local hline="$hline_alnum_arrows_punc_tab_enter" f_sprintf prompt "$msg_what_would_you_like_to_do_with" "$package" local height width rows eval f_dialog_menu_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\" \ --ok-label \"$msg_select\" \ --cancel-label \"$msg_cancel\" \ --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_package_review # # Display a review screen, showing selected packages and what they are marked # for, before proceeding (if the user does not press ESC or choose Cancel) to # operate on each selection. Returns error if no packages have been selected, # or the user has pressed ESC, or if they have chosen Cancel. # f_package_review() { local funcname=f_package_review local prompt # Calculated below local menu_list # Calculated below local hline="$hline_alnum_arrows_punc_tab_enter" f_dprintf "$funcname: SELECTED_PACKAGES=[%s]" "$SELECTED_PACKAGES" f_sprintf prompt "$msg_reviewing_selected_packages" "$_All_nselected" local package varpkg mark for package in $SELECTED_PACKAGES; do mark= f_str2varname "$package" varpkg f_getvar _mark_$varpkg mark [ "$mark" -a ! "${mark#[IRUD]}" ] || continue menu_list="$menu_list '$mark' '$package' " # End-Quote done if [ ! "$menu_list" ]; then f_show_msg "$msg_no_packages_were_selected_for_extraction" return $DIALOG_CANCEL # Might have selected this by accident fi menu_list=$( echo "$menu_list" | sort ) local height width rows eval f_dialog_menu_size height width rows \ \"\$DIALOG_TITLE\" \ \"\$DIALOG_BACKTITLE\" \ \"\$prompt\" \ \"\$hline\" \ $menu_list # Show the review menu (ignore menu choice) eval $DIALOG \ --title \"\$DIALOG_TITLE\" \ --backtitle \"\$DIALOG_BACKTITLE\" \ --hline \"\$hline\" \ --ok-label \"\$msg_proceed\" \ --cancel-label \"\$msg_cancel\" \ --menu \"\$prompt\" \ $height $width $rows \ $menu_list \ 2> /dev/null || return $? # Return if the user pressed ESC or chose Cancel/No # # Process each of the selected packages: # + First, process packages marked for Install. # + Second, process packages marked for Re-install. # + Finally, process packages marked for Uninstall. # for package in $SELECTED_PACKAGES; do mark= f_str2varname "$package" varpkg debug= f_getvar _mark_$varpkg mark [ "$mark" = "I" ] || continue f_dprintf "$funcname: Installing %s package" "$package" f_package_add "$package" done for package in $SELECTED_PACKAGES; do mark= f_str2varname "$package" varpkg debug= f_getvar _mark_$varpkg mark [ "$mark" = "R" ] || continue f_dprintf "$funcname: Reinstalling %s package" "$package" f_package_reinstall "$package" done for package in $SELECTED_PACKAGES; do mark= f_str2varname "$package" varpkg debug= f_getvar _mark_$varpkg mark [ "$mark" = "U" ] || continue f_dprintf "$funcname: Uninstalling %s package" "$package" f_package_delete "$package" || continue f_package_deselect "$package" done return $DIALOG_OK } # f_package_config # # Allow the user to configure packages and install them. Initially, a list of # package categories is loaded/displayed. When the user selects a category, # the menus for that category are built (unlike sysinstall which built all # category menus up-front -- which also took forever, despite the fact that # few people visit more than a couple of categories each time). # f_package_config() { # Did we get an INDEX? f_index_initialize || return $FAILURE # Creates following variables (indirectly via f_index_read()) # CATEGORY_MENU_LIST _categories_{varpkg} _rundeps_{varpkg} # PACKAGE_CATEGORIES _npkgs f_show_info "$msg_building_package_main_menu" # Detect installed packages (updates marks/SELECTED_PACKAGES) f_package_detect_installed export PACKAGES_DETECTED=1 # exported for awk(1) ENVIRON[] local retval category varcat defaultitem category_defaultitem="" while :; do # Display the list of package categories f_package_menu_categories \ CATEGORY_MENU_LIST "$category_defaultitem" retval=$? f_dialog_menutag_fetch category f_dprintf "retval=%u mtag=[%s]" $retval "$category" category_defaultitem="$category" [ $retval -eq $DIALOG_OK ] || break # Maybe the user chose an action (like `Review') case "$category" in "> $msg_review") f_package_review && break continue ;; "> "*) continue esac # Anything else is a package category category=${category# } # Trim leading space if present category=${category%\*} # Trim trailing asterisk if present f_str2varname "$category" varcat local page package varpkg mark menu_choice while :; do # Display the list of packages for selected category page=1 defaultitem="" f_getvar _defaultitem_$varcat defaultitem f_getvar _defaultpage_$varcat page f_package_menu_select \ "$category" "${page:=1}" "$defaultitem" retval=$? f_dialog_menutag_fetch menu_choice f_dprintf "retval=%u mtag=[%s]" $retval "$menu_choice" # NOTE: When --and-widget is used only ESC will cause # dialog(1) to return without going to the next widget. # This is alright in our case as we can still detect # the Cancel button because stdout will be NULL. # Alternatively, Xdialog(1) will terminate with 1 # if/when Cancel is chosen on any widget. if [ $retval -eq $DIALOG_ESC -o ! "$menu_choice" ] then break elif [ $retval -eq $DIALOG_CANCEL ]; then # Using X11, Xdialog(1) returned 1 for Cancel f_show_msg "%s" "$menu_choice" break elif [ $retval -ne $DIALOG_OK ]; then # X11-related error occurred using Xdialog(1) f_show_msg "%s" "$menu_choice" break fi defaultitem="$menu_choice" # NOTE: f_package_menu_select() does not show the # `Previous Page' or `Next Page' items unless needed case "$menu_choice" in "> $msg_previous_page"|"> $msg_previous_page*") page=$(( $page - 1 )) setvar _defaultpage_$varcat $page # Update default-item to match accent that will # be applied by f_package_menu_select(); if the # page-before-prev is cached, add an asterisk. if f_isset \ _index_page_${varcat}_$(( $page - 1 )) then defaultitem="${defaultitem%\*}*" else defaultitem="${defaultitem%\*}" fi setvar _defaultitem_$varcat "$defaultitem" continue ;; "> $msg_next_page"|"> $msg_next_page*") page=$(( $page + 1 )) setvar _defaultpage_$varcat $page # Update default-item to match accent that will # be applied by f_package_menu_select(); if the # page-after-next is cached, add an asterisk. if f_isset \ _index_page_${varcat}_$(( $page + 1 )) then defaultitem="${defaultitem%\*}*" else defaultitem="${defaultitem%\*}" fi setvar _defaultitem_$varcat "$defaultitem" continue ;; "> "*) # Unknown navigation/action item setvar _defaultpage_$varcat $page continue ;; # Do not treat as a package *) setvar _defaultitem_$varcat "$defaultitem" esac # Treat any other selection as a package package="${menu_choice# }" # Trim leading space f_str2varname $package varpkg f_dialog_menuitem_fetch mark mark="${mark#?}" mark="${mark%%\] *}" case "$mark" in "I") mark=" " f_package_deselect $package ;; " "|"D") mark="I" f_package_select $package ;; "X"|"R"|"U") f_package_menu_deselect $package || continue f_dialog_menutag_fetch menu_choice case "$menu_choice" in "X $msg_installed") f_package_deselect "$package" mark="X" ;; "R $msg_reinstall") f_package_select "$package" mark="R" ;; "U $msg_uninstall") f_package_select "$package" mark="U" ;; esac ;; esac export _mark_$varpkg="$mark" # NOTE: exported for awk(1) ENVIRON[] done done } # f_package_add $package_name [$depended] # # Like f_package_extract(), but assumes current media device and chases deps. # Note that $package_name should not contain the archive suffix (e.g., `.tbz'). # If $depended is present and non-NULL, the package is treated as a dependency # (in this function, dependencies are not handled any differently, but the # f_package_extract() function is passed this value and it displays a different # message when installing a dependency versus non-dependency). # f_package_add() { local name="$1" depended="$2" status=$SUCCESS retval local alert=f_show_msg no_confirm= f_getvar $VAR_NO_CONFIRM no_confirm [ "$no_confirm" ] && alert=f_show_info if ! { [ "$name" ] || { f_getvar $VAR_PACKAGE name && [ "$name" ]; }; } then f_dprintf "packageAdd: %s" \ "$msg_no_package_name_passed_in_package_variable" return $FAILURE fi { # Verify and initialize device media if-defined f_media_verify && f_device_init device_media && f_index_initialize } || return $FAILURE # Now we have (indirectly via f_index_read()): # CATEGORY_MENU_LIST _categories_{varpkg} _rundeps_{varpkg} # PACKAGE_CATEGORIES _npkgs local varpkg f_str2varname "$name" varpkg # Just as-in the user-interface (opposed to scripted-use), only allow # packages with at least one category to be recognized. # local pkgcat= if ! f_getvar _categories_$varpkg pkgcat || [ ! "$pkgcat" ]; then # $pkg may be a partial name, search the index (this is slow) f_index_search PACKAGE_INDEX $name name if [ ! "$name" ]; then f_show_msg \ "$msg_sorry_package_was_not_found_in_the_index" \ "$name" return $FAILURE fi f_str2varname "$name" varpkg fi # If invoked through the scripted interface, we likely have not yet # detected the installed packages -- something we should do only once. # if [ ! "$PACKAGES_DETECTED" ]; then f_dprintf "f_package_add: Detecting installed packages" f_package_detect_installed export PACKAGES_DETECTED=1 # exported for awk(1) ENVIRON[] fi # Now we have: _mark_{varpkg}=X for all installed packages # # Since we're maintaining data structures for installed packages, # short-circuit the package dependency checks if the package is already # installed. This prevents wasted cycles, minor delays between package # extractions, and worst-case an infinite loop with a certain faulty # INDEX file. # local mark= f_getvar _mark_$varpkg mark && [ "$mark" = "X" ] && return $SUCCESS local dep vardep rundeps= f_getvar _rundeps_$varpkg rundeps for dep in $rundeps; do f_str2varname "$dep" vardep # Skip dependency if already installed mark= f_getvar _mark_$vardep mark && [ "$mark" = "X" ] && continue # Just as-in the user-interface (opposed to scripted-use), only # allow packages with at least one category to be recognized. # local depcat= if ! f_getvar _categories_$vardep depcat || [ ! "$depcat" ] then $alert "$msg_required_package_not_found" "$dep" [ "$no_confirm" ] && sleep 2 fi f_package_add "$dep" retval=$? if [ $retval -ne $SUCCESS ]; then status=$(( $status | $retval )) # XXX package could be on a future disc volume # XXX (not supporting multiple disc volumes yet) $alert "$msg_loading_of_dependent_package_failed" \ "$dep" [ "$no_confirm" ] && sleep 2 fi done [ $status -eq $SUCCESS ] || return $status # # Done with the deps? Try to load the real m'coy. # f_package_extract device_media "$name" "$depended" retval=$? if [ $retval -ne $SUCCESS ]; then status=$(( $status | $retval )) else setvar _mark_$varpkg X fi return $status } # f_package_extract $device $name [$depended] # # Extract a package based on a namespec and media device. If $depended is # present and non-NULL, the notification displayed while installing the package # has "as a dependency" appended. # f_package_extract() { local funcname=f_package_extract local device="$1" name="$2" depended="$3" local devname= f_musthavepkg_init # Make sure we have a usable pkg(8) with $PKG_ABI $device get name devname f_dprintf "$funcname: device=[%s] name=[%s] depended=[%s]" \ "$devname" "$name" "$depended" # Check to make sure it's not already there local varpkg mark= f_str2varname "$name" varpkg f_getvar _mark_$varpkg mark [ "$mark" = "X" ] && return $SUCCESS if ! f_device_init $device; then f_show_msg \ "$msg_unable_to_initialize_media_type_for_package_extract" return $FAILURE fi # If necessary, initialize the ldconfig hints [ -f "/var/run/ld-elf.so.hints" ] || f_quietly ldconfig /usr/lib /usr/lib/compat /usr/local/lib # Make a couple paranoid locations for temp # files to live if user specified none local tmpdir f_getvar $VAR_PKG_TMPDIR:-/var/tmp tmpdir f_quietly mkdir -p -m 1777 "$tmpdir" local path device_type $device get type device_type case "$name" in */*) path="$name" ;; *) if [ "$device_type" = "$DEVICE_TYPE_HTTP" ]; then path="$PKG_ABI/latest/All/$name" else path="packages/$PKG_ABI/All/$name" fi esac # We have a path, call the device strategy routine to check the file local pkg_ext found= for pkg_ext in "" $PACKAGE_EXTENSIONS; do if f_device_get $device "$path$pkg_ext" $PROBE_EXIST; then path="$path$pkg_ext" found=1 break elif [ "$device_type" = "$DEVICE_TYPE_HTTP" ] && f_device_get $device \ "packages/$PKG_ABI/All/$name$pkg_ext" $PROBE_EXIST then # Mirroring physical media over HTTP path="packages/$PKG_ABI/All/$name$pkg_ext" found=1 break fi done [ "$found" ] && f_dprintf "$funcname: found path=[%s] dev=[%s]" \ "$path" "$devname" local alert=f_show_msg no_confirm= f_getvar $VAR_NO_CONFIRM no_confirm [ "$no_confirm" ] && alert=f_show_info if [ ! "$found" ]; then f_dprintf "$funcname: No such %s file on %s device" \ "$path" "$devname" $alert "$msg_unable_to_fetch_package_from_selected_media" \ "$name" [ "$no_confirm" ] && sleep 2 return $FAILURE fi if [ "$depended" ]; then f_show_info "$msg_adding_package_as_a_dependency_from_media" \ "$name" "$devname" else f_show_info "$msg_adding_package_from_media" "$name" "$devname" fi # Request the package be added via pkg-install(8) if f_debugging; then f_eval_catch $funcname pkg \ 'pkg -d install -${depended:+A}y "%s"' "$name" else f_eval_catch $funcname pkg \ 'pkg install -${depended:+A}y "%s"' "$name" fi if [ $? -ne $SUCCESS ]; then $alert "$msg_pkg_install_apparently_did_not_like_the_package" \ "$name" [ "$no_confirm" ] && sleep 2 else f_show_info "$msg_package_was_added_successfully" "$name" sleep 1 fi return $SUCCESS } # f_package_delete $name # # Delete package by full $name (lacks archive suffix; e.g., `.tbz'). # f_package_delete() { local funcname=f_package_delete local name="$1" if ! { [ "$name" ] || { f_getvar $VAR_PACKAGE name && [ "$name" ]; }; } then f_dprintf "packageDelete: %s" \ "$msg_no_package_name_passed_in_package_variable" return $FAILURE fi f_dprintf "$funcname: name=[%s]" "$name" [ "$name" ] || return $FAILURE { # Verify and initialize device media if-defined f_media_verify && f_device_init device_media && f_index_initialize } || return $FAILURE # Now we have (indirectly via f_index_read()): # CATEGORY_MENU_LIST _categories_{varpkg} _rundeps_{varpkg} # PACKAGE_CATEGORIES _npkgs local varpkg f_str2varname "$name" varpkg # Just as-in the user-interface (opposed to scripted-use), only allow # packages with at least one category to be recognized. # local pkgcat= if ! f_getvar _categories_$varpkg pkgcat || [ ! "$pkgcat" ]; then # $pkg may be a partial name, search the index (this is slow) f_index_search PACKAGE_INDEX "$name" name if [ ! "$name" ]; then f_show_msg \ "$msg_sorry_package_was_not_found_in_the_index" \ "$name" return $FAILURE fi f_str2varname "$name" varpkg fi # If invoked through the scripted interface, we likely have not yet # detected the installed packages -- something we should do only once. # if [ ! "$PACKAGES_DETECTED" ]; then f_dprintf "$funcname: Detecting installed packages" f_package_detect_installed export PACKAGES_DETECTED=1 # exported for awk(1) ENVIRON[] fi # Now we have: _mark_{varpkg}=X for all installed packages # # Return failure if the package is not already installed. # local pkgmark= f_getvar _mark_$varpkg pkgmark if ! [ "$pkgmark" -a ! "${pkgmark#[XUR]}" ]; then f_show_msg "$msg_package_not_installed_cannot_delete" "$name" return $FAILURE fi # # Check for dependencies # local pkgsel depc=0 udeps= for pkgsel in $SELECTED_PACKAGES; do local mark= f_str2varname $pkgsel varpkg debug= f_getvar _mark_$varpkg mark [ "$mark" -a ! "${mark#[XUR]}" ] || continue local dep rundeps= debug= f_getvar _rundeps_$varpkg rundeps for dep in $rundeps; do if [ "$dep" = "$name" ]; then # Maybe this package is marked for deletion too if [ "$mark" = "U" ]; then udeps="$udeps $pkgsel" else depc=$(( $depc + 1 )) fi break fi done done if [ $depc -gt 0 ]; then local grammatical_s= [ $depc -gt 1 ] && grammatical_s=s f_show_msg \ "$msg_package_is_needed_by_other_installed_packages" \ "$name" "$depc" "$grammatical_s" return $FAILURE fi # # Chase dependencies that are marked for uninstallation # for pkgsel in $udeps; do f_dprintf "$funcname: Uninstalling dependency %s (%s)" \ "$pkgsel" "marked for delete" f_package_delete "$pkgsel" done # # OK to perform the delete (no other packages depend on it)... # f_show_info "$msg_uninstalling_package_waiting_for_pkg_delete" "$name" if f_debugging; then f_eval_catch $funcname pkg 'pkg -d delete -y "%s"' "$name" else f_eval_catch $funcname pkg 'pkg delete -y "%s"' "$name" fi if [ $? -ne $SUCCESS ]; then f_show_msg "$msg_pkg_delete_failed" "$name" return $FAILURE else f_dprintf "$funcname: pkg-delete(8) of %s successful" "$name" f_str2varname "$name" varpkg setvar _mark_$varpkg "" fi } # f_package_reinstall $name # # A simple wrapper to f_package_delete() + f_package_add() # f_package_reinstall() { f_package_delete "$1" && f_package_add "$1" } ############################################################ MAIN f_dprintf "%s: Successfully loaded." packages/packages.subr fi # ! $_PACKAGES_PACKAGES_SUBR Index: head/usr.sbin/bsdconfig/startup/share/rcconf.subr =================================================================== --- head/usr.sbin/bsdconfig/startup/share/rcconf.subr (revision 298883) +++ head/usr.sbin/bsdconfig/startup/share/rcconf.subr (revision 298884) @@ -1,500 +1,500 @@ 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. # # $FreeBSD$ # ############################################################ 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 persistant + # 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 persistant cache file exists + # 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 persistant + # (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 persistant global cache + # 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 Index: head/usr.sbin/bsdconfig/startup/share/rcvar.subr =================================================================== --- head/usr.sbin/bsdconfig/startup/share/rcvar.subr (revision 298883) +++ head/usr.sbin/bsdconfig/startup/share/rcvar.subr (revision 298884) @@ -1,236 +1,236 @@ if [ ! "$_STARTUP_RCVAR_SUBR" ]; then _STARTUP_RCVAR_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. # # $FreeBSD$ # ############################################################ INCLUDES BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." startup/rcvar.subr f_include $BSDCFG_SHARE/sysrc.subr ############################################################ CONFIGURATION # # Default path to the `/etc/rc.d' directory where service(8) scripts are stored # : ${ETC_RC_D:=/etc/rc.d} # # Default path to `/etc/rc.subr' (for find_local_scripts_new()) # : ${ETC_RC_SUBR:=/etc/rc.subr} ############################################################ GLOBALS # # Initialize in-memory cache variables # STARTUP_RCVAR_MAP= _STARTUP_RCVAR_MAP= # # Define what an rcvar looks like # STARTUP_RCVAR_REGEX='[[:alpha:]_][[:alnum:]_]*="([Yy][Ee][Ss]|[Nn][Oo])"' # # Default path to on-disk cache file(s) # STARTUP_RCVAR_MAP_CACHEFILE="/var/run/bsdconfig/startup_rcvar_map.cache" ############################################################ FUNCTIONS # f_startup_rcvar_map [$var_to_set] # # Produce a map (beit from in-memory cache or on-disk cache) of rc.d scripts # and their associated rcvar's. The map returned has the following format: # # rcvar default script description # # With each as follows: # # rcvar the variable used to enable this rc.d script # default default value for this variable # script the rc.d script in-question # description description of the variable from rc.conf(5) defaults # # 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_rcvar_map() { local __funcname=f_startup_rcvar_map local __var_to_set="$1" # If the in-memory cached value is available, return it immediately if [ "$_STARTUP_RCVAR_MAP" ]; then if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$STARTUP_RCVAR_MAP" else echo "$STARTUP_RCVAR_MAP" fi return $SUCCESS fi # # create the in-memory cache (potentially from validated on-disk cache) # # Get a list of /etc/rc.d scripts ... local __file __rc_script_list= for __file in "$ETC_RC_D"/*; do [ -f "$__file" ] || continue [ -x "$__file" ] || continue __rc_script_list="$__rc_script_list $__file" done # ... and /usr/local/etc/rc.d scripts __rc_script_list="$__rc_script_list $( local_startup=$( f_sysrc_get local_startup ) f_include "$ETC_RC_SUBR" find_local_scripts_new echo $local_rc )" __rc_script_list="${__rc_script_list# }" # Trim leading space # # Calculate a digest given the checksums of all dependencies (scripts # and the defaults file). This digest will be used to determine if an - # on-disk global persistant cache file (containg this digest on the + # on-disk global persistent cache file (containg this digest on the # first line) is valid and can be used to quickly populate the cache # value for immediate return. # local __rc_script_list_digest __rc_script_list_digest=$( cd "$ETC_RC_D" 2> /dev/null && cksum "$RC_DEFAULTS" $__rc_script_list 2> /dev/null | md5 ) # - # Check to see if the global persistant cache file exists + # Check to see if the global persistent cache file exists # if [ -f "$STARTUP_RCVAR_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 return error. # STARTUP_RCVAR_MAP=$( ( # Get digest as 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 return success. # if [ "$digest" = "$__rc_script_list_digest" ] then cat exit $SUCCESS else # Otherwise, return the current value echo "$STARTUP_RCVAR_MAP" exit $FAILURE fi ) < "$STARTUP_RCVAR_MAP_CACHEFILE" ) local __retval=$? export STARTUP_RCVAR_MAP # Make children faster (export cache) if [ $__retval -eq $SUCCESS ]; then export _STARTUP_RCVAR_MAP=1 if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$STARTUP_RCVAR_MAP" else echo "$STARTUP_RCVAR_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 persistant + # (and after we do, we'll attempt to create the global persistent # cache file to speed up future executions). # STARTUP_RCVAR_MAP=$( for script in $__rc_script_list; do rcvar_list=$( $script rcvar 2> /dev/null | awk -F= \ -v script="$script" ' /^'"$STARTUP_RCVAR_REGEX"'/ { if ( $2 ~ /^"[Yy][Ee][Ss]"$/ ) print $1 ",YES" else print $1 ",NO" }' ) for entry in $rcvar_list; do rcvar="${entry%%,*}" rcvar_default=$( f_sysrc_get_default "$rcvar" ) [ "$rcvar_default" ] || rcvar_default="${entry#*,}" rcvar_desc=$( f_sysrc_desc "$rcvar" ) echo $rcvar ${rcvar_default:-NO} \ $script "$rcvar_desc" done done | sort -u ) export STARTUP_RCVAR_MAP export _STARTUP_RCVAR_MAP=1 if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$STARTUP_RCVAR_MAP" else echo "$STARTUP_RCVAR_MAP" fi # - # Attempt to create/update the persistant global cache + # Attempt to create/update the persistent global cache # # Create a new temporary file to write to local __tmpfile f_eval_catch -dk __tmpfile $__funcname mktemp \ 'mktemp -t "%s"' "$__tmpfile" || return $FAILURE # Write the temporary file contents echo "$__rc_script_list_digest" > "$__tmpfile" echo "$STARTUP_RCVAR_MAP" >> "$__tmpfile" # Finally, move the temporary file into place case "$STARTUP_RCVAR_MAP_CACHEFILE" in */*) f_eval_catch -d $__funcname mkdir \ 'mkdir -p "%s"' "${STARTUP_RCVAR_MAP_CACHEFILE%/*}" esac f_eval_catch -d $__funcname mv \ 'mv "%s" "%s"' "$__tmpfile" "$STARTUP_RCVAR_MAP_CACHEFILE" } ############################################################ MAIN f_dprintf "%s: Successfully loaded." startup/rcvar.subr fi # ! $_STARTUP_RCVAR_SUBR Index: head/usr.sbin/bsdinstall/scripts/zfsboot =================================================================== --- head/usr.sbin/bsdinstall/scripts/zfsboot (revision 298883) +++ head/usr.sbin/bsdinstall/scripts/zfsboot (revision 298884) @@ -1,1673 +1,1673 @@ #!/bin/sh #- # Copyright (c) 2013-2015 Allan Jude # Copyright (c) 2013-2015 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" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." "$0" f_include $BSDCFG_SHARE/device.subr f_include $BSDCFG_SHARE/dialog.subr f_include $BSDCFG_SHARE/password/password.subr f_include $BSDCFG_SHARE/variable.subr ############################################################ CONFIGURATION # # Default name of the boot-pool # : ${ZFSBOOT_POOL_NAME:=zroot} # # Default options to use when creating zroot pool # : ${ZFSBOOT_POOL_CREATE_OPTIONS:=-O compress=lz4 -O atime=off} # # Default name for the boot environment parent dataset # : ${ZFSBOOT_BEROOT_NAME:=ROOT} # # Default name for the primany boot environment # : ${ZFSBOOT_BOOTFS_NAME:=default} # # Default Virtual Device (vdev) type to create # : ${ZFSBOOT_VDEV_TYPE:=stripe} # # Should we use sysctl(8) vfs.zfs.min_auto_ashift=12 to force 4K sectors? # : ${ZFSBOOT_FORCE_4K_SECTORS:=1} # # Should we use geli(8) to encrypt the drives? # NB: Automatically enables ZFSBOOT_BOOT_POOL # : ${ZFSBOOT_GELI_ENCRYPTION=} # # Default path to the geli(8) keyfile used in drive encryption # : ${ZFSBOOT_GELI_KEY_FILE:=/boot/encryption.key} # # Create a separate boot pool? # NB: Automatically set when using geli(8) or MBR # : ${ZFSBOOT_BOOT_POOL=} # # Options to use when creating separate boot pool (if any) # : ${ZFSBOOT_BOOT_POOL_CREATE_OPTIONS:=} # # Default name for boot pool when enabled (e.g., geli(8) or MBR) # : ${ZFSBOOT_BOOT_POOL_NAME:=bootpool} # # Default size for boot pool when enabled (e.g., geli(8) or MBR) # : ${ZFSBOOT_BOOT_POOL_SIZE:=2g} # # Default disks to use (always empty unless being scripted) # : ${ZFSBOOT_DISKS:=} # # Default partitioning scheme to use on disks # : ${ZFSBOOT_PARTITION_SCHEME:=} # # Default boot type to use on disks # : ${ZFSBOOT_BOOT_TYPE:=} # # How much swap to put on each block device in the boot zpool # NOTE: Value passed to gpart(8); which supports SI unit suffixes. # : ${ZFSBOOT_SWAP_SIZE:=2g} # # Should we use geli(8) to encrypt the swap? # : ${ZFSBOOT_SWAP_ENCRYPTION=} # # Should we use gmirror(8) to mirror the swap? # : ${ZFSBOOT_SWAP_MIRROR=} # # Default ZFS datasets for root zpool # # NOTE: Requires /tmp, /var/tmp, /$ZFSBOOT_BOOTFS_NAME/$ZFSBOOT_BOOTFS_NAME # NOTE: Anything after pound/hash character [#] is ignored as a comment. # f_isset ZFSBOOT_DATASETS || ZFSBOOT_DATASETS=" # DATASET OPTIONS (comma or space separated; or both) # Boot Environment [BE] root and default boot dataset /$ZFSBOOT_BEROOT_NAME mountpoint=none /$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME mountpoint=/ # Compress /tmp, allow exec but not setuid /tmp mountpoint=/tmp,exec=on,setuid=off # Don't mount /usr so that 'base' files go to the BEROOT /usr mountpoint=/usr,canmount=off # Home directories separated so they are common to all BEs /usr/home # NB: /home is a symlink to /usr/home # Ports tree /usr/ports setuid=off # Source tree (compressed) /usr/src # Create /var and friends /var mountpoint=/var,canmount=off /var/audit exec=off,setuid=off /var/crash exec=off,setuid=off /var/log exec=off,setuid=off /var/mail atime=on /var/tmp setuid=off " # END-QUOTE # # If interactive and the user has not explicitly chosen a vdev type or disks, # make the user confirm scripted/default choices when proceeding to install. # : ${ZFSBOOT_CONFIRM_LAYOUT:=1} ############################################################ GLOBALS # # Format of a line in printf(1) syntax to add to fstab(5) # FSTAB_FMT="%s\t\t%s\t%s\t%s\t\t%s\t%s\n" # # Command strings for various tasks # CHMOD_MODE='chmod %s "%s"' DD_WITH_OPTIONS='dd if="%s" of="%s" %s' ECHO_APPEND='echo "%s" >> "%s"' GELI_ATTACH='geli attach -j - -k "%s" "%s"' GELI_DETACH_F='geli detach -f "%s"' GELI_PASSWORD_INIT='geli init -b -B "%s" -e %s -J - -K "%s" -l 256 -s 4096 "%s"' GPART_ADD_ALIGN='gpart add %s -t %s "%s"' GPART_ADD_ALIGN_INDEX='gpart add %s -i %s -t %s "%s"' GPART_ADD_ALIGN_INDEX_WITH_SIZE='gpart add %s -i %s -t %s -s %s "%s"' GPART_ADD_ALIGN_LABEL='gpart add %s -l %s -t %s "%s"' GPART_ADD_ALIGN_LABEL_WITH_SIZE='gpart add %s -l %s -t %s -s %s "%s"' GPART_BOOTCODE='gpart bootcode -b "%s" "%s"' GPART_BOOTCODE_PART='gpart bootcode -b "%s" -p "%s" -i %s "%s"' GPART_BOOTCODE_PARTONLY='gpart bootcode -p "%s" -i %s "%s"' GPART_CREATE='gpart create -s %s "%s"' GPART_DESTROY_F='gpart destroy -F "%s"' GPART_SET_ACTIVE='gpart set -a active -i %s "%s"' GPART_SET_LENOVOFIX='gpart set -a lenovofix "%s"' GPART_SET_PMBR_ACTIVE='gpart set -a active "%s"' GRAID_DELETE='graid delete "%s"' LN_SF='ln -sf "%s" "%s"' MKDIR_P='mkdir -p "%s"' MOUNT_TYPE='mount -t %s "%s" "%s"' PRINTF_CONF="printf '%s=\"%%s\"\\\n' %s >> \"%s\"" PRINTF_FSTAB='printf "$FSTAB_FMT" "%s" "%s" "%s" "%s" "%s" "%s" >> "%s"' SHELL_TRUNCATE=':> "%s"' SWAP_GMIRROR_LABEL='gmirror label swap %s' SYSCTL_ZFS_MIN_ASHIFT_12='sysctl vfs.zfs.min_auto_ashift=12' UMOUNT='umount "%s"' ZFS_CREATE_WITH_OPTIONS='zfs create %s "%s"' ZFS_SET='zfs set "%s" "%s"' ZFS_UNMOUNT='zfs unmount "%s"' ZPOOL_CREATE_WITH_OPTIONS='zpool create %s "%s" %s %s' ZPOOL_DESTROY='zpool destroy "%s"' ZPOOL_EXPORT='zpool export "%s"' ZPOOL_IMPORT_WITH_OPTIONS='zpool import %s "%s"' ZPOOL_LABELCLEAR_F='zpool labelclear -f "%s"' ZPOOL_SET='zpool set %s "%s"' # # Strings that should be moved to an i18n file and loaded with f_include_lang() # hline_alnum_arrows_punc_tab_enter="Use alnum, arrows, punctuation, TAB or ENTER" hline_arrows_space_tab_enter="Use arrows, SPACE, TAB or ENTER" hline_arrows_tab_enter="Press arrows, TAB or ENTER" msg_an_unknown_error_occurred="An unknown error occurred" msg_back="Back" msg_cancel="Cancel" msg_change_selection="Change Selection" msg_configure_options="Configure Options:" msg_detailed_disk_info="gpart(8) show %s:\n%s\n\ncamcontrol(8) inquiry %s:\n%s\n\n\ncamcontrol(8) identify %s:\n%s\n" msg_disk_info="Disk Info" msg_disk_info_help="Get detailed information on disk device(s)" msg_disk_singular="disk" msg_disk_plural="disks" msg_encrypt_disks="Encrypt Disks?" msg_encrypt_disks_help="Use geli(8) to encrypt all data partitions" msg_error="Error" msg_force_4k_sectors="Force 4K Sectors?" msg_force_4k_sectors_help="Align partitions to 4K sector boundries and set vfs.zfs.min_auto_ashift=12" msg_freebsd_installer="FreeBSD Installer" msg_geli_password="Enter a strong passphrase, used to protect your encryption keys. You will be required to enter this passphrase each time the system is booted" msg_geli_setup="Initializing encryption on selected disks,\n this will take several seconds per disk" msg_install="Install" msg_install_desc="Proceed with Installation" msg_install_help="Create ZFS boot pool with displayed options" msg_invalid_boot_pool_size="Invalid boot pool size \`%s'" msg_invalid_disk_argument="Invalid disk argument \`%s'" msg_invalid_index_argument="Invalid index argument \`%s'" msg_invalid_swap_size="Invalid swap size \`%s'" msg_invalid_virtual_device_type="Invalid Virtual Device type \`%s'" msg_last_chance_are_you_sure="Last Chance! Are you sure you want to destroy\nthe current contents of the following disks:\n\n %s" msg_last_chance_are_you_sure_color='\\ZrLast Chance!\\ZR Are you \\Z1sure\\Zn you want to \\Zr\\Z1destroy\\Zn\nthe current contents of the following disks:\n\n %s' msg_mirror_desc="Mirror - n-Way Mirroring" msg_mirror_help="[2+ Disks] Mirroring provides the best performance, but the least storage" msg_missing_disk_arguments="missing disk arguments" msg_missing_one_or_more_scripted_disks="Missing one or more scripted disks!" msg_no="NO" msg_no_disks_present_to_configure="No disk(s) present to configure" msg_no_disks_selected="No disks selected." msg_not_enough_disks_selected="Not enough disks selected. (%u < %u minimum)" msg_null_disk_argument="NULL disk argument" msg_null_index_argument="NULL index argument" msg_null_poolname="NULL poolname" msg_ok="OK" msg_partition_scheme="Partition Scheme" msg_partition_scheme_help="Select partitioning scheme. GPT is recommended." msg_please_enter_a_name_for_your_zpool="Please enter a name for your zpool:" msg_please_enter_amount_of_swap_space="Please enter amount of swap space (SI-Unit suffixes\nrecommended; e.g., \`2g' for 2 Gigabytes):" msg_please_select_one_or_more_disks="Please select one or more disks to create a zpool:" msg_pool_name="Pool Name" msg_pool_name_cannot_be_empty="Pool name cannot be empty." msg_pool_name_help="Customize the name of the zpool to be created (Required)" msg_pool_type_disks="Pool Type/Disks:" msg_pool_type_disks_help="Choose type of ZFS Virtual Device and disks to use (Required)" msg_processing_selection="Processing selection..." msg_raidz1_desc="RAID-Z1 - Single Redundant RAID" msg_raidz1_help="[3+ Disks] Withstand failure of 1 disk. Recommended for: 3, 5 or 9 disks" msg_raidz2_desc="RAID-Z2 - Double Redundant RAID" msg_raidz2_help="[4+ Disks] Withstand failure of 2 disks. Recommended for: 4, 6 or 10 disks" msg_raidz3_desc="RAID-Z3 - Triple Redundant RAID" msg_raidz3_help="[5+ Disks] Withstand failure of 3 disks. Recommended for: 5, 7 or 11 disks" msg_rescan_devices="Rescan Devices" msg_rescan_devices_help="Scan for device changes" msg_select="Select" msg_select_a_disk_device="Select a disk device" msg_select_virtual_device_type="Select Virtual Device type:" msg_stripe_desc="Stripe - No Redundancy" msg_stripe_help="[1+ Disks] Striping provides maximum storage but no redundancy" msg_swap_encrypt="Encrypt Swap?" msg_swap_encrypt_help="Encrypt swap partitions with temporary keys, discarded on reboot" msg_swap_invalid="The selected swap size (%s) is invalid. Enter a number optionally followed by units. Example: 2G" msg_swap_mirror="Mirror Swap?" msg_swap_mirror_help="Mirror swap partitions for redundancy, breaks crash dumps" msg_swap_size="Swap Size" msg_swap_size_help="Customize how much swap space is allocated to each selected disk" msg_swap_toosmall="The selected swap size (%s) is to small. Please enter a value greater than 100MB or enter 0 for no swap" msg_these_disks_are_too_small="These disks are smaller than the amount of requested\nswap (%s) and/or geli(8) (%s) partitions, which would\ntake 100%% or more of each of the following selected disks:\n\n %s\n\nRecommend changing partition size(s) and/or selecting a\ndifferent set of disks." msg_unable_to_get_disk_capacity="Unable to get disk capacity of \`%s'" msg_unsupported_partition_scheme="%s is an unsupported partition scheme" msg_user_cancelled="User Cancelled." msg_yes="YES" msg_zfs_configuration="ZFS Configuration" ############################################################ FUNCTIONS # dialog_menu_main # # Display the dialog(1)-based application main menu. # dialog_menu_main() { local title="$DIALOG_TITLE" local btitle="$DIALOG_BACKTITLE" local prompt="$msg_configure_options" local force4k="$msg_no" local usegeli="$msg_no" local swapgeli="$msg_no" local swapmirror="$msg_no" [ "$ZFSBOOT_FORCE_4K_SECTORS" ] && force4k="$msg_yes" [ "$ZFSBOOT_GELI_ENCRYPTION" ] && usegeli="$msg_yes" [ "$ZFSBOOT_SWAP_ENCRYPTION" ] && swapgeli="$msg_yes" [ "$ZFSBOOT_SWAP_MIRROR" ] && swapmirror="$msg_yes" local disks n disks_grammar f_count n $ZFSBOOT_DISKS { [ $n -eq 1 ] && disks_grammar=$msg_disk_singular; } || disks_grammar=$msg_disk_plural # grammar local menu_list=" '>>> $msg_install' '$msg_install_desc' '$msg_install_help' 'T $msg_pool_type_disks' '$ZFSBOOT_VDEV_TYPE: $n $disks_grammar' '$msg_pool_type_disks_help' '- $msg_rescan_devices' '*' '$msg_rescan_devices_help' '- $msg_disk_info' '*' '$msg_disk_info_help' 'N $msg_pool_name' '$ZFSBOOT_POOL_NAME' '$msg_pool_name_help' '4 $msg_force_4k_sectors' '$force4k' '$msg_force_4k_sectors_help' 'E $msg_encrypt_disks' '$usegeli' '$msg_encrypt_disks_help' 'P $msg_partition_scheme' '$ZFSBOOT_PARTITION_SCHEME ($ZFSBOOT_BOOT_TYPE)' '$msg_partition_scheme_help' 'S $msg_swap_size' '$ZFSBOOT_SWAP_SIZE' '$msg_swap_size_help' 'M $msg_swap_mirror' '$swapmirror' '$msg_swap_mirror_help' 'W $msg_swap_encrypt' '$swapgeli' '$msg_swap_encrypt_help' " # END-QUOTE local defaultitem= # Calculated below local hline="$hline_alnum_arrows_punc_tab_enter" local height width rows eval f_dialog_menu_with_help_size height width rows \ \"\$title\" \"\$btitle\" \"\$prompt\" \"\$hline\" $menu_list # Obtain default-item from previously stored selection f_dialog_default_fetch defaultitem local menu_choice menu_choice=$( eval $DIALOG \ --title \"\$title\" \ --backtitle \"\$btitle\" \ --hline \"\$hline\" \ --item-help \ --ok-label \"\$msg_select\" \ --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 menu_choice f_dialog_menutag_store "$menu_choice" # Only update default-item on success [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice" return $retval } # dialog_last_chance $disks ... # # Display a list of the disks that the user is about to destroy. The default # action is to return error status unless the user explicitly (non-default) # selects "Yes" from the noyes dialog. # dialog_last_chance() { local title="$DIALOG_TITLE" local btitle="$DIALOG_BACKTITLE" local prompt # Calculated below local hline="$hline_arrows_tab_enter" local height=8 width=50 prefix=" " local plen=${#prefix} list= line= local max_width=$(( $width - 3 - $plen )) local yes no defaultno extra_args format if [ "$USE_XDIALOG" ]; then yes=ok no=cancel defaultno=default-no extra_args="--wrap --left" format="$msg_last_chance_are_you_sure" else yes=yes no=no defaultno=defaultno extra_args="--colors --cr-wrap" format="$msg_last_chance_are_you_sure_color" fi local disk line_width for disk in $*; do if [ "$line" ]; then line_width=${#line} else line_width=$plen fi line_width=$(( $line_width + 1 + ${#disk} )) # Add newline before disk if it would exceed max_width if [ $line_width -gt $max_width ]; then list="$list$line\n" line="$prefix" height=$(( $height + 1 )) fi # Add the disk to the list line="$line $disk" done # Append the left-overs if [ "${line#$prefix}" ]; then list="$list$line" height=$(( $height + 1 )) fi # Add height for Xdialog(1) [ "$USE_XDIALOG" ] && height=$(( $height + $height / 5 + 3 )) prompt=$( printf "$format" "$list" ) f_dprintf "%s: Last Chance!" "$0" $DIALOG \ --title "$title" \ --backtitle "$btitle" \ --hline "$hline" \ --$defaultno \ --$yes-label "$msg_yes" \ --$no-label "$msg_no" \ $extra_args \ --yesno "$prompt" $height $width } # dialog_menu_layout # # Configure Virtual Device type and disks to use for the ZFS boot pool. User # must select enough disks to satisfy the chosen vdev type. # dialog_menu_layout() { local funcname=dialog_menu_layout local title="$DIALOG_TITLE" local btitle="$DIALOG_BACKTITLE" local vdev_prompt="$msg_select_virtual_device_type" local disk_prompt="$msg_please_select_one_or_more_disks" local vdev_menu_list=" 'stripe' '$msg_stripe_desc' '$msg_stripe_help' 'mirror' '$msg_mirror_desc' '$msg_mirror_help' 'raidz1' '$msg_raidz1_desc' '$msg_raidz1_help' 'raidz2' '$msg_raidz2_desc' '$msg_raidz2_help' 'raidz3' '$msg_raidz3_desc' '$msg_raidz3_help' " # END-QUOTE local disk_check_list= # Calculated below local vdev_hline="$hline_arrows_tab_enter" local disk_hline="$hline_arrows_space_tab_enter" # Warn the user if vdev type is not valid case "$ZFSBOOT_VDEV_TYPE" in stripe|mirror|raidz1|raidz2|raidz3) : known good ;; *) f_dprintf "%s: Invalid virtual device type \`%s'" \ $funcname "$ZFSBOOT_VDEV_TYPE" f_show_err "$msg_invalid_virtual_device_type" \ "$ZFSBOOT_VDEV_TYPE" f_interactive || return $FAILURE esac # Calculate size of vdev menu once only local vheight vwidth vrows eval f_dialog_menu_with_help_size vheight vwidth vrows \ \"\$title\" \"\$btitle\" \"\$vdev_prompt\" \"\$vdev_hline\" \ $vdev_menu_list # Get a list of probed disk devices local disks= debug= f_device_find "" $DEVICE_TYPE_DISK disks # Prune out mounted md(4) devices that may be part of the boot process local disk name new_list= for disk in $disks; do debug= $disk get name name case "$name" in md[0-9]*) f_mounted -b "/dev/$name" && continue ;; esac new_list="$new_list $disk" done disks="${new_list# }" # Debugging if [ "$debug" ]; then local disk_names= for disk in $disks; do debug= $disk get name name disk_names="$disk_names $name" done f_dprintf "$funcname: disks=[%s]" "${disk_names# }" fi if [ ! "$disks" ]; then f_dprintf "No disk(s) present to configure" f_show_err "$msg_no_disks_present_to_configure" return $FAILURE fi # Lets sort the disks array to be more user friendly f_device_sort_by name disks disks # # Operate in a loop so we can (if interactive) repeat if not enough # disks are selected to satisfy the chosen vdev type or user wants to # back-up to the previous menu. # local vardisk ndisks onoff selections vdev_choice breakout device local valid_disks all_valid want_disks desc height width rows while :; do # # Confirm the vdev type that was selected # if f_interactive && [ "$ZFSBOOT_CONFIRM_LAYOUT" ]; then vdev_choice=$( eval $DIALOG \ --title \"\$title\" \ --backtitle \"\$btitle\" \ --hline \"\$vdev_hline\" \ --ok-label \"\$msg_ok\" \ --cancel-label \"\$msg_cancel\" \ --item-help \ --default-item \"\$ZFSBOOT_VDEV_TYPE\" \ --menu \"\$vdev_prompt\" \ $vheight $vwidth $vrows \ $vdev_menu_list \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || return $? # Exit if user pressed ESC or chose Cancel/No f_dialog_data_sanitize vdev_choice ZFSBOOT_VDEV_TYPE="$vdev_choice" f_dprintf "$funcname: ZFSBOOT_VDEV_TYPE=[%s]" \ "$ZFSBOOT_VDEV_TYPE" fi # Determine the number of disks needed for this vdev type want_disks=0 case "$ZFSBOOT_VDEV_TYPE" in stripe) want_disks=1 ;; mirror) want_disks=2 ;; raidz1) want_disks=3 ;; raidz2) want_disks=4 ;; raidz3) want_disks=5 ;; esac # # Warn the user if any scripted disks are invalid # valid_disks= all_valid=${ZFSBOOT_DISKS:+1} # optimism for disk in $ZFSBOOT_DISKS; do if debug= f_device_find -1 \ $disk $DEVICE_TYPE_DISK device then valid_disks="$valid_disks $disk" continue fi f_dprintf "$funcname: \`%s' is not a real disk" "$disk" all_valid= done if [ ! "$all_valid" ]; then if [ "$ZFSBOOT_DISKS" ]; then f_show_err \ "$msg_missing_one_or_more_scripted_disks" else f_dprintf "No disks selected." f_interactive || f_show_err "$msg_no_disks_selected" fi f_interactive || return $FAILURE fi ZFSBOOT_DISKS="${valid_disks# }" # # Short-circuit if we're running non-interactively # if ! f_interactive || [ ! "$ZFSBOOT_CONFIRM_LAYOUT" ]; then f_count ndisks $ZFSBOOT_DISKS [ $ndisks -ge $want_disks ] && break # to success # Not enough disks selected f_dprintf "$funcname: %s: %s (%u < %u minimum)" \ "$ZFSBOOT_VDEV_TYPE" \ "Not enough disks selected." \ $ndisks $want_disks f_interactive || return $FAILURE msg_yes="$msg_change_selection" msg_no="$msg_cancel" \ f_yesno "%s: $msg_not_enough_disks_selected" \ "$ZFSBOOT_VDEV_TYPE" $ndisks $want_disks || return $FAILURE fi # # Confirm the disks that were selected # Loop until the user cancels or selects enough disks # breakout= while :; do # Loop over list of available disks, resetting state for disk in $disks; do f_isset _${disk}_status && _${disk}_status= done # Loop over list of selected disks and create temporary # locals to map statuses onto up-to-date list of disks for disk in $ZFSBOOT_DISKS; do debug= f_device_find -1 \ $disk $DEVICE_TYPE_DISK disk f_isset _${disk}_status || local _${disk}_status _${disk}_status=on done # Create the checklist menu of discovered disk devices disk_check_list= for disk in $disks; do desc= $disk get name name $disk get desc desc f_shell_escape "$desc" desc f_getvar _${disk}_status:-off onoff disk_check_list="$disk_check_list $name '$desc' $onoff" done eval f_dialog_checklist_size height width rows \ \"\$title\" \"\$btitle\" \"\$prompt\" \ \"\$hline\" $disk_check_list selections=$( eval $DIALOG \ --title \"\$DIALOG_TITLE\" \ --backtitle \"\$DIALOG_BACKTITLE\" \ --separate-output \ --hline \"\$hline\" \ --ok-label \"\$msg_ok\" \ --cancel-label \"\$msg_back\" \ --checklist \"\$prompt\" \ $height $width $rows \ $disk_check_list \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || break # Loop if user pressed ESC or chose Cancel/No f_dialog_data_sanitize selections ZFSBOOT_DISKS="$selections" f_dprintf "$funcname: ZFSBOOT_DISKS=[%s]" \ "$ZFSBOOT_DISKS" f_count ndisks $ZFSBOOT_DISKS [ $ndisks -ge $want_disks ] && breakout=break && break # Not enough disks selected f_dprintf "$funcname: %s: %s (%u < %u minimum)" \ "$ZFSBOOT_VDEV_TYPE" \ "Not enough disks selected." \ $ndisks $want_disks msg_yes="$msg_change_selection" msg_no="$msg_cancel" \ f_yesno "%s: $msg_not_enough_disks_selected" \ "$ZFSBOOT_VDEV_TYPE" $ndisks $want_disks || break done [ "$breakout" = "break" ] && break [ "$ZFSBOOT_CONFIRM_LAYOUT" ] || return $FAILURE done return $DIALOG_OK } # zfs_create_diskpart $disk $index # # For each block device to be used in the zpool, rather than just create the # zpool with the raw block devices (e.g., da0, da1, etc.) we create partitions # so we can have some real swap. This also provides wiggle room incase your # replacement drivers do not have the exact same sector counts. # # NOTE: $swapsize and $bootsize should be defined by the calling function. # NOTE: Sets $bootpart and $targetpart for the calling function. # zfs_create_diskpart() { local funcname=zfs_create_diskpart local disk="$1" index="$2" # Check arguments if [ ! "$disk" ]; then f_dprintf "$funcname: NULL disk argument" msg_error="$msg_error: $funcname" \ f_show_err "$msg_null_disk_argument" return $FAILURE fi if [ "${disk#*[$IFS]}" != "$disk" ]; then f_dprintf "$funcname: Invalid disk argument \`%s'" "$disk" msg_error="$msg_error: $funcname" \ f_show_err "$msg_invalid_disk_argument" "$disk" return $FAILURE fi if [ ! "$index" ]; then f_dprintf "$funcname: NULL index argument" msg_error="$msg_error: $funcname" \ f_show_err "$msg_null_index_argument" return $FAILURE fi if ! f_isinteger "$index"; then f_dprintf "$funcname: Invalid index argument \`%s'" "$index" msg_error="$msg_error: $funcname" \ f_show_err "$msg_invalid_index_argument" "$index" return $FAILURE fi f_dprintf "$funcname: disk=[%s] index=[%s]" "$disk" "$index" # Check for unknown partition scheme before proceeding further case "$ZFSBOOT_PARTITION_SCHEME" in ""|MBR|GPT*) : known good ;; *) f_dprintf "$funcname: %s is an unsupported partition scheme" \ "$ZFSBOOT_PARTITION_SCHEME" msg_error="$msg_error: $funcname" f_show_err \ "$msg_unsupported_partition_scheme" \ "$ZFSBOOT_PARTITION_SCHEME" return $FAILURE esac # # Enable boot pool if encryption is desired # [ "$ZFSBOOT_GELI_ENCRYPTION" ] && ZFSBOOT_BOOT_POOL=1 # # ZFSBOOT_BOOT_POOL and BIOS+UEFI boot type are incompatible # if [ "$ZFSBOOT_BOOT_POOL" -a "$ZFSBOOT_BOOT_TYPE" = "BIOS+UEFI" ]; then f_dprintf "$funcname: ZFSBOOT_BOOT_POOL is incompatible with BIOS+UEFI boot type" msg_error="$msg_error: $funcname" f_show_err \ "ZFSBOOT_BOOT_POOL is incompatible with BIOS+UEFI boot type" return $FAILURE fi # # Destroy whatever partition layout is currently on disk. # NOTE: `-F' required to destroy if partitions still exist. # NOTE: Failure is ok here, blank disk will have nothing to destroy. # f_dprintf "$funcname: Destroying all data/layouts on \`%s'..." "$disk" f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" $disk f_eval_catch -d $funcname graid "$GRAID_DELETE" $disk f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" /dev/$disk # Make doubly-sure backup GPT is destroyed f_eval_catch -d $funcname gpart "$GPART_CREATE" gpt $disk f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" $disk # # Lay down the desired type of partition scheme # local setsize mbrindex align_small align_big # # If user has requested 4 K alignment, add these params to the # gpart add calls. With GPT, we align large partitions to 1 M for # improved performance on SSDs. MBR does not always play well with gaps # between partitions, so all alignment is only 4k for that case. # With MBR, we align the BSD partition that contains the MBR, otherwise # the system fails to boot. # if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then align_small="-a 4k" align_big="-a 1m" sysctl kern.geom.part.mbr.enforce_chs=0 fi case "$ZFSBOOT_PARTITION_SCHEME" in ""|GPT*) f_dprintf "$funcname: Creating GPT layout..." # # 1. Create GPT layout using labels # f_eval_catch $funcname gpart "$GPART_CREATE" gpt $disk || return $FAILURE # # Apply workarounds if requested by the user # if [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Lenovo Fix" ]; then f_eval_catch $funcname gpart "$GPART_SET_LENOVOFIX" \ $disk || return $FAILURE elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Active" ]; then f_eval_catch $funcname gpart "$GPART_SET_PMBR_ACTIVE" \ $disk || return $FAILURE fi # # 2. Add small freebsd-boot or efi partition # if [ "$ZFSBOOT_BOOT_TYPE" = "UEFI" -o "$ZFSBOOT_BOOT_TYPE" = "BIOS+UEFI" ]; then f_eval_catch $funcname gpart \ "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \ "$align_small" efiboot$index efi 800k $disk || return $FAILURE f_eval_catch $funcname gpart "$GPART_BOOTCODE_PARTONLY" \ /boot/boot1.efifat 1 $disk || return $FAILURE fi if [ "$ZFSBOOT_BOOT_TYPE" = "BIOS" -o "$ZFSBOOT_BOOT_TYPE" = "BIOS+UEFI" ]; then f_eval_catch $funcname gpart \ "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \ "$align_small" gptboot$index freebsd-boot \ 512k $disk || return $FAILURE if [ "$ZFSBOOT_BOOT_TYPE" = "BIOS" ]; then f_eval_catch $funcname gpart "$GPART_BOOTCODE_PART" \ /boot/pmbr /boot/gptzfsboot 1 $disk || return $FAILURE else f_eval_catch $funcname gpart "$GPART_BOOTCODE_PART" \ /boot/pmbr /boot/gptzfsboot 2 $disk || return $FAILURE fi fi # NB: zpool will use the `zfs#' GPT labels if [ "$ZFSBOOT_BOOT_TYPE" = "BIOS+UEFI" ]; then if [ "$ZFSBOOT_BOOT_POOL" ]; then bootpart=p3 swappart=p4 targetpart=p4 [ ${swapsize:-0} -gt 0 ] && targetpart=p5 else # Bootpart unused bootpart=p3 swappart=p3 targetpart=p3 [ ${swapsize:-0} -gt 0 ] && targetpart=p4 fi else if [ "$ZFSBOOT_BOOT_POOL" ]; then bootpart=p2 swappart=p3 targetpart=p3 [ ${swapsize:-0} -gt 0 ] && targetpart=p4 else # Bootpart unused bootpart=p2 swappart=p2 targetpart=p2 [ ${swapsize:-0} -gt 0 ] && targetpart=p3 fi fi # # Prepare boot pool if enabled (e.g., for geli(8)) # if [ "$ZFSBOOT_BOOT_POOL" ]; then f_eval_catch $funcname gpart \ "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \ "$align_big" boot$index freebsd-zfs \ ${bootsize}b $disk || return $FAILURE # Pedantically nuke any old labels f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/$disk$bootpart if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then # Pedantically detach targetpart for later f_eval_catch -d $funcname geli \ "$GELI_DETACH_F" \ /dev/$disk$targetpart fi fi # # 3. Add freebsd-swap partition labeled `swap#' # if [ ${swapsize:-0} -gt 0 ]; then f_eval_catch $funcname gpart \ "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \ "$align_big" swap$index freebsd-swap \ ${swapsize}b $disk || return $FAILURE # Pedantically nuke any old labels on the swap f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/$disk$swappart fi # # 4. Add freebsd-zfs partition labeled `zfs#' for zroot # f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_LABEL" \ "$align_big" zfs$index freebsd-zfs $disk || return $FAILURE f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/$disk$targetpart ;; MBR) f_dprintf "$funcname: Creating MBR layout..." # # 1. Create MBR layout (no labels) # f_eval_catch $funcname gpart "$GPART_CREATE" mbr $disk || return $FAILURE f_eval_catch $funcname gpart "$GPART_BOOTCODE" /boot/mbr \ $disk || return $FAILURE # # 2. Add freebsd slice with all available space # f_eval_catch $funcname gpart "$GPART_ADD_ALIGN" "$align_small" \ freebsd $disk || return $FAILURE f_eval_catch $funcname gpart "$GPART_SET_ACTIVE" 1 $disk || return $FAILURE # Pedantically nuke any old labels f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/${disk}s1 # Pedantically nuke any old scheme f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" ${disk}s1 # # 3. Write BSD scheme to the freebsd slice # f_eval_catch $funcname gpart "$GPART_CREATE" BSD ${disk}s1 || return $FAILURE # NB: zpool will use s1a (no labels) bootpart=s1a swappart=s1b targetpart=s1d mbrindex=4 # # Always prepare a boot pool on MBR # Do not align this partition, there must not be a gap # ZFSBOOT_BOOT_POOL=1 f_eval_catch $funcname gpart \ "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \ "" 1 freebsd-zfs ${bootsize}b ${disk}s1 || return $FAILURE # Pedantically nuke any old labels f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/$disk$bootpart if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then # Pedantically detach targetpart for later f_eval_catch -d $funcname geli \ "$GELI_DETACH_F" \ /dev/$disk$targetpart fi # # 4. Add freebsd-swap partition # if [ ${swapsize:-0} -gt 0 ]; then f_eval_catch $funcname gpart \ "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \ "$align_small" 2 freebsd-swap ${swapsize}b ${disk}s1 || return $FAILURE # Pedantically nuke any old labels on the swap f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/${disk}s1b fi # # 5. Add freebsd-zfs partition for zroot # f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_INDEX" \ "$align_small" $mbrindex freebsd-zfs ${disk}s1 || return $FAILURE f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ /dev/$disk$targetpart # Pedantic f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \ /boot/zfsboot /dev/${disk}s1 count=1 || return $FAILURE ;; esac # $ZFSBOOT_PARTITION_SCHEME # Update fstab(5) local swapsize f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize if [ "$isswapmirror" ]; then # This is not the first disk in the mirror, do nothing elif [ ${swapsize:-0} -eq 0 ]; then # If swap is 0 sized, don't add it to fstab elif [ "$ZFSBOOT_SWAP_ENCRYPTION" -a "$ZFSBOOT_SWAP_MIRROR" ]; then f_eval_catch $funcname printf "$PRINTF_FSTAB" \ /dev/mirror/swap.eli none swap sw 0 0 \ $BSDINSTALL_TMPETC/fstab || return $FAILURE isswapmirror=1 elif [ "$ZFSBOOT_SWAP_MIRROR" ]; then f_eval_catch $funcname printf "$PRINTF_FSTAB" \ /dev/mirror/swap none swap sw 0 0 \ $BSDINSTALL_TMPETC/fstab || return $FAILURE isswapmirror=1 elif [ "$ZFSBOOT_SWAP_ENCRYPTION" ]; then f_eval_catch $funcname printf "$PRINTF_FSTAB" \ /dev/$disk${swappart}.eli none swap sw 0 0 \ $BSDINSTALL_TMPETC/fstab || return $FAILURE else f_eval_catch $funcname printf "$PRINTF_FSTAB" \ /dev/$disk$swappart none swap sw 0 0 \ $BSDINSTALL_TMPETC/fstab || return $FAILURE fi return $SUCCESS } # zfs_create_boot $poolname $vdev_type $disks ... # # Creates boot pool and dataset layout. Returns error if something goes wrong. # Errors are printed to stderr for collection and display. # zfs_create_boot() { local funcname=zfs_create_boot local zroot_name="$1" local zroot_vdevtype="$2" local zroot_vdevs= # Calculated below local swap_devs= # Calculated below local boot_vdevs= # Used for geli(8) and/or MBR layouts shift 2 # poolname vdev_type local disks="$*" disk local isswapmirror local bootpart targetpart swappart # Set by zfs_create_diskpart() below local create_options # # Pedantic checks; should never be seen # if [ ! "$zroot_name" ]; then f_dprintf "$funcname: NULL poolname" msg_error="$msg_error: $funcname" \ f_show_err "$msg_null_poolname" return $FAILURE fi if [ $# -lt 1 ]; then f_dprintf "$funcname: missing disk arguments" msg_error="$msg_error: $funcname" \ f_show_err "$msg_missing_disk_arguments" return $FAILURE fi f_dprintf "$funcname: poolname=[%s] vdev_type=[%s]" \ "$zroot_name" "$zroot_vdevtype" # # Initialize fstab(5) # f_dprintf "$funcname: Initializing temporary fstab(5) file..." f_eval_catch $funcname sh "$SHELL_TRUNCATE" $BSDINSTALL_TMPETC/fstab || return $FAILURE f_eval_catch $funcname printf "$PRINTF_FSTAB" \ "# Device" Mountpoint FStype Options Dump "Pass#" \ $BSDINSTALL_TMPETC/fstab || return $FAILURE # # Expand SI units in desired sizes # f_dprintf "$funcname: Expanding supplied size values..." local swapsize bootsize if ! f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize; then f_dprintf "$funcname: Invalid swap size \`%s'" \ "$ZFSBOOT_SWAP_SIZE" f_show_err "$msg_invalid_swap_size" "$ZFSBOOT_SWAP_SIZE" return $FAILURE fi if ! f_expand_number "$ZFSBOOT_BOOT_POOL_SIZE" bootsize; then f_dprintf "$funcname: Invalid boot pool size \`%s'" \ "$ZFSBOOT_BOOT_POOL_SIZE" f_show_err "$msg_invalid_boot_pool_size" \ "$ZFSBOOT_BOOT_POOL_SIZE" return $FAILURE fi f_dprintf "$funcname: ZFSBOOT_SWAP_SIZE=[%s] swapsize=[%s]" \ "$ZFSBOOT_SWAP_SIZE" "$swapsize" f_dprintf "$funcname: ZFSBOOT_BOOT_POOL_SIZE=[%s] bootsize=[%s]" \ "$ZFSBOOT_BOOT_POOL_SIZE" "$bootsize" # # Destroy the pool in-case this is our second time 'round (case of # failure and installer presented ``Retry'' option to come back). # # NB: If we don't destroy the pool, later gpart(8) destroy commands # that try to clear existing partitions (see zfs_create_diskpart()) # will fail with a `Device Busy' error, leading to `GEOM exists'. # f_eval_catch -d $funcname zpool "$ZPOOL_DESTROY" "$zroot_name" # # Prepare the disks and build pool device list(s) # f_dprintf "$funcname: Preparing disk partitions for ZFS pool..." # Force 4K sectors using vfs.zfs.min_auto_ashift=12 if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then f_dprintf "$funcname: With 4K sectors..." f_eval_catch $funcname sysctl "$SYSCTL_ZFS_MIN_ASHIFT_12" \ || return $FAILURE fi local n=0 for disk in $disks; do zfs_create_diskpart $disk $n || return $FAILURE # Now $bootpart, $targetpart, and $swappart are set (suffix # for $disk) if [ "$ZFSBOOT_BOOT_POOL" ]; then boot_vdevs="$boot_vdevs $disk$bootpart" fi zroot_vdevs="$zroot_vdevs $disk$targetpart" if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then zroot_vdevs="$zroot_vdevs.eli" fi n=$(( $n + 1 )) done # disks # # If we need/want a boot pool, create it # if [ "$ZFSBOOT_BOOT_POOL" ]; then local bootpool_vdevtype= # Calculated below local bootpool_options= # Calculated below local bootpool_name="$ZFSBOOT_BOOT_POOL_NAME" local bootpool="$BSDINSTALL_CHROOT/$bootpool_name" local zroot_key="${ZFSBOOT_GELI_KEY_FILE#/}" f_dprintf "$funcname: Setting up boot pool..." [ "$ZFSBOOT_GELI_ENCRYPTION" ] && f_dprintf "$funcname: For encrypted root disk..." # Create parent directory for boot pool f_eval_catch -d $funcname umount "$UMOUNT" /mnt f_eval_catch $funcname mount "$MOUNT_TYPE" tmpfs none \ $BSDINSTALL_CHROOT || return $FAILURE # Create mirror across the boot partition on all disks local nvdevs f_count nvdevs $boot_vdevs [ $nvdevs -gt 1 ] && bootpool_vdevtype=mirror create_options="$ZFSBOOT_BOOT_POOL_CREATE_OPTIONS" bootpool_options="-o altroot=$BSDINSTALL_CHROOT" bootpool_options="$bootpool_options $create_options" bootpool_options="$bootpool_options -m \"/$bootpool_name\" -f" f_eval_catch $funcname zpool "$ZPOOL_CREATE_WITH_OPTIONS" \ "$bootpool_options" "$bootpool_name" \ "$bootpool_vdevtype" "$boot_vdevs" || return $FAILURE f_eval_catch $funcname mkdir "$MKDIR_P" "$bootpool/boot" || return $FAILURE if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then # Generate an encryption key using random(4) f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \ /dev/random "$bootpool/$zroot_key" \ "bs=4096 count=1" || return $FAILURE f_eval_catch $funcname chmod "$CHMOD_MODE" \ go-wrx "$bootpool/$zroot_key" || return $FAILURE else # Clean up f_eval_catch $funcname zfs "$ZFS_UNMOUNT" \ "$bootpool_name" || return $FAILURE f_eval_catch -d $funcname umount "$UMOUNT" /mnt # tmpfs fi fi # # Create the geli(8) GEOMS # if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then # Prompt user for password (twice) if ! msg_enter_new_password="$msg_geli_password" \ f_dialog_input_password then f_dprintf "$funcname: User cancelled" f_show_err "$msg_user_cancelled" return $FAILURE fi # Initialize geli(8) on each of the target partitions for disk in $disks; do f_dialog_info "$msg_geli_setup" \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD if ! echo "$pw_password" | f_eval_catch \ $funcname geli "$GELI_PASSWORD_INIT" \ "$bootpool/boot/$disk$targetpart.eli" \ AES-XTS "$bootpool/$zroot_key" \ $disk$targetpart then f_interactive || f_die unset pw_password # Sensitive info return $FAILURE fi if ! echo "$pw_password" | f_eval_catch \ $funcname geli "$GELI_ATTACH" \ "$bootpool/$zroot_key" $disk$targetpart then f_interactive || f_die unset pw_password # Sensitive info return $FAILURE fi done unset pw_password # Sensitive info # Clean up f_eval_catch $funcname zfs "$ZFS_UNMOUNT" "$bootpool_name" || return $FAILURE f_eval_catch -d $funcname umount "$UMOUNT" /mnt # tmpfs fi # # Create the gmirror(8) GEOMS for swap # if [ "$ZFSBOOT_SWAP_MIRROR" ]; then for disk in $disks; do swap_devs="$swap_devs $disk$swappart" done f_eval_catch $funcname gmirror "$SWAP_GMIRROR_LABEL" \ "$swap_devs" || return $FAILURE fi # # Create the ZFS root pool with desired type and disk devices # f_dprintf "$funcname: Creating root pool..." create_options="$ZFSBOOT_POOL_CREATE_OPTIONS" f_eval_catch $funcname zpool "$ZPOOL_CREATE_WITH_OPTIONS" \ "-o altroot=$BSDINSTALL_CHROOT $create_options -m none -f" \ "$zroot_name" "$zroot_vdevtype" "$zroot_vdevs" || return $FAILURE # # Create ZFS dataset layout within the new root pool # f_dprintf "$funcname: Creating ZFS datasets..." echo "$ZFSBOOT_DATASETS" | while read dataset options; do # Skip blank lines and comments case "$dataset" in "#"*|"") continue; esac # Remove potential inline comments in options options="${options%%#*}" # Replace tabs with spaces f_replaceall "$options" " " " " options # Reduce contiguous runs of space to one single space oldoptions= while [ "$oldoptions" != "$options" ]; do oldoptions="$options" f_replaceall "$options" " " " " options done # Replace both commas and spaces with ` -o ' f_replaceall "$options" "[ ,]" " -o " options # Create the dataset with desired options f_eval_catch $funcname zfs "$ZFS_CREATE_WITH_OPTIONS" \ "${options:+-o $options}" "$zroot_name$dataset" || return $FAILURE done # # Set a mountpoint for the root of the pool so newly created datasets # have a mountpoint to inherit # f_dprintf "$funcname: Setting mountpoint for root of the pool..." f_eval_catch $funcname zfs "$ZFS_SET" \ "mountpoint=/$zroot_name" "$zroot_name" || return $FAILURE # Touch up permissions on the tmp directories f_dprintf "$funcname: Modifying directory permissions..." local dir for dir in /tmp /var/tmp; do f_eval_catch $funcname mkdir "$MKDIR_P" \ $BSDINSTALL_CHROOT$dir || return $FAILURE f_eval_catch $funcname chmod "$CHMOD_MODE" 1777 \ $BSDINSTALL_CHROOT$dir || return $FAILURE done # Create symlink(s) if [ "$ZFSBOOT_BOOT_POOL" ]; then f_dprintf "$funcname: Creating /boot symlink for boot pool..." f_eval_catch $funcname ln "$LN_SF" "$bootpool_name/boot" \ $BSDINSTALL_CHROOT/boot || return $FAILURE fi # Set bootfs property local zroot_bootfs="$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME" f_dprintf "$funcname: Setting bootfs property..." f_eval_catch $funcname zpool "$ZPOOL_SET" \ "bootfs=\"$zroot_name/$zroot_bootfs\"" "$zroot_name" || return $FAILURE # Export the pool(s) f_dprintf "$funcname: Temporarily exporting ZFS pool(s)..." f_eval_catch $funcname zpool "$ZPOOL_EXPORT" "$zroot_name" || return $FAILURE if [ "$ZFSBOOT_BOOT_POOL" ]; then f_eval_catch $funcname zpool "$ZPOOL_EXPORT" \ "$bootpool_name" || return $FAILURE fi # MBR boot loader touch-up if [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then f_dprintf "$funcname: Updating MBR boot loader on disks..." - # Stick the ZFS boot loader in the "convienient hole" after + # Stick the ZFS boot loader in the "convenient hole" after # the ZFS internal metadata for disk in $disks; do f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \ /boot/zfsboot /dev/$disk$bootpart \ "skip=1 seek=1024" || return $FAILURE done fi # Re-import the ZFS pool(s) f_dprintf "$funcname: Re-importing ZFS pool(s)..." f_eval_catch $funcname zpool "$ZPOOL_IMPORT_WITH_OPTIONS" \ "-o altroot=\"$BSDINSTALL_CHROOT\"" "$zroot_name" || return $FAILURE if [ "$ZFSBOOT_BOOT_POOL" ]; then f_eval_catch $funcname zpool "$ZPOOL_IMPORT_WITH_OPTIONS" \ "-o altroot=\"$BSDINSTALL_CHROOT\"" \ "$bootpool_name" || return $FAILURE fi # While this is apparently not needed, it seems to help MBR f_dprintf "$funcname: Configuring zpool.cache for zroot..." f_eval_catch $funcname mkdir "$MKDIR_P" $BSDINSTALL_CHROOT/boot/zfs || return $FAILURE f_eval_catch $funcname zpool "$ZPOOL_SET" \ "cachefile=\"$BSDINSTALL_CHROOT/boot/zfs/zpool.cache\"" \ "$zroot_name" || return $FAILURE # Last, but not least... required lines for rc.conf(5)/loader.conf(5) # NOTE: We later concatenate these into their destination f_dprintf "%s: Configuring rc.conf(5)/loader.conf(5) additions..." \ "$funcname" f_eval_catch $funcname echo "$ECHO_APPEND" 'zfs_enable=\"YES\"' \ $BSDINSTALL_TMPETC/rc.conf.zfs || return $FAILURE f_eval_catch $funcname echo "$ECHO_APPEND" \ 'kern.geom.label.disk_ident.enable=\"0\"' \ $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE f_eval_catch $funcname echo "$ECHO_APPEND" \ 'kern.geom.label.gptid.enable=\"0\"' \ $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE if [ "$ZFSBOOT_SWAP_MIRROR" ]; then f_eval_catch $funcname echo "$ECHO_APPEND" \ 'geom_mirror_load=\"YES\"' \ $BSDINSTALL_TMPBOOT/loader.conf.gmirror || return $FAILURE fi # We're all done unless we should go on for boot pool [ "$ZFSBOOT_BOOT_POOL" ] || return $SUCCESS # Set cachefile for boot pool so it auto-imports at system start f_dprintf "$funcname: Configuring zpool.cache for boot pool..." f_eval_catch $funcname zpool "$ZPOOL_SET" \ "cachefile=\"$BSDINSTALL_CHROOT/boot/zfs/zpool.cache\"" \ "$bootpool_name" || return $FAILURE # Some additional geli(8) requirements for loader.conf(5) for option in \ 'zpool_cache_load=\"YES\"' \ 'zpool_cache_type=\"/boot/zfs/zpool.cache\"' \ 'zpool_cache_name=\"/boot/zfs/zpool.cache\"' \ ; do f_eval_catch $funcname echo "$ECHO_APPEND" "$option" \ $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE done f_eval_catch $funcname printf "$PRINTF_CONF" vfs.root.mountfrom \ "\"zfs:$zroot_name/$zroot_bootfs\"" \ $BSDINSTALL_TMPBOOT/loader.conf.root || return $FAILURE # We're all done unless we should go on to do encryption [ "$ZFSBOOT_GELI_ENCRYPTION" ] || return $SUCCESS # # Configure geli(8)-based encryption # f_dprintf "$funcname: Configuring disk encryption..." f_eval_catch $funcname echo "$ECHO_APPEND" 'aesni_load=\"YES\"' \ $BSDINSTALL_TMPBOOT/loader.conf.aesni || return $FAILURE f_eval_catch $funcname echo "$ECHO_APPEND" 'geom_eli_load=\"YES\"' \ $BSDINSTALL_TMPBOOT/loader.conf.geli || return $FAILURE f_eval_catch $funcname echo "$ECHO_APPEND" \ 'geom_eli_passphrase_prompt=\"YES\"' \ $BSDINSTALL_TMPBOOT/loader.conf.geli || return $FAILURE for disk in $disks; do f_eval_catch $funcname printf "$PRINTF_CONF" \ geli_%s_keyfile0_load "$disk$targetpart YES" \ $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart || return $FAILURE f_eval_catch $funcname printf "$PRINTF_CONF" \ geli_%s_keyfile0_type \ "$disk$targetpart $disk$targetpart:geli_keyfile0" \ $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart || return $FAILURE f_eval_catch $funcname printf "$PRINTF_CONF" \ geli_%s_keyfile0_name \ "$disk$targetpart \"$ZFSBOOT_GELI_KEY_FILE\"" \ $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart || return $FAILURE done return $SUCCESS } # dialog_menu_diskinfo # # Prompt the user to select a disk and then provide detailed info on it. # dialog_menu_diskinfo() { local device disk # # Break from loop when user cancels disk selection # while :; do device=$( msg_cancel="$msg_back" f_device_menu \ "$DIALOG_TITLE" "$msg_select_a_disk_device" "" \ $DEVICE_TYPE_DISK 2>&1 ) || break $device get name disk # Show gpart(8) `show' and camcontrol(8) `inquiry' data f_show_msg "$msg_detailed_disk_info" \ "$disk" "$( gpart show $disk 2> /dev/null )" \ "$disk" "$( camcontrol inquiry $disk 2> /dev/null )" \ "$disk" "$( camcontrol identify $disk 2> /dev/null )" done return $SUCCESS } ############################################################ MAIN # # Initialize # f_dialog_title "$msg_zfs_configuration" f_dialog_backtitle "$msg_freebsd_installer" # User may have specifically requested ZFS-related operations be interactive ! f_interactive && f_zfsinteractive && unset $VAR_NONINTERACTIVE # # Debugging # f_dprintf "BSDINSTALL_CHROOT=[%s]" "$BSDINSTALL_CHROOT" f_dprintf "BSDINSTALL_TMPETC=[%s]" "$BSDINSTALL_TMPETC" f_dprintf "FSTAB_FMT=[%s]" "$FSTAB_FMT" # # If the system was booted with UEFI, set the default boot type to UEFI # bootmethod=$( sysctl -n machdep.bootmethod ) f_dprintf "machdep.bootmethod=[%s]" "$bootmethod" if [ "$bootmethod" = "UEFI" ]; then : ${ZFSBOOT_BOOT_TYPE:=BIOS+UEFI} : ${ZFSBOOT_PARTITION_SCHEME:=GPT} else : ${ZFSBOOT_BOOT_TYPE:=BIOS} : ${ZFSBOOT_PARTITION_SCHEME:=GPT} fi # # Loop over the main menu until we've accomplished what we came here to do # while :; do if ! f_interactive; then retval=$DIALOG_OK mtag=">>> $msg_install" else dialog_menu_main retval=$? f_dialog_menutag_fetch mtag fi f_dprintf "retval=%u mtag=[%s]" $retval "$mtag" [ $retval -eq $DIALOG_OK ] || f_die case "$mtag" in ">>> $msg_install") # # First, validate the user's selections # # Make sure they gave us a name for the pool if [ ! "$ZFSBOOT_POOL_NAME" ]; then f_dprintf "Pool name cannot be empty." f_show_err "$msg_pool_name_cannot_be_empty" continue fi # Validate vdev type against number of disks selected/scripted # (also validates that ZFSBOOT_DISKS are real [probed] disks) # NB: dialog_menu_layout supports running non-interactively dialog_menu_layout || continue # Make sure each disk will have room for ZFS if f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize && f_expand_number "$ZFSBOOT_BOOT_POOL_SIZE" bootsize && f_expand_number "1g" zpoolmin then minsize=$(( $swapsize + $zpoolmin )) teeny_disks= [ "$ZFSBOOT_BOOT_POOL" ] && minsize=$(( $minsize + $bootsize )) for disk in $ZFSBOOT_DISKS; do debug= f_device_find -1 \ $disk $DEVICE_TYPE_DISK device $device get capacity disksize || continue [ ${disksize:-0} -ge 0 ] || disksize=0 [ $disksize -lt $minsize ] && teeny_disks="$teeny_disks $disk" done if [ "$teeny_disks" ]; then f_dprintf "swapsize=[%s] bootsize[%s] %s" \ "$ZFSBOOT_SWAP_SIZE" \ "$ZFSBOOT_BOOT_POOL_SIZE" \ "minsize=[$minsize]" f_dprintf "These disks are too small: %s" \ "$teeny_disks" f_show_err "$msg_these_disks_are_too_small" \ "$ZFSBOOT_SWAP_SIZE" \ "$ZFSBOOT_BOOT_POOL_SIZE" \ "$teeny_disks" continue fi fi # # Last Chance! # if f_interactive; then dialog_last_chance $ZFSBOOT_DISKS || continue fi # # Let's do this # vdev_type="$ZFSBOOT_VDEV_TYPE" # Blank the vdev type for the default layout [ "$vdev_type" = "stripe" ] && vdev_type= zfs_create_boot "$ZFSBOOT_POOL_NAME" \ "$vdev_type" $ZFSBOOT_DISKS || continue break # to success ;; ?" $msg_pool_type_disks") ZFSBOOT_CONFIRM_LAYOUT=1 dialog_menu_layout # User has poked settings, disable later confirmation ZFSBOOT_CONFIRM_LAYOUT= ;; "- $msg_rescan_devices") f_device_rescan ;; "- $msg_disk_info") dialog_menu_diskinfo ;; ?" $msg_pool_name") # Prompt the user to input/change the name for the new pool f_dialog_input input \ "$msg_please_enter_a_name_for_your_zpool" \ "$ZFSBOOT_POOL_NAME" && ZFSBOOT_POOL_NAME="$input" ;; ?" $msg_force_4k_sectors") # Toggle the variable referenced both by the menu and later if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then ZFSBOOT_FORCE_4K_SECTORS= else ZFSBOOT_FORCE_4K_SECTORS=1 fi ;; ?" $msg_encrypt_disks") # Toggle the variable referenced both by the menu and later if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then ZFSBOOT_GELI_ENCRYPTION= else ZFSBOOT_FORCE_4K_SECTORS=1 ZFSBOOT_GELI_ENCRYPTION=1 fi ;; ?" $msg_partition_scheme") # Toggle between GPT (BIOS), GPT (UEFI) and MBR if [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT" -a "$ZFSBOOT_BOOT_TYPE" = "BIOS" ]; then ZFSBOOT_PARTITION_SCHEME="GPT" ZFSBOOT_BOOT_TYPE="UEFI" elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT" -a "$ZFSBOOT_BOOT_TYPE" = "UEFI" ]; then ZFSBOOT_PARTITION_SCHEME="GPT" ZFSBOOT_BOOT_TYPE="BIOS+UEFI" elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT" ]; then ZFSBOOT_PARTITION_SCHEME="MBR" ZFSBOOT_BOOT_TYPE="BIOS" elif [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then ZFSBOOT_PARTITION_SCHEME="GPT + Active" ZFSBOOT_BOOT_TYPE="BIOS" elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Active" ]; then ZFSBOOT_PARTITION_SCHEME="GPT + Lenovo Fix" ZFSBOOT_BOOT_TYPE="BIOS" else ZFSBOOT_PARTITION_SCHEME="GPT" ZFSBOOT_BOOT_TYPE="BIOS" fi ;; ?" $msg_swap_size") # Prompt the user to input/change the swap size for each disk while :; do f_dialog_input input \ "$msg_please_enter_amount_of_swap_space" \ "$ZFSBOOT_SWAP_SIZE" && ZFSBOOT_SWAP_SIZE="${input:-0}" if f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize then if [ $swapsize -ne 0 -a $swapsize -lt 104857600 ]; then f_show_err "$msg_swap_toosmall" \ "$ZFSBOOT_SWAP_SIZE" continue; else break; fi else f_show_err "$msg_swap_invalid" \ "$ZFSBOOT_SWAP_SIZE" continue; fi done ;; ?" $msg_swap_mirror") # Toggle the variable referenced both by the menu and later if [ "$ZFSBOOT_SWAP_MIRROR" ]; then ZFSBOOT_SWAP_MIRROR= else ZFSBOOT_SWAP_MIRROR=1 fi ;; ?" $msg_swap_encrypt") # Toggle the variable referenced both by the menu and later if [ "$ZFSBOOT_SWAP_ENCRYPTION" ]; then ZFSBOOT_SWAP_ENCRYPTION= else ZFSBOOT_SWAP_ENCRYPTION=1 fi ;; esac done exit $SUCCESS ################################################################################ # END ################################################################################