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