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 (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 31BSDCFG_SHARE="/usr/share/bsdconfig" 32[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1 33 34BSDCFG_LIBE="/usr/libexec/bsdconfig" 35if [ ! "$_SYSRC_JAILED" ]; then 36 f_dprintf "%s: loading includes..." sysrc.subr 37 f_include_lang $BSDCFG_LIBE/include/messages.subr 38fi 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# 52SUCCESS=0 53FAILURE=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# 64VALID_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# 79f_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# 129f_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# 212f_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# 258f_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# 318f_sysrc_desc_awk=' 319# Variables that should be defined on the invocation line: 320# -v varname="varname" 321# 322BEGIN { 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} 346END { 347 # Clean up the buffer 348 sub(/^[[:space:]]*/, "", buffer) 349 sub(/[[:space:]]*$/, "", buffer) 350 351 print buffer 352 exit ! found 353} 354' 355f_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# 370f_sysrc_set_awk=' 371# Variables that should be defined on the invocation line: 372# -v varname="varname" 373# -v new_value="new_value" 374# 375BEGIN { 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} 427END { exit retval } 428' 429f_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# 545f_sysrc_delete_awk=' 546# Variables that should be defined on the invocation line: 547# -v varname="varname" 548# 549BEGIN { 550 regex = "^[[:space:]]*"varname"=" 551 found = 0 552} 553{ 554 if ( $0 ~ regex ) 555 found = 1 556 else 557 print 558} 559END { exit ! found } 560' 561f_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 631f_dprintf "%s: Successfully loaded." sysrc.subr 632 633fi # ! $_SYSRC_SUBR 634