rc revision 1.172
1#!/bin/sh 2# 3# $NetBSD: rc,v 1.172 2017/02/02 20:49:55 christos Exp $ 4# 5# rc -- 6# Run the scripts in /etc/rc.d with rcorder, and log output 7# to /var/run/rc.log. 8 9# System startup script run by init(8) on autoboot or after single-user. 10# Output and error are redirected to console by init, and the console 11# is the controlling terminal. 12 13export HOME=/ 14export PATH=/sbin:/bin:/usr/sbin:/usr/bin 15umask 022 16 17if [ -e ./rc.subr ] ; then 18 . ./rc.subr # for testing 19else 20 . /etc/rc.subr 21fi 22. /etc/rc.conf 23_rc_conf_loaded=true 24 25: ${RC_LOG_FILE:="/var/run/rc.log"} 26 27# rc.subr redefines echo and printf. Undo that here. 28unset echo ; unalias echo 29unset printf ; unalias printf 30 31if ! checkyesno rc_configured; then 32 echo "/etc/rc.conf is not configured. Multiuser boot aborted." 33 exit 1 34fi 35 36if [ "$1" = autoboot ]; then 37 autoboot=yes 38 rc_fast=yes # run_rc_command(): do fast booting 39fi 40 41# 42# Completely ignore INT and QUIT at the outer level. The rc_real_work() 43# function should do something different. 44# 45trap '' INT QUIT 46 47# 48# This string will be used to mark lines of meta-data sent over the pipe 49# from the rc_real_work() function to the rc_postprocess() function. Lines 50# not so marked are assumed to be output from rc.d scripts. 51# 52# This string is long and unique to ensure that it does not accidentally 53# appear in output from any rc.d script. It must not contain any 54# characters that are special to glob expansion ('*', '?', '[', or ']'). 55# 56rc_metadata_prefix="$0:$$:metadata:"; 57 58# Child scripts may sometimes want to print directly to the original 59# stdout and stderr, bypassing the pipe to the postprocessor. These 60# _rc_*_fd variables are private, shared with /etc/rc.subr, but not 61# intended to be used directly by child scripts. (Child scripts 62# may use rc.subr's no_rc_postprocess function.) 63# 64_rc_original_stdout_fd=7; export _rc_original_stdout_fd 65_rc_original_stderr_fd=8; export _rc_original_stderr_fd 66eval "exec ${_rc_original_stdout_fd}>&1" 67eval "exec ${_rc_original_stderr_fd}>&2" 68fdflags -s +cloexec 7 8 69 70# 71# rc_real_work 72# Do the real work. Output from this function will be piped into 73# rc_postprocess(), and some of the output will be marked as 74# metadata. 75# 76# The body of this function is defined using (...), not {...}, to force 77# it to run in a subshell. 78# 79rc_real_work() 80( 81 stty status '^T' 82 83 # print_rc_metadata() wants to be able to print to the pipe 84 # that goes to our postprocessor, even if its in a context 85 # with redirected output. 86 # 87 _rc_postprocessor_fd=9 ; export _rc_postprocessor_fd 88 _rc_pid=$$ ; export _rc_pid 89 eval "exec ${_rc_postprocessor_fd}>&1" 90 91 # Print a metadata line when we exit 92 # 93 trap 'es=$?; print_rc_metadata "exit:$es"; trap "" 0; exit $es' 0 94 95 # Set shell to ignore SIGINT, but children will not ignore it. 96 # Shell catches SIGQUIT and returns to single user. 97 # 98 trap : INT 99 trap '_msg="Boot interrupted at $(date)"; 100 print_rc_metadata "interrupted:${_msg}"; 101 exit 1' QUIT 102 103 print_rc_metadata "start:$(date)" 104 105 # 106 # The stop_boot() function in rc.subr may kill $RC_PID. We want 107 # it to kill the subshell running this rc_real_work() function, 108 # rather than killing the parent shell, because we want the 109 # rc_postprocess() function to be able to log the error 110 # without being killed itself. 111 # 112 # "$$" is the pid of the top-level shell, not the pid of the 113 # subshell that's executing this function. The command below 114 # tentatively assumes that the parent of the "/bin/sh -c ..." 115 # process will be the current subshell, and then uses "kill -0 116 # ..." to check the result. If the "/bin/sh -c ..." process 117 # fails, or returns the pid of an ephemeral process that exits 118 # before the "kill" command, then we fall back to using "$$". 119 # 120 RC_PID=$(/bin/sh -c 'ps -p $$ -o ppid=') || RC_PID=$$ 121 kill -0 $RC_PID >/dev/null 2>&1 || RC_PID=$$ 122 123 # 124 # As long as process $RC_PID is still running, send a "nop" 125 # metadata message to the postprocessor every few seconds. 126 # This should help flush partial lines that may appear when 127 # rc.d scripts that are NOT marked with "KEYWORD: interactive" 128 # nevertheless attempt to print prompts and wait for input. 129 # 130 ( 131 # First detach from tty, to avoid intercepting SIGINFO. 132 eval "exec ${_rc_original_stdout_fd}<&-" 133 eval "exec ${_rc_original_stderr_fd}<&-" 134 exec </dev/null >/dev/null 2>&1 135 while kill -0 $RC_PID ; do 136 print_rc_metadata "nop" 137 sleep 3 138 done 139 ) & 140 141 # 142 # Get a list of all rc.d scripts, and use rcorder to choose 143 # what order to execute them. 144 # 145 # For testing, allow RC_FILES_OVERRIDE from the environment to 146 # override this. 147 # 148 print_rc_metadata "cmd-name:rcorder" 149 scripts=$(for rcd in ${rc_directories:-/etc/rc.d}; do 150 test -d ${rcd} && echo ${rcd}/*; 151 done) 152 files=$(rcorder -s nostart ${rc_rcorder_flags} ${scripts}) 153 print_rc_metadata "cmd-status:rcorder:$?" 154 155 if [ -n "${RC_FILES_OVERRIDE}" ]; then 156 files="${RC_FILES_OVERRIDE}" 157 fi 158 159 # 160 # Run the scripts in order. 161 # 162 for _rc_elem in $files; do 163 print_rc_metadata "cmd-name:$_rc_elem" 164 run_rc_script $_rc_elem start 165 print_rc_metadata "cmd-status:$_rc_elem:$?" 166 done 167 168 print_rc_metadata "end:$(date)" 169 exit 0 170) 171 172# 173# rc_postprocess 174# Post-process the output from the rc_real_work() function. For 175# each line of input, we have to decide whether to print the line 176# to the console, print a twiddle on the console, print a line to 177# the log, or some combination of these. 178# 179# If rc_silent is true, then suppress most output, instead running 180# rc_silent_cmd (typically "twiddle") for each line. 181# 182# The body of this function is defined using (...), not {...}, to force 183# it to run in a subshell. 184# 185# We have to deal with the following constraints: 186# 187# * There may be no writable file systems early in the boot, so 188# any use of temporary files would be problematic. 189# 190# * Scripts run during the boot may clear /tmp and/var/run, so even 191# if they are writable, using those directories too early may be 192# problematic. We assume that it's safe to write to our log file 193# after the mountcritlocal script has run. 194# 195# * /usr/bin/tee cannot be used because the /usr file system may not 196# be mounted early in the boot. 197# 198# * All calls to the rc_log_message and rc_log_flush functions must be 199# from the same subshell, otherwise the use of a shell variable to 200# buffer log messages will fail. 201# 202rc_postprocess() 203( 204 local line 205 local before after 206 local IFS='' 207 208 # Try quite hard to flush the log to disk when we exit. 209 trap 'es=$?; rc_log_flush FORCE; trap "" 0; exit $es' 0 210 211 yesno_to_truefalse rc_silent 2>/dev/null 212 213 while read -r line ; do 214 case "$line" in 215 "${rc_metadata_prefix}"*) 216 after="${line#*"${rc_metadata_prefix}"}" 217 rc_postprocess_metadata "${after}" 218 ;; 219 *"${rc_metadata_prefix}"*) 220 # magic string is present, but not at the start of 221 # the line. Treat it as a partial line of 222 # ordinary data, followed by a line of metadata. 223 before="${line%"${rc_metadata_prefix}"*}" 224 rc_postprocess_partial_line "${before}" 225 after="${line#*"${rc_metadata_prefix}"}" 226 rc_postprocess_metadata "${after}" 227 ;; 228 *) 229 rc_postprocess_plain_line "${line}" 230 ;; 231 esac 232 done 233 234 # If we get here, then the rc_real_work() function must have 235 # exited uncleanly. A clean exit would have been accompanied by 236 # a line of metadata that would have prevented us from getting 237 # here. 238 # 239 exit 1 240) 241 242# 243# rc_postprocess_plain_line string 244# $1 is a string representing a line of output from one of the 245# rc.d scripts. Append the line to the log, and also either 246# display the line on the console, or run $rc_silent_cmd, 247# depending on the value of $rc_silent. 248# 249rc_postprocess_plain_line() 250{ 251 local line="$1" 252 rc_log_message "${line}" 253 if $rc_silent; then 254 eval "$rc_silent_cmd" 255 else 256 printf "%s\n" "${line}" 257 fi 258} 259 260# 261# rc_postprocess_partial_line string 262# This is just like rc_postprocess_plain_line, except that 263# a newline is not appended to the string. 264# 265rc_postprocess_partial_line() 266{ 267 local line="$1" 268 rc_log_message_n "${line}" 269 if $rc_silent; then 270 eval "$rc_silent_cmd" 271 else 272 printf "%s" "${line}" 273 fi 274} 275 276# 277# rc_postprocess_metadata string 278# $1 is a string containing metadata from the rc_real_work() 279# function. The rc_metadata_prefix marker should already 280# have been removed before the string is passed to this function. 281# Take appropriate action depending on the content of the string. 282# 283rc_postprocess_metadata() 284{ 285 local metadata="$1" 286 local keyword args 287 local msg 288 local IFS=':' 289 290 # given metadata="bleep:foo bar:baz", 291 # set keyword="bleep", args="foo bar:baz", 292 # $1="foo bar", $2="baz" 293 # 294 keyword="${metadata%%:*}" 295 args="${metadata#*:}" 296 set -- $args 297 298 case "$keyword" in 299 start) 300 # Marks the start of the entire /etc/rc script. 301 # $args contains a date/time. 302 rc_log_message "[$0 starting at $args]" 303 if ! $rc_silent; then 304 printf "%s\n" "$args" 305 fi 306 ;; 307 cmd-name) 308 # Marks the start of a child script (usually one of 309 # the /etc/rc.d/* scripts). 310 rc_log_message "[running $1]" 311 ;; 312 cmd-status) 313 # Marks the end of a child script. 314 # $1 is a command name, $2 is the command's exit status. 315 # If the command failed, report it, and add it to a list. 316 if [ "$2" != 0 ]; then 317 rc_failures="${rc_failures}${rc_failures:+ }$1" 318 msg="$1 $(human_exit_code $2)" 319 rc_log_message "$msg" 320 if ! $rc_silent; then 321 printf "%s\n" "$msg" 322 fi 323 fi 324 # After the mountcritlocal script has finished, it's 325 # OK to flush the log to disk 326 case "$1" in 327 */mountcritlocal) 328 rc_log_flush OK 329 ;; 330 esac 331 ;; 332 nop) 333 # Do nothing. 334 # This has the side effect of flushing partial lines, 335 # and the echo() and printf() functions in rc.subr take 336 # advantage of this. 337 ;; 338 note) 339 # Unlike most metadata messages, which should be used 340 # only by /etc/rc and rc.subr, the "note" message may be 341 # used directly by /etc.rc.d/* and similar scripts. 342 # It adds a note to the log file, without displaying 343 # it to stdout. 344 rc_log_message "[NOTE: $args]" 345 ;; 346 end) 347 # Marks the end of processing, after the last child script. 348 # If any child scripts (or other commands) failed, report them. 349 # 350 if [ -n "$rc_failures" ]; then 351 rc_log_message "[failures]" 352 msg="The following components reported failures:" 353 msg="${msg}${nl}$( echo " ${rc_failures}" | fmt )" 354 msg="${msg}${nl}See ${RC_LOG_FILE} for more information." 355 rc_log_message "${msg}" 356 printf "%s\n" "${msg}" 357 fi 358 # 359 # Report the end date/time, even in silent mode 360 # 361 rc_log_message "[$0 finished at $args]" 362 printf "%s\n" "$args" 363 ;; 364 exit) 365 # Marks an exit from the rc_real_work() function. 366 # This may be a normal or abnormal exit. 367 # 368 rc_log_message "[$0 exiting with status $1]" 369 exit $1 370 ;; 371 interrupted) 372 # Marks an interrupt trapped by the rc_real_work() function. 373 # $args is a human-readable message. 374 rc_log_message "$args" 375 printf "%s\n" "$args" 376 ;; 377 *) 378 # an unrecognised line of metadata 379 rc_log_message "[metadata:${metadata}]" 380 ;; 381 esac 382} 383 384# 385# rc_log_message string [...] 386# Write a message to the log file, or buffer it for later. 387# This function appends a newline to the message. 388# 389rc_log_message() 390{ 391 _rc_log_buffer="${_rc_log_buffer}${*}${nl}" 392 rc_log_flush 393} 394 395# 396# rc_log_message_n string [...] 397# Just like rc_log_message, except without appending a newline. 398# 399rc_log_message_n() 400{ 401 _rc_log_buffer="${_rc_log_buffer}${*}" 402 rc_log_flush 403} 404 405# 406# rc_log_flush [OK|FORCE] 407# save outstanding messages from $_rc_log_buffer to $RC_LOG_FILE. 408# 409# The log file is expected to reside in the /var/run directory, which 410# may not be writable very early in the boot sequence, and which is 411# erased a little later in the boot sequence. We therefore avoid 412# writing to the file until we believe it's safe to do so. We also 413# assume that it's reasonable to always append to the file, never 414# truncating it. 415# 416# Optional argument $1 may be "OK" to report that writing to the log 417# file is expected to be safe from now on, or "FORCE" to force writing 418# to the log file even if it may be unsafe. 419# 420# Returns a non-zero status if messages could not be written to the 421# file. 422# 423rc_log_flush() 424{ 425 # 426 # If $_rc_log_flush_ok is false, then it's probably too early to 427 # write to the log file, so don't do it, unless $1 is "FORCE". 428 # 429 : ${_rc_log_flush_ok=false} 430 case "$1:$_rc_log_flush_ok" in 431 OK:*) 432 _rc_log_flush_ok=true 433 ;; 434 FORCE:*) 435 : OK just this once 436 ;; 437 *:true) 438 : OK 439 ;; 440 *) 441 # it's too early in the boot sequence, so don't flush 442 return 1 443 ;; 444 esac 445 446 # 447 # Now append the buffer to the file. The buffer should already 448 # contain a trailing newline, so don't add an extra newline. 449 # 450 if [ -n "$_rc_log_buffer" ]; then 451 if { printf "%s" "${_rc_log_buffer}" >>"${RC_LOG_FILE}" ; } \ 452 2>/dev/null 453 then 454 _rc_log_buffer="" 455 else 456 return 1 457 fi 458 fi 459 return 0 460} 461 462# 463# Most of the action is in the rc_real_work() and rc_postprocess() 464# functions. 465# 466rc_real_work "$@" 2>&1 | rc_postprocess 467exit $? 468