]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/bsdconfig/share/common.subr
Define a setvar() function for platforms using a shell unlike FreeBSD's
[FreeBSD/FreeBSD.git] / usr.sbin / bsdconfig / share / common.subr
1 if [ ! "$_COMMON_SUBR" ]; then _COMMON_SUBR=1
2 #
3 # Copyright (c) 2012 Ron McDowell
4 # Copyright (c) 2012-2014 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 #
44 pgm="${0##*/}"
45
46 #
47 # Program arguments
48 #
49 ARGC="$#"
50 ARGV="$@"
51
52 #
53 # Global exit status variables
54 #
55 SUCCESS=0
56 FAILURE=1
57
58 #
59 # Operating environment details
60 #
61 export UNAME_S="$( uname -s )" # Operating System (i.e. FreeBSD)
62 export UNAME_P="$( uname -p )" # Processor Architecture (i.e. i386)
63 export UNAME_M="$( uname -m )" # Machine platform (i.e. i386)
64 export UNAME_R="$( uname -r )" # Release Level (i.e. X.Y-RELEASE)
65
66 #
67 # Default behavior is to call f_debug_init() automatically when loaded.
68 #
69 : ${DEBUG_SELF_INITIALIZE=1}
70
71 #
72 # Default behavior of f_debug_init() is to truncate $debugFile (set to NULL to
73 # disable truncating the debug file when initializing). To get child processes
74 # to append to the same log file, export this variarable (with a NULL value)
75 # and also export debugFile with the desired value.
76
77 : ${DEBUG_INITIALIZE_FILE=1}
78
79 #
80 # Define standard optstring arguments that should be supported by all programs
81 # using this include (unless DEBUG_SELF_INITIALIZE is set to NULL to prevent
82 # f_debug_init() from autamatically processing "$@" for the below arguments):
83 #
84 #       d       Sets $debug to 1
85 #       D:      Sets $debugFile to $OPTARG
86 #
87 GETOPTS_STDARGS="dD:"
88
89 #
90 # The getopts builtin will return 1 either when the end of "$@" or the first
91 # invalid flag is reached. This makes it impossible to determine if you've
92 # processed all the arguments or simply have hit an invalid flag. In the cases
93 # where we want to tolerate invalid flags (f_debug_init() for example), the
94 # following variable can be appended to your optstring argument to getopts,
95 # preventing it from prematurely returning 1 before the end of the arguments.
96 #
97 # NOTE: This assumes that all unknown flags are argument-less.
98 #
99 GETOPTS_ALLFLAGS="abcdefghijklmnopqrstuvwxyz"
100 GETOPTS_ALLFLAGS="${GETOPTS_ALLFLAGS}ABCDEFGHIJKLMNOPQRSTUVWXYZ"
101 GETOPTS_ALLFLAGS="${GETOPTS_ALLFLAGS}0123456789"
102
103 #
104 # When we get included, f_debug_init() will fire (unless $DEBUG_SELF_INITIALIZE
105 # is set to disable automatic initialization) and process "$@" for a few global
106 # options such as `-d' and/or `-D file'. However, if your program takes custom
107 # flags that take arguments, this automatic processing may fail unexpectedly.
108 #
109 # The solution to this problem is to pre-define (before including this file)
110 # the following variable (which defaults to NULL) to indicate that there are
111 # extra flags that should be considered when performing automatic processing of
112 # globally persistent flags.
113 #
114 : ${GETOPTS_EXTRA:=}
115
116 ############################################################ FUNCTIONS
117
118 # f_dprintf $format [$arguments ...]
119 #
120 # Sensible debug function. Override in ~/.bsdconfigrc if desired.
121 # See /usr/share/examples/bsdconfig/bsdconfigrc for example.
122 #
123 # If $debug is set and non-NULL, prints DEBUG info using printf(1) syntax:
124 #       + To $debugFile, if set and non-NULL
125 #       + To standard output if $debugFile is either NULL or unset
126 #       + To both if $debugFile begins with a single plus-sign (`+')
127 #
128 f_dprintf()
129 {
130         [ "$debug" ] || return $SUCCESS
131         local fmt="$1"; shift
132         case "$debugFile" in ""|+*)
133         printf "DEBUG: $fmt${fmt:+\n}" "$@" >&${TERMINAL_STDOUT_PASSTHRU:-1}
134         esac
135         [ "${debugFile#+}" ] &&
136                 printf "DEBUG: $fmt${fmt:+\n}" "$@" >> "${debugFile#+}"
137         return $SUCCESS
138 }
139
140 # f_debug_init
141 #
142 # Initialize debugging. Truncates $debugFile to zero bytes if set.
143 #
144 f_debug_init()
145 {
146         #
147         # Process stored command-line arguments
148         #
149         set -- $ARGV
150         local OPTIND OPTARG flag
151         f_dprintf "f_debug_init: ARGV=[%s] GETOPTS_STDARGS=[%s]" \
152                   "$ARGV" "$GETOPTS_STDARGS"
153         while getopts "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" flag \
154         > /dev/null; do
155                 case "$flag" in
156                 d) debug=1 ;;
157                 D) debugFile="$OPTARG" ;;
158                 esac
159         done
160         shift $(( $OPTIND - 1 ))
161         f_dprintf "f_debug_init: debug=[%s] debugFile=[%s]" \
162                   "$debug" "$debugFile"
163
164         #
165         # Automagically enable debugging if debugFile is set (and non-NULL)
166         #
167         [ "$debugFile" ] && { [ "${debug+set}" ] || debug=1; }
168
169         #
170         # Make debugging persistant if set
171         #
172         [ "$debug" ] && export debug
173         [ "$debugFile" ] && export debugFile
174
175         #
176         # Truncate debug file unless requested otherwise. Note that we will
177         # trim a leading plus (`+') from the value of debugFile to support
178         # persistant meaning that f_dprintf() should print both to standard
179         # output and $debugFile (minus the leading plus, of course).
180         #
181         local _debug_file="${debugFile#+}"
182         if [ "$_debug_file" -a "$DEBUG_INITIALIZE_FILE" ]; then
183                 if ( umask 022 && :> "$_debug_file" ); then
184                         f_dprintf "Successfully initialized debugFile \`%s'" \
185                                   "$_debug_file"
186                         f_isset debug || debug=1 # turn debugging on if not set
187                 else
188                         unset debugFile
189                         f_dprintf "Unable to initialize debugFile \`%s'" \
190                                   "$_debug_file"
191                 fi
192         fi
193 }
194
195 # f_err $format [$arguments ...]
196 #
197 # Print a message to stderr (fd=2).
198 #
199 f_err()
200 {
201         printf "$@" >&2
202 }
203
204 # f_quietly $command [$arguments ...]
205 #
206 # Run a command quietly (quell any output to stdout or stderr)
207 #
208 f_quietly()
209 {
210         "$@" > /dev/null 2>&1
211 }
212
213 # f_have $anything ...
214 #
215 # A wrapper to the `type' built-in. Returns true if argument is a valid shell
216 # built-in, keyword, or externally-tracked binary, otherwise false.
217 #
218 f_have()
219 {
220         f_quietly type "$@"
221 }
222
223 # setvar $var_to_set [$value]
224 #
225 # Implement setvar for shells such as unlike FreeBSD sh(1).
226 #
227 if ! f_have setvar; then
228 setvar()
229 {
230         [ $# -gt 0 ] || return $SUCCESS
231         local __setvar_var_to_set="$1" __setvar_right="$2" __setvar_left=
232         case $# in
233         1) unset "$__setvar_var_to_set"
234            return $? ;;
235         2) : fall through ;;
236         *) f_err "setvar: too many arguments\n"
237            return $FAILURE
238         esac
239         while case "$__setvar_r" in *\'*) : ;; *) false ; esac
240         do
241                 __setvar_left="$__setvar_left${__setvar_right%%\'*}'\\''"
242                 __setvar_right="${__setvar_right#*\'}"
243         done
244         __setvar_left="$__setvar_left${__setvar_right#*\'}"
245         eval "$__setvar_var_to_set='$__setvar_left'"
246 }
247 fi
248
249 # f_which $anything [$var_to_set]
250 #
251 # A fast built-in replacement for syntaxes such as foo=$( which bar ). In a
252 # comparison of 10,000 runs of this function versus which, this function
253 # completed in under 3 seconds, while `which' took almost a full minute.
254 #
255 # If $var_to_set is missing or NULL, output is (like which) to standard out.
256 # Returns success if a match was found, failure otherwise.
257 #
258 f_which()
259 {
260         local __name="$1" __var_to_set="$2"
261         case "$__name" in */*|'') return $FAILURE; esac
262         local __p IFS=":" __found=
263         for __p in $PATH; do
264                 local __exec="$__p/$__name"
265                 [ -f "$__exec" -a -x "$__exec" ] && __found=1 && break
266         done
267         if [ "$__found" ]; then
268                 if [ "$__var_to_set" ]; then
269                         setvar "$__var_to_set" "$__exec"
270                 else
271                         echo "$__exec"
272                 fi
273                 return $SUCCESS
274         fi
275         return $FAILURE
276 }
277
278 # f_getvar $var_to_get [$var_to_set]
279 #
280 # Utility function designed to go along with the already-builtin setvar.
281 # Allows clean variable name indirection without forking or sub-shells.
282 #
283 # Returns error status if the requested variable ($var_to_get) is not set.
284 #
285 # If $var_to_set is missing or NULL, the value of $var_to_get is printed to
286 # standard output for capturing in a sub-shell (which is less-recommended
287 # because of performance degredation; for example, when called in a loop).
288 #
289 f_getvar()
290 {
291         local __var_to_get="$1" __var_to_set="$2"
292         [ "$__var_to_set" ] || local value
293         eval [ \"\${$__var_to_get+set}\" ]
294         local __retval=$?
295         eval ${__var_to_set:-value}=\"\${$__var_to_get}\"
296         eval f_dprintf '"f_getvar: var=[%s] value=[%s] r=%u"' \
297                 \"\$__var_to_get\" \"\$${__var_to_set:-value}\" \$__retval
298         [ "$__var_to_set" ] || { [ "$value" ] && echo "$value"; }
299         return $__retval
300 }
301
302 # f_isset $var
303 #
304 # Check if variable $var is set. Returns success if variable is set, otherwise
305 # returns failure.
306 #
307 f_isset()
308 {
309         eval [ \"\${${1%%[$IFS]*}+set}\" ]
310 }
311
312 # f_die [$status [$format [$arguments ...]]]
313 #
314 # Abruptly terminate due to an error optionally displaying a message in a
315 # dialog box using printf(1) syntax.
316 #
317 f_die()
318 {
319         local status=$FAILURE
320
321         # If there is at least one argument, take it as the status
322         if [ $# -gt 0 ]; then
323                 status=$1
324                 shift 1 # status
325         fi
326
327         # If there are still arguments left, pass them to f_show_msg
328         [ $# -gt 0 ] && f_show_msg "$@"
329
330         # Optionally call f_clean_up() function if it exists
331         f_have f_clean_up && f_clean_up
332
333         exit $status
334 }
335
336 # f_interrupt
337 #
338 # Interrupt handler.
339 #
340 f_interrupt()
341 {
342         exec 2>&1 # fix sh(1) bug where stderr gets lost within async-trap
343         f_die
344 }
345
346 # f_show_info $format [$arguments ...]
347 #
348 # Display a message in a dialog infobox using printf(1) syntax.
349 #
350 f_show_info()
351 {
352         local msg
353         msg=$( printf "$@" )
354
355         #
356         # Use f_dialog_infobox from dialog.subr if possible, otherwise fall
357         # back to dialog(1) (without options, making it obvious when using
358         # un-aided system dialog).
359         #
360         if f_have f_dialog_info; then
361                 f_dialog_info "$msg"
362         else
363                 dialog --infobox "$msg" 0 0
364         fi
365 }
366
367 # f_show_msg $format [$arguments ...]
368 #
369 # Display a message in a dialog box using printf(1) syntax.
370 #
371 f_show_msg()
372 {
373         local msg
374         msg=$( printf "$@" )
375
376         #
377         # Use f_dialog_msgbox from dialog.subr if possible, otherwise fall
378         # back to dialog(1) (without options, making it obvious when using
379         # un-aided system dialog).
380         #
381         if f_have f_dialog_msgbox; then
382                 f_dialog_msgbox "$msg"
383         else
384                 dialog --msgbox "$msg" 0 0
385         fi
386 }
387
388 # f_show_err $format [$arguments ...]
389 #
390 # Display a message in a dialog box with ``Error'' i18n title (overridden by
391 # setting msg_error) using printf(1) syntax.
392 #
393 f_show_err()
394 {
395         local msg
396         msg=$( printf "$@" )
397
398         : ${msg:=${msg_an_unknown_error_occurred:-An unknown error occurred}}
399
400         if [ "$_DIALOG_SUBR" ]; then
401                 f_dialog_title "${msg_error:-Error}"
402                 f_dialog_msgbox "$msg"
403                 f_dialog_title_restore
404         else
405                 dialog --title "${msg_error:-Error}" --msgbox "$msg" 0 0
406         fi
407         return $SUCCESS
408 }
409
410 # f_yesno $format [$arguments ...]
411 #
412 # Display a message in a dialog yes/no box using printf(1) syntax.
413 #
414 f_yesno()
415 {
416         local msg
417         msg=$( printf "$@" )
418
419         #
420         # Use f_dialog_yesno from dialog.subr if possible, otherwise fall
421         # back to dialog(1) (without options, making it obvious when using
422         # un-aided system dialog).
423         #
424         if f_have f_dialog_yesno; then
425                 f_dialog_yesno "$msg"
426         else
427                 dialog --yesno "$msg" 0 0
428         fi
429 }
430
431 # f_noyes $format [$arguments ...]
432 #
433 # Display a message in a dialog yes/no box using printf(1) syntax.
434 # NOTE: THis is just like the f_yesno function except "No" is default.
435 #
436 f_noyes()
437 {
438         local msg
439         msg=$( printf "$@" )
440
441         #
442         # Use f_dialog_noyes from dialog.subr if possible, otherwise fall
443         # back to dialog(1) (without options, making it obvious when using
444         # un-aided system dialog).
445         #
446         if f_have f_dialog_noyes; then
447                 f_dialog_noyes "$msg"
448         else
449                 dialog --defaultno --yesno "$msg" 0 0
450         fi
451 }
452
453 # f_show_help $file
454 #
455 # Display a language help-file. Automatically takes $LANG and $LC_ALL into
456 # consideration when displaying $file (suffix ".$LC_ALL" or ".$LANG" will
457 # automatically be added prior to loading the language help-file).
458 #
459 # If a language has been requested by setting either $LANG or $LC_ALL in the
460 # environment and the language-specific help-file does not exist we will fall
461 # back to $file without-suffix.
462 #
463 # If the language help-file does not exist, an error is displayed instead.
464 #
465 f_show_help()
466 {
467         local file="$1"
468         local lang="${LANG:-$LC_ALL}"
469
470         [ -f "$file.$lang" ] && file="$file.$lang"
471
472         #
473         # Use f_dialog_textbox from dialog.subr if possible, otherwise fall
474         # back to dialog(1) (without options, making it obvious when using
475         # un-aided system dialog).
476         #
477         if f_have f_dialog_textbox; then
478                 f_dialog_textbox "$file"
479         else
480                 dialog --msgbox "$( cat "$file" 2>&1 )" 0 0
481         fi
482 }
483
484 # f_include $file
485 #
486 # Include a shell subroutine file.
487 #
488 # If the subroutine file exists but returns error status during loading, exit
489 # is called and execution is prematurely terminated with the same error status.
490 #
491 f_include()
492 {
493         local file="$1"
494         f_dprintf "f_include: file=[%s]" "$file"
495         . "$file" || exit $?
496 }
497
498 # f_include_lang $file
499 #
500 # Include a language file. Automatically takes $LANG and $LC_ALL into
501 # consideration when including $file (suffix ".$LC_ALL" or ".$LANG" will
502 # automatically by added prior to loading the language file).
503 #
504 # No error is produced if (a) a language has been requested (by setting either
505 # $LANG or $LC_ALL in the environment) and (b) the language file does not
506 # exist -- in which case we will fall back to loading $file without-suffix.
507 #
508 # If the language file exists but returns error status during loading, exit
509 # is called and execution is prematurely terminated with the same error status.
510 #
511 f_include_lang()
512 {
513         local file="$1"
514         local lang="${LANG:-$LC_ALL}"
515
516         f_dprintf "f_include_lang: file=[%s] lang=[%s]" "$file" "$lang"
517         if [ -f "$file.$lang" ]; then
518                 . "$file.$lang" || exit $?
519         else
520                 . "$file" || exit $?
521         fi
522 }
523
524 # f_usage $file [$key1 $value1 ...]
525 #
526 # Display USAGE file with optional pre-processor macro definitions. The first
527 # argument is the template file containing the usage text to be displayed. If
528 # $LANG or $LC_ALL (in order of preference, respectively) is set, ".encoding"
529 # will automatically be appended as a suffix to the provided $file pathname.
530 #
531 # When processing $file, output begins at the first line containing that is
532 # (a) not a comment, (b) not empty, and (c) is not pure-whitespace. All lines
533 # appearing after this first-line are output, including (a) comments (b) empty
534 # lines, and (c) lines that are purely whitespace-only.
535 #
536 # If additional arguments appear after $file, substitutions are made while
537 # printing the contents of the USAGE file. The pre-processor macro syntax is in
538 # the style of autoconf(1), for example:
539 #
540 #       f_usage $file "FOO" "BAR"
541 #
542 # Will cause instances of "@FOO@" appearing in $file to be replaced with the
543 # text "BAR" before being printed to the screen.
544 #
545 # This function is a two-parter. Below is the awk(1) portion of the function,
546 # afterward is the sh(1) function which utilizes the below awk script.
547 #
548 f_usage_awk='
549 BEGIN { found = 0 }
550 {
551         if ( !found && $0 ~ /^[[:space:]]*($|#)/ ) next
552         found = 1
553         print
554 }
555 '
556 f_usage()
557 {
558         local file="$1"
559         local lang="${LANG:-$LC_ALL}"
560
561         f_dprintf "f_usage: file=[%s] lang=[%s]" "$file" "$lang"
562
563         shift 1 # file
564
565         local usage
566         if [ -f "$file.$lang" ]; then
567                 usage=$( awk "$f_usage_awk" "$file.$lang" ) || exit $FAILURE
568         else
569                 usage=$( awk "$f_usage_awk" "$file" ) || exit $FAILURE
570         fi
571
572         while [ $# -gt 0 ]; do
573                 local key="$1"
574                 export value="$2"
575                 usage=$( echo "$usage" | awk \
576                         "{ gsub(/@$key@/, ENVIRON[\"value\"]); print }" )
577                 shift 2
578         done
579
580         f_err "%s\n" "$usage"
581
582         exit $FAILURE
583 }
584
585 # f_index_file $keyword [$var_to_set]
586 #
587 # Process all INDEX files known to bsdconfig and return the path to first file
588 # containing a menu_selection line with a keyword portion matching $keyword.
589 #
590 # If $LANG or $LC_ALL (in order of preference, respectively) is set,
591 # "INDEX.encoding" files will be searched first.
592 #
593 # If no file is found, error status is returned along with the NULL string.
594 #
595 # If $var_to_set is NULL or missing, output is printed to stdout (which is less
596 # recommended due to performance degradation; in a loop for example).
597 #
598 # This function is a two-parter. Below is the awk(1) portion of the function,
599 # afterward is the sh(1) function which utilizes the below awk script.
600 #
601 f_index_file_awk='
602 # Variables that should be defined on the invocation line:
603 #       -v keyword="keyword"
604 BEGIN { found = 0 }
605 ( $0 ~ "^menu_selection=\"" keyword "\\|" ) {
606         print FILENAME
607         found++
608         exit
609 }
610 END { exit ! found }
611 '
612 f_index_file()
613 {
614         local __keyword="$1" __var_to_set="$2"
615         local __lang="${LANG:-$LC_ALL}"
616         local __indexes="$BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX"
617
618         f_dprintf "f_index_file: keyword=[%s] lang=[%s]" "$__keyword" "$__lang"
619
620         if [ "$__lang" ]; then
621                 if [ "$__var_to_set" ]; then
622                         eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \
623                                 "$f_index_file_awk" $__indexes.$__lang
624                         )"' && return $SUCCESS
625                 else
626                         awk -v keyword="$__keyword" "$f_index_file_awk" \
627                                 $__indexes.$__lang && return $SUCCESS
628                 fi
629                 # No match, fall-thru to non-i18n sources
630         fi
631         if [ "$__var_to_set" ]; then
632                 eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \
633                         "$f_index_file_awk" $__indexes )"' && return $SUCCESS
634         else
635                 awk -v keyword="$__keyword" "$f_index_file_awk" $__indexes &&
636                         return $SUCCESS
637         fi
638
639         # No match? Fall-thru to `local' libexec sources (add-on modules)
640
641         [ "$BSDCFG_LOCAL_LIBE" ] || return $FAILURE
642         __indexes="$BSDCFG_LOCAL_LIBE/*/INDEX"
643         if [ "$__lang" ]; then
644                 if [ "$__var_to_set" ]; then
645                         eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \
646                                 "$f_index_file_awk" $__indexes.$__lang
647                         )"' && return $SUCCESS
648                 else
649                         awk -v keyword="$__keyword" "$f_index_file_awk" \
650                                 $__indexes.$__lang && return $SUCCESS
651                 fi
652                 # No match, fall-thru to non-i18n sources
653         fi
654         if [ "$__var_to_set" ]; then
655                 eval "$__var_to_set"='$( awk -v keyword="$__keyword" \
656                         "$f_index_file_awk" $__indexes )"'
657         else
658                 awk -v keyword="$__keyword" "$f_index_file_awk" $__indexes
659         fi
660 }
661
662 # f_index_menusel_keyword $indexfile $pgm [$var_to_set]
663 #
664 # Process $indexfile and return only the keyword portion of the menu_selection
665 # line with a command portion matching $pgm.
666 #
667 # This function is for internationalization (i18n) mapping of the on-disk
668 # scriptname ($pgm) into the localized language (given language-specific
669 # $indexfile). If $LANG or $LC_ALL (in orderder of preference, respectively) is
670 # set, ".encoding" will automatically be appended as a suffix to the provided
671 # $indexfile pathname.
672 #
673 # If, within $indexfile, multiple $menu_selection values map to $pgm, only the
674 # first one will be returned. If no mapping can be made, the NULL string is
675 # returned.
676 #
677 # If $indexfile does not exist, error status is returned with NULL.
678 #
679 # If $var_to_set is NULL or missing, output is printed to stdout (which is less
680 # recommended due to performance degradation; in a loop for example).
681 #
682 # This function is a two-parter. Below is the awk(1) portion of the function,
683 # afterward is the sh(1) function which utilizes the below awk script.
684 #
685 f_index_menusel_keyword_awk='
686 # Variables that should be defined on the invocation line:
687 #       -v pgm="program_name"
688 #
689 BEGIN {
690         prefix = "menu_selection=\""
691         plen = length(prefix)
692         found = 0
693 }
694 {
695         if (!match($0, "^" prefix ".*\\|.*\"")) next
696
697         keyword = command = substr($0, plen + 1, RLENGTH - plen - 1)
698         sub(/^.*\|/, "", command)
699         sub(/\|.*$/, "", keyword)
700
701         if ( command == pgm )
702         {
703                 print keyword
704                 found++
705                 exit
706         }
707 }
708 END { exit ! found }
709 '
710 f_index_menusel_keyword()
711 {
712         local __indexfile="$1" __pgm="$2" __var_to_set="$3"
713         local __lang="${LANG:-$LC_ALL}" __file="$__indexfile"
714
715         [ -f "$__indexfile.$__lang" ] && __file="$__indexfile.$__lang"
716         f_dprintf "f_index_menusel_keyword: index=[%s] pgm=[%s] lang=[%s]" \
717                   "$__file" "$__pgm" "$__lang"
718
719         if [ "$__var_to_set" ]; then
720                 setvar "$__var_to_set" "$( awk \
721                     -v pgm="$__pgm" "$f_index_menusel_keyword_awk" "$__file"
722                 )"
723         else
724                 awk -v pgm="$__pgm" "$f_index_menusel_keyword_awk" "$__file"
725         fi
726 }
727
728 # f_index_menusel_command $indexfile $keyword [$var_to_set]
729 #
730 # Process $indexfile and return only the command portion of the menu_selection
731 # line with a keyword portion matching $keyword.
732 #
733 # This function is for mapping [possibly international] keywords into the
734 # command to be executed. If $LANG or $LC_ALL (order of preference) is set,
735 # ".encoding" will automatically be appended as a suffix to the provided
736 # $indexfile pathname.
737 #
738 # If, within $indexfile, multiple $menu_selection values map to $keyword, only
739 # the first one will be returned. If no mapping can be made, the NULL string is
740 # returned.
741 #
742 # If $indexfile doesn't exist, error status is returned with NULL.
743 #
744 # If $var_to_set is NULL or missing, output is printed to stdout (which is less
745 # recommended due to performance degradation; in a loop for example).
746 #
747 # This function is a two-parter. Below is the awk(1) portion of the function,
748 # afterward is the sh(1) function which utilizes the below awk script.
749 #
750 f_index_menusel_command_awk='
751 # Variables that should be defined on the invocation line:
752 #       -v key="keyword"
753 #
754 BEGIN {
755         prefix = "menu_selection=\""
756         plen = length(prefix)
757         found = 0
758 }
759 {
760         if (!match($0, "^" prefix ".*\\|.*\"")) next
761
762         keyword = command = substr($0, plen + 1, RLENGTH - plen - 1)
763         sub(/^.*\|/, "", command)
764         sub(/\|.*$/, "", keyword)
765
766         if ( keyword == key )
767         {
768                 print command
769                 found++
770                 exit
771         }
772 }
773 END { exit ! found }
774 '
775 f_index_menusel_command()
776 {
777         local __indexfile="$1" __keyword="$2" __var_to_set="$3" __command
778         local __lang="${LANG:-$LC_ALL}" __file="$__indexfile"
779
780         [ -f "$__indexfile.$__lang" ] && __file="$__indexfile.$__lang"
781         f_dprintf "f_index_menusel_command: index=[%s] key=[%s] lang=[%s]" \
782                   "$__file" "$__keyword" "$__lang"
783
784         [ -f "$__file" ] || return $FAILURE
785         __command=$( awk -v key="$__keyword" \
786                 "$f_index_menusel_command_awk" "$__file" ) || return $FAILURE
787
788         #
789         # If the command pathname is not fully qualified fix-up/force to be
790         # relative to the $indexfile directory.
791         #
792         case "$__command" in
793         /*) : already fully qualified ;;
794         *)
795                 local __indexdir="${__indexfile%/*}"
796                 [ "$__indexdir" != "$__indexfile" ] || __indexdir="."
797                 __command="$__indexdir/$__command"
798         esac
799
800         if [ "$__var_to_set" ]; then
801                 setvar "$__var_to_set" "$__command"
802         else
803                 echo "$__command"
804         fi
805 }
806
807 # f_running_as_init
808 #
809 # Returns true if running as init(1).
810 #
811 f_running_as_init()
812 {
813         #
814         # When a custom init(8) performs an exec(3) to invoke a shell script,
815         # PID 1 becomes sh(1) and $PPID is set to 1 in the executed script.
816         #
817         [ ${PPID:-0} -eq 1 ] # Return status
818 }
819
820 # f_mounted $local_directory
821 # f_mounted -b $device
822 #
823 # Return success if a filesystem is mounted on a particular directory. If `-b'
824 # is present, instead check that the block device (or a partition thereof) is
825 # mounted.
826 #
827 f_mounted()
828 {
829         local OPTIND OPTARG flag use_device=
830         while getopts b flag; do
831                 case "$flag" in
832                 b) use_device=1 ;;
833                 esac
834         done
835         shift $(( $OPTIND - 1 ))
836         if [ "$use_device" ]; then
837                 local device="$1"
838                 mount | grep -Eq \
839                         "^$device([[:space:]]|p[0-9]|s[0-9]|\.nop|\.eli)"
840         else
841                 [ -d "$dir" ] || return $FAILURE
842                 mount | grep -Eq " on $dir \([^)]+\)$"
843         fi
844         # Return status is that of last grep(1)
845 }
846
847 # f_eval_catch [-de] [-k $var_to_set] $funcname $utility \
848 #              $format [$arguments ...]
849 #
850 # Silently evaluate a command in a sub-shell and test for error. If debugging
851 # is enabled a copy of the command and its output is sent to debug (either
852 # stdout or file depending on environment). If an error occurs, output of the
853 # command is displayed in a dialog(1) msgbox using the [above] f_show_err()
854 # function (unless optional `-d' flag is given, then no dialog).
855 #
856 # The $funcname argument is sent to debugging while the $utility argument is
857 # used in the title of the dialog box. The command that is executed as well as
858 # sent to debugging with $funcname is the product of the printf(1) syntax
859 # produced by $format with optional $arguments.
860 #
861 # The following options are supported:
862 #
863 #       -d      Do not use dialog(1).
864 #       -e      Produce error text from failed command on stderr.
865 #       -k var  Save output from the command in var.
866 #
867 # Example 1:
868 #
869 #       debug=1
870 #       f_eval_catch myfunc echo 'echo "%s"' "Hello, World!"
871 #
872 #       Produces the following debug output:
873 #
874 #               DEBUG: myfunc: echo "Hello, World!"
875 #               DEBUG: myfunc: retval=0 <output below>
876 #               Hello, World!
877 #
878 # Example 2:
879 #
880 #       debug=1
881 #       f_eval_catch -k contents myfunc cat 'cat "%s"' /some/file
882 #       # dialog(1) Error ``cat: /some/file: No such file or directory''
883 #       # contents=[cat: /some/file: No such file or directory]
884 #
885 #       Produces the following debug output:
886 #
887 #               DEBUG: myfunc: cat "/some/file"
888 #               DEBUG: myfunc: retval=1 <output below>
889 #               cat: /some/file: No such file or directory
890 #
891 # Example 3:
892 #
893 #       debug=1
894 #       echo 123 | f_eval_catch myfunc rev rev
895 #
896 #       Produces the following debug output:
897 #
898 #               DEBUG: myfunc: rev
899 #               DEBUG: myfunc: retval=0 <output below>
900 #               321
901 #
902 # Example 4:
903 #
904 #       debug=1
905 #       f_eval_catch myfunc true true
906 #
907 #       Produces the following debug output:
908 #
909 #               DEBUG: myfunc: true
910 #               DEBUG: myfunc: retval=0 <no output>
911 #
912 # Example 5:
913 #
914 #       f_eval_catch -de myfunc ls 'ls "%s"' /some/dir
915 #       # Output on stderr ``ls: /some/dir: No such file or directory''
916 #
917 # Example 6:
918 #
919 #       f_eval_catch -dek contents myfunc ls 'ls "%s"' /etc
920 #       # Output from `ls' sent to stderr and also saved in $contents
921 #
922 f_eval_catch()
923 {
924         local __no_dialog= __show_err= __var_to_set=
925
926         #
927         # Process local function arguments
928         #
929         local OPTIND OPTARG __flag
930         while getopts "dek:" __flag > /dev/null; do
931                 case "$__flag" in
932                 d) __no_dialog=1 ;;
933                 e) __show_err=1 ;;
934                 k) __var_to_set="$OPTARG" ;;
935                 esac
936         done
937         shift $(( $OPTIND - 1 ))
938
939         local __funcname="$1" __utility="$2"; shift 2
940         local __cmd __output __retval
941
942         __cmd=$( printf -- "$@" )
943         f_dprintf "%s: %s" "$__funcname" "$__cmd" # Log command *before* eval
944         __output=$( exec 2>&1; eval "$__cmd" )
945         __retval=$?
946         if [ "$__output" ]; then
947                 [ "$__show_err" ] && echo "$__output" >&2
948                 f_dprintf "%s: retval=%i <output below>\n%s" "$__funcname" \
949                           $__retval "$__output"
950         else
951                 f_dprintf "%s: retval=%i <no output>" "$__funcname" $__retval
952         fi
953
954         ! [ "$__no_dialog" -o "$nonInteractive" -o $__retval -eq $SUCCESS ] &&
955                 msg_error="${msg_error:-Error}${__utility:+: $__utility}" \
956                         f_show_err "%s" "$__output"
957                 # NB: f_show_err will handle NULL output appropriately
958
959         [ "$__var_to_set" ] && setvar "$__var_to_set" "$__output"
960
961         return $__retval
962 }
963
964 # f_count $var_to_set arguments ...
965 #
966 # Sets $var_to_set to the number of arguments minus one (the effective number
967 # of arguments following $var_to_set).
968 #
969 # Example:
970 #       f_count count dog house # count=[2]
971 #
972 f_count()
973 {
974         setvar "$1" $(( $# - 1 ))
975 }
976
977 # f_count_ifs $var_to_set string ...
978 #
979 # Sets $var_to_set to the number of words (split by the internal field
980 # separator, IFS) following $var_to_set.
981 #
982 # Example 1:
983 #
984 #       string="word1   word2   word3"
985 #       f_count_ifs count "$string" # count=[3]
986 #       f_count_ifs count $string # count=[3]
987 #
988 # Example 2:
989 #
990 #       IFS=. f_count_ifs count www.freebsd.org # count=[3]
991 #
992 # NB: Make sure to use double-quotes if you are using a custom value for IFS
993 # and you don't want the current value to effect the result. See example 3.
994 #
995 # Example 3:
996 #
997 #       string="a-b c-d"
998 #       IFS=- f_count_ifs count "$string" # count=[3]
999 #       IFS=- f_count_ifs count $string # count=[4]
1000 #
1001 f_count_ifs()
1002 {
1003         local __var_to_set="$1"
1004         shift 1
1005         set -- $*
1006         setvar "$__var_to_set" $#
1007 }
1008
1009 ############################################################ MAIN
1010
1011 #
1012 # Trap signals so we can recover gracefully
1013 #
1014 trap 'f_interrupt' SIGINT
1015 trap 'f_die' SIGTERM SIGPIPE SIGXCPU SIGXFSZ \
1016              SIGFPE SIGTRAP SIGABRT SIGSEGV
1017 trap '' SIGALRM SIGPROF SIGUSR1 SIGUSR2 SIGHUP SIGVTALRM
1018
1019 #
1020 # Clone terminal stdout/stderr so we can redirect to it from within sub-shells
1021 #
1022 eval exec $TERMINAL_STDOUT_PASSTHRU\>\&1
1023 eval exec $TERMINAL_STDERR_PASSTHRU\>\&2
1024
1025 #
1026 # Self-initialize unless requested otherwise
1027 #
1028 f_dprintf "%s: DEBUG_SELF_INITIALIZE=[%s]" \
1029           dialog.subr "$DEBUG_SELF_INITIALIZE"
1030 case "$DEBUG_SELF_INITIALIZE" in
1031 ""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
1032 *) f_debug_init
1033 esac
1034
1035 #
1036 # Log our operating environment for debugging purposes
1037 #
1038 f_dprintf "UNAME_S=[%s] UNAME_P=[%s] UNAME_R=[%s]" \
1039           "$UNAME_S" "$UNAME_P" "$UNAME_R"
1040
1041 f_dprintf "%s: Successfully loaded." common.subr
1042
1043 fi # ! $_COMMON_SUBR