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