bsdconfig revision 293290
1#!/bin/sh
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: stable/10/usr.sbin/bsdconfig/bsdconfig 293290 2016-01-07 00:40:51Z bdrewery $
29#
30############################################################ INCLUDES
31
32# When common.subr is included, it automatically scans "$@" for `-d' and/or
33# `-D file' arguments to conditionally enable debugging. Similarly, when
34# dialog.subr is included, it automatically scans "$@" for `-X' and/or `-S'.
35# To prevent this scanning from becoming confused by extra options, define
36# any/all extra arguments to use in the optstring to getopts when scanning
37# for dedicated options such as those described.
38#
39# NOTE: This needs to be declared before including `common.subr'.
40# NOTE: You really only need to list flags that require an argument as unknown
41#       flags are silently accepted unless they take an argument (in which case
42#       the following argument will terminate option processing unless it looks
43#       like a flag).
44#
45GETOPTS_EXTRA="f:"
46
47BSDCFG_SHARE="/usr/share/bsdconfig"
48. $BSDCFG_SHARE/common.subr || exit 1
49f_dprintf "%s: loading includes..." "$0"
50f_include $BSDCFG_SHARE/dialog.subr
51f_include $BSDCFG_SHARE/mustberoot.subr
52f_include $BSDCFG_SHARE/strings.subr
53
54BSDCFG_LIBE="/usr/libexec/bsdconfig"
55f_include_lang $BSDCFG_LIBE/include/messages.subr
56
57BSDCONFIG_HELPFILE=$BSDCFG_LIBE/include/bsdconfig.hlp
58USAGE_HELPFILE=$BSDCFG_LIBE/include/usage.hlp
59
60############################################################ CONFIGURATION
61
62#
63# Alternate `local' libexec directory for add-on modules (e.g., from ports)
64#
65BSDCFG_LOCAL_LIBE="/usr/local/libexec/bsdconfig"
66
67############################################################ FUNCTIONS
68
69# usage
70#
71# display usage and exit
72#
73usage()
74{
75	local index="INDEX"
76	local cmd_list # Calculated below
77
78	cd $BSDCFG_LIBE
79		# No need to preserve CWD (headed toward exit)
80
81	# Test for language-specific indices
82	f_quietly ls */"$index.${LANG:-$LC_ALL}" &&
83		index="$index.${LANG:-$LC_ALL}"
84
85	cmd_list=$(
86		awk '/^menu_selection="/ {
87			sub(/\|.*/, "")
88			sub(/^menu_selection="/, "")
89			print
90		}' */$index | sort
91	)
92
93	local alt_cmd_list # Calculated below (if $BSDCFG_LOCAL_LIBE exists)
94	if f_quietly cd $BSDCFG_LOCAL_LIBE; then
95		# No need to preserve CWD (headed toward exit)
96
97		# Test for language-specific indices
98		f_quietly ls */"$index.${LANG:-$LC_ALL}" &&
99			index="$index.${LANG:-$LC_ALL}"
100
101		alt_cmd_list=$(
102			awk '/^menu_selection="/ {
103				sub(/\|.*/, "")
104				sub(/^menu_selection="/, "")
105				print
106			}' */$index 2> /dev/null | sort
107		)
108
109		# Conflate lists, removing duplicates
110		cmd_list=$( printf "%s\n%s\n" \
111		                   "$cmd_list" "$alt_cmd_list" | sort -u )
112	fi
113
114	#
115	# Determine the longest command-length (in characters)
116	#
117	local longest_cmd
118	longest_cmd=$( echo "$cmd_list" | f_longest_line_length )
119	f_dprintf "longest_cmd=[%s]" "$longest_cmd"
120
121	#
122	# Determine the maximum width of terminal/console
123	#
124	local max_size="$( stty size 2> /dev/null )"
125	: ${max_size:="24 80"}
126	local max_width="${max_size#*[$IFS]}"
127	f_dprintf "max_width=[%s]" "$max_width"
128
129	#
130	# Using the longest command-length as the width of a single column,
131	# determine if we can use more than one column to display commands.
132	#
133	local x=$longest_cmd ncols=1
134	x=$(( $x + 8 )) # Accommodate leading tab character
135	x=$(( $x + 3 + $longest_cmd )) # Preload end of next column
136	while [ $x -lt $max_width ]; do
137		ncols=$(( $ncols + 1 ))
138		x=$(( $x + 3 + $longest_cmd ))
139	done
140	f_dprintf "ncols=[%u] x=[%u]" $ncols $x
141
142	#
143	# Re-format the command-list into multiple columns
144	#
145	cmd_list=$( eval "$( echo "$cmd_list" |
146		awk -v ncols=$ncols -v size=$longest_cmd '
147		BEGIN {
148			n = 0
149			row_item[1] = ""
150		}
151		function print_row()
152		{
153			fmt = "printf \"\\t%-" size "s"
154			for (i = 1; i < cur_col; i++)
155				fmt = fmt "   %-" size "s"
156			fmt = fmt "\\n\""
157			printf "%s", fmt
158			for (i = 1; i <= cur_col; i++)
159				printf " \"%s\"", row_item[i]
160			print ""
161		}
162		{
163			n++
164			cur_col = (( n - 1 ) % ncols ) + 1
165			printf "f_dprintf \"row_item[%u]=[%%s]\" \"%s\"\n",
166			       cur_col, $0
167			row_item[cur_col] = $0
168			if ( cur_col == ncols ) print_row()
169		}
170		END {
171			if ( cur_col < ncols ) print_row()
172		}' )"
173	)
174
175	f_usage $BSDCFG_LIBE/USAGE \
176	        "PROGRAM_NAME" "$pgm" \
177	        "COMMAND_LIST" "$cmd_list"
178
179	# Never reached
180}
181
182# dialog_menu_main
183#
184# Display the dialog(1)-based application main menu.
185#
186dialog_menu_main()
187{
188	local title="$DIALOG_TITLE"
189	local btitle="$DIALOG_BACKTITLE"
190	local prompt="$msg_menu_text"
191	local menu_list="
192		'X' '$msg_exit'  '$msg_exit_bsdconfig'
193		'1' '$msg_usage' '$msg_quick_start_how_to_use_this_menu_system'
194	" # END-QUOTE
195	local defaultitem= # Calculated below
196	local hline=
197
198	#
199	# Pick up the base modules (directories named `[0-9][0-9][0-9].*')
200	#
201	local menuitem menu_title menu_help menu_selection index=2
202	for menuitem in $( cd $BSDCFG_LIBE && ls -d [0-9][0-9][0-9].* ); do
203		[ -f "$BSDCFG_LIBE/$menuitem/INDEX" ] || continue
204		[ $index -lt ${#DIALOG_MENU_TAGS} ] || break
205
206		menu_program= menu_title= menu_help=
207		f_include_lang $BSDCFG_LIBE/$menuitem/INDEX
208		[ "$menu_program" ] || continue
209
210		case "$menu_program" in
211		/*) : already fully qualified ;;
212		 *) menu_program="$menuitem/$menu_program"
213		esac
214
215		tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
216		setvar "menu_program$tag" "$menu_program"
217
218		f_shell_escape "$menu_title" menu_title
219		f_shell_escape "$menu_help" menu_help
220		menu_list="$menu_list '$tag' '$menu_title' '$menu_help'"
221
222		index=$(( $index + 1 ))
223	done
224
225	#
226	# Process the `local' libexec sources.
227	#
228	# Whereas modules in $BSDCFG_LIBE must be named [0-9][0-9][0-9].*
229	# modules in $BSDCFG_LOCAL_LIBE should NOT be named this way (making it
230	# more practical for port-maintainers).
231	#
232	# This also has the fortunate side-effect of making the de-duplication
233	# effort rather simple (because so-called `base' modules must be named
234	# differently than add-on modules).
235	#
236	local separator_added=
237	for menuitem in $( cd "$BSDCFG_LOCAL_LIBE" 2> /dev/null && ls -d * )
238	do
239		# Skip the module if it looks like a `base' module
240		case "$menuitem" in [0-9][0-9][0-9].*) continue;; esac
241
242		[ -f "$BSDCFG_LOCAL_LIBE/$menuitem/INDEX" ] || continue
243		[ $index -lt ${#DIALOG_MENU_TAGS} ] || break
244
245		menu_program= menu_title= menu_help=
246		f_include_lang $BSDCFG_LOCAL_LIBE/$menuitem/INDEX || continue
247		[ "$menu_program" ] || continue
248
249		if [ ! "$separator_added" ]; then
250			menu_list="$menu_list '-' '-' ''"
251			separator_added=1
252		fi
253
254		case "$menu_program" in
255		/*) : already fully qualified ;;
256		 *) menu_program="$BSDCFG_LOCAL_LIBE/$menuitem/$menu_program"
257		esac
258
259		tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
260		setvar "menu_program$tag" "$menu_program"
261
262		f_shell_escape "$menu_title" menu_title
263		f_shell_escape "$menu_help" menu_help
264		menu_list="$menu_list '$tag' '$menu_title' '$menu_help'"
265
266		index=$(( $index + 1 ))
267	done
268
269	local height width rows
270	eval f_dialog_menu_with_help_size height width rows \
271	                                  \"\$title\"  \
272	                                  \"\$btitle\" \
273	                                  \"\$prompt\" \
274	                                  \"\$hline\"  \
275	                                  $menu_list
276
277	# Obtain default-item from previously stored selection
278	f_dialog_default_fetch defaultitem
279
280	local menu_choice
281	menu_choice=$( eval $DIALOG \
282		--clear                                 \
283		--title \"\$title\"                     \
284		--backtitle \"\$btitle\"                \
285		--hline \"\$hline\"                     \
286		--item-help                             \
287		--ok-label \"\$msg_ok\"                 \
288		--cancel-label \"\$msg_exit_bsdconfig\" \
289		--help-button                           \
290		--help-label \"\$msg_help\"             \
291		${USE_XDIALOG:+--help \"\"}             \
292		--default-item \"\$defaultitem\"        \
293		--menu \"\$prompt\"                     \
294		$height $width $rows                    \
295		$menu_list                              \
296		2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
297	)
298	local retval=$?
299	f_dialog_data_sanitize menu_choice
300	f_dialog_menutag_store "$menu_choice"
301
302	# Only update default-item on success
303	[ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
304
305	return $retval
306}
307
308############################################################ MAIN
309
310#
311# If $0 is not "bsdconfig", interpret it either as a keyword to a menuitem or
312# as a valid resword (see script.subr for additional details about reswords).
313#
314if [ "$pgm" != "bsdconfig" ]; then
315	if indexfile=$( f_index_file "$pgm" ) &&
316	   cmd=$( f_index_menusel_command "$indexfile" "$pgm" )
317	then
318		f_dprintf "pgm=[%s] cmd=[%s] *=[%s]" "$pgm" "$cmd" "$*"
319		exec "$cmd" "$@" || exit 1
320	else
321		f_include $BSDCFG_SHARE/script.subr
322		for resword in $RESWORDS; do
323			[ "$pgm" = "$resword" ] || continue
324			# Found a match
325			f_dprintf "pgm=[%s] A valid resWord!" "$pgm"
326			f_dispatch $resword $resword "$@"
327			exit $?
328		done
329	fi
330fi
331
332#
333# Process command-line arguments
334#
335scripts_loaded=0
336while getopts f:h$GETOPTS_STDARGS flag; do
337	case "$flag" in
338	f) [ $scripts_loaded -eq 0 ] && f_include $BSDCFG_SHARE/script.subr
339	   f_script_load "$OPTARG"
340	   scripts_loaded=$(( $scripts_loaded + 1 )) ;;
341	h|\?) usage ;;
342	esac
343done
344shift $(( $OPTIND - 1 ))
345
346# If we've loaded any scripts, do not continue any further
347[ $scripts_loaded -gt 0 ] && exit
348
349#
350# Initialize
351#
352f_dialog_title "$msg_main_menu"
353
354[ "$SECURE" ] && f_mustberoot_init
355
356# Incorporate rc-file if it exists
357[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
358
359#
360# If a non-option argument was passed, process it as a menuitem selection...
361#
362if [ "$1" ]; then
363	#
364	# ...unless it's a long-option for usage.
365	#
366	case "$1" in -help|--help|-\?)
367		usage
368		# Not reached
369	esac
370
371	#
372	# Find the INDEX (possibly i18n) claiming this keyword and get the
373	# command to execute from the menu_selection line.
374	#
375	if ! { indexfile=$( f_index_file "$1" ) &&
376	       cmd=$( f_index_menusel_command "$indexfile" "$1" )
377	}; then
378		# no matches, display usage (which shows valid keywords)
379		f_err "%s: %s: $msg_not_found\n" "$pgm" "$1"
380		usage
381		# Not reached
382	fi
383
384	f_dprintf "cmd=[%s] *=[%s]" "$cmd" "$*"
385	shift
386	exec $cmd ${USE_XDIALOG:+-X} "$@" || exit 1
387	# Not reached
388fi
389
390#
391# Launch application main menu
392#
393while :; do
394	dialog_menu_main
395	retval=$?
396	f_dialog_menutag_fetch mtag
397	f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
398
399	if [ $retval -eq $DIALOG_HELP ]; then
400		f_show_help "$BSDCONFIG_HELPFILE"
401		continue
402	elif [ $retval -ne $DIALOG_OK ]; then
403		f_die
404	fi
405
406	case "$mtag" in
407	X) break ;;
408	1) # Usage
409	   f_show_help "$USAGE_HELPFILE"
410	   continue
411	esac
412
413	# Anything else is a dynamically loaded menuitem
414
415	f_getvar menu_program$mtag menu_program
416	case "$menu_program" in
417	/*) cmd="$menu_program" ;;
418	 *) cmd="$BSDCFG_LIBE/$menu_program"
419	esac
420	f_dprintf "cmd=[%s]" "$cmd"
421	$cmd ${USE_XDIALOG:+-X}
422done
423
424exit $SUCCESS
425
426################################################################################
427# END
428################################################################################
429