#!/usr/bin/ash # SPDX-License-Identifier: GPL-2.0-only # 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() { # assigned by parse_cmdline # shellcheck disable=SC2154 [ "${quiet}" != "y" ] && echo "$@" } err() { echo "ERROR: $*" } log_kmsg() { local fmt="$1"; shift # shellcheck disable=SC2059 printf "<31>initramfs: $fmt\n" "$@" } poll_device() { # busybox ash supports string replacements # shellcheck disable=SC3060 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 # assigned in /init # shellcheck disable=SC2154 if [ "$udevd_running" -eq 1 ]; then msg "Waiting $seconds seconds for device $device ..." >&2 while [ ! -b "$device" ] && [ "$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" ] } # shellcheck disable=SC2120 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" # shellcheck disable=SC1090 . "/hooks/$hook" type "$fn" >/dev/null || continue msg ":: running $desc [$hook]" "$fn" done } set_log_option() { local opt # busybox ash supports string replacements # shellcheck disable=SC3060 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 # busybox ash supports string indexing # shellcheck disable=SC3057 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 # shellcheck disable=SC2086 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 # busybox ash supports string indexing # shellcheck disable=SC3057 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() { local device [ -x /sbin/fsck ] || return 255 device="$(resolve_device "$1")" if [ ! -b "$device" ]; then err "device '$1' not found. Skipping fsck." return 255 fi # If the device is specified via a tag supported by util-linux fsck, # pass the unmodified tag to fsck. case "$1" in 'UUID='* | 'LABEL='* | 'PARTUUID='* | 'PARTLABEL='*) device="$1" ;; *) : ;; esac if [ -n "$fastboot" ]; then msg ":: skipping fsck on '$1'" return fi msg ":: performing fsck on '$1'" fsck -Ta -C"$FSCK_FD" "$device" -- ${forcefsck+-f} } fsck_root() { # assigned by parse_cmdline # shellcheck disable=SC2154 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 # shellcheck disable=SC2086 run_hookfunctions 'run_emergencyhook' 'emergency hook' $EMERGENCYHOOKS 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. *" \ "* *" \ "*********************************************" # shellcheck disable=SC2119 launch_interactive_shell echo ":: Automatic reboot in progress" sleep 2 reboot -f elif bitfield_has_bit "$fsckret" 2; then # shellcheck disable=SC2086 run_hookfunctions 'run_emergencyhook' 'emergency hook' $EMERGENCYHOOKS 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 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" ] && [ "$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/*) # rootdelay is assigned by parse_cmdline # shellcheck disable=SC2154 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" ] && [ -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 -t "${rootfstype:-auto}" -o "${rwopt:-ro}${rootflags:+,$rootflags}" "$root" "$1"; then # shellcheck disable=SC2086 run_hookfunctions 'run_emergencyhook' 'emergency hook' $EMERGENCYHOOKS err "Failed to mount '$root' on real root" echo "You are now being dropped into an emergency shell." # shellcheck disable=SC2119 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 # Setup /dev symlinks if [ -e /proc/kcore ]; then ln -sfT /proc/kcore /dev/core fi ln -sfT /proc/self/fd /dev/fd ln -sfT /proc/self/fd/0 /dev/stdin ln -sfT /proc/self/fd/1 /dev/stdout ln -sfT /proc/self/fd/2 /dev/stderr } # vim: set ft=sh ts=4 sw=4 et: