1#!/bin/sh 2# 3# $NetBSD: rc,v 1.175 2020/09/08 16:10:53 martin 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 fdflags -s +cloexec 9 91 92 # Print a metadata line when we exit 93 # 94 trap 'es=$?; print_rc_metadata "exit:$es"; trap "" 0; exit $es' 0 95 96 # Set shell to ignore SIGINT, but children will not ignore it. 97 # Shell catches SIGQUIT and returns to single user. 98 # 99 trap : INT 100 trap '_msg="Boot interrupted at $(date)"; 101 print_rc_metadata "interrupted:${_msg}"; 102 exit 1' QUIT 103 104 print_rc_metadata "start:$(date)" 105 106 # 107 # The stop_boot() function in rc.subr may kill $RC_PID. We want 108 # it to kill the subshell running this rc_real_work() function, 109 # rather than killing the parent shell, because we want the 110 # rc_postprocess() function to be able to log the error 111 # without being killed itself. 112 # 113 # "$$" is the pid of the top-level shell, not the pid of the 114 # subshell that's executing this function. The command below 115 # tentatively assumes that the parent of the "/bin/sh -c ..." 116 # process will be the current subshell, and then uses "kill -0 117 # ..." to check the result. If the "/bin/sh -c ..." process 118 # fails, or returns the pid of an ephemeral process that exits 119 # before the "kill" command, then we fall back to using "$$". 120 # 121 RC_PID=$(/bin/sh -c 'ps -p $$ -o ppid=') || RC_PID=$$ 122 kill -0 $RC_PID >/dev/null 2>&1 || RC_PID=$$ 123 124 # 125 # As long as process $RC_PID is still running, send a "nop" 126 # metadata message to the postprocessor every few seconds. 127 # This should help flush partial lines that may appear when 128 # rc.d scripts that are NOT marked with "KEYWORD: interactive" 129 # nevertheless attempt to print prompts and wait for input. 130 # 131 ( 132 # First detach from tty, to avoid intercepting SIGINFO. 133 eval "exec ${_rc_original_stdout_fd}<&-" 134 eval "exec ${_rc_original_stderr_fd}<&-" 135 exec </dev/null >/dev/null 2>&1 136 while kill -0 $RC_PID ; do 137 print_rc_metadata "nop" 138 sleep 3 139 done 140 ) & 141 142 # 143 # Get a list of all rc.d scripts, and use rcorder to choose 144 # what order to execute them. 145 # 146 # For testing, allow RC_FILES_OVERRIDE from the environment to 147 # override this. 148 # 149 print_rc_metadata "cmd-name:rcorder" 150 scripts=$(for rcd in ${rc_directories:-/etc/rc.d}; do 151 test -d ${rcd} && echo ${rcd}/*; 152 done) 153 files=$(rcorder -s nostart ${rc_rcorder_flags} ${scripts}) 154 print_rc_metadata "cmd-status:rcorder:$?" 155 156 if [ -n "${RC_FILES_OVERRIDE}" ]; then 157 files="${RC_FILES_OVERRIDE}" 158 fi 159 160 # 161 # Run the scripts in order. 162 # 163 for _rc_elem in $files; do 164 print_rc_metadata "cmd-name:$_rc_elem" 165 run_rc_script $_rc_elem start 166 print_rc_metadata "cmd-status:$_rc_elem:$?" 167 done 168 169 print_rc_metadata "end:$(date)" 170 exit 0 171) 172 173# 174# rc_postprocess 175# Post-process the output from the rc_real_work() function. For 176# each line of input, we have to decide whether to print the line 177# to the console, print a twiddle on the console, print a line to 178# the log, or some combination of these. 179# 180# If rc_silent is true, then suppress most output, instead running 181# rc_silent_cmd (typically "twiddle") for each line. 182# 183# The body of this function is defined using (...), not {...}, to force 184# it to run in a subshell. 185# 186# We have to deal with the following constraints: 187# 188# * There may be no writable file systems early in the boot, so 189# any use of temporary files would be problematic. 190# 191# * Scripts run during the boot may clear /tmp and/var/run, so even 192# if they are writable, using those directories too early may be 193# problematic. We assume that it's safe to write to our log file 194# after the CRITLOCALMOUNTED script has run. 195# 196# * /usr/bin/tee cannot be used because the /usr file system may not 197# be mounted early in the boot. 198# 199# * All calls to the rc_log_message and rc_log_flush functions must be 200# from the same subshell, otherwise the use of a shell variable to 201# buffer log messages will fail. 202# 203rc_postprocess() 204( 205 local line 206 local before after 207 local IFS='' 208 209 # Try quite hard to flush the log to disk when we exit. 210 trap 'es=$?; rc_log_flush FORCE; trap "" 0; exit $es' 0 211 212 yesno_to_truefalse rc_silent 2>/dev/null 213 214 while read -r line ; do 215 case "$line" in 216 "${rc_metadata_prefix}"*) 217 after="${line#*"${rc_metadata_prefix}"}" 218 rc_postprocess_metadata "${after}" 219 ;; 220 *"${rc_metadata_prefix}"*) 221 # magic string is present, but not at the start of 222 # the line. Treat it as a partial line of 223 # ordinary data, followed by a line of metadata. 224 before="${line%"${rc_metadata_prefix}"*}" 225 rc_postprocess_partial_line "${before}" 226 after="${line#*"${rc_metadata_prefix}"}" 227 rc_postprocess_metadata "${after}" 228 ;; 229 *) 230 rc_postprocess_plain_line "${line}" 231 ;; 232 esac 233 done 234 235 # If we get here, then the rc_real_work() function must have 236 # exited uncleanly. A clean exit would have been accompanied by 237 # a line of metadata that would have prevented us from getting 238 # here. 239 # 240 exit 1 241) 242 243# 244# rc_postprocess_plain_line string 245# $1 is a string representing a line of output from one of the 246# rc.d scripts. Append the line to the log, and also either 247# display the line on the console, or run $rc_silent_cmd, 248# depending on the value of $rc_silent. 249# 250rc_postprocess_plain_line() 251{ 252 local line="$1" 253 rc_log_message "${line}" 254 if $rc_silent; then 255 eval "$rc_silent_cmd" 256 else 257 printf "%s\n" "${line}" 258 fi 259} 260 261# 262# rc_postprocess_partial_line string 263# This is just like rc_postprocess_plain_line, except that 264# a newline is not appended to the string. 265# 266rc_postprocess_partial_line() 267{ 268 local line="$1" 269 rc_log_message_n "${line}" 270 if $rc_silent; then 271 eval "$rc_silent_cmd" 272 else 273 printf "%s" "${line}" 274 fi 275} 276 277# 278# rc_postprocess_metadata string 279# $1 is a string containing metadata from the rc_real_work() 280# function. The rc_metadata_prefix marker should already 281# have been removed before the string is passed to this function. 282# Take appropriate action depending on the content of the string. 283# 284rc_postprocess_metadata() 285{ 286 local metadata="$1" 287 local keyword args 288 local msg 289 local IFS=':' 290 291 # given metadata="bleep:foo bar:baz", 292 # set keyword="bleep", args="foo bar:baz", 293 # $1="foo bar", $2="baz" 294 # 295 keyword="${metadata%%:*}" 296 args="${metadata#*:}" 297 set -- $args 298 299 case "$keyword" in 300 start) 301 # Marks the start of the entire /etc/rc script. 302 # $args contains a date/time. 303 rc_log_message "[$0 starting at $args]" 304 if ! $rc_silent; then 305 printf "%s\n" "$args" 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 CRITLOCALMOUNTED script has finished, it's 326 # OK to flush the log to disk 327 case "$1" in 328 */CRITLOCALMOUNTED) 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