diff options
Diffstat (limited to 'scripts/functions')
-rw-r--r-- | scripts/functions | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/scripts/functions b/scripts/functions new file mode 100644 index 0000000..60f9195 --- /dev/null +++ b/scripts/functions @@ -0,0 +1,688 @@ +# -*- shell-script -*- + +_log_msg() +{ + if [ "${quiet?}" = "y" ]; then return; fi + # shellcheck disable=SC2059 + printf "$@" + return 0 # Prevents error carry over in case of unavailable console +} + +log_success_msg() +{ + _log_msg "Success: %s\\n" "$*" +} + +log_failure_msg() +{ + _log_msg "Failure: %s\\n" "$*" +} + +log_warning_msg() +{ + _log_msg "Warning: %s\\n" "$*" +} + +log_begin_msg() +{ + _log_msg "Begin: %s ... " "$*" +} + +log_end_msg() +{ + _log_msg "done.\\n" +} + +panic() +{ + local console rest IFS + + if command -v chvt >/dev/null 2>&1; then + chvt 1 + fi + + echo "$@" + + # The panic= parameter implies we should disallow console access + if [ -n "${panic?}" ]; then + delay= + case "${panic?}" in + -*[![:digit:].]*) # invalid: wait forever + ;; + -*) # timeout < 0: reboot immediately + delay=0 + ;; + 0 | *[![:digit:].]*) # timeout = 0 or invalid: wait forever + ;; + *) # timeout > 0: seconds before rebooting + delay="${panic}" + ;; + esac + if [ -n "${delay}" ]; then + echo "Rebooting automatically due to panic= boot argument" + sleep "${delay}" + reboot -f + else + echo "Halting automatically due to panic= boot argument" + halt -f + fi + exit # in case reboot fails, force kernel panic + fi + + run_scripts /scripts/panic + + # Try to use setsid, which will enable job control in the shell + # and paging in more + if command -v setsid >/dev/null 2>&1; then + unset IFS + read -r console rest </proc/consoles + if [ "${console}" = "tty0" ]; then + # Need to choose a specific VT + console="tty1" + fi + # We don't have 'setsid -c' so we need to setsid, open + # the tty, and finally exec an interactive shell + REASON="$*" PS1='(initramfs) ' setsid sh -c "exec sh -i <>/dev/${console} 1>&0 2>&1" + else + REASON="$*" PS1='(initramfs) ' sh -i </dev/console >/dev/console 2>&1 + fi +} + +maybe_break() +{ + case ",${break?}," in + *,$1,*) + if [ "$1" = "top" ]; then + # udev is not yet running, so load keyboard drivers + if [ "${quiet}" = "y" ]; then + opts="-q" + else + opts="-v" + fi + /sbin/modprobe ${opts} -a i8042 atkbd ehci-pci ehci-orion \ + ehci-hcd ohci-hcd ohci-pci uhci-hcd usbhid xhci \ + xhci-pci xhci-hcd + sleep 2 + for modalias in /sys/bus/hid/devices/*/modalias; do + if [ -f "${modalias}" ]; then + /sbin/modprobe ${opts} -b "$(cat "${modalias}")" + fi + done + fi + panic "Spawning shell within the initramfs" + ;; + esac +} + +# For boot time only; this is overridden at build time in hook-functions +run_scripts() +{ + initdir=${1} + [ ! -d "${initdir}" ] && return + + shift + . "${initdir}/ORDER" +} + +# Load custom modules first +load_modules() +{ + if [ -e /conf/modules ]; then + while read -r m; do + # Skip empty lines + if [ -z "$m" ]; then + continue + fi + # Skip comments - d?ash removes whitespace prefix + com=$(printf "%.1s" "${m}") + if [ "$com" = "#" ]; then + continue + fi + # shellcheck disable=SC2086 + /sbin/modprobe $m + done < /conf/modules + fi +} + +_uptime() { + local uptime + uptime="$(cat /proc/uptime)" + uptime="${uptime%%[. ]*}" + echo "$uptime" +} + +time_elapsed() { + if [ -z "${starttime-}" ]; then + log_failure_msg "time_elapsed() called before \$starttime initialized" + echo 0 + fi + local delta + delta="$(_uptime)" + delta=$((delta - starttime)) + echo "$delta" +} + +# lilo compatibility +parse_numeric() { + case $1 in + *:*) + # Does it match /[0-9]*:[0-9]*/? + minor=${1#*:} + major=${1%:*} + case $major$minor in + *[!0-9]*) + # No. + return + ;; + esac + ;; + "" | *[!A-Fa-f0-9]*) + # "", "/*", etc. + return + ;; + *) + # [A-Fa-f0-9]* + value=$(( 0x${1} )) + minor=$(( (value & 0xff) | (value >> 12) & 0xfff00 )) + major=$(( (value >> 8) & 0xfff )) + ;; + esac + + # shellcheck disable=SC2034 + ROOT="/dev/block/${major}:${minor}" +} + +# Parameter: device node to check +# Echos fstype to stdout +# Return value: indicates if an fs could be recognized +get_fstype () +{ + local FS FSTYPE + FS="${1}" + + # blkid has a more complete list of file systems, + # but fstype is more robust + FSTYPE="unknown" + eval "$(fstype "${FS}" 2> /dev/null)" + if [ "$FSTYPE" = "unknown" ]; then + FSTYPE=$(blkid -o value -s TYPE "${FS}") || return + fi + echo "${FSTYPE}" + return 0 +} + +_set_netdev_from_ip_param() +{ + # If the ip= parameter is present and specifies a device, use + # that in preference to any device name we already have + local IFS=: + set -f + # shellcheck disable=SC2086 + set -- ${IP-} + set +f + if [ -n "${6-}" ]; then + DEVICE="$6" + return 0 + fi + return 1 +} + +_set_netdev_from_hw_address() +{ + local want_address="$1" + local device + for device in /sys/class/net/*; do + if [ -f "$device/address" ] && + [ "$(cat "$device/address")" = "$want_address" ]; then + DEVICE="${device##*/}" + return 0 + fi + done + return 1 +} + +_usable_netdev_exists() +{ + # Look for a device with IFF_LOOPBACK clear and (IFF_BROADCAST + # or IFF_POINTTOPOINT) set. This is the same test the kernel + # and ipconfig apply to find a device. + local device + local flags + for device in /sys/class/net/*; do + if [ -f "${device}/flags" ]; then + flags="$(cat "${device}/flags")" + if [ "$((flags & 8))" -eq 0 ] && + [ "$((flags & 0x12))" -ne 0 ]; then + return 0 + fi + fi + done + return 1 +} + +_update_ip_param() +{ + # If the ip= parameter is present, and is a colon-separated list, + # but does not specify a device, substitute in the device name + # we have + local IFS=: + set -f + # shellcheck disable=SC2086 + set -- ${IP} + set +f + if [ -z "${6-}" ] && [ $# -ge 2 ] && [ -n "${DEVICE-}" ]; then + IP="$1:$2:$3:$4:$5:${DEVICE}" + shift 6 || shift $# + IP="${IP}:$*" + fi +} + +configure_networking() +{ + local netdev_desc + + # The order of precedence here is: + # 1. Device specified by ip= kernel parameter + # 2. Device matching MAC specified by BOOTIF= kernel parameter + # 3. Build-time DEVICE variable + # In case 2 we only discover the device name while waiting + # for a device. + if _set_netdev_from_ip_param; then + netdev_desc="${DEVICE}" + elif [ -n "${BOOTIF-}" ]; then + # pxelinux sets BOOTIF to a value based on the mac address of the + # network card used to PXE boot + # pxelinux sets BOOTIF to 01-$mac_address + + # strip off the leading "01-", which isn't part of the mac + # address + temp_mac=${BOOTIF#*-} + + # convert to typical mac address format by replacing "-" with ":" + bootif_mac="" + IFS='-' + for x in $temp_mac ; do + if [ -z "$bootif_mac" ]; then + bootif_mac="$x" + else + bootif_mac="$bootif_mac:$x" + fi + done + unset IFS + + _set_netdev_from_hw_address "${bootif_mac}" + netdev_desc="device with address ${bootif_mac}" + elif [ -n "${DEVICE-}" ]; then + netdev_desc="${DEVICE}" + else + netdev_desc="any network device" + fi + + # networking already configured thus bail out + [ -n "${DEVICE-}" ] && [ -e /run/net-"${DEVICE}".conf ] && return 0 + + local netdevwait=180 + log_begin_msg "Waiting up to ${netdevwait} secs for ${netdev_desc} to become available" + while true; do + if [ "$(time_elapsed)" -ge "$netdevwait" ]; then + log_failure_msg "Network device did not appear in time" + break + fi + if [ -n "${DEVICE-}" ]; then + [ -e "/sys/class/net/${DEVICE}" ] && break + elif [ -n "${bootif_mac-}" ]; then + _set_netdev_from_hw_address "${bootif_mac}" && break + else + _usable_netdev_exists && break + fi + sleep 1 + done + log_end_msg + + _update_ip_param + + wait_for_udev 10 + + # support ip options see linux sources + # Documentation/filesystems/nfs/nfsroot.txt + # Documentation/frv/booting.txt + + for ROUNDTTT in 2 3 4 6 9 16 25 36 64 100; do + + # The NIC is to be configured if this file does not exist. + # Ip-Config tries to create this file and when it succeds + # creating the file, ipconfig is not run again. + for x in /run/net-"${DEVICE-}".conf /run/net-*.conf ; do + [ -e "$x" ] && break 2 + done + + case "${IP-}" in + none|off) + # Do nothing + ;; + ""|on|any) + # Bring up device + ipconfig -t ${ROUNDTTT} "${DEVICE}" + ;; + dhcp|bootp|rarp|both) + ipconfig -t ${ROUNDTTT} -c "${IP}" -d "${DEVICE}" + ;; + *) + ipconfig -t ${ROUNDTTT} -d "$IP" + ;; + esac + done + + # source ipconfig output (first try specific bootdevice, then any + # interface...). ipconfig should have quit after first response + local file + for file in "/run/net-${DEVICE-}.conf" /run/net-*.conf; do + if [ -e "$file" ]; then + . "$file" + break + fi + done + + netinfo_to_resolv_conf /etc/resolv.conf "/run/net-${DEVICE-}.conf" /run/net-*.conf + + # shellcheck disable=SC2169 + if test -n "${HOSTNAME-}"; then + # shellcheck disable=SC2169 + persist_hostname "${HOSTNAME}" "${DNSDOMAIN-}" + fi +} + +# Write resolv.conf from /run/net-<device> style files. +# $1=resolv.conf path (print to stdout if "-" is specified) +# remaining parameters: /run/net-<device> style files +netinfo_to_resolv_conf() +{ + local domain="" n="" nameservers="" resolv_conf="" search="" output="$1" CR=" +" + shift + + for net_file in "$@"; do + # Define sourced variables as local to avoid causing side effects. + # shellcheck disable=SC2034 + local DEVICE PROTO IPV4ADDR IPV4BROADCAST IPV4NETMASK IPV4ROUTE0SUBNET \ + IPV4ROUTE0GATEWAY IPV4ROUTE1SUBNET IPV4ROUTE1GATEWAY IPV4GATEWAY \ + IPV4DNS0 IPV4DNS1 HOSTNAME DNSDOMAIN NISDOMAIN ROOTSERVER ROOTPATH \ + filename UPTIME DHCPLEASETIME DOMAINSEARCH + [ -e "$net_file" ] || continue + . "$net_file" || { echo "Error: Failed to source $net_file" >&2; return 1; } + if test -n "${DNSDOMAIN}"; then + domain="${DNSDOMAIN}" + fi + for n in "${IPV4DNS0}" "${IPV4DNS1}"; do + if test -z "$n" -o "$n" = 0.0.0.0; then + continue + fi + # Remove duplicates + case " ${nameservers} " in + *\ $n\ *) + continue ;; + esac + nameservers="${nameservers} ${n}" + done + for n in ${DOMAINSEARCH}; do + [ -n "$n" ] || continue + # Remove duplicates + case " ${search} " in + *\ $n\ *) + continue ;; + esac + search="${search} ${n}" + done + done + + # Construct content for resolv.conf + if test -n "${domain}"; then + resolv_conf="${resolv_conf}domain ${domain}${CR}" + fi + for n in ${nameservers}; do + resolv_conf="${resolv_conf}nameserver $n${CR}" + done + if test -n "${search}"; then + resolv_conf="${resolv_conf}search ${search# }${CR}" + fi + + if test -z "$resolv_conf"; then + echo "no search or nameservers found in $*" 1>&2 + return + fi + if test "$output" = "-"; then + printf "%s" "$resolv_conf" + else + printf "%s" "$resolv_conf" > "$output" + fi +} + +_generate_hosts_content() +{ + local hostname="$1" + local domain="$2" + local fqdn + + if test -n "${domain}"; then + fqdn="${hostname}.${domain} " + else + fqdn="" + fi + cat <<EOF +127.0.0.1 localhost +127.0.1.1 ${fqdn}${hostname} + +# The following lines are desirable for IPv6 capable hosts +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +EOF +} + +# Write hostname into /etc/hostname and /etc/hosts +# $1=hostname +# $2=domain (optional) +persist_hostname() +{ + local hostname="$1" + local domain="${2-}" + + echo "$hostname" > /etc/hostname + echo "$hostname" > /proc/sys/kernel/hostname + _generate_hosts_content "$hostname" "$domain" > /etc/hosts +} + +# Wait for queued kernel/udev events +wait_for_udev() +{ + command -v udevadm >/dev/null 2>&1 || return 0 + udevadm settle ${1:+--timeout=$1} +} + +# Find a specific fstab entry +# $1=mountpoint +# $2=fstype (optional) +# returns 0 on success, 1 on failure (not found or no fstab) +read_fstab_entry() { + # Not found by default. + found=1 + + for file in ${rootmnt?}/etc/fstab; do + if [ -f "$file" ]; then + # shellcheck disable=SC2034 + while read -r MNT_FSNAME MNT_DIR MNT_TYPE MNT_OPTS MNT_FREQ MNT_PASS MNT_JUNK; do + case "$MNT_FSNAME" in + ""|\#*) + continue; + ;; + esac + if [ "$MNT_DIR" = "$1" ]; then + if [ -n "${2-}" ]; then + [ "$MNT_TYPE" = "$2" ] || continue; + fi + found=0 + break 2 + fi + done < "$file" + fi + done + + return $found +} + +# Resolve device node from a name. This expands any LABEL or UUID. +# $1=name +# Resolved name is echoed. +resolve_device() { + DEV="$1" + + case "$DEV" in + LABEL=* | UUID=* | PARTLABEL=* | PARTUUID=*) + DEV="$(blkid -l -t "$DEV" -o device)" || return 1 + ;; + esac + [ -e "$DEV" ] && echo "$DEV" +} + +# Check a file system. +# $1=device +# $2=mountpoint (for diagnostics only) +# $3=type (may be "auto") +_checkfs_once() +{ + DEV="$1" + NAME="$2" + TYPE="$3" + if [ "$NAME" = "/" ] ; then + NAME="root" + fi + FSCK_LOGFILE=/run/initramfs/fsck.log + FSCK_STAMPFILE=/run/initramfs/fsck-${NAME#/} + + if [ "${TYPE}" = "auto" ]; then + TYPE="$(get_fstype "${DEV}")" + fi + + FSCKCODE=0 + if [ -z "${TYPE}" ]; then + log_warning_msg "Type of $NAME file system is unknown, so skipping check." + return + fi + if ! command -v fsck >/dev/null 2>&1; then + log_warning_msg "fsck not present, so skipping $NAME file system" + return + fi + if [ "${fastboot?}" = "y" ] ; then + log_warning_msg "Fast boot enabled, so skipping $NAME file system check." + return + fi + + if [ "${forcefsck?}" = "y" ] + then + force="-f" + else + force="" + fi + + if [ "${fsckfix?}" = "y" ] + then + fix="-y" + elif [ "${fsckfix?}" = "n" ] + then + fix="-n" + else + fix="-a" + fi + + spinner="" + if [ -z "${debug?}" ]; then + spinner="-C" + fi + + if [ "${quiet}" = n ] + then + log_begin_msg "Will now check $NAME file system" + # shellcheck disable=SC2086 + logsave -a -s $FSCK_LOGFILE fsck $spinner $force $fix -V -t "$TYPE" "$DEV" + FSCKCODE=$? + log_end_msg + else + log_begin_msg "Checking $NAME file system" + # shellcheck disable=SC2086 + logsave -a -s $FSCK_LOGFILE fsck $spinner $force $fix -T -t "$TYPE" "$DEV" + FSCKCODE=$? + log_end_msg + fi + + # NOTE: "failure" is defined as exiting with a return code of + # 4, possibly or-ed with other flags. A return code of 1 + # indicates that file system errors were corrected but that + # the boot may proceed. + # + if [ "$FSCKCODE" -eq 32 ] + then + log_warning_msg "File system check was interrupted by user" + elif [ $((FSCKCODE & 4)) -eq 4 ] + then + log_failure_msg "File system check of the $NAME filesystem failed" + return 1 + elif [ "$FSCKCODE" -gt 1 ] + then + log_warning_msg "File system check failed but did not detect errors" + sleep 5 + else + true >"$FSCK_STAMPFILE" + fi + return 0 +} + +checkfs() +{ + while ! _checkfs_once "$@"; do + panic "The $2 filesystem on $1 requires a manual fsck" + done +} + +# Mount a file system. We parse the information from the fstab. This +# should be overridden by any boot script which can mount arbitrary +# filesystems such as /usr. This default implementation delegates to +# local or nfs based upon the filesystem type. +# $1=mountpoint mount location +mountfs() +{ + type=local + read_fstab_entry "$1" + if [ "${MNT_TYPE}" = "nfs" ] || [ "${MNT_TYPE}" = "nfs4" ]; then + type=nfs + fi + + ${type}_mount_fs "$1" +} + +# Mount the root file system. It should be overridden by all +# boot scripts. +mountroot() +{ + : +} + +# Run /scripts/${boot}-top. This should be overridden by all boot +# scripts. +mount_top() +{ + : +} + +# Run /scripts/${boot}-premount. This should be overridden by all boot +# scripts. +mount_premount() +{ + : +} + +# Run /scripts/${boot}-bottom. This should be overridden by all boot +# scripts. +mount_bottom() +{ + : +} |