diff options
Diffstat (limited to 'usr.sbin/bsdconfig/share')
36 files changed, 15782 insertions, 0 deletions
diff --git a/usr.sbin/bsdconfig/share/Makefile b/usr.sbin/bsdconfig/share/Makefile new file mode 100644 index 0000000..a09697a --- /dev/null +++ b/usr.sbin/bsdconfig/share/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +SUBDIR= media packages + +FILESDIR= ${SHAREDIR}/bsdconfig +FILES= common.subr device.subr dialog.subr geom.subr keymap.subr \ + mustberoot.subr script.subr strings.subr struct.subr \ + sysrc.subr variable.subr + +.include <bsd.prog.mk> diff --git a/usr.sbin/bsdconfig/share/Makefile.depend b/usr.sbin/bsdconfig/share/Makefile.depend new file mode 100644 index 0000000..f80275d --- /dev/null +++ b/usr.sbin/bsdconfig/share/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bsdconfig/share/common.subr b/usr.sbin/bsdconfig/share/common.subr new file mode 100644 index 0000000..5996446 --- /dev/null +++ b/usr.sbin/bsdconfig/share/common.subr @@ -0,0 +1,1047 @@ +if [ ! "$_COMMON_SUBR" ]; then _COMMON_SUBR=1 +# +# Copyright (c) 2012 Ron McDowell +# Copyright (c) 2012-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$ +# +############################################################ 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 + # + [ "$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 + # 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 <output below> +# 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 <output below> +# 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 <output below> +# 321 +# +# Example 4: +# +# debug=1 +# f_eval_catch myfunc true true +# +# Produces the following debug output: +# +# DEBUG: myfunc: true +# DEBUG: myfunc: retval=0 <no output> +# +# 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 <output below>\n%s" "$__funcname" \ + $__retval "$__output" + else + f_dprintf "%s: retval=%i <no output>" "$__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' SIGINT +trap 'f_die' SIGTERM SIGPIPE SIGXCPU SIGXFSZ \ + SIGFPE SIGTRAP SIGABRT SIGSEGV +trap '' SIGALRM SIGPROF SIGUSR1 SIGUSR2 SIGHUP SIGVTALRM + +# +# 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 diff --git a/usr.sbin/bsdconfig/share/device.subr b/usr.sbin/bsdconfig/share/device.subr new file mode 100644 index 0000000..d95684d --- /dev/null +++ b/usr.sbin/bsdconfig/share/device.subr @@ -0,0 +1,1396 @@ +if [ ! "$_DEVICE_SUBR" ]; then _DEVICE_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..." device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/geom.subr +f_include $BSDCFG_SHARE/strings.subr +f_include $BSDCFG_SHARE/struct.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ GLOBALS + +NDEVICES=0 # Set by f_device_register(), used by f_device_*() + +# +# A "device" from legacy sysinstall's point of view (mostly) +# +# NB: Disk devices have their `private' property set to GEOM ident +# NB: USB devices have their `private' property set to USB disk device name +# +f_struct_define DEVICE \ + capacity \ + desc \ + devname \ + enabled \ + flags \ + get \ + init \ + name \ + private \ + shutdown \ + type \ + volume + +# Network devices have their `private' property set to this +f_struct_define DEVICE_INFO \ + extras \ + ipaddr \ + ipv6addr \ + netmask \ + use_dhcp \ + use_rtsol + +# +# Device types for f_device_register(), f_device_find(), et al. +# +setvar DEVICE_TYPE_ANY "any" # Any +setvar DEVICE_TYPE_NONE "NONE" # Unknown +setvar DEVICE_TYPE_DISK "DISK" # GEOM `DISK' +setvar DEVICE_TYPE_FLOPPY "FD" # GEOM `FD' +setvar DEVICE_TYPE_FTP "FTP" # Dynamic network device +setvar DEVICE_TYPE_NETWORK "NETWORK" # See f_device_get_all_network +setvar DEVICE_TYPE_CDROM "CDROM" # GEOM `DISK' +setvar DEVICE_TYPE_USB "USB" # GEOM `PART' +setvar DEVICE_TYPE_DOS "DOS" # GEOM `DISK' `PART' or `LABEL' +setvar DEVICE_TYPE_UFS "UFS" # GEOM `DISK' `PART' or `LABEL' +setvar DEVICE_TYPE_NFS "NFS" # Dynamic network device +setvar DEVICE_TYPE_HTTP_PROXY "HTTP_PROXY" # Dynamic network device +setvar DEVICE_TYPE_HTTP "HTTP" # Dynamic network device + +# Network devices have the following flags available +setvar IF_ETHERNET 1 +setvar IF_WIRELESS 2 +setvar IF_ACTIVE 4 + +# +# Default behavior is to call f_device_get_all() automatically when loaded. +# +: ${DEVICE_SELF_SCAN_ALL=1} + +# +# Device Catalog variables +# +DEVICE_CATALOG_APPEND_ONLY= # Used by f_device_catalog_set() +NCATALOG_DEVICES=0 # Used by f_device_catalog_*() and MAIN + +# +# A ``catalog'' device is for mapping GEOM devices to media devices (for +# example, determining if a $GEOM_CLASS_DISK geom is $DEVICE_TYPE_CDROM or +# $DEVICE_TYPE_DISK) and also getting default descriptions for devices that +# either lack a GEOM provided description or lack a presence in GEOM) +# +f_struct_define CATALOG_DEVICE \ + desc \ + name \ + type + +############################################################ FUNCTIONS + +# f_device_register $var_to_set $name $desc $devname $type $enabled +# $init_function $get_function $shutdown_function +# $private $capacity +# +# Register a device. A `structure' (see struct.subr) is created and if +# $var_to_set is non-NULL, upon success holds the name of the struct created. +# The remaining positional arguments correspond to the properties of the +# `DEVICE' structure-type to be assigned (defined above). +# +# If not already registered (based on $name and $type), a new device is created +# and $NDEVICES is incremented. +# +f_device_register() +{ + local __var_to_set="$1" __name="$2" __desc="$3" __devname="$4" + local __type="$5" __enabled="$6" __init_func="$7" __get_func="$8" + local __shutdown_func="$9" __private="${10}" __capacity="${11}" + + # Required parameter(s) + [ "$__name" ] || return $FAILURE + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" "" || return $FAILURE + fi + + local __device + if f_device_find -1 "$__name" "$__type" __device; then + f_struct_free "$__device" + f_struct_new DEVICE "$__device" || return $FAILURE + else + __device=device_$(( NDEVICES + 1 )) + f_struct_new DEVICE "$__device" || return $FAILURE + NDEVICES=$(( $NDEVICES + 1 )) + fi + $__device set name "$__name" + $__device set desc "$__desc" + $__device set devname "$__devname" + $__device set type "$__type" + $__device set enabled "$__enabled" + $__device set init "$__init_func" + $__device set get "$__get_func" + $__device set shutdown "$__shutdown_func" + $__device set private "$__private" + $__device set capacity "$__capacity" + + [ "$__var_to_set" ] && setvar "$__var_to_set" "$__device" + return $SUCCESS +} + +# f_device_reset +# +# Reset the registered device chain. +# +f_device_reset() +{ + local n=1 + while [ $n -le $NDEVICES ]; do + f_device_shutdown device_$n + + # + # XXX This potentially leaks $dev->private if it's being + # used to point to something dynamic, but you're not supposed + # to call this routine at such times that some open instance + # has its private member pointing somewhere anyway. + # + f_struct_free device_$n + + n=$(( $n + 1 )) + done + NDEVICES=0 +} + +# f_device_reset_network +# +# Reset the registered network device chain. +# +f_device_reset_network() +{ + local n=1 device type private i + while [ $n -le $NDEVICES ]; do + device=device_$n + f_struct $device || continue + $device get type type + [ "$type" = "$DEVICE_TYPE_NETWORK" ] || continue + + # + # Leave the device up (don't call shutdown routine) + # + + # Network devices may have DEVICE_INFO private member + $device get private private + [ "$private" ] && f_struct_free "$private" + + # Free the network device + f_struct_free $device + + # Fill the gap we just created + i=$n + while [ $i -lt $NDEVICES ]; do + f_struct_copy device_$(( $i + 1 )) device_$i + done + f_struct_free device_$NDEVICES + + # Finally decrement the number of devices + NDEVICES=$(( $NDEVICES - 1 )) + + n=$(( $n + 1 )) + done +} + +# f_device_get_all +# +# Get all device information for all devices. +# +f_device_get_all() +{ + local devname type desc capacity + + f_dprintf "f_device_get_all: Probing devices..." + f_dialog_info "$msg_probing_devices_please_wait_this_can_take_a_while" + + # First go for the network interfaces + f_device_get_all_network + + # Next, go for the GEOM devices we might want to use as media + local geom geoms geom_name + debug= f_geom_find "" $GEOM_CLASS_DEV geoms + for geom in $geoms; do + if ! f_device_probe_geom $geom; then + debug= $geom get name geom_name + f_dprintf "WARNING! Unable to classify %s as %s" \ + "GEOM device $geom_name" "media source" + fi + done +} + +# f_device_get_all_network +# +# Get all network device information for attached network devices. +# +f_device_get_all_network() +{ + local devname desc device flags + for devname in $( ifconfig -l ); do + # Eliminate network devices that don't make sense + case "$devname" in + lo*) continue ;; + esac + + # Try and find its description + f_device_desc "$devname" $DEVICE_TYPE_NETWORK desc + + f_dprintf "Found network device named %s" "$devname" + debug= f_device_register device $devname "$desc" \ + "$devname" $DEVICE_TYPE_NETWORK 1 \ + f_media_init_network "" f_media_shutdown_network "" -1 + + # Set flags based on media and status + flags=0 + eval "$( ifconfig $devname 2> /dev/null | awk -v var=flags ' + function _or(var, mask) { + printf "%s=$(( $%s | $%s ))\n", var, var, mask + } + BEGIN { S = "[[:space:]]+" } + { + if (!match($0, "^" S "(media|status):" S)) next + value = substr($0, RLENGTH + 1) + if ($1 == "media:") { + if (value ~ /Ethernet/) _or(var, "IF_ETHERNET") + if (value ~ /802\.11/) _or(var, "IF_WIRELESS") + } else if ($1 == "status:") { + if (value ~ /^active/) _or(var, "IF_ACTIVE") + } + }' )" + $device set flags $flags + done +} + +# f_device_rescan +# +# Rescan all devices, after closing previous set - convenience function. +# +f_device_rescan() +{ + f_device_reset + f_geom_rescan + f_device_get_all +} + +# f_device_rescan_network +# +# Rescan all network devices, after closing previous set - for convenience. +# +f_device_rescan_network() +{ + f_device_reset_network + f_device_get_all_network +} + +# f_device_probe_geom $geom +# +# Probe a single GEOM device and if it can be classified as a media source, +# register it using f_device_register() with known type-specific arguments. +# +f_device_probe_geom() +{ + local geom="$1" + + f_struct "$geom" || return $FAILURE + + # geom associated variables + local geom_name geom_consumer provider_ref geom_provider= + local provider_geom provider_config provider_class= + local provider_config_type catalog_struct catalog_type + local disk_ident + + # gnop(8)/geli(8) associated variables (p for `parent device') + local p_devname p_geom p_consumer p_provider_ref p_provider + local p_provider_config p_provider_geom p_provider_class + + # md(4) associated variables + local config config_type config_file magic= + + # Temporarily disable debugging to keep debug output light + local old_debug="$debug" debug= + + # + # Get the GEOM name (for use below in device registration) + # + $geom get name devname || continue + + # + # Attempt to get the consumer, provider, provider config, and + # provider class for this geom (errors ignored). + # + # NB: Each GEOM in the `DEV' class should have one consumer. + # That consumer should have a reference to its provider. + # + $geom get consumer1 geom_consumer + f_struct "$geom_consumer" get provider_ref provider_ref && + f_geom_find_by id "$provider_ref" provider geom_provider + if f_struct "$geom_provider"; then + $geom_provider get config provider_config + f_geom_parent $geom_provider provider_geom && + f_geom_parent $provider_geom provider_class + fi + + # + # Get values for device registration (errors ignored) + # + f_struct "$provider_class" get name type + f_struct "$geom_provider" get mediasize capacity + f_struct "$provider_config" get descr desc + + # + # For gnop(8), geli(8), or combination thereof, change device type to + # that of the consumer + # + p_devname= p_geom= p_provider= p_provider_config= + case "$devname" in + *.nop.eli) p_devname="${devname%.nop.eli}" ;; + *.eli.nop) p_devname="${devname%.eli.nop}" ;; + *.eli) p_devname="${devname%.eli}" ;; + *.nop) p_devname="${devname%.nop}" ;; + esac + [ "$p_devname" ] && f_geom_find "$p_devname" $GEOM_CLASS_DEV p_geom + if [ "${p_geom:-$geom}" != "$geom" ]; then + f_struct "$p_geom" get consumer1 p_consumer + f_struct "$p_consumer" get provider_ref p_provider_ref && + f_geom_find_by id "$p_provider_ref" provider p_provider + if f_struct "$p_provider"; then + $p_provider get config p_provider_config + f_geom_parent $p_provider p_provider_geom && + f_geom_parent $p_provider_geom p_provider_class + fi + f_struct "$p_provider_class" get name type + fi + + # Look up geom device in device catalog for default description + f_device_catalog_get \ + $DEVICE_TYPE_ANY "${p_devname:-$devname}" catalog_struct + [ "$desc" ] || f_struct "catalog_device_$catalog_struct" get desc desc + + # Use device catalog entry for potential re-classification(s) + f_struct "catalog_device_$catalog_struct" get type catalog_type + + # Restore debugging for this next part (device registration) + debug="$old_debug" + + # + # Register the device + # + local retval device + case "$type" in + $GEOM_CLASS_DISK) + # First attempt to classify by device catalog (see MAIN) + case "$catalog_type" in + $DEVICE_TYPE_CDROM) + f_dprintf "Found CDROM device for disk %s" "$devname" + debug= f_device_register device "$devname" "$desc" \ + "/dev/$devname" $DEVICE_TYPE_CDROM 1 \ + f_media_init_cdrom f_media_get_cdrom \ + f_media_shutdown_cdrom "" "$capacity" && + return $SUCCESS + ;; + esac + + # Fall back to register label device as a disk and taste it + f_dprintf "Found disk device named %s" "$devname" + debug= f_struct "$p_provider_config" get \ + ident disk_ident || + debug= f_struct "$provider_config" get \ + ident disk_ident + debug= f_device_register device "$devname" "$desc" \ + "/dev/$devname" $DEVICE_TYPE_DISK 1 \ + "" "" "" "$disk_ident" "$capacity" + retval=$? + + # Detect ``dangerously dedicated'' filesystems (errors ignored) + f_device_probe_disk_fs device "$devname" "$capacity" && + retval=$SUCCESS + + return $retval + ;; + $GEOM_CLASS_FD) + f_dprintf "Found floppy device named %s" "$devname" + debug= f_device_register device "$devname" "$desc" \ + "/dev/$devname" $DEVICE_TYPE_FLOPPY 1 \ + f_media_init_floppy f_media_get_floppy \ + f_media_shutdown_floppy "" "$capacity" + return $? + ;; + $GEOM_CLASS_LABEL) + : fall through to below section # reduces indentation level + ;; + $GEOM_CLASS_MD) + f_dprintf "Found disk device named %s" "$devname" + debug= f_device_register device "$devname" "$desc" \ + "/dev/$devname" $DEVICE_TYPE_DISK 1 \ + "" "" "" "" "$capacity" + retval=$? + + # + # Attempt to get file(1) magic to potentially classify as + # alternate media type. If unable to get magic, fall back to + # md(4) characteristics (such as vnode filename). + # + [ -r "/dev/$devname" ] && + magic=$( file -bs "/dev/$devname" 2> /dev/null ) + if [ ! "$magic" ]; then + # Fall back to md(4) characteristics + if f_struct "$p_provider_config"; then + config="$p_provider_config" + else + config="$provider_config" + fi + debug= f_struct "$config" get type config_type + debug= f_struct "$config" get file config_file + + # Substitute magic for below based on type and file + case "$config_type=$config_file" in + vnode=*.iso) magic="ISO 9660" ;; + esac + fi + f_device_probe_disk_fs device \ + "$devname" "$capacity" "$magic" && + retval=$SUCCESS # Errors ignored + + return $retval + ;; + $GEOM_CLASS_PART) + if f_struct "$p_provider_config"; then + config="$p_provider_config" + else + config="$provider_config" + fi + debug= f_struct "$config" get type provider_config_type + f_device_probe_geom_part device \ + "$provider_config_type" "$devname" "$capacity" + retval=$? + device_type=$DEVICE_TYPE_NONE + [ $retval -eq $SUCCESS ] && + debug= f_struct "$device" get type device_type + + # Potentially re-classify as USB device + if [ "$device_type" = "$DEVICE_TYPE_UFS" -a \ + "$catalog_type" = "$DEVICE_TYPE_USB" ] + then + f_dprintf "Found USB device for partition %s" \ + "$devname" + debug= f_struct "$p_provider_geom" get \ + name disk_name || + debug= f_struct "$provider_geom" get \ + name disk_name + debug= f_device_register device "$devname" "$desc" \ + "/dev/$devname" $DEVICE_TYPE_USB 1 \ + f_media_init_usb f_media_get_usb \ + f_media_shutdown_usb "$disk_name" "$capacity" + retval=$? + fi + + return $retval + ;; + $GEOM_CLASS_RAID) + # Use the provider geom name as the description + if [ ! "$desc" ]; then + f_struct "$p_provider_geom" get name desc || + f_struct "$provider_geom" get name desc + fi + + f_dprintf "Found disk device named %s" "$devname" + debug= f_device_register device \ + "$devname" "${desc:-GEOM RAID device}" \ + "/dev/$devname" $DEVICE_TYPE_DISK 1 \ + "" "" "" "" "$capacity" + retval=$? + + # Detect ``dangerously dedicated'' filesystems + f_device_probe_disk_fs device "$devname" "$capacity" && + retval=$SUCCESS # Errors ignored + + return $retval + ;; + $GEOM_CLASS_ZFS_ZVOL) + f_dprintf "Found disk device named %s" "$devname" + debug= f_device_register device \ + "$devname" "${desc:-GEOM ZFS::ZVOL device}" \ + "/dev/$devname" $DEVICE_TYPE_DISK 1 \ + "" "" "" "" "$capacity" + retval=$? + + # Detect ``dangerously dedicated'' filesystems + f_device_probe_disk_fs device "$devname" "$capacity" && + retval=$SUCCESS # Errors ignored + + return $retval + ;; + *) + return $FAILURE # Unknown GEOM class + esac + + # + # Still here? Must be $GEOM_CLASS_LABEL + # + + local label_geom label_devname label_devgeom= label_devconsumer + local label_devprovider= label_devprovider_ref label_devprovider_config + local label_gpart_type + + if f_struct "$p_provider"; then + label_geom="$p_provider_geom" + else + label_geom="$provider_geom" + fi + + case "$devname" in + gpt/*|gptid/*) + # + # Attempt to get the partition type by getting the `config' + # member of the provider for our device (which is named in the + # parent geom of our current provider). + # + debug= f_struct "$label_geom" get name label_devname && + debug= f_geom_find "$label_devname" $GEOM_CLASS_DEV \ + label_devgeom + debug= f_struct "$label_devgeom" get \ + consumer1 label_devconsumer + debug= f_struct "$label_devconsumer" get \ + provider_ref label_devprovider_ref && + debug= f_geom_find_by id "$label_devprovider_ref" \ + provider label_devprovider + debug= f_struct "$label_devprovider" get \ + config label_devprovider_config + debug= f_struct "$label_devprovider_config" get \ + type label_gpart_type + + # + # Register device label based on partition type + # + f_device_probe_geom_part device \ + "$label_gpart_type" "$devname" "$capacity" + return $? + ;; + iso9660/*) + f_dprintf "Found CDROM device labeled %s" "$devname" + debug= f_device_register device \ + "$devname" "ISO9660 file system" \ + "/dev/$devname" $DEVICE_TYPE_CDROM 1 \ + f_media_init_cdrom f_media_get_cdrom \ + f_media_shutdown_cdrom "" "$capacity" + return $? + ;; + label/*) + # For generic labels, use provider geom name as real device + debug= f_struct "$label_geom" get name label_devname + + # Look up label geom device in device catalog for default desc + debug= f_device_catalog_get \ + $DEVICE_TYPE_ANY "$label_devname" catalog_struct + [ "$desc" ] || debug= f_struct \ + "catalog_device_$catalog_struct" get desc desc + + # Use device catalog entry for potential re-classification(s) + debug= f_struct "catalog_device_$catalog_struct" get \ + type catalog_type + + # First attempt to classify by device catalog (see MAIN) + case "$catalog_type" in + $DEVICE_TYPE_CDROM) + f_dprintf "Found CDROM device for disk %s" "$devname" + debug= f_device_register device "$devname" "$desc" \ + "/dev/$devname" $DEVICE_TYPE_CDROM 1 \ + f_media_init_cdrom f_media_get_cdrom \ + f_media_shutdown_cdrom "" "$capacity" && + return $SUCCESS + ;; + esac + + # Fall back to register label device as a disk and taste it + f_dprintf "Found disk device labeled %s" "$devname" + debug= f_device_register device \ + "$devname" "GEOM LABEL device" \ + "/dev/$devname" $DEVICE_TYPE_DISK 1 \ + "" "" "" "" "$capacity" + retval=$? + + # Detect ``dangerously dedicated'' filesystems (errors ignored) + f_device_probe_disk_fs device "$devname" "$capacity" && + retval=$SUCCESS + + return $retval + ;; + msdosfs/*) + f_dprintf "Found DOS partition labeled %s" "$devname" + debug= f_device_register device "$devname" "DOS file system" \ + "/dev/$devname" $DEVICE_TYPE_DOS 1 \ + f_media_init_dos f_media_get_dos \ + f_media_shutdown_dos "" "$capacity" + return $? + ;; + ufs/*|ufsid/*) + f_dprintf "Found UFS partition labeled %s" "$devname" + debug= f_device_register device "$devname" "UFS file system" \ + "/dev/$devname" $DEVICE_TYPE_UFS 1 \ + f_media_init_ufs f_media_get_ufs \ + f_media_shutdown_ufs "" "$capacity" + return $? + ;; + ext2fs/*|ntfs/*|reiserfs/*) + return $FAILURE # No media device handlers for these labels + ;; + esac + + # Unable to classify GEOM label + return $FAILURE +} + +# f_device_probe_geom_part $var_to_set $gpart_type $devname $capacity [$magic] +# +# Given a gpart(8) partition type and a device name, register the device if it +# is a known partition type that we can handle. If $var_to_set is non-NULL, +# upon success holds the DEVICE struct name of the registered device. +# +# Returns success if the device was successfully registered, failure otherwise. +# +f_device_probe_geom_part() +{ + local __var_to_set="$1" __gpart_type="$2" __devname="$3" + local __capacity="${4:--1}" __magic="$5" + + # + # Register device based on partition type + # NB: !0 equates to `unused' bsdlabel + # + case "$__gpart_type" in + fat16|fat32) + f_dprintf "Found DOS partition named %s" "$__devname" + debug= f_device_register "$__var_to_set" \ + "$__devname" "DOS file system" \ + "/dev/$__devname" $DEVICE_TYPE_DOS 1 \ + f_media_init_dos f_media_get_dos \ + f_media_shutdown_dos "" "$__capacity" + return $? + ;; + freebsd|!0) # Commonly used inappropriately, taste for FreeBSD + [ -r "/dev/$__devname" -a ! "$__magic" ] && + __magic=$( file -bs "/dev/$__devname" 2> /dev/null ) + case "$__magic" in + *"Unix Fast File system"*) + f_dprintf "Found UFS partition named %s" "$__devname" + debug= f_device_register "$__var_to_set" \ + "$__devname" "UFS file system" \ + "/dev/$__devname" $DEVICE_TYPE_UFS 1 \ + f_media_init_ufs f_media_get_ufs \ + f_media_shutdown_ufs "" "$__capacity" + return $? + esac + return $FAILURE + ;; + freebsd-ufs) + f_dprintf "Found UFS partition named %s" "$__devname" + debug= f_device_register "$__var_to_set" \ + "$__devname" "UFS file system" \ + "/dev/$__devname" $DEVICE_TYPE_UFS 1 \ + f_media_init_ufs f_media_get_ufs \ + f_media_shutdown_ufs "" "$__capacity" + return $? + ;; + apple-*|linux-*|ms-*|netbsd-*|ntfs|vmware-*) + return $FAILURE # No device types for these + ;; + bios-*|ebr|efi|mbr|freebsd-boot|freebsd-swap) + return $FAILURE # Not a source for media + ;; + freebsd-nandfs|freebsd-vinum|freebsd-zfs) + return $FAILURE # Unsupported as media source + ;; + esac + + return $FAILURE # Unknown partition type +} + +# f_device_probe_disk_fs $var_to_set $devname [$capacity [$magic]] +# +# Given a device name, taste it and register the device if it is a so-called +# ``dangerously dedicated'' file system written without a partition table. +# Tasting is done using file(1) (specifically `file -bs') but if $magic is +# present and non-NULL it is used instead. If $var_to_set is non-NULL, upon +# success holds the DEVICE struct name of the registered device. +# +# Returns success if the device was successfully registered, failure otherwise. +# +f_device_probe_disk_fs() +{ + local __var_to_set="$1" __devname="$2" __capacity="${3:--1}" + local __magic="$4" + + [ -r "/dev/${__devname#/dev/}" -a ! "$__magic" ] && + __magic=$( file -bs "/dev/$__devname" 2> /dev/null ) + + case "$__magic" in + *"ISO 9660"*) + f_dprintf "Found CDROM device for disk %s" "$__devname" + debug= f_device_register "$__var_to_set" \ + "$__devname" "ISO9660 file system" \ + "/dev/$__devname" $DEVICE_TYPE_CDROM 1 \ + f_media_init_cdrom f_media_get_cdrom \ + f_media_shutdown_cdrom "" "$__capacity" + return $? + ;; + *"Unix Fast File system"*) + f_dprintf "Found UFS device for disk %s" "$__devname" + debug= f_device_register "$__var_to_set" \ + "$__devname" "UFS file system" \ + "/dev/$__devname" $DEVICE_TYPE_UFS 1 \ + f_media_init_ufs f_media_get_ufs \ + f_media_shutdown_ufs "" "$__capacity" + return $? + ;; + *"FAT (12 bit)"*|*"FAT (16 bit)"*|*"FAT (32 bit)"*) + f_dprintf "Found DOS device for disk %s" "$__devname" + debug= f_device_register "$__var_to_set" \ + "$__devname" "DOS file system" \ + "/dev/$__devname" $DEVICE_TYPE_DOS 1 \ + f_media_init_dos f_media_get_dos \ + f_media_shutdown_dos "" "$__capacity" + return $? + ;; + esac + + return $FAILURE # Unknown file system type +} + +# f_device_catalog_get $type $name [$var_to_set] +# +# Fetch the struct name of the catalog device matching device $name. If $type +# is either NULL, missing, or set to $DEVICE_TYPE_ANY then only $name is used. +# Returns success if a match was found, otherwise failure. +# +# If $var_to_set is missing or NULL, the 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_device_catalog_get() +{ + local __type="$1" __name="$2" __var_to_set="$3" + local __dname= + + # Return failure if no $name + [ "$__name" ] || return $FAILURE + + # Disable debugging to keep debug output light + local debug= + + # + # Attempt to create an alternate-form of $__name that contains the + # first contiguous string of numbers replaced with `%d' for comparison + # against stored pattern names (see MAIN). + # + local __left="${__name%%[0-9]*}" __right="${__name#*[0-9]}" + if [ "$__left" != "$__name" ]; then + # Chop leading digits from right 'til we hit first non-digit + while :; do + case "$__right" in + [0-9]*) __right="${__right#[0-9]}" ;; + *) break + esac + done + __dname="${__left}%d$__right" + fi + + [ "$__type" = "$DEVICE_TYPE_ANY" ] && __type= + local __dev __dev_name __dev_type + for __dev in $DEVICE_CATALOG; do + catalog_device_$__dev get name __dev_name + [ "$__dev_name" = "$__name" -o "$__dev_name" = "$__dname" ] || + continue + catalog_device_$__dev get type __dev_type + [ "${__type:-$__dev_type}" = "$__dev_type" ] || continue + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" $__dev + else + echo $__dev + fi + return $? + done + + [ "$__var_to_set" ] && setvar "$__var_to_set" "" + return $FAILURE +} + +# f_device_catalog_set $type $name $desc +# +# Store a description (desc) in-association with device $type and $name. +# Returns success unless $name is NULL or missing. Use f_device_catalog_get() +# routine with the same $name and optionally $type to retrieve catalog device +# structure (see CATALOG_DEVICE struct definition in GLOBALS section). +# +f_device_catalog_set() +{ + local type="$1" name="$2" desc="$3" + local struct dev dev_type found= + + [ "$name" ] || return $FAILURE + + # Disable debugging to keep debug output light + local debug= + + f_str2varname "$name" struct + if [ ! "$DEVICE_CATALOG_APPEND_ONLY" ]; then + for dev in $DEVICE_CATALOG; do + [ "$dev" = "$struct" ] || continue + found=1 break + done + fi + if [ "$found" ]; then + f_struct_free "catalog_device_$struct" + else + DEVICE_CATALOG="$DEVICE_CATALOG $struct" + fi + f_struct_new CATALOG_DEVICE "catalog_device_$struct" || return $FAILURE + catalog_device_$struct set type "$type" + catalog_device_$struct set name "$name" + catalog_device_$struct set desc "$desc" + return $SUCCESS +} + +# f_device_desc $device_name $device_type [$var_to_set] +# +# Print a description for a device name (eg., `fxp0') given a specific device +# type/class. +# +# If $var_to_set is missing or NULL, the device description 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_device_desc() +{ + local __name="$1" __type="$2" __var_to_set="$3" + local __devname __devunit __cp + + # Check variables + [ "$__name" ] || return $SUCCESS + [ "$__type" = "$DEVICE_TYPE_ANY" ] && type= + [ "$__var_to_set" ] && { setvar "$__var_to_set" "" || return; } + + # + # Return sysctl MIB dev.NAME.UNIT.%desc if it exists, otherwise fall + # through to further alternate methods. + # + if f_have sysctl; then + __devname="${__name%%[0-9]*}" + __devunit="${__name#$__devname}" + __devunit="${__devunit%%[!0-9]*}" + if [ "$__var_to_set" ]; then + if __cp=$( + sysctl -n "dev.$__devname.$__devunit.%desc" \ + 2> /dev/null + ); then + setvar "$__var_to_set" "$__cp" && + return $SUCCESS + fi + else + sysctl -n "dev.$__devname.$__devunit.%desc" \ + 2> /dev/null && return $SUCCESS + fi + fi + + # Look up device in catalog for default description + local __catalog_struct + debug= f_device_catalog_get "$__type" "$__name" __catalog_struct + debug= f_struct "catalog_device_$__catalog_struct" get \ + desc "$__var_to_set" && return $SUCCESS + + # + # Sensible fall-backs for specific types + # + case "$__type" in + $DEVICE_TYPE_CDROM) __cp="<unknown cdrom device type>" ;; + $DEVICE_TYPE_DISK) __cp="<unknown disk device type>" ;; + $DEVICE_TYPE_FLOPPY) __cp="<unknown floppy device type>" ;; + $DEVICE_TYPE_USB) __cp="<unknown USB storage device type>" ;; + $DEVICE_TYPE_NETWORK) __cp="<unknown network interface type>" ;; + *) + __cp="<unknown device type>" + esac + + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" "$__cp" + else + echo "$__cp" + fi + + return $FAILURE +} + +# f_device_is_ethernet $device +# +# Returns true if $device is a wired Ethernet network interface. Otherwise +# returns false. Example wired interfaces include: fxp0 em0 bge0 rl0 etc. +# +f_device_is_ethernet() +{ + local dev="$1" type flags + + # Make sure we have an actual device by that name + f_struct "$dev" || return $FAILURE + + # Make sure that the device is a network device + $dev get type type + [ "$type" = "$DEVICE_TYPE_NETWORK" ] || return $FAILURE + + # Make sure that the media flags indicate that it is Ethernet + $dev get flags flags + [ $(( ${flags:-0} & $IF_ETHERNET )) -eq $IF_ETHERNET ] +} + +# f_device_is_wireless $device +# +# Returns true if $device is a Wireless network interface. Otherwise returns +# false. Examples of wireless interfaces include: iwn0 +# +f_device_is_wireless() +{ + local dev="$1" type flags + + # Make sure we have an actual device by that name + f_struct "$dev" || return $FAILURE + + # Make sure that the device is a network device + $dev get type type + [ "$type" = "$DEVICE_TYPE_NETWORK" ] || return $FAILURE + + # Make sure that the media flags indicate that it is 802.11 wireless + $dev get flags flags + [ $(( ${flags:-0} & $IF_WIRELESS )) -eq $IF_WIRELESS ] +} + +# f_device_is_active $device +# +# Returns true if $device is active. Otherwise returns false. Currently this +# only works for network interfaces. +# +f_device_is_active() +{ + local dev="$1" type flags=0 + + # Make sure we have an actual device by that name + f_struct "$dev" || return $FAILURE + + $dev get type type + case "$type" in + $DEVICE_TYPE_NETWORK) + # Make sure that the media flags indicate that it is active + $dev get flags flags + [ $(( ${flags:-0} & $IF_ACTIVE )) -eq $IF_ACTIVE ] + ;; + *) + return $FAILURE + esac +} + +# f_device_find [-1] $name [$type [$var_to_set]] +# +# Find one or more registered devices by name, type, or both. Returns a space- +# separated list of devices matching the search criterion. +# +# If `-1' option flag is given, only the first matching device is returned. +# +# If $var_to_set is missing or NULL, the device 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_device_find() +{ + local OPTIND OPTARG flag only_one= + while getopts 1 flag; do + case "$flag" in + 1) only_one=1 ;; + esac + done + shift $(( $OPTIND - 1 )) + + local __name="$1" __type="${2:-$DEVICE_TYPE_ANY}" __var_to_set="$3" + local __n=1 __devname __devtype __found= + while [ $__n -le $NDEVICES ]; do + device_$__n get name __devname + device_$__n get type __devtype + if [ "$__name" = "$__devname" -o ! "$__name" ] && + [ "$__type" = "$DEVICE_TYPE_ANY" -o \ + "$__type" = "$__devtype" ] + then + __found="$__found device_$__n" + [ "$only_one" ] && break + fi + __n=$(( $__n + 1 )) + done + + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" "${__found# }" + else + echo $__found + fi + [ "$__found" ] # Return status +} + +# f_device_init $device +# +# Initialize a device by evaluating its `init' function. The $device argument +# is a DEVICE struct name. +# +f_device_init() +{ + local device="$1" init_func + f_struct "$device" || return $? + $device get init init_func + ${init_func:-:} "$device" +} + +# f_device_get $device $file [$probe] +# +# Read $file by evaluating the device's `get' function. The file is commonly +# produced on standard output (but it truly depends on the function called). +# The $device argument is a DEVICE struct name. +# +f_device_get() +{ + local device="$1" file="$2" probe="$3" get_func + f_struct "$device" || return $? + $device get get get_func + ${get_func:-:} "$device" "$file" ${3+"$probe"} +} + +# f_device_shutdown $device +# +# Shutdown a device by evaluating its `shutdown' function. The $device argument +# is a DEVICE struct name. +# +f_device_shutdown() +{ + local device="$1" shutdown_func + f_struct "$device" || return $? + $device get shutdown shutdown_func + ${shutdown_func:-:} "$device" +} + +# f_devices_sort_by $property $var_to_get [$var_to_set] +# +# Take list of devices from $var_to_get (separated by whitespace, newline +# included) and sort them by $property (e.g., `name'). The sorted list of +# DEVICE struct names is returned on standard output separated by whitespace +# (newline to be specific) unless $var_to_set is present and non-NULL. +# +# 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_device_sort_by_awk=' +# Variables that should be defined on the invocation line: +# -v prop="property" +function _asorti(src, dest) +{ + k = nitems = 0 + for (i in src) dest[++nitems] = i + 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 +} +{ + split($0, devs, FS) + for (d in devs) { + name = ENVIRON["_struct_value_" devs[d] "_" prop] + devices[name] = devs[d] + } +} +END { + nitems = _asorti(devices, devices_sorted) + for (i = 1; i <= nitems; i++) print devices[devices_sorted[i]] +} +' +f_device_sort_by() +{ + local __property="${1:-name}" __var_to_get="$2" __var_to_set="$3" + + f_isset "$__var_to_get" || return $FAILURE + + local __dev + for __dev in $( f_getvar "$__var_to_get" ); do + export _struct_value_${__dev}_$__property + done + + local __cp + setvar "${__var_to_set:-__cp}" "$( + f_getvar "$__var_to_get" | + awk -v prop="$__property" "$f_device_sort_by_awk" + )" + [ "$__var_to_set" ] || echo "$__cp" +} + +# f_device_menu $title $prompt $hline $device_type [$helpfile] +# +# Display a menu listing all the devices of a certain type in the system. +# +f_device_menu() +{ + f_dialog_title "$1" + local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE" + f_dialog_title_restore + + local prompt="$2" hline="$3" type="$4" helpfile="$5" + + local devs + f_device_find "" "$type" devs || return $DIALOG_CANCEL + + local name desc menu_list= + f_device_sort_by name devs devs + for dev in $devs; do + $dev get name name + $dev get desc desc + f_shell_escape "$name" name + f_shell_escape "$desc" desc + menu_list="$menu_list + '$name' '$desc'" # END-QUOTE + done + menu_list="${menu_list#$NL}" + + local height width rows + eval f_dialog_menu_size height width rows \ + \"\$title\" \ + \"\$btitle\" \ + \"\$prompt\" \ + \"\$hline\" \ + $menu_list + + local errexit= + case $- in *e*) errexit=1; esac + set +e + + local mtag + while :; do + mtag=$( eval $DIALOG \ + --title \"\$title\" \ + --backtitle \"\$btitle\" \ + --hline \"\$hline\" \ + --ok-label \"\$msg_ok\" \ + --cancel-label \"\$msg_cancel\" \ + ${helpfile:+ \ + --help-button \ + --help-label \"\$msg_help\" \ + ${USE_XDIALOG:+--help \"\"} \ + } \ + --menu \"\$prompt\" \ + $height $width $rows \ + $menu_list \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) + local retval=$? + + [ $retval -ne $DIALOG_HELP ] && break + # Otherwise, the Help button was pressed + f_show_help "$helpfile" + # ...then loop back to menu + done + f_dprintf "retval=%u mtag=[%s]" $retval "$mtag" + + [ "$errexit" ] && set -e + + if [ $retval -eq $DIALOG_OK ]; then + # Clean up the output of [X]dialog(1) + f_dialog_data_sanitize mtag + + # Map the user's choice back to a struct name + local index device + index=$( eval f_dialog_menutag2index \"\$mtag\" $menu_list ) + device=$( set -- $devs; eval echo \${$index} ) + + echo "$device" >&2 + fi + + return $retval +} + +# +# Short-hand +# +f_cdrom() { f_device_catalog_set $DEVICE_TYPE_CDROM "$1" "$2"; } +f_disk() { f_device_catalog_set $DEVICE_TYPE_DISK "$1" "$2"; } +f_floppy() { f_device_catalog_set $DEVICE_TYPE_FLOPPY "$1" "$2"; } +f_usb() { f_device_catalog_set $DEVICE_TYPE_USB "$1" "$2"; } +f_network() { f_device_catalog_set $DEVICE_TYPE_NETWORK "$1" "$2"; } + +############################################################ MAIN + +# +# The below classifications allow us to re-group the GEOM devices from the +# `DEV' GEOM class appropriately while providing fall-back descriptions both +# for making the below code more maintainable and handling the rare case the +# GEOM device lacks a description. +# + +DEVICE_CATALOG_APPEND_ONLY=1 # Make initial loading faster + +# CDROM, Disk, Floppy, and USB devices/names +f_cdrom "cd%d" "SCSI CDROM drive" +f_cdrom "mcd%d" "Mitsumi (old model) CDROM drive" +f_cdrom "scd%d" "Sony CDROM drive - CDU31/33A type" +f_disk "aacd%d" "Adaptec FSA RAID array" +f_disk "ada%d" "ATA/SATA disk device" +f_disk "amrd%d" "AMI MegaRAID drive" +f_disk "da%d" "SCSI disk device" +f_disk "idad%d" "Compaq RAID array" +f_disk "ipsd%d" "IBM ServeRAID RAID array" +f_disk "md%d" "md(4) disk device" +f_disk "mfid%d" "LSI MegaRAID SAS array" +f_disk "mlxd%d" "Mylex RAID disk" +f_disk "twed%d" "3ware ATA RAID array" +f_disk "vtbd%d" "VirtIO Block Device" +f_floppy "fd%d" "Floppy Drive unit A" +f_usb "da%da" "USB Mass Storage Device" + +# Network interfaces/names +f_network "ae%d" "Attansic/Atheros L2 Fast Ethernet" +f_network "age%d" "Attansic/Atheros L1 Gigabit Ethernet" +f_network "alc%d" "Atheros AR8131/AR8132 PCIe Ethernet" +f_network "ale%d" "Atheros AR8121/AR8113/AR8114 PCIe Ethernet" +f_network "an%d" "Aironet 4500/4800 802.11 wireless adapter" +f_network "ath%d" "Atheros IEEE 802.11 wireless adapter" +f_network "aue%d" "ADMtek USB Ethernet adapter" +f_network "axe%d" "ASIX Electronics USB Ethernet adapter" +f_network "bce%d" "Broadcom NetXtreme II Gigabit Ethernet card" +f_network "bfe%d" "Broadcom BCM440x PCI Ethernet card" +f_network "bge%d" "Broadcom BCM570x PCI Gigabit Ethernet card" +f_network "bm%d" "Apple BMAC Built-in Ethernet" +f_network "bwn%d" "Broadcom BCM43xx IEEE 802.11 wireless adapter" +f_network "cas%d" "Sun Cassini/Cassini+ or NS DP83065 Saturn Ethernet" +f_network "cc3i%d" "SDL HSSI sync serial PCI card" +f_network "cue%d" "CATC USB Ethernet adapter" +f_network "cxgb%d" "Chelsio T3 10Gb Ethernet card" +f_network "dc%d" "DEC/Intel 21143 (and clones) PCI Fast Ethernet card" +f_network "de%d" "DEC DE435 PCI NIC or other DC21040-AA based card" +f_network "disc%d" "Software discard network interface" +f_network "ed%d" "Novell NE1000/2000; 3C503; NE2000-compatible PCMCIA" +f_network "el%d" "3Com 3C501 Ethernet card" +f_network "em%d" "Intel(R) PRO/1000 Ethernet card" +f_network "en%d" "Efficient Networks ATM PCI card" +f_network "ep%d" "3Com 3C509 Ethernet card/3C589 PCMCIA" +f_network "et%d" "Agere ET1310 based PCI Express Gigabit Ethernet card" +f_network "ex%d" "Intel EtherExpress Pro/10 Ethernet card" +f_network "fe%d" "Fujitsu MB86960A/MB86965A Ethernet card" +f_network "fpa%d" "DEC DEFPA PCI FDDI card" +f_network "fwe%d" "FireWire Ethernet emulation" +f_network "fwip%d" "IP over FireWire" +f_network "fxp%d" "Intel EtherExpress Pro/100B PCI Fast Ethernet card" +f_network "gem%d" "Apple GMAC or Sun ERI/GEM Ethernet adapter" +f_network "hme%d" "Sun HME (Happy Meal Ethernet) Ethernet adapter" +f_network "ie%d" "AT&T StarLAN 10 and EN100; 3Com 3C507; NI5210" +f_network "igb%d" "Intel(R) PRO/1000 PCI Express Gigabit Ethernet card" +f_network "ipw%d" "Intel PRO/Wireless 2100 IEEE 802.11 adapter" +f_network "iwi%d" "Intel PRO/Wireless 2200BG/2225BG/2915ABG adapter" +f_network "iwn%d" "Intel Wireless WiFi Link 4965AGN IEEE 802.11n adapter" +f_network "ix%d" "Intel Etherexpress Ethernet card" +f_network "ixgb%d" "Intel(R) PRO/10Gb Ethernet card" +f_network "ixgbe%d" "Intel(R) PRO/10Gb Ethernet card" +f_network "jme%d" "JMicron JMC250 Gigabit/JMC260 Fast Ethernet" +f_network "kue%d" "Kawasaki LSI USB Ethernet adapter" +f_network "le%d" "AMD Am7900 LANCE or Am79C9xx PCnet Ethernet adapter" +f_network "lge%d" "Level 1 LXT1001 Gigabit Ethernet card" +f_network "lnc%d" "Lance/PCnet (Isolan/Novell NE2100/NE32-VL) Ethernet" +f_network "lo%d" "Loop-back (local) network interface" +f_network "lp%d" "Parallel Port IP (PLIP) peer connection" +f_network "malo%d" "Marvell Libertas 88W8335 802.11 wireless adapter" +f_network "msk%d" "Marvell/SysKonnect Yukon II Gigabit Ethernet" +f_network "mxge%d" "Myricom Myri10GE 10Gb Ethernet card" +f_network "nfe%d" "NVIDIA nForce MCP Ethernet" +f_network "ng%d" "Vimage netgraph(4) bridged Ethernet device" +f_network "nge%d" "NatSemi PCI Gigabit Ethernet card" +f_network "nve%d" "NVIDIA nForce MCP Ethernet" +f_network "nxge%d" "Neterion Xframe 10GbE Server/Storage adapter" +f_network "pcn%d" "AMD Am79c79x PCI Ethernet card" +f_network "plip%d" "Parallel Port IP (PLIP) peer connection" +f_network "ral%d" "Ralink Technology IEEE 802.11 wireless adapter" +f_network "ray%d" "Raytheon Raylink 802.11 wireless adapter" +f_network "re%d" "RealTek 8139C+/8169/8169S/8110S PCI Ethernet adapter" +f_network "rl%d" "RealTek 8129/8139 PCI Ethernet card" +f_network "rue%d" "RealTek USB Ethernet card" +f_network "rum%d" "Ralink Technology USB IEEE 802.11 wireless adapter" +f_network "sf%d" "Adaptec AIC-6915 PCI Ethernet card" +f_network "sge%d" "Silicon Integrated Systems SiS190/191 Ethernet" +f_network "sis%d" "SiS 900/SiS 7016 PCI Ethernet card" +f_network "sk%d" "SysKonnect PCI Gigabit Ethernet card" +f_network "sn%d" "SMC/Megahertz Ethernet card" +f_network "snc%d" "SONIC Ethernet card" +f_network "sr%d" "SDL T1/E1 sync serial PCI card" +f_network "ste%d" "Sundance ST201 PCI Ethernet card" +f_network "stge%d" "Sundance/Tamarack TC9021 Gigabit Ethernet" +f_network "ti%d" "Alteon Networks PCI Gigabit Ethernet card" +f_network "tl%d" "Texas Instruments ThunderLAN PCI Ethernet card" +f_network "tx%d" "SMC 9432TX Ethernet card" +f_network "txp%d" "3Com 3cR990 Ethernet card" +f_network "uath%d" "Atheros AR5005UG and AR5005UX USB wireless adapter" +f_network "upgt%d" "Conexant/Intersil PrismGT USB wireless adapter" +f_network "ural%d" "Ralink Technology RT2500USB 802.11 wireless adapter" +f_network "urtw%d" "Realtek 8187L USB wireless adapter" +f_network "vge%d" "VIA VT612x PCI Gigabit Ethernet card" +f_network "vlan%d" "IEEE 802.1Q VLAN network interface" +f_network "vr%d" "VIA VT3043/VT86C100A Rhine PCI Ethernet card" +f_network "vx%d" "3COM 3c590 / 3c595 Ethernet card" +f_network "wb%d" "Winbond W89C840F PCI Ethernet card" +f_network "wi%d" "Lucent WaveLAN/IEEE 802.11 wireless adapter" +f_network "wpi%d" "Intel 3945ABG IEEE 802.11 wireless adapter" +f_network "wx%d" "Intel Gigabit Ethernet (82452) card" +f_network "xe%d" "Xircom/Intel EtherExpress Pro100/16 Ethernet card" +f_network "xl%d" "3COM 3c90x / 3c90xB PCI Ethernet card" +f_network "zyd%d" "ZyDAS ZD1211/ZD1211B USB 802.11 wireless adapter" + +DEVICE_CATALOG_APPEND_ONLY= # Additional loading modifies existing devices + +f_count NCATALOG_DEVICES $DEVICE_CATALOG +f_dprintf "%s: Initialized device catalog with %u names/descriptions." \ + device.subr $NCATALOG_DEVICES + +# +# Scan for the above devices unless requeted otherwise +# +f_dprintf "%s: DEVICE_SELF_SCAN_ALL=[%s]" device.subr "$DEVICE_SELF_SCAN_ALL" +case "$DEVICE_SELF_SCAN_ALL" in +""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;; +*) f_device_get_all +esac + +f_dprintf "%s: Successfully loaded." device.subr + +fi # ! $_DEVICE_SUBR diff --git a/usr.sbin/bsdconfig/share/dialog.subr b/usr.sbin/bsdconfig/share/dialog.subr new file mode 100644 index 0000000..d7c2d2c --- /dev/null +++ b/usr.sbin/bsdconfig/share/dialog.subr @@ -0,0 +1,2341 @@ +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. +# +# 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 +# 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 + __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 + __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 )) + [ $duration -gt 1 ] && 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 diff --git a/usr.sbin/bsdconfig/share/geom.subr b/usr.sbin/bsdconfig/share/geom.subr new file mode 100644 index 0000000..912a5c7 --- /dev/null +++ b/usr.sbin/bsdconfig/share/geom.subr @@ -0,0 +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 +# 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("</" type ">.*$", "", data) + return data + } + ############################################### OPENING PATTERNS + $1 == "<mesh>" { 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 == "<config>" && mesh { create_config() } + ############################################### PROPERTIES + $1 ~ /^<[[:alnum:]]+>/ { + prop = $1 + sub(/^</, "", prop); sub(/>.*/, "", prop) + set_value(prop, extract_data(prop)) + } + ############################################### CLOSING PATTERNS + $1 ~ "^</(consumer|provider|config)>$" { cur_struct-- } + $1 == "</geom>" { + set_value("nconsumers", struct_count["consumer"]) + set_value("nproviders", struct_count["provider"]) + cur_struct-- + struct_count["consumer"] = 0 + struct_count["provider"] = 0 + } + $1 == "</class>" { + set_value("ngeoms", struct_count["geom"]) + cur_struct-- + struct_count["consumer"] = 0 + struct_count["provider"] = 0 + struct_count["geom"] = 0 + } + $1 == "</mesh>" { + 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 diff --git a/usr.sbin/bsdconfig/share/keymap.subr b/usr.sbin/bsdconfig/share/keymap.subr new file mode 100644 index 0000000..7f2f87c --- /dev/null +++ b/usr.sbin/bsdconfig/share/keymap.subr @@ -0,0 +1,263 @@ +if [ ! "$_KEYMAP_SUBR" ]; then _KEYMAP_SUBR=1 +# +# 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..." keymap.subr +f_include $BSDCFG_SHARE/struct.subr + +############################################################ CONFIGURATION + +# +# Defaults taken from usr.sbin/kbdmap/kbdmap.h +# +: ${DEFAULT_LANG:=en} +: ${DEFAULT_KEYMAP_DIR:=/usr/share/syscons/keymaps} + +############################################################ GLOBALS + +KEYMAPS= +NKEYMAPS=0 + +# A "keymap" from kbdmap's point of view +f_struct_define KEYMAP \ + desc \ + keym \ + mark + +# +# Default behavior is to call f_keymap_get_all() automatically when loaded. +# +: ${KEYMAP_SELF_SCAN_ALL=1} + +############################################################ FUNCTIONS + +# f_keymap_register $name $desc $keym $mark +# +# Register a keymap. A `structure' (see struct.subr) is created with the name +# keymap_$name (so make sure $name contains only alpha-numeric characters or +# the underscore, `_'). The remaining arguments after $name correspond to the +# propertise of the `KEYMAP' structure-type (defined above). +# +# If not already registered, the keymap is then appended to the KEYMAPS +# environment variable, a space-separated list of all registered keymaps. +# +f_keymap_register() +{ + local name="$1" desc="$2" keym="$3" mark="$4" + + f_struct_new KEYMAP "keymap_$name" || return $FAILURE + keymap_$name set desc "$desc" + keymap_$name set keym "$keym" + keymap_$name set mark "$mark" + + # Scan our global register to see if needs ammending + local k found= + for k in $KEYMAPS; do + [ "$k" = "$name" ] || continue + found=1 && break + done + [ "$found" ] || KEYMAPS="$KEYMAPS $name" + + return $SUCCESS +} + +# f_keymap_checkfile $keymap +# +# Check that $keymap is a readable kbdmap(5) file. Returns success if $keymap +# is a file, is readable, and exists in $DEFAULT_KEYMAP_DIR; otherwise failure. +# If debugging is enabled, an appropriate debug error message is printed if +# $keymap is not available. +# +f_keymap_checkfile() +{ + local keym="$1" + + # Fixup keymap if it doesn't already contain at least one `/' + [ "${keym#*/}" = "$keym" ] && keym="$DEFAULT_KEYMAP_DIR/$keym" + + # Short-cuts + [ -f "$keym" -a -r "$keym" ] && return $SUCCESS + f_debugging || return $FAILURE + + # Print an appropriate debug error message + if [ ! -e "$keym" ]; then + f_dprintf "%s: No such file or directory" "$keym" + elif [ ! -f "$keym" ]; then + f_dprintf "%s: Not a file!" "$keym" + elif [ ! -r "$keym" ]; then + f_dprintf "%s: Permission denied" "$keym" + fi + + return $FAILURE +} + +# f_keymap_get_all +# +# Get all keymap information for kbdmap(5) entries both in the database and +# loosely existing in $DEFAULT_KEYMAP_DIR. +# +f_keymap_get_all() +{ + local fname=f_keymap_get_all + local lang="${LC_ALL:-${LC_CTYPE:-${LANG:-$DEFAULT_LANG}}}" + [ "$lang" = "C" ] && lang="$DEFAULT_LANG" + + f_dprintf "%s: Looking for keymap files..." $fname + f_dialog_info "$msg_looking_for_keymap_files" + f_dprintf "DEFAULT_LANG=[%s]" "$DEFAULT_LANG" + + eval "$( awk -F: -v lang="$lang" -v lang_default="$DEFAULT_LANG" ' + BEGIN { + # en_US.ISO8859-1 -> en_..\.ISO8859-1 + dialect = lang + if (length(dialect) >= 6 && + substr(dialect, 3, 1) == "_") + dialect = substr(dialect, 1, 3) ".." \ + substr(dialect, 6) + printf "f_dprintf \"dialect=[%%s]\" \"%s\";\n", dialect + + # en_US.ISO8859-1 -> en + lang_abk = lang + if (length(lang_abk) >= 3 && + substr(lang_abk, 3, 1) == "_") + lang_abk = substr(lang_abk, 1, 2) + printf "f_dprintf \"lang_abk=[%%s]\" \"%s\";\n", + lang_abk + } + function find_token(buffer, token) + { + if (split(buffer, tokens, /,/) == 0) return 0 + found = 0 + for (t in tokens) + if (token == tokens[t]) { found = 1; break } + return found + } + function add_keymap(desc,mark,keym) + { + marks[keym] = mark + name = keym + gsub(/[^[:alnum:]_]/, "_", name) + gsub(/'\''/, "'\''\\'\'\''", desc); + printf "f_keymap_checkfile %s && " \ + "f_keymap_register %s '\'%s\'' %s %u\n", + keym, name, desc, keym, mark + } + !/^[[:space:]]*(#|$)/ { + sub(/^[[:space:]]*/, "", $0) + keym = $1 + if (keym ~ /^(MENU|FONT)$/) next + lg = ($2 == "" ? lang_default : $2) + + # Match the entry and store the type of match we made + # as the mark value (so that if we make a better match + # later on with a higher mark, it overwrites previous) + + mark = marks[keym]; + if (find_token(lg, lang)) + add_keymap($3, 4, keym) # Best match + else if (mark <= 3 && find_token(lg, dialect)) + add_keymap($3, 3, keym) + else if (mark <= 2 && find_token(lg, lang_abk)) + add_keymap($3, 2, keym) + else if (mark <= 1 && find_token(lg, lang_default)) + add_keymap($3, 1, keym) + else if (mark <= 0) + add_keymap($3, 0, keym) + } + ' "$DEFAULT_KEYMAP_DIR/INDEX.${DEFAULT_KEYMAP_DIR##*/}" )" + + + # + # Look for keymaps not in database + # + local direntry keym name + set +f # glob + for direntry in "$DEFAULT_KEYMAP_DIR"/*; do + [ "${direntry##*.}" = ".kbd" ] || continue + keym="${direntry##*/}" + f_str2varname "$keym" name + f_struct keymap_$name && continue + f_keymap_checkfile "$keym" && + f_keymap_register $name "${keym%.*}" "$keym" 0 + f_dprintf "%s: not in kbdmap(5) database" "$keym" + done + + # + # Sort the items by their descriptions + # + f_dprintf "%s: Sorting keymap entries by description..." $fname + KEYMAPS=$( + for k in $KEYMAPS; do + echo -n "$k " + # NOTE: Translate '8x8' to '8x08' before sending to + # sort(1) so that things work out as we might expect. + debug= keymap_$k get desc | awk 'gsub(/8x8/,"8x08")||1' + done | sort -k2 | awk '{ + printf "%s%s", (started ? " " : ""), $1; started = 1 + }' + ) + + return $SUCCESS +} + +# f_keymap_kbdcontrol $keymap +# +# Install keyboard map file from $keymap. +# +f_keymap_kbdcontrol() +{ + local keymap="$1" + + [ "$keymap" ] || return $SUCCESS + + # Fixup keymap if it doesn't already contain at least one `/' + [ "${keymap#*/}" = "$keymap" ] && keymap="$DEFAULT_KEYMAP_DIR/$keymap" + + [ "$USE_XDIALOG" ] || kbdcontrol -l "$keymap" +} + +############################################################ MAIN + +# +# Scan for keymaps unless requeted otherwise +# +f_dprintf "%s: KEYMAP_SELF_SCAN_ALL=[%s]" keymap.subr "$KEYMAP_SELF_SCAN_ALL" +case "$KEYMAP_SELF_SCAN_ALL" in +""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;; +*) f_keymap_get_all +esac + +f_count NKEYMAPS $KEYMAPS +f_dprintf "%s: Found %u keymap file(s)." keymap.subr $NKEYMAPS + +f_dprintf "%s: Successfully loaded." keymap.subr + +fi # ! $_KEYMAP_SUBR diff --git a/usr.sbin/bsdconfig/share/media/Makefile b/usr.sbin/bsdconfig/share/media/Makefile new file mode 100644 index 0000000..135515b --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +FILESDIR= ${SHAREDIR}/bsdconfig/media +FILES= any.subr cdrom.subr common.subr directory.subr dos.subr \ + floppy.subr ftp.subr http.subr httpproxy.subr network.subr \ + nfs.subr options.subr tcpip.subr ufs.subr usb.subr + +.include <bsd.prog.mk> diff --git a/usr.sbin/bsdconfig/share/media/Makefile.depend b/usr.sbin/bsdconfig/share/media/Makefile.depend new file mode 100644 index 0000000..f80275d --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bsdconfig/share/media/any.subr b/usr.sbin/bsdconfig/share/media/any.subr new file mode 100644 index 0000000..516d2d2 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/any.subr @@ -0,0 +1,149 @@ +if [ ! "$_MEDIA_ANY_SUBR" ]; then _MEDIA_ANY_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/any.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/cdrom.subr +f_include $BSDCFG_SHARE/media/directory.subr +f_include $BSDCFG_SHARE/media/dos.subr +f_include $BSDCFG_SHARE/media/floppy.subr +f_include $BSDCFG_SHARE/media/ftp.subr +f_include $BSDCFG_SHARE/media/http.subr +f_include $BSDCFG_SHARE/media/httpproxy.subr +f_include $BSDCFG_SHARE/media/nfs.subr +f_include $BSDCFG_SHARE/media/options.subr +f_include $BSDCFG_SHARE/media/ufs.subr +f_include $BSDCFG_SHARE/media/usb.subr +f_include $BSDCFG_SHARE/struct.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +MEDIA_HELPFILE=$BSDCFG_LIBE/include/media.hlp + +############################################################ FUNCTIONS + +# f_media_get_type +# +# Prompt the user to select amongst the known media types (included above). +# +# If the user does not cancel or press Esc, invokes the f_media_set_* function +# associated with the chosen media type. If after all that we have a struct +# named `device_media' then success is returned, otherwise failure. +# +# NOTE: The f_media_set_* function should create the `device_media' struct. +# See `struct.subr' and the above `media/*.subr' includes for more details. +# +f_media_get_type() +{ + f_dialog_title "$msg_choose_installation_media" + local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE" + f_dialog_title_restore + local prompt="$msg_choose_installation_media_description" + local menu_list=" + '1 $msg_cd_dvd' '$msg_install_from_a_freebsd_cd_dvd' + '2 $msg_ftp' '$msg_install_from_an_ftp_server' + '3 $msg_http_proxy' + '$msg_install_from_an_ftp_server_thru_proxy' + '4 $msg_http_direct' '$msg_install_from_an_http_server' + '5 $msg_directory' '$msg_install_from_the_existing_filesystem' + '6 $msg_nfs' '$msg_install_over_nfs' + '7 $msg_dos' '$msg_install_from_a_dos_partition' + '8 $msg_ufs' '$msg_install_from_a_ufs_partition' + '9 $msg_floppy' '$msg_install_from_a_floppy_disk_set' + 'A $msg_usb' '$msg_install_from_a_usb_drive' + 'X $msg_options' '$msg_view_set_various_media_options' + " # END-QUOTE + local hline="$hline_choose_help_for_more_information_on_media_types" + + local height width rows + eval f_dialog_menu_size height width rows \ + \"\$title\" \ + \"\$btitle\" \ + \"\$prompt\" \ + \"\$hline\" \ + $menu_list + + local mtag + while :; do + mtag=$( eval $DIALOG \ + --title \"\$title\" \ + --backtitle \"\$btitle\" \ + --hline \"\$hline\" \ + --ok-label \"\$msg_ok\" \ + --cancel-label \"\$msg_cancel\" \ + --help-button \ + --help-label \"\$msg_help\" \ + ${USE_XDIALOG:+--help \"\"} \ + --menu \"\$prompt\" \ + $height $width $rows \ + $menu_list \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) + local retval=$? + f_dialog_data_sanitize mtag + f_dprintf "retval=%s mtag=[%s]" $retval "$mtag" + + if [ $retval -eq $DIALOG_HELP ]; then + f_show_help "$MEDIA_HELPFILE" + continue + elif [ $retval -ne $DIALOG_OK ]; then + return $FAILURE + fi + + case "$mtag" in + ?" $msg_cd_dvd") f_media_set_cdrom ;; + ?" $msg_ftp") f_media_set_ftp ;; + ?" $msg_http_proxy") f_media_set_http_proxy ;; + ?" $msg_http_direct") f_media_set_http ;; + ?" $msg_directory") f_media_set_directory ;; + ?" $msg_dos") f_media_set_dos ;; + ?" $msg_nfs") f_media_set_nfs ;; + ?" $msg_ufs") f_media_set_ufs ;; + ?" $msg_floppy") f_media_set_floppy ;; + ?" $msg_usb") f_media_set_usb ;; + ?" $msg_options") + f_media_options_menu + continue + ;; + esac + break + done + + f_struct device_media || return $FAILURE +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/any.subr + +fi # ! $_MEDIA_ANY_SUBR diff --git a/usr.sbin/bsdconfig/share/media/cdrom.subr b/usr.sbin/bsdconfig/share/media/cdrom.subr new file mode 100644 index 0000000..bbbd638 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/cdrom.subr @@ -0,0 +1,217 @@ +if [ ! "$_MEDIA_CDROM_SUBR" ]; then _MEDIA_CDROM_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/cdrom.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.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 + +############################################################ GLOBALS + +CDROM_MOUNTED= +CDROM_PREVIOUSLY_MOUNTED= +CDROM_INIT_QUIET= + +############################################################ FUNCTIONS + +# f_media_set_cdrom +# +# Return success if we both found and set the media type to be a CD. +# +f_media_set_cdrom() +{ + f_media_close + + local devs ndevs + f_device_find "" $DEVICE_TYPE_CDROM devs + f_count ndevs $devs + + if [ ${ndevs:=0} -eq 0 ]; then + f_interactive && f_show_msg "$msg_no_cd_dvd_devices_found" + return $FAILURE + elif [ $ndevs -eq 1 ]; then + f_struct_copy $devs device_media + else + local dev + local title="$msg_choose_a_cd_dvd_type" + local prompt="$msg_please_select_a_cd_dvd_drive" + local hline= + + dev=$( f_device_menu \ + "$title" "$prompt" "$hline" $DEVICE_TYPE_CDROM \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || + return $FAILURE + + f_struct_copy "$dev" device_media + fi + + f_struct device_media || return $FAILURE +} + +# f_media_init_cdrom $device +# +# Initializes the CDROM media device. Returns success if able to mount the CD +# device using mount_cd9660(8). +# +f_media_init_cdrom() +{ + local funcname=f_media_init_cdrom + local dev="$1" devname err + + f_struct "$dev" get devname devname || return $FAILURE + f_dprintf "Init routine called for CDROM device. devname=[%s]" \ + "$devname" + + if [ "$CDROM_MOUNTED" ]; then + f_dprintf "CDROM device already mounted." + return $SUCCESS + fi + + if [ ! -e "$MOUNTPOINT" ]; then + f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" || + return $FAILURE + fi + + if ! f_eval_catch -dk err $funcname mount_cd9660 \ + 'mount_cd9660 "%s" "%s"' "$devname" "$MOUNTPOINT" + then + err="${err#mount_cd9660: }"; err="${err#$devname: }" + case "$err" in + "Device busy") + # Perhaps the CDROM drive is already mounted as /cdrom + if f_mounted /cdrom; then + CDROM_PREVIOUSLY_MOUNTED=1 + MOUNTPOINT=/cdrom + err= + fi + ;; + esac + case "$err" in + "") : good ;; # no error + *) + [ "$CDROM_INIT_QUIET" ] || + f_show_msg "$msg_error_mounting_device" \ + "$devname" "$MOUNTPOINT" "$err" + return $FAILURE + esac + fi + CDROM_MOUNTED=1 + + : xxx # /cdrom.inf has been deprecated since 9.0-R + + # No other CDROM media validation at this time + + return $SUCCESS +} + +# f_media_get_cdrom $device $file [$probe_type] +# +# Returns data from $file on a mounted CDROM device. Similar to cat(1). If +# $probe_type is present and non-NULL, returns success if $file exists. If +# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to +# standard-out. +# +f_media_get_cdrom() +{ + local dev="$1" file="$2" probe_type="$3" + local name + + $dev get name name + f_dprintf "f_media_get_cdrom: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type" +} + +# f_media_shutdown_cdrom $device +# +# Shuts down the CDROM device. Return status should be ignored. +# +f_media_shutdown_cdrom() +{ + local funcname=f_media_shutdown_cdrom + local dev="$1" err + + [ "$CDROM_MOUNTED" ] || return $FAILURE + + if [ "$CDROM_PREVIOUSLY_MOUNTED" ]; then + CDROM_MOUNTED= + return $SUCCESS + fi + + if ! f_eval_catch -dk err $funcname umount \ + 'umount -f "%s"' "$MOUNTPOINT" + then + err="${err#umount: }"; err="${err#*: }" + f_show_msg "$msg_could_not_unmount_the_cdrom_dvd" \ + "$MOUNTPOINT" "$err" + else + CDROM_MOUNTED= + fi +} + +# f_media_eject_cdrom $device +# +# Eject the media from the CDROM device. Returns success. +# +f_media_eject_cdrom() +{ + local funcname=f_media_eject_cdrom + local dev="$1" name devname err + + f_struct "$dev" || return $SUCCESS + $dev get name name || return $SUCCESS + $dev get devname devname || return $SUCCESS + + # Don't eject labels + case "$name" in */*) return $SUCCESS; esac + + f_dprintf "Ejecting CDROM/DVD at %s" "$devname" + if ! f_eval_catch -dk err $funcname cdcontrol \ + 'cdcontrol -f "%s" eject' "$devname" + then + f_dprintf "Could not eject the CDROM/DVD from %s: %s" \ + "$devname" "${err#cdcontrol: }" + fi + return $SUCCESS +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/cdrom.subr + +fi # ! $_MEDIA_CDROM_SUBR diff --git a/usr.sbin/bsdconfig/share/media/common.subr b/usr.sbin/bsdconfig/share/media/common.subr new file mode 100644 index 0000000..0bd420d --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/common.subr @@ -0,0 +1,155 @@ +if [ ! "$_MEDIA_COMMON_SUBR" ]; then _MEDIA_COMMON_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/common.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/media/any.subr +f_include $BSDCFG_SHARE/struct.subr + +############################################################ GLOBALS + +# +# Where to mount media +# +MOUNTPOINT=/dist + +# +# Media probe values to use for `f_media_get_TYPE media $file $PROBE' or +# `f_device_get device_media $file $PROBE' (where $PROBE is one of the below +# values). +# +PROBE_EXIST=1 +PROBE_SIZE=2 + +############################################################ FUNCTIONS + +# f_media_open +# +# Returms success if able to initialize the media device. +# +f_media_open() +{ + f_dprintf "f_media_open: Verifying and initiliazing media device" + { # Verify and initialize device media if-defined + f_struct device_media && + f_media_verify && + f_device_init device_media + } || return $FAILURE +} + +# f_media_close +# +# Shuts down the media device, see f_device_shutdown() from device.subr for +# more details. +# +f_media_close() +{ + f_dprintf "f_media_close: Shutting down media device" + f_struct device_media && + f_device_shutdown device_media + f_struct_free device_media +} + +# f_media_verify +# +# Returns success if the media device is available, and if not, prompts the +# user to select a media type. See f_media_get_type() from media/any.subr for +# more details. +# +f_media_verify() +{ + f_dprintf "f_media_verify: Verifying media device" + f_struct device_media || f_media_get_type +} + +# f_media_generic_get $base $file [$probe_type] +# +# A generic open which follows a well-known "path" of places to look. If +# $probe_type is present and non-NULL, returns success if $file exists. If +# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to +# standard-out. +# +f_media_generic_get() +{ + local funcname=f_media_generic_get + local base="$1" file="$2" probe_type="$3" + + local fname=f_media_generic_get + f_dprintf "%s: base=[%s] files=[%s] probe_type=%s" \ + $fname "$base" "$file" "$probe_type" + + local rel path + f_getvar $VAR_RELNAME rel + for path in \ + "$base/$file" \ + "$base/FreeBSD/$file" \ + "$base/releases/$file" \ + "$base/$rel/$file" \ + ; do + if [ -f "$path" -a -r "$path" ]; then + f_dprintf "%s: file exists path=[%s]" $fname "$path" + if [ "$probe_type" = "$PROBE_SIZE" ]; then + local size + f_eval_catch -dk size $funcname stat \ + 'stat -f %%z "%s"' "$path" || size=-1 + f_isinteger "$size" || size=-1 + echo $size + fi + [ "$probe_type" ] && return $SUCCESS + cat "$path" + return $? + fi + done + + path="$base/releases/$rel/$file" # Final path to try + if [ -f "$path" -a -r "$path" ]; then + f_dprintf "%s: file exists path=[%s]" $fname "$path" + if [ "$probe_type" = "$PROBE_SIZE" ]; then + local size + f_eval_catch -dk size $funcname stat \ + 'stat -f %%z "%s"' "$path" || size=-1 + f_isinteger "$size" || size=-1 + echo $size + fi + [ "$probe_type" ] && return $SUCCESS + elif [ "$probe_type" ]; then + [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1" + return $FAILURE + fi + cat "$base/releases/$rel/$file" # Final path to try +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/common.subr + +fi # ! $_MEDIA_COMMON_SUBR diff --git a/usr.sbin/bsdconfig/share/media/directory.subr b/usr.sbin/bsdconfig/share/media/directory.subr new file mode 100644 index 0000000..004cb74 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/directory.subr @@ -0,0 +1,151 @@ +if [ ! "$_MEDIA_DIRECTORY_SUBR" ]; then _MEDIA_DIRECTORY_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/directory.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.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 + +############################################################ GLOBALS + +DIRECTORY_CHECKED= + +############################################################ FUNCTIONS + +# f_media_set_directory +# +# Return success if we both found and set the media type to be a local +# directory. +# +# Variables from variable.subr that can be used to script user input: +# +# VAR_DIRECTORY_PATH +# Path to an existing directory containing the FreeBSD +# distribution files. +# +f_media_set_directory() +{ + local path + + f_media_close + + f_variable_get_value $VAR_DIRECTORY_PATH \ + "$msg_enter_a_fully_qualified_pathname_for_the_directory" + f_getvar $VAR_DIRECTORY_PATH path + [ "$path" ] || return $FAILURE + + f_struct_new DEVICE device_directory + device_directory set name "$path" + device_directory set get f_media_get_directory + device_directory set init f_media_init_directory + device_directory set shutdown f_media_shutdown_directory + device_directory set private "$path" + + f_struct_copy device_directory device_media + f_struct_free device_directory + + f_struct device_media || return $FAILURE +} + +# f_media_init_directory $device +# +# Initializes the Directory media device. Returns success if the directory path +# both exists and is a directory. +# +f_media_init_directory() +{ + local dev="$1" path + + $dev get private path || return $FAILURE + f_dprintf "Init routine called for Directory device. path=[%s]" \ + "$path" + + # Track whether we've been through here before (for remote filesystems + # mounted in the directory path, not repeating these queries saves us + # valuable time for slow/uncooperative links). + if [ "$DIRECTORY_CHECKED" ]; then + f_dprintf "Directory device already checked." + return $SUCCESS + fi + + if [ ! -e "$path" ]; then + f_show_msg "$msg_no_such_file_or_directory" \ + "f_media_init_directory" "$path" + return $FAILURE + elif [ ! -d "$path" ]; then + f_show_msg "$msg_not_a_directory" \ + "f_media_init_directory" "$path" + return $FAILURE + fi + DIRECTORY_CHECKED=1 + return $SUCCESS +} + +# f_media_get_directory $device $file [$probe_type] +# +# Returns data from $file in the existing/current filesystem. Similar to +# cat(1). If $probe_type is present and non-NULL, returns success if $file +# exists. If $probe_type is equal to $PROBE_SIZE, prints the size of $file in +# bytes to standard-out. +# +f_media_get_directory() +{ + local dev="$1" file="$2" probe_type="$3" path + local name + + $dev get name name + f_dprintf "f_media_get_directory: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + $dev get private path + f_media_generic_get "$path" "$file" "$probe_type" +} + +# f_media_shutdown_directory $device +# +# Shuts down the Directory device. Return status should be ignored. +# +f_media_shutdown_directory() +{ + DIRECTORY_CHECKED= +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/directory.subr + +fi # ! $_MEDIA_DIRECTORY_SUBR diff --git a/usr.sbin/bsdconfig/share/media/dos.subr b/usr.sbin/bsdconfig/share/media/dos.subr new file mode 100644 index 0000000..df91aeb --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/dos.subr @@ -0,0 +1,165 @@ +if [ ! "$_MEDIA_DOS_SUBR" ]; then _MEDIA_DOS_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/dos.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.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 + +############################################################ GLOBALS + +DOS_MOUNTED= + +############################################################ FUNCTIONS + +# f_media_set_dos +# +# Return success if we both found and set the media type to be a DOS partition. +# +f_media_set_dos() +{ + f_media_close + + local devs ndevs + f_device_find "" $DEVICE_TYPE_DOS devs + f_count ndevs $devs + + if [ ${ndevs:=0} -eq 0 ]; then + f_show_msg "$msg_no_dos_primary_partitions_found" + return $FAILURE + elif [ $ndevs -eq 1 ]; then + f_struct_copy $devs device_media + else + local dev + local title="$msg_choose_a_dos_partition" + local prompt="$msg_please_select_dos_partition" + local hline= + + dev=$( f_device_menu \ + "$title" "$prompt" "$hline" $DEVICE_TYPE_DOS \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || + return $FAILURE + + f_struct_copy "$dev" device_media + fi + + f_struct device_media || return $FAILURE +} + +# f_media_init_dos $device +# +# Initializes the DOS media device. Returns success if able to mount the DOS +# partition device using mount_msdosfs(8). +# +f_media_init_dos() +{ + local funcname=f_media_init_dos + local dev="$1" devname err + + $dev get devname devname || return $FAILURE + f_dprintf "Init routine called for DOS device. devname=[%s]" \ + "$devname" + + if [ "$DOS_MOUNTED" ]; then + f_dprintf "DOS device already mounted." + return $SUCCESS + fi + + if [ ! -e "$MOUNTPOINT" ]; then + f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" || + return $FAILURE + fi + + if ! f_eval_catch -dk err $funcname mount_msdosfs \ + 'mount_msdosfs "%s" "%s"' "$devname" "$MOUNTPOINT" + then + err="${err#mount_msdosfs: }"; err="${err#$devname: }" + f_show_msg "$msg_error_mounting_device" \ + "$devname" "$MOUNTPOINT" "$err" + return $FAILURE + fi + DOS_MOUNTED=1 + return $SUCCESS +} + +# f_media_get_dos $device $file [$probe_type] +# +# Returns data from $file on a mounted DOS partition device. Similar to cat(1). +# If $probe_type is present and non-NULL, returns success if $file exists. If +# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to +# standard-out. +# +f_media_get_dos() +{ + local dev="$1" file="$2" probe_type="$3" + local name + + $dev get name name + f_dprintf "f_media_get_dos: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type" +} + +# f_media_shutdown_dos $device +# +# Shuts down the DOS partition device using umount(8). Return status should be +# ignored. +# +f_media_shutdown_dos() +{ + local funcname=f_media_shutdown_dos + local dev="$1" err + + [ "$DOS_MOUNTED" ] || return $FAILURE + + if ! f_eval_catch -dk err $funcname umount \ + 'umount -f "%s"' "$MOUNTPOINT" + then + err="${err#umount: }"; err="${err#*: }" + f_show_msg "$msg_could_not_unmount_the_dos_partition" \ + "$MOUNTPOINT" "$err" + else + DOS_MOUNTED= + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/dos.subr + +fi # ! $_MEDIA_DOS_SUBR diff --git a/usr.sbin/bsdconfig/share/media/floppy.subr b/usr.sbin/bsdconfig/share/media/floppy.subr new file mode 100644 index 0000000..bf402e5 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/floppy.subr @@ -0,0 +1,229 @@ +if [ ! "$_MEDIA_FLOPPY_SUBR" ]; then _MEDIA_FLOPPY_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/floppy.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.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 + +############################################################ GLOBALS + +FLOPPY_MOUNTED= +FLOPPY_DISTWANTED= + +############################################################ FUNCTIONS + +# f_media_set_floppy +# +# Return success if we both found and set the media type to be a floppy. +# +f_media_set_floppy() +{ + f_media_close + + local devs ndevs + f_device_find "" $DEVICE_TYPE_FLOPPY devs + f_count ndevs $devs + + if [ ${ndevs:=0} -eq 0 ]; then + f_interactive && f_show_msg "$msg_no_floppy_devices_found" + return $FAILURE + elif [ $ndevs -eq 1 ]; then + f_struct_copy $devs device_media + else + local dev + local title="$msg_choose_a_floppy_drive" + local prompt="$msg_please_select_a_floppy_drive" + local hline= + + dev=$( f_device_menu \ + "$title" "$prompt" "$hline" $DEVICE_TYPE_FLOPPY \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || + return $FAILURE + + f_struct_copy "$dev" device_media + fi + + f_struct device_media && + device_media unset private + + f_struct device_media || return $FAILURE +} + +# f_media_init_floppy $device +# +# Initializes the Floppy media device. Returns success if able to mount the +# Floppy disk device using either mount_msdosfs(8) or mount(8) (tried in that +# order). +# +f_media_init_floppy() +{ + local funcname=f_media_init_floppy + local dev="$1" devname err + + $dev get devname devname || return $FAILURE + f_dprintf "Init floppy called for %s distribution. devname=[%s]" \ + "${FLOPPY_DISTWANTED:-some}" "$devname" + + if [ "$FLOPPY_MOUNTED" ]; then + f_dprintf "Floppy device already mounted." + return $SUCCESS + fi + + local mp + $dev get private mp + if [ ! -e "${mp:=$MOUNTPOINT}" ] && ! f_quietly mkdir -p "$mp"; then + f_show_msg "$msg_unable_to_make_directory_mountpoint" \ + "$mp" "$devname" + return $FAILURE + fi + + if f_interactive; then + local desc + $dev get desc desc + if [ "$FLOPPY_DISTWANTED" ]; then + f_show_msg "$msg_please_insert_floppy_in_drive" "$desc" + else + f_show_msg "$msg_please_insert_floppy_containing" \ + "$FLOPPY_DISTWANTED" "$desc" + fi + fi + + if ! { + f_eval_catch -dk err $funcname mount_msdosfs \ + 'mount_msdosfs -o ro -m 0777 -u 0 -g 0 "%s" "%s"' \ + "$devname" "$mp" || + f_eval_catch -dk err $funcname mount \ + 'mount -o ro "%s" "%s"' "$devname" "$mp" + }; then + err="${err#mount: }"; err="${err#*: }" + local name + $dev get name name + f_show_msg "$msg_error_mounting_floppy_device" \ + "$name" "$devname" "$mp" "$err" + return $FAILURE + fi + FLOPPY_MOUNTED=1 + FLOPPY_DISTWANTED= + return $SUCCESS +} + +# f_media_get_floppy $device $file [$probe_type] +# +# Returns data from $file on a mounted Floppy disk device. Similar to cat(1). +# If $probe_type is present and non-NULL, limits retries to zero and returns +# success if $file exists. If $probe_type is equal to $PROBE_SIZE, prints the +# size of $file in bytes to standard-out. +# +f_media_get_floppy() +{ + local funcname=f_media_get_floppy + local dev="$1" file="$2" probe_type="$3" + local name + + $dev get name name + f_dprintf "f_media_get_floppy: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + # + # floppies don't use f_media_generic_get() because it's too expensive + # to speculatively open files on a floppy disk. Make user get it + # right or give up with floppies. + # + local mp + $dev get private mp + local fp="${mp:=$MOUNTPOINT}/$file" + if ! [ -f "$fp" -a -r "$fp" ]; then + local nretries=4 + [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1" + [ "$probe_type" ] && return $FAILURE + while ! [ -f "$fp" -a -r "$fp" ]; do + if [ $nretries -eq 0 ]; then + f_show_msg "$msg_failed_to_get_floppy_file" \ + "$fp" + [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1" + return $FAILURE + fi + FLOPPY_DISTWANTED="$fp" + f_media_shutdown_floppy "$dev" + f_media_init_floppy "$dev" || return $FAILURE + nretries=$(( $nretries - 1 )) + done + fi + # + # If we reach here, $file exists + # + if [ "$probe_type" = "$PROBE_SIZE" ]; then + local size + f_eval_catch -dk size $funcname stat 'stat -f %%z "%s"' "$fp" + f_isinteger "$size" || size=-1 + echo "$size" + fi + [ "$probe_type" ] && return $SUCCESS + cat "$fp" +} + +# f_media_shutdown_floppy $device +# +# Shuts down the Floppy disk device using umount(8). Return status should be +# ignored. +# +f_media_shutdown_floppy() +{ + local funcname=f_media_shutdown_floppy + local dev="$1" err mp + + [ "$FLOPPY_MOUNTED" ] || return $FAILURE + + $dev get private mp + if f_eval_catch -d $funcname umount \ + 'umount -f "%s"' "${mp:=$MOUNTPOINT}" + then + FLOPPY_MOUNTED= + if f_interactive && [ "$_systemState" != "fixit" ]; then + local desc + $dev get desc desc + f_show_msg "$msg_you_may_remove_the_floppy" "$desc" + fi + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/floppy.subr + +fi # ! $_MEDIA_FLOPPY_SUBR diff --git a/usr.sbin/bsdconfig/share/media/ftp.subr b/usr.sbin/bsdconfig/share/media/ftp.subr new file mode 100644 index 0000000..a249a01 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/ftp.subr @@ -0,0 +1,911 @@ +if [ ! "$_MEDIA_FTP_SUBR" ]; then _MEDIA_FTP_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/ftp.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.subr +f_include $BSDCFG_SHARE/media/tcpip.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 + +############################################################ GLOBALS + +FTP_SKIP_RESOLV= + +URL_MAX=261261 # according to actual fetch(1) test-results + +FTP_DIRS=" + . + releases/$UNAME_P + snapshots/$UNAME_P + pub/FreeBSD + pub/FreeBSD/releases/$UNAME_P + pub/FreeBSD/snapshots/$UNAME_P + pub/FreeBSD-Archive/old-releases/$UNAME_P +" # END-QUOTE + +############################################################ FUNCTIONS + +# f_dialog_menu_media_ftp +# +# Prompt the user to select from a range of ``built-in'' FTP servers or specify +# their own. If the user makes a choice and doesn't cancel or press Esc, stores +# the user's choice in VAR_FTP_PATH (see variables.subr) and returns success. +# +f_dialog_menu_media_ftp() +{ + f_dialog_title "$msg_please_select_a_freebsd_ftp_distribution_site" + local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE" + f_dialog_title_restore + local prompt="$msg_please_select_the_site_closest_to_you_or_other" + local menu_list=" + '$msg_main_site' 'ftp.freebsd.org' + 'URL' '$msg_specify_some_other_ftp_site' + 'IPv6 $msg_main_site' 'ftp.freebsd.org' + ' IPv6 $msg_france' 'ftp4.fr.freebsd.org' + ' IPv6 $msg_france #8' 'ftp8.fr.freebsd.org' + ' IPv6 $msg_ireland' 'ftp3.ie.freebsd.org' + ' IPv6 $msg_japan' 'ftp2.jp.freebsd.org' + ' IPv6 $msg_sweden' 'ftp4.se.freebsd.org' + ' IPv6 $msg_usa' 'ftp4.us.freebsd.org' + ' IPv6 $msg_turkey' 'ftp2.tr.freebsd.org' + '$msg_primary' 'ftp1.freebsd.org' + ' $msg_primary #2' 'ftp2.freebsd.org' + ' $msg_primary #3' 'ftp3.freebsd.org' + ' $msg_primary #4' 'ftp4.freebsd.org' + ' $msg_primary #5' 'ftp5.freebsd.org' + ' $msg_primary #6' 'ftp6.freebsd.org' + ' $msg_primary #7' 'ftp7.freebsd.org' + ' $msg_primary #10' 'ftp10.freebsd.org' + ' $msg_primary #11' 'ftp11.freebsd.org' + ' $msg_primary #12' 'ftp12.freebsd.org' + ' $msg_primary #13' 'ftp13.freebsd.org' + ' $msg_primary #14' 'ftp14.freebsd.org' + '$msg_armenia' 'ftp1.am.freebsd.org' + '$msg_australia' 'ftp.au.freebsd.org' + ' $msg_australia #2' 'ftp2.au.freebsd.org' + ' $msg_australia #3' 'ftp3.au.freebsd.org' + '$msg_austria' 'ftp.at.freebsd.org' + '$msg_brazil' 'ftp2.br.freebsd.org' + ' $msg_brazil #3' 'ftp3.br.freebsd.org' + ' $msg_brazil #4' 'ftp4.br.freebsd.org' + '$msg_canada' 'ftp.ca.freebsd.org' + '$msg_china' 'ftp.cn.freebsd.org' + '$msg_czech_republic' 'ftp.cz.freebsd.org' + '$msg_denmark' 'ftp.dk.freebsd.org' + '$msg_estonia' 'ftp.ee.freebsd.org' + '$msg_finland' 'ftp.fi.freebsd.org' + '$msg_france' 'ftp.fr.freebsd.org' + ' $msg_france #3' 'ftp3.fr.freebsd.org' + ' $msg_france #4' 'ftp4.fr.freebsd.org' + ' $msg_france #5' 'ftp5.fr.freebsd.org' + ' $msg_france #6' 'ftp6.fr.freebsd.org' + ' $msg_france #7' 'ftp7.fr.freebsd.org' + ' $msg_france #8' 'ftp8.fr.freebsd.org' + '$msg_germany' 'ftp.de.freebsd.org' + ' $msg_germany #2' 'ftp2.de.freebsd.org' + ' $msg_germany #4' 'ftp4.de.freebsd.org' + ' $msg_germany #5' 'ftp5.de.freebsd.org' + ' $msg_germany #6' 'ftp6.de.freebsd.org' + ' $msg_germany #7' 'ftp7.de.freebsd.org' + ' $msg_germany #8' 'ftp8.de.freebsd.org' + '$msg_greece' 'ftp.gr.freebsd.org' + ' $msg_greece #2' 'ftp2.gr.freebsd.org' + '$msg_ireland' 'ftp3.ie.freebsd.org' + '$msg_israel' 'ftp.il.freebsd.org' + '$msg_italy' 'ftp.it.freebsd.org' + '$msg_japan' 'ftp.jp.freebsd.org' + ' $msg_japan #2' 'ftp2.jp.freebsd.org' + ' $msg_japan #3' 'ftp3.jp.freebsd.org' + ' $msg_japan #4' 'ftp4.jp.freebsd.org' + ' $msg_japan #5' 'ftp5.jp.freebsd.org' + ' $msg_japan #6' 'ftp6.jp.freebsd.org' + ' $msg_japan #7' 'ftp7.jp.freebsd.org' + ' $msg_japan #8' 'ftp8.jp.freebsd.org' + ' $msg_japan #9' 'ftp9.jp.freebsd.org' + '$msg_korea' 'ftp.kr.freebsd.org' + ' $msg_korea #2' 'ftp2.kr.freebsd.org' + '$msg_latvia' 'ftp.lv.freebsd.org' + '$msg_lithuania' 'ftp.lt.freebsd.org' + '$msg_netherlands' 'ftp.nl.freebsd.org' + ' $msg_netherlands #2' 'ftp2.nl.freebsd.org' + '$msg_new_zealand' 'ftp.nz.freebsd.org' + '$msg_norway' 'ftp.no.freebsd.org' + '$msg_poland' 'ftp.pl.freebsd.org' + ' $msg_poland #2' 'ftp2.pl.freebsd.org' + '$msg_russia' 'ftp.ru.freebsd.org' + ' $msg_russia #2' 'ftp2.ru.freebsd.org' + ' $msg_russia #4' 'ftp4.ru.freebsd.org' + ' $msg_russia #5' 'ftp5.ru.freebsd.org' + ' $msg_russia #6' 'ftp6.ru.freebsd.org' + '$msg_slovak_republic' 'ftp.sk.freebsd.org' + ' $msg_slovak_republic #2' 'ftp2.sk.freebsd.org' + '$msg_slovenia' 'ftp.si.freebsd.org' + '$msg_south_africa' 'ftp.za.freebsd.org' + ' $msg_south_africa #2' 'ftp2.za.freebsd.org' + ' $msg_south_africa #4' 'ftp4.za.freebsd.org' + '$msg_spain' 'ftp.es.freebsd.org' + ' $msg_spain #3' 'ftp3.es.freebsd.org' + '$msg_sweden' 'ftp.se.freebsd.org' + ' $msg_sweden #2' 'ftp2.se.freebsd.org' + ' $msg_sweden #3' 'ftp3.se.freebsd.org' + ' $msg_sweden #4' 'ftp4.se.freebsd.org' + ' $msg_sweden #6' 'ftp6.se.freebsd.org' + '$msg_switzerland' 'ftp.ch.freebsd.org' + '$msg_taiwan' 'ftp.tw.freebsd.org' + ' $msg_taiwan #2' 'ftp2.tw.freebsd.org' + ' $msg_taiwan #3' 'ftp3.tw.freebsd.org' + ' $msg_taiwan #4' 'ftp4.tw.freebsd.org' + ' $msg_taiwan #6' 'ftp6.tw.freebsd.org' + ' $msg_taiwan #11' 'ftp11.tw.freebsd.org' + '$msg_uk' 'ftp.uk.freebsd.org' + ' $msg_uk #2' 'ftp2.uk.freebsd.org' + ' $msg_uk #3' 'ftp3.uk.freebsd.org' + ' $msg_uk #4' 'ftp4.uk.freebsd.org' + ' $msg_uk #5' 'ftp5.uk.freebsd.org' + '$msg_ukraine' 'ftp.ua.freebsd.org' + ' $msg_ukraine #7' 'ftp7.ua.freebsd.org' + '$msg_usa #1' 'ftp1.us.freebsd.org' + ' $msg_usa #2' 'ftp2.us.freebsd.org' + ' $msg_usa #3' 'ftp3.us.freebsd.org' + ' $msg_usa #4' 'ftp4.us.freebsd.org' + ' $msg_usa #5' 'ftp5.us.freebsd.org' + ' $msg_usa #6' 'ftp6.us.freebsd.org' + ' $msg_usa #8' 'ftp8.us.freebsd.org' + ' $msg_usa #10' 'ftp10.us.freebsd.org' + ' $msg_usa #11' 'ftp11.us.freebsd.org' + ' $msg_usa #13' 'ftp13.us.freebsd.org' + ' $msg_usa #14' 'ftp14.us.freebsd.org' + ' $msg_usa #15' 'ftp15.us.freebsd.org' + " # END-QUOTE + local hline="$msg_select_a_site_thats_close" + + local height width rows + eval f_dialog_menu_size height width rows \ + \"\$title\" \ + \"\$btitle\" \ + \"\$prompt\" \ + \"\$hline\" \ + $menu_list + + local mtag + mtag=$( eval $DIALOG \ + --title \"\$title\" \ + --backtitle \"\$btitle\" \ + --hline \"\$hline\" \ + --ok-label \"\$msg_ok\" \ + --cancel-label \"\$msg_cancel\" \ + --menu \"\$prompt\" \ + $height $width $rows \ + $menu_list \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) || return $DIALOG_CANCEL + f_dialog_data_sanitize mtag + + case "$mtag" in + URL) setvar $VAR_FTP_PATH "other" ;; + *) + local value + value=$( eval f_dialog_menutag2item \"\$mtag\" $menu_list ) + setvar $VAR_FTP_PATH "ftp://$value" + esac + + return $DIALOG_OK +} + +# f_media_set_ftp +# +# Return success if we both found and set the media type to be an FTP server. +# Variables from variable.subr that can be used to script user input: +# +# VAR_FTP_PATH +# Can be a URL (including "ftp://" protocol-prefix) or "other" +# (user is prompted to enter FTP URL). If a URL, can optionally +# contain directory prefix after hostname/port. Valid examples +# include: +# ftp://myhost +# ftp://somename:21/pub/ +# ftp://192.168.2.3/pub/ +# ftp://[::1]:21/ +# The default port if not specified is 21. +# VAR_NAMESERVER [Optional] +# If set, overrides resolv.conf(5) and sets the nameserver that +# is used to convert names into addresses (when a name converts +# into multiple addresses, the first address to successfully +# connect is used). +# +# Meanwhile, the following variables from variable.subr are set after +# successful execution: +# +# VAR_FTP_HOST +# The FTP host to connect to, parsed from VAR_FTP_PATH. In the +# example case of IPv6 where VAR_FTP_PATH is "ftp://[::1]", this +# variable will be set to "::1" (the outer brackets are removed). +# VAR_FTP_PORT +# The TCP port to connect to, parsed from VAR_FTP_PATH. Usually +# 21 unless VAR_FTP_PATH was of one of the following forms: +# ftp://hostname:OTHER_PORT +# ftp://hostname:OTHER_PORT/* +# ftp://ip:OTHER_PORT +# ftp://ip:OTHER_PORT/* +# ftp://[ip6]:OTHER_PORT +# ftp://[ip6]:OTHER_PORT/* +# VAR_FTP_DIR +# If VAR_FTP_PATH contained a directory element (e.g., +# "ftp://localhost/pub") this variable contains only the +# directory element (e.g., "/pub"). +# +f_media_set_ftp() +{ + f_media_close + + local url + f_getvar $VAR_FTP_PATH url + + # If we've been through here before ... + if f_struct device_network && [ "${url#$msg_other}" ]; then + f_dialog_yesno "$msg_reuse_old_ftp_site_selection_values" || + url= + fi + + if [ ! "$url" ]; then + f_dialog_menu_media_ftp || return $FAILURE + f_getvar $VAR_FTP_PATH url + fi + [ "$url" ] || return $FAILURE + + case "$url" in + other) + setvar $VAR_FTP_PATH "ftp://" + f_variable_get_value $VAR_FTP_PATH \ + "$msg_please_specify_url_of_a_freebsd_distribution" + f_getvar $VAR_FTP_PATH url + if [ ! "${url#ftp://}" ]; then + unset $VAR_FTP_PATH + return $FAILURE + fi + if [ ${#url} -gt ${URL_MAX:-261261} ]; then + f_show_msg "$msg_length_of_specified_url_is_too_long" \ + ${#url} ${URL_MAX:-261261} + unset $VAR_FTP_PATH + return $FAILURE + fi + case "$url" in + ftp://*) : valid URL ;; + *) + f_show_msg "$msg_sorry_invalid_url" "$url" + unset $VAR_FTP_PATH + return $FAILURE + esac + esac + case "$url" in + ftp://*) : valid URL ;; + *) + f_show_msg "$msg_sorry_invalid_url" "$url" + unset $VAR_FTP_PATH + return $FAILURE + esac + + # Set the name of the FTP device to the URL + f_struct_new DEVICE device_ftp + device_ftp set name "$url" + + if ! f_struct device_network || + ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration" + then + f_struct device_network && + f_device_shutdown device_network + if ! f_device_select_tcp; then + unset $VAR_FTP_PATH + return $FAILURE + fi + local dev if + f_getvar $VAR_NETWORK_DEVICE if + f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev + f_struct_copy "$dev" device_network + fi + if ! f_device_init device_network; then + f_dprintf "f_media_set_ftp: %s" "$msg_net_device_init_failed" + unset $VAR_FTP_PATH + return $FAILURE + fi + + local hostname="${url#*://}" port=21 dir=/ + case "$hostname" in + # + # The order in-which the below individual cases appear is important! + # + "["*"]":*/*) # IPv6 address with port and directory + f_dprintf "Looks like an IPv6 addr with port/dir: %s" \ + "$hostname" + hostname="${hostname#\[}" + port="${hostname#*\]:}" + port="${port%%[!0-9]*}" + dir="/${hostname#*/}" + hostname="${hostname%%\]:*}" + ;; + "["*"]":*) # IPv6 address with port + f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname" + hostname="${hostname#\[}" + port="${hostname#*\]:}" + port="${port%%[!0-9]*}" + hostname="${hostname%%\]:*}" + ;; + "["*"]"/*) # IPv6 address with directory + f_dprintf "Looks like an IPv6 addr with dir: %s" "$hostname" + hostname="${hostname#\[}" + dir="/${hostname#*/}" + hostname="${hostname%%\]*}" + ;; + "["*"]") # IPv6 address + f_dprintf "Looks like an IPv6 addr: %s" "$hostname" + hostname="${hostname#\[}" + hostname="${hostname%\]}" + ;; + # + # ^^^ IPv6 above / DNS Name or IPv4 below vvv + # + *:*/*) # DNS name or IPv4 address with port and directory + f_dprintf "Looks like a %s with port/dir: %s" \ + "DNS name or IPv4 addr" "$hostname" + port="${hostname#*:}" + port="${port%%[!0-9]*}" + dir="/${hostname#*/}" + hostname="${hostname%%:*}" + ;; + *:*) # DNS name or IPv4 address with port + f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \ + "$hostname" + port="${hostname#*:}" + hostname="${hostname%%:*}" + ;; + */*) # DNS name or IPv4 address with directory + f_dprintf "Looks like a DNS name or IPv4 addr with dir: %s" \ + "$hostname" + dir="/${hostname#*/}" + hostname="${hostname%%/*}" + ;; + *) # DNS name or IPv4 address + f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname" + : leave hostname as-is + esac + + f_dprintf "hostname = \`%s'" "$hostname" + f_dprintf "dir = \`%s'" "$dir" + f_dprintf "port \# = \`%d'" "$port" + + local ns + f_getvar $VAR_NAMESERVER ns + [ "$ns" ] || f_resolv_conf_nameservers ns + if [ "$ns" -a ! "$FTP_SKIP_RESOLV" ] && ! { + f_validate_ipaddr "$hostname" || + f_validate_ipaddr6 "$hostname" + }; then + f_show_info "$msg_looking_up_host" "$hostname" + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_media_set_ftp" "$hostname" + if ! f_quietly f_host_lookup "$hostname"; then + f_show_msg "$msg_cannot_resolve_hostname" "$hostname" + f_struct device_network && + f_device_shutdown device_network + f_struct_free device_network + unset $VAR_FTP_PATH + return $FAILURE + fi + f_dprintf "Found DNS entry for %s successfully." "$hostname" + fi + + setvar $VAR_FTP_HOST "$hostname" + setvar $VAR_FTP_PORT "$port" + setvar $VAR_FTP_DIR "$dir" + + device_ftp set type $DEVICE_TYPE_FTP + device_ftp set init f_media_init_ftp + device_ftp set get f_media_get_ftp + device_ftp set shutdown f_media_shutdown_ftp + device_ftp set private device_network + f_struct_copy device_ftp device_media + f_struct_free device_ftp + + return $SUCCESS +} + +# f_media_set_ftp_active +# +# Wrapper to f_media_set_ftp to access FTP servers actively. +# +f_media_set_ftp_active() +{ + setvar $VAR_FTP_STATE "active" + f_media_set_ftp +} + +# f_media_set_ftp_passive +# +# Wrapper to f_media_set_ftp to access FTP servers passively. +# +f_media_set_ftp_passive() +{ + setvar $VAR_FTP_STATE "passive" + f_media_set_ftp +} + +# f_media_set_ftp_userpass +# +# Prompt the user to enter/confirm the username/password variables that will +# be used to communicate with the FTP servers. Returns success if the user does +# not cancel or press Esc to either username or password. +# +# Variables from variable.subr that can be used to script user input: +# +# VAR_FTP_USER +# The username to send via ftp(1) when connecting to an FTP +# server. +# VAR_FTP_PASS +# The password to send with the above username. +# +# Does not prompt for confirmation of values if VAR_NONINTERACTIVE is set (see +# variable.subr for more information). +# +f_media_set_ftp_userpass() +{ + local user pass + f_variable_get_value $VAR_FTP_USER \ + "$msg_please_enter_the_username_you_wish_to_login_as" + f_getvar $VAR_FTP_USER user + if [ "$user" ]; then + f_variable_get_value $VAR_FTP_PASS \ + "$msg_please_enter_the_password_for_this_user" + f_getvar $VAR_FTP_PASS pass + else + pass= + fi + [ "$pass" ] # Return status +} + +# f_device_network_up $device +# +# Brings up attached network device, if any - takes FTP device as arg. +# +f_device_network_up() +{ + local dev="$1" netDev + f_struct "$dev" || return $FAILURE + $dev get private netDev || return $SUCCESS # No net == happy net +debug=1 f_dprintf "netDev=[$netDev]" + f_device_init $netDev +} + +# f_device_network_down $device +# +# Brings down attached network device, if any - takes FTP device as arg. +# +f_device_network_down() +{ + local dev="$1" netDev + f_struct "$dev" || return $FAILURE + $dev get private netDev || return $SUCCESS + f_device_shutdown $netDev +} + +# f_media_init_ftp $device +# +# Initializes the FTP media device. Returns success if both able to log into +# the FTP server and confirm the existence of at least one known release path +# using ftp(1). +# +# Variables from variable.subr used to initialize the connection are as follows +# (all of which are configured by f_media_set_ftp above): +# +# VAR_FTP_PATH +# The unparsed FTP URL representing the server to contact. +# Usually "ftp://server" for example. Can contain TCP port number +# and/or directory path (but should not contain username/password +# info). +# VAR_FTP_HOST +# The FTP host to connect to. Can be an IPv4 address (e.g., +# 127.0.0.1), IPv6 address (e.g., ::1), or DNS hostname. Usually +# set automatically in f_media_set_ftp() by parsing VAR_FTP_PATH. +# VAR_FTP_PORT +# The TCP port to connect to. Usually set automatically in +# f_media_set_ftp() by parsing VAR_FTP_PATH. +# VAR_FTP_DIR +# The base FTP directory to use when downloading files from the +# FTP server. Usually set automatically in f_media_set_ftp() by +# parsing VAR_FTP_PATH. +# VAR_FTP_USER [Optional] +# If unset, defaults to using anonymous access. +# VAR_FTP_PASS [Optional] +# If unset, defaults to a sensible value. +# +# In addition, the following (managed either manually or by f_media_set_ftp_*): +# +# VAR_FTP_STATE +# Sets FTPMODE for ftp(1) and can be one of: +# active active mode FTP only +# auto automatic determination of passive or active +# (this is the default) +# gate gate-ftp mode +# passive passive mode FTP only +# See ftp(1) for additional information. +# +# And last, but not least (managed automatically or manually): +# +# VAR_RELNAME +# Defaults to being set to $(uname -r) but can be overridden. +# This sets the name of a release to look for as part of a well +# known set of paths to search for release data once connected +# via FTP. If set to "__RELEASE" or "any" then the VAR_FTP_DIR is +# taken as the absolute path to the release and no further +# searching is done (see FTP_DIRS above in the GLOBALS section +# for a list of well known paths that are used when searching for +# a VAR_RELNAME sub-directory). +# +f_media_init_ftp() +{ + local dev="$1" + local url + + $dev get name url + f_dprintf "Init routine called for FTP device. url=[%s]" "$url" + + if [ "$FTP_INITIALIZED" ]; then + f_dprintf "FTP device already initialized." + return $SUCCESS + fi + + # If we can't initialize the network, bag it! + f_device_network_up $dev || return $FAILURE + + local cp + while :; do + f_getvar $VAR_FTP_PATH cp + if [ ! "$cp" ]; then + if ! f_media_set_ftp || + ! f_getvar $VAR_FTP_PATH cp || + [ ! "$cp" ] + then + f_show_msg "$msg_unable_to_get_proper_ftp_path" + f_device_network_down $dev + return $FAILURE + fi + fi + + local ftp_host ftp_dir + if ! { + f_getvar $VAR_FTP_HOST ftp_host && + f_getvar $VAR_FTP_DIR ftp_dir + }; then + f_show_msg "$msg_missing_ftp_host_or_directory" + f_device_network_down $dev + return $FAILURE + fi + + local ftp_port + f_getvar $VAR_FTP_PORT ftp_port + local host="$ftp_host" port="${ftp_port:+:$ftp_port}" + case "$host" in *:*) host="[$host]"; esac + + local user pass use_anon= + f_getvar $VAR_FTP_USER user + if [ ! "$user" ]; then + user="anonymous" + use_anon=1 + fi + if ! f_getvar $VAR_FTP_PASS pass; then + f_getvar $VAR_HOSTNAME cp + if f_running_as_init; then + pass="installer@$cp" + else + local name="$( id -un 2> /dev/null )" + pass="${name:-ftp}@$cp" + fi + fi + + f_show_info "$msg_logging_in_to_user_at_host" \ + "$user" "$ftp_host" + + local userpass="" + if [ ! "$use_anon" ] && [ "$user" -o "$pass" ]; then + userpass="$user${pass:+:$( f_uriencode "$pass" )}" + userpass="$userpass${userpass:+@}" + fi + + local mode rx + f_getvar $VAR_FTP_STATE mode + + if [ "$ftp_dir" ]; then + if ! rx=$( + printf 'cd "%s"\npwd\n' "$ftp_dir" | eval \ + FTPMODE=\"\$mode\" \ + ${use_anon:+FTPANONPASS=\"\$pass\"} \ + ftp -V ${use_anon:+-a} \ + \"ftp://\$userpass\$host\$port\" \ + 2>&1 + ); then + f_show_msg "$msg_couldnt_open_ftp_connection" \ + "$ftp_host" "$rx" + break # to failure + fi + if echo "$rx" | awk -v dir="/${ftp_dir#/}" ' + BEGIN { + found = 0 + if ( dir != "/" ) sub("/$", "", dir) + } + /^Remote directory: / { + sub(/^[^:]*:[[:space:]]*/, "") + if ($0 != dir) next + found = 1; exit + } + END { exit ! found } + '; then + setvar $VAR_FTP_DIR "$ftp_dir" + setvar $VAR_FTP_PATH \ + "ftp://$ftp_host/${ftp_dir#/}" + else + f_show_msg \ + "$msg_please_check_the_url_and_try_again" \ + "ftp://$ftp_host/${ftp_dir#/}" + break # to failure + fi + fi + + # + # Now that we've verified that the path we're given is ok, + # let's try to be a bit intelligent in locating the release we + # are looking for. First off, if the release is specified as + # "__RELEASE" or "any", then just assume that the current + # directory is the one we want and give up. + # + local rel + f_getvar $VAR_RELNAME rel + f_dprintf "f_media_init_ftp: rel=[%s]" "$rel" + + case "$rel" in + __RELEASE|any) + FTP_INITIALIZED=YES + return $SUCCESS + ;; + *) + # + # Ok, since we have a release variable, let's walk + # through the list of directories looking for a release + # directory. First successful CWD wins. + # + if ! rx=$( + for dir in $FTP_DIRS; do + # Avoid confusing some servers + [ "$dir" = "." ] && continue + printf 'cd "/%s/%s"\npwd\n' \ + "$dir" "$rel" + done | eval \ + FTPMODE=\"\$mode\" \ + ${use_anon:+FTPANONPASS=\"\$pass\"} \ + ftp -V ${use_anon:+-a} \ + \"ftp://\$userpass\$host\$port\" \ + 2>&1 + ); then + f_show_msg "$msg_couldnt_open_ftp_connection" \ + "$ftp_host" "$rx" + break # to failure + fi + + local fdir + if fdir=$( echo "$rx" | awk ' + /^Remote directory: / { + sub(/^[^:]*:[[:space:]]*/, "") + if ($0 == "/") next + # Exit after the first dir + found++; print; exit + } + END { exit ! found } + ' ); then + setvar $VAR_FTP_DIR "$fdir" + setvar $VAR_FTP_PATH "ftp://$ftp_host$fdir" + FTP_INITIALIZED=YES + return $SUCCESS + else + f_yesno "$msg_cant_find_distribution" \ + "$rel" "$ftp_host" + if [ $? -eq $DIALOG_OK ]; then + unset $VAR_FTP_PATH + f_media_set_ftp && continue + fi + fi + esac + break # to failure + done + + unset FTP_INITIALIZED $VAR_FTP_PATH + f_device_network_down $dev + return $FAILURE +} + +# f_media_get_ftp $device $file [$probe_type] +# +# Returns data from $file on an FTP server using ftp(1). Please note that +# $device is unused but must be present (even if null). Information is instead +# gathered from the environment. If $probe_type is present and non-NULL, +# returns success if $file exists. If $probe_type is equal to $PROBE_SIZE, +# prints the size of $file in bytes to standard-out. +# +# Variables from variable.subr used to configure the connection are as follows +# (all of which are configured by f_media_set_ftp above): +# +# VAR_FTP_HOST +# FTP host to connect to. Can be an IPv4 address, IPv6 address, +# or DNS hostname of your choice. +# VAR_FTP_PORT +# TCP port to connect on; see f_media_set_ftp() above. +# VAR_FTP_USER [Optional] +# If unset, defaults to using anonymous access. +# VAR_FTP_PASS [Optional] +# If unset, defaults to a sensible value. +# +# In addition, the following (managed either manually or by f_media_set_ftp_*): +# +# VAR_FTP_STATE +# Sets FTPMODE for ftp(1) and can be one of: +# active active mode FTP only +# auto automatic determination of passive or active +# (this is the default) +# gate gate-ftp mode +# passive passive mode FTP only +# See ftp(1) for additional information. +# +# See variable.subr for additional information. +# +# Example usage: +# f_media_set_ftp +# f_media_get_ftp media $file +# +f_media_get_ftp() +{ + local funcname=f_media_get_ftp + local dev="$1" file="$2" probe_type="$3" hosts= + + f_dprintf "f_media_get_ftp: dev=[%s] file=[%s] probe_type=%s" \ + "$dev" "$file" "$probe_type" + + local ftp_host ftp_port + f_getvar $VAR_FTP_HOST ftp_host + f_getvar $VAR_FTP_PORT ftp_port + + if [ ! "$FTP_INITIALIZED" ]; then + f_dprintf "No FTP connection open, can't get file %s" "$file" + return $FAILURE + fi + + if ! { + f_validate_ipaddr "$ftp_host" || + f_validate_ipaddr6 "$ftp_host" || + { + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_media_get_ftp" "$ftp_host" + f_host_lookup "$ftp_host" hosts + } + }; then + # All the above validations failed + [ "$hosts" ] && f_dialog_msgbox "$hosts" + return $FAILURE + elif [ ! "$hosts" ]; then + # One of the first two validations passed + hosts="$ftp_host" + fi + + local host connected= + for host in $hosts; do + f_quietly nc -nz "$host" "$ftp_port" || continue + connected=1; break + done + if [ ! "$connected" ]; then + f_show_msg "$msg_couldnt_connect_to_ftp_server %s:%s" \ + "$ftp_host" "$ftp_port" + return $FAILURE + fi + + local user pass use_anon= + f_getvar $VAR_FTP_USER user + if [ ! "$user" ]; then + user="anonymous" + use_anon=1 + fi + if ! f_getvar $VAR_FTP_PASS pass; then + f_getvar $VAR_HOSTNAME cp + if f_running_as_init; then + pass="installer@$cp" + else + local name="$( id -un 2> /dev/null )" + pass="${name:-ftp}@$cp" + fi + fi + + local userpass="" + if [ ! "$use_anon" ] && [ "$user" -o "$pass" ]; then + userpass="$user${pass:+:$( f_uriencode "$pass" )}" + userpass="$userpass${userpass:+@}" + fi + + local dir mode rx + f_getvar $VAR_FTP_DIR\#/ dir + f_getvar $VAR_FTP_STATE mode + + local port="${ftp_port:+:$ftp_port}" + case "$host" in *:*) host="[$host]"; esac + + f_dprintf "sending ftp request for: %s" "ftp://$host$port/$dir/$file" + + if [ "$probe_type" ]; then + local url="ftp://$userpass$host$port/$dir/$file" size + [ "$use_anon" ] && url="ftp://$host$port/$dir/$file" + if ! f_eval_catch -dk size $funcname fetch \ + 'fetch -s "%s"' "$url" || ! f_isinteger "$size" + then + f_dprintf "size request failed!" + [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1" + return $FAILURE + fi + [ "$probe_type" = "$PROBE_SIZE" ] && echo "$size" + return $SUCCESS + fi + + eval FTPMODE=\"\$mode\" ${use_anon:+FTPANONPASS=\"\$pass\"} \ + ftp -V ${use_anon:+-a} -o - \ + \"ftp://\$userpass\$host\$port/\$dir/\$file\" 2> /dev/null + local retval=$? + + [ $retval -eq $SUCCESS ] || f_dprintf "request failed!" + return $retval +} + +# f_media_shutdown_ftp $device +# +# Shuts down the FTP device. Return status should be ignored. Note that since +# we don't maintain an open connection to the FTP server there's nothing to do. +# +f_media_shutdown_ftp() +{ + [ "$FTP_INITIALIZED" ] || return $SUCCESS + + unset FTP_INITIALIZED +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/ftp.subr + +fi # ! $_MEDIA_FTP_SUBR diff --git a/usr.sbin/bsdconfig/share/media/http.subr b/usr.sbin/bsdconfig/share/media/http.subr new file mode 100644 index 0000000..b928f7d --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/http.subr @@ -0,0 +1,688 @@ +if [ ! "$_MEDIA_HTTP_SUBR" ]; then _MEDIA_HTTP_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/http.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.subr +f_include $BSDCFG_SHARE/media/tcpip.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 + +############################################################ GLOBALS + +HTTP_SKIP_RESOLV= + +URL_MAX=261261 + # NOTE: This is according to actual fetch(1) test-results. We actually + # use nc(1) to retrieve files, but it's still a good idea to keep the + # URLs short enough that fetch(1) won't complain. + +HTTP_DIRS=" + . + releases/$UNAME_P + snapshots/$UNAME_P + pub/FreeBSD + pub/FreeBSD/releases/$UNAME_P + pub/FreeBSD/snapshots/$UNAME_P + pub/FreeBSD-Archive/old-releases/$UNAME_P +" # END-QUOTE + +############################################################ FUNCTIONS + +# f_dialog_menu_media_http +# +# Prompt the user to select from a range of ``built-in'' HTTP servers or +# specify their own. If the user makes a choice and doesn't cancel or press +# Esc, stores the user's choice in VAR_FTP_PATH (see variable.subr) and returns +# success. +# +f_dialog_menu_media_http() +{ + f_dialog_title "$msg_please_select_a_freebsd_http_distribution_site" + local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE" + f_dialog_title_restore + local prompt="$msg_please_select_the_site_closest_to_you_or_other" + local menu_list=" + 'dist $msg_main_site' 'ftp.freebsd.org' + 'pkg $msg_main_site' 'pkg.freebsd.org' + 'URL' '$msg_specify_some_other_http_site' + " # END-QUOTE + local hline="$msg_select_a_site_thats_close" + + local height width rows + eval f_dialog_menu_size height width rows \ + \"\$title\" \ + \"\$btitle\" \ + \"\$prompt\" \ + \"\$hline\" \ + $menu_list + + local mtag + mtag=$( eval $DIALOG \ + --title \"\$title\" \ + --backtitle \"\$btitle\" \ + --hline \"\$hline\" \ + --ok-label \"\$msg_ok\" \ + --cancel-label \"\$msg_cancel\" \ + --menu \"\$prompt\" \ + $height $width $rows \ + $menu_list \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) || return $DIALOG_CANCEL + f_dialog_data_sanitize mtag + + case "$mtag" in + URL) setvar $VAR_HTTP_PATH "other" ;; + *) + local value + value=$( eval f_dialog_menutag2item \"\$mtag\" $menu_list ) + setvar $VAR_HTTP_PATH "http://$value" + esac + + return $DIALOG_OK +} + +# f_media_set_http +# +# Return success if we both found and set the media type to be an HTTP server. +# +# Variables from variable.subr that can be used to script user input: +# +# VAR_HTTP_PATH +# URL containing host and optionally a target path to the release +# repository on the HTTP server. Valid examples include: +# http://myhost +# http://somename:80/pub/ +# http://192.168.2.3/pub/ +# http://[::1]:8000/ +# The default port if not specified is 80. +# VAR_NAMESERVER [Optional] +# If set, overrides resolv.conf(5) and sets the nameserver that +# is used to convert names into addresses (when a name converts +# into multiple addresses, the first address to successfully +# connect is used). +# +# Meanwhile, the following variables from variable.subr are set after +# successful execution: +# +# VAR_HTTP_HOST +# The HTTP host to connect to, parsed from VAR_HTTP_PATH. In the +# example case of IPv6 where VAR_HTTP_PATH is "http://[::1]" this +# variable will be set to "::1" (the outer brackets are removed). +# VAR_HTTP_PORT +# The TCP port to connect to, parsed from VAR_HTTP_PATH. Usually +# 80 unless VAR_HTTP_PATH was one of the following forms: +# http://hostname:OTHER_PORT +# http://hostname:OTHER_PORT/* +# http://ip:OTHER_PORT +# http://ip:OTHER_PORT/* +# http://[ip6]:OTHER_PORT +# http://[ip6]:OTHER_PORT/* +# VAR_HTTP_DIR +# If VAR_HTTP_PATH contained a directory element (e.g., +# "http://localhost/pub") this variable contains only the +# directory element (e.g., "/pub"). +# +f_media_set_http() +{ + f_media_close + + local url + f_getvar $VAR_HTTP_PATH url + + # If we've been through here before ... + if f_struct device_network && [ "${url#$msg_other}" ]; then + f_dialog_yesno "$msg_reuse_old_http_site_settings" || url= + fi + + if [ ! "$url" ]; then + f_dialog_menu_media_http || return $FAILURE + f_getvar $VAR_HTTP_PATH url + fi + [ "$url" ] || return $FAILURE + + case "$url" in + other) + setvar $VAR_HTTP_PATH "http://" + f_variable_get_value $VAR_HTTP_PATH \ + "$msg_please_specify_url_of_freebsd_http_distribution" + f_getvar $VAR_HTTP_PATH url + if [ ! "${url#http://}" ]; then + unset $VAR_HTTP_PATH + return $FAILURE + fi + if [ ${#url} -gt ${URL_MAX:-261261} ]; then + f_show_msg "$msg_length_of_specified_url_is_too_long" \ + ${#url} ${URL_MAX:-261261} + unset $VAR_HTTP_PATH + return $FAILURE + fi + case "$url" in + http://*) : valid URL ;; + *) + f_show_msg "$msg_sorry_invalid_url" "$url" + unset $VAR_HTTP_PATH + return $FAILURE + esac + esac + case "$url" in + http://*) : valid URL ;; + *) + f_show_msg "$msg_sorry_invalid_url" "$url" + unset $VAR_HTTP_PATH + return $FAILURE + esac + + # Set the name of the HTTP device to the URL + f_struct_new DEVICE device_http + device_http set name "$url" + + if ! f_struct device_network || + ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration" + then + f_struct device_network && + f_device_shutdown device_network + if ! f_device_select_tcp; then + unset $VAR_HTTP_PATH + return $FAILURE + fi + local dev if + f_getvar $VAR_NETWORK_DEVICE if + f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev + f_struct_copy "$dev" device_network + fi + if ! f_device_init device_network; then + f_dprintf "f_media_set_http: %s" "$msg_net_device_init_failed" + unset $VAR_HTTP_PATH + return $FAILURE + fi + + local hostname="${url#*://}" port=80 dir=/ + case "$hostname" in + # + # The order in-which the below individual cases appear is important! + # + "["*"]":*/*) # IPv6 address with port and directory + f_dprintf "Looks like an IPv6 addr with port/dir: %s" \ + "$hostname" + hostname="${hostname#\[}" + port="${hostname#*\]:}" + port="${port%%[!0-9]*}" + dir="/${hostname#*/}" + hostname="${hostname%%\]:*}" + ;; + "["*"]":*) # IPv6 address with port + f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname" + hostname="${hostname#\[}" + port="${hostname#*\]:}" + port="${port%%[!0-9]*}" + hostname="${hostname%%\]:*}" + ;; + "["*"]"/*) # IPv6 address with directory + f_dprintf "Looks like an IPv6 addr with dir: %s" "$hostname" + hostname="${hostname#\[}" + dir="/${hostname#*/}" + hostname="${hostname%%\]*}" + ;; + "["*"]") # IPv6 address + f_dprintf "Looks like an IPv6 addr: %s" "$hostname" + hostname="${hostname#\[}" + hostname="${hostname%\]}" + ;; + # + # ^^^ IPv6 above / DNS Name or IPv4 below vvv + # + *:*/*) # DNS name or IPv4 address with port and directory + f_dprintf "Looks like a %s with port/dir: %s" \ + "DNS name or IPv4 addr" "$hostname" + port="${hostname#*:}" + port="${port%%[!0-9]*}" + dir="/${hostname#*/}" + hostname="${hostname%%:*}" + ;; + *:*) # DNS name or IPv4 address with port + f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \ + "$hostname" + port="${hostname#*:}" + hostname="${hostname%%:*}" + ;; + */*) # DNS name or IPv4 address with directory + f_dprintf "Looks like a DNS name or IPv4 addr with dir: %s" \ + "$hostname" + dir="/${hostname#*/}" + hostname="${hostname%%/*}" + ;; + *) # DNS name or IPv4 address + f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname" + : leave hostname as-is + esac + + f_dprintf "hostname = \`%s'" "$hostname" + f_dprintf "dir = \`%s'" "$dir" + f_dprintf "port \# = \`%d'" "$port" + + local ns + f_getvar $VAR_NAMESERVER ns + [ "$ns" ] || f_resolv_conf_nameservers ns + if [ "$ns" -a ! "$HTTP_SKIP_RESOLV" ] && ! { + f_validate_ipaddr "$hostname" || + f_validate_ipaddr6 "$hostname" + }; then + f_show_info "$msg_looking_up_host" "$hostname" + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_media_set_http" "$hostname" + if ! f_quietly f_host_lookup "$hostname"; then + f_show_msg "$msg_cannot_resolve_hostname" "$hostname" + f_struct device_network && + f_device_shutdown device_network + f_struct_free device_network + unset $VAR_HTTP_PATH + return $FAILURE + fi + f_dprintf "Found DNS entry for %s successfully." "$hostname" + fi + + setvar $VAR_HTTP_HOST "$hostname" + setvar $VAR_HTTP_PORT "$port" + setvar $VAR_HTTP_DIR "$dir" + + device_http set type $DEVICE_TYPE_HTTP + device_http set init f_media_init_http + device_http set get f_media_get_http + device_http set shutdown f_media_shutdown_http + device_http set private device_network + f_struct_copy device_http device_media + f_struct_free device_http + + return $SUCCESS +} + +# f_http_check_access [$connect_only] +# +# Return success if able list a remote HTTP directory. If $connect_only is +# present and non-null, then returns success if a connection can be made. +# Variables from variable.subr that can be used to script user input: +# +# VAR_HTTP_HOST +# The HTTP server host name, IPv4 address or IPv6 address. +# Valid examples include: +# myhost +# 192.168.2.3 +# ::1 +# VAR_HTTP_PORT +# The TCP port to connect to when communicating with the server. +# VAR_HTTP_PATH +# The HTTP path sent to the server. Unused if $connect_only is +# present and non-NULL. +# +f_http_check_access() +{ + local connect_only="$1" hosts= + + local http_host http_port + f_getvar $VAR_HTTP_HOST http_host + f_getvar $VAR_HTTP_PORT http_port + + if ! { + f_validate_ipaddr "$http_host" || + f_validate_ipaddr6 "$http_host" || + { + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_http_check_access" "$http_host" + f_host_lookup "$http_host" hosts + } + }; then + # All the above validations failed + [ "$hosts" ] && f_dialog_msgbox "$hosts" + unset $VAR_HTTP_HOST + return $FAILURE + elif [ ! "$hosts" ]; then + # One of the first two validations passed + hosts="$http_host" + fi + + local host connected= + for host in $hosts; do + f_quietly nc -nz "$host" "$http_port" || continue + connected=1; break + done + if [ ! "$connected" ]; then + f_show_msg "$msg_couldnt_connect_to_server http://%s:%s/" \ + "$http_host" "$http_port" + unset $VAR_HTTP_HOST + return $FAILURE + fi + [ "$connect_only" ] && return $SUCCESS + + local http_path + f_getvar $VAR_HTTP_PATH http_path + f_show_info "$msg_checking_access_to" "$http_path" + + local rx + case "$http_path" in + http://*|/*) : valid request ;; + *) http_path="/$http_path" # full URI requests only + esac + if ! rx=$( + printf "GET %s/ HTTP/1.0\r\n\r\n" "${http_path%/}" | + nc -n "$host" "$http_port" + ); then + f_show_msg "$msg_couldnt_connect_to_server http://%s:%s/" \ + "$http_host" "$http_port" + unset $VAR_HTTP_HOST + return $FAILURE + fi + + local hdr + hdr=$( echo "$rx" | awk '/^\r$/{exit}{print}' ) + + local http_found=$FAILURE + if echo "$hdr" | awk ' + BEGIN { found = 0 } + /^HTTP.... 200 / { + found = 1 + exit + } + END { exit ! found } + '; then + http_found=$SUCCESS + fi + + return $http_found +} + +# f_media_init_http $device +# +# Initializes the HTTP media device. Returns success if able to confirm the +# existence of at least one known HTTP server release path directly via HTTP +# using f_http_check_access(), above. +# +# Variables from variable.subr that can be used to script user input: +# +# VAR_HTTP_HOST +# The HTTP server to connect to. Must be set. Also see +# f_http_check_access() for additional variables. +# VAR_RELNAME +# Usually set to `uname -r' but can be overridden. +# VAR_HTTP_PATH +# The HTTP path sent to the server. Usually set by calling +# f_media_set_http(). +# +# Meanwhile, after successful execution, the following variables (also from +# variable.subr) are set: +# +# VAR_HTTP_PATH +# The [possibly] adjusted VAR_HTTP_PATH that was found to contain +# a valid FreeBSD repository. +# +f_media_init_http() +{ + local dev="$1" + f_dprintf "Init routine called for HTTP device. dev=[%s]" "$dev" + + if [ "$HTTP_INITIALIZED" ]; then + f_dprintf "HTTP device already initialized." + return $SUCCESS + fi + + # + # First verify access + # + local connect_only=1 + f_http_check_access $connect_only + + local http_host + f_getvar $VAR_HTTP_HOST http_host + while [ ! "$http_host" ]; do + f_media_set_http || return $FAILURE + f_http_check_access $connect_only + f_getvar $VAR_HTTP_HOST http_host + done + + local http_path http_found=$FAILURE + while :; do + # + # Now that we've verified that the path we're given is ok, + # let's try to be a bit intelligent in locating the release we + # are looking for. First off, if the release is specified as + # "__RELEASE" or "any", then just assume that the current + # directory is the one we want and give up. + # + local rel + f_getvar $VAR_RELNAME rel + f_dprintf "f_media_init_http: rel=[%s]" "$rel" + + case "$rel" in + __RELEASE|any) + f_getvar $VAR_HTTP_DIR $VAR_HTTP_PATH + f_http_check_access + http_found=$? + ;; + *) + # + # Ok, since we have a release variable, let's walk + # through the list of directories looking for a release + # directory. First successful path wins. + # + local fdir hp + f_getvar $VAR_HTTP_PATH%/ hp + setvar $VAR_HTTP_PATH "$hp/$PKG_ABI/latest" + if [ "$PKG_ABI" ] && f_http_check_access; then + http_found=$SUCCESS + setvar $VAR_HTTP_PATH "$hp" + else + for fdir in $HTTP_DIRS; do + setvar $VAR_HTTP_PATH "$hp/$fdir/$rel" + if f_http_check_access; then + http_found=$SUCCESS + break + fi + done + fi + esac + + [ $http_found -eq $SUCCESS ] && HTTP_INITIALIZED=YES break + + f_getvar $VAR_HTTP_PATH http_path + f_show_msg "$msg_please_check_the_url_and_try_again" \ + "$http_path" + + unset HTTP_INITIALIZED $VAR_HTTP_PATH + f_media_set_http || break + done + + return $http_found +} + +# f_media_get_http $device $file [$probe_type] +# +# Returns data from $file on an HTTP server using nc(1). Please note that +# $device is unused but must be present (even if null). Information is instead +# gathered from the environment. If $probe_type is both present and non-NULL, +# this function exits after receiving the HTTP header response from the server +# (if the HTTP response code is 200, success is returned; otherwise failure). +# If $probe_type is equal to $PROBE_SIZE, prints the content-length in bytes +# from the response (or -1 if not found) to standard-out. +# +# The variables used to configure the connection are as follows (all of which +# are configured by f_media_set_http above): +# +# VAR_HTTP_HOST +# HTTP server which to connect. Can be an IPv4 address, IPv6 +# address, or DNS hostname of your choice. +# VAR_HTTP_PORT +# TCP port to connect on; see f_media_set_http above. +# VAR_HTTP_PATH +# Directory prefix to use when requesting $file. Default is `/' +# unless f_media_init_http was able to use f_http_check_access +# to validate one of the defaults in $HTTP_DIRS (see GLOBALS at +# the top of this file); assuming VAR_RELNAME was not set to +# either `__RELEASE' or `any' (indicating that the global set of +# $HTTP_DIRS should be ignored). +# +# See variable.subr for additional information. +# +# Example usage: +# f_media_set_http +# f_media_get_http media $file +# +f_media_get_http() +{ + local dev="$1" file="$2" probe_type="$3" hosts= + local name + + $dev get name name + f_dprintf "f_media_get_http: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + local http_host http_port + f_getvar $VAR_HTTP_HOST http_host + f_getvar $VAR_HTTP_PORT http_port + + if [ ! "$HTTP_INITIALIZED" ]; then + f_dprintf "No HTTP connection open, can't get file %s" "$file" + return $FAILURE + fi + + if ! { + f_validate_ipaddr "$http_host" || + f_validate_ipaddr6 "$http_host" || + { + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_media_get_http" "$http_host" + f_host_lookup "$http_host" hosts + } + }; then + # All the above validations failed + [ "$hosts" ] && f_dialog_msgbox "$hosts" + return $FAILURE + elif [ ! "$hosts" ]; then + # One of the first two validations passed + hosts="$http_host" + fi + + local host connected= + for host in $hosts; do + f_quietly nc -nz "$host" "$http_port" || continue + connected=1; break + done + if [ ! "$connected" ]; then + f_show_msg "$msg_couldnt_connect_to_server http://%s:%s/" \ + "$http_host" "$http_port" + return $FAILURE + fi + + local http_path + f_getvar $VAR_HTTP_PATH%/ http_path + case "$http_path" in + http://*|/*) : valid request ;; + *) http_path="/$http_path" # full URI requests only + esac + + local url="$http_path/$file" rx + f_dprintf "sending http request for: %s" "$url" + f_dprintf "using nc to connect to: %s:%s" "$host" "$http_port" + printf "GET %s HTTP/1.0\r\n\r\n" "$url" | nc -n "$host" "$http_port" | + ( + # + # scan the headers of the response + # this is extremely quick'n dirty + # + + rv=0 length=-1 + while read LINE; do + case "$LINE" in + HTTP*) + f_dprintf "received response: %s" "$LINE" + set -- $LINE; rv=$2 + f_isinteger "$rv" || rv=0 + ;; + "Content-Length: "*) + length="${LINE%
}" + length="${length#Content-Length: }" + f_dprintf "received content-length: %s" \ + "$length" + ;; + *) + [ "${LINE%
}" ] || break # End of headers + esac + done + + [ $rv -ge 500 ] && exit 5 + [ $rv -eq 404 ] && exit 44 + [ $rv -ge 400 ] && exit 4 + [ $rv -ge 300 ] && exit 3 + [ $rv -eq 200 ] || exit $FAILURE + + if [ ! "$probe_type" ]; then + cat # output the rest ``as-is'' + elif [ "$probe_type" = "$PROBE_SIZE" ]; then + f_isinteger "$length" || length=-1 + echo "$length" + fi + exit 200 + ) + local retval=$? + [ $retval -eq 200 ] && return $SUCCESS + [ "$probe_type" ] && return $FAILURE + + case "$retval" in + 5) f_show_msg "$msg_server_error_when_requesting_url" "$url" ;; + 44) f_show_msg "$msg_url_was_not_found" "$url" ;; + 4) f_show_msg "$msg_client_error" ;; + *) f_show_msg "$msg_error_when_requesting_url" "$url" ;; + esac 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + return $FAILURE +} + +# f_media_shutdown_http $device +# +# Shuts down the HTTP device. Return status should be ignored. Note that since +# we don't maintain an open connection to the HTTP server, nothing to do. +# +f_media_shutdown_http() +{ + [ "$HTTP_INITIALIZED" ] || return $SUCCESS + + unset HTTP_INITIALIZED +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/http.subr + +fi # ! $_MEDIA_HTTP_SUBR diff --git a/usr.sbin/bsdconfig/share/media/httpproxy.subr b/usr.sbin/bsdconfig/share/media/httpproxy.subr new file mode 100644 index 0000000..1ef516f --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/httpproxy.subr @@ -0,0 +1,463 @@ +if [ ! "$_MEDIA_HTTPPROXY_SUBR" ]; then _MEDIA_HTTPPROXY_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/httpproxy.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/ftp.subr +f_include $BSDCFG_SHARE/media/tcpip.subr +f_include $BSDCFG_SHARE/variable.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ FUNCTIONS + +# f_media_set_http_proxy +# +# Return success if we both found and set the media type to be an ftp server, +# accessed via http proxy. +# +# Variables from variable.subr that can be used to script user input: +# +# VAR_HTTP_PROXY +# HTTP Proxy server to use. Valid examples include: +# myhost +# somename:3128 +# 192.168.2.3 +# [::1]:8080 +# The default port if not specified is 3128. +# +# Variables from variable.subr that are set after successful execution include +# the following: +# +# VAR_HTTP_PROXY_HOST The host portion of VAR_HTTP_PROXY. +# VAR_HTTP_PROXY_PORT The TCP port parsed from VAR_HTTP_PROXY. +# +# See also f_media_set_ftp() for additional variables. +# +f_media_set_http_proxy() +{ + FTP_SKIP_RESOLV=1 f_media_set_ftp || return $FAILURE + + f_variable_get_value $VAR_HTTP_PROXY \ + "$msg_please_enter_the_address_of_the_http_proxy" + + local proxy + f_getvar $VAR_HTTP_PROXY proxy + [ "$proxy" ] || return $FAILURE + + local hostname="$proxy" port=3128 + case "$hostname" in + # + # The order in-which the below individual cases appear is important! + # + "["*"]":*) # IPv6 address with port + f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname" + hostname="${hostname#\[}" + port="${hostname#*\]:}" + port="${port%%[!0-9]*}" + hostname="${hostname%%\]:*}" + ;; + "["*"]") # IPv6 address + f_dprintf "Looks like an IPv6 addr: %s" "$hostname" + hostname="${hostname#\[}" + hostname="${hostname%\]}" + ;; + # + # ^^^ IPv6 above / DNS Name or IPv4 below vvv + # + *:*) # DNS name or IPv4 address with port + f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \ + "$hostname" + port="${hostname#*:}" + hostname="${hostname%%:*}" + ;; + *) # DNS name or IPv4 address + f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname" + : leave hostname as-is + esac + + setvar $VAR_HTTP_PROXY_HOST "$hostname" + setvar $VAR_HTTP_PROXY_PORT "$port" + + if f_debugging; then + f_dprintf "VAR_FTP_PATH : %s" "$( f_getvar $VAR_FTP_PATH )" + f_dprintf "VAR_HTTP_PROXY_HOST, _PORT: %s:%s" \ + "$( f_getvar $VAR_HTTP_PROXY_HOST )" \ + "$( f_getvar $VAR_HTTP_PROXY_PORT )" + fi + + # media device has been set by f_media_set_ftp(), overwrite partly: + device_media set type $DEVICE_TYPE_HTTP_PROXY + device_media set init f_media_init_http_proxy + device_media set get f_media_get_http_proxy + device_media unset shutdown + + return $SUCCESS +} + +# f_http_proxy_check_access [$connect_only] +# +# Return success if able list a remote FTP directory via HTTP proxy. If +# $connect_only is present and non-null, then returns success if a connection +# can be made. Variables from variable.subr that can be used to script user +# input: +# +# VAR_HTTP_PROXY_HOST +# The HTTP proxy server host name, IPv4 address or IPv6 address. +# Valid examples include: +# myhost +# 192.168.2.3 +# ::1 +# VAR_HTTP_PROXY_PORT +# The TCP port to connect to when communicating with the HTTP +# proxy server. +# VAR_HTTP_PROXY_PATH +# The FTP URL sent to the HTTP proxy server. Unused if +# $connect_only is present and non-NULL. +# +f_http_proxy_check_access() +{ + local connect_only="$1" hosts= + + local proxy_host proxy_port + f_getvar $VAR_HTTP_PROXY_HOST proxy_host + f_getvar $VAR_HTTP_PROXY_PORT proxy_port + + if ! { + f_validate_ipaddr "$proxy_host" || + f_validate_ipaddr6 "$proxy_host" || + { + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_http_proxy_check_access" "$proxy_host" + f_host_lookup "$proxy_host" hosts + } + }; then + # All the above validations failed + [ "$hosts" ] && f_dialog_msgbox "$hosts" + unset $VAR_HTTP_PROXY_HOST + return $FAILURE + elif [ ! "$hosts" ]; then + # One of the first two validations passed + hosts="$proxy_host" + fi + + local host connected= + for host in $hosts; do + f_quietly nc -nz "$host" "$proxy_port" || continue + connected=1; break + done + if [ ! "$connected" ]; then + f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \ + "$proxy_host" "$proxy_port" + unset $VAR_HTTP_PROXY_HOST + return $FAILURE + fi + [ "$connect_only" ] && return $SUCCESS + + # + # Some proxies fetch files with certain extensions in "ascii mode" + # instead of "binary mode" for FTP. The FTP server then translates all + # LF to CRLF. + # + # You can force Squid to use binary mode by appending ";type=i" to the + # URL, which is what sysinstall(8) has traditionally done. + # + + local proxy_path + f_getvar $VAR_HTTP_PROXY_PATH proxy_path + f_show_info "$msg_checking_access_to" "$proxy_path" + + local rx + if ! rx=$( + printf "GET %s/ HTTP/1.0\r\n\r\n" "${proxy_path%/}" | + nc -n "$host" "$proxy_port" + ); then + f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \ + "$proxy_host" "$proxy_port" + unset $VAR_HTTP_PROXY_HOST + return $FAILURE + fi + + local hdr + hdr=$( echo "$rx" | awk '/^\r$/{exit}{print}' ) + + local http_found=$FAILURE + if echo "$hdr" | awk ' + BEGIN { found = 0 } + /^HTTP.... 200 / { + found = 1 + exit + } + END { exit ! found } + '; then + http_found=$SUCCESS + fi + + # + # Scan the headers of the response + # this is extremely quick'n dity + # + + unset $VAR_HTTP_FTP_MODE + if echo "$hdr" | awk ' + BEGIN { found = 0 } + { + if (!match($0, /^Server: /)) next + found = ( substr($0, 9, 5) ~ /[Ss]quid/ ) + } + END { exit ! found } + '; then + setvar $VAR_HTTP_FTP_MODE ";type=i" + else + setvar $VAR_HTTP_FTP_MODE "" + fi + + return $http_found +} + +# f_media_init_http_proxy $device +# +# Initializes the HTTP Proxy media device. Returns success if able to confirm +# the existence of at least one known FTP server release path via HTTP proxy +# using f_http_proxy_check_access(), above. +# +# Variables from variable.subr that can be used to script user input: +# +# VAR_HTTP_PROXY_HOST +# The HTTP proxy server to connect to. Usually set by having +# f_media_set_http_proxy() parse VAR_HTTP_PROXY. Must be set. +# Also see f_http_proxy_check_access() for additional variables. +# VAR_RELNAME +# Usually set to `uname -r' but can be overridden. +# VAR_FTP_PATH +# The FTP URL to send to the HTTP proxy server. Usually set by +# calling f_media_set_ftp(). +# +# Meanwhile, after successful execution, the following variables (also from +# variable.subr) are set: +# +# VAR_HTTP_PROXY_PATH +# The [possibly] adjusted VAR_FTP_PATH that was found to contain +# a valid FreeBSD repository. +# +f_media_init_http_proxy() +{ + local dev="$1" + f_dprintf "Init routine called for HTTP Proxy device. dev=[%s]" "$dev" + + # + # First verify access + # + local connect_only=1 + f_http_proxy_check_access $connect_only + + local proxy_host + f_getvar $VAR_HTTP_PROXY_HOST proxy_host + while [ ! "$proxy_host" ]; do + f_media_set_http_proxy || return $FAILURE + f_http_proxy_check_access $connect_only + f_getvar $VAR_HTTP_PROXY_HOST proxy_host + done + + local rel proxy_path http_found=$FAILURE + while :; do + # + # If the release is specified as "__RELEASE" or "any", then + # just assume that the path the user gave is ok. + # + f_getvar $VAR_RELNAME rel + f_dprintf "f_media_init_http_proxy: rel=[%s]" "$rel" + + case "$rel" in + __RELEASE|any) + f_getvar $VAR_FTP_PATH $VAR_HTTP_PROXY_PATH + f_http_proxy_check_access + http_found=$? + ;; + *) + local fdir fp + f_getvar $VAR_FTP_PATH%/ fp + for fdir in $FTP_DIRS; do + setvar $VAR_HTTP_PROXY_PATH "$fp/$fdir/$rel" + if f_http_proxy_check_access; then + http_found=$SUCCESS + break + fi + done + esac + + [ $http_found -eq $SUCCESS ] && break + + f_getvar $VAR_HTTP_PROXY_PATH proxy_path + f_show_msg "$msg_please_check_the_url_and_try_again" \ + "$proxy_path" + + unset $VAR_HTTP_PROXY_PATH + f_media_set_http_proxy || break + done + + return $http_found +} + +# f_media_get_http_proxy $device $file [$probe_type] +# +# Returns data from $file on an FTP server via HTTP proxy using nc(1). Please +# note that $device is unused but must be present (even if null). Information +# is instead gathered from the environment. If $probe_type is both present and +# non-NULL, this function exits after receiving the HTTP header response from +# the proxy server (if the HTTP response code is 200, success is returned; +# otherwise failure). If $probe_type is equal to $PROBE_SIZE, prints the +# content-length in bytes from the response (or -1 if not found) to standard- +# out. +# +# The variables used to configure the connection are as follows (all of which +# are configured by f_media_set_http_proxy above): +# +# VAR_HTTP_PROXY_HOST +# HTTP proxy host to connect. Can be an IPv4 address, IPv6 +# address, or DNS hostname of your choice. +# VAR_HTTP_PROXY_PORT +# TCP port to connect on; see f_media_set_http_proxy above. +# VAR_HTTP_PROXY_PATH +# URL (including "ftp://" protocol-prefix) of FTP directory to +# use as a prefix when requesting $file via HTTP proxy. +# +# See variable.subr for additional information. +# +# Example usage: +# f_media_set_http_proxy +# f_media_get_http_proxy media $file +# +f_media_get_http_proxy() +{ + local dev="$1" file="$2" probe_type="$3" hosts= + + f_dprintf "f_media_get_http_proxy: dev=[%s] file=[%s] probe_type=%s" \ + "$dev" "$file" "$probe_type" + + local proxy_host proxy_port + f_getvar $VAR_HTTP_PROXY_HOST proxy_host + f_getvar $VAR_HTTP_PROXY_PORT proxy_port + + if ! { + f_validate_ipaddr "$proxy_host" || + f_validate_ipaddr6 "$proxy_host" || + { + f_dprintf "%s: Looking up hostname, %s, using host(1)" \ + "f_media_get_http_proxy" "$proxy_host" + f_host_lookup "$proxy_host" hosts + } + }; then + # All the above validations failed + [ "$hosts" ] && f_dialog_msgbox "$hosts" + return $FAILURE + elif [ ! "$hosts" ]; then + # One of the first two validations passed + hosts="$proxy_host" + fi + + local host connected= + for host in $hosts; do + f_quietly nc -nz "$host" "$proxy_port" || continue + connected=1; break + done + if [ ! "$connected" ]; then + f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \ + "$proxy_host" "$proxy_port" + return $FAILURE + fi + + local proxy_path mode + f_getvar $VAR_HTTP_PROXY_PATH%/ proxy_path + f_getvar $VAR_HTTP_FTP_MODE mode + local url="$proxy_path/$file$mode" rx + + f_dprintf "sending http request for: %s" "$url" + printf "GET %s HTTP/1.0\r\n\r\n" "$url" | nc -n "$host" "$proxy_port" | + ( + # + # scan the headers of the response + # this is extremely quick'n dirty + # + + rv=0 length=-1 + while read LINE; do + case "$LINE" in + HTTP*) + f_dprintf "received response: %s" "$LINE" + set -- $LINE; rv=$2 + f_isinteger "$rv" || rv=0 + ;; + "Content-Length: "*) + length="${LINE%
}" + length="${length#Content-Length: }" + f_dprintf "received content-length: %s" \ + "$length" + ;; + *) + [ "${LINE%
}" ] || break # End of headers + esac + done + + [ $rv -ge 500 ] && exit 5 + [ $rv -eq 404 ] && exit 44 + [ $rv -ge 400 ] && exit 4 + [ $rv -ge 300 ] && exit 3 + [ $rv -eq 200 ] || exit $FAILURE + + if [ ! "$probe_type" ]; then + cat # output the rest ``as-is'' + elif [ "$probe_type" = "$PROBE_SIZE" ]; then + f_isinteger "$length" || length=-1 + echo "$length" + fi + exit 200 + ) + local retval=$? + [ $retval -eq 200 ] && return $SUCCESS + [ "$probe_type" ] && return $FAILURE + + case "$retval" in + 5) f_show_msg "$msg_server_error_when_requesting_url" "$url" ;; + 44) f_show_msg "$msg_url_was_not_found" "$url" ;; + 4) f_show_msg "$msg_client_error" ;; + *) f_show_msg "$msg_error_when_requesting_url" "$url" ;; + esac 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + return $FAILURE +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/httpproxy.subr + +fi # ! $_MEDIA_HTTPPROXY_SUBR diff --git a/usr.sbin/bsdconfig/share/media/network.subr b/usr.sbin/bsdconfig/share/media/network.subr new file mode 100644 index 0000000..6e17200 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/network.subr @@ -0,0 +1,182 @@ +if [ ! "$_MEDIA_NETWORK_SUBR" ]; then _MEDIA_NETWORK_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/network.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/tcpip.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ GLOBALS + +NETWORK_INITIALIZED= + +############################################################ FUNCTIONS + +# f_media_init_network $device +# +# Initialize a network device (such as `fxp0', `em0', etc.). Returns success if +# able to successfully initialize the device. If not running as init (basically +# from the FreeBSD install media) then assume that the network has already been +# initialized and returns success. +# +# The variables (from variable.subr) used to initialize the network are as +# follows (all of which are configured either automatically or manaully): +# +# VAR_IFCONFIG + device_name (e.g., `ifconfig_em0') +# Automatically populated but can be overridden in a script. This +# defines the ifconfig(8) properties specific to a chosen network +# interface device. Optional if VAR_IPV6ADDR is set. +# VAR_IPV6ADDR [Optional] +# If not running as init (and setting up RTSOL connections for +# the interface), then must be set manually. If set, used as the +# IPv6 configuration for the given network interface device. +# VAR_GATEWAY [Optional] +# If not running as init (and setting up a static connection for +# the interface) then must be set (usually via rc.conf(5), but +# can be set manually to override). If unset, the user is warned +# but not prevented from proceeding (as most connections need a +# default route but not everyone). +# +f_media_init_network() +{ + local dev="$1" + + f_dprintf "Init routine called for network device \`%s'." "$dev" + if [ "$NETWORK_INITIALIZED" ]; then + f_dprintf "Network already initialized." + return $SUCCESS + elif ! f_running_as_init; then + f_dprintf "Not running as init -- calling the deed done." + NETWORK_INITIALIZED=1 + return $SUCCESS + fi + + if [ ! -e "$RESOLV_CONF" ]; then + if ! f_config_resolv; then + f_show_msg "$msg_cant_seem_to_write_out_resolv_conf" \ + "$RESOLV_CONF" + return $FAILURE + fi + fi + + local cp + if f_getvar $VAR_IFCONFIG$dev cp; then + # + # If this interface isn't a DHCP one, bring it up. + # If it is, then it's already up. + # + case "$cp" in + *DHCP*) + f_dprintf "A DHCP interface. Should already be up." + ;; + *) + f_dprintf "Not a DHCP interface." + if ! f_quietly ifconfig "$dev" $cp; then + f_show_msg "$msg_unable_to_configure_device" \ + "$dev" + return $FAILURE + fi + local rp + f_getvar $VAR_GATEWAY rp + if [ ! "$rp" ]; then + f_show_msg "$msg_no_gateway_has_been_set" + else + # + # Explicitly flush all routes to get back to a + # known sane state. We don't need to check this + # exit code because if anything fails it will + # show up in the route add below. + # + f_quietly route -n flush + f_dprintf "Adding default route to %s." "$rp" + if ! f_quietly route -n add default "$rp"; then + f_show_msg \ + "$msg_failed_to_add_default_route" + return $FAILURE + fi + fi + esac + elif ! { f_getvar $VAR_IPV6ADDR cp && [ "$cp" ]; }; then + f_show_msg "$msg_device_is_not_configured" "$dev" + return $FAILURE + fi + + f_dprintf "Network initialized successfully." + NETWORK_INITIALIZED=1 + return $SUCCESS +} + +# f_media_shutdown_network $device +# +# Shuts down the configured network device (e.g., `fxp0', `em0', etc.) and +# deletes the default route (if configured). Returns failure if the device +# passed has not been configured. If not running as init (basically from the +# FreeBSD install media) then does nothing and returns success. +# +f_media_shutdown_network() +{ + local dev="$1" cp + + f_dprintf "Shutdown called for network device %s" "$dev" + if [ ! "$NETWORK_INITIALIZED" ]; then + f_dprintf "Network not initialized -- nothing to do." + return $SUCCESS + fi + + unset NETWORK_INITIALIZED + unset $VAR_NETWORK_DEVICE + + if ! f_running_as_init; then + f_dprintf "Not running as init -- calling the deed done." + return $SUCCESS + fi + + f_getvar $VAR_IFCONFIG$dev cp || return $FAILURE + f_dprintf "ifconfig %s down" "$dev" + f_quietly ifconfig $dev down || + f_show_msg "$msg_unable_to_down_the_interface_properly" "$dev" + + if f_getvar $VAR_GATEWAY cp; then + f_dprintf "Deleting default route." + f_quietly route -n delete default + fi + + return $SUCCESS +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/network.subr + +fi # ! $_MEDIA_NETWORK_SUBR diff --git a/usr.sbin/bsdconfig/share/media/nfs.subr b/usr.sbin/bsdconfig/share/media/nfs.subr new file mode 100644 index 0000000..d83661f --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/nfs.subr @@ -0,0 +1,258 @@ +if [ ! "$_MEDIA_NFS_SUBR" ]; then _MEDIA_NFS_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/nfs.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.subr +f_include $BSDCFG_SHARE/media/network.subr +f_include $BSDCFG_SHARE/media/tcpip.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 + +############################################################ GLOBALS + +NFS_MOUNTED= + +############################################################ FUNCTIONS + +# f_media_set_nfs +# +# Return success if we both found and set the media type to be an NFS server. +# Variables from variable.subr that can be used to script user input: +# +# VAR_NFS_PATH +# The NFS path specification (host:path) to use when mounting the +# remote repository. +# VAR_NAMESERVER [Optional] +# Automatically populated from resolv.conf(5) but can be +# overridden. If set, the host portion of VAR_NFS_PATH is +# looked up using f_host_lookup() from `tcpip.subr'. +# +# Meanwhile, the following variables from variable.subr are set after +# successful execution: +# +# VAR_NFS_HOST +# The host portion of the NFS path specification, parsed from +# VAR_NFS_PATH. +# +f_media_set_nfs() +{ + local nfs + + f_media_close + + f_variable_get_value $VAR_NFS_PATH \ + "$msg_please_enter_the_full_nfs_file_specification" + f_getvar $VAR_NFS_PATH nfs + [ "$nfs" ] || return $FAILURE + + case "$nfs" in + *:*) : valid NFS path ;; + *) + f_show_msg "$msg_invalid_nfs_path_specification" + return $FAILURE + esac + + f_struct_new DEVICE device_nfs + device_nfs set name "$nfs" + + if ! f_struct device_network || + ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration" + then + f_struct device_network && + f_device_shutdown device_network + f_device_select_tcp || return $FAILURE + local dev if + f_getvar $VAR_NETWORK_DEVICE if + f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev + f_struct_copy "$dev" device_network + fi + f_device_init device_network || + f_dprintf "%s: $msg_net_device_init_failed\n" f_media_set_nfs + + local hostname="${nfs%%:*}" + if f_isset $VAR_NAMESERVER && ! { + f_validate_ipaddr "$hostname" || f_validate_ipaddr6 "$hostname" + }; then + f_show_info "$msg_looking_up_host" "$hostname" + f_dprintf "%s Looking up hostname, %s, using host(1)" \ + "f_media_set_nfs" "$hostname" + if ! f_quietly f_host_lookup "$hostname"; then + f_show_msg "$msg_cannot_resolve_hostname" "$hostname" + f_struct device_network && + f_device_shutdown device_network + f_struct_free device_network + unset $VAR_NFS_PATH + return $FAILURE + fi + f_dprintf "Found DNS entry for %s successfully." "$hostname" + fi + + setvar $VAR_NFS_HOST "$hostname" + + device_nfs set type $DEVICE_TYPE_NFS + device_nfs set init f_media_init_nfs + device_nfs set get f_media_get_nfs + device_nfs set shutdown f_media_shutdown_nfs + device_nfs set private device_network # in name only (deref'd later) + + f_struct_copy device_nfs device_media + f_struct_free device_nfs + + return $SUCCESS +} + +# f_media_init_nfs $device +# +# Initializes the NFS media device. Returns success if able to mount the NFS +# device using mount_nfs(1). +# +# The variables (from variable.subr) used to initialize the NFS mount are as +# follows (all of which are configured manually/optionally from the options +# menu): +# +# VAR_NFS_TCP [Optional] +# If non-NULL, adds the "tcp" option via `-o' to mount_nfs(8). +# VAR_NFS_V3 [Optional] +# If non-NULL, adds the "nfsv3" option via `-o' to mount_nfs(8). +# VAR_NFS_SECURE [Optional] +# If non-NULL, adds the "-P" flag to mount_nfs(8). +# VAR_SLOW_ETHER [Optional] +# If non-NULL, adjusts the read/write size to avoid timeouts. +# +f_media_init_nfs() +{ + local funcname=f_media_init_nfs + local dev="$1" name err + + $dev get name name || return $FAILURE + f_dprintf "Init routine called for NFS device. name=[%s]" \ + "$name" + + if [ "$NFS_MOUNTED" ]; then + f_dprintf "NFS device already mounted." + return $SUCCESS + fi + + if ! f_device_init device_network; then + f_dprintf "f_media_init_nfs: %s" "$msg_net_device_init_failed" + return $FAILURE + fi + + if [ ! -e "$MOUNTPOINT" ]; then + f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" || + return $FAILURE + fi + + local cp tcp="" use3="" secure="" readsize=4096 writesize=4096 + f_getvar $VAR_NFS_TCP cp + [ "$cp" = "YES" ] && tcp=1 + f_getvar $VAR_NFS_V3 cp + [ "$cp" = "YES" ] && use3=1 + f_getvar $VAR_NFS_SECURE cp + [ "$cp" = "YES" ] && secure=1 + f_getvar $VAR_SLOW_ETHER cp + [ "$cp" = "YES" ] && readsize=1024 writesize=1024 + + local options="rsize=$readsize,wsize=$writesize" + [ "$use3" ] && options="$options,nfsv3" + [ "$tcp" ] && options="$options,tcp" + + if ! f_eval_catch -dk err $funcname mount_nfs \ + 'mount_nfs %s -o "%s" "%s" "%s"' \ + "${secure:+-P}" "$options" "$name" "$MOUNTPOINT" + then + err="${err#mount_nfs: }" + f_show_msg "$msg_error_mounting_device" \ + "$name" "$MOUNTPOINT" "$err" + f_struct device_network && + f_device_shutdown device_network + return $FAILURE + fi + NFS_MOUNTED=1 + + f_dprintf "Mounted NFS device %s onto %s" "$name" "$MOUNTPOINT" + + return $SUCCESS +} + +# f_media_get_nfs $device $file [$probe_type] +# +# Returns data from $file on a mounted NFS device. Similar to cat(1). If +# $probe_type is present and non-NULL, returns success if $file exists. If +# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to +# standard-out. +# +f_media_get_nfs() +{ + local dev="$1" file="$2" probe_type="$3" + local name + + $dev get name name + f_dprintf "f_media_get_nfs: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type" +} + +# f_media_shutdown_nfs $device +# +# Shuts down the NFS device using umount(8). Return status should be ignored. +# +f_media_shutdown_nfs() +{ + local funcname=f_media_shutdown_nfs + local dev="$1" err + + [ "$NFS_MOUNTED" ] || return $FAILURE + + f_dprintf "Unmounting NFS partition on %s" "$MOUNTPOINT" + if ! f_eval_catch -dk err $funcname umount \ + 'umount -f "%s"' "$MOUNTPOINT" + then + err="${err#umount: }"; err="${err#*: }" + f_show_msg "$msg_could_not_unmount_the_nfs_partition" \ + "$MOUNTPOINT" "$err" + else + NFS_MOUNTED= + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/nfs.subr + +fi # ! $_MEDIA_NFS_SUBR diff --git a/usr.sbin/bsdconfig/share/media/options.subr b/usr.sbin/bsdconfig/share/media/options.subr new file mode 100644 index 0000000..bc9568e --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/options.subr @@ -0,0 +1,327 @@ +if [ ! "$_MEDIA_OPTIONS_SUBR" ]; then _MEDIA_OPTIONS_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/options.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/any.subr +f_include $BSDCFG_SHARE/media/ftp.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 + +OPTIONS_HELPFILE=$BSDCFG_LIBE/include/options.hlp + +############################################################ FUNCTIONS + +# f_media_options_menu +# +# Prompt the user to confirm/edit various media settings. Returns success. +# +f_media_options_menu() +{ + f_dialog_title "$msg_options_editor" + local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE" + f_dialog_title_restore + local prompt= + local menu_list # Calculated below + local defaultitem= + local hline="$hline_arrows_tab_enter" + + # + # A hack so that the dialogs below are always interactive in a script + # + local old_interactive= + if ! f_interactive; then + f_getvar $VAR_NONINTERACTIVE old_interactive + unset $VAR_NONINTERACTIVE + fi + + local cp + while :; do + menu_list= + + f_getvar $VAR_NFS_SECURE cp + if [ "$cp" = "YES" ]; then menu_list="$menu_list + ' $msg_nfs_secure' 'YES' + '$msg_nfs_server_talks_only_on_a_secure_port'" + else menu_list="$menu_list + ' $msg_nfs_secure' 'NO' + '$msg_nfs_server_talks_only_on_a_secure_port'" + fi + + f_getvar $VAR_SLOW_ETHER cp + if [ "$cp" = "YES" ]; then menu_list="$menu_list + ' $msg_nfs_slow' 'YES' + '$msg_user_is_using_a_slow_pc_or_ethernet_card'" + else menu_list="$menu_list + ' $msg_nfs_slow' 'NO' + '$msg_user_is_using_a_slow_pc_or_ethernet_card'" + fi + + f_getvar $VAR_NFS_TCP cp + if [ "$cp" = "YES" ]; then menu_list="$menu_list + ' $msg_nfs_tcp' 'YES' '$msg_use_tcp_protocol_for_nfs'" + else menu_list="$menu_list + ' $msg_nfs_tcp' 'NO' '$msg_use_tcp_protocol_for_nfs'" + fi + + f_getvar $VAR_NFS_V3 cp + if [ "$cp" = "YES" ]; then menu_list="$menu_list + ' $msg_nfs_version_3' 'YES' '$msg_use_nfs_version_3'" + else menu_list="$menu_list + ' $msg_nfs_version_3' 'NO' '$msg_use_nfs_version_3'" + fi + + f_getvar $VAR_DEBUG cp + if [ "$cp" ]; then menu_list="$menu_list + ' $msg_debugging' 'YES' + '$msg_emit_extra_debugging_output'" + else menu_list="$menu_list + ' $msg_debugging' 'NO' + '$msg_emit_extra_debugging_output'" + fi + + f_getvar $VAR_NO_CONFIRM cp + if [ "$cp" ]; then menu_list="$menu_list + ' $msg_yes_to_all' 'YES' + '$msg_assume_yes_to_all_non_critical_dialogs'" + else menu_list="$menu_list + ' $msg_yes_to_all' 'NO' + '$msg_assume_yes_to_all_non_critical_dialogs'" + fi + + f_getvar $VAR_TRY_DHCP cp + if [ "$cp" = "YES" ]; then menu_list="$menu_list + ' $msg_dhcp' 'YES' + '$msg_attempt_automatic_dhcp_configuration'" + else menu_list="$menu_list + ' $msg_dhcp' 'NO' + '$msg_attempt_automatic_dhcp_configuration'" + fi + + f_getvar $VAR_TRY_RTSOL cp + if [ "$cp" = "YES" ]; then menu_list="$menu_list + ' $msg_ipv6' 'YES' + '$msg_attempt_ipv6_configuration_of_interfaces'" + else menu_list="$menu_list + ' $msg_ipv6' 'NO' + '$msg_attempt_ipv6_configuration_of_interfaces'" + fi + + f_getvar $VAR_FTP_USER cp + menu_list="$menu_list + ' $msg_ftp_username' '$cp' + '$msg_username_and_password_to_use'" + + f_getvar $VAR_EDITOR cp + menu_list="$menu_list + ' $msg_editor' '$cp' '$msg_which_text_editor_to_use'" + + f_getvar $VAR_RELNAME cp + menu_list="$menu_list + ' $msg_release_name' '$cp' + '$msg_which_release_to_attempt_to_load'" + + if f_struct device_media; then + device_media get type cp + case "$cp" in + $DEVICE_TYPE_UFS|$DEVICE_TYPE_DISK) + cp="$msg_file_system" ;; + $DEVICE_TYPE_DIRECTORY) cp="$msg_directory" ;; + $DEVICE_TYPE_FLOPPY) cp="$msg_floppy" ;; + $DEVICE_TYPE_FTP) cp="$msg_ftp" ;; + $DEVICE_TYPE_HTTP_PROXY) cp="$msg_http_proxy" ;; + $DEVICE_TYPE_HTTP) cp="$msg_http_direct" ;; + $DEVICE_TYPE_CDROM) cp="$msg_cdrom" ;; + $DEVICE_TYPE_USB) cp="$msg_usb" ;; + $DEVICE_TYPE_DOS) cp="$msg_dos" ;; + $DEVICE_TYPE_NFS) cp="$msg_nfs" ;; + *) + cp="<$msg_unknown>" + esac + else + cp="<$msg_not_yet_set>" + fi + menu_list="$menu_list + ' $msg_media_type' '$cp' + '$msg_the_current_installation_media_type'" + + f_getvar $VAR_MEDIA_TIMEOUT cp + menu_list="$menu_list + ' $msg_media_timeout' '$cp' + '$msg_timeout_value_in_seconds_for_slow_media'" + + f_getvar $VAR_PKG_TMPDIR cp + menu_list="$menu_list + ' $msg_package_temp' '$cp' + '$msg_directory_where_package_temporary_files_go'" + + menu_list="$menu_list + ' $msg_rescan_devices' '<*>' + '$msg_rerun_bsdconfig_initial_device_probe' + ' $msg_use_defaults' '[${msg_reset}]' + '$msg_reset_all_values_to_startup_defaults' + " # END-QUOTE + + local height width rows + eval f_dialog_menu_with_help_size height width rows \ + \"\$title\" \ + \"\$btitle\" \ + \"\$prompt\" \ + \"\$hline\" \ + $menu_list + + local mtag + mtag=$( eval $DIALOG \ + --title \"\$title\" \ + --backtitle \"\$btitle\" \ + --hline \"\$hline\" \ + --item-help \ + --ok-label \"\$msg_ok\" \ + --cancel-label \"\$msg_done\" \ + --help-button \ + --help-label \"\$msg_help\" \ + --default-item \"\$defaultitem\" \ + ${USE_XDIALOG:+--help \"\"} \ + --menu \"\$prompt\" \ + $height $width $rows \ + $menu_list \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) + local retval=$? + f_dialog_data_sanitize mtag + defaultitem="$mtag" + f_dprintf "retval=%s mtag=[%s]" $retval "$mtag" + + if [ $retval -eq $DIALOG_HELP ]; then + f_show_help "$OPTIONS_HELPFILE" + continue + elif [ $retval -ne $DIALOG_OK ]; then + break # to success + fi + + case "$mtag" in + " $msg_nfs_secure") + f_getvar $VAR_NFS_SECURE cp + if [ "$cp" = "YES" ]; then + export $VAR_NFS_SECURE="NO" + else + export $VAR_NFS_SECURE="YES" + fi ;; + " $msg_nfs_slow") + f_getvar $VAR_SLOW_ETHER cp + if [ "$cp" = "YES" ]; then + export $VAR_SLOW_ETHER="NO" + else + export $VAR_SLOW_ETHER="YES" + fi ;; + " $msg_nfs_tcp") + f_getvar $VAR_NFS_TCP cp + if [ "$cp" = "YES" ]; then + export $VAR_NFS_TCP="NO" + else + export $VAR_NFS_TCP="YES" + fi ;; + " $msg_nfs_version_3") + f_getvar $VAR_NFS_V3 cp + if [ "$cp" = "YES" ]; then + export $VAR_NFS_V3="NO" + else + export $VAR_NFS_V3="YES" + fi ;; + " $msg_debugging") + if f_getvar $VAR_DEBUG cp && [ "$cp" ]; then + unset $VAR_DEBUG + else + export $VAR_DEBUG=1 + fi ;; + " $msg_yes_to_all") + if f_getvar $VAR_NO_CONFIRM cp && [ "$cp" ]; then + unset $VAR_NO_CONFIRM + else + export $VAR_NO_CONFIRM=1 + fi ;; + " $msg_dhcp") + f_getvar $VAR_TRY_DHCP cp + if [ "$cp" = "YES" ]; then + export $VAR_TRY_DHCP="NO" + else + export $VAR_TRY_DHCP="YES" + fi ;; + " $msg_ipv6") + f_getvar $VAR_TRY_RTSOL cp + if [ "$cp" = "YES" ]; then + export $VAR_TRY_RTSOL="NO" + else + export $VAR_TRY_RTSOL="YES" + fi ;; + " $msg_ftp_username") + f_media_set_ftp_userpass ;; + " $msg_editor") + f_variable_get_value $VAR_EDITOR \ + "$msg_please_specify_the_name_of_the_text_editor" + ;; + " $msg_release_name") + f_variable_get_value $VAR_RELNAME \ + "$msg_please_specify_the_release_you_wish_to_load" + ;; + " $msg_media_type") + f_media_get_type ;; + " $msg_media_timeout") + f_variable_get_value $VAR_MEDIA_TIMEOUT \ + "$msg_please_specify_the_number_of_seconds_to_wait" + ;; + " $msg_package_temp") + f_variable_get_value $VAR_PKG_TMPDIR \ + "$msg_please_specify_a_temporary_directory" + ;; + " $msg_rescan_devices") + f_device_rescan ;; + " $msg_use_defaults") + f_variable_set_defaults ;; + esac + done + + # Restore old VAR_NONINTERACTIVE if needed. + [ "$old_interactive" ] && + setvar $VAR_NONINTERACTIVE "$old_interactive" + + return $SUCCESS +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/options.subr + +fi # ! $_MEDIA_OPTIONS_SUBR diff --git a/usr.sbin/bsdconfig/share/media/tcpip.subr b/usr.sbin/bsdconfig/share/media/tcpip.subr new file mode 100644 index 0000000..42c6e20 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/tcpip.subr @@ -0,0 +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: +# +# 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 diff --git a/usr.sbin/bsdconfig/share/media/ufs.subr b/usr.sbin/bsdconfig/share/media/ufs.subr new file mode 100644 index 0000000..27e2f27 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/ufs.subr @@ -0,0 +1,198 @@ +if [ ! "$_MEDIA_UFS_SUBR" ]; then _MEDIA_UFS_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/ufs.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.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 + +############################################################ GLOBALS + +UFS_MOUNTED= + +############################################################ FUNCTIONS + +# f_media_set_ufs +# +# Return success if we both found and set the media type to be a UFS partition. +# Variables from variable.subr that can be used to script user input: +# +# VAR_UFS_PATH +# Path to a UFS character device node to be used with mount(8) in +# mounting a UFS formatted partition. Valid examples include: +# /dev/da0s1a +# /dev/ad4s1e +# However, other forms may be valid (see mount(8) for additional +# information). +# +f_media_set_ufs() +{ + local ufs + + f_media_close + + local devs ndevs + f_device_find "" $DEVICE_TYPE_UFS devs + f_count ndevs $devs + + if [ ${ndevs:=0} -eq 0 ]; then + f_variable_get_value $VAR_UFS_PATH \ + "$msg_enter_the_device_name_of_a_ufs_formatted_partition" + f_getvar $VAR_UFS_PATH ufs + [ "$ufs" ] || return $FAILURE + + local fstype + fstype=$( df -nT $ufs 2> /dev/null | + awk '!/Type/{print $2;exit}' ) + + f_struct_new DEVICE device_ufs + device_ufs set name ${fstype:-ufs} + device_ufs set devname "$ufs" + device_ufs set get f_media_get_ufs + device_ufs set init f_media_init_ufs + device_ufs set shutdown f_media_shutdown_ufs + device_ufs unset private + + f_struct_copy device_ufs device_media + f_struct_free device_ufs + elif [ $ndevs -eq 1 ]; then + f_struct_copy $devs device_media + else + local dev + local title="$msg_choose_a_ufs_partition" + local prompt="$msg_please_select_ufs_partition" + local hline="" + + dev=$( f_device_menu \ + "$title" "$prompt" "$hline" $DEVICE_TYPE_UFS \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || + return $FAILURE + + f_struct_copy "$dev" device_media + fi + + f_struct device_media || return $FAILURE +} + +# f_media_init_ufs $device +# +# Initializes the UFS media device. Returns success if able to mount the UFS +# partition device using mount(1). +# +f_media_init_ufs() +{ + local funcname=f_media_init_ufs + local dev="$1" devname err + + $dev get devname devname || return $FAILURE + f_dprintf "Init routine called for UFS device. devname=[%s]" \ + "$devname" + + if [ "$UFS_MOUNTED" ]; then + f_dprintf "UFS device already mounted." + return $SUCCESS + fi + + if [ ! -e "$devname" ]; then + f_show_msg "$msg_no_such_file_or_directory" \ + "f_media_init_ufs" "$devname" + return $FAILURE + fi + + if [ ! -e "$MOUNTPOINT" ]; then + f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" || + return $FAILURE + fi + + if ! f_eval_catch -dk err $funcname mount \ + 'mount "%s" "%s"' "$devname" "$MOUNTPOINT" + then + err="${err#mount: }"; err="${err#$devname : }" + f_show_msg "$msg_error_mounting_device" \ + "$devname" "$MOUNTPOINT" "$err" + return $FAILURE + fi + UFS_MOUNTED=1 + return $SUCCESS +} + +# f_media_get_ufs $device $file [$probe_type] +# +# Returns data from $file on a mounted UFS partition device. Similar to cat(1). +# If $probe_type is present and non-NULL, returns success if $file exists. If +# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to +# standard-out. +# +f_media_get_ufs() +{ + local dev="$1" file="$2" probe_type="$3" + local name + + $dev get name name + f_dprintf "f_media_get_ufs: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type" +} + +# f_media_shutdown_ufs $device +# +# Shuts down the UFS device using umount(8). Return status should be ignored. +# +f_media_shutdown_ufs() +{ + local funcname=f_media_shutdown_ufs + local dev="$1" err + + [ "$UFS_MOUNTED" ] || return $FAILURE + + if ! f_eval_catch -dk err $funcname umount \ + 'umount -f "%s"' "$MOUNTPOINT" + then + err="${err#umount: }"; err="${err#*: }" + f_show_msg "$msg_could_not_unmount_the_ufs_partition" \ + "$MOUNTPOINT" "$err" + else + UFS_MOUNTED= + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/ufs.subr + +fi # ! $_MEDIA_UFS_SUBR diff --git a/usr.sbin/bsdconfig/share/media/usb.subr b/usr.sbin/bsdconfig/share/media/usb.subr new file mode 100644 index 0000000..5ee9bc0 --- /dev/null +++ b/usr.sbin/bsdconfig/share/media/usb.subr @@ -0,0 +1,176 @@ +if [ ! "$_MEDIA_USB_SUBR" ]; then _MEDIA_USB_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/usb.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/media/common.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 + +############################################################ GLOBALS + +USB_MOUNTED= + +############################################################ FUNCTIONS + +# f_media_set_usb +# +# Attempt to use USB as the media type. Return success if we both found and set +# the media type to be a USB drive. +# +f_media_set_usb() +{ + f_media_close + + local devs ndevs + f_device_find "" $DEVICE_TYPE_USB devs + f_count ndevs $devs + + if [ ${ndevs:=0} -eq 0 ]; then + f_show_msg "$msg_no_usb_devices_found" + return $FAILURE + elif [ $ndevs -eq 1 ]; then + f_struct_copy $devs device_media + else + local dev + local title="$msg_choose_a_usb_drive" + local prompt="$msg_please_select_a_usb_drive" + local hline= + + dev=$( f_device_menu \ + "$title" "$prompt" "$hline" $DEVICE_TYPE_USB \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) || + return $FAILURE + + f_struct_copy "$dev" device_media + fi + + f_struct device_media && + device_media unset private + + if f_interactive; then + local name + f_struct device_media get name name + f_show_msg "$msg_using_usb_device" "$name" + fi + + f_struct device_media || return $FAILURE +} + +# f_media_init_usb $device +# +# Initializes the USB media device. Returns success if able to mount the USB +# disk device using mount(8). +# +f_media_init_usb() +{ + local funcname=f_media_init_usb + local dev="$1" devname err + + $dev get devname devname || return $FAILURE + f_dprintf "Init routine called for USB device. devname=[%s]" \ + "$devname" + + if [ "$USB_MOUNTED" ]; then + f_dprintf "USB device already mounted." + return $SUCCESS + fi + + if [ ! -e "$MOUNTPOINT" ]; then + f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" || + return $FAILURE + fi + + if f_eval_catch -dk err $funcname mount \ + 'mount "%s" "%s"' "$devname" "$MOUNTPOINT" + then + USB_MOUNTED=1 + return $SUCCESS + fi + + err="${err#mount: }"; err="${err#$devname: }" + f_show_msg "$msg_error_mounting_usb_drive" \ + "$devname" "$MOUNTPOINT" "$err" + return $FAILURE +} + +# f_media_get_usb $device $file [$probe_type] +# +# Returns data from $file on a mounted USB disk device. Similar to cat(1). If +# $probe_type is present and non-NULL, returns success if $file exists. If +# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to +# standard-out. +# +f_media_get_usb() +{ + local dev="$1" file="$2" probe_type="$3" + local name + + $dev get name name + f_dprintf "f_media_get_usb: dev=[%s] file=[%s] probe_type=%s" \ + "$name" "$file" "$probe_type" + + f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type" +} + +# f_media_shutdown_usb $device +# +# Shuts down the USB disk device using umount(8). Return status should be +# ignored. +# +f_media_shutdown_usb() +{ + local funcname=f_media_shutdown_usb + local dev="$1" err + + [ "$USB_MOUNTED" ] || return $FAILURE + + if ! f_eval_catch -dk err $funcname umount \ + 'umount -f "%s"' "$MOUNTPOINT" + then + err="${err#umount: }"; err="${err#*: }" + f_show_msg "$msg_could_not_unmount_the_ufs_partition" \ + "$MOUNTPOINT" "$err" + else + USB_MOUNTED= + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." media/usb.subr + +fi # ! $_MEDIA_USB_SUBR diff --git a/usr.sbin/bsdconfig/share/mustberoot.subr b/usr.sbin/bsdconfig/share/mustberoot.subr new file mode 100644 index 0000000..88ff818 --- /dev/null +++ b/usr.sbin/bsdconfig/share/mustberoot.subr @@ -0,0 +1,424 @@ +if [ ! "$_MUSTBEROOT_SUBR" ]; then _MUSTBEROOT_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..." mustberoot.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/strings.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ CONFIGURATION +# NOTE: These are not able to be overridden/inherited for security purposes. + +# +# Number of tries a user gets to enter his/her password before we log the +# sudo(8) failure and exit. +# +PASSWD_TRIES=3 + +# +# While in SECURE mode, should authentication as `root' be allowed? Set to +# non-NULL to enable authentication as `root', otherwise disabled. +# +# WARNING: +# Unless using a custom sudo(8) configuration, user `root' should not be +# allowed because no password is required to become `root' when already `root' +# and therefore, any value entered as password will work. +# +SECURE_ALLOW_ROOT= + +# +# While in SECURE mode, should we divulge (through error message) when the +# requested authentication user does not exist? Set to non-NULL to enable, +# otherwise a non-existent user is treated like an invalid password. +# +SECURE_DIVULGE_UNKNOWN_USER= + +############################################################ FUNCTIONS + +# f_become_root_via_sudo +# +# If not running as root, prompt for sudo(8) credentials to become root. +# Re-execution of the current program via sudo is automatically handled. +# +# The following environment variables effect functionality: +# +# USE_XDIALOG Either NULL or Non-NULL. If given a value will indicate +# that Xdialog(1) should be used instead of dialog(1). +# +f_become_root_via_sudo() +{ + local funcname=f_become_root_via_sudo + local prompt hline height width rows msg + + [ "$( id -u )" = "0" ] && return $SUCCESS + + f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm" + + # + # Ask the user if it's OK to become root via sudo(8) and give them + # the option to save this preference (by touch(1)ing a file in the + # user's $HOME directory). + # + local checkpath="${HOME%/}/.bsdconfig_uses_sudo" + if [ ! -e "$checkpath" ]; then + f_sprintf prompt "$msg_you_are_not_root_but" bsdconfig + f_sprintf msg "$msg_always_try_sudo_when_run_as" "$USER" + local menu_list=" + 'X' '$msg_cancel_exit' + '1' '$msg' + '2' '$msg_try_sudo_only_this_once' + " # END-QUOTE + hline="$hline_arrows_tab_enter" + + eval f_dialog_menu_size height width rows \ + \"\$DIALOG_TITLE\" \ + \"\$DIALOG_BACKTITLE\" \ + \"\$prompt\" \ + \"\$hline\" \ + $menu_list + + local mtag + mtag=$( eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --hline \"\$hline\" \ + --ok-label \"\$msg_ok\" \ + --cancel-label \"\$msg_cancel\" \ + --menu \"\$prompt\" \ + $height $width $rows \ + $menu_list \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) || f_die + f_dialog_data_sanitize mtag + + case "$mtag" in + X) # Cancel/Exit + f_die ;; + 1) # Always try sudo(8) when run as $user + f_eval_catch $funcname touch \ + 'touch "%s"' "$checkpath" && + f_show_msg "$msg_created_path" "$checkpath" + esac + else + # + # This user has created the path signing-off on sudo(8)-use + # but let's still give them a short/quick/unobtrusive reminder + # + f_dialog_info "$msg_becoming_root_via_sudo" + [ "$USE_XDIALOG" ] || sleep 0.6 + fi + + # + # Check sudo(8) access before prompting for password. + # + :| sudo -S -v 2> /dev/null + if [ $? -ne $SUCCESS ]; then + # + # sudo(8) access denied. Prompt for their password. + # + prompt="$msg_please_enter_password" + hline="$hline_alnum_punc_tab_enter" + f_dialog_inputbox_size height width \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$prompt" \ + "$hline" + + # + # Continue prompting until they either Cancel, succeed + # or exceed the number of allowed failures. + # + local password nfailures=0 retval + while [ $nfailures -lt $PASSWD_TRIES ]; do + if [ "$USE_XDIALOG" ]; then + password=$( $DIALOG \ + --title "$DIALOG_TITLE" \ + --backtitle "$DIALOG_BACKTITLE" \ + --hline "$hline" \ + --ok-label "$msg_ok" \ + --cancel-label "$msg_cancel" \ + --password --inputbox "$prompt" \ + $height $width \ + 2>&1 > /dev/null + ) + retval=$? + + # Catch X11-related errors + if [ $retval -eq $DIALOG_ESC ]; then + f_die $retval "$password" + elif [ $retval -ne $DIALOG_OK ]; then + # User cancelled + exit $retval + fi + else + password=$( $DIALOG \ + --title "$DIALOG_TITLE" \ + --backtitle "$DIALOG_BACKTITLE" \ + --hline "$hline" \ + --ok-label "$msg_ok" \ + --cancel-label "$msg_cancel" \ + --insecure \ + --passwordbox "$prompt" \ + $height $width \ + 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD + ) || exit $? + fi + debug= f_dialog_line_sanitize password + + # + # Validate sudo(8) credentials + # + sudo -S -v 2> /dev/null <<-EOF + $password + EOF + retval=$? + unset password # scrub memory + if [ $retval -eq $SUCCESS ]; then + # Access granted... + break + else + # Access denied... + nfailures=$(( $nfailures + 1 )) + + # introduce a short delay + if [ $nfailures -lt $PASSWD_TRIES ]; then + f_dialog_info "$msg_sorry_try_again" + sleep 1 + fi + fi + done + + # + # If user exhausted number of allowed password tries, log + # the security event and exit immediately. + # + if [ $nfailures -ge $PASSWD_TRIES ]; then + f_sprintf msg "$msg_nfailed_attempts" "$nfailures" + logger -p auth.notice -t sudo " " \ + "$USER : $msg" \ + "; TTY=$(tty)" \ + "; PWD=$PWD" \ + "; USER=root" \ + "; COMMAND=$0" + f_die 1 "sudo: $msg" + fi + fi + + # Use xauth(1) to grant root the ability to use this X11/SSH session + if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" -a "$DISPLAY" ]; then + f_have xauth || f_die 1 \ + "$msg_no_such_file_or_directory" "$pgm" "xauth" + local HOSTNAME displaynum + HOSTNAME=$(hostname) + displaynum="${DISPLAY#*:}" + xauth -f ~/.Xauthority extract - $HOSTNAME/unix:$displaynum \ + $HOSTNAME:$displaynum | sudo sh -c 'xauth -ivf \ + ~root/.Xauthority merge - > /dev/null 2>&1' + fi + + # Re-execute ourselves with sudo(8) + f_dprintf "%s: Becoming root via sudo(8)..." mustberoot.subr + if [ $ARGC -gt 0 ]; then + exec sudo "$0" $ARGV + else + exec sudo "$0" + fi + exit $? # Never reached unless error +} + +# f_authenticate_some_user +# +# Only used if running as root and requires X11 (see USE_XDIALOG below). +# Prompts the user to enter a username and password to be authenticated via +# sudo(8) to proceed. +# +# The following environment variables effect functionality: +# +# USE_XDIALOG Either NULL or Non-NULL. If given a value will indicate +# that Xdialog(1) should be used instead of dialog(1). +# +f_authenticate_some_user() +{ + local msg hline height width + + f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm" + + # + # Secure-mode has been requested. + # + + [ "$USE_XDIALOG" ] || f_die 1 "$msg_secure_mode_requires_x11" + [ "$(id -u)" = "0" ] || f_die 1 "$msg_secure_mode_requires_root" + + # + # Prompt for sudo(8) credentials. + # + + msg="$msg_please_enter_username_password" + hline="$hline_alnum_punc_tab_enter" + f_xdialog_2inputsbox_size height width \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$msg" \ + "$field_username" "" \ + "$field_password" "" + height=$(( $height + 2 )) # Add height for --password + + # + # Continue prompting until they either Cancel, succeed or exceed the + # number of allowed failures. + # + local user_pass nfailures=0 retval + while [ $nfailures -lt $PASSWD_TRIES ]; do + user_pass=$( $DIALOG \ + --title "$DIALOG_TITLE" \ + --backtitle "$DIALOG_BACKTITLE" \ + --hline "$hline" \ + --ok-label "$msg_ok" \ + --cancel-label "$msg_cancel" \ + --password --2inputsbox "$msg" \ + $height $width \ + "$field_username" "" \ + "$field_password" "" \ + 2>&1 > /dev/null ) + retval=$? + + # Catch X11-related errors + [ $retval -eq $DIALOG_ESC ] && f_die $retval "$user_pass" + + # Exit if the user cancelled. + [ $retval -eq $DIALOG_OK ] || exit $retval + + # + # Make sure the user exists and is non-root + # + local user password + user="${user_pass%%/*}" + password="${user_pass#*/}" + unset user_pass # scrub memory + if [ ! "$user" ]; then + nfailures=$(( $nfailures + 1 )) + f_show_msg "$msg_no_username" + continue + fi + if [ ! "$SECURE_ALLOW_ROOT" ]; then + case "$user" in + root|toor) + nfailures=$(( $nfailures + 1 )) + f_show_msg "$msg_user_disallowed" "$user" + continue + esac + fi + if ! f_quietly id "$user"; then + nfailures=$(( $nfailures + 1 )) + if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then + f_show_msg "$msg_unknown_user" "$user" + elif [ $nfailures -lt $PASSWD_TRIES ]; then + f_dialog_info "$msg_sorry_try_again" + sleep 1 + fi + continue + fi + + # + # Validate sudo(8) credentials for given user + # + su -m "$user" <<-EOF + sh <<EOS + sudo -k + sudo -S -v 2> /dev/null <<EOP + $password + EOP + EOS + EOF + retval=$? + unset user + unset password # scrub memory + + if [ $retval -eq $SUCCESS ]; then + # Access granted... + break + else + # Access denied... + nfailures=$(( $nfailures + 1 )) + + # introduce a short delay + if [ $nfailures -lt $PASSWD_TRIES ]; then + f_dialog_info "$msg_sorry_try_again" + sleep 1 + fi + fi + done + + # + # If user exhausted number of allowed password tries, log + # the security event and exit immediately. + # + if [ $nfailures -ge $PASSWD_TRIES ]; then + f_sprintf msg "$msg_nfailed_attempts" "$nfailures" + logger -p auth.notice -t sudo " " \ + "${SUDO_USER:-$USER} : $msg" \ + "; TTY=$(tty)" \ + "; PWD=$PWD" \ + "; USER=root" \ + "; COMMAND=$0" + f_die 1 "sudo: $message" + fi +} + +# f_mustberoot_init +# +# If not already root, make the switch to root by re-executing ourselves via +# sudo(8) using user-supplied credentials. +# +# The following environment variables effect functionality: +# +# SECURE Either NULL or Non-NULL. If given a value will indicate +# that (while running as root) sudo(8) authentication is +# required to proceed. +# +f_mustberoot_init() +{ + if [ "$(id -u)" != "0" -a ! "$SECURE" ]; then + f_become_root_via_sudo + elif [ "$SECURE" ]; then + f_authenticate_some_user + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." mustberoot.subr + +fi # ! $_MUSTBEROOT_SUBR diff --git a/usr.sbin/bsdconfig/share/packages/Makefile b/usr.sbin/bsdconfig/share/packages/Makefile new file mode 100644 index 0000000..bc0e59a --- /dev/null +++ b/usr.sbin/bsdconfig/share/packages/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +FILESDIR= ${SHAREDIR}/bsdconfig/packages +FILES= categories.subr index.subr musthavepkg.subr packages.subr + +.include <bsd.prog.mk> diff --git a/usr.sbin/bsdconfig/share/packages/Makefile.depend b/usr.sbin/bsdconfig/share/packages/Makefile.depend new file mode 100644 index 0000000..f80275d --- /dev/null +++ b/usr.sbin/bsdconfig/share/packages/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bsdconfig/share/packages/categories.subr b/usr.sbin/bsdconfig/share/packages/categories.subr new file mode 100644 index 0000000..474c41d --- /dev/null +++ b/usr.sbin/bsdconfig/share/packages/categories.subr @@ -0,0 +1,209 @@ +if [ ! "$_PACKAGES_CATEGORIES_SUBR" ]; then _PACKAGES_CATEGORIES_SUBR=1 +# +# Copyright (c) 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..." packages/categories.subr +f_include $BSDCFG_SHARE/strings.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ GLOBALS + +CATEGORIES= +NCATEGORIES=0 + +############################################################ FUNCTIONS + +# f_category_desc_get $category [$var_to_set] +# +# Fetch the description of a given category. Returns success if a match was +# found, otherwise failure. +# +# If $var_to_set is missing or NULL, the category description 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_category_desc_get() +{ + local __category="$1" __var_to_set="$2" __cat __varcat + + # Return failure if $category + [ "$__category" ] || return $FAILURE + + for __cat in $CATEGORIES; do + [ "$__cat" = "$__category" ] || continue + f_str2varname $__cat __varcat + f_getvar _category_$__varcat $__var_to_set + return $? + done + return $FAILURE +} + +# f_category_desc_set $category $desc +# +# Store a description in-association with a category. $category should be +# alphanumeric and can include the underscore [_] but should not contain +# whitespace. Returns success unless $category is NULL or no arguments. Use the +# f_category_desc_get() routine with the same $category to retrieve the stored +# description. +# +f_category_desc_set() +{ + local category="$1" desc="$2" + local cat varcat found= + [ "$category" ] || return $FAILURE + for cat in $CATEGORIES; do + [ "$cat" = "$category" ] || continue + f_str2varname $cat varcat + f_isset _category_$varcat || continue + found=1 && break + done + if [ ! "$found" ]; then + CATEGORIES="$CATEGORIES $category" + fi + f_str2varname $category varcat + setvar "_category_$varcat" "$desc" + # Export the variable for awk(1) ENVIRON visibility + export "_category_$varcat" + return $SUCCESS +} + +############################################################ MAIN + +# +# Load descriptions for package categories. Note that we don't internationalize +# category names because this would be confusing for people used to browsing +# the FTP mirrors or are otherwise familiar with an interface that does not +# provide internationalized names. The descriptions can be used to provide i18n +# users a description of the non-i18n category name. +# +f_category() { f_category_desc_set "$1" "$2"; } +f_category All "$msg_all_desc" +f_category accessibility "$msg_accessibility_desc" +f_category afterstep "$msg_afterstep_desc" +f_category arabic "$msg_arabic_desc" +f_category archivers "$msg_archivers_desc" +f_category astro "$msg_astro_desc" +f_category audio "$msg_audio_desc" +f_category benchmarks "$msg_benchmarks_desc" +f_category biology "$msg_biology_desc" +f_category cad "$msg_cad_desc" +f_category chinese "$msg_chinese_desc" +f_category comms "$msg_comms_desc" +f_category converters "$msg_converters_desc" +f_category databases "$msg_databases_desc" +f_category deskutils "$msg_deskutils_desc" +f_category devel "$msg_devel_desc" +f_category dns "$msg_dns_desc" +f_category docs "$msg_docs_desc" +f_category editors "$msg_editors_desc" +f_category elisp "$msg_elisp_desc" +f_category emulators "$msg_emulators_desc" +f_category enlightenment "$msg_enlightenment_desc" +f_category finance "$msg_finance_desc" +f_category french "$msg_french_desc" +f_category ftp "$msg_ftp_desc" +f_category games "$msg_games_desc" +f_category geography "$msg_geography_desc" +f_category german "$msg_german_desc" +f_category gnome "$msg_gnome_desc" +f_category gnustep "$msg_gnustep_desc" +f_category graphics "$msg_graphics_desc" +f_category hamradio "$msg_hamradio_desc" +f_category haskell "$msg_haskell_desc" +f_category hebrew "$msg_hebrew_desc" +f_category hungarian "$msg_hungarian_desc" +f_category ipv6 "$msg_ipv6_desc" +f_category irc "$msg_irc_desc" +f_category japanese "$msg_japanese_desc" +f_category java "$msg_java_desc" +f_category kde "$msg_kde_desc" +f_category kld "$msg_kld_desc" +f_category korean "$msg_korean_desc" +f_category lang "$msg_lang_desc" +f_category linux "$msg_linux_desc" +f_category lisp "$msg_lisp_desc" +f_category mail "$msg_mail_desc" +f_category math "$msg_math_desc" +f_category mbone "$msg_mbone_desc" +f_category misc "$msg_misc_desc" +f_category multimedia "$msg_multimedia_desc" +f_category net "$msg_net_desc" +f_category net-im "$msg_net_im_desc" +f_category net-mgmt "$msg_net_mgmt_desc" +f_category net-p2p "$msg_net_p2p_desc" +f_category news "$msg_news_desc" +f_category palm "$msg_palm_desc" +f_category parallel "$msg_parallel_desc" +f_category pear "$msg_pear_desc" +f_category perl5 "$msg_perl5_desc" +f_category plan9 "$msg_plan9_desc" +f_category polish "$msg_polish_desc" +f_category ports-mgmt "$msg_ports_mgmt_desc" +f_category portuguese "$msg_portuguese_desc" +f_category print "$msg_print_desc" +f_category python "$msg_python_desc" +f_category ruby "$msg_ruby_desc" +f_category rubygems "$msg_rubygems_desc" +f_category russian "$msg_russian_desc" +f_category scheme "$msg_scheme_desc" +f_category science "$msg_science_desc" +f_category security "$msg_security_desc" +f_category shells "$msg_shells_desc" +f_category spanish "$msg_spanish_desc" +f_category sysutils "$msg_sysutils_desc" +f_category tcl "$msg_tcl_desc" +f_category textproc "$msg_textproc_desc" +f_category tk "$msg_tk_desc" +f_category ukrainian "$msg_ukrainian_desc" +f_category vietnamese "$msg_vietnamese_desc" +f_category windowmaker "$msg_windowmaker_desc" +f_category www "$msg_www_desc" +f_category x11 "$msg_x11_desc" +f_category x11-clocks "$msg_x11_clocks_desc" +f_category x11-drivers "$msg_x11_drivers_desc" +f_category x11-fm "$msg_x11_fm_desc" +f_category x11-fonts "$msg_x11_fonts_desc" +f_category x11-servers "$msg_x11_servers_desc" +f_category x11-themes "$msg_x11_themes_desc" +f_category x11-toolkits "$msg_x11_toolkits_desc" +f_category x11-wm "$msg_x11_wm_desc" +f_category xfce "$msg_xfce_desc" +f_category zope "$msg_zope_desc" + +f_count NCATEGORIES $CATEGORIES +f_dprintf "%s: Initialized %u package category descriptions." \ + packages/categories.subr $NCATEGORIES + +f_dprintf "%s: Successfully loaded." packages/categories.subr + +fi # ! $_PACKAGES_CATEGORIES_SUBR diff --git a/usr.sbin/bsdconfig/share/packages/index.subr b/usr.sbin/bsdconfig/share/packages/index.subr new file mode 100644 index 0000000..f3c1713 --- /dev/null +++ b/usr.sbin/bsdconfig/share/packages/index.subr @@ -0,0 +1,414 @@ +if [ ! "$_PACKAGES_INDEX_SUBR" ]; then _PACKAGES_INDEX_SUBR=1 +# +# Copyright (c) 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..." 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-packagesite.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 + # 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 + # + 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 + f_show_info \ + "$msg_located_index_now_reading_package_data_from_it" + 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 + # + + f_show_info "$msg_generating_index_from_pkg_database" + eval "$__var_to_set"='$( pkg rquery -I | sort )' + + # + # Attempt to create the persistant 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 + + f_show_info "$msg_located_index_now_reading_package_data_from_it" + 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 "\"" + + }' )" # 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 diff --git a/usr.sbin/bsdconfig/share/packages/musthavepkg.subr b/usr.sbin/bsdconfig/share/packages/musthavepkg.subr new file mode 100644 index 0000000..929823e --- /dev/null +++ b/usr.sbin/bsdconfig/share/packages/musthavepkg.subr @@ -0,0 +1,87 @@ +if [ ! "$_PACKAGES_MUSTHAVEPKG_SUBR" ]; then _PACKAGES_MUSTHAVEPKG_SUBR=1 +# +# Copyright (c) 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..." packages/musthavepkg.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/mustberoot.subr + +############################################################ FUNCTIONS + +# f_musthavepkg_init +# +# Validate pkg(8) is installed and set $PKG_ABI global if not already set. +# Returns success unless pkg(8) is not installed and user refuses to install +# it (upon prompt when running interactively). +# +f_musthavepkg_init() +{ + local funcname=f_musthavepkg_init + local pkg_abi_awk='$1~/^ABI/{print $NF; exit}' + + if [ "$PKG_ABI" ]; then # Already set + f_dprintf "PKG_ABI=[%s]" "$PKG_ABI" + export PKG_ABI + f_quietly pkg -N -vv # return status (pkg(8) functional?) + return $? + fi + + # Attempt to get PKG_ABI without prematurely bootstrapping pkg(8) + if f_eval_catch -k PKG_ABI $funcname pkg \ + "pkg -N -vv | awk '%s'" "$pkg_abi_awk" + then + f_dprintf "PKG_ABI=[%s]" "$PKG_ABI" + export PKG_ABI + return $SUCCESS + fi + + # pkg(8) not yet bootstrapped; ask for permission unless nonInteractive + f_dialog_yesno "$msg_pkg_not_yet_installed_install_now" || + f_die 1 "$msg_must_have_pkg_to_execute" "$pgm" + + f_mustberoot_init # Have to be root to install pkg(8) + + # Bootstrap pkg(8) + f_dialog_info "$msg_bootstrapping_pkg" + f_eval_catch -k PKG_ABI $funcname pkg \ + "ASSUME_ALWAYS_YES=1 pkg -vv | awk '%s'" "$pkg_abi_awk" || + f_die 1 "$msg_must_have_pkg_to_execute" "$pgm" + + f_dprintf "PKG_ABI=[%s]" "$PKG_ABI" + export PKG_ABI + return $SUCCESS +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." packages/musthavepkg.subr + +fi # ! $_PACKAGES_MUSTHAVEPKG_SUBR diff --git a/usr.sbin/bsdconfig/share/packages/packages.subr b/usr.sbin/bsdconfig/share/packages/packages.subr new file mode 100644 index 0000000..f3ec707 --- /dev/null +++ b/usr.sbin/bsdconfig/share/packages/packages.subr @@ -0,0 +1,1192 @@ +if [ ! "$_PACKAGES_PACKAGES_SUBR" ]; then _PACKAGES_PACKAGES_SUBR=1 +# +# Copyright (c) 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..." "$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 +# 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 +# $_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 + + # 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 diff --git a/usr.sbin/bsdconfig/share/script.subr b/usr.sbin/bsdconfig/share/script.subr new file mode 100644 index 0000000..b562e99 --- /dev/null +++ b/usr.sbin/bsdconfig/share/script.subr @@ -0,0 +1,219 @@ +if [ ! "$_SCRIPT_SUBR" ]; then _SCRIPT_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..." script.subr +f_include $BSDCFG_SHARE/device.subr +f_include $BSDCFG_SHARE/media/any.subr +f_include $BSDCFG_SHARE/media/tcpip.subr +f_include $BSDCFG_SHARE/mustberoot.subr +f_include $BSDCFG_SHARE/networking/services.subr +f_include $BSDCFG_SHARE/packages/packages.subr +f_include $BSDCFG_SHARE/usermgmt/group.subr +f_include $BSDCFG_SHARE/usermgmt/user.subr +f_include $BSDCFG_SHARE/variable.subr + +############################################################ GLOBALS + +RESWORDS= + +############################################################ FUNCTIONS + +# f_resword_new $resword $function +# +# Create a new `reserved' word for scripting purposes. Reswords call pre- +# defined functions but differ from those functions in the following ways: +# +# + Unless noError is set (must be non-NULL), if calling the resword +# results in failure, the application will terminate prematurely. +# + noError is unset after each/every resword is called. +# +# Reswords should not be used in bsdconfig itself (hence the name `reserved +# word') but instead only in scripts loaded through f_script_load(). +# +f_resword_new() +{ + local resword="$1" func="$2" + [ "$resword" ] || return $FAILURE + f_dprintf "script.subr: New resWord %s -> %s" "$resword" "$func" + eval $resword\(\){ f_dispatch $func $resword \"\$@\"\; } + RESWORDS="$RESWORDS${RESWORDS:+ }$resword" +} + +# f_dispatch $func $resword +# +# Wrapper function used by `reserved words' (reswords) to call other functions. +# If $noError is set and non-NULL, a failure result from $func is ignored, +# otherwise the application is prematurely terminated using f_die(). +# +# NOTE: $noError is unset after every call. +# +f_dispatch() +{ + local func="$1" resword="$2" + shift 2 # func resword + f_dprintf "f_dispatch: calling resword \`%s'" "$resword" + eval $func "$@" + local retval=$? + if [ $retval -ne $SUCCESS ]; then + local _ignore_this_error + f_getvar $VAR_NO_ERROR _ignore_this_error + [ "$_ignore_this_error" ] || f_die $retval \ + "$msg_command_failed_rest_of_script_aborted" "$resword" + fi + unset $VAR_NO_ERROR +} + +# f_script_load [$file] +# +# Load a script (usually filled with reswords). If $file is missing or NULL, +# use one of the following instead (in order): +# +# $configFile (global) +# install.cfg +# /stand/install.fg +# /tmp/install.cfg +# +# Unknown/unregistered reswords will generate sh(1) syntax errors but not cause +# premature termination. +# +# Returns success if a script was loaded and itself returned success. +# +f_script_load() +{ + local funcname=f_script_load + local script="$1" config_file retval=$SUCCESS + + f_dprintf "$funcname: script=[%s]" "$script" + if [ ! "$script" ]; then + f_getvar $VAR_CONFIG_FILE config_file + for script in \ + $config_file \ + install.cfg \ + /stand/install.cfg \ + /tmp/install.cfg \ + ; do + [ -e "$script" ] && break + done + fi + + local old_interactive= + f_getvar $VAR_NONINTERACTIVE old_interactive # save a copy + + # Hint to others that we're running from a script, should they care + setvar $VAR_NONINTERACTIVE yes + + if [ "$script" = "-" ]; then + f_dprintf "$funcname: Loading script from stdin" + eval "$( cat )" + retval=$? + else + f_dprintf "$funcname: Loading script \`%s'" "$script" + if [ ! -e "$script" ]; then + f_show_msg "$msg_unable_to_open" "$script" + return $FAILURE + fi + . "$script" + retval=$? + fi + + [ "$old_interactive" ] && + setvar $VAR_NONINTERACTIVE "$old_interactive" + + return $retval +} + +############################################################ MAIN + +# +# Reserved words meant for scripting +# + +# this file +f_resword_new loadConfig f_script_load + +# device.subr +f_resword_new deviceRescan f_device_rescan + +# media/common.subr +f_resword_new mediaOpen f_media_open +f_resword_new mediaClose f_media_close + +# media includes +f_resword_new mediaGetType f_media_get_type # media/any.subr +f_resword_new mediaSetCDROM f_media_set_cdrom # media/cdrom.subr +f_resword_new mediaSetDOS f_media_set_dos # media/dos.subr +f_resword_new mediaSetDirectory f_media_set_directory # media/directory.subr +f_resword_new mediaSetFloppy f_media_set_floppy # media/floppy.subr +f_resword_new mediaSetNFS f_media_set_nfs # media/nfs.subr +f_resword_new mediaSetUFS f_media_set_ufs # media/ufs.subr +f_resword_new mediaSetUSB f_media_set_usb # media/usb.subr +f_resword_new optionsEditor f_media_options_menu # media/options.subr +f_resword_new tcpMenuSelect f_dialog_menu_select_tcp # media/tcp.subr + +# media/ftp.subr +f_resword_new mediaSetFTP f_media_set_ftp +f_resword_new mediaSetFTPActive f_media_set_ftp_active +f_resword_new mediaSetFTPPassive f_media_set_ftp_passive +f_resword_new mediaSetFTPUserPass f_media_set_ftp_userpass + +# media/http.subr +f_resword_new mediaSetHTTP f_media_set_http + +# media/httpproxy.subr +f_resword_new mediaSetHTTPProxy f_media_set_http_proxy + +# networking/services.subr +f_resword_new configPCNFSD f_config_pcnfsd + +# packages/packages.subr +f_resword_new configPackages f_package_config +f_resword_new packageAdd f_package_add +f_resword_new packageDelete f_package_delete +f_resword_new packageReinstall f_package_reinstall + +# usermgmt/group.subr +f_resword_new addGroup f_group_add +f_resword_new deleteGroup f_group_delete +f_resword_new editGroup f_group_edit + +# usermgmt/user.subr +f_resword_new addUser f_user_add +f_resword_new deleteUser f_user_delete +f_resword_new editUser f_user_edit + +# variable.subr +f_resword_new installVarDefaults f_variable_set_defaults +f_resword_new dumpVariables f_dump_variables + +f_dprintf "%s: Successfully loaded." script.subr + +fi # ! $_SCRIPT_SUBR diff --git a/usr.sbin/bsdconfig/share/strings.subr b/usr.sbin/bsdconfig/share/strings.subr new file mode 100644 index 0000000..487e061 --- /dev/null +++ b/usr.sbin/bsdconfig/share/strings.subr @@ -0,0 +1,454 @@ +if [ ! "$_STRINGS_SUBR" ]; then _STRINGS_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 + +############################################################ GLOBALS + +# +# A Literal newline (for use with f_replace_all(), or IFS, or whatever) +# +NL=" +" # END-QUOTE + +# +# Valid characters that can appear in an sh(1) variable name +# +# Please note that the character ranges A-Z and a-z should be avoided because +# these can include accent characters (which are not valid in a variable name). +# For example, A-Z matches any character that sorts after A but before Z, +# including A and Z. Although ASCII order would make more sense, that is not +# how it works. +# +VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" + +############################################################ FUNCTIONS + +# f_substr "$string" $start [$length] +# +# Simple wrapper to awk(1)'s `substr' function. +# +f_substr() +{ + local string="$1" start="${2:-0}" len="${3:-0}" + echo "$string" | awk "{ print substr(\$0, $start, $len) }" +} + +# f_snprintf $var_to_set $size $format [$arguments ...] +# +# Similar to snprintf(3), write at most $size number of bytes into $var_to_set +# using printf(1) syntax (`$format [$arguments ...]'). The value of $var_to_set +# is NULL unless at-least one byte is stored from the output. +# +f_snprintf() +{ + local __var_to_set="$1" __size="$2" + shift 2 # var_to_set size + eval "$__var_to_set"=\$\( printf -- \"\$@\" \| \ + awk -v max=\"\$__size\" \'' + { + len = length($0) + max -= len + print substr($0,0,(max > 0 ? len : max + len)) + if ( max < 0 ) exit + max-- + }'\' \) +} + +# f_sprintf $var_to_set $format [$arguments ...] +# +# Similar to sprintf(3), write a string into $var_to_set using printf(1) syntax +# (`$format [$arguments ...]'). +# +f_sprintf() +{ + local __var_to_set="$1" + shift 1 # var_to_set + eval "$__var_to_set"=\$\( printf -- \"\$@\" \) +} + +# f_vsnprintf $var_to_set $size $format $format_args +# +# Similar to vsnprintf(3), write at most $size number of bytes into $var_to_set +# using printf(1) syntax (`$format $format_args'). The value of $var_to_set is +# NULL unless at-least one byte is stored from the output. +# +# Example 1: +# +# limit=7 format="%s" +# format_args="'abc 123'" # 3-spaces between abc and 123 +# f_vsnprintf foo $limit "$format" "$format_args" # foo=[abc 1] +# +# Example 2: +# +# limit=12 format="%s %s" +# format_args=" 'doghouse' 'foxhound' " +# # even more spaces added to illustrate escape-method +# f_vsnprintf foo $limit "$format" "$format_args" # foo=[doghouse fox] +# +# Example 3: +# +# limit=13 format="%s %s" +# f_shell_escape arg1 'aaa"aaa' # arg1=[aaa"aaa] (no change) +# f_shell_escape arg2 "aaa'aaa" # arg2=[aaa'\''aaa] (escaped s-quote) +# format_args="'$arg1' '$arg2'" # use single-quotes to surround args +# f_vsnprintf foo $limit "$format" "$format_args" # foo=[aaa"aaa aaa'a] +# +# In all of the above examples, the call to f_vsnprintf() does not change. Only +# the contents of $limit, $format, and $format_args changes in each example. +# +f_vsnprintf() +{ + eval f_snprintf \"\$1\" \"\$2\" \"\$3\" $4 +} + +# f_vsprintf $var_to_set $format $format_args +# +# Similar to vsprintf(3), write a string into $var_to_set using printf(1) +# syntax (`$format $format_args'). +# +f_vsprintf() +{ + eval f_sprintf \"\$1\" \"\$2\" $3 +} + +# f_longest_line_length +# +# Simple wrapper to an awk(1) script to print the length of the longest line of +# input (read from stdin). Supports the newline escape-sequence `\n' for +# splitting a single line into multiple lines. +# +f_longest_line_length_awk=' +BEGIN { longest = 0 } +{ + if (split($0, lines, /\\n/) > 1) + { + for (n in lines) + { + len = length(lines[n]) + longest = ( len > longest ? len : longest ) + } + } + else + { + len = length($0) + longest = ( len > longest ? len : longest ) + } +} +END { print longest } +' +f_longest_line_length() +{ + awk "$f_longest_line_length_awk" +} + +# f_number_of_lines +# +# Simple wrapper to an awk(1) script to print the number of lines read from +# stdin. Supports newline escape-sequence `\n' for splitting a single line into +# multiple lines. +# +f_number_of_lines_awk=' +BEGIN { num_lines = 0 } +{ + num_lines += split(" "$0, unused, /\\n/) +} +END { print num_lines } +' +f_number_of_lines() +{ + awk "$f_number_of_lines_awk" +} + +# f_isinteger $arg +# +# Returns true if argument is a positive/negative whole integer. +# +f_isinteger() +{ + local arg="${1#-}" + [ "${arg:-x}" = "${arg%[!0-9]*}" ] +} + +# f_uriencode [$text] +# +# Encode $text for the purpose of embedding safely into a URL. Non-alphanumeric +# characters are converted to `%XX' sequence where XX represents the hexa- +# decimal ordinal of the non-alphanumeric character. If $text is missing, data +# is instead read from standard input. +# +f_uriencode_awk=' +BEGIN { + output = "" + for (n = 0; n < 256; n++) pack[sprintf("%c", n)] = sprintf("%%%02x", n) +} +{ + sline = "" + slen = length($0) + for (n = 1; n <= slen; n++) { + char = substr($0, n, 1) + if ( char !~ /^[[:alnum:]_]$/ ) char = pack[char] + sline = sline char + } + output = output ( output ? "%0a" : "" ) sline +} +END { print output } +' +f_uriencode() +{ + if [ $# -gt 0 ]; then + echo "$1" | awk "$f_uriencode_awk" + else + awk "$f_uriencode_awk" + fi +} + +# f_uridecode [$text] +# +# Decode $text from a URI. Encoded characters are converted from their `%XX' +# sequence into original unencoded ASCII sequences. If $text is missing, data +# is instead read from standard input. +# +f_uridecode_awk=' +BEGIN { for (n = 0; n < 256; n++) chr[n] = sprintf("%c", n) } +{ + sline = "" + slen = length($0) + for (n = 1; n <= slen; n++) + { + seq = substr($0, n, 3) + if ( seq ~ /^%[[:xdigit:]][[:xdigit:]]$/ ) { + hex = substr(seq, 2, 2) + sline = sline chr[sprintf("%u", "0x"hex)] + n += 2 + } else + sline = sline substr(seq, 1, 1) + } + print sline +} +' +f_uridecode() +{ + if [ $# -gt 0 ]; then + echo "$1" | awk "$f_uridecode_awk" + else + awk "$f_uridecode_awk" + fi +} + +# f_replaceall $string $find $replace [$var_to_set] +# +# Replace all occurrences of $find in $string with $replace. If $var_to_set is +# either missing or NULL, the variable name is produced on standard out for +# capturing in a sub-shell (which is less recommended due to performance +# degradation). +# +# To replace newlines or a sequence containing the newline character, use $NL +# as `\n' is not supported. +# +f_replaceall() +{ + local __left="" __right="$1" + local __find="$2" __replace="$3" __var_to_set="$4" + while :; do + case "$__right" in *$__find*) + __left="$__left${__right%%$__find*}$__replace" + __right="${__right#*$__find}" + continue + esac + break + done + __left="$__left${__right#*$__find}" + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" "$__left" + else + echo "$__left" + fi +} + +# f_str2varname $string [$var_to_set] +# +# Convert a string into a suitable value to be used as a variable name +# by converting unsuitable characters into the underscrore [_]. If $var_to_set +# is either missing or NULL, the variable name is produced on standard out for +# capturing in a sub-shell (which is less recommended due to performance +# degradation). +# +f_str2varname() +{ + local __string="$1" __var_to_set="$2" + f_replaceall "$__string" "[!$VALID_VARNAME_CHARS]" "_" "$__var_to_set" +} + +# f_shell_escape $string [$var_to_set] +# +# Escape $string for shell eval statement(s) by replacing all single-quotes +# with a special sequence that creates a compound string when interpolated +# by eval with surrounding single-quotes. +# +# For example: +# +# foo="abc'123" +# f_shell_escape "$foo" bar # bar=[abc'\''123] +# eval echo \'$bar\' # produces abc'123 +# +# This is helpful when processing an argument list that has to retain its +# escaped structure for later evaluations. +# +# WARNING: Surrounding single-quotes are not added; this is the responsibility +# of the code passing the escaped values to eval (which also aids readability). +# +f_shell_escape() +{ + local __string="$1" __var_to_set="$2" + f_replaceall "$__string" "'" "'\\''" "$__var_to_set" +} + +# f_shell_unescape $string [$var_to_set] +# +# The antithesis of f_shell_escape(), this function takes an escaped $string +# and expands it. +# +# For example: +# +# foo="abc'123" +# f_shell_escape "$foo" bar # bar=[abc'\''123] +# f_shell_unescape "$bar" # produces abc'123 +# +f_shell_unescape() +{ + local __string="$1" __var_to_set="$2" + f_replaceall "$__string" "'\\''" "'" "$__var_to_set" +} + +# f_expand_number $string [$var_to_set] +# +# Unformat $string into a number, optionally to be stored in $var_to_set. This +# function follows the SI power of two convention. +# +# The prefixes are: +# +# Prefix Description Multiplier +# k kilo 1024 +# M mega 1048576 +# G giga 1073741824 +# T tera 1099511627776 +# P peta 1125899906842624 +# E exa 1152921504606846976 +# +# NOTE: Prefixes are case-insensitive. +# +# Upon successful completion, success status is returned; otherwise the number +# -1 is produced ($var_to_set set to -1 or if $var_to_set is NULL or missing) +# on standard output. In the case of failure, the error status will be one of: +# +# Status Reason +# 1 Given $string contains no digits +# 2 An unrecognized prefix was given +# 3 Result too large to calculate +# +f_expand_number() +{ + local __string="$1" __var_to_set="$2" + local __cp __num __bshift __maxinput + + # Remove any leading non-digits + __string="${__string#${__string%%[0-9]*}}" + + # Store the numbers (no trailing suffix) + __num="${__string%%[!0-9]*}" + + # Produce `-1' if string didn't contain any digits + if [ ! "$__num" ]; then + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" -1 + else + echo -1 + fi + return 1 # 1 = "Given $string contains no digits" + fi + + # Remove all the leading numbers from the string to get at the prefix + __string="${__string#"$__num"}" + + # + # Test for invalid prefix (and determine bitshift length) + # + case "$__string" in + ""|[[:space:]]*) # Shortcut + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" $__num + else + echo $__num + fi + return $SUCCESS ;; + [Kk]*) __bshift=10 ;; + [Mm]*) __bshift=20 ;; + [Gg]*) __bshift=30 ;; + [Tt]*) __bshift=40 ;; + [Pp]*) __bshift=50 ;; + [Ee]*) __bshift=60 ;; + *) + # Unknown prefix + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" -1 + else + echo -1 + fi + return 2 # 2 = "An unrecognized prefix was given" + esac + + # Determine if the wheels fall off + __maxinput=$(( 0x7fffffffffffffff >> $__bshift )) + if [ $__num -gt $__maxinput ]; then + # Input (before expanding) would exceed 64-bit signed int + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" -1 + else + echo -1 + fi + return 3 # 3 = "Result too large to calculate" + fi + + # Shift the number out and produce it + __num=$(( $__num << $__bshift )) + if [ "$__var_to_set" ]; then + setvar "$__var_to_set" $__num + else + echo $__num + fi +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." strings.subr + +fi # ! $_STRINGS_SUBR diff --git a/usr.sbin/bsdconfig/share/struct.subr b/usr.sbin/bsdconfig/share/struct.subr new file mode 100644 index 0000000..78c785f --- /dev/null +++ b/usr.sbin/bsdconfig/share/struct.subr @@ -0,0 +1,206 @@ +if [ ! "$_STRUCT_SUBR" ]; then _STRUCT_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 + +############################################################ FUNCTIONS + +# f_struct_define $type $member_name1 ... +# +# Define a new `structure' type $type made up of the properties $member_name1 +# $member_name2 and so-on. Properties are not typed and can hold any type of +# data (including names of other structs). +# +# Before creating instances of a struct (using f_struct_new $type $name) you +# should use this function to define $type. +# +# Both $type and member names should consist only of alpha-numeric letters or +# the underscore. +# +f_struct_define() +{ + local type="$1" + [ "$type" ] || return $FAILURE + shift + setvar "_struct_typedef_$type" "$*" +} + +# f_struct_new $type $name +# +# Create a new `structure' named $name of type $type. There are two ways to +# access properties of a struct, but they are not equal (each method has its +# own unique benefits, discussed below). +# +# The primary method of accessing (both setting and getting) properties of any +# struct is through the f_struct() function below. +# +# The secondary method of accessing data is by using $name as a function. +# +# Both access methods are cross-platform compatible with any version of sh(1). +# Below is an example of the primary access method: +# +# f_struct_new MY_STRUCT_TYPE my_struct +# f_struct my_struct set abc 123 +# f_struct my_struct get abc # prints 123 to stdout +# f_struct my_struct get abc abc # sets local variable $abc to 123 +# +# Alternatively, the secondary access method (details below): +# +# f_struct_new MY_STRUCT_TYPE my_struct +# my_struct set abc 123 +# my_struct get abc # prints 123 to stdout +# my_struct get abc abc # sets local variable $abc to 123 +# +# The secondary form should only be used if/when: +# + You are certain that the structure already exists +# + You want a syntax error if/when the struct does not exist +# +# The primary benefit to the secondary form is syntax cleanliness and read- +# ability. If you are unsure if a given struct exists (which would cause a +# syntax error when using this form), you can use the primary access method to +# first test for the existence of the struct. For example: +# +# if f_struct my_struct; then +# my_struct get abc # only executed if my_struct exists +# fi +# +# For more information, see the f_struct() function. +# +f_struct_new() +{ + local type="$1" name="$2" + f_dprintf "f_struct_new: type=[%s] name=[%s]" "$type" "$name" + [ "$name" ] || return $FAILURE + setvar "_struct_type_$name" "$type" || return $FAILURE + # OK to use bare $name at this point + eval $name\(\){ f_struct $name \"\$@\"\; } +} + +# f_struct $name +# f_struct $name get $property [$var_to_set] +# f_struct $name set $property $new_value +# f_struct $name unset $property +# +# Access routine for getting, setting, unsetting, and testing properties of +# `structures'. +# +# If only given $name, returns success if struct $name has been created (using +# the f_struct_new() function above). +# +# For getting properties of a struct (versus setting) there are two methods of +# access. If $var_to_set is missing or NULL, the value of the property 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). Returns success unless the property is unset. +# +# For setting properties of a struct, sets the value of $property to $new_value +# and returns success. +# +# For unsetting, the underlying environment variable associated with the given +# $property is unset. +# +f_struct() +{ + local __name="$1" __action="$2" __property="$3" + case $# in + 0) return $FAILURE ;; + 1) f_have "$__name" ;; + *) case "$__action" in + get) local __var_to_set="$4" + f_getvar "_struct_value_${__name}_$__property" "$__var_to_set" + ;; + set) local new_value="$4" + setvar "_struct_value_${__name}_$__property" "$new_value" ;; + unset) unset "_struct_value_${__name}_$__property" ;; + esac + esac + # Return the status of the last command above +} + +# f_struct_free $name +# +# Unset the collection of environment variables and accessor-function +# associated with struct $name. +# +f_struct_free() +{ + local name="$1" type member members + f_getvar "_struct_type_$name" type + f_dprintf "f_struct_free: name=[%s] type=[%s]" "$name" "$type" + [ "$name" ] || return $FAILURE + f_getvar "_struct_typedef_$type" members + for member in $members; do + f_struct "$name" unset $member + done + unset -f "$name" + unset "_struct_type_$name" +} + +# f_struct_copy $from_name $to_name +# +# Copy the properties of one struct to another. If struct $to_name does not +# exist, it is created. If struct $from_name does not exist, nothing is done +# and struct $to_name remains unmodified. +# +# Returns success unless struct $to_name did not exist and f_struct_new() was +# unable to create it. +# +f_struct_copy() +{ + local from_name="$1" to_name="$2" type + f_dprintf "f_struct_copy: from_name=[%s] to_name=[%s]" \ + "$from_name" "$to_name" + f_getvar "_struct_type_$from_name" type + f_struct "$to_name" || + f_struct_new "$type" "$to_name" || return $FAILURE + f_struct "$from_name" || return $SUCCESS + f_dprintf "f_struct_copy: copying properties from %s to %s" \ + "$from_name" "$to_name" + local property properties from_value n=0 k=0 + f_getvar "_struct_typedef_$type" properties + for property in $properties; do + k=$(( $k + 1 )) + if f_struct "$from_name" get $property from_value; then + f_struct "$to_name" set $property "$from_value" + n=$(( $n + 1 )) + else + f_struct "$to_name" unset $property + fi + done + f_dprintf "f_struct_copy: copied %u of %u properties from %s to %s" \ + "$n" "$k" "$from_name" "$to_name" +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." struct.subr + +fi # ! $_STRUCT_SUBR diff --git a/usr.sbin/bsdconfig/share/sysrc.subr b/usr.sbin/bsdconfig/share/sysrc.subr new file mode 100644 index 0000000..346bf10 --- /dev/null +++ b/usr.sbin/bsdconfig/share/sysrc.subr @@ -0,0 +1,746 @@ +if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_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" +[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1 + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +if [ ! "$_SYSRC_JAILED" ]; then + f_dprintf "%s: loading includes..." sysrc.subr + f_include_lang $BSDCFG_LIBE/include/messages.subr +fi + +############################################################ CONFIGURATION + +# +# Standard pathnames (inherit values from shell if available) +# +: ${RC_DEFAULTS:="/etc/defaults/rc.conf"} + +############################################################ GLOBALS + +# +# Global exit status variables +# +SUCCESS=0 +FAILURE=1 + +# +# Valid characters that can appear in an sh(1) variable name +# +# Please note that the character ranges A-Z and a-z should be avoided because +# these can include accent characters (which are not valid in a variable name). +# For example, A-Z matches any character that sorts after A but before Z, +# including A and Z. Although ASCII order would make more sense, that is not +# how it works. +# +VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" + +############################################################ FUNCTIONS + +# f_clean_env [ --except $varname ... ] +# +# Unset all environment variables in the current scope. An optional list of +# arguments can be passed, indicating which variables to avoid unsetting; the +# `--except' is required to enable the exclusion-list as the remainder of +# positional arguments. +# +# Be careful not to call this in a shell that you still expect to perform +# $PATH expansion in, because this will blow $PATH away. This is best used +# within a sub-shell block "(...)" or "$(...)" or "`...`". +# +f_clean_env() +{ + local var arg except= + + # + # Should we process an exclusion-list? + # + if [ "$1" = "--except" ]; then + except=1 + shift 1 + fi + + # + # Loop over a list of variable names from set(1) built-in. + # + for var in $( set | awk -F= \ + '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \ + | grep -v '^except$' + ); do + # + # In POSIX bourne-shell, attempting to unset(1) OPTIND results + # in "unset: Illegal number:" and causes abrupt termination. + # + [ "$var" = OPTIND ] && continue + + # + # Process the exclusion-list? + # + if [ "$except" ]; then + for arg in "$@" ""; do + [ "$var" = "$arg" ] && break + done + [ "$arg" ] && continue + fi + + unset "$var" + done +} + +# f_sysrc_get $varname +# +# Get a system configuration setting from the collection of system- +# configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf and +# /etc/rc.conf.local) +# +# NOTE: Additional shell parameter-expansion formats are supported. For +# example, passing an argument of "hostname%%.*" (properly quoted) will +# return the hostname up to (but not including) the first `.' (see sh(1), +# "Parameter Expansion" for more information on additional formats). +# +f_sysrc_get() +{ + # Sanity check + [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE + + # Taint-check variable name + case "$1" in + [0-9]*) + # Don't expand possible positional parameters + return $FAILURE ;; + *) + [ "$1" ] || return $FAILURE + esac + + ( # Execute within sub-shell to protect parent environment + + # + # Clear the environment of all variables, preventing the + # expansion of normals such as `PS1', `TERM', etc. + # + f_clean_env --except IFS RC_CONFS RC_DEFAULTS + + . "$RC_DEFAULTS" > /dev/null 2>&1 + + unset RC_DEFAULTS + # no longer needed + + # + # If the query is for `rc_conf_files' then store the value that + # we inherited from sourcing RC_DEFAULTS (above) so that we may + # conditionally restore this value after source_rc_confs in the + # event that RC_CONFS does not customize the value. + # + if [ "$1" = "rc_conf_files" ]; then + _rc_conf_files="$rc_conf_files" + fi + + # + # If RC_CONFS is defined, set $rc_conf_files to an explicit + # value, modifying the default behavior of source_rc_confs(). + # + if [ "${RC_CONFS+set}" ]; then + rc_conf_files="$RC_CONFS" + _rc_confs_set=1 + fi + + source_rc_confs > /dev/null 2>&1 + + # + # If the query was for `rc_conf_files' AND after calling + # source_rc_confs the value has not changed, then we should + # restore the value to the one inherited from RC_DEFAULTS + # before performing the final query (preventing us from + # returning what was set via RC_CONFS when the intent was + # instead to query the value from the file(s) specified). + # + if [ "$1" = "rc_conf_files" -a \ + "$_rc_confs_set" -a \ + "$rc_conf_files" = "$RC_CONFS" \ + ]; then + rc_conf_files="$_rc_conf_files" + unset _rc_conf_files + unset _rc_confs_set + fi + + unset RC_CONFS + # no longer needed + + # + # This must be the last functional line for both the sub-shell + # and the function to preserve the return status from formats + # such as "${varname?}" and "${varname:?}" (see "Parameter + # Expansion" in sh(1) for more information). + # + eval echo '"${'"$1"'}"' 2> /dev/null + ) +} + +# f_sysrc_service_configs [-a|-p] $name [$var_to_set] +# +# Get a list of optional `rc.conf.d' entries sourced by system `rc.d' script +# $name (see rc.subr(8) for additional information on `rc.conf.d'). If $name +# exists in `/etc/rc.d' or $local_startup directories and is an rc(8) script +# the result is a space separated list of `rc.conf.d' entries sourced by the +# $name `rc.d' script. Otherwise, if $name exists as a binary `rc.d' script, +# the result is ``/etc/rc.conf.d/$name /usr/local/etc/rc.conf.d/$name''. The +# result is NULL if $name does not exist. +# +# If $var_to_set is missing or NULL, output is to standard out. Returns success +# if $name was found, failure otherwise. +# +# If `-a' flag is given and $var_to_set is non-NULL, append result to value of +# $var_to_set rather than overwriting current contents. +# +# If `-p' flag is given and $var_to_set is non-NULL, prepend result to value of +# $var_to_set rather than overwriting current contents. +# +# NB: The `-a' and `-p' option flags are mutually exclusive. +# +f_sysrc_service_configs() +{ + local OPTIND=1 OPTARG __flag __append= __prepend= + local __local_startup __dir __spath __stype __names= + + while getopts ap __flag; do + case "$__flag" in + a) __append=1 __prepend= ;; + p) __prepend=1 __append= ;; + esac + done + shift $(( $OPTIND - 1 )) + + [ $# -gt 0 ] || return $FAILURE + local __sname="$1" __var_to_set="$2" + + __local_startup=$( f_sysrc_get local_startup ) + for __dir in /etc/rc.d $__local_startup; do + __spath="$__dir/$__sname" + [ -f "$__spath" -a -x "$__spath" ] || __spath= continue + break + done + [ "$__spath" ] || return $FAILURE + + __stype=$( file -b "$__spath" 2> /dev/null ) + case "$__stype" in + *"shell script"*) + __names=$( exec 9<&1 1>&- 2>&- + last_name= + print_name() { + local name="$1" + [ "$name" = "$last_name" ] && return + echo "$name" >&9 + last_name="$name" + } + eval "$( awk '{ + gsub(/load_rc_config /, "print_name ") + gsub(/run_rc_command /, ": ") + print + }' "$__spath" )" + ) ;; + *) + __names="$__sname" + esac + + local __name __test_path __configs= + for __name in $__names; do + for __dir in /etc/rc.d $__local_startup; do + __test_path="${__dir%/rc.d}/rc.conf.d/$__name" + [ -d "$__test_path" ] || + __configs="$__configs $__test_path" continue + for __test_path in "$__test_path"/*; do + [ -f "$__test_path" ] || continue + __configs="$__configs $__test_path" + done + done + done + __configs="${__configs# }" + + if [ "$__var_to_set" ]; then + local __cur= + [ "$__append" -o "$__prepend" ] && + f_getvar "$__var_to_set" __cur + [ "$__append" ] && __configs="$__cur{$__cur:+ }$__configs" + [ "$__prepend" ] && __configs="$__configs${__cur:+ }$__cur" + setvar "$__var_to_set" "$__configs" + else + echo "$__configs" + fi + + return $SUCCESS +} + +# f_sysrc_get_default $varname +# +# Get a system configuration default setting from the default rc.conf(5) file +# (or whatever RC_DEFAULTS points at). +# +f_sysrc_get_default() +{ + # Sanity check + [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE + + # Taint-check variable name + case "$1" in + [0-9]*) + # Don't expand possible positional parameters + return $FAILURE ;; + *) + [ "$1" ] || return $FAILURE + esac + + ( # Execute within sub-shell to protect parent environment + + # + # Clear the environment of all variables, preventing the + # expansion of normals such as `PS1', `TERM', etc. + # + f_clean_env --except RC_DEFAULTS + + . "$RC_DEFAULTS" > /dev/null 2>&1 + + unset RC_DEFAULTS + # no longer needed + + # + # This must be the last functional line for both the sub-shell + # and the function to preserve the return status from formats + # such as "${varname?}" and "${varname:?}" (see "Parameter + # Expansion" in sh(1) for more information). + # + eval echo '"${'"$1"'}"' 2> /dev/null + ) +} + +# f_sysrc_find $varname +# +# Find which file holds the effective last-assignment to a given variable +# within the rc.conf(5) file(s). +# +# If the variable is found in any of the rc.conf(5) files, the function prints +# the filename it was found in and then returns success. Otherwise output is +# NULL and the function returns with error status. +# +f_sysrc_find() +{ + local varname="${1%%[!$VALID_VARNAME_CHARS]*}" + local regex="^[[:space:]]*$varname=" + local rc_conf_files="$( f_sysrc_get rc_conf_files )" + local conf_files= + local file + + # Check parameters + case "$varname" in + ""|[0-9]*) return $FAILURE + esac + + # + # If RC_CONFS is defined, set $rc_conf_files to an explicit + # value, modifying the default behavior of source_rc_confs(). + # + [ "${RC_CONFS+set}" ] && rc_conf_files="$RC_CONFS" + + # + # Reverse the order of files in rc_conf_files (the boot process sources + # these in order, so we will search them in reverse-order to find the + # last-assignment -- the one that ultimately effects the environment). + # + for file in $rc_conf_files; do + conf_files="$file${conf_files:+ }$conf_files" + done + + # + # Append the defaults file (since directives in the defaults file + # indeed affect the boot process, we'll want to know when a directive + # is found there). + # + conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS" + + # + # Find which file matches assignment to the given variable name. + # + for file in $conf_files; do + [ -f "$file" -a -r "$file" ] || continue + if grep -Eq "$regex" $file; then + echo $file + return $SUCCESS + fi + done + + return $FAILURE # Not found +} + +# f_sysrc_desc $varname +# +# Attempts to return the comments associated with varname from the rc.conf(5) +# defaults file `/etc/defaults/rc.conf' (or whatever RC_DEFAULTS points to). +# +# Multi-line comments are joined together. Results are NULL if no description +# could be found. +# +# This function is a two-parter. Below is the awk(1) portion of the function, +# afterward is the sh(1) function which utilizes the below awk script. +# +f_sysrc_desc_awk=' +# Variables that should be defined on the invocation line: +# -v varname="varname" +# +BEGIN { + regex = "^[[:space:]]*"varname"=" + found = 0 + buffer = "" +} +{ + if ( ! found ) + { + if ( ! match($0, regex) ) next + + found = 1 + sub(/^[^#]*(#[[:space:]]*)?/, "") + buffer = $0 + next + } + + if ( !/^[[:space:]]*#/ || + /^[[:space:]]*[[:alpha:]_][[:alnum:]_]*=/ || + /^[[:space:]]*#[[:alpha:]_][[:alnum:]_]*=/ || + /^[[:space:]]*$/ ) exit + + sub(/(.*#)*[[:space:]]*/, "") + buffer = buffer" "$0 +} +END { + # Clean up the buffer + sub(/^[[:space:]]*/, "", buffer) + sub(/[[:space:]]*$/, "", buffer) + + print buffer + exit ! found +} +' +f_sysrc_desc() +{ + awk -v varname="$1" "$f_sysrc_desc_awk" < "$RC_DEFAULTS" +} + +# f_sysrc_set $varname $new_value +# +# Change a setting in the system configuration files (edits the files in-place +# to change the value in the last assignment to the variable). If the variable +# does not appear in the source file, it is appended to the end of the primary +# system configuration file `/etc/rc.conf'. +# +# This function is a two-parter. Below is the awk(1) portion of the function, +# afterward is the sh(1) function which utilizes the below awk script. +# +f_sysrc_set_awk=' +# Variables that should be defined on the invocation line: +# -v varname="varname" +# -v new_value="new_value" +# +BEGIN { + regex = "^[[:space:]]*"varname"=" + found = retval = 0 +} +{ + # If already found... just spew + if ( found ) { print; next } + + # Does this line match an assignment to our variable? + if ( ! match($0, regex) ) { print; next } + + # Save important match information + found = 1 + matchlen = RSTART + RLENGTH - 1 + + # Store the value text for later munging + value = substr($0, matchlen + 1, length($0) - matchlen) + + # Store the first character of the value + t1 = t2 = substr(value, 0, 1) + + # Assignment w/ back-ticks, expression, or misc. + # We ignore these since we did not generate them + # + if ( t1 ~ /[`$\\]/ ) { retval = 1; print; next } + + # Assignment w/ single-quoted value + else if ( t1 == "'\''" ) { + sub(/^'\''[^'\'']*/, "", value) + if ( length(value) == 0 ) t2 = "" + sub(/^'\''/, "", value) + } + + # Assignment w/ double-quoted value + else if ( t1 == "\"" ) { + sub(/^"(.*\\\\+")*[^"]*/, "", value) + if ( length(value) == 0 ) t2 = "" + sub(/^"/, "", value) + } + + # Assignment w/ non-quoted value + else if ( t1 ~ /[^[:space:];]/ ) { + t1 = t2 = "\"" + sub(/^[^[:space:]]*/, "", value) + } + + # Null-assignment + else if ( t1 ~ /[[:space:];]/ ) { t1 = t2 = "\"" } + + printf "%s%c%s%c%s\n", substr($0, 0, matchlen), \ + t1, new_value, t2, value +} +END { exit retval } +' +f_sysrc_set() +{ + local funcname=f_sysrc_set + local varname="$1" new_value="$2" + + # Check arguments + [ "$varname" ] || return $FAILURE + + # + # Find which rc.conf(5) file contains the last-assignment + # + local not_found= + local file="$( f_sysrc_find "$varname" )" + if [ "$file" = "$RC_DEFAULTS" -o ! "$file" ]; then + # + # We either got a null response (not found) or the variable + # was only found in the rc.conf(5) defaults. In either case, + # let's instead modify the first file from $rc_conf_files. + # + + not_found=1 + + # + # If RC_CONFS is defined, use $RC_CONFS + # rather than $rc_conf_files. + # + if [ "${RC_CONFS+set}" ]; then + file="${RC_CONFS%%[$IFS]*}" + else + file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' ) + fi + fi + + # + # If not found, append new value to last file and return. + # + if [ "$not_found" ]; then + echo "$varname=\"$new_value\"" >> "$file" + return $? + fi + + # + # Perform sanity checks. + # + if [ ! -w "$file" ]; then + f_err "$msg_cannot_create_permission_denied\n" \ + "$pgm" "$file" + return $FAILURE + fi + + # + # Create a new temporary file to write to. + # + local tmpfile + if ! f_eval_catch -dk tmpfile $funcname mktemp 'mktemp -t "%s"' "$pgm" + then + echo "$tmpfile" >&2 + return $FAILURE + fi + + # + # Fixup permissions (else we're in for a surprise, as mktemp(1) creates + # the temporary file with 0600 permissions, and if we simply mv(1) the + # temporary file over the destination, the destination will inherit the + # permissions from the temporary file). + # + local mode + f_eval_catch -dk mode $funcname stat 'stat -f "%%#Lp" "%s"' "$file" || + mode=0644 + f_eval_catch -d $funcname chmod 'chmod "%s" "%s"' "$mode" "$tmpfile" + + # + # Fixup ownership. The destination file _is_ writable (we tested + # earlier above). However, this will fail if we don't have sufficient + # permissions (so we throw stderr into the bit-bucket). + # + local owner + f_eval_catch -dk owner $funcname stat \ + 'stat -f "%%u:%%g" "%s"' "$file" || owner="root:wheel" + f_eval_catch -d $funcname chown 'chown "%s" "%s"' "$owner" "$tmpfile" + + # + # Operate on the matching file, replacing only the last occurrence. + # + local new_contents retval + new_contents=$( tail -r $file 2> /dev/null ) + new_contents=$( echo "$new_contents" | awk -v varname="$varname" \ + -v new_value="$new_value" "$f_sysrc_set_awk" ) + retval=$? + + # + # Write the temporary file contents. + # + echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE + if [ $retval -ne $SUCCESS ]; then + echo "$varname=\"$new_value\"" >> "$tmpfile" + fi + + # + # Taint-check our results. + # + if ! f_eval_catch -d $funcname sh '/bin/sh -n "%s"' "$tmpfile"; then + f_err "$msg_previous_syntax_errors\n" "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Finally, move the temporary file into place. + # + f_eval_catch -de $funcname mv 'mv "%s" "%s"' "$tmpfile" "$file" +} + +# f_sysrc_delete $varname +# +# Remove a setting from the system configuration files (edits files in-place). +# Deletes all assignments to the given variable in all config files. If the +# `-f file' option is passed, the removal is restricted to only those files +# specified, otherwise the system collection of rc_conf_files is used. +# +# This function is a two-parter. Below is the awk(1) portion of the function, +# afterward is the sh(1) function which utilizes the below awk script. +# +f_sysrc_delete_awk=' +# Variables that should be defined on the invocation line: +# -v varname="varname" +# +BEGIN { + regex = "^[[:space:]]*"varname"=" + found = 0 +} +{ + if ( $0 ~ regex ) + found = 1 + else + print +} +END { exit ! found } +' +f_sysrc_delete() +{ + local funcname=f_sysrc_delete + local varname="$1" + local file + + # Check arguments + [ "$varname" ] || return $FAILURE + + # + # Operate on each of the specified files + # + local tmpfile + for file in ${RC_CONFS-$( f_sysrc_get rc_conf_files )}; do + [ -e "$file" ] || continue + + # + # Create a new temporary file to write to. + # + if ! f_eval_catch -dk tmpfile $funcname mktemp \ + 'mktemp -t "%s"' "$pgm" + then + echo "$tmpfile" >&2 + return $FAILURE + fi + + # + # Fixup permissions and ownership (mktemp(1) defaults to 0600 + # permissions) to instead match the destination file. + # + local mode owner + f_eval_catch -dk mode $funcname stat \ + 'stat -f "%%#Lp" "%s"' "$file" || mode=0644 + f_eval_catch -dk owner $funcname stat \ + 'stat -f "%%u:%%g" "%s"' "$file" || owner="root:wheel" + f_eval_catch -d $funcname chmod \ + 'chmod "%s" "%s"' "$mode" "$tmpfile" + f_eval_catch -d $funcname chown \ + 'chown "%s" "%s"' "$owner" "$tmpfile" + + # + # Operate on the file, removing all occurrences, saving the + # output in our temporary file. + # + awk -v varname="$varname" "$f_sysrc_delete_awk" "$file" \ + > "$tmpfile" + if [ $? -ne $SUCCESS ]; then + # The file didn't contain any assignments + rm -f "$tmpfile" + continue + fi + + # + # Taint-check our results. + # + if ! f_eval_catch -d $funcname sh '/bin/sh -n "%s"' "$tmpfile" + then + f_err "$msg_previous_syntax_errors\n" \ + "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Perform sanity checks + # + if [ ! -w "$file" ]; then + f_err "$msg_permission_denied\n" "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Finally, move the temporary file into place. + # + f_eval_catch -de $funcname mv \ + 'mv "%s" "%s"' "$tmpfile" "$file" || return $FAILURE + done +} + +############################################################ MAIN + +f_dprintf "%s: Successfully loaded." sysrc.subr + +fi # ! $_SYSRC_SUBR diff --git a/usr.sbin/bsdconfig/share/variable.subr b/usr.sbin/bsdconfig/share/variable.subr new file mode 100644 index 0000000..c453f67 --- /dev/null +++ b/usr.sbin/bsdconfig/share/variable.subr @@ -0,0 +1,315 @@ +if [ ! "$_VARIABLE_SUBR" ]; then _VARIABLE_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..." variable.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/strings.subr + +############################################################ GLOBALS + +VARIABLES= + +# +# Default behavior is to call f_variable_set_defaults() when loaded. +# +: ${VARIABLE_SELF_INITIALIZE=1} + +# +# File to write when f_dump_variables() is called. +# +: ${VARIABLE_DUMPFILE:=/etc/bsdconfig.vars} + +############################################################ FUNCTIONS + +# f_variable_new $handle $variable +# +# Register a new variable named $variable with the given reference-handle +# $handle. The environment variable $handle is set to $variable allowing you to +# use the f_getvar() function (from common.subr) with $handle to get the value +# of environment variable $variable. For example: +# +# f_variable_new VAR_ABC abc +# +# allows the later indirection: +# +# f_getvar $VAR_ABC +# +# to return the value of environment variable `abc'. Variables registered in +# this manner are recorded in the $VARIABLES environment variable for later +# allowing dynamic enumeration of so-called `registered/advertised' variables. +# +f_variable_new() +{ + local handle="$1" variable="$2" + [ "$handle" ] || return $FAILURE + f_dprintf "variable.subr: New variable %s -> %s" "$handle" "$variable" + setvar $handle $variable + VARIABLES="$VARIABLES${VARIABLES:+ }$handle" +} + +# f_variable_get_value $var [ $fmt [ $opts ... ] ] +# +# Unless nonInteractive is set, prompt the user with a given value (pre-filled +# with the value of $var) and give them the chance to change the value. +# +# Unlike f_getvar() (from common.subr) which can return a variable to the +# caller on standard output, this function has no [meaningful] output. +# +# Returns success unless $var is either NULL or missing. +# +f_variable_get_value() +{ + local var="$1" cp + + [ "$var" ] || return $FAILURE + + if ! { f_getvar $var cp && ! f_interactive; }; then + shift 1 # var + f_dialog_input cp "$( printf "$@" )" "$cp" && setvar $var "$cp" + fi + + return $SUCCESS +} + +# f_variable_set_defaults +# +# Installs sensible defaults for registered/advertised variables. +# +f_variable_set_defaults() +{ + f_dprintf "f_variable_set_defaults: Initializing defaults..." + + # + # Initialize various user-edittable values to their defaults + # + setvar $VAR_EDITOR "${EDITOR:-/usr/bin/ee}" + setvar $VAR_FTP_STATE "auto" + setvar $VAR_FTP_USER "ftp" + setvar $VAR_HOSTNAME "$( hostname )" + setvar $VAR_MEDIA_TIMEOUT "300" + setvar $VAR_NFS_SECURE "NO" + setvar $VAR_NFS_TCP "NO" + setvar $VAR_NFS_V3 "YES" + setvar $VAR_PKG_TMPDIR "/var/tmp" + setvar $VAR_RELNAME "$UNAME_R" + + # + # Debugging + # + if f_debugging; then + local var + for var in \ + $VAR_EDITOR \ + $VAR_FTP_STATE \ + $VAR_FTP_USER \ + $VAR_HOSTNAME \ + $VAR_MEDIA_TIMEOUT \ + $VAR_NFS_SECURE \ + $VAR_NFS_TCP \ + $VAR_NFS_V3 \ + $VAR_PKG_TMPDIR \ + $VAR_RELNAME \ + ; do + f_quietly f_getvar $var + done + fi + + f_dprintf "f_variable_set_defaults: Defaults initialized." +} + +# f_dump_variables +# +# Dump a list of registered/advertised variables and their respective values to +# $VARIABLE_DUMPFILE. Returns success unless the file couldn't be written. If +# an error occurs, it is displayed using f_dialog_msgbox() (from dialog.subr). +# +f_dump_variables() +{ + local err + if ! err=$( + ( for handle in $VARIABLES; do + f_getvar $handle var || continue + f_getvar $var value || continue + f_shell_escape "$value" value + printf "%s='%s'\n" "$var" "$value" + done > "$VARIABLE_DUMPFILE" ) 2>&1 + ); then + f_dialog_msgbox "$err" + return $FAILURE + fi +} + +# f_debugging +# +# Are we in debug mode? Returns success if extra DEBUG information has been +# requested (by setting $debug to non-NULL), otherwise false. +# +f_debugging() +{ + local value + f_getvar $VAR_DEBUG value && [ "$value" ] +} + +# f_interactive +# +# Are we running interactively? Return error if $nonInteractive is set and non- +# NULL, otherwise return success. +# +f_interactive() +{ + local value + ! f_getvar $VAR_NONINTERACTIVE value || [ ! "$value" ] +} + +# f_netinteractive +# +# Has the user specifically requested the network-portion of configuration and +# setup to be performed interactively? Returns success if the user has asked +# for the network configuration to be done interactively even if perhaps over- +# all non-interactive mode has been requested (by setting nonInteractive). +# +# Returns success if $netInteractive is set and non-NULL. +# +f_netinteractive() +{ + local value + f_getvar $VAR_NETINTERACTIVE value && [ "$value" ] +} + +# f_zfsinteractive +# +# Has the user specifically requested the ZFS-portion of configuration and +# setup to be performed interactively? Returns success if the user has asked +# for the ZFS configuration to be done interactively even if perhaps overall +# non-interactive mode has been requested (by setting nonInteractive). +# +# Returns success if $zfsInteractive is set and non-NULL. +# +f_zfsinteractive() +{ + local value + f_getvar $VAR_ZFSINTERACTIVE value && [ "$value" ] +} + +############################################################ MAIN + +# +# Variables that can be tweaked from config files +# +# Handle Variable Name +f_variable_new VAR_CONFIG_FILE configFile +f_variable_new VAR_DEBUG debug +f_variable_new VAR_DEBUG_FILE debugFile +f_variable_new VAR_DIRECTORY_PATH _directoryPath +f_variable_new VAR_DOMAINNAME domainname +f_variable_new VAR_EDITOR editor +f_variable_new VAR_EXTRAS ifconfig_ +f_variable_new VAR_FTP_DIR ftpDirectory +f_variable_new VAR_FTP_HOST ftpHost +f_variable_new VAR_FTP_PASS ftpPass +f_variable_new VAR_FTP_PATH _ftpPath +f_variable_new VAR_FTP_PORT ftpPort +f_variable_new VAR_FTP_STATE ftpState +f_variable_new VAR_FTP_USER ftpUser +f_variable_new VAR_GATEWAY defaultrouter +f_variable_new VAR_GROUP group +f_variable_new VAR_GROUP_GID groupGid +f_variable_new VAR_GROUP_MEMBERS groupMembers +f_variable_new VAR_GROUP_PASSWORD groupPassword +f_variable_new VAR_HOSTNAME hostname +f_variable_new VAR_HTTP_DIR httpDirectory +f_variable_new VAR_HTTP_FTP_MODE httpFtpMode +f_variable_new VAR_HTTP_HOST httpHost +f_variable_new VAR_HTTP_PATH _httpPath +f_variable_new VAR_HTTP_PORT httpPort +f_variable_new VAR_HTTP_PROXY httpProxy +f_variable_new VAR_HTTP_PROXY_HOST httpProxyHost +f_variable_new VAR_HTTP_PROXY_PATH _httpProxyPath +f_variable_new VAR_HTTP_PROXY_PORT httpProxyPort +f_variable_new VAR_IFCONFIG ifconfig_ +f_variable_new VAR_IPADDR ipaddr +f_variable_new VAR_IPV6ADDR ipv6addr +f_variable_new VAR_IPV6_ENABLE ipv6_activate_all_interfaces +f_variable_new VAR_KEYMAP keymap +f_variable_new VAR_MEDIA_TIMEOUT MEDIA_TIMEOUT +f_variable_new VAR_MEDIA_TYPE mediaType +f_variable_new VAR_NAMESERVER nameserver +f_variable_new VAR_NETINTERACTIVE netInteractive +f_variable_new VAR_NETMASK netmask +f_variable_new VAR_NETWORK_DEVICE netDev +f_variable_new VAR_NFS_HOST nfsHost +f_variable_new VAR_NFS_PATH nfsPath +f_variable_new VAR_NFS_SECURE nfs_reserved_port_only +f_variable_new VAR_NFS_TCP nfs_use_tcp +f_variable_new VAR_NFS_V3 nfs_use_v3 +f_variable_new VAR_NONINTERACTIVE nonInteractive +f_variable_new VAR_NO_CONFIRM noConfirm +f_variable_new VAR_NO_ERROR noError +f_variable_new VAR_NO_INET6 noInet6 +f_variable_new VAR_PACKAGE package +f_variable_new VAR_PKG_TMPDIR PKG_TMPDIR +f_variable_new VAR_PORTS_PATH ports +f_variable_new VAR_RELNAME releaseName +f_variable_new VAR_SLOW_ETHER slowEthernetCard +f_variable_new VAR_TRY_DHCP tryDHCP +f_variable_new VAR_TRY_RTSOL tryRTSOL +f_variable_new VAR_UFS_PATH ufs +f_variable_new VAR_USER user +f_variable_new VAR_USER_ACCOUNT_EXPIRE userAccountExpire +f_variable_new VAR_USER_DOTFILES_CREATE userDotfilesCreate +f_variable_new VAR_USER_GECOS userGecos +f_variable_new VAR_USER_GID userGid +f_variable_new VAR_USER_GROUPS userGroups +f_variable_new VAR_USER_GROUP_DELETE userGroupDelete +f_variable_new VAR_USER_HOME userHome +f_variable_new VAR_USER_HOME_CREATE userHomeCreate +f_variable_new VAR_USER_HOME_DELETE userHomeDelete +f_variable_new VAR_USER_LOGIN_CLASS userLoginClass +f_variable_new VAR_USER_PASSWORD userPassword +f_variable_new VAR_USER_PASSWORD_EXPIRE userPasswordExpire +f_variable_new VAR_USER_SHELL userShell +f_variable_new VAR_USER_UID userUid +f_variable_new VAR_ZFSINTERACTIVE zfsInteractive + +# +# Self-initialize unless requested otherwise +# +f_dprintf "%s: VARIABLE_SELF_INITIALIZE=[%s]" \ + variable.subr "$VARIABLE_SELF_INITIALIZE" +case "$VARIABLE_SELF_INITIALIZE" in +""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;; +*) f_variable_set_defaults +esac + +f_dprintf "%s: Successfully loaded." variable.subr + +fi # ! $_VARIABLE_SUBR |