]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.sbin/bsdconfig/share/sysrc.subr
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / usr.sbin / bsdconfig / share / sysrc.subr
1 if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_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 (INCLUDING, 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
31 BSDCFG_SHARE="/usr/share/bsdconfig"
32 [ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1
33
34 BSDCFG_LIBE="/usr/libexec/bsdconfig"
35 if [ ! "$_SYSRC_JAILED" ]; then
36         f_dprintf "%s: loading includes..." sysrc.subr
37         f_include_lang $BSDCFG_LIBE/include/messages.subr
38 fi
39
40 ############################################################ CONFIGURATION
41
42 #
43 # Standard pathnames (inherit values from shell if available)
44 #
45 : ${RC_DEFAULTS:="/etc/defaults/rc.conf"}
46
47 ############################################################ GLOBALS
48
49 #
50 # Global exit status variables
51 #
52 SUCCESS=0
53 FAILURE=1
54
55 #
56 # Valid characters that can appear in an sh(1) variable name
57 #
58 # Please note that the character ranges A-Z and a-z should be avoided because
59 # these can include accent characters (which are not valid in a variable name).
60 # For example, A-Z matches any character that sorts after A but before Z,
61 # including A and Z. Although ASCII order would make more sense, that is not
62 # how it works.
63 #
64 VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
65
66 ############################################################ FUNCTIONS
67
68 # f_clean_env [ --except $varname ... ]
69 #
70 # Unset all environment variables in the current scope. An optional list of
71 # arguments can be passed, indicating which variables to avoid unsetting; the
72 # `--except' is required to enable the exclusion-list as the remainder of
73 # positional arguments.
74 #
75 # Be careful not to call this in a shell that you still expect to perform
76 # $PATH expansion in, because this will blow $PATH away. This is best used
77 # within a sub-shell block "(...)" or "$(...)" or "`...`".
78 #
79 f_clean_env()
80 {
81         local var arg except=
82
83         #
84         # Should we process an exclusion-list?
85         #
86         if [ "$1" = "--except" ]; then
87                 except=1
88                 shift 1
89         fi
90
91         #
92         # Loop over a list of variable names from set(1) built-in.
93         #
94         for var in $( set | awk -F= \
95                 '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \
96                 | grep -v '^except$'
97         ); do
98                 #
99                 # In POSIX bourne-shell, attempting to unset(1) OPTIND results
100                 # in "unset: Illegal number:" and causes abrupt termination.
101                 #
102                 [ "$var" = OPTIND ] && continue
103
104                 #
105                 # Process the exclusion-list?
106                 #
107                 if [ "$except" ]; then
108                         for arg in "$@" ""; do
109                                 [ "$var" = "$arg" ] && break
110                         done
111                         [ "$arg" ] && continue
112                 fi
113
114                 unset "$var"
115         done
116 }
117
118 # f_sysrc_get $varname
119 #
120 # Get a system configuration setting from the collection of system-
121 # configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf
122 # and /etc/rc.conf).
123 #
124 # NOTE: Additional shell parameter-expansion formats are supported. For
125 # example, passing an argument of "hostname%%.*" (properly quoted) will
126 # return the hostname up to (but not including) the first `.' (see sh(1),
127 # "Parameter Expansion" for more information on additional formats).
128 #
129 f_sysrc_get()
130 {
131         # Sanity check
132         [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
133
134         # Taint-check variable name
135         case "$1" in
136         [0-9]*)
137                 # Don't expand possible positional parameters
138                 return $FAILURE ;;
139         *)
140                 [ "$1" ] || return $FAILURE
141         esac
142
143         ( # Execute within sub-shell to protect parent environment
144
145                 #
146                 # Clear the environment of all variables, preventing the
147                 # expansion of normals such as `PS1', `TERM', etc.
148                 #
149                 f_clean_env --except IFS RC_CONFS RC_DEFAULTS
150
151                 . "$RC_DEFAULTS" > /dev/null 2>&1
152
153                 unset RC_DEFAULTS
154                         # no longer needed
155
156                 #
157                 # If the query is for `rc_conf_files' then store the value that
158                 # we inherited from sourcing RC_DEFAULTS (above) so that we may
159                 # conditionally restore this value after source_rc_confs in the
160                 # event that RC_CONFS does not customize the value.
161                 #
162                 if [ "$1" = "rc_conf_files" ]; then
163                         _rc_conf_files="$rc_conf_files"
164                 fi
165
166                 #
167                 # If RC_CONFS is defined, set $rc_conf_files to an explicit
168                 # value, modifying the default behavior of source_rc_confs().
169                 #
170                 if [ "${RC_CONFS+set}" ]; then
171                         rc_conf_files="$RC_CONFS"
172                         _rc_confs_set=1
173                 fi
174
175                 source_rc_confs > /dev/null 2>&1
176
177                 #
178                 # If the query was for `rc_conf_files' AND after calling
179                 # source_rc_confs the value has not changed, then we should
180                 # restore the value to the one inherited from RC_DEFAULTS
181                 # before performing the final query (preventing us from
182                 # returning what was set via RC_CONFS when the intent was
183                 # instead to query the value from the file(s) specified).
184                 #
185                 if [ "$1" = "rc_conf_files" -a \
186                      "$_rc_confs_set" -a \
187                      "$rc_conf_files" = "$RC_CONFS" \
188                 ]; then
189                         rc_conf_files="$_rc_conf_files"
190                         unset _rc_conf_files
191                         unset _rc_confs_set
192                 fi
193
194                 unset RC_CONFS
195                         # no longer needed
196
197                 #
198                 # This must be the last functional line for both the sub-shell
199                 # and the function to preserve the return status from formats
200                 # such as "${varname?}" and "${varname:?}" (see "Parameter
201                 # Expansion" in sh(1) for more information).
202                 #
203                 eval echo '"${'"$1"'}"' 2> /dev/null
204         )
205 }
206
207 # f_sysrc_get_default $varname
208 #
209 # Get a system configuration default setting from the default rc.conf(5) file
210 # (or whatever RC_DEFAULTS points at).
211 #
212 f_sysrc_get_default()
213 {
214         # Sanity check
215         [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
216
217         # Taint-check variable name
218         case "$1" in
219         [0-9]*)
220                 # Don't expand possible positional parameters
221                 return $FAILURE ;;
222         *)
223                 [ "$1" ] || return $FAILURE
224         esac
225
226         ( # Execute within sub-shell to protect parent environment
227
228                 #
229                 # Clear the environment of all variables, preventing the
230                 # expansion of normals such as `PS1', `TERM', etc.
231                 #
232                 f_clean_env --except RC_DEFAULTS
233
234                 . "$RC_DEFAULTS" > /dev/null 2>&1
235
236                 unset RC_DEFAULTS
237                         # no longer needed
238
239                 #
240                 # This must be the last functional line for both the sub-shell
241                 # and the function to preserve the return status from formats
242                 # such as "${varname?}" and "${varname:?}" (see "Parameter
243                 # Expansion" in sh(1) for more information).
244                 #
245                 eval echo '"${'"$1"'}"' 2> /dev/null
246         )
247 }
248
249 # f_sysrc_find $varname
250 #
251 # Find which file holds the effective last-assignment to a given variable
252 # within the rc.conf(5) file(s).
253 #
254 # If the variable is found in any of the rc.conf(5) files, the function prints
255 # the filename it was found in and then returns success. Otherwise output is
256 # NULL and the function returns with error status.
257 #
258 f_sysrc_find()
259 {
260         local varname="${1%%[!$VALID_VARNAME_CHARS]*}"
261         local regex="^[[:space:]]*$varname="
262         local rc_conf_files="$( f_sysrc_get rc_conf_files )"
263         local conf_files=
264         local file
265
266         # Check parameters
267         case "$varname" in
268         ""|[0-9]*) return $FAILURE
269         esac
270
271         #
272         # If RC_CONFS is defined, set $rc_conf_files to an explicit
273         # value, modifying the default behavior of source_rc_confs().
274         #
275         [ "${RC_CONFS+set}" ] && rc_conf_files="$RC_CONFS"
276
277         #
278         # Reverse the order of files in rc_conf_files (the boot process sources
279         # these in order, so we will search them in reverse-order to find the
280         # last-assignment -- the one that ultimately effects the environment).
281         #
282         for file in $rc_conf_files; do
283                 conf_files="$file${conf_files:+ }$conf_files"
284         done
285
286         #
287         # Append the defaults file (since directives in the defaults file
288         # indeed affect the boot process, we'll want to know when a directive
289         # is found there).
290         #
291         conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS"
292
293         #
294         # Find which file matches assignment to the given variable name.
295         #
296         for file in $conf_files; do
297                 [ -f "$file" -a -r "$file" ] || continue
298                 if grep -Eq "$regex" $file; then
299                         echo $file
300                         return $SUCCESS
301                 fi
302         done
303
304         return $FAILURE # Not found
305 }
306
307 # f_sysrc_desc $varname
308 #
309 # Attempts to return the comments associated with varname from the rc.conf(5)
310 # defaults file `/etc/defaults/rc.conf' (or whatever RC_DEFAULTS points to).
311 #
312 # Multi-line comments are joined together. Results are NULL if no description
313 # could be found.
314 #
315 # This function is a two-parter. Below is the awk(1) portion of the function,
316 # afterward is the sh(1) function which utilizes the below awk script.
317 #
318 f_sysrc_desc_awk='
319 # Variables that should be defined on the invocation line:
320 #       -v varname="varname"
321 #
322 BEGIN {
323         regex = "^[[:space:]]*"varname"="
324         found = 0
325         buffer = ""
326 }
327 {
328         if ( ! found )
329         {
330                 if ( ! match($0, regex) ) next
331
332                 found = 1
333                 sub(/^[^#]*(#[[:space:]]*)?/, "")
334                 buffer = $0
335                 next
336         }
337
338         if ( !/^[[:space:]]*#/ ||
339               /^[[:space:]]*[[:alpha:]_][[:alnum:]_]*=/ ||
340               /^[[:space:]]*#[[:alpha:]_][[:alnum:]_]*=/ ||
341               /^[[:space:]]*$/ ) exit
342
343         sub(/(.*#)*[[:space:]]*/, "")
344         buffer = buffer" "$0
345 }
346 END {
347         # Clean up the buffer
348         sub(/^[[:space:]]*/, "", buffer)
349         sub(/[[:space:]]*$/, "", buffer)
350
351         print buffer
352         exit ! found
353 }
354 '
355 f_sysrc_desc()
356 {
357         awk -v varname="$1" "$f_sysrc_desc_awk" < "$RC_DEFAULTS"
358 }
359
360 # f_sysrc_set $varname $new_value
361 #
362 # Change a setting in the system configuration files (edits the files in-place
363 # to change the value in the last assignment to the variable). If the variable
364 # does not appear in the source file, it is appended to the end of the primary
365 # system configuration file `/etc/rc.conf'.
366 #
367 # This function is a two-parter. Below is the awk(1) portion of the function,
368 # afterward is the sh(1) function which utilizes the below awk script.
369 #
370 f_sysrc_set_awk='
371 # Variables that should be defined on the invocation line:
372 #       -v varname="varname"
373 #       -v new_value="new_value"
374 #
375 BEGIN {
376         regex = "^[[:space:]]*"varname"="
377         found = retval = 0
378 }
379 {
380         # If already found... just spew
381         if ( found ) { print; next }
382
383         # Does this line match an assignment to our variable?
384         if ( ! match($0, regex) ) { print; next }
385
386         # Save important match information
387         found = 1
388         matchlen = RSTART + RLENGTH - 1
389
390         # Store the value text for later munging
391         value = substr($0, matchlen + 1, length($0) - matchlen)
392
393         # Store the first character of the value
394         t1 = t2 = substr(value, 0, 1)
395
396         # Assignment w/ back-ticks, expression, or misc.
397         # We ignore these since we did not generate them
398         #
399         if ( t1 ~ /[`$\\]/ ) { retval = 1; print; next }
400
401         # Assignment w/ single-quoted value
402         else if ( t1 == "'\''" ) {
403                 sub(/^'\''[^'\'']*/, "", value)
404                 if ( length(value) == 0 ) t2 = ""
405                 sub(/^'\''/, "", value)
406         }
407
408         # Assignment w/ double-quoted value
409         else if ( t1 == "\"" ) {
410                 sub(/^"(.*\\\\+")*[^"]*/, "", value)
411                 if ( length(value) == 0 ) t2 = ""
412                 sub(/^"/, "", value)
413         }
414
415         # Assignment w/ non-quoted value
416         else if ( t1 ~ /[^[:space:];]/ ) {
417                 t1 = t2 = "\""
418                 sub(/^[^[:space:]]*/, "", value)
419         }
420
421         # Null-assignment
422         else if ( t1 ~ /[[:space:];]/ ) { t1 = t2 = "\"" }
423
424         printf "%s%c%s%c%s\n", substr($0, 0, matchlen), \
425                 t1, new_value, t2, value
426 }
427 END { exit retval }
428 '
429 f_sysrc_set()
430 {
431         local varname="$1" new_value="$2"
432
433         # Check arguments
434         [ "$varname" ] || return $FAILURE
435
436         #
437         # Find which rc.conf(5) file contains the last-assignment
438         #
439         local not_found=
440         local file="$( f_sysrc_find "$varname" )"
441         if [ "$file" = "$RC_DEFAULTS" -o ! "$file" ]; then
442                 #
443                 # We either got a null response (not found) or the variable
444                 # was only found in the rc.conf(5) defaults. In either case,
445                 # let's instead modify the first file from $rc_conf_files.
446                 #
447
448                 not_found=1
449
450                 #
451                 # If RC_CONFS is defined, use $RC_CONFS
452                 # rather than $rc_conf_files.
453                 #
454                 if [ "${RC_CONFS+set}" ]; then
455                         file="${RC_CONFS%%[$IFS]*}"
456                 else
457                         file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' )
458                 fi
459         fi
460
461         #
462         # If not found, append new value to last file and return.
463         #
464         if [ "$not_found" ]; then
465                 echo "$varname=\"$new_value\"" >> "$file"
466                 return $?
467         fi
468
469         #
470         # Perform sanity checks.
471         #
472         if [ ! -w "$file" ]; then
473                 f_err "$msg_cannot_create_permission_denied\n" \
474                       "$pgm" "$file"
475                 return $FAILURE
476         fi
477
478         #
479         # Create a new temporary file to write to.
480         #
481         local tmpfile="$( mktemp -t "$pgm" )"
482         [ "$tmpfile" ] || return $FAILURE
483
484         #
485         # Fixup permissions (else we're in for a surprise, as mktemp(1) creates
486         # the temporary file with 0600 permissions, and if we simply mv(1) the
487         # temporary file over the destination, the destination will inherit the
488         # permissions from the temporary file).
489         #
490         local mode
491         mode=$( stat -f '%#Lp' "$file" 2> /dev/null )
492         f_quietly chmod "${mode:-0644}" "$tmpfile"
493
494         #
495         # Fixup ownership. The destination file _is_ writable (we tested
496         # earlier above). However, this will fail if we don't have sufficient
497         # permissions (so we throw stderr into the bit-bucket).
498         #
499         local owner
500         owner=$( stat -f '%u:%g' "$file" 2> /dev/null )
501         f_quietly chown "${owner:-root:wheel}" "$tmpfile"
502
503         #
504         # Operate on the matching file, replacing only the last occurrence.
505         #
506         local new_contents retval
507         new_contents=$( tail -r $file 2> /dev/null )
508         new_contents=$( echo "$new_contents" | awk -v varname="$varname" \
509                 -v new_value="$new_value" "$f_sysrc_set_awk" )
510         retval=$?
511
512         #
513         # Write the temporary file contents.
514         #
515         echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE
516         if [ $retval -ne $SUCCESS ]; then
517                 echo "$varname=\"$new_value\"" >> "$tmpfile"
518         fi
519
520         #
521         # Taint-check our results.
522         #
523         if ! /bin/sh -n "$tmpfile"; then
524                 f_err "$msg_previous_syntax_errors\n" "$pgm" "$file"
525                 rm -f "$tmpfile"
526                 return $FAILURE
527         fi
528
529         #
530         # Finally, move the temporary file into place.
531         #
532         mv "$tmpfile" "$file"
533 }
534
535 # f_sysrc_delete $varname
536 #
537 # Remove a setting from the system configuration files (edits files in-place).
538 # Deletes all assignments to the given variable in all config files. If the
539 # `-f file' option is passed, the removal is restricted to only those files
540 # specified, otherwise the system collection of rc_conf_files is used.
541 #
542 # This function is a two-parter. Below is the awk(1) portion of the function,
543 # afterward is the sh(1) function which utilizes the below awk script.
544 #
545 f_sysrc_delete_awk='
546 # Variables that should be defined on the invocation line:
547 #       -v varname="varname"
548 #
549 BEGIN {
550         regex = "^[[:space:]]*"varname"="
551         found = 0
552 }
553 {
554         if ( $0 ~ regex )
555                 found = 1
556         else
557                 print
558 }
559 END { exit ! found }
560 '
561 f_sysrc_delete()
562 {
563         local varname="$1"
564         local file
565
566         # Check arguments
567         [ "$varname" ] || return $FAILURE
568
569         #
570         # Operate on each of the specified files
571         #
572         for file in ${RC_CONFS-$( f_sysrc_get rc_conf_files )}; do
573                 [ -e "$file" ] || continue
574
575                 #
576                 # Create a new temporary file to write to.
577                 #
578                 local tmpfile="$( mktemp -t "$pgm" )"
579                 [ "$tmpfile" ] || return $FAILURE
580
581                 #
582                 # Fixup permissions and ownership (mktemp(1) defaults to 0600
583                 # permissions) to instead match the destination file.
584                 #
585                 local mode owner
586                 mode=$( stat -f '%#Lp' "$file" 2> /dev/null )
587                 owner=$( stat -f '%u:%g' "$file" 2> /dev/null )
588                 f_quietly chmod "${mode:-0644}" "$tmpfile"
589                 f_quietly chown "${owner:-root:wheel}" "$tmpfile"
590
591                 #
592                 # Operate on the file, removing all occurrences, saving the
593                 # output in our temporary file.
594                 #
595                 awk -v varname="$varname" "$f_sysrc_delete_awk" "$file" \
596                         > "$tmpfile"
597                 if [ $? -ne $SUCCESS ]; then
598                         # The file didn't contain any assignments
599                         rm -f "$tmpfile"
600                         continue
601                 fi
602
603                 #
604                 # Taint-check our results.
605                 #
606                 if ! /bin/sh -n "$tmpfile"; then
607                         f_err "$msg_previous_syntax_errors\n" \
608                               "$pgm" "$file"
609                         rm -f "$tmpfile"
610                         return $FAILURE
611                 fi
612
613                 #
614                 # Perform sanity checks
615                 #
616                 if [ ! -w "$file" ]; then
617                         f_err "$msg_permission_denied\n" "$pgm" "$file"
618                         rm -f "$tmpfile"
619                         return $FAILURE
620                 fi
621
622                 #
623                 # Finally, move the temporary file into place.
624                 #
625                 mv "$tmpfile" "$file"
626         done
627 }
628
629 ############################################################ MAIN
630
631 f_dprintf "%s: Successfully loaded." sysrc.subr
632
633 fi # ! $_SYSRC_SUBR