Created
December 8, 2021 14:30
-
-
Save Hanaasagi/2665e1e28ffcf91d5d62f72fa52fb732 to your computer and use it in GitHub Desktop.
initramfs-linux.img
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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