444 lines
12 KiB
Bash
Executable File
444 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# tiny initramfs
|
|
#
|
|
|
|
print()
|
|
{
|
|
printf "%b %s\n" "${2:-"\033[1;37m>>\033[m"}" "$1"
|
|
}
|
|
|
|
panic()
|
|
{
|
|
print "${1:-unexpected error occurred}" \
|
|
"\033[1;31m!!\033[m" >&2; exit 1
|
|
}
|
|
|
|
usage()
|
|
{
|
|
cat << EOF
|
|
usage: ${0##*/} [option...]
|
|
-o, --output <file> set initramfs output path
|
|
default is /boot/initramfs-$(uname -r)
|
|
|
|
-c, --config <file> set config file path
|
|
default is /etc/tinyramfs/config
|
|
|
|
-m, --modules <dir> set modules directory
|
|
default is /lib/modules
|
|
|
|
-s, --sources <dir> set sources directory
|
|
default is /usr/share/tinyramfs
|
|
|
|
-k, --kernel <ver> set kernel version
|
|
default is $(uname -r)
|
|
|
|
-H, --hooks <dir> set hooks directory
|
|
default is /etc/tinyramfs/hooks (user hooks)
|
|
and /usr/share/tinyramfs/hooks (system hooks)
|
|
|
|
-d, --debug enable debug mode
|
|
-f, --force overwrite initramfs image
|
|
|
|
EOF
|
|
}
|
|
|
|
prepare_environment()
|
|
{
|
|
while [ "$1" ]; do case "$1" in
|
|
-o | --output)
|
|
output="${2:?}"; shift 2
|
|
;;
|
|
-c | --config)
|
|
config="${2:?}"; shift 2
|
|
;;
|
|
-m | --modules)
|
|
moddir="${2:?}"; shift 2
|
|
;;
|
|
-s | --sources)
|
|
srcdir="${2:?}"; shift 2
|
|
;;
|
|
-k | --kernel)
|
|
kernel="${2:?}"; shift 2
|
|
;;
|
|
-H | --hooks)
|
|
hksdir="${2:?}"; shift 2
|
|
;;
|
|
-d | --debug)
|
|
debug=1; shift 1
|
|
;;
|
|
-f | --force)
|
|
force=1; shift 1
|
|
;;
|
|
-h | --help)
|
|
usage; exit 0
|
|
;;
|
|
*)
|
|
printf "invalid option: %s\n\n" "$1"
|
|
|
|
usage; exit 1
|
|
;;
|
|
esac; done
|
|
|
|
print "preparing environment"
|
|
|
|
. "${config:-/etc/tinyramfs/config}"
|
|
|
|
: "${kernel:=$(uname -r)}"
|
|
: "${moddir:=/lib/modules}"
|
|
: "${srcdir:=/usr/share/tinyramfs}"
|
|
: "${output:=/boot/tinyramfs-${kernel}}"
|
|
|
|
mkdir -p "${tmpdir:=${TMPDIR:-/tmp}/tinyramfs.$$}"
|
|
|
|
# false positive
|
|
# shellcheck disable=2015
|
|
[ "$debug" = 1 ] && set -x || trap "rm -rf $tmpdir" EXIT INT
|
|
}
|
|
|
|
prepare_initramfs()
|
|
{
|
|
print "preparing initramfs"
|
|
|
|
# make directories
|
|
mkdir -p \
|
|
"${tmpdir}/dev" \
|
|
"${tmpdir}/sys" \
|
|
"${tmpdir}/tmp" \
|
|
"${tmpdir}/run" \
|
|
"${tmpdir}/var" \
|
|
"${tmpdir}/proc" \
|
|
"${tmpdir}/root" \
|
|
"${tmpdir}/usr/lib" \
|
|
"${tmpdir}/usr/bin" \
|
|
"${tmpdir}/mnt/root" \
|
|
"${tmpdir}/etc/tinyramfs"
|
|
|
|
# make symlinks
|
|
ln -s usr/lib "${tmpdir}/lib"
|
|
ln -s usr/bin "${tmpdir}/bin"
|
|
ln -s usr/bin "${tmpdir}/sbin"
|
|
ln -s ../run "${tmpdir}/var/run"
|
|
ln -s ../run/lock "${tmpdir}/var/lock"
|
|
ln -s bin "${tmpdir}/usr/sbin"
|
|
|
|
# TODO make blkid optional
|
|
# copy required binaries
|
|
for _binary in \
|
|
\[ sh ln env kill mkdir \
|
|
blkid sleep mount printf \
|
|
switch_root "${srcdir}/device-helper"
|
|
do
|
|
copy_binary "$_binary"
|
|
done
|
|
|
|
# copy init
|
|
cp "${srcdir}/init" "${tmpdir}/init"
|
|
chmod 755 "${tmpdir}/init"
|
|
|
|
# copy config
|
|
cp "$config" "${tmpdir}/etc/tinyramfs/config"
|
|
chmod 600 "${tmpdir}/etc/tinyramfs/config"
|
|
}
|
|
|
|
copy_binary()
|
|
{
|
|
binary=$(command -v "$1")
|
|
|
|
# check if binary exist and builtin
|
|
case "$binary" in */*) ;;
|
|
"")
|
|
panic "$1 does not exist"
|
|
;;
|
|
*)
|
|
# word splitting is safe by design
|
|
# shellcheck disable=2086
|
|
IFS=:; set -- $PATH; unset IFS
|
|
|
|
# assume that `command -v` returned builtin command.
|
|
# this behavior depends on shell implementation.
|
|
# to be independented we simply iterating over PATH
|
|
# to find external alternative ( e.g kill => /bin/kill )
|
|
for _dir; do
|
|
[ -x "${_dir}/${binary}" ] || ! continue
|
|
|
|
binary="${_dir}/${binary}"; break
|
|
done || panic "$1 does not exist"
|
|
;;
|
|
esac
|
|
|
|
# check if binary already exist
|
|
[ -e "${tmpdir}/bin/${binary##*/}" ] && return 0
|
|
|
|
# iterate throught symlinks and copy them
|
|
while [ -h "$binary" ]; do symlink=$(ls -ld "$binary")
|
|
cp -P "$binary" "${tmpdir}/bin" || panic
|
|
binary="${binary%/*}/${symlink##* ->*[ /]}"
|
|
done
|
|
|
|
{
|
|
cp "$binary" "${tmpdir}/bin"
|
|
chmod 755 "${tmpdir}/bin/${binary##*/}"
|
|
} || panic
|
|
|
|
strip "${tmpdir}/bin/${binary##*/}" > /dev/null 2>&1 || :
|
|
|
|
# copy binary dependencies if any
|
|
ldd "$binary" 2> /dev/null |
|
|
|
|
while read -r _library || [ "$_library" ]; do
|
|
|
|
# skip unneeded stuff
|
|
[ "${_library##*vdso*}" ] || continue
|
|
|
|
_library="${_library#* => }"
|
|
_library="${_library% *}"
|
|
|
|
copy_library "$_library"
|
|
done
|
|
}
|
|
|
|
copy_library()
|
|
{
|
|
library="$1"
|
|
|
|
# check if library already exist
|
|
[ -e "${tmpdir}/lib/${library##*/}" ] && return 0
|
|
|
|
# iterate throught symlinks and copy them
|
|
while [ -h "$library" ]; do symlink=$(ls -ld "$library")
|
|
cp -P "$library" "${tmpdir}/lib" || panic
|
|
library="${library%/*}/${symlink##* ->*[ /]}"
|
|
done
|
|
|
|
{
|
|
cp "$library" "${tmpdir}/lib"
|
|
chmod 755 "${tmpdir}/lib/${library##*/}"
|
|
} || panic
|
|
|
|
strip "${tmpdir}/lib/${library##*/}" > /dev/null 2>&1 || :
|
|
}
|
|
|
|
copy_module()
|
|
{
|
|
module="$1"
|
|
|
|
modprobe -S "$kernel" -D "$module" 2> /dev/null |
|
|
|
|
while read -r _ module || [ "$module" ]; do
|
|
|
|
# check if module contains full path(not builtin)
|
|
[ "${module##*/*}" ] && continue
|
|
|
|
# check if module already exist
|
|
[ -e "${tmpdir}${module}" ] && continue
|
|
|
|
{
|
|
mkdir -p "${tmpdir}${module%/*}"
|
|
cp "$module" "${tmpdir}${module}"
|
|
chmod 644 "${tmpdir}${module}"
|
|
} || panic
|
|
done
|
|
}
|
|
|
|
copy_hook()
|
|
{
|
|
hook="$1"
|
|
|
|
for _dir in "$hksdir" /etc/tinyramfs/hooks /usr/share/tinyramfs/hooks; do
|
|
[ -f "${_dir}/${hook}/${hook}" ] || ! continue
|
|
|
|
print "running $hook hook"; . "${_dir}/${hook}/${hook}"
|
|
|
|
if [ -f "${_dir}/${hook}/${hook}.init" ]; then
|
|
mkdir -p "${tmpdir}/usr/share/tinyramfs/hooks/${hook##*/}"
|
|
cp "${_dir}/${hook}/${hook}.init" \
|
|
"${tmpdir}/usr/share/tinyramfs/hooks/${hook##*/}"
|
|
|
|
elif [ -f "${_dir}/${hook}/${hook}.init.early" ]; then
|
|
mkdir -p "${tmpdir}/usr/share/tinyramfs/hooks/${hook##*/}"
|
|
cp "${_dir}/${hook}/${hook}.init.early" \
|
|
"${tmpdir}/usr/share/tinyramfs/hooks/${hook##*/}"
|
|
fi || panic
|
|
|
|
break
|
|
done || panic "could not run $hook"
|
|
}
|
|
|
|
copy_modules()
|
|
{
|
|
# skip this function if kernel
|
|
# compiled with builtin modules
|
|
if [ "$monolith" = 1 ]; then
|
|
print "skipping modules"
|
|
return 0
|
|
|
|
elif [ "$hostonly" = 1 ]; then
|
|
print "copying hostonly modules"
|
|
|
|
# perform autodetection of modules via /sys
|
|
# see https://wiki.archlinux.org/index.php/Modalias
|
|
find /sys/devices -name modalias -exec sort -u {} + |
|
|
|
|
while read -r _module || [ "$_module" ]; do
|
|
|
|
# skip unneeded modules and skip modules which
|
|
# depends on them as well
|
|
case $(modprobe -S "$kernel" -D "$_module") in
|
|
*wmi* | *gpu* | *net*) continue ;;
|
|
esac 2> /dev/null
|
|
|
|
copy_module "$_module"
|
|
done
|
|
|
|
# copy root filesystem module
|
|
if [ "$root_type" ]; then
|
|
copy_module "$root_type"
|
|
else
|
|
while read -r _ _dir _type _; do
|
|
[ "$_dir" = / ] || ! continue
|
|
|
|
copy_module "$_type"; break
|
|
done < /proc/mounts ||
|
|
panic "could not copy root fs module"
|
|
fi
|
|
else
|
|
print "copying all modules"
|
|
|
|
find \
|
|
"${moddir}/${kernel}/kernel/fs" \
|
|
"${moddir}/${kernel}/kernel/lib" \
|
|
"${moddir}/${kernel}/kernel/arch" \
|
|
"${moddir}/${kernel}/kernel/crypto" \
|
|
"${moddir}/${kernel}/kernel/drivers/md" \
|
|
"${moddir}/${kernel}/kernel/drivers/ata" \
|
|
"${moddir}/${kernel}/kernel/drivers/scsi" \
|
|
"${moddir}/${kernel}/kernel/drivers/block" \
|
|
"${moddir}/${kernel}/kernel/drivers/virtio" \
|
|
"${moddir}/${kernel}/kernel/drivers/usb/host" \
|
|
"${moddir}/${kernel}/kernel/drivers/usb/storage" \
|
|
-type f 2> /dev/null |
|
|
|
|
while read -r _module || [ "$_module" ]; do
|
|
|
|
# strip path and extension
|
|
_module="${_module##*/}"
|
|
_module="${_module%%.*}"
|
|
|
|
# skip unneeded modules and skip modules which
|
|
# depends on them as well
|
|
case $(modprobe -S "$kernel" -D "$_module") in
|
|
*wmi* | *gpu* | *net*) continue ;;
|
|
esac 2> /dev/null
|
|
|
|
copy_module "$_module"
|
|
done
|
|
fi
|
|
|
|
copy_binary modprobe
|
|
|
|
cp "${moddir}/${kernel}/modules.builtin" \
|
|
"${moddir}/${kernel}/modules.order" \
|
|
"${tmpdir}${moddir}/${kernel}"
|
|
|
|
chmod 644 \
|
|
"${tmpdir}${moddir}/${kernel}/modules.builtin" \
|
|
"${tmpdir}${moddir}/${kernel}/modules.order"
|
|
|
|
depmod -b "$tmpdir" "$kernel"
|
|
}
|
|
|
|
copy_devmgr()
|
|
{
|
|
print "configuring device manager"
|
|
|
|
# false positive
|
|
# shellcheck disable=2016
|
|
case "$devmgr" in
|
|
none)
|
|
# TODO implement mode without device manager using deprecated
|
|
# /sys/kernel/uevent_helper or /proc/sys/kernel/hotplug
|
|
;;
|
|
mdev)
|
|
for _binary in mdev find; do
|
|
copy_binary "$_binary"
|
|
done
|
|
|
|
printf "%s\n" \
|
|
'SUBSYSTEM=block;.* 0:0 660 @device-helper' \
|
|
> "${tmpdir}/etc/mdev.conf"
|
|
|
|
[ "$monolith" = 1 ] || printf "%s\n" \
|
|
'$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"' \
|
|
>> "${tmpdir}/etc/mdev.conf"
|
|
;;
|
|
mdevd)
|
|
for _binary in mdevd mdevd-coldplug; do
|
|
copy_binary "$_binary"
|
|
done
|
|
|
|
printf "%s\n" \
|
|
'SUBSYSTEM=block;.* 0:0 660 @device-helper' \
|
|
> "${tmpdir}/etc/mdev.conf"
|
|
|
|
[ "$monolith" = 1 ] || printf "%s\n" \
|
|
'$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"' \
|
|
>> "${tmpdir}/etc/mdev.conf"
|
|
;;
|
|
udev)
|
|
# why systemd violates FHS and places daemon in /lib ?
|
|
udevd=$(command -v /lib/systemd/systemd-udevd) || udevd=udevd
|
|
|
|
for _binary in "$udevd" udevadm; do
|
|
copy_binary "$_binary"
|
|
done
|
|
|
|
mkdir -p "${tmpdir}/lib/udev/rules.d"
|
|
|
|
printf "%s\n" \
|
|
'SUBSYSTEMS=="block", ACTION=="add", RUN+="/bin/device-helper"' \
|
|
> "${tmpdir}/lib/udev/rules.d/device-helper.rules"
|
|
|
|
[ "$monolith" = 1 ] || printf "%s\n" \
|
|
'ENV{MODALIAS}=="?*", ACTION=="add", RUN+="/bin/modprobe %E{MODALIAS}"' \
|
|
>> "${tmpdir}/lib/udev/rules.d/device-helper.rules"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
make_initramfs()
|
|
(
|
|
print "generating initramfs image"
|
|
|
|
# check if image already exist
|
|
[ "$force" != 1 ] && [ -e "$output" ] &&
|
|
panic "initramfs image already exist"
|
|
|
|
cd "$tmpdir"; find . |
|
|
cpio -oH newc 2> /dev/null |
|
|
${compress:-cat} > "$output" ||
|
|
panic "failed to generate initramfs image"
|
|
|
|
print "done! check out $output"
|
|
)
|
|
|
|
# int main()
|
|
{
|
|
[ "$(id -u)" = 0 ] || panic "must be run as root"
|
|
|
|
# enable exit on error and disable globbing
|
|
set -ef
|
|
|
|
prepare_environment "$@"
|
|
prepare_initramfs
|
|
|
|
# copy and run hooks if any
|
|
for _hook in $hooks; do
|
|
copy_hook "$_hook"
|
|
done
|
|
|
|
copy_devmgr
|
|
copy_modules
|
|
make_initramfs
|
|
}
|