1if [ ! "$_COMMON_SUBR" ]; then _COMMON_SUBR=1 2# 3# Copyright (c) 2012 Ron McDowell 4# Copyright (c) 2012 Devin Teske 5# All rights reserved. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26# SUCH DAMAGE. 27# 28# $FreeBSD$ 29# 30############################################################ CONFIGURATION 31 32# 33# Default file descriptors to link to stdout/stderr for passthru allowing 34# redirection within a sub-shell to bypass directly to the terminal. 35# 36: ${TERMINAL_STDOUT_PASSTHRU:=3}} 37: ${TERMINAL_STDERR_PASSTHRU:=4}} 38 39############################################################ GLOBALS 40 41# 42# Program name 43# 44pgm="${0##*/}" 45 46# 47# Program arguments 48# 49ARGC="$#" 50ARGV="$@" 51 52# 53# Global exit status variables 54# 55SUCCESS=0 56FAILURE=1 57 58############################################################ FUNCTIONS 59 60# 61# This is an empty function by default, to use it, copy 62# /usr/share/examples/bsdconfig/bsdconfigrc to $HOME/.bsdconfigrc 63# 64f_dprintf() 65{ 66 : this page intentionally left blank 67} 68[ "$DEBUGGING" ] && f_dprintf() 69{ 70 local fmt="$1" 71 shift 72 printf "$fmt${fmt:+\n}" "$@" \ 73 >&${TERMINAL_STDOUT_PASSTHRU:-1} 74} && 75 export DEBUGGING 76 77# f_err $fmt [ $opts ... ] 78# 79# Print a message to stderr (fd=2). 80# 81f_err() 82{ 83 printf "$@" >&${TERMINAL_STDERR_PASSTHRU:-2} 84} 85 86# f_quietly $command [ $arguments ... ] 87# 88# Run a command quietly (quell any output to stdout or stderr) 89# 90f_quietly() 91{ 92 "$@" > /dev/null 2>&1 93} 94 95# f_have $anything ... 96# 97# A wrapper to the `type' built-in. Returns true if argument is a valid shell 98# built-in, keyword, or externally-tracked binary, otherwise false. 99# 100f_have() 101{ 102 f_quietly type "$@" 103} 104 105# f_die [ $status [ $fmt [ $opts ... ]]] 106# 107# Abruptly terminate due to an error optionally displaying a message in a 108# dialog box using printf(1) syntax. 109# 110f_die() 111{ 112 local status=$FAILURE 113 114 # If there is at least one argument, take it as the status 115 if [ $# -gt 0 ]; then 116 status=$1 117 shift 1 # status 118 fi 119 120 # If there are still arguments left, pass them to f_show_msg 121 [ $# -gt 0 ] && f_show_msg "$@" 122 123 # Optionally call f_clean_up() function if it exists 124 f_have f_clean_up && f_clean_up 125 126 exit $status 127} 128 129# f_interrupt 130# 131# Interrupt handler. 132# 133f_interrupt() 134{ 135 exec 2>&1 # fix sh(1) bug where stderr gets lost within async-trap 136 f_die 137} 138 139# f_show_info $fmt [ $opts ... ] 140# 141# Display a message in a dialog infobox using printf(1) syntax. 142# 143f_show_info() 144{ 145 local msg 146 msg=$( printf "$@" ) 147 148 # 149 # Use f_dialog_infobox from dialog.subr if possible, otherwise fall 150 # back to dialog(1) (without options, making it obvious when using 151 # un-aided system dialog). 152 # 153 if f_have f_dialog_info; then 154 f_dialog_info "$msg" 155 else 156 dialog --infobox "$msg" 0 0 157 fi 158} 159 160# f_show_msg $fmt [ $opts ... ] 161# 162# Display a message in a dialog box using printf(1) syntax. 163# 164f_show_msg() 165{ 166 local msg 167 msg=$( printf "$@" ) 168 169 # 170 # Use f_dialog_msgbox from dialog.subr if possible, otherwise fall 171 # back to dialog(1) (without options, making it obvious when using 172 # un-aided system dialog). 173 # 174 if f_have f_dialog_msgbox; then 175 f_dialog_msgbox "$msg" 176 else 177 dialog --msgbox "$msg" 0 0 178 fi 179} 180 181# f_show_help $file 182# 183# Display a language help-file. Automatically takes $LANG and $LC_ALL into 184# consideration when displaying $file (suffix ".$LC_ALL" or ".$LANG" will 185# automatically be added prior to loading the language help-file). 186# 187# If a language has been requested by setting either $LANG or $LC_ALL in the 188# environment and the language-specific help-file does not exist we will fall 189# back to $file without-suffix. 190# 191# If the language help-file does not exist, an error is displayed instead. 192# 193f_show_help() 194{ 195 local file="$1" 196 local lang="${LANG:-$LC_ALL}" 197 198 [ -f "$file.$lang" ] && file="$file.$lang" 199 200 # 201 # Use f_dialog_textbox from dialog.subr if possible, otherwise fall 202 # back to dialog(1) (without options, making it obvious when using 203 # un-aided system dialog). 204 # 205 if f_have f_dialog_textbox; then 206 f_dialog_textbox "$file" 207 else 208 dialog --msgbox "$( cat "$file" 2>&1 )" 0 0 209 fi 210} 211 212# f_include $file 213# 214# Include a shell subroutine file. 215# 216# If the subroutine file exists but returns error status during loading, exit 217# is called and execution is prematurely terminated with the same error status. 218# 219f_include() 220{ 221 local file="$1" 222 f_dprintf "f_include: file=[%s]" "$file" 223 . "$file" || exit $? 224} 225 226# f_include_lang $file 227# 228# Include a language file. Automatically takes $LANG and $LC_ALL into 229# consideration when including $file (suffix ".$LC_ALL" or ".$LANG" will 230# automatically by added prior to loading the language file). 231# 232# No error is produced if (a) a language has been requested (by setting either 233# $LANG or $LC_ALL in the environment) and (b) the language file does not 234# exist -- in which case we will fall back to loading $file without-suffix. 235# 236# If the language file exists but returns error status during loading, exit 237# is called and execution is prematurely terminated with the same error status. 238# 239f_include_lang() 240{ 241 local file="$1" 242 local lang="${LANG:-$LC_ALL}" 243 244 f_dprintf "f_include_lang: file=[%s] lang=[%s]" "$file" "$lang" 245 if [ -f "$file.$lang" ]; then 246 . "$file.$lang" || exit $? 247 else 248 . "$file" || exit $? 249 fi 250} 251 252# f_usage $file [ $key1 $value1 ... ] 253# 254# Display USAGE file with optional pre-processor macro definitions. The first 255# argument is the template file containing the usage text to be displayed. If 256# $LANG or $LC_ALL (in order of preference, respectively) is set, ".encoding" 257# will automatically be appended as a suffix to the provided $file pathname. 258# 259# When processing $file, output begins at the first line containing that is 260# (a) not a comment, (b) not empty, and (c) is not pure-whitespace. All lines 261# appearing after this first-line are output, including (a) comments (b) empty 262# lines, and (c) lines that are purely whitespace-only. 263# 264# If additional arguments appear after $file, substitutions are made while 265# printing the contents of the USAGE file. The pre-processor macro syntax is in 266# the style of autoconf(1), for example: 267# 268# f_usage $file "FOO" "BAR" 269# 270# Will cause instances of "@FOO@" appearing in $file to be replaced with the 271# text "BAR" before bering printed to the screen. 272# 273# This function is a two-parter. Below is the awk(1) portion of the function, 274# afterward is the sh(1) function which utilizes the below awk script. 275# 276f_usage_awk=' 277BEGIN { found = 0 } 278{ 279 if ( !found && $0 ~ /^[[:space:]]*($|#)/ ) next 280 found = 1 281 print 282} 283' 284f_usage() 285{ 286 local file="$1" 287 local lang="${LANG:-$LC_ALL}" 288 289 f_dprintf "f_usage: file=[%s] lang=[%s]" "$file" "$lang" 290 291 shift 1 # file 292 293 local usage 294 if [ -f "$file.$lang" ]; then 295 usage=$( awk "$f_usage_awk" "$file.$lang" ) || exit $FAILURE 296 else 297 usage=$( awk "$f_usage_awk" "$file" ) || exit $FAILURE 298 fi 299 300 while [ $# -gt 0 ]; do 301 local key="$1" 302 export value="$2" 303 usage=$( echo "$usage" | awk \ 304 "{ gsub(/@$key@/, ENVIRON[\"value\"]); print }" ) 305 shift 2 306 done 307 308 f_err "%s\n" "$usage" 309 310 exit $FAILURE 311} 312 313# f_index_file $keyword 314# 315# Process all INDEX files known to bsdconfig and return the path to first file 316# containing a menu_selection line with a keyword portion matching $keyword. 317# 318# If $LANG or $LC_ALL (in order of preference, respectively) is set, 319# "INDEX.encoding" files will be searched first. 320# 321# If no file is found, error status is returned along with the NULL string. 322# 323# This function is a two-parter. Below is the awk(1) portion of the function, 324# afterward is the sh(1) function which utilizes the below awk script. 325# 326f_index_file_awk=' 327# Variables that should be defined on the invocation line: 328# -v keyword="keyword" 329BEGIN { found = 0 } 330( $0 ~ "^menu_selection=\"" keyword "\\|" ) { 331 print FILENAME 332 found++ 333 exit 334} 335END { exit ! found } 336' 337f_index_file() 338{ 339 local keyword="$1" 340 local lang="${LANG:-$LC_ALL}" 341 342 f_dprintf "f_index_file: keyword=[%s] lang=[%s]" "$keyword" "$lang" 343 344 if [ "$lang" ]; then 345 awk -v keyword="$keyword" "$f_index_file_awk" \ 346 $BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX.$lang && 347 return 348 # No match, fall-thru to non-i18n sources 349 fi 350 awk -v keyword="$keyword" "$f_index_file_awk" \ 351 $BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX 352} 353 354# f_index_menusel_keyword $indexfile $pgm 355# 356# Process $indexfile and return only the keyword portion of the menu_selection 357# line with a command portion matching $pgm. 358# 359# This function is for internationalization (i18n) mapping of the on-disk 360# scriptname ($pgm) into the localized language (given language-specific 361# $indexfile). If $LANG or $LC_ALL (in orderder of preference, respectively) is 362# set, ".encoding" will automatically be appended as a suffix to the provided 363# $indexfile pathname. 364# 365# If, within $indexfile, multiple $menu_selection values map to $pgm, only the 366# first one will be returned. If no mapping can be made, the NULL string is 367# returned. 368# 369# If $indexfile does not exist, error status is returned with NULL. 370# 371# This function is a two-parter. Below is the awk(1) portion of the function, 372# afterward is the sh(1) function which utilizes the below awk script. 373# 374f_index_menusel_keyword_awk=' 375# Variables that should be defined on the invocation line: 376# -v pgm="program_name" 377# 378BEGIN { 379 prefix = "menu_selection=\"" 380 plen = length(prefix) 381 found = 0 382} 383{ 384 if (!match($0, "^" prefix ".*\\|.*\"")) next 385 386 keyword = command = substr($0, plen + 1, RLENGTH - plen - 1) 387 sub(/^.*\|/, "", command) 388 sub(/\|.*$/, "", keyword) 389 390 if ( command == pgm ) 391 { 392 print keyword 393 found++ 394 exit 395 } 396} 397END { exit ! found } 398' 399f_index_menusel_keyword() 400{ 401 local indexfile="$1" pgm="$2" 402 local lang="${LANG:-$LC_ALL}" 403 404 f_dprintf "f_index_menusel_keyword: index=[%s] pgm=[%s] lang=[%s]" \ 405 "$indexfile" "$pgm" "$lang" 406 407 if [ -f "$indexfile.$lang" ]; then 408 awk -v pgm="$pgm" \ 409 "$f_index_menusel_keyword_awk" \ 410 "$indexfile.$lang" 411 elif [ -f "$indexfile" ]; then 412 awk -v pgm="$pgm" \ 413 "$f_index_menusel_keyword_awk" \ 414 "$indexfile" 415 fi 416} 417 418# f_index_menusel_command $indexfile $keyword 419# 420# Process $indexfile and return only the command portion of the menu_selection 421# line with a keyword portion matching $keyword. 422# 423# This function is for mapping [possibly international] keywords into the 424# command to be executed. If $LANG or $LC_ALL (order of preference) is set, 425# ".encoding" will automatically be appended as a suffix to the provided 426# $indexfile pathname. 427# 428# If, within $indexfile, multiple $menu_selection values map to $keyword, only 429# the first one will be returned. If no mapping can be made, the NULL string is 430# returned. 431# 432# If $indexfile doesn't exist, error status is returned with NULL. 433# 434# This function is a two-parter. Below is the awk(1) portion of the function, 435# afterward is the sh(1) function which utilizes the below awk script. 436# 437f_index_menusel_command_awk=' 438# Variables that should be defined on the invocation line: 439# -v key="keyword" 440# 441BEGIN { 442 prefix = "menu_selection=\"" 443 plen = length(prefix) 444 found = 0 445} 446{ 447 if (!match($0, "^" prefix ".*\\|.*\"")) next 448 449 keyword = command = substr($0, plen + 1, RLENGTH - plen - 1) 450 sub(/^.*\|/, "", command) 451 sub(/\|.*$/, "", keyword) 452 453 if ( keyword == key ) 454 { 455 print command 456 found++ 457 exit 458 } 459} 460END { exit ! found } 461' 462f_index_menusel_command() 463{ 464 local indexfile="$1" keyword="$2" command 465 local lang="${LANG:-$LC_ALL}" 466 467 f_dprintf "f_index_menusel_command: index=[%s] key=[%s] lang=[%s]" \ 468 "$indexfile" "$keyword" "$lang" 469 470 if [ -f "$indexfile.$lang" ]; then 471 command=$( awk -v key="$keyword" \ 472 "$f_index_menusel_command_awk" \ 473 "$indexfile.$lang" ) || return $FAILURE 474 elif [ -f "$indexfile" ]; then 475 command=$( awk -v key="$keyword" \ 476 "$f_index_menusel_command_awk" \ 477 "$indexfile" ) || return $FAILURE 478 else 479 return $FAILURE 480 fi 481 482 # 483 # If the command pathname is not fully qualified fix-up/force to be 484 # relative to the $indexfile directory. 485 # 486 case "$command" in 487 /*) : already fully qualified ;; 488 *) 489 local indexdir="${indexfile%/*}" 490 [ "$indexdir" != "$indexfile" ] || indexdir="." 491 command="$indexdir/$command" 492 esac 493 494 echo "$command" 495} 496 497############################################################ MAIN 498 499# 500# Trap signals so we can recover gracefully 501# 502trap 'f_interrupt' SIGINT 503trap 'f_die' SIGTERM SIGPIPE SIGXCPU SIGXFSZ \ 504 SIGFPE SIGTRAP SIGABRT SIGSEGV 505trap '' SIGALRM SIGPROF SIGUSR1 SIGUSR2 SIGHUP SIGVTALRM 506 507# 508# Clone terminal stdout/stderr so we can redirect to it from within sub-shells 509# 510eval exec $TERMINAL_STDOUT_PASSTHRU\>\&1 511eval exec $TERMINAL_STDERR_PASSTHRU\>\&2 512 513fi # ! $_COMMON_SUBR 514