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 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 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 # 203 rc_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 # 250 rc_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 # 266 rc_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 # 284 rc_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 # 390 rc_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 # 400 rc_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 # 424 rc_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 # 467 rc_real_work "$@" 2>&1 | rc_postprocess 468 exit $? 469