# This file contains common functions used in init and in hooks # logging targets _rdlog_file=$(( 1 << 0 )) _rdlog_kmsg=$(( 1 << 1 )) _rdlog_cons=$(( 1 << 2 )) _rdlog_all=$(( (1 << 3) - 1 )) msg () { [ "${quiet}" != "y" ] && echo $@ } err () { echo "ERROR: $@" } log_kmsg() { local fmt=$1; shift printf "<31>initramfs: $fmt\n" "$@" } poll_device() { local device=$1 seconds=${2//[!0-9]} [ "${seconds:-x}" = x ] && seconds=10 deciseconds=$(( seconds * 10 )) # tenths of a second sleepinterval=1 [ -b "$device" ] && return 0 if [ "$udevd_running" -eq 1 ]; then msg "Waiting $seconds seconds for device $device ..." >&2 while [ ! -b "$device" -a "$deciseconds" -gt 0 ]; do if [ "$sleepinterval" -ge 10 ]; then sleep 1 deciseconds=$(( deciseconds - 10 )) else sleep .$sleepinterval deciseconds=$(( deciseconds - sleepinterval )) sleepinterval=$(( sleepinterval * 2 )) fi done fi [ -b "$device" ] } launch_interactive_shell() { export PS1='[rootfs \W]\$ ' # explicitly redirect to /dev/console in case we're logging. note that # anything done in the rescue shell will NOT be logged. { [ "$1" = "--exec" ] && exec sh -i sh -i } 0/dev/console 2>/dev/console } bitfield_has_bit() { [ $(( $1 & $2 )) -eq $2 ] } major_minor_to_device() { local dev [ -e "/sys/dev/block/$1:$2" ] || return 1 if dev=$(readlink -f "/sys/dev/block/$1:$2" 2>/dev/null); then echo "/dev/${dev##*/}" return 0 fi return 1 } run_hookfunctions() { local hook fn=$1 desc=$2 shift 2 for hook in "$@"; do [ -x "/hooks/$hook" ] || continue unset "$fn" . "/hooks/$hook" type "$fn" >/dev/null || continue msg ":: running $desc [$hook]" "$fn" done } set_log_option() { local opt for opt in ${1//|/ }; do case $opt in all) rd_logmask=$_rdlog_all ;; kmsg) rd_logmask=$(( rd_logmask | _rdlog_kmsg )) ;; file) rd_logmask=$(( rd_logmask | _rdlog_file )) ;; console) rd_logmask=$(( rd_logmask | _rdlog_cons )) ;; *) err "unknown rd.log parameter: '$opt'" ;; esac done } parse_cmdline() { local _w _quoted _lhs _rhs _cmdline read -r _cmdline for _w in $_cmdline; do if [ -z "$_quoted" ]; then case $_w in # ignore everything after a # in the commandline \#*) break ;; # special cases rw|ro) rwopt=$_w ;; fsck.mode=*) case ${_w#*=} in force) forcefsck=y ;; skip) fastboot=y ;; *) err "unknown fsck.mode parameter: '${_w#*=}'" ;; esac ;; rd.*) case ${_w#rd.} in debug) rd_debug=y ;; log) rd_logmask=$(( _rdlog_kmsg | _rdlog_cons )) ;; log=*) set_log_option "${_w#rd.log=}" ;; esac ;; # abide by shell variable naming rules [[:alpha:]_]*=*) _rhs=${_w#*=} _lhs=${_w%%=*} _lhs=${_lhs//[-.]/_} if [ '"' = "${_rhs:0:1}" ]; then if [ '"' = "${_rhs:$((${#_rhs}-1))}" ]; then _rhs="${_rhs:1:$((${#_rhs}-2))}" else _rhs=${_rhs:1} _quoted=1 continue fi fi eval $_lhs=\$_rhs ;; [[:alpha:]_]*) _lhs=${_w//[-.]/_} eval $_lhs=y ;; esac else if [ '"' = "${_w:$((${#_w}-1))}" ]; then _rhs="$_rhs ${_w%\"}" unset _quoted eval $_lhs=\$_rhs else _rhs="$_rhs $_w" fi fi done } fsck_device() { [ -x /sbin/fsck ] || return 255 if [ ! -b "$1" ]; then err "device '$1' not found. Skipping fsck." return 255 fi if [ -n "$fastboot" ]; then msg ":: skipping fsck on '$1'" return fi msg ":: performing fsck on '$1'" fsck -Ta -C"$FSCK_FD" "$1" -- ${forcefsck+-f} } fsck_root() { fsck_device "$root" fsckret=$? if [ -n "$fsckret" ] && [ "$fsckret" -ne 255 ]; then # handle error conditions; do nothing on success. if bitfield_has_bit "$fsckret" 4; then err "Bailing out. Run 'fsck $root' manually" printf '%s\n' \ "********** FILESYSTEM CHECK FAILED **********" \ "* *" \ "* Please run fsck manually. After leaving *" \ "* this maintenance shell, the system will *" \ "* reboot automatically. *" \ "* *" \ "*********************************************" launch_interactive_shell echo ":: Automatic reboot in progress" sleep 2 reboot -f elif bitfield_has_bit "$fsckret" 2; then printf '%s\n' \ "************** REBOOT REQUIRED **************" \ "* *" \ "* automatically restarting in 10 seconds *" \ "* *" \ "*********************************************" sleep 10 reboot -f elif bitfield_has_bit "$fsckret" 8; then err "fsck failed on '$root'" elif bitfield_has_bit "$fsckret" 16; then err "Failed to invoke fsck: usage or syntax error" elif bitfield_has_bit "$fsckret" 32; then echo ":: fsck cancelled on user request" elif bitfield_has_bit "$fsckret" 128; then err "fatal error invoking fsck" fi # ensure that root is going to be mounted rw. Otherwise, systemd # might fsck the device again. Annoy the user so that they fix this. if [ "${rwopt:-ro}" != 'rw' ]; then echo "********************** WARNING **********************" echo "* *" echo "* The root device is not configured to be mounted *" echo "* read-write! It may be fsck'd again later. *" echo "* *" echo "*****************************************************" fi fi } resolve_device() { local major minor dev tag device=$1 # attempt to resolve devices immediately. if this fails # and udev is running, fall back on lazy resolution using # /dev/disk/by-* symlinks. this is flexible enough to support # usage of tags without udev and "slow" devices like root on # USB, which might not immediately show up. case $device in UUID=*|LABEL=*|PARTUUID=*|PARTLABEL=*) dev=$(blkid -lt "$device" -o device) if [ -z "$dev" -a "$udevd_running" -eq 1 ]; then tag=$(awk -v t="${device%%=*}" 'BEGIN { print tolower(t) }') dev=/dev/disk/by-$tag/${device#*=} fi esac [ -n "$dev" ] && device=$dev case $device in # path to kernel named block device /dev/*) if poll_device "$device" "$rootdelay"; then echo "$device" return 0 fi ;; # hex encoded major/minor, such as from LILO [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]|[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) major=$(( 0x0$device >> 8 )) minor=$(( 0x0$device & 0xff )) ;; 0x[0-9a-fA-F][0-9a-fA-F]*) major=$(( device >> 8 )) minor=$(( device & 0xff )) ;; esac if [ -n "$major" -a -n "$minor" ]; then device=$(major_minor_to_device "$major" "$minor" || echo '/dev/root') if [ ! -b "$device" ]; then msg "Creating device node with major $major and minor $minor." >&2 mknod "$device" b "$major" "$minor" fi echo "$device" return 0 fi return 1 } default_mount_handler() { if [ ! -b "$root" ]; then err "Unable to find root device '$root'." echo "You are being dropped to a recovery shell" echo " Type 'exit' to try and continue booting" launch_interactive_shell msg "Trying to continue (this will most likely fail) ..." fi msg ":: mounting '$root' on real root" if ! mount ${fstype:+-t $fstype} -o ${rwopt:-ro}${rootflags:+,$rootflags} "$root" "$1"; then echo "You are now being dropped into an emergency shell." launch_interactive_shell msg "Trying to continue (this will most likely fail) ..." fi } rdlogger_start() { # rd.debug implies rd.log=console if rd.log(=.*)? isn't present if [ "$rd_logmask" -eq 0 ] && [ -n "$rd_debug" ]; then rd_logmask=$_rdlog_cons fi [ "$rd_logmask" -gt 0 ] || return mkfifo /run/initramfs/rdlogger.pipe rdlogger /dev/console 2>&1 & printf %s $! >/run/initramfs/rdlogger.pid exec >/run/initramfs/rdlogger.pipe 2>&1 [ -n "$rd_debug" ] && set -x # messages would be otherwise lost if we don't unset quiet. this does, # however, mean that passing rd.log=console will negate the effects of # 'quiet' for initramfs console output. unset quiet } rdlogger_stop() { local i=0 pid [ -e /run/initramfs/rdlogger.pipe ] || return [ -n "$rd_debug" ] && { set +x; } 2>/dev/null # this nudges rdlogger to exit exec 0<>/dev/console 1<>/dev/console 2<>/dev/console # wait up to 1 second for a graceful shutdown until [ ! -e /run/initramfs/rdlogger.pipe ] || [ $i -eq 10 ]; do sleep 0.1 i=$(( i + 1 )) done if [ $i -eq 10 ]; then # racy! the logger might still go away on its own read -r pid /dev/null if [ -n "$pid" ]; then kill "$pid" 2>/dev/null fi fi } rdlogger() { local line # establish logging FDs. Either redirect to an appropriate target or to # /dev/null. This way, log methods can simply write and the associated FD # will Do The Right Thing™. # rd.log=console if bitfield_has_bit "$rd_logmask" "$_rdlog_cons"; then exec 4>/dev/console else exec 4>/dev/null fi # rd.log=kmsg if [ -c /dev/kmsg ] && bitfield_has_bit "$rd_logmask" "$_rdlog_kmsg"; then exec 5>/dev/kmsg else exec 5>/dev/null fi # rd.log=file if bitfield_has_bit "$rd_logmask" "$_rdlog_file"; then exec 6>/run/initramfs/init.log else exec 6>/dev/null fi while read -r line; do # rd.log=console printf '%s\n' "$line" >&4 # rd.log=kmsg log_kmsg '%s' "$line" >&5 # rd.log=file printf '%s\n' "$line" >&6 done # EOF, shutting down... exec 4>&- 5>&- 6>&- rm -f /run/initramfs/rdlogger.pipe /run/initramfs/rdlogger.pid } # vim: set ft=sh ts=4 sw=4 et: