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