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