1 #!/bin/sh 2 # 3 # $NetBSD: rc,v 1.176 2026/08/20 14:43:31 thorpej 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 13 export HOME=/ 14 export PATH=/sbin:/bin:/usr/sbin:/usr/bin 15 umask 022 16 17 if [ -e ./rc.subr ] ; then 18 . ./rc.subr # for testing 19 else 20 . /etc/rc.subr 21 fi 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. 28 unset echo ; unalias echo 29 unset printf ; unalias printf 30 31 if ! checkyesno rc_configured; then 32 echo "/etc/rc.conf is not configured. Multiuser boot aborted." 33 exit 1 34 fi 35 36 if [ "$1" = autoboot ]; then 37 autoboot=yes 38 rc_fast=yes # run_rc_command(): do fast booting 39 fi 40 41 # 42 # Completely ignore INT and QUIT at the outer level. The rc_real_work() 43 # function should do something different. 44 # 45 trap '' 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 # 56 rc_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 66 eval "exec ${_rc_original_stdout_fd}>&1" 67 eval "exec ${_rc_original_stderr_fd}>&2" 68 fdflags -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 # 79 rc_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 the order in which to 144 # execute them. 145 # 146 if ! _rc_order_cache_is_valid; then 147 _rc_update_rcorder_cache=YES 148 _rc_order_build_list 149 else 150 _rc_update_rcorder_cache=NO 151 fi 152 153 # 154 # $_rc_ordered_script_list now contains the rc.d scripts 155 # to process. 156 # 157 files="$_rc_ordered_script_list" 158 159 # 160 # For testing, allow RC_FILES_OVERRIDE from the environment to 161 # override this. 162 # 163 if [ -n "${RC_FILES_OVERRIDE}" ]; then 164 _rc_update_rcorder_cache=NO 165 files="${RC_FILES_OVERRIDE}" 166 fi 167 168 # 169 # Run the scripts in order. 170 # 171 for _rc_elem in $files; do 172 print_rc_metadata "cmd-name:$_rc_elem" 173 run_rc_script "$_rc_elem" start 174 print_rc_metadata "cmd-status:$_rc_elem:$?" 175 done 176 177 if [ "$_rc_update_rcorder_cache" = YES ]; then 178 _rc_order_save_cache 179 fi 180 181 print_rc_metadata "end:$(date)" 182 exit 0 183 ) 184 185 # 186 # rc_postprocess 187 # Post-process the output from the rc_real_work() function. For 188 # each line of input, we have to decide whether to print the line 189 # to the console, print a twiddle on the console, print a line to 190 # the log, or some combination of these. 191 # 192 # If rc_silent is true, then suppress most output, instead running 193 # rc_silent_cmd (typically "twiddle") for each line. 194 # 195 # The body of this function is defined using (...), not {...}, to force 196 # it to run in a subshell. 197 # 198 # We have to deal with the following constraints: 199 # 200 # * There may be no writable file systems early in the boot, so 201 # any use of temporary files would be problematic. 202 # 203 # * Scripts run during the boot may clear /tmp and/var/run, so even 204 # if they are writable, using those directories too early may be 205 # problematic. We assume that it's safe to write to our log file 206 # after the CRITLOCALMOUNTED script has run. 207 # 208 # * /usr/bin/tee cannot be used because the /usr file system may not 209 # be mounted early in the boot. 210 # 211 # * All calls to the rc_log_message and rc_log_flush functions must be 212 # from the same subshell, otherwise the use of a shell variable to 213 # buffer log messages will fail. 214 # 215 rc_postprocess() 216 ( 217 local line 218 local before after 219 local IFS='' 220 221 # Try quite hard to flush the log to disk when we exit. 222 trap 'es=$?; rc_log_flush FORCE; trap "" 0; exit $es' 0 223 224 yesno_to_truefalse rc_silent 2>/dev/null 225 226 while read -r line ; do 227 case "$line" in 228 "${rc_metadata_prefix}"*) 229 after="${line#*"${rc_metadata_prefix}"}" 230 rc_postprocess_metadata "${after}" 231 ;; 232 *"${rc_metadata_prefix}"*) 233 # magic string is present, but not at the start of 234 # the line. Treat it as a partial line of 235 # ordinary data, followed by a line of metadata. 236 before="${line%"${rc_metadata_prefix}"*}" 237 rc_postprocess_partial_line "${before}" 238 after="${line#*"${rc_metadata_prefix}"}" 239 rc_postprocess_metadata "${after}" 240 ;; 241 *) 242 rc_postprocess_plain_line "${line}" 243 ;; 244 esac 245 done 246 247 # If we get here, then the rc_real_work() function must have 248 # exited uncleanly. A clean exit would have been accompanied by 249 # a line of metadata that would have prevented us from getting 250 # here. 251 # 252 exit 1 253 ) 254 255 # 256 # rc_postprocess_plain_line string 257 # $1 is a string representing a line of output from one of the 258 # rc.d scripts. Append the line to the log, and also either 259 # display the line on the console, or run $rc_silent_cmd, 260 # depending on the value of $rc_silent. 261 # 262 rc_postprocess_plain_line() 263 { 264 local line="$1" 265 rc_log_message "${line}" 266 if $rc_silent; then 267 eval "$rc_silent_cmd" 268 else 269 printf "%s\n" "${line}" 270 fi 271 } 272 273 # 274 # rc_postprocess_partial_line string 275 # This is just like rc_postprocess_plain_line, except that 276 # a newline is not appended to the string. 277 # 278 rc_postprocess_partial_line() 279 { 280 local line="$1" 281 rc_log_message_n "${line}" 282 if $rc_silent; then 283 eval "$rc_silent_cmd" 284 else 285 printf "%s" "${line}" 286 fi 287 } 288 289 # 290 # rc_postprocess_metadata string 291 # $1 is a string containing metadata from the rc_real_work() 292 # function. The rc_metadata_prefix marker should already 293 # have been removed before the string is passed to this function. 294 # Take appropriate action depending on the content of the string. 295 # 296 rc_postprocess_metadata() 297 { 298 local metadata="$1" 299 local keyword args 300 local msg 301 local IFS=':' 302 303 # given metadata="bleep:foo bar:baz", 304 # set keyword="bleep", args="foo bar:baz", 305 # $1="foo bar", $2="baz" 306 # 307 keyword="${metadata%%:*}" 308 args="${metadata#*:}" 309 set -- $args 310 311 case "$keyword" in 312 start) 313 # Marks the start of the entire /etc/rc script. 314 # $args contains a date/time. 315 rc_log_message "[$0 starting at $args]" 316 if ! $rc_silent; then 317 printf "%s\n" "$args" 318 fi 319 ;; 320 cmd-name) 321 # Marks the start of a child script (usually one of 322 # the /etc/rc.d/* scripts). 323 rc_log_message "[running $1]" 324 ;; 325 cmd-status) 326 # Marks the end of a child script. 327 # $1 is a command name, $2 is the command's exit status. 328 # If the command failed, report it, and add it to a list. 329 if [ "$2" != 0 ]; then 330 rc_failures="${rc_failures}${rc_failures:+ }$1" 331 msg="$1 $(human_exit_code $2)" 332 rc_log_message "$msg" 333 if ! $rc_silent; then 334 printf "%s\n" "$msg" 335 fi 336 fi 337 # After the CRITLOCALMOUNTED script has finished, it's 338 # OK to flush the log to disk 339 case "$1" in 340 */CRITLOCALMOUNTED) 341 rc_log_flush OK 342 ;; 343 esac 344 ;; 345 nop) 346 # Do nothing. 347 # This has the side effect of flushing partial lines, 348 # and the echo() and printf() functions in rc.subr take 349 # advantage of this. 350 ;; 351 note) 352 # Unlike most metadata messages, which should be used 353 # only by /etc/rc and rc.subr, the "note" message may be 354 # used directly by /etc.rc.d/* and similar scripts. 355 # It adds a note to the log file, without displaying 356 # it to stdout. 357 rc_log_message "[NOTE: $args]" 358 ;; 359 end) 360 # Marks the end of processing, after the last child script. 361 # If any child scripts (or other commands) failed, report them. 362 # 363 if [ -n "$rc_failures" ]; then 364 rc_log_message "[failures]" 365 msg="The following components reported failures:" 366 msg="${msg}${nl}$( echo " ${rc_failures}" | fmt )" 367 msg="${msg}${nl}See ${RC_LOG_FILE} for more information." 368 rc_log_message "${msg}" 369 printf "%s\n" "${msg}" 370 fi 371 # 372 # Report the end date/time, even in silent mode 373 # 374 rc_log_message "[$0 finished at $args]" 375 printf "%s\n" "$args" 376 ;; 377 exit) 378 # Marks an exit from the rc_real_work() function. 379 # This may be a normal or abnormal exit. 380 # 381 rc_log_message "[$0 exiting with status $1]" 382 exit $1 383 ;; 384 interrupted) 385 # Marks an interrupt trapped by the rc_real_work() function. 386 # $args is a human-readable message. 387 rc_log_message "$args" 388 printf "%s\n" "$args" 389 ;; 390 *) 391 # an unrecognised line of metadata 392 rc_log_message "[metadata:${metadata}]" 393 ;; 394 esac 395 } 396 397 # 398 # rc_log_message string [...] 399 # Write a message to the log file, or buffer it for later. 400 # This function appends a newline to the message. 401 # 402 rc_log_message() 403 { 404 _rc_log_buffer="${_rc_log_buffer}${*}${nl}" 405 rc_log_flush 406 } 407 408 # 409 # rc_log_message_n string [...] 410 # Just like rc_log_message, except without appending a newline. 411 # 412 rc_log_message_n() 413 { 414 _rc_log_buffer="${_rc_log_buffer}${*}" 415 rc_log_flush 416 } 417 418 # 419 # rc_log_flush [OK|FORCE] 420 # save outstanding messages from $_rc_log_buffer to $RC_LOG_FILE. 421 # 422 # The log file is expected to reside in the /var/run directory, which 423 # may not be writable very early in the boot sequence, and which is 424 # erased a little later in the boot sequence. We therefore avoid 425 # writing to the file until we believe it's safe to do so. We also 426 # assume that it's reasonable to always append to the file, never 427 # truncating it. 428 # 429 # Optional argument $1 may be "OK" to report that writing to the log 430 # file is expected to be safe from now on, or "FORCE" to force writing 431 # to the log file even if it may be unsafe. 432 # 433 # Returns a non-zero status if messages could not be written to the 434 # file. 435 # 436 rc_log_flush() 437 { 438 # 439 # If $_rc_log_flush_ok is false, then it's probably too early to 440 # write to the log file, so don't do it, unless $1 is "FORCE". 441 # 442 : ${_rc_log_flush_ok=false} 443 case "$1:$_rc_log_flush_ok" in 444 OK:*) 445 _rc_log_flush_ok=true 446 ;; 447 FORCE:*) 448 : OK just this once 449 ;; 450 *:true) 451 : OK 452 ;; 453 *) 454 # it's too early in the boot sequence, so don't flush 455 return 1 456 ;; 457 esac 458 459 # 460 # Now append the buffer to the file. The buffer should already 461 # contain a trailing newline, so don't add an extra newline. 462 # 463 if [ -n "$_rc_log_buffer" ]; then 464 if { printf "%s" "${_rc_log_buffer}" >>"${RC_LOG_FILE}" ; } \ 465 2>/dev/null 466 then 467 _rc_log_buffer="" 468 else 469 return 1 470 fi 471 fi 472 return 0 473 } 474 475 # 476 # Most of the action is in the rc_real_work() and rc_postprocess() 477 # functions. 478 # 479 rc_real_work "$@" 2>&1 | rc_postprocess 480 exit $? 481