improve portability, code quality, fix bugs, etc...

This commit is contained in:
illiliti 2020-06-28 06:58:57 +03:00
parent 1287f2996b
commit 9c16bad562
10 changed files with 539 additions and 578 deletions

View File

@ -4,10 +4,17 @@ BINDIR = ${PREFIX}/bin
DATADIR = ${PREFIX}/share DATADIR = ${PREFIX}/share
install: install:
install -Dm600 config ${DESTDIR}${SYSCONFDIR}/tinyramfs/config mkdir -p \
install -Dm755 tinyramfs ${DESTDIR}${BINDIR}/tinyramfs ${DESTDIR}${DATADIR}/tinyramfs/hooks \
install -Dm755 init ${DESTDIR}${DATADIR}/tinyramfs/init ${DESTDIR}${SYSCONFDIR}/tinyramfs \
install -Dm755 device-helper ${DESTDIR}${DATADIR}/tinyramfs/device-helper ${DESTDIR}${BINDIR}
cp -R hooks/* ${DESTDIR}${DATADIR}/tinyramfs/hooks/
cp init device-helper ${DESTDIR}${DATADIR}/tinyramfs
chmod -R 644 ${DESTDIR}${DATADIR}/tinyramfs
cp config ${DESTDIR}${SYSCONFDIR}/tinyramfs
chmod 600 ${DESTDIR}${SYSCONFDIR}/tinyramfs/config
cp tinyramfs ${DESTDIR}${BINDIR}/tinyramfs
chmod 755 ${DESTDIR}${BINDIR}/tinyramfs
uninstall: uninstall:
rm -f ${DESTDIR}${BINDIR}/tinyramfs rm -f ${DESTDIR}${BINDIR}/tinyramfs

View File

@ -7,27 +7,24 @@ Features
-------- --------
- No `local`'s, no bashisms, only POSIX shell - No `local`'s, no bashisms, only POSIX shell
- Easy configuration - Portable, not distro specific
- Easy to use configuration
- Build time and init time hooks
- LUKS (detached header, key), LVM
- mdev, mdevd, eudev - mdev, mdevd, eudev
- LUKS, LVM
- LUKS detached header and key embedded into initramfs
Dependencies Dependencies
------------ ------------
* POSIX utilities ( find, mkdir, ... ) * POSIX utilities
* POSIX shell * POSIX shell
* `switch_root` * `switch_root`
* `readlink`
* `install`
* `mount` * `mount`
* `blkid` * `blkid`
* `cpio` * `cpio`
* `strip` * POSIX SD `strip`
- Optional - Optional
* `gzip` * `mdev` OR `mdevd` OR `eudev` OR `systemd-udevd`
- Required by default
* `mdev` OR `mdevd` OR `eudev`
- systemd-udevd not tested - systemd-udevd not tested
* `lvm2` * `lvm2`
- Required for LVM support - Required for LVM support
@ -39,9 +36,8 @@ Dependencies
Notes Notes
----- -----
* busybox modutils doesn't handle soft dependencies (modules.softdep). You must manually include them using `modules` config option * busybox modutils doesn't handle soft dependencies (modules.softdep). You must manually copy them using hooks
* busybox and toybox blkid doesn't support PARTUUID. You must use util-linux blkid * busybox and toybox blkid doesn't support PARTUUID. You must use util-linux blkid for PARTUUID support
* zsh (in POSIX mode) shows some errors in init stage. Just ignore these errors, it's harmless
* `cp` in toybox incorrectly handles `-P` flag. You need to apply patch from [this issue](https://github.com/landley/toybox/issues/174) or replace cp with another implementation * `cp` in toybox incorrectly handles `-P` flag. You need to apply patch from [this issue](https://github.com/landley/toybox/issues/174) or replace cp with another implementation
Installation Installation
@ -57,20 +53,6 @@ tinyramfs -o /boot/initramfs
# reboot... # reboot...
``` ```
Usage
-----
```
usage: tinyramfs [option]
-o, --output <file> set initramfs output path
-c, --config <file> set config file path
-m, --moddir <dir> set modules directory
-k, --kernel <ver> set kernel version
-F, --files <dir> set files directory
-d, --debug enable debug mode
-f, --force overwrite initramfs image
```
Configuration Configuration
------------- -------------

110
config
View File

@ -2,23 +2,9 @@
# configuration # configuration
# #
# uncomment and fill settings which you needed # uncomment and fill settings which you needed
# debug mode
# default - 0
# #
#debug=0|1 # 0 - disable
# 1 - enable
# overwrite initramfs
# default - 0
#
#force=0|1
# initramfs output path
#
# default - /tmp/initramfs-$kernel
# example - output="/tmp/myinitramfs.img.gz"
#
#output=""
# monolithic kernel # monolithic kernel
# #
@ -27,48 +13,35 @@
# #
#monolith=0|1 #monolith=0|1
# modules directory
#
# default - /lib/modules
# example - moddir="/mnt/root/lib/modules"
#
#moddir=""
# kernel version
#
# default - $(uname -r)
# example - kernel="5.4.18_1"
#
#kernel=""
# compression program # compression program
# #
# default - gzip -9 # supported - whatever you choose
# example - # default - none
# compress="pigz -9" # example - compress="pigz -9"
# compress="none" # disable compress
# #
#compress="" #compress=""
# root # root file system
# #
# supported - PARTUUID, DEVICE, LABEL, UUID # supported - UUID, LABEL, DEVICE, PARTUUID
# example - # example -
# root="/dev/sda1" # root="/dev/sda1"
# root="/dev/dm-0" # root="/dev/dm-0"
# root="/dev/disk/by-uuid/13bcb7cc-8fe5-4f8e-a1fe-e4b5b336f3ef"
# root="PARTUUID=35f923c5-083a-4950-a4da-e611d0778121" # root="PARTUUID=35f923c5-083a-4950-a4da-e611d0778121"
# #
#root="" #root=""
# root type # root file system type
# #
# default - autodetected # default - autodetected throught /proc/mounts
# example - root_type="btrfs" # example - root_type="btrfs"
# #
#root_type="" #root_type=""
# root options # root file system options
# example - see fstab(5) # example - root_opts="subvol=mysubvolume,nodatacow,defaults"
# see fstab(5) man page for more info
# #
#root_opts="" #root_opts=""
@ -79,72 +52,57 @@
# hostonly mode # hostonly mode
# #
# default - 0
# dramatically reduce initramfs size # dramatically reduce initramfs size
# useful only for modular kernels # useful only for modular kernels
# #
#hostonly=0|1 #hostonly=0|1
# additional modules # hooks
# example - modules="fat crc32c_generic"
# #
#modules="" # currently supported - lvm, luks
# example -
# exclude modules # hooks="lvm luks" will support booting LUKS on LVM
# example - modules_exclude="wmi fuse" # hooks="luks lvm" will support booting LVM on LUKS
# hooks="luks" will support booting only LUKS
# and so on...
# #
#modules_exclude="" #hooks=""
# additional binaries
# example - binaries="ls cat /path/to/mycustomprog"
#
#binaries=""
# LVM support
# default - 0
#
#lvm=0|1
# LVM options # LVM options
# #
# supported - tag, name, group, config, discard # supported - tag, name, group, config, discard
# description - # description -
# tag - trigger lvm by tag # tag - trigger lvm by tag
# name - trigger lvm by logical volume name # name - trigger lvm by logical volume name. group must be specified
# group - trigger lvm by volume group name # group - trigger lvm by volume group name
# config - embed host lvm config # config - embed host lvm config
# discard - enable issue_discards # discard - enable issue_discards
# example - # example -
# lvm_opts="tag=lvm-server" # lvm_opts="tag=lvm-server"
# lvm_opts="name=lv1,group=vg1" # lvm_opts="name=lv1,group=vg1"
# lvm_opts="config=1,discard" # lvm_opts="config=1,discard=1"
# lvm_opts="discard=1" # lvm_opts="discard=1"
# #
#lvm_opts="" #lvm_opts=""
# LUKS support
# default - 0
#
#luks=0|1
# LUKS encrypted root
#
# supported - PARTUUID, DEVICE, LABEL, UUID
# example -
# luks_root="/dev/sda1"
# luks_root="PARTUUID=35f923c5-083a-4950-a4da-e611d0778121"
#
#luks_root=""
# LUKS options # LUKS options
# #
# supported - key, name, header, discard # supported - key, name, root, header, discard
# description - # description -
# key - embed key # key - embed key
# name - device mapper name # name - device mapper name
# root - encrypted root ( UUID, LABEL, DEVICE, PARTUUID )
# header - embed header # header - embed header
# discard - enable allow-discards # discard - enable allow-discards
# example - # example -
# luks_opts="key=/path/to/keyfile,name=myluksroot,header=/path/to/header,discard" # luks_opts="root=PARTUUID=35f923c5-083a-4950-a4da-e611d0778121,key=/path/to/keyfile,name=myluksroot,header=/path/to/header,discard=1"
# luks_opts="discard=1" # luks_opts="root=PARTUUID=35f923c5-083a-4950-a4da-e611d0778121,
# key=/path/to/keyfile,
# name=myluksroot,
# header=/path/to/header,
# discard=1"
#
# luks_opts="root=/dev/sda1,discard=1"
# #
#luks_opts="" #luks_opts=""

View File

@ -6,7 +6,6 @@ create_symlink()
{ {
dir="$1"; sym="$2" dir="$1"; sym="$2"
# remove double quotes
sym="${sym%\"}" sym="${sym%\"}"
sym="${sym#\"}" sym="${sym#\"}"
sym="${dir}/${sym}" sym="${dir}/${sym}"
@ -20,7 +19,7 @@ create_symlink()
[ -b "/dev/${dev_name=${DEVPATH##*/}}" ] || exit 1 [ -b "/dev/${dev_name=${DEVPATH##*/}}" ] || exit 1
# prevent race condition # prevent race condition
while ! blkid "/dev/${dev_name}"; do sleep 1; done blkid "/dev/${dev_name}" || sleep 2
for line in $(blkid "/dev/${dev_name}"); do case "${line%%=*}" in for line in $(blkid "/dev/${dev_name}"); do case "${line%%=*}" in
UUID) create_symlink /dev/disk/by-uuid "${line##*=}" ;; UUID) create_symlink /dev/disk/by-uuid "${line##*=}" ;;

37
hooks/luks/luks Normal file
View File

@ -0,0 +1,37 @@
# vim: set ft=sh:
#
# handle_luks()
{
print "configuring LUKS"
[ "$hostonly" = 1 ] &&
for _module in \
aes ecb xts lrw wp512 sha256 \
sha512 twofish serpent dm-crypt
do
copy_module "$_module"
done
copy_binary cryptsetup
# avoid possible issues with libgcc_s.so.1
# see https://bugs.archlinux.org/task/56771
[ -e /lib/libgcc_s.so.1 ] && copy_library /lib/libgcc_s.so.1
# word splitting is safe by design
# shellcheck disable=2086
IFS=,; set -- $luks_opts; unset IFS
set -C; for opt; do case "${opt%%=*}" in
key | header)
cp "${opt#*=}" "${tmpdir}/root/${opt%%=*}"
chmod 400 "${tmpdir}/root/${opt%%=*}"
sed "s|${opt#*=}|/root/${opt%%=*}|" \
"${tmpdir}/etc/tinyramfs/config" > "${tmpdir}/_"
cp "${tmpdir}/_" "${tmpdir}/etc/tinyramfs/config"
chmod 600 "${tmpdir}/etc/tinyramfs/config"
rm "${tmpdir}/_"
esac || panic; done; set +C
}

31
hooks/luks/luks.init Normal file
View File

@ -0,0 +1,31 @@
# vim: set ft=sh:
#
# unlock_luks()
{
[ "$break" = luks ] && { print "break before unlock_luks()"; sh; }
mkdir -p /run/cryptsetup
IFS=,; set -- $luks_opts; unset IFS
for opt; do case "$opt" in
discard=1) luks_discard="--allow-discards" ;;
header=*) luks_header="--${opt}" ;;
name=*) luks_name="${opt#*=}" ;;
root=*) luks_root="${opt#*=}" ;;
key=*) luks_key="-d ${opt#*=}" ;;
esac; done
resolve_device "$luks_root"
set -- \
"$luks_key" "$luks_header" "$luks_discard" \
"$device" "${luks_name:-crypt-${device##*/}}"
# libdevice-mapper assumes that udev has dm rules
# which is not true because we use our device-helper for dm stuff
# this variable fixes possible(?) hang
export DM_DISABLE_UDEV=1
cryptsetup open $@ || panic "failed to unlock LUKS"
}

44
hooks/lvm/lvm Normal file
View File

@ -0,0 +1,44 @@
# vim: set ft=sh:
#
# handle_lvm()
{
print "configuring LVM"
[ "$hostonly" = 1 ] &&
for _module in \
dm-log dm-cache dm-mirror \
dm-snapshot dm-multipath dm-thin-pool
do
copy_module "$_module"
done
copy_binary lvm
lvm_config="
devices {
write_cache_state = 0
}
backup {
backup = 0
archive = 0
}
global {
use_lvmetad = 0
}"
# word splitting is safe by design
# shellcheck disable=2086
IFS=,; set -- $lvm_opts; unset IFS
for opt; do case "$opt" in
config=1) embed_lvm_config=
esac; done
mkdir -p "${tmpdir}/etc/lvm"
lvm config \
--config "$lvm_config" \
${embed_lvm_config+--mergedconfig} \
> "${tmpdir}/etc/lvm/lvm.conf"
}

35
hooks/lvm/lvm.init Normal file
View File

@ -0,0 +1,35 @@
# vim: set ft=sh:
#
# trigger_lvm()
{
[ "$break" = lvm ] && { print "break before trigger_lvm()"; sh; }
mkdir -p /run/lvm /run/lock/lvm
IFS=,; set -- $lvm_opts; unset IFS
for opt; do case "$opt" in
discard=1) lvm_discard="--config=devices{issue_discards=1}" ;;
config=0) : > /etc/lvm/lvm.conf ;;
group=*) lvm_group="${opt##*=}" ;;
name=*) lvm_name="/${opt##*=}" ;;
tag=*) lvm_tag="@${opt##*=}" ;;
esac; done
set -- "--sysinit" "-qq" "-aay" "$lvm_discard"
# libdevice-mapper assumes that udev have dm rules
# which is not true because we use our device-helper for dm stuff
# this variable fixes possible(?) hang
export DM_DISABLE_UDEV=1
if [ "$lvm_group" ] && [ "$lvm_name" ]; then
lvm lvchange $@ "${lvm_group}${lvm_name}"
elif [ "$lvm_group" ]; then
lvm vgchange $@ "$lvm_group"
elif [ "$lvm_tag" ]; then
lvm lvchange $@ "$lvm_tag"
else
lvm vgchange $@
fi || panic "failed to trigger LVM"
}

111
init
View File

@ -1,12 +1,7 @@
#!/bin/sh -ef #!/bin/sh
# #
# tiny init # tiny init
# #
# word splitting is safe by design
# shellcheck disable=2068,2046,2086
#
# false positive
# shellcheck disable=2154,2163,1091
print() print()
{ {
@ -21,21 +16,33 @@ panic()
resolve_device() resolve_device()
{ {
count=0; device= count=0; device="$1"
case "${1%%=*}" in case "${device%%=*}" in /dev/*) ;;
/dev/*) device="$1" ;; UUID) device="/dev/disk/by-uuid/${device#*=}" ;;
UUID) device="/dev/disk/by-uuid/${1##*=}" ;; LABEL) device="/dev/disk/by-label/${device#*=}" ;;
LABEL) device="/dev/disk/by-label/${1##*=}" ;; PARTUUID) device="/dev/disk/by-partuuid/${device#*=}" ;;
PARTUUID) device="/dev/disk/by-partuuid/${1##*=}" ;;
esac esac
# prevent race condition # prevent race condition
# XXX what the hell happens here?
# why this loop sometimes trigger panic if i remove '|| :'
while [ ! -b "$device" ]; do sleep 1 while [ ! -b "$device" ]; do sleep 1
[ "$(( count += 1 ))" != 30 ] || { [ "$((count += 1))" = 30 ] && {
panic "failed to lookup partition" panic "failed to lookup partition"
break break
} }
done || :
}
run_hook()
{
type="$1"; hksdir=/usr/share/tinyramfs/hooks
# run hooks if any
for hook in $hooks; do
[ -f "${hksdir}/${hook}/${hook}.${type}" ] || continue
. "${hksdir}/${hook}/${hook}.${type}"
done done
} }
@ -45,23 +52,17 @@ prepare_environment()
export \ export \
PATH=/bin TERM=linux SHELL=/bin/sh \ PATH=/bin TERM=linux SHELL=/bin/sh \
LANG=C LC_ALL=C PS1="# " HOME=/root \ LANG=C LC_ALL=C PS1="# " HOME=/root
mount -t proc -o nosuid,noexec,nodev proc /proc mount -t proc -o nosuid,noexec,nodev proc /proc
mount -t sysfs -o nosuid,noexec,nodev sys /sys mount -t sysfs -o nosuid,noexec,nodev sys /sys
mount -t tmpfs -o nosuid,nodev,mode=0755 run /run mount -t tmpfs -o nosuid,nodev,mode=0755 run /run
mount -t devtmpfs -o nosuid,noexec,mode=0755 dev /dev mount -t devtmpfs -o nosuid,noexec,mode=0755 dev /dev
mkdir -p /run/cryptsetup /run/lock /run/lvm
ln -s /proc/self/fd /dev/fd ln -s /proc/self/fd /dev/fd
ln -s fd/0 /dev/stdin ln -s fd/0 /dev/stdin
ln -s fd/1 /dev/stdout ln -s fd/1 /dev/stdout
ln -s fd/2 /dev/stderr ln -s fd/2 /dev/stderr
trap panic EXIT
[ ! "$modules" ] || modprobe -a "$modules"
} }
parse_cmdline() parse_cmdline()
@ -69,14 +70,14 @@ parse_cmdline()
read -r cmdline < /proc/cmdline read -r cmdline < /proc/cmdline
for line in $cmdline; do case "$line" in for line in $cmdline; do case "$line" in
debug | debug=1) set -x ;; rootfstype=*) root_type="${line#*=}" ;;
rootfstype=*) root_type="${line##*=}" ;; rootflags=*) root_opts="${line#*=}" ;;
rootflags=*) root_opts="${line##*=}" ;; debug=1) set -x ;;
ro | rw) rorw="-o $line" ;; ro | rw) rorw="-o $line" ;;
--*) init_args="${cmdline##*--}"; break ;; --*) init_args="${cmdline#*-- }"; break ;;
*=*) command export "$line" ;; *=*) command export "$line" ;;
*) command export "${line}=1" ;; *) command export "${line}=1" ;;
esac 2> /dev/null || continue; done esac 2> /dev/null || :; done
} }
setup_devmgr() setup_devmgr()
@ -107,55 +108,6 @@ setup_devmgr()
esac 2> /dev/null esac 2> /dev/null
} }
unlock_luks()
{
[ "$break" = luks ] && { print "break before unlock_luks()"; sh; }
{ IFS=,; set -- $luks_opts; unset IFS; }
for opt; do case "$opt" in
discard | discard=1) luks_discard="--allow-discards" ;;
header=*) luks_header="--${opt}" ;;
name=*) luks_name="${opt##*=}" ;;
key=*) luks_key="-d ${opt##*=}" ;;
esac; done
resolve_device "$luks_root"
set -- \
"$luks_key" "$luks_header" "$luks_discard" \
"$device" "${luks_name:-crypt-${device##*/}}"
cryptsetup open $@ || panic "failed to unlock LUKS"
}
trigger_lvm()
{
[ "$break" = lvm ] && { print "break before trigger_lvm()"; sh; }
{ IFS=,; set -- $lvm_opts; unset IFS; }
for opt; do case "$opt" in
discard | discard=1) lvm_discard="--config=devices{issue_discards=1}" ;;
config=0) : > /etc/lvm/lvm.conf ;;
group=*) lvm_group="${opt##*=}" ;;
name=*) lvm_name="/${opt##*=}" ;;
tag=*) lvm_tag="@${opt##*=}" ;;
esac; done
set -- "--sysinit" "-qq" "-aay" "$lvm_discard"
if [ "$lvm_group" ] && [ "$lvm_name" ]; then
lvm lvchange $@ "${lvm_group}${lvm_name}"
elif [ "$lvm_group" ]; then
lvm vgchange $@ "$lvm_group"
elif [ "$lvm_tag" ]; then
lvm lvchange $@ "$lvm_tag"
else
lvm vgchange $@
fi || panic "failed to trigger LVM"
}
mount_root() mount_root()
{ {
[ "$break" = root ] && { print "break before mount_root()"; sh; } [ "$break" = root ] && { print "break before mount_root()"; sh; }
@ -184,6 +136,7 @@ boot_system()
set -- "/mnt/root" "${init:-/sbin/init}" "$init_args" set -- "/mnt/root" "${init:-/sbin/init}" "$init_args"
# POSIX exec has no -c flag to execute command with empty environment
# use 'env -i' to prevent leaking exported variables # use 'env -i' to prevent leaking exported variables
exec env -i \ exec env -i \
TERM=linux \ TERM=linux \
@ -193,14 +146,16 @@ boot_system()
# int main() # int main()
{ {
# enable exit on error and disable globbing
# trap EXIT signal
set -ef; trap panic EXIT
prepare_environment prepare_environment
run_hook init.early
parse_cmdline parse_cmdline
setup_devmgr setup_devmgr
# trigger lvm twice to handle both LUKS on LVM and LVM on LUKS run_hook init
[ "$lvm" = 1 ] && trigger_lvm
[ "$luks" = 1 ] && unlock_luks
[ "$lvm" = 1 ] && trigger_lvm
mount_root mount_root
boot_system boot_system

689
tinyramfs
View File

@ -2,8 +2,6 @@
# #
# tiny initramfs # tiny initramfs
# #
# false positive
# shellcheck disable=2154
print() print()
{ {
@ -19,41 +17,58 @@ panic()
usage() usage()
{ {
cat << EOF cat << EOF
usage: $0 [option] usage: ${0##*/} [option...]
-o, --output <file> set initramfs output path -o, --output <file> set initramfs output path
default is /boot/initramfs-$(uname -r)
-c, --config <file> set config file path -c, --config <file> set config file path
-m, --moddir <dir> set modules directory 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 -k, --kernel <ver> set kernel version
-F, --files <dir> set files directory 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 -d, --debug enable debug mode
-f, --force overwrite initramfs image -f, --force overwrite initramfs image
EOF EOF
} }
parse_args() prepare_environment()
{ {
while [ "$1" ]; do case "$1" in while [ "$1" ]; do case "$1" in
-o | --output) -o | --output)
_output="${2:?}"; shift 2 output="${2:?}"; shift 2
;; ;;
-c | --config) -c | --config)
_config="${2:?}"; shift 2 config="${2:?}"; shift 2
;; ;;
-m | --moddir) -m | --modules)
_moddir="${2:?}"; shift 2 moddir="${2:?}"; shift 2
;;
-s | --sources)
srcdir="${2:?}"; shift 2
;; ;;
-k | --kernel) -k | --kernel)
_kernel="${2:?}"; shift 2 kernel="${2:?}"; shift 2
;; ;;
-F | --files) -H | --hooks)
_filesdir="${2:?}"; shift 2 hksdir="${2:?}"; shift 2
;; ;;
-d | --debug) -d | --debug)
_debug=1; shift 1 debug=1; shift 1
;; ;;
-f | --force) -f | --force)
_force=1; shift 1 force=1; shift 1
;; ;;
-h | --help) -h | --help)
usage; exit 0 usage; exit 0
@ -64,370 +79,81 @@ parse_args()
usage; exit 1 usage; exit 1
;; ;;
esac; done esac; done
}
prepare_environment()
{
print "preparing environment" print "preparing environment"
# false positive . "${config:-/etc/tinyramfs/config}"
# shellcheck disable=1090
for _file in "$_config" /etc/tinyramfs/config; do
[ -f "$_file" ] && { . "$_file"; break; }
done || panic "failed to source config"
for _dir in "$_filesdir" /usr/share/tinyramfs; do : "${kernel:=$(uname -r)}"
[ -d "$_dir" ] && { filesdir="$_dir"; break; } : "${moddir:=/lib/modules}"
done || panic "failed to locate required files" : "${srcdir:=/usr/share/tinyramfs}"
: "${output:=/boot/tinyramfs-${kernel}}"
# general variables mkdir -p "${tmpdir:=${TMPDIR:-/tmp}/tinyramfs.$$}"
debug="${_debug:-${debug:-0}}"
force="${_force:-${force:-0}}"
moddir="${_moddir:-${moddir:-/lib/modules}}"
kernel="${_kernel:-${kernel:-$(uname -r)}}"
output="${_output:-${output:-/tmp/initramfs-${kernel}}}"
mkdir -p "${workdir=${XDG_CACHE_HOME:-${TMPDIR:-/tmp}}/initramfs.$$}"
# helpers variables
workdirbin="${workdir}/usr/bin/"
workdirlib="${workdir}/usr/lib/"
modker="${moddir}/${kernel}"
# false positive # false positive
# shellcheck disable=2015 # shellcheck disable=2015
[ "$debug" = 1 ] && set -x || trap trap_helper EXIT INT [ "$debug" = 1 ] && set -x || trap "rm -rf $tmpdir" EXIT INT
} }
trap_helper() prepare_initramfs()
{ {
[ "${ret=$?}" = 0 ] || print "preparing initramfs"
print "unexpected error occurred" \
"\033[1;31m!!\033[m" >&2
print "removing working directory"; rm -rf "$workdir"
exit "$ret"
}
populate_config()
{
printf "%s\n" "$@" >> "${workdir}/etc/tinyramfs/config"
}
install_requirements()
{
print "installing requirements"
# install required binaries
for _binary in \[ sh ln kill mkdir env \
blkid sleep mount printf \
switch_root "${filesdir}/device-helper"
do
install_binary "$_binary"
done
# install user specified binaries
for _binary in $binaries; do
install_binary "$_binary"
done
# install init
install -m755 "${filesdir}/init" "${workdir}/init"
# fix ubase mount issue
: > "${workdir}/etc/fstab"
populate_config \
"root='$root'" \
"devmgr='$devmgr'" \
"root_type='$root_type'" \
"root_opts='$root_opts'"
}
create_structure()
{
print "creating directory structure"
# make directories
mkdir -p \ mkdir -p \
"${workdir}/dev" \ "${tmpdir}/dev" \
"${workdir}/sys" \ "${tmpdir}/sys" \
"${workdir}/tmp" \ "${tmpdir}/tmp" \
"${workdir}/run" \ "${tmpdir}/run" \
"${workdir}/var" \ "${tmpdir}/var" \
"${workdir}/proc" \ "${tmpdir}/proc" \
"${workdir}/root" \ "${tmpdir}/root" \
"${workdir}/usr/lib" \ "${tmpdir}/usr/lib" \
"${workdir}/usr/bin" \ "${tmpdir}/usr/bin" \
"${workdir}/mnt/root" \ "${tmpdir}/mnt/root" \
"${workdir}/etc/tinyramfs" "${tmpdir}/etc/tinyramfs"
}
create_symlinks() # make symlinks
{ ln -s usr/lib "${tmpdir}/lib"
print "creating symlinks" 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"
ln -s usr/lib "${workdir}/lib" # TODO make blkid optional
ln -s usr/lib "${workdir}/lib64" # copy required binaries
ln -s usr/bin "${workdir}/bin" for _binary in \
ln -s usr/bin "${workdir}/sbin" \[ sh ln env kill mkdir \
ln -s ../run "${workdir}/var/run" blkid sleep mount printf \
ln -s ../run/lock "${workdir}/var/lock" switch_root "${srcdir}/device-helper"
ln -s bin "${workdir}/usr/sbin"
ln -s lib "${workdir}/usr/lib64"
}
install_devmgr()
{
print "installing 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
install_binary "$_binary"
done
printf "%s\n" \
'SUBSYSTEM=block;.* 0:0 660 @device-helper' \
> "${workdir}/etc/mdev.conf"
[ "$monolith" = 1 ] && return 0
printf "%s\n" \
'$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"' \
>> "${workdir}/etc/mdev.conf"
;;
mdevd)
for _binary in mdevd mdevd-coldplug; do
install_binary "$_binary"
done
printf "%s\n" \
'SUBSYSTEM=block;.* 0:0 660 @device-helper' \
> "${workdir}/etc/mdev.conf"
[ "$monolith" = 1 ] && return 0
printf "%s\n" \
'$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"' \
>> "${workdir}/etc/mdev.conf"
;;
udev)
for _binary in udevd udevadm; do
install_binary "$_binary"
done
mkdir -p "${workdir}/usr/lib/udev/rules.d"
printf "%s\n" \
'SUBSYSTEMS=="block", ACTION=="add", RUN+="/bin/device-helper"' \
> "${workdir}/usr/lib/udev/rules.d/device-helper.rules"
[ "$monolith" = 1 ] && return 0
printf "%s\n" \
'ENV{MODALIAS}=="?*", ACTION=="add", RUN+="/bin/modprobe %E{MODALIAS}"' \
>> "${workdir}/usr/lib/udev/rules.d/device-helper.rules"
;;
esac
}
install_lvm()
{
print "installing LVM"
install_binary lvm
lvm_config="
devices {
write_cache_state = 0
}
backup {
backup = 0
archive = 0
}
global {
use_lvmetad = 0
}"
# word splitting is safe by design
# shellcheck disable=2086
{ IFS=,; set -- $lvm_opts; unset IFS; }
for opt; do case "$opt" in
config | config=1) embed_lvm_config=1 ;;
esac; done
mkdir -p "${workdir}/etc/lvm"
lvm config \
--config "$lvm_config" \
${embed_lvm_config+--mergedconfig} \
> "${workdir}/etc/lvm/lvm.conf"
populate_config "lvm='$lvm'" "lvm_opts='$lvm_opts'"
}
install_luks()
{
print "installing LUKS"
install_binary cryptsetup
# fix libgcc_s.so.1 missing error
# see https://bugs.archlinux.org/task/56771
[ -e /usr/lib/libgcc_s.so.1 ] &&
install_library /usr/lib/libgcc_s.so.1
# word splitting is safe by design
# shellcheck disable=2086
{ IFS=,; set -- $luks_opts; unset IFS; }
for opt; do case "${opt%%=*}" in
key | header)
install -m400 "${opt##*=}" "${workdir}/root/${opt%%=*}" || panic
luks_opts=$(printf "%s" "$luks_opts" | sed "s|${opt##*=}|/root/${opt%%=*}|")
;;
esac; done
populate_config \
"luks='$luks'" \
"luks_root='$luks_root'" \
"luks_opts='$luks_opts'"
}
install_module()
{
module="$1"
modprobe -S "$kernel" -D "$module" 2> /dev/null |
while read -r module || [ "$module" ]; do
# skip unneeded stuff
for _exclude_module in wmi gpu net builtin $modules_exclude; do
case "$module" in *"$_exclude_module"*) continue 2 ;; esac
done
module="${module#insmod }"
# check if module already installed
[ -e "${workdir}${module}" ] && continue
install -Dm644 "$module" "${workdir}${module}" || panic
done
}
install_hostonly_modules()
{
print "installing hostonly modules"
# perform autodetection of modules via /sys
find /sys -name modalias -exec sort -u {} + |
while read -r _module || [ "$_module" ]; do
install_module "$_module"
done
# install LVM modules
[ "$lvm" = 1 ] &&
for _module in dm-thin-pool dm-multipath \
dm-snapshot dm-cache dm-log dm-mirror
do do
install_module "$_module" copy_binary "$_binary"
done done
# install LUKS modules # copy init
[ "$luks" = 1 ] && cp "${srcdir}/init" "${tmpdir}/init"
for _module in aes dm-crypt sha256 sha512 \ chmod 755 "${tmpdir}/init"
wp512 ecb lrw xts twofish serpent
do
install_module "$_module"
done
# install root filesystem module # copy config
if [ "$root_type" ]; then cp "$config" "${tmpdir}/etc/tinyramfs/config"
install_module "$root_type" chmod 600 "${tmpdir}/etc/tinyramfs/config"
else
while read -r _ _dir _type _; do
[ "$_dir" = / ] || continue
install_module "${root_type=$_type}"; break
done < /proc/mounts || panic
fi
} }
install_all_modules() copy_binary()
{
print "installing all modules"
find \
"${modker}/kernel/fs" \
"${modker}/kernel/lib" \
"${modker}/kernel/arch" \
"${modker}/kernel/crypto" \
"${modker}/kernel/drivers/md" \
"${modker}/kernel/drivers/ata" \
"${modker}/kernel/drivers/scsi" \
"${modker}/kernel/drivers/block" \
"${modker}/kernel/drivers/virtio" \
"${modker}/kernel/drivers/usb/host" \
"${modker}/kernel/drivers/usb/storage" \
-type f 2> /dev/null |
while read -r _module || [ "$_module" ]; do
# strip path and extension
_module="${_module##*/}"
_module="${_module%%.*}"
install_module "$_module"
done
}
install_modules()
{
if [ "$hostonly" = 1 ]; then
install_hostonly_modules
else
install_all_modules
fi
# install user specified modules if any
[ "$modules" ] && {
for _module in $modules; do
install_module "$_module"
done; populate_config "modules='$modules'"
}
install_binary modprobe
install -m644 \
"${modker}/modules.builtin" \
"${modker}/modules.order" \
"${workdir}${modker}"
depmod -b "$workdir" "$kernel"
}
install_binary()
{ {
binary=$(command -v "$1") binary=$(command -v "$1")
# check if binary exist and builtin # check if binary exist and builtin
case "$binary" in */*) ;; case "$binary" in */*) ;;
"") "")
panic "$1 doesn't exist" panic "$1 does not exist"
;; ;;
*) *)
# word splitting is safe by design # word splitting is safe by design
# shellcheck disable=2086 # shellcheck disable=2086
{ IFS=:; set -- $PATH; unset IFS; } IFS=:; set -- $PATH; unset IFS
# assume that `command -v` returned builtin command. # assume that `command -v` returned builtin command.
# this behavior depends on shell implementation. # this behavior depends on shell implementation.
@ -437,24 +163,27 @@ install_binary()
[ -x "${_dir}/${binary}" ] || ! continue [ -x "${_dir}/${binary}" ] || ! continue
binary="${_dir}/${binary}"; break binary="${_dir}/${binary}"; break
done || panic "couldn't find external $1" done || panic "$1 does not exist"
;; ;;
esac esac
# check if binary already installed # check if binary already exist
[ -e "${workdirbin}${binary##*/}" ] && return 0 [ -e "${tmpdir}/bin/${binary##*/}" ] && return 0
# iterate throught symlinks and copy them # iterate throught symlinks and copy them
while [ -h "$binary" ]; do while [ -h "$binary" ]; do symlink=$(ls -ld "$binary")
cp -P "$binary" "$workdirbin" || panic cp -P "$binary" "${tmpdir}/bin" || panic
readlink_binary=$(readlink "$binary") binary="${binary%/*}/${symlink##* ->*[ /]}"
binary="${binary%/*}/${readlink_binary##*/}"
done done
install -m755 "$binary" "${workdirbin}${binary##*/}" || panic {
strip "${workdirbin}${binary##*/}" > /dev/null 2>&1 || : cp "$binary" "${tmpdir}/bin"
chmod 755 "${tmpdir}/bin/${binary##*/}"
} || panic
# install binary dependencies if any strip "${tmpdir}/bin/${binary##*/}" > /dev/null 2>&1 || :
# copy binary dependencies if any
ldd "$binary" 2> /dev/null | ldd "$binary" 2> /dev/null |
while read -r _library || [ "$_library" ]; do while read -r _library || [ "$_library" ]; do
@ -465,44 +194,229 @@ install_binary()
_library="${_library#* => }" _library="${_library#* => }"
_library="${_library% *}" _library="${_library% *}"
install_library "$_library" copy_library "$_library"
done done
} }
install_library() copy_library()
{ {
library="$1" library="$1"
# check if library already installed # check if library already exist
[ -e "${workdirlib}${library##*/}" ] && return 0 [ -e "${tmpdir}/lib/${library##*/}" ] && return 0
# iterate throught symlinks and copy them # iterate throught symlinks and copy them
while [ -h "$library" ]; do while [ -h "$library" ]; do symlink=$(ls -ld "$library")
cp -P "$library" "$workdirlib" || panic cp -P "$library" "${tmpdir}/lib" || panic
readlink_library=$(readlink "$library") library="${library%/*}/${symlink##* ->*[ /]}"
library="${library%/*}/${readlink_library##*/}"
done done
install -m755 "$library" "${workdirlib}${library##*/}" || panic {
strip "${workdirlib}${library##*/}" > /dev/null 2>&1 || : cp "$library" "${tmpdir}/lib"
chmod 755 "${tmpdir}/lib/${library##*/}"
} || panic
strip "${tmpdir}/lib/${library##*/}" > /dev/null 2>&1 || :
} }
create_initramfs() 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 "creating initramfs image" print "generating initramfs image"
# check if image already exist # check if image already exist
[ "$force" != 1 ] && [ -e "$output" ] && [ "$force" != 1 ] && [ -e "$output" ] &&
panic "initramfs image already exist" panic "initramfs image already exist"
cd "$workdir"; find . | cd "$tmpdir"; find . |
cpio -oH newc 2> /dev/null |
if [ "$compress" = none ]; then ${compress:-cat} > "$output" ||
cpio -oH newc
else
cpio -oH newc | ${compress:-gzip -9}
fi \
> "$output" 2> /dev/null ||
panic "failed to generate initramfs image" panic "failed to generate initramfs image"
print "done! check out $output" print "done! check out $output"
@ -515,16 +429,15 @@ create_initramfs()
# enable exit on error and disable globbing # enable exit on error and disable globbing
set -ef set -ef
parse_args "$@" prepare_environment "$@"
prepare_environment prepare_initramfs
create_structure
create_symlinks
[ "$lvm" = 1 ] && install_lvm # copy and run hooks if any
[ "$luks" = 1 ] && install_luks for _hook in $hooks; do
[ "$monolith" = 1 ] || install_modules copy_hook "$_hook"
done
install_devmgr copy_devmgr
install_requirements copy_modules
create_initramfs make_initramfs
} }