diff --git a/Makefile b/Makefile index 735167c..041c3db 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,17 @@ BINDIR = ${PREFIX}/bin DATADIR = ${PREFIX}/share install: - install -Dm600 config ${DESTDIR}${SYSCONFDIR}/tinyramfs/config - install -Dm755 tinyramfs ${DESTDIR}${BINDIR}/tinyramfs - install -Dm755 init ${DESTDIR}${DATADIR}/tinyramfs/init - install -Dm755 device-helper ${DESTDIR}${DATADIR}/tinyramfs/device-helper + mkdir -p \ + ${DESTDIR}${DATADIR}/tinyramfs/hooks \ + ${DESTDIR}${SYSCONFDIR}/tinyramfs \ + ${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: rm -f ${DESTDIR}${BINDIR}/tinyramfs diff --git a/README.md b/README.md index b295ab2..0456796 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,24 @@ Features -------- - 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 -- LUKS, LVM -- LUKS detached header and key embedded into initramfs Dependencies ------------ -* POSIX utilities ( find, mkdir, ... ) +* POSIX utilities * POSIX shell * `switch_root` -* `readlink` -* `install` * `mount` * `blkid` * `cpio` -* `strip` +* POSIX SD `strip` - Optional -* `gzip` - - Required by default -* `mdev` OR `mdevd` OR `eudev` +* `mdev` OR `mdevd` OR `eudev` OR `systemd-udevd` - systemd-udevd not tested * `lvm2` - Required for LVM support @@ -39,9 +36,8 @@ Dependencies Notes ----- -* busybox modutils doesn't handle soft dependencies (modules.softdep). You must manually include them using `modules` config option -* busybox and toybox blkid doesn't support PARTUUID. You must use util-linux blkid -* zsh (in POSIX mode) shows some errors in init stage. Just ignore these errors, it's harmless +* 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 for PARTUUID support * `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 @@ -57,20 +53,6 @@ tinyramfs -o /boot/initramfs # reboot... ``` -Usage ------ - -``` -usage: tinyramfs [option] - -o, --output set initramfs output path - -c, --config set config file path - -m, --moddir set modules directory - -k, --kernel set kernel version - -F, --files set files directory - -d, --debug enable debug mode - -f, --force overwrite initramfs image -``` - Configuration ------------- diff --git a/config b/config index 4c2fe1c..46e6443 100644 --- a/config +++ b/config @@ -2,23 +2,9 @@ # configuration # # uncomment and fill settings which you needed - -# debug mode -# default - 0 # -#debug=0|1 - -# overwrite initramfs -# default - 0 -# -#force=0|1 - -# initramfs output path -# -# default - /tmp/initramfs-$kernel -# example - output="/tmp/myinitramfs.img.gz" -# -#output="" +# 0 - disable +# 1 - enable # monolithic kernel # @@ -27,48 +13,35 @@ # #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 # -# default - gzip -9 -# example - -# compress="pigz -9" -# compress="none" # disable compress +# supported - whatever you choose +# default - none +# example - compress="pigz -9" # #compress="" -# root +# root file system # -# supported - PARTUUID, DEVICE, LABEL, UUID +# supported - UUID, LABEL, DEVICE, PARTUUID # example - # root="/dev/sda1" # root="/dev/dm-0" +# root="/dev/disk/by-uuid/13bcb7cc-8fe5-4f8e-a1fe-e4b5b336f3ef" # root="PARTUUID=35f923c5-083a-4950-a4da-e611d0778121" # #root="" -# root type +# root file system type # -# default - autodetected +# default - autodetected throught /proc/mounts # example - root_type="btrfs" # #root_type="" -# root options -# example - see fstab(5) +# root file system options +# example - root_opts="subvol=mysubvolume,nodatacow,defaults" +# see fstab(5) man page for more info # #root_opts="" @@ -79,72 +52,57 @@ # hostonly mode # +# default - 0 # dramatically reduce initramfs size # useful only for modular kernels # #hostonly=0|1 -# additional modules -# example - modules="fat crc32c_generic" +# hooks # -#modules="" - -# exclude modules -# example - modules_exclude="wmi fuse" +# currently supported - lvm, luks +# example - +# hooks="lvm luks" will support booting LUKS on LVM +# hooks="luks lvm" will support booting LVM on LUKS +# hooks="luks" will support booting only LUKS +# and so on... # -#modules_exclude="" - -# additional binaries -# example - binaries="ls cat /path/to/mycustomprog" -# -#binaries="" - -# LVM support -# default - 0 -# -#lvm=0|1 +#hooks="" # LVM options # # supported - tag, name, group, config, discard # description - # 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 # config - embed host lvm config # discard - enable issue_discards # example - # lvm_opts="tag=lvm-server" # lvm_opts="name=lv1,group=vg1" -# lvm_opts="config=1,discard" +# lvm_opts="config=1,discard=1" # lvm_opts="discard=1" # #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 # -# supported - key, name, header, discard +# supported - key, name, root, header, discard # description - # key - embed key # name - device mapper name +# root - encrypted root ( UUID, LABEL, DEVICE, PARTUUID ) # header - embed header # discard - enable allow-discards # example - -# luks_opts="key=/path/to/keyfile,name=myluksroot,header=/path/to/header,discard" -# 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=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="" diff --git a/device-helper b/device-helper index a4e1381..0ec8b2c 100755 --- a/device-helper +++ b/device-helper @@ -6,7 +6,6 @@ create_symlink() { dir="$1"; sym="$2" - # remove double quotes sym="${sym%\"}" sym="${sym#\"}" sym="${dir}/${sym}" @@ -20,7 +19,7 @@ create_symlink() [ -b "/dev/${dev_name=${DEVPATH##*/}}" ] || exit 1 # 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 UUID) create_symlink /dev/disk/by-uuid "${line##*=}" ;; diff --git a/hooks/luks/luks b/hooks/luks/luks new file mode 100644 index 0000000..c9bd3d4 --- /dev/null +++ b/hooks/luks/luks @@ -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 +} diff --git a/hooks/luks/luks.init b/hooks/luks/luks.init new file mode 100644 index 0000000..116def3 --- /dev/null +++ b/hooks/luks/luks.init @@ -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" +} diff --git a/hooks/lvm/lvm b/hooks/lvm/lvm new file mode 100644 index 0000000..63230a2 --- /dev/null +++ b/hooks/lvm/lvm @@ -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" +} + diff --git a/hooks/lvm/lvm.init b/hooks/lvm/lvm.init new file mode 100644 index 0000000..fbe4f9a --- /dev/null +++ b/hooks/lvm/lvm.init @@ -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" +} diff --git a/init b/init index 2bf4292..f86f786 100755 --- a/init +++ b/init @@ -1,12 +1,7 @@ -#!/bin/sh -ef +#!/bin/sh # # tiny init # -# word splitting is safe by design -# shellcheck disable=2068,2046,2086 -# -# false positive -# shellcheck disable=2154,2163,1091 print() { @@ -21,21 +16,33 @@ panic() resolve_device() { - count=0; device= + count=0; device="$1" - case "${1%%=*}" in - /dev/*) device="$1" ;; - UUID) device="/dev/disk/by-uuid/${1##*=}" ;; - LABEL) device="/dev/disk/by-label/${1##*=}" ;; - PARTUUID) device="/dev/disk/by-partuuid/${1##*=}" ;; + case "${device%%=*}" in /dev/*) ;; + UUID) device="/dev/disk/by-uuid/${device#*=}" ;; + LABEL) device="/dev/disk/by-label/${device#*=}" ;; + PARTUUID) device="/dev/disk/by-partuuid/${device#*=}" ;; esac # prevent race condition + # XXX what the hell happens here? + # why this loop sometimes trigger panic if i remove '|| :' while [ ! -b "$device" ]; do sleep 1 - [ "$(( count += 1 ))" != 30 ] || { + [ "$((count += 1))" = 30 ] && { panic "failed to lookup partition" 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 } @@ -45,23 +52,17 @@ prepare_environment() export \ 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 sysfs -o nosuid,noexec,nodev sys /sys mount -t tmpfs -o nosuid,nodev,mode=0755 run /run 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 fd/0 /dev/stdin ln -s fd/1 /dev/stdout ln -s fd/2 /dev/stderr - - trap panic EXIT - - [ ! "$modules" ] || modprobe -a "$modules" } parse_cmdline() @@ -69,14 +70,14 @@ parse_cmdline() read -r cmdline < /proc/cmdline for line in $cmdline; do case "$line" in - debug | debug=1) set -x ;; - rootfstype=*) root_type="${line##*=}" ;; - rootflags=*) root_opts="${line##*=}" ;; + rootfstype=*) root_type="${line#*=}" ;; + rootflags=*) root_opts="${line#*=}" ;; + debug=1) set -x ;; ro | rw) rorw="-o $line" ;; - --*) init_args="${cmdline##*--}"; break ;; + --*) init_args="${cmdline#*-- }"; break ;; *=*) command export "$line" ;; *) command export "${line}=1" ;; - esac 2> /dev/null || continue; done + esac 2> /dev/null || :; done } setup_devmgr() @@ -107,55 +108,6 @@ setup_devmgr() 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() { [ "$break" = root ] && { print "break before mount_root()"; sh; } @@ -184,6 +136,7 @@ boot_system() 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 exec env -i \ TERM=linux \ @@ -193,14 +146,16 @@ boot_system() # int main() { + # enable exit on error and disable globbing + # trap EXIT signal + set -ef; trap panic EXIT + prepare_environment + run_hook init.early parse_cmdline setup_devmgr - # trigger lvm twice to handle both LUKS on LVM and LVM on LUKS - [ "$lvm" = 1 ] && trigger_lvm - [ "$luks" = 1 ] && unlock_luks - [ "$lvm" = 1 ] && trigger_lvm + run_hook init mount_root boot_system diff --git a/tinyramfs b/tinyramfs index 1663c0e..05ae50d 100755 --- a/tinyramfs +++ b/tinyramfs @@ -2,8 +2,6 @@ # # tiny initramfs # -# false positive -# shellcheck disable=2154 print() { @@ -19,41 +17,58 @@ panic() usage() { cat << EOF -usage: $0 [option] +usage: ${0##*/} [option...] -o, --output set initramfs output path + default is /boot/initramfs-$(uname -r) + -c, --config set config file path - -m, --moddir set modules directory - -k, --kernel set kernel version - -F, --files set files directory + default is /etc/tinyramfs/config + + -m, --modules set modules directory + default is /lib/modules + + -s, --sources set sources directory + default is /usr/share/tinyramfs + + -k, --kernel set kernel version + default is $(uname -r) + + -H, --hooks 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 } -parse_args() +prepare_environment() { while [ "$1" ]; do case "$1" in -o | --output) - _output="${2:?}"; shift 2 + output="${2:?}"; shift 2 ;; -c | --config) - _config="${2:?}"; shift 2 + config="${2:?}"; shift 2 ;; - -m | --moddir) - _moddir="${2:?}"; shift 2 + -m | --modules) + moddir="${2:?}"; shift 2 + ;; + -s | --sources) + srcdir="${2:?}"; shift 2 ;; -k | --kernel) - _kernel="${2:?}"; shift 2 + kernel="${2:?}"; shift 2 ;; - -F | --files) - _filesdir="${2:?}"; shift 2 + -H | --hooks) + hksdir="${2:?}"; shift 2 ;; -d | --debug) - _debug=1; shift 1 + debug=1; shift 1 ;; -f | --force) - _force=1; shift 1 + force=1; shift 1 ;; -h | --help) usage; exit 0 @@ -64,370 +79,81 @@ parse_args() usage; exit 1 ;; esac; done -} -prepare_environment() -{ print "preparing environment" - # false positive - # shellcheck disable=1090 - for _file in "$_config" /etc/tinyramfs/config; do - [ -f "$_file" ] && { . "$_file"; break; } - done || panic "failed to source config" + . "${config:-/etc/tinyramfs/config}" - for _dir in "$_filesdir" /usr/share/tinyramfs; do - [ -d "$_dir" ] && { filesdir="$_dir"; break; } - done || panic "failed to locate required files" + : "${kernel:=$(uname -r)}" + : "${moddir:=/lib/modules}" + : "${srcdir:=/usr/share/tinyramfs}" + : "${output:=/boot/tinyramfs-${kernel}}" - # general variables - 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}" + mkdir -p "${tmpdir:=${TMPDIR:-/tmp}/tinyramfs.$$}" # false positive # 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 "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" + print "preparing initramfs" + # make directories mkdir -p \ - "${workdir}/dev" \ - "${workdir}/sys" \ - "${workdir}/tmp" \ - "${workdir}/run" \ - "${workdir}/var" \ - "${workdir}/proc" \ - "${workdir}/root" \ - "${workdir}/usr/lib" \ - "${workdir}/usr/bin" \ - "${workdir}/mnt/root" \ - "${workdir}/etc/tinyramfs" -} + "${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" -create_symlinks() -{ - print "creating symlinks" + # 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" - ln -s usr/lib "${workdir}/lib" - ln -s usr/lib "${workdir}/lib64" - ln -s usr/bin "${workdir}/bin" - ln -s usr/bin "${workdir}/sbin" - ln -s ../run "${workdir}/var/run" - ln -s ../run/lock "${workdir}/var/lock" - 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" + # 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 - # install LVM modules - [ "$lvm" = 1 ] && - for _module in dm-thin-pool dm-multipath \ - dm-snapshot dm-cache dm-log dm-mirror - do - install_module "$_module" - done + # copy init + cp "${srcdir}/init" "${tmpdir}/init" + chmod 755 "${tmpdir}/init" - # install LUKS modules - [ "$luks" = 1 ] && - for _module in aes dm-crypt sha256 sha512 \ - wp512 ecb lrw xts twofish serpent - do - install_module "$_module" - done - - # install root filesystem module - if [ "$root_type" ]; then - install_module "$root_type" - else - while read -r _ _dir _type _; do - [ "$_dir" = / ] || continue - - install_module "${root_type=$_type}"; break - done < /proc/mounts || panic - fi + # copy config + cp "$config" "${tmpdir}/etc/tinyramfs/config" + chmod 600 "${tmpdir}/etc/tinyramfs/config" } -install_all_modules() -{ - 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() +copy_binary() { binary=$(command -v "$1") # check if binary exist and builtin case "$binary" in */*) ;; "") - panic "$1 doesn't exist" + panic "$1 does not exist" ;; *) # word splitting is safe by design # shellcheck disable=2086 - { IFS=:; set -- $PATH; unset IFS; } + IFS=:; set -- $PATH; unset IFS # assume that `command -v` returned builtin command. # this behavior depends on shell implementation. @@ -437,24 +163,27 @@ install_binary() [ -x "${_dir}/${binary}" ] || ! continue binary="${_dir}/${binary}"; break - done || panic "couldn't find external $1" + done || panic "$1 does not exist" ;; esac - # check if binary already installed - [ -e "${workdirbin}${binary##*/}" ] && return 0 + # check if binary already exist + [ -e "${tmpdir}/bin/${binary##*/}" ] && return 0 # iterate throught symlinks and copy them - while [ -h "$binary" ]; do - cp -P "$binary" "$workdirbin" || panic - readlink_binary=$(readlink "$binary") - binary="${binary%/*}/${readlink_binary##*/}" + while [ -h "$binary" ]; do symlink=$(ls -ld "$binary") + cp -P "$binary" "${tmpdir}/bin" || panic + binary="${binary%/*}/${symlink##* ->*[ /]}" 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 | while read -r _library || [ "$_library" ]; do @@ -465,45 +194,230 @@ install_binary() _library="${_library#* => }" _library="${_library% *}" - install_library "$_library" + copy_library "$_library" done } -install_library() +copy_library() { library="$1" - # check if library already installed - [ -e "${workdirlib}${library##*/}" ] && return 0 + # check if library already exist + [ -e "${tmpdir}/lib/${library##*/}" ] && return 0 # iterate throught symlinks and copy them - while [ -h "$library" ]; do - cp -P "$library" "$workdirlib" || panic - readlink_library=$(readlink "$library") - library="${library%/*}/${readlink_library##*/}" + while [ -h "$library" ]; do symlink=$(ls -ld "$library") + cp -P "$library" "${tmpdir}/lib" || panic + library="${library%/*}/${symlink##* ->*[ /]}" 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 [ "$force" != 1 ] && [ -e "$output" ] && panic "initramfs image already exist" - cd "$workdir"; find . | - - if [ "$compress" = none ]; then - cpio -oH newc - else - cpio -oH newc | ${compress:-gzip -9} - fi \ - > "$output" 2> /dev/null || - panic "failed to generate initramfs image" + cd "$tmpdir"; find . | + cpio -oH newc 2> /dev/null | + ${compress:-cat} > "$output" || + panic "failed to generate initramfs image" print "done! check out $output" ) @@ -515,16 +429,15 @@ create_initramfs() # enable exit on error and disable globbing set -ef - parse_args "$@" - prepare_environment - create_structure - create_symlinks + prepare_environment "$@" + prepare_initramfs - [ "$lvm" = 1 ] && install_lvm - [ "$luks" = 1 ] && install_luks - [ "$monolith" = 1 ] || install_modules + # copy and run hooks if any + for _hook in $hooks; do + copy_hook "$_hook" + done - install_devmgr - install_requirements - create_initramfs + copy_devmgr + copy_modules + make_initramfs }