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# 59# Operating environment details 60# 61export UNAME_S="$(uname -s)" # Operating System (i.e. FreeBSD) 62export UNAME_P="$(uname -p)" # Processor Architecture (i.e. i386) 63export UNAME_R="$(uname -r)" # Release Level (i.e. X.Y-RELEASE) 64 65############################################################ FUNCTIONS 66 67# f_dprintf $fmt [ $opts ... ] 68# 69# Sensible debug function. Override in ~/.bsdconfigrc if desired. 70# See /usr/share/examples/bsdconfig/bsdconfigrc for example. 71# 72# If $debug is set and non-NULL, prints DEBUG info using printf(1) syntax: 73# + To $debugFile, if set and non-NULL 74# + To standard output if $debugFile is either NULL or unset 75# + To both if $debugFile begins with a single plus-sign (`+') 76# 77f_dprintf() 78{ 79 [ "$debug" ] || return $SUCCESS 80 local fmt="$1"; shift 81 case "$debugFile" in ""|+*) 82 printf "DEBUG: $fmt${fmt:+\n}" "$@" >&${TERMINAL_STDOUT_PASSTHRU:-1} 83 esac 84 [ "${debugFile#+}" ] && 85 printf "DEBUG: $fmt${fmt:+\n}" "$@" >> "${debugFile#+}" 86 return $SUCCESS 87} 88 89# f_err $fmt [ $opts ... ] 90# 91# Print a message to stderr (fd=2). 92# 93f_err() 94{ 95 printf "$@" >&${TERMINAL_STDERR_PASSTHRU:-2} 96} 97 98# f_quietly $command [ $arguments ... ] 99# 100# Run a command quietly (quell any output to stdout or stderr) 101# 102f_quietly() 103{ 104 "$@" > /dev/null 2>&1 105} 106 107# f_have $anything ... 108# 109# A wrapper to the `type' built-in. Returns true if argument is a valid shell 110# built-in, keyword, or externally-tracked binary, otherwise false. 111# 112f_have() 113{ 114 f_quietly type "$@" 115} 116 117# f_getvar $var_to_get [$var_to_set] 118# 119# Utility function designed to go along with the already-builtin setvar. 120# Allows clean variable name indirection without forking or sub-shells. 121# 122# Returns error status if the requested variable ($var_to_get) is not set. 123# 124# If $var_to_set is missing or NULL, the value of $var_to_get is printed to 125# standard output for capturing in a sub-shell (which is less-recommended 126# because of performance degredation; for example, when called in a loop). 127# 128f_getvar() 129{ 130 local var_to_get="$1" var_to_set="$2" 131 [ "$var_to_set" ] || local value 132 eval ${var_to_set:-value}=\"\${$var_to_get}\" 133 eval [ \"\${$var_to_get+set}\" ] 134 local retval=$? 135 eval f_dprintf '"f_getvar: var=[%s] value=[%s] r=%u"' \ 136 \"\$var_to_get\" \"\$${var_to_set:-value}\" \$retval 137 [ "$var_to_set" ] || { [ "$value" ] && echo "$value"; } 138 return $retval 139} 140 141# f_die [ $status [ $fmt [ $opts ... ]]] 142# 143# Abruptly terminate due to an error optionally displaying a message in a 144# dialog box using printf(1) syntax. 145# 146f_die() 147{ 148 local status=$FAILURE 149 150 # If there is at least one argument, take it as the status 151 if [ $# -gt 0 ]; then 152 status=$1 153 shift 1 # status 154 fi 155 156 # If there are still arguments left, pass them to f_show_msg 157 [ $# -gt 0 ] && f_show_msg "$@" 158 159 # Optionally call f_clean_up() function if it exists 160 f_have f_clean_up && f_clean_up 161 162 exit $status 163} 164 165# f_interrupt 166# 167# Interrupt handler. 168# 169f_interrupt() 170{ 171 exec 2>&1 # fix sh(1) bug where stderr gets lost within async-trap 172 f_die 173} 174 175# f_show_info $fmt [ $opts ... ] 176# 177# Display a message in a dialog infobox using printf(1) syntax. 178# 179f_show_info() 180{ 181 local msg 182 msg=$( printf "$@" ) 183 184 # 185 # Use f_dialog_infobox from dialog.subr if possible, otherwise fall 186 # back to dialog(1) (without options, making it obvious when using 187 # un-aided system dialog). 188 # 189 if f_have f_dialog_info; then 190 f_dialog_info "$msg" 191 else 192 dialog --infobox "$msg" 0 0 193 fi 194} 195 196# f_show_msg $fmt [ $opts ... ] 197# 198# Display a message in a dialog box using printf(1) syntax. 199# 200f_show_msg() 201{ 202 local msg 203 msg=$( printf "$@" ) 204 205 # 206 # Use f_dialog_msgbox from dialog.subr if possible, otherwise fall 207 # back to dialog(1) (without options, making it obvious when using 208 # un-aided system dialog). 209 # 210 if f_have f_dialog_msgbox; then 211 f_dialog_msgbox "$msg" 212 else 213 dialog --msgbox "$msg" 0 0 214 fi 215} 216 217 218# f_yesno $fmt [ $opts ... ] 219# 220# Display a message in a dialog yes/no box using printf(1) syntax. 221# 222f_yesno() 223{ 224 local msg 225 msg=$( printf "$@" ) 226 227 # 228 # Use f_dialog_yesno from dialog.subr if possible, otherwise fall 229 # back to dialog(1) (without options, making it obvious when using 230 # un-aided system dialog). 231 # 232 if f_have f_dialog_yesno; then 233 f_dialog_yesno "$msg" 234 else 235 dialog --yesno "$msg" 0 0 236 fi 237} 238 239# f_noyes $fmt [ $opts ... ] 240# 241# Display a message in a dialog yes/no box using printf(1) syntax. 242# NOTE: THis is just like the f_yesno function except "No" is default. 243# 244f_noyes() 245{ 246 local msg 247 msg=$( printf "$@" ) 248 249 # 250 # Use f_dialog_noyes from dialog.subr if possible, otherwise fall 251 # back to dialog(1) (without options, making it obvious when using 252 # un-aided system dialog). 253 # 254 if f_have f_dialog_noyes; then 255 f_dialog_noyes "$msg" 256 else 257 dialog --defaultno --yesno "$msg" 0 0 258 fi 259} 260 261# f_show_help $file 262# 263# Display a language help-file. Automatically takes $LANG and $LC_ALL into 264# consideration when displaying $file (suffix ".$LC_ALL" or ".$LANG" will 265# automatically be added prior to loading the language help-file). 266# 267# If a language has been requested by setting either $LANG or $LC_ALL in the 268# environment and the language-specific help-file does not exist we will fall 269# back to $file without-suffix. 270# 271# If the language help-file does not exist, an error is displayed instead. 272# 273f_show_help() 274{ 275 local file="$1" 276 local lang="${LANG:-$LC_ALL}" 277 278 [ -f "$file.$lang" ] && file="$file.$lang" 279 280 # 281 # Use f_dialog_textbox from dialog.subr if possible, otherwise fall 282 # back to dialog(1) (without options, making it obvious when using 283 # un-aided system dialog). 284 # 285 if f_have f_dialog_textbox; then 286 f_dialog_textbox "$file" 287 else 288 dialog --msgbox "$( cat "$file" 2>&1 )" 0 0 289 fi 290} 291 292# f_include $file 293# 294# Include a shell subroutine file. 295# 296# If the subroutine file exists but returns error status during loading, exit 297# is called and execution is prematurely terminated with the same error status. 298# 299f_include() 300{ 301 local file="$1" 302 f_dprintf "f_include: file=[%s]" "$file" 303 . "$file" || exit $? 304} 305 306# f_include_lang $file 307# 308# Include a language file. Automatically takes $LANG and $LC_ALL into 309# consideration when including $file (suffix ".$LC_ALL" or ".$LANG" will 310# automatically by added prior to loading the language file). 311# 312# No error is produced if (a) a language has been requested (by setting either 313# $LANG or $LC_ALL in the environment) and (b) the language file does not 314# exist -- in which case we will fall back to loading $file without-suffix. 315# 316# If the language file exists but returns error status during loading, exit 317# is called and execution is prematurely terminated with the same error status. 318# 319f_include_lang() 320{ 321 local file="$1" 322 local lang="${LANG:-$LC_ALL}" 323 324 f_dprintf "f_include_lang: file=[%s] lang=[%s]" "$file" "$lang" 325 if [ -f "$file.$lang" ]; then 326 . "$file.$lang" || exit $? 327 else 328 . "$file" || exit $? 329 fi 330} 331 332# f_usage $file [ $key1 $value1 ... ] 333# 334# Display USAGE file with optional pre-processor macro definitions. The first 335# argument is the template file containing the usage text to be displayed. If 336# $LANG or $LC_ALL (in order of preference, respectively) is set, ".encoding" 337# will automatically be appended as a suffix to the provided $file pathname. 338# 339# When processing $file, output begins at the first line containing that is 340# (a) not a comment, (b) not empty, and (c) is not pure-whitespace. All lines 341# appearing after this first-line are output, including (a) comments (b) empty 342# lines, and (c) lines that are purely whitespace-only. 343# 344# If additional arguments appear after $file, substitutions are made while 345# printing the contents of the USAGE file. The pre-processor macro syntax is in 346# the style of autoconf(1), for example: 347# 348# f_usage $file "FOO" "BAR" 349# 350# Will cause instances of "@FOO@" appearing in $file to be replaced with the 351# text "BAR" before bering printed to the screen. 352# 353# This function is a two-parter. Below is the awk(1) portion of the function, 354# afterward is the sh(1) function which utilizes the below awk script. 355# 356f_usage_awk=' 357BEGIN { found = 0 } 358{ 359 if ( !found && $0 ~ /^[[:space:]]*($|#)/ ) next 360 found = 1 361 print 362} 363' 364f_usage() 365{ 366 local file="$1" 367 local lang="${LANG:-$LC_ALL}" 368 369 f_dprintf "f_usage: file=[%s] lang=[%s]" "$file" "$lang" 370 371 shift 1 # file 372 373 local usage 374 if [ -f "$file.$lang" ]; then 375 usage=$( awk "$f_usage_awk" "$file.$lang" ) || exit $FAILURE 376 else 377 usage=$( awk "$f_usage_awk" "$file" ) || exit $FAILURE 378 fi 379 380 while [ $# -gt 0 ]; do 381 local key="$1" 382 export value="$2" 383 usage=$( echo "$usage" | awk \ 384 "{ gsub(/@$key@/, ENVIRON[\"value\"]); print }" ) 385 shift 2 386 done 387 388 f_err "%s\n" "$usage" 389 390 exit $FAILURE 391} 392 393# f_index_file $keyword 394# 395# Process all INDEX files known to bsdconfig and return the path to first file 396# containing a menu_selection line with a keyword portion matching $keyword. 397# 398# If $LANG or $LC_ALL (in order of preference, respectively) is set, 399# "INDEX.encoding" files will be searched first. 400# 401# If no file is found, error status is returned along with the NULL string. 402# 403# This function is a two-parter. Below is the awk(1) portion of the function, 404# afterward is the sh(1) function which utilizes the below awk script. 405# 406f_index_file_awk=' 407# Variables that should be defined on the invocation line: 408# -v keyword="keyword" 409BEGIN { found = 0 } 410( $0 ~ "^menu_selection=\"" keyword "\\|" ) { 411 print FILENAME 412 found++ 413 exit 414} 415END { exit ! found } 416' 417f_index_file() 418{ 419 local keyword="$1" 420 local lang="${LANG:-$LC_ALL}" 421 422 f_dprintf "f_index_file: keyword=[%s] lang=[%s]" "$keyword" "$lang" 423 424 if [ "$lang" ]; then 425 awk -v keyword="$keyword" "$f_index_file_awk" \ 426 $BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX.$lang && 427 return 428 # No match, fall-thru to non-i18n sources 429 fi 430 awk -v keyword="$keyword" "$f_index_file_awk" \ 431 $BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX 432} 433 434# f_index_menusel_keyword $indexfile $pgm 435# 436# Process $indexfile and return only the keyword portion of the menu_selection 437# line with a command portion matching $pgm. 438# 439# This function is for internationalization (i18n) mapping of the on-disk 440# scriptname ($pgm) into the localized language (given language-specific 441# $indexfile). If $LANG or $LC_ALL (in orderder of preference, respectively) is 442# set, ".encoding" will automatically be appended as a suffix to the provided 443# $indexfile pathname. 444# 445# If, within $indexfile, multiple $menu_selection values map to $pgm, only the 446# first one will be returned. If no mapping can be made, the NULL string is 447# returned. 448# 449# If $indexfile does not exist, error status is returned with NULL. 450# 451# This function is a two-parter. Below is the awk(1) portion of the function, 452# afterward is the sh(1) function which utilizes the below awk script. 453# 454f_index_menusel_keyword_awk=' 455# Variables that should be defined on the invocation line: 456# -v pgm="program_name" 457# 458BEGIN { 459 prefix = "menu_selection=\"" 460 plen = length(prefix) 461 found = 0 462} 463{ 464 if (!match($0, "^" prefix ".*\\|.*\"")) next 465 466 keyword = command = substr($0, plen + 1, RLENGTH - plen - 1) 467 sub(/^.*\|/, "", command) 468 sub(/\|.*$/, "", keyword) 469 470 if ( command == pgm ) 471 { 472 print keyword 473 found++ 474 exit 475 } 476} 477END { exit ! found } 478' 479f_index_menusel_keyword() 480{ 481 local indexfile="$1" pgm="$2" 482 local lang="${LANG:-$LC_ALL}" 483 484 f_dprintf "f_index_menusel_keyword: index=[%s] pgm=[%s] lang=[%s]" \ 485 "$indexfile" "$pgm" "$lang" 486 487 if [ -f "$indexfile.$lang" ]; then 488 awk -v pgm="$pgm" \ 489 "$f_index_menusel_keyword_awk" \ 490 "$indexfile.$lang" 491 elif [ -f "$indexfile" ]; then 492 awk -v pgm="$pgm" \ 493 "$f_index_menusel_keyword_awk" \ 494 "$indexfile" 495 fi 496} 497 498# f_index_menusel_command $indexfile $keyword 499# 500# Process $indexfile and return only the command portion of the menu_selection 501# line with a keyword portion matching $keyword. 502# 503# This function is for mapping [possibly international] keywords into the 504# command to be executed. If $LANG or $LC_ALL (order of preference) is set, 505# ".encoding" will automatically be appended as a suffix to the provided 506# $indexfile pathname. 507# 508# If, within $indexfile, multiple $menu_selection values map to $keyword, only 509# the first one will be returned. If no mapping can be made, the NULL string is 510# returned. 511# 512# If $indexfile doesn't exist, error status is returned with NULL. 513# 514# This function is a two-parter. Below is the awk(1) portion of the function, 515# afterward is the sh(1) function which utilizes the below awk script. 516# 517f_index_menusel_command_awk=' 518# Variables that should be defined on the invocation line: 519# -v key="keyword" 520# 521BEGIN { 522 prefix = "menu_selection=\"" 523 plen = length(prefix) 524 found = 0 525} 526{ 527 if (!match($0, "^" prefix ".*\\|.*\"")) next 528 529 keyword = command = substr($0, plen + 1, RLENGTH - plen - 1) 530 sub(/^.*\|/, "", command) 531 sub(/\|.*$/, "", keyword) 532 533 if ( keyword == key ) 534 { 535 print command 536 found++ 537 exit 538 } 539} 540END { exit ! found } 541' 542f_index_menusel_command() 543{ 544 local indexfile="$1" keyword="$2" command 545 local lang="${LANG:-$LC_ALL}" 546 547 f_dprintf "f_index_menusel_command: index=[%s] key=[%s] lang=[%s]" \ 548 "$indexfile" "$keyword" "$lang" 549 550 if [ -f "$indexfile.$lang" ]; then 551 command=$( awk -v key="$keyword" \ 552 "$f_index_menusel_command_awk" \ 553 "$indexfile.$lang" ) || return $FAILURE 554 elif [ -f "$indexfile" ]; then 555 command=$( awk -v key="$keyword" \ 556 "$f_index_menusel_command_awk" \ 557 "$indexfile" ) || return $FAILURE 558 else 559 return $FAILURE 560 fi 561 562 # 563 # If the command pathname is not fully qualified fix-up/force to be 564 # relative to the $indexfile directory. 565 # 566 case "$command" in 567 /*) : already fully qualified ;; 568 *) 569 local indexdir="${indexfile%/*}" 570 [ "$indexdir" != "$indexfile" ] || indexdir="." 571 command="$indexdir/$command" 572 esac 573 574 echo "$command" 575} 576 577############################################################ MAIN 578 579# 580# Trap signals so we can recover gracefully 581# 582trap 'f_interrupt' SIGINT 583trap 'f_die' SIGTERM SIGPIPE SIGXCPU SIGXFSZ \ 584 SIGFPE SIGTRAP SIGABRT SIGSEGV 585trap '' SIGALRM SIGPROF SIGUSR1 SIGUSR2 SIGHUP SIGVTALRM 586 587# 588# Clone terminal stdout/stderr so we can redirect to it from within sub-shells 589# 590eval exec $TERMINAL_STDOUT_PASSTHRU\>\&1 591eval exec $TERMINAL_STDERR_PASSTHRU\>\&2 592 593# 594# Make debugging persistant if set 595# 596[ "$debug" ] && export debug 597 598# 599# Truncate the debug file upon initialization (now). Note that we will trim a 600# leading plus (`+') from the value of debugFile to support persistant meaning 601# that f_dprintf() should print both to standard output and $debugFile (minus 602# the leading plus, of course). 603# 604_debug_file="${debugFile#+}" 605if [ "$_debug_file" ]; then 606 if ( umask 022 && :> "$_debug_file" ); then 607 f_dprintf "Successfully initialized debugFile \`%s'" \ 608 "$_debug_file" 609 else 610 unset debugFile 611 f_dprintf "Unable to initialize debugFile \`%s'" \ 612 "$_debug_file" 613 fi 614fi 615unset _debug_file 616 617# 618# Log our operating environment for debugging purposes 619# 620f_dprintf "UNAME_S=[%s] UNAME_P=[%s] UNAME_R=[%s]" \ 621 "$UNAME_S" "$UNAME_P" "$UNAME_R" 622 623f_dprintf "%s: Successfully loaded." common.subr 624 625fi # ! $_COMMON_SUBR 626