commit: b3171e7b01912ed27f62911c8a9beee57ef1d976
parent e4ea48246c6fcb7d12a8bbc1b7db50e3b99d55ac
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sat, 27 Apr 2024 12:19:08 +0200
Initial Commit for bootstrap-initrd
Diffstat:
A | .gitignore | 4 | ++++ |
M | README.md | 51 | ++++++++++++++++++++++++++++++++++++++------------- |
D | init | 88 | ------------------------------------------------------------------------------- |
A | init.c | 126 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | init.sh | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ls-stub.c | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | make-initrd.sh | 93 | +++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
A | mv-stub.c | 39 | +++++++++++++++++++++++++++++++++++++++ |
8 files changed, 424 insertions(+), 132 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+# SPDX-FileCopyrightText: 2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: ISC
+/initramfs-tcc
+/initramfs-tcc.cpio.xz
diff --git a/README.md b/README.md
@@ -1,21 +1,46 @@
-# make-initrd
+# bootstrap-initrd
```
-Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact@hacktivis.me>
+Copyright 2017 Haelwenn (lanodan) Monnier <contact@hacktivis.me>
SPDX-License-Identifier: ISC
```
-A barebones initrd (aka initramfs) generator for Linux, intended for systems with simple but special needs like an mirrored LUKS+ZFS root pool.
+A initrd generator for Linux to bootstrap a POSIX-ish system from a reasonably small binary seed, full source bootstrap isn't intended as a goal.
-Write your own ``init`` executable (my own is provided as example) and declare it's required commands in ``make-initrd.sh`` which will generate the initramfs.
+Status: Very early experiments, doesn't yet builds everything, generator very likely doesn't runs on your machine.
-## Dependencies
-* POSIX environment
-* (optionnal) busybox
-* (optionnal) lddtree command from pax-utils, required for glibc systems
+## Rationales
+### TCC (binary)
+Need to start somewhere and TCC allows to interpret C, allowing to avoid seeding another interpreter.
+The reason to not pick Guile+MesCC instead is because I find Guile Scheme to be less well-known than amd64 itself.
-## Tested systems
-All are using ZFS on LUKS.
+### musl (binary)
+Need a libc to start somewhere, tryhards could maybe compile it from source with TCC to reduce the seed further but this hasn't been tried.
-* Gentoo Linux amd64 musl
-* Gentoo Linux amd64 glibc
-* Alpine Linux amd64 (musl)
+### mksh (binary)
+Temporary workaround to mrsh lack of working interactivity in the console (Due to some missing TTY initialization?).
+
+### mrsh
+emersion POSIX shell, somewhat incomplete but buildable with only a C Compiler.
+
+### yacc
+The reference implementation of yacc, only needs a C Compiler to build. (Unlike byacc and bison)
+
+### utils-std
+My own software, which explicitly allows to bootstrap with an incomplete POSIX environment.
+
+### minised
+Incomplete implementation of `sed(1)` (notably missing `-i`), but has minimal dependencies.
+Likely to be replaced with a `sed(1)` from a \*BSD.
+
+### (One True) awk
+Reference implementation and AFAIK the one used in BSDs.
+
+### bmake
+NetBSD make made portable, uses an autotools configure script but much simpler than the GNU Make one.
+
+## Launching in QEMU
+```
+$ qemu-system-x86_64 -enable-kvm -cpu host -m 1536 -kernel /boot/vmlinuz-6.6.21-gentoo -initrd /tmp/initramfs-tcc.cpio.xz -append 'init=/init console=ttyS0 panic=1' -nographic -no-reboot
+```
+
+* Combination of `panic=1` and `-no-reboot` allows to exit+relaunch
diff --git a/init b/init
@@ -1,88 +0,0 @@
-#!/bin/sh
-# Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact@hacktivis.me>
-# SPDX-License-Identifier: ISC
-
-export PATH=/bin:/sbin:/usr/bin:/usr/sbin
-export init=/sbin/init newroot=/newroot root=zroot/ROOT/gentoo sh=/bin/sh level=3\ -a dev_hotplug=mdev
-
-
-rescueshell() {
- export PS1='rsh:$(tty | cut -c6-):$PWD # '
- if command -v setsid >/dev/null; then
- # shellcheck disable=SC2094
- setsid "$sh" -i -0<"$console" 1>"$console" 2>&1
- else
- # shellcheck disable=SC2094
- "$sh" -i 0<"$console" 1>"$console" 2>&1
- fi
-}
-
-die() {
- echo "Dropping into a rescueshell..."
- rescueshell || exec $sh -i
-}
-
-getdev() {
- blkid | grep "$1" | cut -d: -f1
-}
-
-set -v
-
-/bin/busybox --install /usr/bin
-
-umask 0077
-mount -t proc proc /proc
-mount -t sysfs sysfs /sys
-if grep -q devtmpfs /proc/filesystems; then
- mount -t devtmpfs devtmpfs /dev
-else
- mount -t tmpfs tmpfs /dev
-fi
-
-# shellcheck disable=SC2013
-for arg in $(cat /proc/cmdline); do
- case $arg in
- rescue*)
- export rescue=1
- ;;
- single)
- export level=2
- ;;
- level*|init*|root*|crypt_root*|sh*|dev_hotplug*)
- # shellcheck disable=SC2163
- export "$arg"
- ;;
- esac
-done
-
-root="${root/#ZFS=}"
-
-"$dev_hotplug" -s || die
-
-command -v "$dev_hotplug" > /proc/sys/kernel/hotplug
-
-[ -h /dev/fd ] || ln -fs /proc/self/fd /dev/fd
-[ -h /dev/stderr ] || ln -fs /proc/self/stderr /dev/stderr
-[ -h /dev/stdin ] || ln -fs /proc/self/stdin /dev/stdin
-[ -h /dev/stdout ] || ln -fs /proc/self/stdout /dev/stdout
-: "${console:=/dev/console}"
-# shellcheck disable=SC2094
-exec 0<"$console" 1>"$console" 2>&1
-
-#root=$(getdev $crypt_root)
-#[ $root ] || root=/dev/sda2
-#cryptsetup open $root root || die
-#
-#mount /dev/mapper/root $newroot || die
-
-modprobe zfs || die
-
-cryptsetup open /dev/sda2 cryptrpool || die
-
-zpool import -d /dev/mapper -d /dev -N rpool || die
-
-mount -t zfs -o rw,zfsutil "$root" "$newroot" || die
-
-for d in /proc /sys /dev; do umount -l "$d"; done
-
-exec switch_root "$newroot" "${init:-/sbin/init}" "$level" || die
diff --git a/init.c b/init.c
@@ -0,0 +1,126 @@
+// environ
+#define _GNU_SOURCE
+
+#include <spawn.h> // posix_spawn
+#include <unistd.h> // chdir, environ
+#include <stdlib.h> // setenv
+#include <stdio.h> // fprintf
+#include <string.h> // strerror
+#include <errno.h>
+#include <sys/wait.h> // waitpid
+#include <sys/stat.h> // mknod
+#include <sys/sysmacros.h> // makedev
+#include <sys/mount.h>
+#include <fcntl.h> // O_RDONLY
+
+#include "mrsh_tcc.h"
+
+static int
+exec_wait(char *args[])
+{
+ pid_t child = 0;
+ // Allows to avoid a fork+exec
+ int ret = posix_spawn(&child, args[0], NULL, NULL, args, environ);
+ if(ret != 0) {
+ fprintf(stderr, "Failed spawning command with argv[0] = '%s': %s\n", args[0], strerror(errno));
+ return -1;
+ }
+
+ if(waitpid(child, NULL, 0) == -1) {
+ fprintf(stderr, "Failed waiting for child of pid %d: %s\n", child, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+build_mrsh()
+{
+ chdir("/mrsh-a858396b79ba217760b0982dd6f45c91c5192c3b");
+
+ fprintf(stderr, "Compiling mrsh\n");
+
+ if(exec_wait(mrsh_tcc_cmd) < 0) return -1;
+
+ fprintf(stderr, "Compiled mrsh !\n");
+
+ return 0;
+}
+
+static void
+e_mknod(const char *path, mode_t mode, dev_t dev)
+{
+ if(mknod(path, mode, dev) < 0)
+ fprintf(stderr, "Failed creating device '%s': %s\n", path, strerror(errno));
+}
+
+int
+main(int argc, char *argv[])
+{
+ setenv("PATH", "/bin", 1);
+ if(build_mrsh() < 0) return 1;
+
+ //e_mknod("/dev/null", S_IFCHR|0666, makedev(0x1, 0x3));
+ //e_mknod("/dev/tty", S_IFCHR|0666, makedev(0x5, 0x0));
+ //e_mknod("/dev/tty1", S_IFCHR|0620, makedev(0x4, 0x1));
+ //e_mknod("/dev/console", S_IFCHR|0660, makedev(0x5, 0x1));
+
+ if(mount("sysfs", "/sys", "sysfs", MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME, NULL) < 0)
+ fprintf(stderr, "Failed to mount /sys: %s\n", strerror(errno));
+
+ if(mount("proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME, "hidepid=2") < 0)
+ fprintf(stderr, "Failed to mount /proc: %s\n", strerror(errno));
+
+ if(mount("devtmpfs", "/dev", "devtmpfs", MS_NOSUID|MS_NOEXEC, NULL) < 0)
+ fprintf(stderr, "Failed to mount /proc: %s\n", strerror(errno));
+
+#if 0
+ int console_ro = open("/dev/console", O_RDONLY);
+ if(console_ro < 0)
+ {
+ fprintf(stderr, "Failed ro-opening /dev/console: %s\n", strerror(errno));
+ return 1;
+ }
+
+ // turn stdin into /dev/console
+ if(dup2(console_ro, 0) < 0)
+ {
+ fprintf(stderr, "Failed setting /dev/console as stdin: %s\n", strerror(errno));
+ return 1;
+ }
+
+ int console_wo = open("/dev/console", O_WRONLY);
+ if(console_wo < 0)
+ {
+ fprintf(stderr, "Failed wo-opening /dev/console: %s\n", strerror(errno));
+ return 1;
+ }
+
+ // turn stdout into /dev/console
+ if(dup2(console_wo, 1) < 0)
+ {
+ fprintf(stderr, "Failed setting /dev/console as stdout: %s\n", strerror(errno));
+ return 1;
+ }
+
+ // turn stderr into /dev/console
+ if(dup2(console_wo, 2) < 0)
+ {
+ fprintf(stderr, "Failed setting /dev/console as stderr: %s\n", strerror(errno));
+ return 1;
+ }
+#endif
+
+ fprintf(stderr, "Launching: /bin/mrsh /init.sh\n");
+
+ setenv("PS1", "> ", 1);
+
+ if(execl("/bin/mrsh", "/bin/mrsh", "/init.sh", NULL) < 0)
+ {
+ fprintf(stderr, "Failed to execute: %s\n", strerror(errno));
+ return 1;
+ }
+
+ fprintf(stderr, "Unreachable\n");
+}
diff --git a/init.sh b/init.sh
@@ -0,0 +1,102 @@
+#!/bin/mrsh
+profile_export() {
+ export "$@"
+ echo "export '$@'" >>/etc/profile
+}
+
+build_awk() {
+ cd /awk-*/
+ yacc -d -b awkgram awkgram.y
+ $CC -o maketab maketab.c
+ ./maketab awkgram.tab.h >proctab.c
+ $CC -o /bin/awk awkgram.tab.c b.c main.c parse.c proctab.c tran.c lib.c run.c lex.c
+}
+
+build_stubs() {
+ $CC $CFLAGS -o /bin/ls /ls-stub.c
+ $CC $CFLAGS -o /bin/mv /mv-stub.c
+}
+
+build_bmake() {
+ cd /bmake/
+ ./configure
+ sh ./make-bootstrap.sh
+ link bmake /bin/bmake
+}
+
+set -ex -o pipefail
+
+export CC=tcc
+export CFLAGS="-Os -Wall -Wextra"
+
+cd /yacc-*/
+$CC $CFLAGS -o /bin/yacc *.c
+
+cd /utils-std-*/
+mrsh ./makeless.sh
+
+profile_export PATH="$PATH:$PWD/cmd/"
+profile_export YACC="yacc"
+# re-export due to prior lack of echo
+profile_export CC="$CC"
+profile_export CFLAGS="$CFLAGS"
+
+export SHELL="/bin/mksh"
+# Note: This is a hardlink, no ln -s yet
+link $SHELL /bin/sh
+
+cd /minised-1.16
+$CC $CFLAGS sedcomp.c sedexec.c -o /bin/sed
+
+build_awk
+
+# stubs for utilities currently missing from utils-std
+
+build_stubs
+
+cat >/bin/hostname <<'EOF'
+#!/bin/sh
+exec uname -n
+EOF
+
+chmod +x /bin/hostname
+
+cat >/bin/grep <<'EOF'
+#!/bin/sh
+set -e
+while [ "$1" = "-*" ]
+do
+ args="$args $1"
+ shift
+done
+
+re="$1"
+shift
+
+exec sed $args -n -e "/${re}/p" "$@"
+EOF
+
+chmod +x /bin/grep
+
+cat >/bin/egrep <<'EOF'
+#!/bin/sh
+set -e
+exec grep -E "$@"
+EOF
+
+chmod +x /bin/egrep
+
+cat >/bin/fgrep <<'EOF'
+#!/bin/sh
+set -e
+exec grep -F "$@"
+EOF
+
+chmod +x /bin/fgrep
+
+build_bmake
+
+cd /
+
+# Note: Strip out -l on switching back to mrsh
+exec $SHELL -l
diff --git a/ls-stub.c b/ls-stub.c
@@ -0,0 +1,53 @@
+#include <sys/stat.h>
+#include <stdio.h> // fprintf
+#include <unistd.h> // getopt
+#include <stdbool.h>
+#include <fcntl.h> // AT_SYMLINK_NOFOLLOW
+#include <string.h> // strerror
+#include <errno.h>
+
+int
+main(int argc, char *argv[])
+{
+ bool opt_d = false, print_inode = false;
+ int stat_opts = 0;
+
+ int c = -1;
+ while((c = getopt(argc, argv, ":di")) != -1)
+ {
+ switch(c)
+ {
+ case 'd':
+ stat_opts |= AT_SYMLINK_NOFOLLOW;
+ opt_d = true;
+ break;
+ case 'i':
+ print_inode = true;
+ break;
+ case '?':
+ fprintf(stderr, "ls: Unknown option '-%c'\n", optopt);
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if(!opt_d) fprintf(stderr, "ls: Warning, readdir not implemented yet\n");
+
+ for(int i = 0; i<argc; i++)
+ {
+ char *file = argv[i];
+ struct stat status;
+ if(fstatat(AT_FDCWD, file, &status, stat_opts) < 0)
+ {
+ fprintf(stderr, "ls: Error getting status for file '%s': %s\n", file, strerror(errno));
+ return 1;
+ }
+
+ if(print_inode) printf("%lu ", status.st_ino);
+ printf("%s\n", file);
+ }
+
+ return 0;
+}
diff --git a/make-initrd.sh b/make-initrd.sh
@@ -1,59 +1,90 @@
#!/bin/sh
-# Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact@hacktivis.me>
+# Copyright 2017 Haelwenn (lanodan) Monnier <contact@hacktivis.me>
# SPDX-License-Identifier: ISC
-out_base=initramfs-all
-elves="busybox blkid lsblk cryptsetup mksh zfs zpool"
+elves="mksh"
+tarballs="
+ utils-std-ff0b40a.tar.gz
+ bmake-20230909.tar.gz
+ minised-1.16.tar.gz
+ make-4.4.1.tar.gz
+ yacc-1.9.1.tar.Z
+ byacc-20240109.tgz
+ nawk-20230909.tar.gz
+ mrsh-a858396b79ba217760b0982dd6f45c91c5192c3b.tar.gz
+"
WORKDIR="$(pwd)"
+name_base="initramfs-tcc"
+out_base="${WORKDIR}/${name_base}"
die() {
echo "[Error] die: ${*}"
exit 1
}
+gen_mrsh_tcc_h() {
+ cd mrsh-a858396b79ba217760b0982dd6f45c91c5192c3b || die
+ printf 'char *mrsh_tcc_cmd[] = {'
+ printf -- '"%s", ' /bin/tcc -Iinclude -o /bin/mrsh
+ find -name '*.c' | grep -v readline | grep -v example | sed -e 's;^./;";' -e 's;$;",;' | tr '\n' ' ' | sed 's;$;NULL;'
+ printf '};'
+ cd "$out_base" || die "Failed: cd $out_base"
+}
+
+set -e
+
if test -e "$out_base"; then
- rm -fr "$out_base" || die "Failed: rm -fr $out_base"
+ rm -fr "$out_base"
fi
mkdir -p "$out_base" || die "Failed: mkdir $out_base"
cd "$out_base" || die "Failed: cd $out_base"
-mkdir -p bin lib dev proc sys newroot etc || die "Failed creating base directories"
+
+for i in $tarballs; do
+ tar xf "/var/cache/distfiles/$i" || die "Failed extracting $i"
+done
+rm yacc-1.9.1/yacc || die
+
+deblob
+
+mkdir -p bin dev proc sys etc tmp || die "Failed creating base directories"
ln -s /proc/mounts etc/mtab || die "Failed symlink for /etc/mtab"
ln -s . usr
-ln -s lib lib64
-ln -s lib lib32
-
-if test -d "/lib/modules"
-then
- cp -pr "/lib/modules" lib || die "Failed copying kernel modules"
-else
- echo "[Warning] '/lib/modules' doesn’t exist"
-fi
-
-mknod -m 660 dev/console c 5 1
-mknod -m 666 dev/urandom c 1 9
-mknod -m 666 dev/random c 1 8
-#mknod -m 640 dev/mem c 1 1
-mknod -m 666 dev/null c 1 3
-mknod -m 666 dev/tty c 5 0
-mknod -m 666 dev/zero c 1 5
-mknod -m 620 dev/tty1 c 4 1
+chmod 777 tmp
for elf in $elves; do
origin=$(command -v "$elf")
cp "$origin" bin/ || die
done
-cp "${WORKDIR}/init" . || die "copying init"
-chmod 755 init || die "init chmod"
-ln bin/mksh bin/sh || die "default shell link"
-
for lib in $(find bin -type f -exec lddtree -l {} + | grep -v bin/ | sort | uniq); do
cp "$lib" lib/ || die
done
-if find . -print0 | cpio --null -ov --format=newc | xz -9 --check=crc32 > "../${out_base}.cpio.xz"; then
- test -e "/boot/${out_base}.cpio.xz" && mv "/boot/${out_base}.cpio.xz" "/boot/${out_base}.cpio.xz.old"
- cp "../${out_base}.cpio.xz" /boot
-fi
+for i in fd stderr stdin stdout; do
+ ln -fs proc/self/$i dev/$i
+done
+
+cp "${WORKDIR}/init.c" ./init || die "copying init"
+sed -i '1i#!/bin/tcc -run' ./init || die "failed adding tcc shebang to init"
+chmod 755 init || die "init chmod"
+
+for i in init.sh ls-stub.c mv-stub.c
+do
+ cp -p "${WORKDIR}/$i" ./ || die "failed copying $i"
+done
+
+QMERGE=1 qmerge -yOKv --root $PWD dev-lang/tcc || die "Failed qmerge'ing tcc"
+
+qlist musl | grep -v -e /bin/ -e /etc/ -e /sbin/ | while read f
+do
+ mkdir -p "$(dirname "./$f")" || die
+ cp -pr "$f" "./$f" || die
+done
+
+gen_mrsh_tcc_h > mrsh_tcc.h
+
+deblob -n
+
+find . -print0 | cpio --null -o --format=newc | xz -9 --check=crc32 > "${out_base}.cpio.xz"
cd "${WORKDIR}"
diff --git a/mv-stub.c b/mv-stub.c
@@ -0,0 +1,39 @@
+#include <stdio.h> // fprintf, rename
+#include <unistd.h> // getopt
+#include <string.h> // strerror
+#include <errno.h>
+
+int
+main(int argc, char *argv[])
+{
+ int c = -1;
+ while((c = getopt(argc, argv, ":f")) != -1)
+ {
+ switch(c)
+ {
+ case 'f':
+ // ignored
+ break;
+ case '?':
+ fprintf(stderr, "mv: Unknown option '-%c'\n", optopt);
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc != 2)
+ {
+ fprintf(stderr, "Usage: mv [-f] src dest\n");
+ return 1;
+ }
+
+ if(rename(argv[0], argv[1]) != 0)
+ {
+ fprintf(stderr, "mv: Failed renaming: %s\n", strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}