diff --git a/scripts/lib/mkcpio.py b/scripts/lib/mkcpio.py new file mode 100644 index 00000000..a14360a4 --- /dev/null +++ b/scripts/lib/mkcpio.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Write a newc-format cpio archive of a directory tree to stdout. + +Used by scripts/build-linux-bootstrap.sh to pack the Nucleic Linux bootstrap initramfs without a +`cpio` binary (which macOS lacks by default). Emits the SVR4/newc format the Linux kernel's initramfs +loader expects; handles regular files, directories, and symlinks. Byte-identical on Linux and macOS. +""" +import os +import stat +import sys + + +def field(n: int) -> bytes: + return b"%08X" % (n & 0xFFFFFFFF) + + +def emit(out, name: bytes, st, data: bytes): + # newc header: magic + 13 eight-hex fields, then name (NUL-terminated, 4-byte padded), then data + # (4-byte padded). + hdr = b"070701" + hdr += field(st.st_ino if st else 0) + hdr += field(st.st_mode if st else 0) + hdr += field(0) # uid — normalize to root so the initramfs is reproducible + hdr += field(0) # gid + hdr += field(st.st_nlink if st else 1) + hdr += field(0) # mtime — normalize for reproducibility + hdr += field(len(data)) + hdr += field(0) * 4 # devmajor/minor, rdevmajor/minor + namebytes = name + b"\x00" + hdr += field(len(namebytes)) + hdr += field(0) # check (unused for newc) + out.write(hdr) + out.write(namebytes) + pad = (-(len(hdr) + len(namebytes))) % 4 + out.write(b"\x00" * pad) + out.write(data) + out.write(b"\x00" * ((-len(data)) % 4)) + + +def main(): + rootdir = sys.argv[1] + out = sys.stdout.buffer + entries = [] + for dirpath, dirnames, filenames in os.walk(rootdir): + for d in sorted(dirnames): + entries.append(os.path.join(dirpath, d)) + for f in sorted(filenames): + entries.append(os.path.join(dirpath, f)) + for path in entries: + rel = os.path.relpath(path, rootdir).encode() + st = os.lstat(path) + if stat.S_ISLNK(st.st_mode): + data = os.readlink(path).encode() + elif stat.S_ISDIR(st.st_mode): + data = b"" + else: + with open(path, "rb") as fh: + data = fh.read() + emit(out, rel, st, data) + # Trailer. + class Z: + st_ino = 0 + st_mode = 0 + st_nlink = 1 + emit(out, b"TRAILER!!!", Z(), b"") + + +if __name__ == "__main__": + main()