xref: /freebsd/usr.sbin/bsdconfig/startup/share/rcconf.subr (revision 724b4bfdf1306e4f2c451b6d146fe0fe0353b2c8)
1if [ ! "$_STARTUP_RCCONF_SUBR" ]; then _STARTUP_RCCONF_SUBR=1
2#
3# Copyright (c) 2006-2012 Devin Teske
4# All Rights Reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27# $FreeBSD$
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32. $BSDCFG_SHARE/common.subr || exit 1
33f_include $BSDCFG_SHARE/sysrc.subr
34
35BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
36f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
37
38############################################################ GLOBALS
39
40#
41# Initialize in-memory cache variables
42#
43STARTUP_RCCONF_MAP=
44_STARTUP_RCCONF_MAP=
45
46#
47# Define what a variable looks like
48#
49STARTUP_RCCONF_REGEX="^[[:alpha:]_][[:alnum:]_]*="
50
51#
52# Default path to on-disk cache file(s)
53#
54STARTUP_RCCONF_MAP_CACHEFILE="/var/run/bsdconfig/startup_rcconf_map.cache"
55
56############################################################ FUNCTIONS
57
58# f_startup_rcconf_list
59#
60# Produce a list of non-default configuration variables configured in the
61# rc.conf(5) collection of files.
62#
63f_startup_rcconf_list()
64{
65	( # Operate within a sub-shell to protect the parent environment
66		. "$RC_DEFAULTS" > /dev/null
67		f_clean_env --except PATH STARTUP_RCCONF_REGEX rc_conf_files
68		source_rc_confs > /dev/null
69		export _rc_conf_files_file="$( f_sysrc_find rc_conf_files )"
70		export RC_DEFAULTS
71		set | awk -F= "
72		function test_print(var)
73		{
74			if ( var == \"OPTIND\" ) return
75			if ( var == \"PATH\" ) return
76			if ( var == \"RC_DEFAULTS\" ) return
77			if ( var == \"STARTUP_RCCONF_REGEX\" ) return
78			if ( var == \"_rc_conf_files_file\" ) return
79			if ( var == \"rc_conf_files\" )
80			{
81				if ( ENVIRON[\"_rc_conf_files_file\"] == \
82				     ENVIRON[\"RC_DEFAULTS\"] ) return
83			}
84			print var
85		}
86		/$STARTUP_RCCONF_REGEX/ { test_print(\$1) }"
87	)
88}
89
90# f_startup_rcconf_map
91#
92# Produce a map (beit from in-memory cache or on-disk cache) of rc.conf(5)
93# variables and their descriptions. The map returned has the following format:
94#
95# 	var description
96#
97# With each as follows:
98#
99# 	var           the rc.conf(5) variable
100# 	description   description of the variable
101#
102f_startup_rcconf_map()
103{
104	# If the in-memory cached value is available, return it immediately
105	if [ "$_STARTUP_RCCONF_MAP" ]; then
106		echo "$STARTUP_RCCONF_MAP"
107		return $SUCCESS
108	fi
109
110	#
111	# create the in-memory cache (potentially from validated on-disk cache)
112	#
113
114	#
115	# Calculate digest used to determine if the on-disk global persistant
116	# cache file (containing this digest on the first line) is valid and
117	# can be used to quickly populate the cache value for immediate return.
118	#
119	local rc_defaults_digest
120	rc_defaults_digest=$( md5 < "$RC_DEFAULTS" )
121
122	#
123	# Check to see if the global persistant cache file exists
124	#
125	if [ -f "$STARTUP_RCCONF_MAP_CACHEFILE" ]; then
126		#
127		# Attempt to populate the in-memory cache with the (soon to be)
128		# be validated on-disk cache. If validation fails, fall-back to
129		# the current value and provide error exit status.
130		#
131		STARTUP_RCCONF_MAP=$(
132			(	# Get digest as the first word on the first line
133				read digest rest_ignored
134
135				#
136				# If the stored digest matches the calculated-
137				# one populate the in-memory cache from the on-
138				# disk cache and provide success exit status.
139				#
140				if [ "$digest" = "$rc_defaults_digest" ]; then
141					cat
142					exit $SUCCESS
143				else
144					# Otherwise, return the current value
145					echo "$STARTUP_RCCONF_MAP"
146					exit $FAILURE
147				fi
148			) < "$STARTUP_RCCONF_MAP_CACHEFILE"
149		)
150		export STARTUP_RCCONF_MAP
151		if [ $? -eq $SUCCESS ]; then
152			export _STARTUP_RCCONF_MAP=1
153			echo "$STARTUP_RCCONF_MAP"
154			return $SUCCESS
155		fi
156		# Otherwise, fall-thru to create in-memory cache from scratch
157	fi
158
159	#
160	# If we reach this point, we need to generate the data from scratch
161	# (and after we do, we'll attempt to create the global persistant
162	# cache file to speed up future executions).
163	#
164
165	STARTUP_RCCONF_MAP=$(
166		f_clean_env --except \
167			PATH                 \
168			RC_DEFAULTS          \
169			STARTUP_RCCONF_REGEX \
170			f_sysrc_desc_awk
171		. "$RC_DEFAULTS"
172
173		# Unset variables we don't want reported
174		unset source_rc_confs_defined
175
176		for var in $( set | awk -F= "
177			function test_print(var)
178			{
179				if ( var == \"OPTIND\" ) return
180				if ( var == \"PATH\" ) return
181				if ( var == \"RC_DEFAULTS\" ) return
182				if ( var == \"STARTUP_RCCONF_REGEX\" ) return
183				if ( var == \"f_sysrc_desc_awk\" ) return
184				print var
185			}
186			/$STARTUP_RCCONF_REGEX/ { test_print(\$1) }
187		" ); do
188			echo $var "$( f_sysrc_desc $var )"
189		done
190	)
191	export STARTUP_RCCONF_MAP
192	export _STARTUP_RCCONF_MAP=1
193	echo "$STARTUP_RCCONF_MAP"
194
195	#
196	# Attempt to create the persistant global cache
197	#
198
199	# Create a new temporary file to write to
200	local tmpfile="$( mktemp -t "$pgm" )"
201	[ "$tmpfile" ] || return $FAILURE
202
203	# Write the temporary file contents
204	echo "$rc_defaults_digest" > "$tmpfile"
205	echo "$STARTUP_RCCONF_MAP" >> "$tmpfile"
206
207	# Finally, move the temporary file into place
208	case "$STARTUP_RCCONF_MAP_CACHEFILE" in
209	*/*) f_quietly mkdir -p "${STARTUP_RCCONF_MAP_CACHEFILE%/*}"
210	esac
211	mv "$tmpfile" "$STARTUP_RCCONF_MAP_CACHEFILE"
212}
213
214# f_startup_rcconf_map_expand
215#
216# Expands the map ($RCCONF_MAP) into the shell environment namespace by
217# creating _${var}_desc variables containing the description of each variable
218# encountered.
219#
220# NOTE: Variables are exported for later-required awk(1) ENVIRON visibility.
221#
222f_startup_rcconf_map_expand()
223{
224	eval "$( echo "$RCCONF_MAP" | awk '
225	BEGIN {
226		rword = "^[[:space:]]*[^[:space:]]*[[:space:]]*"
227	}
228	{
229		var  = $1
230		desc = $0
231		sub(rword, "", desc)
232		gsub(/'\''/, "'\''\\'\'\''", desc)
233		printf "_%s_desc='\''%s'\''\n", var, desc
234		printf "export _%s_desc\n", var
235	}' )"
236}
237
238# f_dialog_input_view_details
239#
240# Display a menu for selecting which details are to be displayed. The following
241# variables are tracked/modified by the menu/user's selection:
242#
243# 	SHOW_DESC		Show or hide descriptions
244#
245# Mutually exclusive options:
246#
247# 	SHOW_VALUE		Show the value (default; override only)
248# 	SHOW_VALUE_DEFAULT	Show both value and default
249# 	SHOW_CONFIGURED		Show rc.conf(5) file variable is configured in
250#
251# Each variable is treated as a boolean (NULL for false, non-NULL for true).
252#
253# Variables are exported for later-required awk(1) ENVIRON visibility.
254#
255f_dialog_input_view_details()
256{
257	local menu_list size
258	local hline="$hline_arrows_tab_enter"
259	local prompt=""
260
261	local md=" " m1=" " m2=" " m3=" "
262	[ "$SHOW_DESC"          ] && md="X"
263	[ "$SHOW_VALUE"         ] && m1="*"
264	[ "$SHOW_DEFAULT_VALUE" ] && m2="*"
265	[ "$SHOW_CONFIGURED"    ] && m3="*"
266
267	menu_list="
268		'X $msg_exit'                     '$msg_exit_this_menu'
269		'R $msg_reset'                    '$msg_reset_desc'
270		'D [$md] $msg_desc'               '$msg_desc_desc'
271		'1 ($m1) $msg_show_value'         '$msg_show_value_desc'
272		'2 ($m2) $msg_show_default_value' '$msg_show_default_value_desc'
273		'3 ($m3) $msg_show_configured'    '$msg_show_configured_desc'
274	" # END-QUOTE
275
276	size=$( eval f_dialog_menu_size \
277	        	\"\$DIALOG_TITLE\"     \
278	        	\"\$DIALOG_BACKTITLE\" \
279	                \"\$prompt\"           \
280	        	\"\$hline\"            \
281	        	$menu_list             )
282
283	f_dialog_title "$msg_choose_view_details"
284
285	local dialog_menu
286	dialog_menu=$( eval $DIALOG \
287		--title \"\$DIALOG_TITLE\" \
288		--backtitle \"\$DIALOG_BACKTITLE\" \
289		--hline \"\$hline\"                \
290		--ok-label \"\$msg_ok\"            \
291		--cancel-label \"\$msg_cancel\"    \
292		--menu \"\$prompt\" $size          \
293		$menu_list                         \
294		2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
295	)
296
297	local retval=$?
298	setvar DIALOG_MENU_$$ "$dialog_menu"
299	local mtag="$( f_dialog_menutag )"
300
301	f_dialog_title_restore
302
303	[ $retval -eq 0 ] || return $SUCCESS
304	[ "$mtag" = "X $msg_exit" ] && return $SUCCESS
305
306	case "$mtag" in
307	"R $msg_reset")
308		SHOW_VALUE=1
309		SHOW_DESC=1
310		SHOW_DEFAULT_VALUE=
311		SHOW_CONFIGURED=
312		;;
313	"D [X] $msg_desc") SHOW_DESC=  ;;
314	"D [ ] $msg_desc") SHOW_DESC=1 ;;
315	"1 ("?") $msg_show_value")
316		SHOW_VALUE=1
317		SHOW_DEFAULT_VALUE=
318		SHOW_CONFIGURED=
319		;;
320	"2 ("?") $msg_show_default_value")
321		SHOW_VALUE=
322		SHOW_DEFAULT_VALUE=1
323		SHOW_CONFIGURED=
324		;;
325	"3 ("?") $msg_show_configured")
326		SHOW_VALUE=
327		SHOW_DEFAULT_VALUE=
328		SHOW_CONFIGURED=1
329		;;
330	esac
331}
332
333# f_dialog_input_rclist
334#
335# Presents a menu of rc.conf(5) defaults (with, or without descriptions). This
336# function should be treated like a call to dialog(1) (the exit status should
337# be captured and f_dialog_menutag() should be used to clean-up and get the
338# user's response).
339#
340f_dialog_input_rclist()
341{
342	local size
343	local hline="$hline_arrows_tab_enter"
344	local prompt="$msg_please_select_an_rcconf_directive"
345	local menu_list
346
347	menu_list="
348		'X $msg_exit' '' ${SHOW_DESC:+'$msg_exit_help'}
349	" # END-QUOTE
350
351	if [ ! "$_RCCONF_MAP" ]; then
352		# Generate RCCONF_MAP of `var desc ...' per-line
353		f_dialog_info "$msg_creating_rcconf_map"
354		RCCONF_MAP=$( f_startup_rcconf_map )
355		export RCCONF_MAP
356		# Generate _${var}_desc variables from $RCCONF_MAP
357		f_startup_rcconf_map_expand
358		export _RCCONF_MAP=1
359	fi
360
361	menu_list="$menu_list $(
362		export SHOW_DESC
363		echo "$RCCONF_MAP" | awk '
364		BEGIN {
365			prefix = ""
366			rword  = "^[[:space:]]*[^[:space:]]*[[:space:]]*"
367		}
368		{
369			cur_prefix = tolower(substr($1, 1, 1))
370			printf "'\''"
371			if ( prefix != cur_prefix )
372				prefix = cur_prefix
373			else
374				printf " "
375			rcvar  = $1
376			printf "%s'\'' '\'\''", rcvar
377			if ( ENVIRON["SHOW_DESC"] ) {
378				desc = $0
379				sub(rword, "", desc)
380				gsub(/'\''/, "'\''\\'\'\''", desc)
381				printf " '\''%s'\''", desc
382			}
383			printf "\n"
384		}'
385	)"
386
387	set -f # noglob
388
389	size=$( eval f_dialog_menu_${SHOW_DESC:+with_help_}size \
390	        	\"\$DIALOG_TITLE\"     \
391	        	\"\$DIALOG_BACKTITLE\" \
392	        	\"\$prompt\"           \
393	        	\"\$hline\"            \
394	        	$menu_list             )
395
396	local dialog_menu
397	dialog_menu=$( eval $DIALOG \
398		--title \"\$DIALOG_TITLE\"         \
399		--backtitle \"\$DIALOG_BACKTITLE\" \
400		--hline \"\$hline\"                \
401		--ok-label \"\$msg_ok\"            \
402		--cancel-label \"\$msg_cancel\"    \
403		${SHOW_DESC:+--item-help}          \
404		--menu \"\$prompt\" $size          \
405		$menu_list                         \
406		2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
407	)
408	local retval=$?
409	setvar DIALOG_MENU_$$ "$dialog_menu"
410	return $retval
411}
412
413# f_dialog_input_rcvar [$init]
414#
415# Allows the user to enter the name for a new rc.conf(5) variable. If the user
416# does not cancel or press ESC, the $rcvar variable will hold the newly-
417# configured value upon return.
418#
419f_dialog_input_rcvar()
420{
421	local msg="$msg_please_enter_rcvar_name"
422	local hline="$hline_alnum_tab_enter"
423
424	#
425	# Loop until the user provides taint-free/valid input
426	#
427	local size _input="$1"
428	while :; do
429		size=$( f_dialog_inputbox_size \
430		        	"$DIALOG_TITLE"     \
431		        	"$DIALOG_BACKTITLE" \
432		        	"$msg"              \
433		        	"$_input"           \
434		        	"$hline"            )
435
436		local dialog_inputbox
437		dialog_inputbox=$( eval $DIALOG \
438			--title \"\$DIALOG_TITLE\"         \
439			--backtitle \"\$DIALOG_BACKTITLE\" \
440			--hline \"\$hline\"                \
441			--ok-label \"\$msg_ok\"            \
442			--cancel-label \"\$msg_cancel\"    \
443			--inputbox \"\$msg\" $size         \
444			\"\$_input\"                       \
445			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
446		)
447
448		retval=$?
449		setvar DIALOG_INPUTBOX_$$ "$dialog_inputbox"
450		_input=$( f_dialog_inputstr )
451
452		# Return if user either pressed ESC or chosen Cancel/No
453		[ $retval -eq $SUCCESS ] || return $retval
454
455		# Check for invalid entry (1of2)
456		if ! echo "$_input" | grep -q "^[[:alpha:]_]"; then
457			f_show_msg "$msg_rcvar_must_start_with"
458			continue
459		fi
460
461		# Check for invalid entry (2of2)
462		if ! echo "$_input" | grep -q "^[[:alpha:]_][[:alnum:]_]*$"
463		then
464			f_show_msg "$msg_rcvar_contains_invalid_chars"
465			continue
466		fi
467
468		rcvar="$_input"
469		break
470	done
471
472	f_dprintf "f_dialog_input_rcvar: rcvar->[$rcvar]"
473
474	return $SUCCESS
475}
476
477fi # ! $_STARTUP_RCCONF_SUBR
478