# 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 } startswith() { local word=$1 prefix=$2 case $word in $prefix*) return 0 ;; esac return 1 } endswith() { local word=$1 suffix=$2 case $word in *$suffix) return 0 ;; esac return 1 } parse_cmdline_item() { local key=$1 value=$2 case $key in rw|ro) rwopt=$key ;; fstype) # The kernel understands 'rootfstype', but mkinitcpio has (without # documentation) supported 'fstype' instead. Ensure we support both # for backwards compat, but make fstype legacy. rootfstype=$value ;; fsck.mode) case $value in force) forcefsck=y ;; skip) fastboot=y ;; *) err "unknown fsck.mode parameter: '$value'" ;; esac ;; rd.debug) rd_debug=y ;; rd.log) if [ -n "$value" ]; then set_log_option "$value" else rd_logmask=$(( _rdlog_kmsg | _rdlog_cons )) fi ;; [![:alpha:]_]*|[[:alpha:]_]*[![:alnum:]_]*) # invalid shell variable, ignore it ;; *) # valid shell variable eval "$key"='${value:-y}' ;; esac } process_cmdline_param() { local item_callback=$1 key=$2 value=$3 # maybe unquote the value if startswith "$value" "[\"']" && endswith "$value" "${value:0:1}"; then value=${value#?} value=${value%?} fi "$item_callback" "$key" "$value" } parse_cmdline() { local item_callback=${1:-parse_cmdline_item} local cmdline word quoted key value set -f read -r cmdline set -- $cmdline set +f for word; do if [ -n "$quoted" ]; then value="$value $word" else case $word in *=*) key=${word%%=*} value=${word#*=} if startswith "$value" "[\"']"; then quoted=${value:0:1} fi ;; '#'*) break ;; *) key=$word ;; esac fi if [ -n "$quoted" ]; then if endswith "$value" "$quoted"; then unset quoted else continue fi fi process_cmdline_param "$item_callback" "$key" "$value" unset key value done if [ -n "$key" ]; then process_cmdline_param "$item_callback" "$key" "$value" fi } 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 } # TODO: this really needs to follow the logic of systemd's encode_devnode_name # function more closely. tag_to_udev_path() { awk -v "tag=$1" -v "value=$2" ' BEGIN { gsub(/\//, "\\x2f", value) printf "/dev/disk/by-%s/%s\n", tolower(tag), value }' } 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 dev=$(tag_to_udev_path "${device%%=*}" "${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() { msg ":: mounting '$root' on real root" if ! mount ${rootfstype:+-t $rootfstype} -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 } mount_setup() { mount -t proc proc /proc -o nosuid,noexec,nodev mount -t sysfs sys /sys -o nosuid,noexec,nodev mount -t devtmpfs dev /dev -o mode=0755,nosuid mount -t tmpfs run /run -o nosuid,nodev,mode=0755 mkdir -m755 /run/initramfs if [ -e /sys/firmware/efi ]; then mount -t efivarfs efivarfs /sys/firmware/efi/efivars -o nosuid,nodev,noexec fi } # vim: set ft=sh ts=4 sw=4 et: