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