rc revision 1.164
1#!/bin/sh 2# 3# $NetBSD: rc,v 1.164 2009/09/11 18:17:04 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 27if ! checkyesno rc_configured; then 28 echo "/etc/rc.conf is not configured. Multiuser boot aborted." 29 exit 1 30fi 31 32if [ "$1" = autoboot ]; then 33 autoboot=yes 34 rc_fast=yes # run_rc_command(): do fast booting 35fi 36 37# 38# Completely ignore INT and QUIT at the outer level. The rc_real_work() 39# function should do something different. 40# 41trap '' INT QUIT 42 43# 44# This string will be used to mark lines of meta-data sent over the pipe 45# from the rc_real_work() function to the rc_postprocess() function. Lines 46# not so marked are assumed to be output from rc.d scripts. 47# 48# This string is long and unique to ensure that it does not accidentally 49# appear in output from any rc.d script. It must not contain any 50# characters that are special to glob expansion ('*', '?', '[', or ']'). 51# 52rc_metadata_prefix="$0:$$:metadata:"; 53 54# Child scripts may sometimes want to print directly to the original 55# stdout and stderr, bypassing the pipe to the postprocessor. These 56# _rc_*_fd variables are private, shared with /etc/rc.subr, but not 57# intended to be used directly by child scripts. (Child scripts 58# may use rc.subr's no_rc_postprocess function.) 59# 60_rc_original_stdout_fd=7; export _rc_original_stdout_fd 61_rc_original_stderr_fd=8; export _rc_original_stderr_fd 62eval "exec ${_rc_original_stdout_fd}>&1" 63eval "exec ${_rc_original_stderr_fd}>&2" 64 65# 66# rc_real_work 67# Do the real work. Output from this function will be piped into 68# rc_postprocess(), and some of the output will be marked as 69# metadata. 70# 71# The body of this function is defined using (...), not {...}, to force 72# it to run in a subshell. 73# 74rc_real_work() 75( 76 stty status '^T' 77 78 # print_rc_metadata() wants to be able to print to the pipe 79 # that goes to our postprocessor, even if its in a context 80 # with redirected output. 81 # 82 _rc_postprocessor_fd=9 ; export _rc_postprocessor_fd 83 eval "exec ${_rc_postprocessor_fd}>&1" 84 85 # Print a metadata line when we exit 86 # 87 trap 'es=$?; print_rc_metadata "exit:$es"; trap "" 0; exit $es' 0 88 89 # Set shell to ignore SIGINT, but children will not ignore it. 90 # Shell catches SIGQUIT and returns to single user. 91 # 92 trap : INT 93 trap '_msg="Boot interrupted at $(date)"; 94 print_rc_metadata "interrupted:${_msg}"; 95 exit 1' QUIT 96 97 print_rc_metadata "start:$(date)" 98 99 # 100 # The stop_boot() function in rc.subr may kill $RC_PID. We want 101 # it to kill the subshell running this rc_real_work() function, 102 # rather than killing the parent shell, because we want the 103 # rc_postprocess() function to be able to log the error 104 # without being killed itself. 105 # 106 # "$$" is the pid of the top-level shell, not the pid of the 107 # subshell that's executing this function. The command below 108 # tentatively assumes that the parent of the "/bin/sh -c ..." 109 # process will be the current subshell, and then uses "kill -0 110 # ..." to check the result. If the "/bin/sh -c ..." process 111 # fails, or returns the pid of an ephemeral process that exits 112 # before the "kill" command, then we fall back to using "$$". 113 # 114 RC_PID=$(/bin/sh -c 'ps -p $$ -o ppid=') || RC_PID=$$ 115 kill -0 $RC_PID >/dev/null 2>&1 || RC_PID=$$ 116 117 # 118 # Get a list of all rc.d scripts, and use rcorder to choose 119 # what order to execute them. 120 # 121 # For testing, allow RC_FILES_OVERRIDE from the environment to 122 # override this. 123 # 124 print_rc_metadata "cmd-name:rcorder" 125 scripts=$(for rcd in ${rc_directories:-/etc/rc.d}; do 126 test -d ${rcd} && echo ${rcd}/*; 127 done) 128 files=$(rcorder -s nostart ${rc_rcorder_flags} ${scripts}) 129 print_rc_metadata "cmd-status:rcorder:$?" 130 131 if [ -n "${RC_FILES_OVERRIDE}" ]; then 132 files="${RC_FILES_OVERRIDE}" 133 fi 134 135 # 136 # Run the scripts in order. 137 # 138 for _rc_elem in $files; do 139 print_rc_metadata "cmd-name:$_rc_elem" 140 run_rc_script $_rc_elem start 141 print_rc_metadata "cmd-status:$_rc_elem:$?" 142 done 143 144 print_rc_metadata "end:$(date)" 145 exit 0 146) 147 148# 149# rc_postprocess 150# Post-process the output from the rc_real_work() function. For 151# each line of input, we have to decide whether to print the line 152# to the console, print a twiddle on the console, print a line to 153# the log, or some combination of these. 154# 155# If rc_silent is true, then suppress most output, instead running 156# rc_silent_cmd (typically "twiddle") for each line. 157# 158# The body of this function is defined using (...), not {...}, to force 159# it to run in a subshell. 160# 161# We have to deal with the following constraints: 162# 163# * There may be no writable file systems early in the boot, so 164# any use of temporary files would be problematic. 165# 166# * Scripts run during the boot may clear /tmp and/var/run, so even 167# if they are writable, using those directories too early may be 168# problematic. We assume that it's safe to write to our log file 169# after the mountcritlocal script has run. 170# 171# * /usr/bin/tee cannot be used because the /usr file system may not 172# be mounted early in the boot. 173# 174# * All calls to the rc_log_message and rc_log_flush functions must be 175# from the same subshell, otherwise the use of a shell variable to 176# buffer log messages will fail. 177# 178rc_postprocess() 179( 180 local line 181 local before after 182 local IFS='' 183 184 # Try quite hard to flush the log to disk when we exit. 185 trap 'es=$?; rc_log_flush FORCE; trap "" 0; exit $es' 0 186 187 yesno_to_truefalse rc_silent 2>/dev/null 188 189 while read -r line ; do 190 case "$line" in 191 "${rc_metadata_prefix}"*) 192 after="${line#*"${rc_metadata_prefix}"}" 193 rc_postprocess_metadata "${after}" 194 ;; 195 *"${rc_metadata_prefix}"*) 196 # magic string is present, but not at the start of 197 # the line. Treat it like two separate lines. 198 before="${line%"${rc_metadata_prefix}"*}" 199 rc_postprocess_plain_line "${before}" 200 after="${line#*"${rc_metadata_prefix}"}" 201 rc_postprocess_metadata "${after}" 202 ;; 203 *) 204 rc_postprocess_plain_line "${line}" 205 ;; 206 esac 207 done 208 209 # If we get here, then the rc_real_work() function must have 210 # exited uncleanly. A clean exit would have been accompanied by 211 # a line of metadata that would have prevented us from getting 212 # here. 213 # 214 exit 1 215) 216 217# 218# rc_postprocess_plain_line string 219# $1 is a string representing a line of output from one of the 220# rc.d scripts. Append the line to the log, and also either 221# display the line on the console, or run $rc_silent_cmd, 222# depending on the value of $rc_silent. 223# 224rc_postprocess_plain_line() 225{ 226 local line="$1" 227 rc_log_message "${line}" 228 if $rc_silent; then 229 eval "$rc_silent_cmd" 230 else 231 printf "%s\n" "${line}" 232 fi 233} 234 235# 236# rc_postprocess_metadata string 237# $1 is a string containing metadata from the rc_real_work() 238# function. The rc_metadata_prefix marker should already 239# have been removed before the string is passed to this function. 240# Take appropriate action depending on the content of the string. 241# 242rc_postprocess_metadata() 243{ 244 local metadata="$1" 245 local keyword args 246 local msg 247 local IFS=':' 248 249 # given metadata="bleep:foo bar:baz", 250 # set keyword="bleep", args="foo bar:baz", 251 # $1="foo bar", $2="baz" 252 # 253 keyword="${metadata%%:*}" 254 args="${metadata#*:}" 255 set -- $args 256 257 case "$keyword" in 258 start) 259 # $args contains a date/time 260 rc_log_message "[$0 starting at $args]" 261 if ! $rc_silent; then 262 printf "%s\n" "$args" 263 fi 264 ;; 265 cmd-name) 266 rc_log_message "[running $1]" 267 ;; 268 cmd-status) 269 # $1 is a command name, $2 is the command's exit status. 270 # If the command failed, report it, and add it to a list. 271 if [ "$2" != 0 ]; then 272 rc_failures="${rc_failures}${rc_failures:+ }$1" 273 msg="$1 reported failure status $2" 274 rc_log_message "$msg" 275 if ! $rc_silent; then 276 printf "%s\n" "$msg" 277 fi 278 fi 279 # After the mountcritlocal script has finished, it's 280 # OK to flush the log to disk 281 case "$1" in 282 */mountcritlocal) 283 rc_log_flush OK 284 ;; 285 esac 286 ;; 287 note) 288 rc_log_message "[NOTE: $args]" 289 ;; 290 end) 291 # 292 # If any scripts (or other commands) failed, report them. 293 # 294 if [ -n "$rc_failures" ]; then 295 rc_log_message "[failures]" 296 msg="The following components reported failures:" 297 msg="${msg}${nl}$( echo " ${rc_failures}" | fmt )" 298 msg="${msg}${nl}See ${RC_LOG_FILE} for more information." 299 rc_log_message "${msg}" 300 printf "%s\n" "${msg}" 301 fi 302 # 303 # Report the end date/time, even in silent mode 304 # 305 rc_log_message "[$0 finished at $args]" 306 printf "%s\n" "$args" 307 ;; 308 exit) 309 rc_log_message "[$0 exiting with status $1]" 310 exit $1 311 ;; 312 interrupted) 313 # $args is a human-readable message 314 rc_log_message "$args" 315 printf "%s\n" "$args" 316 ;; 317 *) 318 # an unrecognised line of metadata 319 rc_log_message "[metadata:${metadata}]" 320 ;; 321 esac 322} 323 324# 325# rc_log_message string [...] 326# write a message to the log file, or buffer it for later. 327# 328rc_log_message() 329{ 330 _rc_log_buffer="${_rc_log_buffer}${*}${nl}" 331 rc_log_flush 332} 333 334# 335# rc_log_flush [OK|FORCE] 336# save outstanding messages from $_rc_log_buffer to $RC_LOG_FILE. 337# 338# The log file is expected to reside in the /var/run directory, which 339# may not be writable very early in the boot sequence, and which is 340# erased a little later in the boot sequence. We therefore avoid 341# writing to the file until we believe it's safe to do so. We also 342# assume that it's reasonable to always append to the file, never 343# truncating it. 344# 345# Optional argument $1 may be "OK" to report that writing to the log 346# file is expected to be safe from now on, or "FORCE" to force writing 347# to the log file even if it may be unsafe. 348# 349# Returns a non-zero status if messages could not be written to the 350# file. 351# 352rc_log_flush() 353{ 354 # 355 # If $_rc_log_flush_ok is false, then it's probably too early to 356 # write to the log file, so don't do it, unless $1 is "FORCE". 357 # 358 : ${_rc_log_flush_ok=false} 359 case "$1:$_rc_log_flush_ok" in 360 OK:*) 361 _rc_log_flush_ok=true 362 ;; 363 FORCE:*) 364 : OK just this once 365 ;; 366 *:true) 367 : OK 368 ;; 369 *) 370 # it's too early in the boot sequence, so don't flush 371 return 1 372 ;; 373 esac 374 375 # 376 # Now append the buffer to the file. The buffer should already 377 # contain a trailing newline, so don't add an extra newline. 378 # 379 if [ -n "$_rc_log_buffer" ]; then 380 if { printf "%s" "${_rc_log_buffer}" >>"${RC_LOG_FILE}" ; } \ 381 2>/dev/null 382 then 383 _rc_log_buffer="" 384 else 385 return 1 386 fi 387 fi 388 return 0 389} 390 391# 392# Most of the action is in the rc_real_work() and rc_postprocess() 393# functions. 394# 395rc_real_work "$@" 2>&1 | rc_postprocess 396exit $? 397