diff options
author | dteske <dteske@FreeBSD.org> | 2012-09-18 22:28:42 +0000 |
---|---|---|
committer | dteske <dteske@FreeBSD.org> | 2012-09-18 22:28:42 +0000 |
commit | 282d6b7f2c0f1fb51d911f75ef9989f62e389985 (patch) | |
tree | 3ef909c692976c6a6b0854f8d722182e78ff8915 /usr.sbin/bsdconfig/share | |
parent | 969b25f00f504248a5c234274661472d753475ad (diff) | |
download | FreeBSD-src-282d6b7f2c0f1fb51d911f75ef9989f62e389985.zip FreeBSD-src-282d6b7f2c0f1fb51d911f75ef9989f62e389985.tar.gz |
Move major includes into /usr/share/bsdconfig for easy external access.
Reviewed by: adrian (co-mentor)
Approved by: adrian (co-mentor)
Diffstat (limited to 'usr.sbin/bsdconfig/share')
-rw-r--r-- | usr.sbin/bsdconfig/share/Makefile | 11 | ||||
-rw-r--r-- | usr.sbin/bsdconfig/share/common.subr | 299 | ||||
-rw-r--r-- | usr.sbin/bsdconfig/share/dialog.subr | 1443 | ||||
-rw-r--r-- | usr.sbin/bsdconfig/share/mustberoot.subr | 362 | ||||
-rw-r--r-- | usr.sbin/bsdconfig/share/strings.subr | 104 | ||||
-rw-r--r-- | usr.sbin/bsdconfig/share/sysrc.subr | 618 |
6 files changed, 2837 insertions, 0 deletions
diff --git a/usr.sbin/bsdconfig/share/Makefile b/usr.sbin/bsdconfig/share/Makefile new file mode 100644 index 0000000..7f299ec --- /dev/null +++ b/usr.sbin/bsdconfig/share/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +NO_OBJ= + +FILESDIR= ${SHAREDIR}/bsdconfig +FILES= common.subr dialog.subr mustberoot.subr strings.subr sysrc.subr + +beforeinstall: + mkdir -p ${DESTDIR}${FILESDIR} + +.include <bsd.prog.mk> diff --git a/usr.sbin/bsdconfig/share/common.subr b/usr.sbin/bsdconfig/share/common.subr new file mode 100644 index 0000000..c724c62 --- /dev/null +++ b/usr.sbin/bsdconfig/share/common.subr @@ -0,0 +1,299 @@ +if [ ! "$_COMMON_SUBR" ]; then _COMMON_SUBR=1 +# +# Copyright (c) 2012 Ron McDowell +# Copyright (c) 2012 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$ +# +############################################################ GLOBALS + +# +# Program name +# +pgm="${0##*/}" + +# +# Program arguments +# +ARGC="$#" +ARGV="$@" + +# +# Global exit status variables +# +SUCCESS=0 +FAILURE=1 + +############################################################ FUNCTIONS + +# +# This is an empty function by default, to use it, copy +# /usr/share/examples/bsdconfig/bsdconfigrc to $HOME/.bsdconfigrc +# +f_dprintf() +{ + : this page intentionally left blank +} + +# f_err $fmt [ $opts ... ] +# +# 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 "$@" +} + +# f_die [ $status [ $fmt [ $opts ... ]]] +# +# 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_msg $fmt [ $opts ... ] +# +# 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_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" + . "$file" || exit $? +} + +# f_include_lang $file +# +# Include a language file. Automatically takes $LANG and $LC_ALL into +# considerationg 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 "lang=[$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 bering 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 "lang=[$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_menu_selection $file $pgm +# +# Process $file looking for $menu_selection values that correspond to $pgm. +# This function is for internationalization (i18n) mapping of the on-disk +# scriptname ($pgm) into the localized language (given language-specific +# $file). If $LANG or $LC_ALL (in orderder of preference, respectively) is set, +# ".encoding" will automatically be appended as a suffix to the provided $file +# pathname. +# +# If, within $file, 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 $file does not exist, error status is returned along with the NULL string. +# +# 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_awk=' +# Variables that should be defined on the invocation line: +# -v pgm="program_name" +# +( $0 ~ "^menu_selection=.*\\|" pgm "\"" ) { + sub(/\|.*/, "") + sub(/^menu_selection="/, "") + print + exit +} +' +f_index_menu_selection() +{ + local file="$1" pgm="$2" + local lang="${LANG:-$LC_ALL}" + + f_dprintf "lang=[$lang]" + + if [ -f "$file.$lang" ]; then + awk -v pgm="$pgm" "$f_index_menusel_awk" "$file.$lang" || + exit $FAILURE + elif [ -f "$file" ]; then + awk -v pgm="$pgm" "$f_index_menusel_awk" "$file" || + exit $FAILURE + else + return $FAILURE + fi +} + +############################################################ 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 + +fi # ! $_COMMON_SUBR diff --git a/usr.sbin/bsdconfig/share/dialog.subr b/usr.sbin/bsdconfig/share/dialog.subr new file mode 100644 index 0000000..cced388 --- /dev/null +++ b/usr.sbin/bsdconfig/share/dialog.subr @@ -0,0 +1,1443 @@ +if [ ! "$_DIALOG_SUBR" ]; then _DIALOG_SUBR=1 +# +# Copyright (c) 2006-2012 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 (INLUDING, 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_include $BSDCFG_SHARE/strings.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ CONFIGURATION + +# +# Default directory to store dialog(1) temporary files +# +: ${DIALOG_TMPDIR:="/tmp"} + +############################################################ 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 + +# +# Default behavior is to call f_dialog_init() automatically if not already +# called manually by the time the first f_dialog_*() function is used. +# +: ${DIALOG_SELF_INITIALIZE=1} + +############################################################ GENERIC 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 [ "$new_title" ]; 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 [ "$new_backtitle" ]; 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_infobox_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, and [optionally] hline returning +# the optimal width and height for the box (not exceeding the actual terminal +# width or height). +# +# Newline character sequences (``\n'') in $prompt are expanded as-is done by +# dialog(1). +# +# Output is in the format of "height width". +# +f_dialog_infobox_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" n=0 + local min_width max_size + + if [ "$USE_XDIALOG" ]; then + min_width=35 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_width=24 + max_size=$( stty size ) # usually "24 80" + fi + + local max_height="${max_size%%[$IFS]*}" + local max_width="${max_size##*[$IFS]}" + local height width=$min_width + + # + # Bump width for long titles (but don't exceed terminal width). + # + n=$(( ${#title} + 4 )) + if [ $n -gt $width -a $n -gt $min_width ]; then + # Add 16.6% width for Xdialog(1) + [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) + + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + + # + # For Xdialog(1), bump width for long backtitles (which appear within + # the window; don't exceed maximum width). + # + if [ "$USE_XDIALOG" ]; then + n=$(( ${#btitle} + 4 )) + n=$(( $n + $n / 6 )) + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # + # Bump width for long prompts (if not already at maximum width). + # + if [ $width -lt $max_width ]; then + n=$( echo "$prompt" | f_longest_line_length ) + n=$(( $n + 4 )) + + # Add 16.6% width for Xdialog(1) + [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) + + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # + # Bump width for long hlines (if not already at maximum width). + # NOTE: Though Xdialog(1) supports `--hline', it's not currently used. + # + if [ ! "$USE_XDIALOG" ]; then + if [ $width -lt $max_width ]; then + n=$(( ${#hline} + 10 )) + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + fi + + # + # Set height based on number of rows in prompt + # + height=$( echo "$prompt" | f_number_of_lines ) + height=$(( $height + 2 )) + + # + # 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 + + # Make sure height is less than maximum screen size + [ $height -le $max_height ] || height=$max_height + + # Return both + echo "$height $width" +} + +# f_dialog_buttonbox_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, and [optionally] hline returning +# the optimal width and height for the box (not exceeding the actual terminal +# width or height). +# +# Newline character sequences (``\n'') in $prompt are expanded as-is done by +# dialog(1). +# +# Output is in the format of "height width". +# +f_dialog_buttonbox_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" + local size="$( f_dialog_infobox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local height="${size%%[$IFS]*}" + local width="${size##*[$IFS]}" + + # Add height to accomodate the buttons + height=$(( $height + 2 )) + + # Adjust for clipping with Xdialog(1) on Linux/GTK2 + [ "$USE_XDIALOG" ] && height=$(( $height + 3 )) + + # + # Enforce maximum height regardless + # + local max_size + if [ "$USE_XDIALOG" ]; then + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + max_size=$( stty size ) # usually "24 80" + fi + local max_height="${max_size%%[$IFS]*}" + [ $height -le $max_height ] || height=$max_height + + # Return both + echo "$height $width" +} + +# f_dialog_inputbox_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, initial text, and [optionally] +# hline returning the optimal width and height for the box (not exceeding the +# actual terminal width and height). +# +# Newline character sequences (``\n'') in $prompt are expanded as-is done by +# dialog(1). +# +# Output is in the format of "height width". +# +f_dialog_inputbox_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" init="$4" hline="$5" n + local size="$( f_dialog_buttonbox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local height="${size%%[$IFS]*}" + local width="${size##*[$IFS]}" + + local min_width max_size + if [ "$USE_XDIALOG" ]; then + min_width=35 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_width=24 + max_size=$( stty size ) # usually "24 80" + fi + local max_height="${max_size%%[$IFS]*}" + local max_width="${max_size##*[$IFS]}" + + # + # Add height to accomodate the input box + # + [ ! "$USE_XDIALOG" ] && height=$(( $height + 3 )) + [ $height -le $max_height ] || height=$max_height + + # + # Bump width for initial text (if not already at maximum width). + # NOTE: Something neither dialog(1)/Xdialog(1) do, but worth it! + # + if [ $width -lt $max_width ]; then + n=$(( ${#init} + 7 )) + + # Add 16.6% width for Xdialog(1) + [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) + + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # Return both + echo "$height $width" +} + +# f_xdialog_2inputsbox_size $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 as arguments (in order of +# appearance) 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 returning the optimal width and height for the box (not exceeding the +# actual terminal width and height). +# +# Newline character sequences (``\n'') in $prompt are expanded as-is done by +# Xdialog(1). +# +# Output is in the format of "height width". +# +f_xdialog_2inputsbox_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" + local label1="$4" init1="$5" label2="$6" init2="$7" n + local size="$( f_dialog_inputbox_size \ + "$title" "$btitle" "$prompt" "$init1" )" + local height="${size%%[$IFS]*}" + local width="${size##*[$IFS]}" + + local min_width=35 + local max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + local max_height="${max_size%%[$IFS]*}" + local max_width="${max_size##*[$IFS]}" + + # Add height for first label + height=$(( $height + 2 )) + + # + # Bump width for first label text (if not already at maximum width). + # + if [ $width -lt $max_width ]; then + n=$(( ${#label1} + 7 )) + + # Add 16.6% width for Xdialog(1) + n=$(( $n + $n / 6 )) + + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # Add height for second label + height=$(( $height + 2 )) + + # + # Bump width for second label text (if not already at maximum width). + # + if [ $width -lt $max_width ]; then + n=$(( ${#label2} + 7 )) + + # Add 16.6% width for Xdialog(1) + n=$(( $n + $n / 6 )) + + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # Add height for a second inputbox + height=$(( $height + 2 )) + + # + # Bump width for second initial text (if not already at maximum width). + # NOTE: Something neither dialog(1)/Xdialog(1) do, but worth it! + # + if [ $width -lt $max_width ]; then + n=$(( ${#init2} + 7 )) + + # Add 16.6% width for Xdialog(1) + n=$(( $n + $n / 6 )) + + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # Return both + echo "$height $width" +} + +# f_dialog_menu_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, hline and list of tag/item pairs, +# returning the optimal width and height for the menu (not exceeding the actual +# terminal width or height). +# +# Output is in the format of "height width rows". +# +f_dialog_menu_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" n=0 + local min_width min_rows max_size + + if [ "$USE_XDIALOG" ]; then + min_width=35 + min_rows=1 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_width=24 + min_rows=0 + max_size=$( stty size ) # usually "24 80" + fi + + local max_width="${max_size##*[$IFS]}" + local max_height="${max_size%%[$IFS]*}" + local box_size="$( f_dialog_infobox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local box_height="${box_size%%[$IFS]*}" + local box_width="${box_size##*[$IFS]}" + local max_rows=$(( $max_height - 8 )) + local height width=$box_width rows=$min_rows + + shift 4 # title/btitle/prompt/hline + + # If there's no prompt, bump the max-rows by 1 + [ "$prompt" ] || max_rows=$(( $max_rows + 1 )) + + # + # The sum total between the longest tag-length and longest item-length + # should be used for the menu width (not to exceed terminal width). + # + # Also, calculate the number of rows (not to exceed terminal height). + # + local longest_tag=0 longest_item=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 -lt $max_rows ] && rows=$(( $rows + 1 )) + done + + # Update width + n=$(( $longest_tag + $longest_item + 10 )) + [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) # Add 16.6% for Xdialog(1) + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + + # Fix rows and set height + [ $rows -gt 0 ] || rows=1 + if [ "$USE_XDIALOG" ]; then + height=$(( $rows + $box_height + 7 )) + else + height=$(( $rows + $box_height + 4 )) + fi + [ $height -le $max_height ] || height=$max_height + + # Return all three + echo "$height $width $rows" +} + +# f_dialog_menu_with_help_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, hline and list of tag/item/help +# triplets, returning the optimal width and height for the menu (not exceeding +# the actual terminal width or height). +# +# Output is in the format of "height width rows". +# +f_dialog_menu_with_help_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" n=0 + local min_width min_rows max_size + + if [ "$USE_XDIALOG" ]; then + min_width=35 + min_rows=1 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_width=24 + min_rows=0 + max_size=$( stty size ) # usually "24 80" + fi + + local max_width="${max_size##*[$IFS]}" + local max_height="${max_size%%[$IFS]*}" + local box_size="$( f_dialog_infobox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local box_height="${box_size%%[$IFS]*}" + local box_width="${box_size##*[$IFS]}" + local max_rows=$(( $max_height - 8 )) + local height width=$box_width rows=$min_rows + + shift 4 # title/btitle/prompt/hline + + # If there's no prompt, bump the max-rows by 1 + [ "$prompt" ] || max_rows=$(( $max_rows + 1 )) + + # + # The sum total between the longest tag-length and longest item-length + # should be used for the menu width (not to exceed terminal width). + # + # Also, calculate the number of rows (not to exceed terminal height). + # + # Also, calculate the longest help while we're here. This will be used + # to influence the width of the menu if (and only-if) using Xdialog(1). + # + local longest_tag=0 longest_item=0 longest_help=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 -lt $max_rows ] && rows=$(( $rows + 1 )) + done + + # Update width + n=$(( $longest_tag + $longest_item + 10 )) + [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) # Add 16.6% for Xdialog(1) + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + + # Update width for help text if using Xdialog(1) + if [ "$USE_XDIALOG" ]; then + n=$(( $longest_help + 10 )) + n=$(( $n + $n / 6 )) # +16.6% + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + fi + + # Fix rows and set height + [ $rows -gt 0 ] || rows=1 + if [ "$USE_XDIALOG" ]; then + height=$(( $rows + $box_height + 8 )) + else + height=$(( $rows + $box_height + 4 )) + fi + [ $height -le $max_height ] || height=$max_height + + # Return all three + echo "$height $width $rows" +} + +# f_dialog_radiolist_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, hline and list of tag/item/status +# triplets, returning the optimal width and height for the radiolist (not +# exceeding the actual terminal width or height). +# +# Output is in the format of "height width rows". +# +f_dialog_radiolist_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" n=0 + local min_width min_rows max_size + + if [ "$USE_XDIALOG" ]; then + min_width=35 + min_rows=1 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_width=24 + min_rows=0 + max_size=$( stty size ) # usually "24 80" + fi + + local max_width="${max_size##*[$IFS]}" + local max_height="${max_size%%[$IFS]*}" + local box_size="$( f_dialog_infobox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local box_height="${box_size%%[$IFS]*}" + local box_width="${box_size##*[$IFS]}" + local max_rows=$(( $max_height - 8 )) + local height width=$box_width rows=$min_rows + + shift 4 # title/btitle/prompt/hline + + # + # The sum total between the longest tag-length, longest item-length, + # and radio-button width should be used for the menu width (not to + # exceed terminal width). + # + # Also, calculate the number of rows (not to exceed terminal height). + # + local longest_tag=0 longest_item=0 + while [ $# -ge 2 ]; do + local tag="$1" item="$2" help="$3" + shift 3 # tag/item/status + + [ ${#tag} -gt $longest_tag ] && longest_tag=${#tag} + [ ${#item} -gt $longest_item ] && longest_item=${#item} + [ $rows -lt $max_rows ] && rows=$(( $rows + 1 )) + done + + # Update width + n=$(( $longest_tag + $longest_item + 13 )) + [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) # Add 16.6% for Xdialog(1) + if [ $n -gt $width -a $n -gt $min_width ]; then + if [ $n -lt $max_width ]; then + width=$n + else + width=$max_width + fi + fi + + # Fix rows and set height + [ $rows -gt 0 ] || rows=1 + if [ "$USE_XDIALOG" ]; then + height=$(( $rows + $box_height + 7 )) + else + height=$(( $rows + $box_height + 4 )) + fi + [ $height -le $max_height ] || height=$max_height + + # Return all three + echo "$height $width $rows" +} + +# f_dialog_calendar_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, and [optionally] hline returning +# the optimal width and height for the box (not exceeding the actual terminal +# width and height). +# +# Newline character sequences (``\n'') in $prompt are expanded as-is done by +# dialog(1). +# +# Output is in the format of "height width". +# +f_dialog_calendar_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" n + local size="$( f_dialog_infobox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local height="${size%%[$IFS]*}" + local width="${size##*[$IFS]}" + + local min_width min_height max_size + if [ "$USE_XDIALOG" ]; then + min_height=15 + min_width=55 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_height=0 + min_width=40 + max_size=$( stty size ) # usually "24 80" + fi + local max_height="${max_size%%[$IFS]*}" + local max_width="${max_size##*[$IFS]}" + + # + # Enforce the minimum width for displaying the calendar + # + [ $width -ge $min_width ] || width=$min_width + + # + # When using dialog(1), the calendar box is unique from other dialog(1) + # boxes in-that the height passed should not accomodate the 15-lines + # required to display the calendar. This does not apply to Xdialog(1). + # + # When using Xdialog(1), the height must accomodate the 15-lines + # required to display the calendar. + # + # NOTE: Also under dialog(1), because we can't predict whether the user + # has disabled shadow's in their `$HOME/.dialogrc' file, we'll subtract + # 16 rather than 15. This does not apply to Xdialog(1). + # + max_height=$(( $max_height - 16 )) + height=$( echo "$prompt" | f_number_of_lines ) + if [ "$USE_XDIALOG" ]; then + # Add height to accomodate for the embedded calendar widget + height=$(( $height + $min_height - 1 )) + + # Also, bump height if backtitle is enabled + if [ "$btitle" ]; then + local n="$( echo "$btitle" | f_number_of_lines )" + height=$(( $height + $n + 2 )) + fi + else + [ "$prompt" ] && height=$(( $height + 1 )) + fi + [ $height -le $max_height ] || height=$max_height + + # + # The calendar box refuses to display if too large. + # + max_width=$(( $max_width - 2 )) + [ $width -le $max_width ] || width=$max_width + + # Return both + echo "$height $width" +} + +# f_dialog_timebox_size $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 as arguments (in order of +# appearance) the title, backtitle, prompt, and [optionally] hline returning +# the optimal width and height for the box (not exceeding the actual terminal +# width and height). +# +# Newline character sequences (``\n'') in $prompt are expanded as-is done by +# dialog(1). +# +# Output is in the format of "height width". +# +f_dialog_timebox_size() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local title="$1" btitle="$2" prompt="$3" hline="$4" n + local size="$( f_dialog_infobox_size \ + "$title" "$btitle" "$prompt" "$hline" )" + local height="${size%%[$IFS]*}" + local width="${size##*[$IFS]}" + + local min_width min_height max_size + if [ "$USE_XDIALOG" ]; then + min_width=40 + max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION + else + min_height=0 + min_width=20 + max_size=$( stty size ) # usually "24 80" + fi + local max_height="${max_size%%[$IFS]*}" + local max_width="${max_size##*[$IFS]}" + + # + # Enforce the minimum width for displaying the timebox + # + [ $width -ge $min_width ] || width=$min_width + + # + # When using dialog(1), the timebox box is unique from other dialog(1) + # boxes in-that the height passed should not accomodate the 6-lines + # required to display the timebox. This does not apply to Xdialog(1). + # + # When using Xdialog(1), the height seems to have no effect. All values + # provide the same results. + # + # NOTE: Also under dialog(1), because we can't predict whether the user + # has disabled shadow's in their `$HOME/.dialogrc' file, we'll subtract + # 7 rather than 6. This does not apply to Xdialog(1). + # + if [ "$USE_XDIALOG" ]; then + height=0 # Autosize; all values produce same results + else + max_height=$(( $max_height - 7 )) + height=$( echo "$prompt" | f_number_of_lines ) + height=$(( $height + 1 )) + [ $height -le $max_height ] || height=$max_height + [ "$prompt" ] && height=$(( $height + 1 )) + fi + + # + # The timebox box refuses to display if too large. + # + max_width=$(( $max_width - 2 )) + [ $width -le $max_width ] || width=$max_width + + # Return both + echo "$height $width" +} + +############################################################ CLEAR FUNCTIONS + +# f_dialog_clear +# +# Clears any/all previous dialog(1) displays. +# +f_dialog_clear() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + $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 dialog_clear) is called. +# +f_dialog_info() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local info_text="$*" + local size="$( f_dialog_infobox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$info_text" )" + + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + ${USE_XDIALOG:+--ignore-eof} \ + ${USE_XDIALOG:+--no-buttons} \ + --infobox \"\$info_text\" $size +} + +# 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. +# +f_xdialog_info() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local info_text="$*" + local size="$( f_dialog_infobox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$info_text" )" + + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --no-close --no-buttons \ + --infobox \"\$info_text\" $size \ + -1 # timeout of -1 means abort when EOF on stdin +} + +############################################################ MSGBOX FUNCTIONS + +# f_dialog_msgbox $msg_text ... +# +# 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() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local msg_text="$*" + local size="$( f_dialog_buttonbox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$msg_text" )" + + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --ok-label \"\$msg_ok\" \ + --msgbox \"\$msg_text\" $size +} + +############################################################ YESNO FUNCTIONS + +# f_dialog_yesno $msg_text ... +# +# 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() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local msg_text="$*" + local hline="$hline_arrows_tab_enter" + local size="$( f_dialog_buttonbox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$msg_text" \ + "$hline" )" + + if [ "$USE_XDIALOG" ]; then + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --hline \"\$hline\" \ + --ok-label \"\$msg_yes\" \ + --cancel-label \"\$msg_no\" \ + --yesno \"\$msg_text\" $size + else + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --hline \"\$hline\" \ + --yes-label \"\$msg_yes\" \ + --no-label \"\$msg_no\" \ + --yesno \"\$msg_text\" $size + fi +} + +# f_dialog_noyes $msg_text ... +# +# 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() +{ + [ "$DIALOG_SELF_INITIALIZE" ] && f_dialog_init + + local msg_text="$*" + local hline="$hline_arrows_tab_enter" + local size="$( f_dialog_buttonbox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$msg_text" \ + "$hline" )" + + if [ "$USE_XDIALOG" ]; then + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --hline \"\$hline\" \ + --default-no \ + --ok-label \"\$msg_yes\" \ + --cancel-label \"\$msg_no\" \ + --yesno \"\$msg_text\" $size + else + eval $DIALOG \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --hline \"\$hline\" \ + --defaultno \ + --yes-label \"\$msg_yes\" \ + --no-label \"\$msg_no\" \ + --yesno \"\$msg_text\" $size + fi +} + +############################################################ INPUT FUNCTIONS + +# f_dialog_inputstr +# +# Obtain the inputstr entered by the user from the most recently displayed +# dialog(1) inputbox and clean up any temporary files. +# +f_dialog_inputstr() +{ + local tmpfile="$DIALOG_TMPDIR/dialog.inputbox.$$" + + [ -f "$tmpfile" ] || return $FAILURE + + # Skip warnings and trim leading/trailing whitespace from user input + awk ' + BEGIN { found = 0 } + { + if ( ! found ) + { + if ( $0 ~ /^$/ ) next + if ( $0 ~ /^Gdk-WARNING \*\*:/ ) next + found = 1 + } + sub(/^[[:space:]]*/, "") + sub(/[[:space:]]*$/, "") + print + } + ' "$tmpfile" 2> /dev/null + f_quietly rm -f "$tmpfile" + + return $SUCCESS +} + +############################################################ MENU FUNCTIONS + +# f_dialog_menutag +# +# Obtain the menutag chosen by the user from the most recently displayed +# dialog(1) menu and clean up any temporary files. +# +f_dialog_menutag() +{ + local tmpfile="$DIALOG_TMPDIR/dialog.menu.$$" + + [ -f "$tmpfile" ] || return $FAILURE + + awk ' + BEGIN { found = 0 } + { + if ( found ) # ... just spew + { + print + next + } + if ( $0 ~ /^$/ ) next + if ( $0 ~ /^Gdk-WARNING \*\*:/ ) next + found = 1 + print + } + ' "$tmpfile" 2> /dev/null + f_quietly rm -f "$tmpfile" + + 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 +} + +############################################################ 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. +# +f_dialog_init() +{ + DIALOG_SELF_INITIALIZE= + + # + # Process stored command-line arguments + # + SECURE=$( set -- "$ARGV" + while getopts S flag > /dev/null; do + case "$flag" in + S) echo 1;; + \?) continue;; + esac + done + ) + USE_XDIALOG=$( set -- "$ARGV" + while getopts SX flag > /dev/null; do + case "$flag" in + S|X) echo 1;; + \?) continue;; + esac + done + ) + + # + # Process `-X' command-line option + # + [ "$USE_XDIALOG" ] && DIALOG=Xdialog + + # + # Sanity check, or die gracefully + # + if ! f_have $DIALOG; then + unset USE_XDIALOG + failed_dialog="$DIALOG" + DIALOG=dialog + f_die 1 "$msg_no_such_file_or_directory" "$pgm" "$failed_dialog" + 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) + 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 + if ! maxsize=$( LANG= LC_ALL= $DIALOG --print-maxsize 2>&1 ) + then + # Xdialog(1) failed, fall back to dialog(1) + unset USE_XDIALOG + size=$( f_dialog_buttonbox_size "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$maxsize" "" ) + eval dialog \ + --title \"\$DIALOG_TITLE\" \ + --backtitle \"\$DIALOG_BACKTITLE\" \ + --ok-label \"\$msg_ok\" \ + --msgbox \"\$maxsize\" $size + exit $FAILURE + fi + + XDIALOG_MAXSIZE=$( + set -- ${maxsize##*:} + + height=${1%,} + width=$2 + + echo $height $width + ) + unset maxsize + 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 + _DIALOG_TITLE="$DIALOG_TITLE" + DIALOG_TITLE="$DIALOG_BACKTITLE" + DIALOG_BACKTITLE="$_DIALOG_TITLE" + unset _DIALOG_TITLE + fi +} + +############################################################ CLEAN-UP FUNCTIONS + +# f_clean_up +# +# Clean-up routines (run when script exits or is killed). +# +f_clean_up() +{ + f_quietly rm -f "$DIALOG_TMPDIR"/dialog.*.$$ +} + +############################################################ MAIN + +# +# Trap signals so we can recover gracefully +# +trap 'f_clean_up' EXIT + +fi # ! $_DIALOG_SUBR diff --git a/usr.sbin/bsdconfig/share/mustberoot.subr b/usr.sbin/bsdconfig/share/mustberoot.subr new file mode 100644 index 0000000..ec3e16a --- /dev/null +++ b/usr.sbin/bsdconfig/share/mustberoot.subr @@ -0,0 +1,362 @@ +if [ ! "$_MUSTBEROOT_SUBR" ]; then _MUSTBEROOT_SUBR=1 +# +# Copyright (c) 2006-2012 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 (INLUDING, 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_include $BSDCFG_SHARE/dialog.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 msg hline size + + [ "$( id -u )" = "0" ] && return $SUCCESS + + f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm" + + # + # 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. + # + msg="$msg_please_enter_password" + hline="$hline_alnum_punc_tab_enter" + size=$( f_dialog_inputbox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$msg" \ + "$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 "$msg" $size \ + 2>&1 > /dev/null ) + retval=$? + + # Catch X11-related errors + [ $retval -eq 255 ] && + f_die $retval "$password" + else + $DIALOG \ + --title "$DIALOG_TITLE" \ + --backtitle "$DIALOG_BACKTITLE" \ + --hline "$hline" \ + --ok-label "$msg_ok" \ + --cancel-label "$msg_cancel" \ + --insecure \ + --passwordbox "$msg" $size \ + 2> "$DIALOG_TMPDIR/dialog.inputbox.$$" + retval=$? + password=$( f_dialog_inputstr ) + fi + + # Exit if the user cancelled. + [ $retval -eq $SUCCESS ] || exit $retval + + # + # 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 + msg=$( printf "$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) + 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 size width height + + 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" + size=$( f_xdialog_2inputsbox_size \ + "$DIALOG_TITLE" \ + "$DIALOG_BACKTITLE" \ + "$msg" \ + "$field_username" "" \ + "$field_password" "" ) + width="${size##*[$IFS]}" + height="${size%%[$IFS]*}" + 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 255 ] && f_die $retval "$user_pass" + + # Exit if the user cancelled. + [ $retval -eq $SUCCESS ] || 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_dialog_msgbox "$msg_no_username" + continue + fi + if [ ! "$SECURE_ALLOW_ROOT" ]; then + case "$user" in + root|toor) + nfailures=$(( $nfailures + 1 )) + f_dialog_msgbox "$( printf \ + "$msg_user_disallowed" "$user" )" + continue + esac + fi + if ! f_quietly id "$user"; then + nfailures=$(( $nfailures + 1 )) + if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then + f_dialog_msgbox "$( printf \ + "$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 + msg=$( printf "$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 +} + +fi # ! $_MUSTBEROOT_SUBR diff --git a/usr.sbin/bsdconfig/share/strings.subr b/usr.sbin/bsdconfig/share/strings.subr new file mode 100644 index 0000000..1a330ad --- /dev/null +++ b/usr.sbin/bsdconfig/share/strings.subr @@ -0,0 +1,104 @@ +if [ ! "$_STRINGS_SUBR" ]; then _STRINGS_SUBR=1 +# +# Copyright (c) 2006-2012 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 (INLUDING, 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$ + +# 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_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" + + # Prevent division-by-zero + [ "$arg" = "0" ] && return $SUCCESS + + # Attempt to perform arithmetic divison (an operation which will exit + # with error unless arg is a valid positive/negative whole integer). + # + ( : $((0/$arg)) ) > /dev/null 2>&1 +} + +fi # ! $_STRINGS_SUBR diff --git a/usr.sbin/bsdconfig/share/sysrc.subr b/usr.sbin/bsdconfig/share/sysrc.subr new file mode 100644 index 0000000..014ab46 --- /dev/null +++ b/usr.sbin/bsdconfig/share/sysrc.subr @@ -0,0 +1,618 @@ +if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_SUBR=1 +# +# Copyright (c) 2006-2012 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 (INLUDING, 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 + +BSDCFG_LIBE="/usr/libexec/bsdconfig" +f_include_lang $BSDCFG_LIBE/include/messages.subr + +############################################################ CONFIGURATION + +# +# Standard pathnames (inherit values from shell if available) +# +: ${RC_DEFAULTS:="/etc/defaults/rc.conf"} + +############################################################ GLOBALS + +# +# Global exit status variables +# +SUCCESS=0 +FAILURE=1 + +############################################################ 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). +# +# 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 RC_CONFS RC_DEFAULTS SUCCESS + + . "$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(). + # + ( : ${RC_CONFS?} ) > /dev/null 2>&1 + if [ $? -eq ${SUCCESS:-0} ]; then + rc_conf_files="$RC_CONFS" + _rc_confs_set=1 + fi + + unset SUCCESS + # no longer needed + + source_rc_confs > /dev/null 2>&1 + + # + # If the query was for `rc_conf_files' AND after calling + # source_rc_confs the vaue 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_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" + local regex="^[[:space:]]*$varname=" + local rc_conf_files="$( f_sysrc_get rc_conf_files )" + local conf_files= + local file + + # Check parameters + [ "$varname" ] || return $FAILURE + + # + # If RC_CONFS is defined, set $rc_conf_files to an explicit + # value, modifying the default behavior of source_rc_confs(). + # + [ "$RC_CONFS" ] && rc_conf_files="$RC_CONFS" + + # + # Reverse the order of files in rc_conf_files (the boot process sources + # these in order, so we will search them in reverse-order to find the + # last-assignment -- the one that ultimately effects the environment). + # + for file in $rc_conf_files; do + conf_files="$file${conf_files:+ }$conf_files" + done + + # + # Append the defaults file (since directives in the defaults file + # indeed affect the boot process, we'll want to know when a directive + # is found there). + # + conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS" + + # + # Find which file matches assignment to the given variable name. + # + for file in $conf_files; do + [ -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 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" ]; then + file="${RC_CONFS%%[$IFS]*}" + else + file=$( f_sysrc_get rc_conf_files ) + file="${file%%[$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="$( mktemp -t "$pgm" )" + [ "$tmpfile" ] || return $FAILURE + + # + # Fixup permissions (else we're in for a surprise, as mktemp(1) creates + # the temporary file with 0600 permissions, and if we simply mv(1) the + # temporary file over the destination, the destination will inherit the + # permissions from the temporary file). + # + local mode + mode=$( stat -f '%#Lp' "$file" 2> /dev/null ) + f_quietly chmod "${mode:-0644}" "$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 + owner=$( stat -f '%u:%g' "$file" 2> /dev/null ) + f_quietly chown "${owner:-root:wheel}" "$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 ! /bin/sh -n "$tmpfile"; then + f_err "$msg_previous_syntax_errors\n" "$pgm" "$file" + rm -f "$tmpfile" + return $FAILURE + fi + + # + # Finally, move the temporary file into place. + # + mv "$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 varname="$1" + local file + + # Check arguments + [ "$varname" ] || return $FAILURE + + # + # Operate on each of the specified files + # + for file in ${RC_CONFS:-$( f_sysrc_get rc_conf_files )}; do + [ -e "$file" ] || continue + + # + # Create a new temporary file to write to. + # + local tmpfile="$( mktemp -t "$pgm" )" + [ "$tmpfile" ] || return $FAILURE + + # + # Fixup permissions and ownership (mktemp(1) defaults to 0600 + # permissions) to instead match the destination file. + # + local mode owner + mode=$( stat -f '%#Lp' "$file" 2> /dev/null ) + owner=$( stat -f '%u:%g' "$file" 2> /dev/null ) + f_quietly chmod "${mode:-0644}" "$tmpfile" + f_quietly chown "${owner:-root:wheel}" "$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 ! /bin/sh -n "$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. + # + mv "$tmpfile" "$file" + done +} + +fi # ! $_SYSRC_SUBR |