1#!/bin/sh 2# 3# $NetBSD: rc,v 1.166 2011/08/11 22:52:47 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 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 # Get a list of all rc.d scripts, and use rcorder to choose 123 # what order to execute them. 124 # 125 # For testing, allow RC_FILES_OVERRIDE from the environment to 126 # override this. 127 # 128 print_rc_metadata "cmd-name:rcorder" 129 scripts=$(for rcd in ${rc_directories:-/etc/rc.d}; do 130 test -d ${rcd} && echo ${rcd}/*; 131 done) 132 files=$(rcorder -s nostart ${rc_rcorder_flags} ${scripts}) 133 print_rc_metadata "cmd-status:rcorder:$?" 134 135 if [ -n "${RC_FILES_OVERRIDE}" ]; then 136 files="${RC_FILES_OVERRIDE}" 137 fi 138 139 # 140 # Run the scripts in order. 141 # 142 for _rc_elem in $files; do 143 print_rc_metadata "cmd-name:$_rc_elem" 144 run_rc_script $_rc_elem start 145 print_rc_metadata "cmd-status:$_rc_elem:$?" 146 done 147 148 print_rc_metadata "end:$(date)" 149 exit 0 150) 151 152# 153# rc_postprocess 154# Post-process the output from the rc_real_work() function. For 155# each line of input, we have to decide whether to print the line 156# to the console, print a twiddle on the console, print a line to 157# the log, or some combination of these. 158# 159# If rc_silent is true, then suppress most output, instead running 160# rc_silent_cmd (typically "twiddle") for each line. 161# 162# The body of this function is defined using (...), not {...}, to force 163# it to run in a subshell. 164# 165# We have to deal with the following constraints: 166# 167# * There may be no writable file systems early in the boot, so 168# any use of temporary files would be problematic. 169# 170# * Scripts run during the boot may clear /tmp and/var/run, so even 171# if they are writable, using those directories too early may be 172# problematic. We assume that it's safe to write to our log file 173# after the mountcritlocal script has run. 174# 175# * /usr/bin/tee cannot be used because the /usr file system may not 176# be mounted early in the boot. 177# 178# * All calls to the rc_log_message and rc_log_flush functions must be 179# from the same subshell, otherwise the use of a shell variable to 180# buffer log messages will fail. 181# 182rc_postprocess() 183( 184 local line 185 local before after 186 local IFS='' 187 188 # Try quite hard to flush the log to disk when we exit. 189 trap 'es=$?; rc_log_flush FORCE; trap "" 0; exit $es' 0 190 191 yesno_to_truefalse rc_silent 2>/dev/null 192 193 while read -r line ; do 194 case "$line" in 195 "${rc_metadata_prefix}"*) 196 after="${line#*"${rc_metadata_prefix}"}" 197 rc_postprocess_metadata "${after}" 198 ;; 199 *"${rc_metadata_prefix}"*) 200 # magic string is present, but not at the start of 201 # the line. Treat it as a partial line of 202 # ordinary data, followed by a line of metadata. 203 before="${line%"${rc_metadata_prefix}"*}" 204 rc_postprocess_partial_line "${before}" 205 after="${line#*"${rc_metadata_prefix}"}" 206 rc_postprocess_metadata "${after}" 207 ;; 208 *) 209 rc_postprocess_plain_line "${line}" 210 ;; 211 esac 212 done 213 214 # If we get here, then the rc_real_work() function must have 215 # exited uncleanly. A clean exit would have been accompanied by 216 # a line of metadata that would have prevented us from getting 217 # here. 218 # 219 exit 1 220) 221 222# 223# rc_postprocess_plain_line string 224# $1 is a string representing a line of output from one of the 225# rc.d scripts. Append the line to the log, and also either 226# display the line on the console, or run $rc_silent_cmd, 227# depending on the value of $rc_silent. 228# 229rc_postprocess_plain_line() 230{ 231 local line="$1" 232 rc_log_message "${line}" 233 if $rc_silent; then 234 eval "$rc_silent_cmd" 235 else 236 printf "%s\n" "${line}" 237 fi 238} 239 240# 241# rc_postprocess_partial_line string 242# This is just like rc_postprocess_plain_line, except that 243# a newline is not appended to the string. 244# 245rc_postprocess_partial_line() 246{ 247 local line="$1" 248 rc_log_message_n "${line}" 249 if $rc_silent; then 250 eval "$rc_silent_cmd" 251 else 252 printf "%s" "${line}" 253 fi 254} 255 256# 257# rc_postprocess_metadata string 258# $1 is a string containing metadata from the rc_real_work() 259# function. The rc_metadata_prefix marker should already 260# have been removed before the string is passed to this function. 261# Take appropriate action depending on the content of the string. 262# 263rc_postprocess_metadata() 264{ 265 local metadata="$1" 266 local keyword args 267 local msg 268 local IFS=':' 269 270 # given metadata="bleep:foo bar:baz", 271 # set keyword="bleep", args="foo bar:baz", 272 # $1="foo bar", $2="baz" 273 # 274 keyword="${metadata%%:*}" 275 args="${metadata#*:}" 276 set -- $args 277 278 case "$keyword" in 279 start) 280 # $args contains a date/time 281 rc_log_message "[$0 starting at $args]" 282 if ! $rc_silent; then 283 printf "%s\n" "$args" 284 fi 285 ;; 286 cmd-name) 287 rc_log_message "[running $1]" 288 ;; 289 cmd-status) 290 # $1 is a command name, $2 is the command's exit status. 291 # If the command failed, report it, and add it to a list. 292 if [ "$2" != 0 ]; then 293 rc_failures="${rc_failures}${rc_failures:+ }$1" 294 msg="$1 $(human_exit_code $2)" 295 rc_log_message "$msg" 296 if ! $rc_silent; then 297 printf "%s\n" "$msg" 298 fi 299 fi 300 # After the mountcritlocal script has finished, it's 301 # OK to flush the log to disk 302 case "$1" in 303 */mountcritlocal) 304 rc_log_flush OK 305 ;; 306 esac 307 ;; 308 nop) 309 # Do nothing. 310 ;; 311 note) 312 rc_log_message "[NOTE: $args]" 313 ;; 314 end) 315 # 316 # If any scripts (or other commands) failed, report them. 317 # 318 if [ -n "$rc_failures" ]; then 319 rc_log_message "[failures]" 320 msg="The following components reported failures:" 321 msg="${msg}${nl}$( echo " ${rc_failures}" | fmt )" 322 msg="${msg}${nl}See ${RC_LOG_FILE} for more information." 323 rc_log_message "${msg}" 324 printf "%s\n" "${msg}" 325 fi 326 # 327 # Report the end date/time, even in silent mode 328 # 329 rc_log_message "[$0 finished at $args]" 330 printf "%s\n" "$args" 331 ;; 332 exit) 333 rc_log_message "[$0 exiting with status $1]" 334 exit $1 335 ;; 336 interrupted) 337 # $args is a human-readable message 338 rc_log_message "$args" 339 printf "%s\n" "$args" 340 ;; 341 *) 342 # an unrecognised line of metadata 343 rc_log_message "[metadata:${metadata}]" 344 ;; 345 esac 346} 347 348# 349# rc_log_message string [...] 350# Write a message to the log file, or buffer it for later. 351# This function appends a newline to the message. 352# 353rc_log_message() 354{ 355 _rc_log_buffer="${_rc_log_buffer}${*}${nl}" 356 rc_log_flush 357} 358 359# 360# rc_log_message_n string [...] 361# Just like rc_log_message, except without appending a newline. 362# 363rc_log_message_n() 364{ 365 _rc_log_buffer="${_rc_log_buffer}${*}" 366 rc_log_flush 367} 368 369# 370# rc_log_flush [OK|FORCE] 371# save outstanding messages from $_rc_log_buffer to $RC_LOG_FILE. 372# 373# The log file is expected to reside in the /var/run directory, which 374# may not be writable very early in the boot sequence, and which is 375# erased a little later in the boot sequence. We therefore avoid 376# writing to the file until we believe it's safe to do so. We also 377# assume that it's reasonable to always append to the file, never 378# truncating it. 379# 380# Optional argument $1 may be "OK" to report that writing to the log 381# file is expected to be safe from now on, or "FORCE" to force writing 382# to the log file even if it may be unsafe. 383# 384# Returns a non-zero status if messages could not be written to the 385# file. 386# 387rc_log_flush() 388{ 389 # 390 # If $_rc_log_flush_ok is false, then it's probably too early to 391 # write to the log file, so don't do it, unless $1 is "FORCE". 392 # 393 : ${_rc_log_flush_ok=false} 394 case "$1:$_rc_log_flush_ok" in 395 OK:*) 396 _rc_log_flush_ok=true 397 ;; 398 FORCE:*) 399 : OK just this once 400 ;; 401 *:true) 402 : OK 403 ;; 404 *) 405 # it's too early in the boot sequence, so don't flush 406 return 1 407 ;; 408 esac 409 410 # 411 # Now append the buffer to the file. The buffer should already 412 # contain a trailing newline, so don't add an extra newline. 413 # 414 if [ -n "$_rc_log_buffer" ]; then 415 if { printf "%s" "${_rc_log_buffer}" >>"${RC_LOG_FILE}" ; } \ 416 2>/dev/null 417 then 418 _rc_log_buffer="" 419 else 420 return 1 421 fi 422 fi 423 return 0 424} 425 426# 427# Most of the action is in the rc_real_work() and rc_postprocess() 428# functions. 429# 430rc_real_work "$@" 2>&1 | rc_postprocess 431exit $? 432