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