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