#!/bin/sh

# Generate an initramfs image, much like mkinitramfs(8) but simpler
#
# Copyright © 2021-2022 Guilhem Moulin <guilhem@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

set -eu
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
export PATH

unset DEBUG
EXTRACT_DIR="$1"
KERNEL_VERSION="$2"
INITRD="$3"

UTILS="$(dirname -- "$0")"
DESTDIR="$(mktemp --directory -- "$INITRD.XXXXXXXXXX")"
trap "rm -r${DEBUG:+v}f -- \"$DESTDIR\"" EXIT INT TERM

# from /usr/sbin/mkinitramfs: create usr-merged filesystem layout, to
# avoid duplicates if the host filesystem is usr-merged
for d in /bin /lib* /sbin; do
    [ -d "$d" ] || continue
    mkdir -p "$DESTDIR/usr$d"
    ln -sT "usr$d" "$DESTDIR$d"
done

install -m0755 "$UTILS/init" "$DESTDIR/init"
install -m0755 "$UTILS/debootstrap" "$DESTDIR/debootstrap"
cat >"$DESTDIR/init.conf" <<- EOF
	HOSTNAME="$TESTNAME"
	export HOSTNAME
	PACKAGES="$PACKAGES"
	BOOT="$BOOT"
	CONSOLE="$CONSOLE"
	ARCH="$ARCH"
	MERGED_USR="$MERGED_USR"
EOF

for p in setup preinst postinst bottom; do
    # setup: sourced after creating the BIOS or EFI boot partition
    # preinst: run in chroot after debootstrap, but before installing extra packages
    # postinst: optionally run in chroot after installing extra packages
    # bottom: last thing to run before shutdown
    if [ -f "$TESTDIR/$TESTNAME.d/$p" ]; then
        install -m0755 "$TESTDIR/$TESTNAME.d/$p" "$DESTDIR/init.$p"
    fi
done

MODULES="dm_crypt ext4 btrfs raid0 raid1"
if [ "$BOOT" = "efi" ]; then
    MODULES="$MODULES efivarfs nls_ascii nls_cp437 vfat"
fi

depmod -ab "$EXTRACT_DIR" "$KERNEL_VERSION"
for kmod in virtio_console virtio_blk virtio_pci virtio_rng \
        "$EXTRACT_DIR/lib/modules/$KERNEL_VERSION"/kernel/arch/*/crypto/*.ko \
        "$EXTRACT_DIR/lib/modules/$KERNEL_VERSION"/kernel/crypto/*.ko \
        $MODULES; do
    kmod="${kmod##*/}"
    modprobe -aid "$EXTRACT_DIR" -S "$KERNEL_VERSION" --show-depends "${kmod%.ko}"
done | while read -r insmod kmod; do
    [ "$insmod" = "insmod" ] || continue
    [ "$kmod" = "${kmod%.ko *}" ] || kmod="${kmod%.ko *}.ko"
    kmod_rel="${kmod#"$EXTRACT_DIR/lib/modules/$KERNEL_VERSION/"}"
    if [ ! -f "$kmod" ] || [ "${kmod_rel#kernel/}" = "$kmod_rel" ]; then
        echo "Error: Unexpected modprobe output: $insmod $kmod" >&2
        exit 1
    fi
    mkdir -p "$DESTDIR/lib/modules/$KERNEL_VERSION/${kmod_rel%/*}"
    ln -f${DEBUG:+v}T -- "$kmod" "$DESTDIR/lib/modules/$KERNEL_VERSION/$kmod_rel"
done

ln -t "$DESTDIR/lib/modules/$KERNEL_VERSION" -- \
    "$EXTRACT_DIR/lib/modules/$KERNEL_VERSION/modules.order" \
    "$EXTRACT_DIR/lib/modules/$KERNEL_VERSION/modules.builtin"
depmod -wab "$DESTDIR" "$KERNEL_VERSION"

verbose="${DEBUG-}"
. /usr/share/initramfs-tools/hook-functions # for copy_exec()
if [ -f "$TESTDIR/$TESTNAME.d/mkinitramfs" ]; then
    . "$TESTDIR/$TESTNAME.d/mkinitramfs"
fi

copy_exec /bin/cp
copy_exec /bin/rm
copy_exec /bin/chmod

copy_exec /sbin/modprobe
copy_exec /sbin/blkid
copy_exec /sbin/sfdisk
copy_exec /sbin/mkswap
copy_exec /sbin/swapon
copy_exec /sbin/swapoff
copy_exec /sbin/cryptsetup
copy_exec /sbin/dmsetup
copy_exec /usr/bin/dpkg-deb
copy_exec /bin/tar

# assume ossl-modules/legacy.so and libgcc_s.so are relative to the linked libcryptsetup.so
libdir="$(env --unset=LD_PRELOAD ldd /sbin/cryptsetup | sed -nr '/.*=>\s*(\S+)\/libcryptsetup\.so\..*/ {s//\1/p;q}')"
copy_exec "$libdir/ossl-modules/legacy.so" || true
copy_libgcc "$libdir"

for p in /sbin/cryptsetup /sbin/lvm /sbin/mdadm /sbin/mke2fs /sbin/mkfs.btrfs /bin/btrfs; do
    if [ -x "$p" ]; then
        copy_exec "$p"
    fi
done

if [ "$BOOT" = "efi" ]; then
    if [ ! -x "/sbin/mkfs.vfat" ]; then
        echo "Couldn't find mkfs.vfat, is the 'dosfstools' package installed?" >&2
        exit 1
    fi
    copy_exec /sbin/mkfs.vfat
fi

cp -pLt "$DESTDIR/lib" /lib/klibc-*.so
for cmd in cat chroot ln ls mkdir mount mv sh umount uname; do
    exe="/usr/lib/klibc/bin/$cmd"
    if [ ! -f "$exe" ] || [ ! -x "$exe" ]; then
        echo "No such executable: $exe" >&2
        exit 1
    fi
    copy_exec "$exe" /bin
done

# copy udevd and (some of) its rules
copy_exec /lib/systemd/systemd-udevd
copy_exec /bin/udevadm

mkdir -p -- "$DESTDIR/etc/udev" "$DESTDIR/lib/udev/rules.d"
cat >"$DESTDIR/etc/udev/udev.conf" <<-EOF
	udev_log=info
	resolve_names=never
EOF
for rules in 50-udev-default.rules 55-dm.rules 60-block.rules \
        60-persistent-storage.rules 60-persistent-storage-dm.rules \
        63-md-raid-arrays.rules 95-dm-notify.rules; do
    if [ -e "/lib/udev/rules.d/$rules" ]; then
        cp -T "/lib/udev/rules.d/$rules" "$DESTDIR/lib/udev/rules.d/$rules"
    fi
done

cd "$DESTDIR"
find . -print0 | cpio -o0 -R 0:0 -H newc --quiet ${DEBUG:+--verbose} >"$INITRD"
