summaryrefslogtreecommitdiff
path: root/opt/tinyrd
diff options
context:
space:
mode:
authorrawnix ports <ports@rawnix.org>2026-09-26 18:19:05 +0000
committerrawnix ports <ports@rawnix.org>2026-09-26 18:19:05 +0000
commitc82e88dd00d1b0b7fcfb4e5628d99611388cef6f (patch)
tree56d864b5a5c6c145edd96178ae219a8f90e191a6 /opt/tinyrd
downloadports-c82e88dd00d1b0b7fcfb4e5628d99611388cef6f.tar.gz
sync 2026-09-26 18:19 UTC
1672 files changed, 151396 insertions(+)
Diffstat (limited to 'opt/tinyrd')
-rw-r--r--opt/tinyrd/.footprint2
-rw-r--r--opt/tinyrd/.signature8
-rw-r--r--opt/tinyrd/MAKEPKG75
-rw-r--r--opt/tinyrd/tinyrd.c526
4 files changed, 611 insertions, 0 deletions
diff --git a/opt/tinyrd/.footprint b/opt/tinyrd/.footprint
new file mode 100644
index 0000000..eff12bd
--- /dev/null
+++ b/opt/tinyrd/.footprint
@@ -0,0 +1,2 @@
+drwxr-xr-x root/root boot/
+-rw-r--r-- root/root boot/tinyrd.img
diff --git a/opt/tinyrd/.signature b/opt/tinyrd/.signature
new file mode 100644
index 0000000..97fbf65
--- /dev/null
+++ b/opt/tinyrd/.signature
@@ -0,0 +1,8 @@
+RWTZ9IduCSQ/mJOxIZ0dTcfhHbV+FMPe2veVvxydOeP758894pIqTG7nqTzqpOk/EQCdpZFDaiqljWeMr+0q0YiGnXn4op8KTQo=
+
+SHA256 (MAKEPKG) = 71de855fde5c2f4afe668f6adfb56e80feebeba63546a26d983a7dc8f982bd7a
+SHA256 (.footprint) = 1ea2580bfeb3deb69693fdbd86ba941e9f49a8e6de4aabde25480471cec53f3f
+SHA256 (tinyrd.c) = 79dc8126afab289ff924f382face04ad18ae53d3257a7349dbef52d864c6b590
+SHA256 (cryptsetup-2.8.8.tar.xz) = 3acfa685f2dd7fcc832e0b77bc7093aa7da554a51ce8dafbb4138eaa854eee35
+SHA256 (LVM2.2.03.35.tgz) = ebf28b3427535e2b5abd9991cd839b61622a0dbfb8c86df0f7af1f69dcaa8371
+SHA256 (tinyrd#0.1-2.pkg.tar.gz) = ca2af533897984e79fd44e5467d9c57248f9adf4e114d81d0fa8e5c90dfeecff
diff --git a/opt/tinyrd/MAKEPKG b/opt/tinyrd/MAKEPKG
new file mode 100644
index 0000000..a8cce18
--- /dev/null
+++ b/opt/tinyrd/MAKEPKG
@@ -0,0 +1,75 @@
+#!/bin/mkpkg
+# description: rawnix initramfs: static /init that unlocks a LUKS root
+# url: https://rawnix.org
+#
+# The image is generic (no modules, no config), so one package serves every
+# kernel and nothing regenerates it. Staged in /boot; limine-sync mirrors it.
+#
+# libcryptsetup and libdevmapper are built here as private static libraries
+# and never installed. The system ports stay shared-only, and the initramfs
+# wants a different configuration anyway:
+# - libdevmapper without udev_sync (no udev before root; the system one
+# has it, and would drag in libudev.a)
+# - kernel crypto backend (no libcrypto.a; needs CRYPTO_USER_API_HASH=y)
+# - internal argon2, no blkid, no tokens
+#
+# Versions are literal below (source= and the two cd lines): keep cryptsetup
+# equal to the cryptsetup port and LVM2 equal to device-mapper. A security fix in either reaches the initramfs only by bumping release here.
+
+name=tinyrd
+version=0.1
+release=2
+depends=()
+makedeps=(pkgconf popt util-linux json-c)
+source=(tinyrd.c
+ https://www.kernel.org/pub/linux/utils/cryptsetup/v2.8/cryptsetup-2.8.8.tar.xz
+ https://sourceware.org/pub/lvm2/LVM2.2.03.35.tgz)
+
+sha256sums=(
+ "79dc8126afab289ff924f382face04ad18ae53d3257a7349dbef52d864c6b590"
+ "3acfa685f2dd7fcc832e0b77bc7093aa7da554a51ce8dafbb4138eaa854eee35"
+ "ebf28b3427535e2b5abd9991cd839b61622a0dbfb8c86df0f7af1f69dcaa8371"
+)
+
+build() {
+ top=$PWD
+ _p=$top/static # private prefix, never packaged
+ export CC=clang
+ export PKG_CONFIG_PATH=$_p/lib/pkgconfig:$PKG_CONFIG_PATH
+
+ cd $top/LVM2.2.03.35
+ ./configure --prefix=$_p --enable-static_link --disable-udev_sync \
+ --disable-selinux --disable-blkid_wiping --disable-readline --disable-nls \
+ --with-thin=none --with-cache=none --with-vdo=none \
+ --with-writecache=none --with-integrity=none
+ make -C libdm ioctl/libdevmapper.a
+ install -Dm644 libdm/libdevmapper.h $_p/include/libdevmapper.h
+ install -Dm644 libdm/ioctl/libdevmapper.a $_p/lib/libdevmapper.a
+ install -Dm644 libdm/libdevmapper.pc $_p/lib/pkgconfig/devmapper.pc
+
+ # configure insists on popt even with every tool disabled; only the lib is built
+ cd $top/cryptsetup-2.8.8
+ CPPFLAGS="-I$_p/include $CPPFLAGS" LDFLAGS="-L$_p/lib $LDFLAGS" \
+ ./configure --prefix=$_p --disable-shared --enable-static \
+ --with-crypto_backend=kernel \
+ --enable-internal-argon2 --enable-internal-sse-argon2 --disable-libargon2 \
+ --disable-blkid --disable-udev --disable-selinux \
+ --disable-external-tokens --disable-ssh-token --disable-hw-opal \
+ --disable-cryptsetup --disable-veritysetup --disable-integritysetup \
+ --disable-pwquality --disable-asciidoc --disable-nls
+ make libcryptsetup.la
+ make install-libLTLIBRARIES install-includeHEADERS install-pkgconfigDATA
+
+ cd $top
+ $CC $CFLAGS -I$_p/include -static -o tinyrd tinyrd.c \
+ -L$_p/lib $LDFLAGS $(pkg-config --static --libs libcryptsetup)
+ ./tinyrd -o tinyrd.img
+ install -Dm644 tinyrd.img $PKG/boot/tinyrd.img
+}
+
+signify() {
+ untrusted comment: public key
+ RWTZ9IduCSQ/mL8337TEUinPwT92xFEUpD92hkS7IxcOnzTt9QdpohT3
+}
+
+# vim: filetype=sh
diff --git a/opt/tinyrd/tinyrd.c b/opt/tinyrd/tinyrd.c
new file mode 100644
index 0000000..d89e385
--- /dev/null
+++ b/opt/tinyrd/tinyrd.c
@@ -0,0 +1,526 @@
+/*
+ * tinyrd — the rawnix initramfs.
+ *
+ * One file, one static binary, two roles:
+ *
+ * tinyrd -o FILE write a newc cpio whose /init is this binary
+ * /init (pid 1) optionally unlock a LUKS root, mount root, switch_root
+ *
+ * The archive carries no kernel modules, so one image serves every kernel
+ * and nothing has to be regenerated per version. The price is that the
+ * unlock path must be built in:
+ *
+ * BLK_DEV_INITRD DEVTMPFS BLK_DEV_DM DM_CRYPT CRYPTO_XTS
+ * CRYPTO_AES_NI_INTEL CRYPTO_SHA256 CRYPTO_HMAC
+ * CRYPTO_USER_API_SKCIPHER CRYPTO_USER_API_HASH, the root filesystem,
+ * and whatever the keyboard needs (i8042/atkbd, or xhci + usbhid).
+ *
+ * libcryptsetup here uses the kernel crypto backend: every hash goes
+ * through AF_ALG. Without CRYPTO_USER_API_HASH the header will not load
+ * ("Cannot initialize crypto backend").
+ *
+ * Kernel command line:
+ *
+ * root=/dev/... | UUID=<fs uuid>
+ * required. A device path is what the
+ * kernel would use; UUID= survives the
+ * disk being renamed, which /dev/sda2
+ * does not — adding a drive, moving a
+ * cable or booting the same image in a
+ * VM is enough
+ * cryptroot=UUID=<luks-uuid> optional; root= must then be
+ * /dev/mapper/<name>, and <name> is the
+ * mapping created
+ * rootfstype= default ext4
+ * rootflags= passed to mount(2) as data
+ * ro | rw default ro, last one wins
+ * init= default /sbin/init
+ *
+ * Without cryptroot= it mounts root= directly, so the same image boots an
+ * unencrypted system.
+ *
+ * After the switch the new root sees exactly what the kernel leaves it
+ * when there is no initramfs: devtmpfs on /dev, root mounted ro, nothing
+ * else. Stage 1 needs no initramfs awareness.
+ *
+ * Build: see the port. It links a private static libcryptsetup and
+ * libdevmapper; static is mandatory and the builder refuses otherwise.
+ */
+
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+
+#include <libcryptsetup.h>
+
+#define NEWROOT "/newroot"
+#define WAIT_SECS 30
+
+/* ── builder ─────────────────────────────────────────────────────── */
+
+/* Parents before children: the kernel's unpacker does not create them. */
+static const struct {
+ const char *name;
+ unsigned mode, rmaj, rmin;
+} skel[] = {
+ { "dev", S_IFDIR | 0755, 0, 0 },
+ { "dev/console", S_IFCHR | 0600, 5, 1 }, /* kernel opens it before /init */
+ { "newroot", S_IFDIR | 0755, 0, 0 },
+ { "proc", S_IFDIR | 0555, 0, 0 },
+ { "run", S_IFDIR | 0755, 0, 0 },
+ { "sys", S_IFDIR | 0555, 0, 0 },
+};
+
+static void pad4(FILE *f, size_t n)
+{
+ static const char z[3];
+ fwrite(z, 1, (4 - n % 4) % 4, f);
+}
+
+/* uid/gid 0, mtime 0: the image is reproducible and needs no root to build */
+static void cpio_entry(FILE *f, unsigned ino, unsigned mode, unsigned rmaj,
+ unsigned rmin, const char *name, const void *data,
+ unsigned size)
+{
+ unsigned nsz = strlen(name) + 1;
+
+ fprintf(f, "070701%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X",
+ ino, mode, 0u, 0u, S_ISDIR(mode) ? 2u : 1u, 0u, size,
+ 0u, 0u, rmaj, rmin, nsz, 0u);
+ fwrite(name, 1, nsz, f);
+ pad4(f, 110 + nsz);
+ if (size) {
+ fwrite(data, 1, size, f);
+ pad4(f, size);
+ }
+}
+
+static unsigned char *slurp(const char *path, unsigned *size)
+{
+ unsigned char *buf = NULL;
+ struct stat st;
+ size_t off = 0;
+ ssize_t r;
+ int fd;
+
+ if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0)
+ return NULL;
+ if (fstat(fd, &st) == 0 && st.st_size > 0 && (buf = malloc(st.st_size)))
+ while (off < (size_t)st.st_size) {
+ if ((r = read(fd, buf + off, st.st_size - off)) <= 0) {
+ free(buf);
+ buf = NULL;
+ break;
+ }
+ off += r;
+ }
+ close(fd);
+ *size = off;
+ return buf;
+}
+
+/*
+ * A dynamic /init fails with ENOENT (its interpreter is missing), and the
+ * kernel reports that as a panic nobody connects to the link flags.
+ */
+static int is_static(const unsigned char *b, unsigned n)
+{
+ Elf64_Ehdr eh;
+ Elf64_Phdr ph;
+ unsigned i;
+
+ if (n < sizeof eh || memcmp(b, ELFMAG, SELFMAG) || b[EI_CLASS] != ELFCLASS64)
+ return 0;
+ memcpy(&eh, b, sizeof eh);
+ for (i = 0; i < eh.e_phnum; i++) {
+ size_t off = eh.e_phoff + (size_t)i * eh.e_phentsize;
+ if (off + sizeof ph > n)
+ return 0;
+ memcpy(&ph, b + off, sizeof ph);
+ if (ph.p_type == PT_INTERP)
+ return 0;
+ }
+ return 1;
+}
+
+static int build(const char *out, const char *argv0)
+{
+ unsigned char *self;
+ unsigned size, i, ino = 1;
+ char tmp[4096];
+ FILE *f;
+ int bad;
+
+ /* argv[0] covers a build sandbox that hides /proc */
+ if (!(self = slurp("/proc/self/exe", &size)) && !(self = slurp(argv0, &size))) {
+ perror("tinyrd: cannot read own executable");
+ return 1;
+ }
+ if (!is_static(self, size)) {
+ fputs("tinyrd: not a static binary; the initramfs has no libc\n", stderr);
+ return 1;
+ }
+ snprintf(tmp, sizeof tmp, "%s.tmp", out);
+ if (!(f = fopen(tmp, "wb"))) {
+ perror(tmp);
+ return 1;
+ }
+ for (i = 0; i < sizeof skel / sizeof *skel; i++)
+ cpio_entry(f, ino++, skel[i].mode, skel[i].rmaj, skel[i].rmin,
+ skel[i].name, NULL, 0);
+ cpio_entry(f, ino++, S_IFREG | 0755, 0, 0, "init", self, size);
+ cpio_entry(f, 0, 0, 0, 0, "TRAILER!!!", NULL, 0);
+
+ bad = ferror(f);
+ bad |= fclose(f) != 0;
+ if (bad || rename(tmp, out) < 0) {
+ perror(out);
+ unlink(tmp);
+ return 1;
+ }
+ return 0;
+}
+
+/* ── init ────────────────────────────────────────────────────────── */
+
+static char cmdbuf[4096];
+static char *tok[256];
+static int ntok;
+static char founddev[288];
+
+static void say(const char *fmt, ...)
+{
+ va_list ap;
+
+ fputs("tinyrd: ", stderr);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+/* pid 1 must never return: that is a panic with the message scrolled away */
+static void fatal(const char *fmt, ...)
+{
+ va_list ap;
+ char c;
+
+ fputs("tinyrd: ", stderr);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputs("\ntinyrd: press Enter to reboot\n", stderr);
+ if (read(0, &c, 1) == 1) {
+ sync();
+ reboot(RB_AUTOBOOT);
+ }
+ for (;;)
+ pause();
+}
+
+static void read_cmdline(void)
+{
+ ssize_t n = -1;
+ char *s;
+ int fd;
+
+ if ((fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC)) >= 0) {
+ n = read(fd, cmdbuf, sizeof cmdbuf - 1);
+ close(fd);
+ }
+ if (n < 0)
+ fatal("cannot read /proc/cmdline");
+ cmdbuf[n] = '\0';
+ for (s = strtok(cmdbuf, " \t\n"); s && ntok < 256; s = strtok(NULL, " \t\n"))
+ tok[ntok++] = s;
+}
+
+/* last key=value wins, as in the kernel */
+static const char *param(const char *key)
+{
+ size_t k = strlen(key);
+ const char *v = NULL;
+ int i;
+
+ for (i = 0; i < ntok; i++)
+ if (!strncmp(tok[i], key, k) && tok[i][k] == '=')
+ v = tok[i] + k + 1;
+ return v;
+}
+
+static int want_rw(void)
+{
+ int i, rw = 0;
+
+ for (i = 0; i < ntok; i++)
+ if (!strcmp(tok[i], "rw"))
+ rw = 1;
+ else if (!strcmp(tok[i], "ro"))
+ rw = 0;
+ return rw;
+}
+
+/*
+ * Filesystem UUIDs, so root= need not name a device. Each entry is a
+ * magic to confirm the type and the offset of the 16 raw bytes; there
+ * is no blkid here and no udev to have populated /dev/disk/by-uuid.
+ */
+static const struct {
+ off_t moff;
+ const char *magic;
+ size_t mlen;
+ off_t uoff;
+} fs_sigs[] = {
+ { 1080, "\x53\xEF", 2, 1128 }, /* ext2/3/4 */
+ { 0, "XFSB", 4, 32 }, /* xfs */
+ { 0x10040, "_BHRfS_M", 8, 0x10020 }, /* btrfs */
+};
+
+static int fs_uuid_is(const char *dev, const char *uuid)
+{
+ unsigned char m[8], u[16];
+ char got[37];
+ int fd, i, ok = 0;
+
+ if ((fd = open(dev, O_RDONLY | O_NONBLOCK | O_CLOEXEC)) < 0)
+ return 0;
+ for (i = 0; !ok && i < (int)(sizeof fs_sigs / sizeof *fs_sigs); i++) {
+ if (pread(fd, m, fs_sigs[i].mlen, fs_sigs[i].moff) != (ssize_t)fs_sigs[i].mlen ||
+ memcmp(m, fs_sigs[i].magic, fs_sigs[i].mlen) ||
+ pread(fd, u, sizeof u, fs_sigs[i].uoff) != (ssize_t)sizeof u)
+ continue;
+ snprintf(got, sizeof got,
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+ "%02x%02x%02x%02x%02x%02x",
+ u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7],
+ u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]);
+ ok = !strcasecmp(got, uuid);
+ }
+ close(fd);
+ return ok;
+}
+
+/* LUKS1 and LUKS2 both keep a 40-byte NUL-padded uuid at offset 168 */
+static int luks_uuid_is(const char *dev, const char *uuid)
+{
+ unsigned char h[208];
+ int fd, ok = 0;
+
+ if ((fd = open(dev, O_RDONLY | O_NONBLOCK | O_CLOEXEC)) < 0)
+ return 0;
+ if (pread(fd, h, sizeof h, 0) == (ssize_t)sizeof h &&
+ !memcmp(h, "LUKS\xba\xbe", 6)) {
+ h[207] = '\0';
+ ok = !strcasecmp((char *)h + 168, uuid);
+ }
+ close(fd);
+ return ok;
+}
+
+/* one scan for both, since the only difference is what is compared */
+static int probe_uuid(const char *uuid, int luks)
+{
+ DIR *d = opendir("/sys/class/block");
+ struct dirent *e;
+ int found = 0;
+
+ if (!d)
+ return 0;
+ while (!found && (e = readdir(d))) {
+ if (e->d_name[0] == '.')
+ continue;
+ snprintf(founddev, sizeof founddev, "/dev/%s", e->d_name);
+ found = luks ? luks_uuid_is(founddev, uuid)
+ : fs_uuid_is(founddev, uuid);
+ }
+ closedir(d);
+ return found;
+}
+
+static int probe_luks(const char *uuid) { return probe_uuid(uuid, 1); }
+static int probe_fs(const char *uuid) { return probe_uuid(uuid, 0); }
+
+static int probe_node(const char *path)
+{
+ struct stat st;
+
+ return stat(path, &st) == 0 && S_ISBLK(st.st_mode);
+}
+
+/* nvme and usb probe asynchronously; /init can start before the disk exists */
+static void wait_for(int (*probe)(const char *), const char *arg, const char *what)
+{
+ struct timespec t = { 0, 100 * 1000 * 1000 };
+ int i;
+
+ for (i = 0; !probe(arg); i++) {
+ if (i == 10)
+ say("waiting for %s", what);
+ if (i == WAIT_SECS * 10)
+ fatal("%s did not appear within %ds", what, WAIT_SECS);
+ nanosleep(&t, NULL);
+ }
+}
+
+static ssize_t ask(char *buf, size_t n)
+{
+ struct termios old, t;
+ int tty = tcgetattr(0, &old) == 0;
+ ssize_t r;
+
+ if (tty) {
+ t = old;
+ t.c_lflag &= ~ECHO;
+ t.c_lflag |= ECHONL;
+ tcsetattr(0, TCSAFLUSH, &t);
+ }
+ fputs("Passphrase: ", stderr);
+ r = read(0, buf, n);
+ if (tty)
+ tcsetattr(0, TCSAFLUSH, &old);
+ if (r <= 0)
+ return -1;
+ if (buf[r - 1] == '\n')
+ r--;
+ return r;
+}
+
+static void cs_log(int level, const char *msg, void *usrptr)
+{
+ (void)usrptr;
+ if (level == CRYPT_LOG_ERROR)
+ fprintf(stderr, "tinyrd: %s", msg);
+}
+
+static void unlock(const char *dev, const char *name)
+{
+ struct crypt_device *cd = NULL;
+ char pass[1024];
+ ssize_t n;
+ int r;
+
+ /* no udev here: libdevmapper must create /dev/mapper/<name> itself */
+ setenv("DM_DISABLE_UDEV", "1", 1);
+ crypt_set_log_callback(NULL, cs_log, NULL);
+
+ if ((r = crypt_init(&cd, dev)) < 0 || (r = crypt_load(cd, CRYPT_LUKS, NULL)) < 0)
+ fatal("%s: cannot load LUKS header: %s", dev, strerror(-r));
+
+ /* LUKS2 persistent flags (allow-discards, no_*_workqueue) apply here */
+ for (;;) {
+ if ((n = ask(pass, sizeof pass)) < 0)
+ fatal("cannot read passphrase from console");
+ r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, pass, n, 0);
+ explicit_bzero(pass, sizeof pass);
+ if (r >= 0)
+ break;
+ if (r != -EPERM)
+ fatal("activating %s on %s: %s", name, dev, strerror(-r));
+ say("no key available with this passphrase");
+ }
+ crypt_free(cd);
+ unsetenv("DM_DISABLE_UDEV"); /* would leak into stage 1's environment */
+}
+
+static int init_main(char **argv)
+{
+ const char *root, *cr, *fstype, *init;
+ char path[4096];
+ int fd;
+
+ if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) < 0)
+ fatal("mount /dev: %s", strerror(errno));
+ if (fcntl(0, F_GETFD) < 0 && (fd = open("/dev/console", O_RDWR)) >= 0) {
+ dup2(fd, 0), dup2(fd, 1), dup2(fd, 2);
+ if (fd > 2)
+ close(fd);
+ }
+ if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0 ||
+ mount("sysfs", "/sys", "sysfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0 ||
+ mount("tmpfs", "/run", "tmpfs", MS_NOSUID | MS_NODEV, "mode=0755") < 0)
+ fatal("mount proc/sys/run: %s", strerror(errno));
+
+ read_cmdline();
+
+ root = param("root");
+ if (!root || (strncmp(root, "/dev/", 5) && strncasecmp(root, "UUID=", 5)))
+ fatal("root= must be a /dev path or UUID=<fs uuid>, got %s",
+ root ? root : "nothing");
+
+ if ((cr = param("cryptroot"))) {
+ if (strncasecmp(cr, "UUID=", 5) || !cr[5])
+ fatal("cryptroot= takes UUID=<luks uuid>");
+ if (strncmp(root, "/dev/mapper/", 12) || !root[12] || strchr(root + 12, '/'))
+ fatal("cryptroot= needs root=/dev/mapper/<name>");
+ wait_for(probe_luks, cr + 5, cr);
+ unlock(founddev, root + 12);
+ } else if (!strncasecmp(root, "UUID=", 5)) {
+ if (!root[5])
+ fatal("root=UUID= needs a uuid");
+ wait_for(probe_fs, root + 5, root);
+ root = founddev;
+ } else {
+ wait_for(probe_node, root, root);
+ }
+
+ fstype = param("rootfstype");
+ if (!fstype)
+ fstype = "ext4";
+ if (mount(root, NEWROOT, fstype, want_rw() ? 0 : MS_RDONLY, param("rootflags")) < 0)
+ fatal("mount %s (%s): %s", root, fstype, strerror(errno));
+
+ init = param("init");
+ if (!init)
+ init = "/sbin/init";
+ snprintf(path, sizeof path, NEWROOT "%s", init);
+ if (access(path, X_OK) < 0)
+ fatal("%s: %s on %s", init, strerror(errno), root);
+
+ /* hand over /dev, drop the rest; the new root mounts its own */
+ if (mount("/dev", NEWROOT "/dev", NULL, MS_MOVE, NULL) < 0)
+ fatal("move /dev: %s", strerror(errno));
+ umount2("/run", MNT_DETACH);
+ umount2("/sys", MNT_DETACH);
+ umount2("/proc", MNT_DETACH);
+
+ /* free the rootfs; we wrote the archive, so we know what is in it */
+ unlink("/init");
+ unlink("/dev/console");
+ rmdir("/dev");
+ rmdir("/proc");
+ rmdir("/sys");
+ rmdir("/run");
+
+ if (chdir(NEWROOT) < 0 || mount(".", "/", NULL, MS_MOVE, NULL) < 0 ||
+ chroot(".") < 0 || chdir("/") < 0)
+ fatal("switch_root: %s", strerror(errno));
+
+ /* same argv the kernel would have given it: bare words like "single" */
+ argv[0] = (char *)init;
+ execv(init, argv);
+ fatal("exec %s: %s", init, strerror(errno));
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ if (getpid() == 1)
+ return init_main(argv);
+ if (argc == 3 && !strcmp(argv[1], "-o"))
+ return build(argv[2], argv[0]);
+ fputs("usage: tinyrd -o FILE\n", stderr);
+ return 2;
+}