Skip to content

Instantly share code, notes, and snippets.

@Hanaasagi
Created December 8, 2021 14:30
Show Gist options
  • Save Hanaasagi/2665e1e28ffcf91d5d62f72fa52fb732 to your computer and use it in GitHub Desktop.
Save Hanaasagi/2665e1e28ffcf91d5d62f72fa52fb732 to your computer and use it in GitHub Desktop.
initramfs-linux.img
#!/usr/bin/ash
udevd_running=0
mount_handler=default_mount_handler
init=/sbin/init
rd_logmask=0
. /init_functions
mount_setup
# parse the kernel command line
parse_cmdline </proc/cmdline
# setup logging as early as possible
rdlogger_start
for d in ${disablehooks//,/ }; do
[ -e "/hooks/$d" ] && chmod 644 "/hooks/$d"
done
. /config
run_hookfunctions 'run_earlyhook' 'early hook' $EARLYHOOKS
if [ -n "$earlymodules$MODULES" ]; then
modprobe -qab ${earlymodules//,/ } $MODULES
fi
run_hookfunctions 'run_hook' 'hook' $HOOKS
# honor the old behavior of break=y as a synonym for break=premount
if [ "${break}" = "y" ] || [ "${break}" = "premount" ]; then
echo ":: Pre-mount break requested, type 'exit' to resume operation"
launch_interactive_shell
fi
rootdev=$(resolve_device "$root") && root=$rootdev
unset rootdev
fsck_root
# Mount root at /new_root
"$mount_handler" /new_root
run_hookfunctions 'run_latehook' 'late hook' $LATEHOOKS
run_hookfunctions 'run_cleanuphook' 'cleanup hook' $CLEANUPHOOKS
if [ "$(stat -c %D /)" = "$(stat -c %D /new_root)" ]; then
# Nothing got mounted on /new_root. This is the end, we don't know what to do anymore
# We fall back into a shell, but the shell has now PID 1
# This way, manual recovery is still possible.
err "Failed to mount the real root device."
echo "Bailing out, you are on your own. Good luck."
echo
launch_interactive_shell --exec
elif [ ! -x "/new_root${init}" ]; then
# Successfully mounted /new_root, but ${init} is missing
# The same logic as above applies
err "Root device mounted successfully, but ${init} does not exist."
echo "Bailing out, you are on your own. Good luck."
echo
launch_interactive_shell --exec
fi
if [ "${break}" = "postmount" ]; then
echo ":: Post-mount break requested, type 'exit' to resume operation"
launch_interactive_shell
fi
# this should always be the last thing we do before the switch_root.
rdlogger_stop
exec env -i \
"TERM=$TERM" \
/usr/bin/switch_root /new_root $init "$@"
# vim: set ft=sh ts=4 sw=4 et:
# 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 1>/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 </run/initramfs/rdlogger.pipe >/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 </run/initramfs/rdlogger.pid 2>/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:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment