#!/usr/bin/env bash # Build busybox (one program that contains ~300 small Unix tools, called # "applets") for Foundry OS: # 1. unpack the cached tarball (with Python: no bzip2 program needed), # 2. use the committed configuration ports/busybox/busybox.config (see # ports/busybox/README.md for what is switched off and why), # 3. run busybox's Makefile with our compiler: # make CC=toolchain/foundry-cc LD=ld HOSTCC=gcc # The result is ONE static-PIE program. It is installed as # /usr/lib/busybox/busybox on the ext2 root, next to one symlink per applet # (/usr/lib/busybox/awk -> busybox, ...), all OFF the default PATH, so the # Foundry OS Rust tools stay the default. Run an applet as # busybox awk ... (with /usr/lib/busybox/busybox spelled out) # or as /usr/lib/busybox/awk ... # # Usage: ports/busybox/build.sh CACHE_DIR BUILD_DIR OUT_DIR # OUT_DIR/busybox is the program, OUT_DIR/applets.txt lists the applet # names, OUT_DIR/LICENSE is busybox's licence (GPL v2). # # Host needs (besides gcc and the musl sysroot): python3, and the Linux # kernel's userspace headers in /usr/include/linux and /usr/include/asm # (Debian/Ubuntu package linux-libc-dev). busybox includes a few of them # (, ...), and foundry-cc only searches the musl sysroot, so we # copy them into BUILD_DIR/kernel-headers and add that with -isystem. set -euo pipefail HERE=$(cd "$(dirname "$0")" && pwd) ROOT=$(cd "$HERE/../.." && pwd) CACHE=$(realpath "$1") BUILD=$(realpath -m "$2") OUTDIR=$(realpath -m "$3") read -r _ file < "$HERE/busybox.sha256" rm -rf "$BUILD" && mkdir -p "$BUILD" python3 "$HERE/untar.py" "$CACHE/$file" "$BUILD" src=$BUILD/${file%.tar.bz2} # Linux userspace headers. -L copies the files that symlinks point at. kh=$BUILD/kernel-headers asm=/usr/include/$(gcc -print-multiarch 2>/dev/null)/asm [ -d "$asm" ] || asm=/usr/include/asm for d in /usr/include/linux /usr/include/asm-generic "$asm"; do [ -d "$d" ] || { echo "[busybox] missing $d: install linux-libc-dev"; exit 1; } done mkdir -p "$kh" cp -rL /usr/include/linux /usr/include/asm-generic "$kh/" cp -rL "$asm" "$kh/asm" [ -d /usr/include/mtd ] && cp -rL /usr/include/mtd "$kh/" # busybox's build pipes its config through `bzip2 -1` (for `busybox # --show-config`-style data). No bzip2 on the host? Use a tiny Python one. if ! command -v bzip2 > /dev/null; then mkdir -p "$BUILD/hostbin" cp "$HERE/bzip2-shim.py" "$BUILD/hostbin/bzip2" chmod +x "$BUILD/hostbin/bzip2" export PATH=$BUILD/hostbin:$PATH fi cd "$src" cp "$HERE/busybox.config" .config # oldconfig fills in nothing new if busybox.config matches this version; # if it had to ask anything, the committed file is stale. { yes "" || true; } | make oldconfig > "$BUILD/oldconfig.log" 2>&1 if ! diff -q <(grep '^CONFIG_\|is not set' "$HERE/busybox.config") \ <(grep '^CONFIG_\|is not set' .config) > /dev/null; then echo "[busybox] busybox.config is out of date for this busybox version:" echo " rerun ports/busybox/mkconfig.sh and commit the result"; exit 1 fi # LD=ld: busybox first links each directory with `ld -r`, which can't be # combined with foundry-cc's -static-pie. The final link still uses CC. make -j"$(nproc)" CC="$ROOT/toolchain/foundry-cc" LD=ld HOSTCC=gcc \ EXTRA_CFLAGS="-isystem $kh" busybox busybox.links > "$BUILD/make.log" 2>&1 || { tail -30 "$BUILD/make.log"; exit 1; } if readelf -l busybox | grep -q INTERP; then echo "[busybox] busybox has a PT_INTERP: not a static-PIE"; exit 1 fi rm -rf "$OUTDIR" && mkdir -p "$OUTDIR" cp busybox "$OUTDIR/busybox" strip --strip-debug "$OUTDIR/busybox" sed 's,.*/,,' busybox.links | sort > "$OUTDIR/applets.txt" # /bin/ls -> ls cp LICENSE "$OUTDIR/LICENSE" echo "[busybox] built $OUTDIR/busybox: $(wc -l < "$OUTDIR/applets.txt") applets, $(stat -c %s "$OUTDIR/busybox") bytes, static-PIE"