From e0a85813448d45755f6f370189fb61842dd59a7c Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 26 Sep 2026 00:58:34 +1000 Subject: [PATCH 01/73] Add omarchy-hw-platform to tell Apple Silicon and Qualcomm machines apart aarch64 covers Apple Silicon Macs, Snapdragon laptops, Raspberry Pis and VMs, so the CPU architecture can't pick platform setup. omarchy-hw-platform prints apple-silicon, qualcomm, generic-aarch64 or generic from the vendor prefixes in the device tree's root compatible (/proc/device-tree, then /sys/firmware/devicetree/base) and the CPU, and fails when they contradict each other. Its qualcomm answer uses the same evidence as the dragon branch's omarchy-hw-qualcomm-soc, and the fixtures check the two agree. omarchy-hw-apple-silicon is the Apple predicate on top of it. An image built away from its hardware names its target in a root-owned manifest, /var/lib/omarchy/image/target (format=1, platform=). While the root is being built rather than booted, the detector answers from it and never reads the build host's device tree; a booted system always answers from its hardware. As root it restarts in an empty environment, so no variable picks the platform for privileged setup. Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com> Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com> --- bin/omarchy-hw-apple-silicon | 17 + bin/omarchy-hw-platform | 189 ++++++++++ docs/file-layout.md | 4 + test/shell.d/base-test.sh | 39 ++ test/shell.d/hw-platform-test.sh | 205 +++++++++++ test/shell.d/image-target-platform-test.sh | 410 +++++++++++++++++++++ 6 files changed, 864 insertions(+) create mode 100755 bin/omarchy-hw-apple-silicon create mode 100755 bin/omarchy-hw-platform create mode 100644 test/shell.d/hw-platform-test.sh create mode 100644 test/shell.d/image-target-platform-test.sh diff --git a/bin/omarchy-hw-apple-silicon b/bin/omarchy-hw-apple-silicon new file mode 100755 index 00000000000..d10777e55c2 --- /dev/null +++ b/bin/omarchy-hw-apple-silicon @@ -0,0 +1,17 @@ +#!/bin/bash -p + +# omarchy:summary=Detect whether the computer is an Apple Silicon Mac. + +# Callers run this by absolute path, from systemd and from the shell, so use the +# detector shipped beside it rather than whichever one PATH finds. -p keeps a +# root caller's exported functions out, so a launch without it is refused, and +# nothing here searches PATH. +if [[ $- != *p* ]]; then + echo "Refusing an unsafe Bash startup." >&2 + exit 126 +fi + +dir=${BASH_SOURCE[0]%/*} +[[ $dir != "${BASH_SOURCE[0]}" ]] || dir=. +platform=$("$dir/omarchy-hw-platform") || exit 1 +[[ $platform == "apple-silicon" ]] diff --git a/bin/omarchy-hw-platform b/bin/omarchy-hw-platform new file mode 100755 index 00000000000..d3885912bb8 --- /dev/null +++ b/bin/omarchy-hw-platform @@ -0,0 +1,189 @@ +#!/bin/bash -p + +# omarchy:summary=Print the hardware platform: apple-silicon, qualcomm, generic-aarch64 or generic. + +# ARM laptops have no DMI, so the board identity is the root node of the device +# tree the firmware hands the kernel: "apple," from Asahi's m1n1 on every +# Apple Silicon Mac, "qcom," on Snapdragon laptops. Each compatible string +# is a NUL-separated list, and only a token's vendor prefix counts. +# +# An image is built away from the machine it will run on, so while this root is +# being built rather than booted, the image-target manifest the builder wrote +# names the platform and the build host's device tree is never read. +# +# Fixture roots let unprivileged tests fake the device tree, PID 1 and the image +# root. Naming any of them makes the whole view a fixture: nothing live is read, +# not even the device tree when only the image root is named. A privileged +# caller must not let its environment pick the platform at all. +# Privileged mode (-p) keeps exported functions and BASH_ENV out of this shell, +# so a launch without it is refused. Root then starts over in an empty +# environment and reads only the live paths with a fixed PATH. +if [[ $- != *p* ]]; then + echo "Refusing an unsafe Bash startup." >&2 + exit 126 +fi + +if (( EUID == 0 )) && [[ ${1:-} != --clean-environment ]]; then + exec /usr/bin/env -i PATH=/usr/bin /bin/bash -p -- "${BASH_SOURCE[0]}" --clean-environment "$@" +fi + +if (( EUID == 0 )); then + export PATH=/usr/bin + fixtures="" +else + fixtures=${OMARCHY_PROC_ROOT:-}${OMARCHY_SYS_ROOT:-}${OMARCHY_IMAGE_ROOT:-} +fi + +if [[ -z $fixtures ]]; then + proc=/proc + sources=(/proc/device-tree/compatible /sys/firmware/devicetree/base/compatible) + image_root="" + image_dir=/var/lib/omarchy/image + image_owner=0 +else + proc=${OMARCHY_PROC_ROOT:-} + sources=() + [[ -z ${OMARCHY_PROC_ROOT:-} ]] || sources+=("$OMARCHY_PROC_ROOT/device-tree/compatible") + [[ -z ${OMARCHY_SYS_ROOT:-} ]] || sources+=("$OMARCHY_SYS_ROOT/firmware/devicetree/base/compatible") + image_root=${OMARCHY_IMAGE_ROOT:-} + image_dir=${image_root:+$image_root/var/lib/omarchy/image} + image_owner=$EUID +fi +manifest=$image_dir/target + +contradiction() { + echo "Error: contradictory platform identity: $1" >&2 + exit 1 +} + +if ! machine=$(uname -m) || [[ -z $machine ]]; then + echo "Error: cannot read the CPU architecture" >&2 + exit 1 +fi + +# A booted system is one systemd runs as PID 1 from this root. An image build +# is told apart by what it shows: no /run/systemd/system (a root with a /run of +# its own), PID 1's root being another (a chroot on the build host, even with +# the host's /run bound in, as arch-chroot does), or PID 1 being something other +# than systemd (a PID namespace whose PID 1 is the build). What cannot be seen, +# such as PID 1's root to a normal user or /proc/1 behind hidepid, never makes a +# build: a booted system always answers from its hardware, so a build runs as +# root. A caller in a private PID namespace looks like a build too, so nothing +# that asks the detector runs with PrivatePIDs=. +booted() { + local comm + + [[ -d $image_root/run/systemd/system ]] || return 1 + if [[ -n $proc && -e $proc/1/root ]]; then + [[ $proc/1/root -ef $image_root/ ]] || return 1 + fi + if [[ -n $proc && -r $proc/1/comm ]]; then + read -r comm <"$proc/1/comm" && [[ $comm == "systemd" ]] || return 1 + fi +} + +# Owned by root (the test's own user for a fixture), not a symlink, and not +# writable by anyone else. +trusted() { + local owner mode + + [[ -e $1 && ! -L $1 ]] || return 1 + read -r owner mode < <(stat -c '%u %a' -- "$1") || return 1 + [[ $owner =~ ^[0-9]+$ && $mode =~ ^[0-7]+$ ]] && (( owner == image_owner && (8#$mode & 8#022) == 0 )) +} + +# The manifest: format=1, platform= one of the four names, comments and unknown +# keys ignored, and any other line refused. The detector reads it itself, so it +# stands alone wherever a package ships a copy of it. +image_platform() { + local line key value format="" platform="" + + if [[ ! -d $image_dir || ! -f $manifest ]] || ! trusted "$image_dir" || ! trusted "$manifest"; then + echo "Error: the image manifest $manifest is not a root-owned regular file" >&2 + return 1 + fi + + while IFS= read -r line || [[ -n $line ]]; do + [[ -n $line && $line != \#* ]] || continue + if [[ $line != *=* ]]; then + echo "Error: the image manifest $manifest is malformed: $line" >&2 + return 1 + fi + key=${line%%=*} + value=${line#*=} + case $key in + format) format=$value ;; + platform) platform=$value ;; + esac + done <"$manifest" + + if [[ $format != "1" ]]; then + echo "Error: the image manifest $manifest is not format=1" >&2 + return 1 + fi + if [[ ! $platform =~ ^(apple-silicon|qualcomm|generic-aarch64|generic)$ ]]; then + echo "Error: the image manifest $manifest names no known platform: ${platform:-none}" >&2 + return 1 + fi + + echo "$platform" +} + +if [[ -n $image_dir ]] && [[ -e $manifest || -L $manifest ]] && ! booted; then + platform=$(image_platform) || exit 1 + if [[ $platform == "generic" ]]; then + [[ $machine != "aarch64" ]] || contradiction "$manifest names $platform hardware but the CPU is $machine" + else + [[ $machine == "aarch64" ]] || contradiction "$manifest names $platform hardware but the CPU is $machine" + fi + echo "$platform" + exit 0 +fi + +vendor="" +vendor_source="" +for source in "${sources[@]}"; do + [[ -r $source ]] || continue + + apple=0 + qcom=0 + mapfile -d '' -t tokens <"$source" + for token in "${tokens[@]}"; do + [[ $token == "apple,"* ]] && apple=1 + [[ $token == "qcom,"* ]] && qcom=1 + done + + if (( apple && qcom )); then + contradiction "$source names both Apple and Qualcomm hardware" + elif (( apple )); then + found=apple + elif (( qcom )); then + found=qcom + else + found=none + fi + + if [[ -n $vendor && $vendor != "$found" ]]; then + contradiction "$vendor_source says $vendor but $source says $found" + fi + vendor=$found + vendor_source=$source +done + +case $vendor in + apple | qcom) + [[ $machine == "aarch64" ]] || contradiction "$vendor_source names $vendor hardware but the CPU is $machine" + if [[ $vendor == "apple" ]]; then + echo apple-silicon + else + echo qualcomm + fi + ;; + *) + if [[ $machine == "aarch64" ]]; then + echo generic-aarch64 + else + echo generic + fi + ;; +esac diff --git a/docs/file-layout.md b/docs/file-layout.md index 5caaba5669d..bed2a105739 100644 --- a/docs/file-layout.md +++ b/docs/file-layout.md @@ -317,6 +317,10 @@ finalization. It sources: Logging goes to `/var/log/omarchy-install.log` via `install/helpers/logging.sh`. +Platform-specific setup asks `omarchy-hw-platform`, which prints `apple-silicon`, `qualcomm`, `generic-aarch64` or `generic`. It reads the vendor prefix of each token in the device tree's root `compatible` (`apple,` or `qcom,`, from `/proc/device-tree` or `/sys/firmware/devicetree/base`) and the CPU architecture, and fails when they contradict each other. Gate a platform on it or on a predicate built on it, such as `omarchy-hw-apple-silicon`, never on `uname -m` alone: `test/shell.d/architecture-gates-test.sh` fails on an architecture check that isn't a reviewed ABI, binary or repository exception. + +An image built away from the machine it will run on names its target in a root-owned manifest, `/var/lib/omarchy/image/target` (`format=1`, `platform=`, unknown keys ignored). While the root is being built rather than booted, the detector answers from the manifest and never reads the build host's device tree. The root counts as built when it shows it: no `/run/systemd/system`, PID 1's root is another one (a chroot), or PID 1 is not systemd (a PID namespace). A booted system always answers from its hardware, even with a manifest left behind, so no unit that asks the detector may use `PrivatePIDs=`. As root the detector restarts in an empty environment and ignores the fixture variables its tests use. + The package lists the ISO pacstraps live at `install/omarchy-base.packages` and `install/omarchy-other.packages`; the ISO builder also reads them when constructing its offline mirror. diff --git a/test/shell.d/base-test.sh b/test/shell.d/base-test.sh index 476b8f8316d..8be97eb975f 100644 --- a/test/shell.d/base-test.sh +++ b/test/shell.d/base-test.sh @@ -89,6 +89,45 @@ require_compositor() { exit 0 } +# Fake the hardware identity omarchy-hw-platform reads: a device tree under +# $1/proc and a uname in $1/bin reporting the platform's CPU. Run the code under +# test with OMARCHY_PROC_ROOT="$1/proc" and PATH="$1/bin:$ROOT/bin:...", so the +# real detector and its wrappers answer from the fixture. +fake_platform() { + local dir="$1" platform="$2" machine=aarch64 + local -a compatible=() + + case $platform in + apple-silicon) compatible=(apple,j416c apple,t6021 apple,arm-platform) ;; + qualcomm) compatible=(lenovo,yoga-slim7x qcom,x1e80100) ;; + generic-aarch64) compatible=(raspberrypi,5-model-b brcm,bcm2712) ;; + generic) machine=x86_64 ;; + *) fail "fake_platform knows the platform $platform" ;; + esac + + rm -rf "$dir/proc" + mkdir -p "$dir/proc" "$dir/bin" + if (( ${#compatible[@]} > 0 )); then + mkdir -p "$dir/proc/device-tree" + printf '%s\0' "${compatible[@]}" >"$dir/proc/device-tree/compatible" + fi + + cat >"$dir/bin/uname" </dev/null; then + live=$("${root_runner[@]}" "$detector") || fail "root detects the live platform" + [[ $live =~ ^(apple-silicon|qualcomm|generic-aarch64|generic)$ ]] || fail "root detects the live platform" "live: $live" + for platform in apple-silicon qualcomm generic-aarch64 generic; do + fixture="$test_tmp/$platform" + if [[ -f $fixture/proc/device-tree/compatible ]]; then + mkdir -p "$fixture/sys/firmware/devicetree/base" + cp "$fixture/proc/device-tree/compatible" "$fixture/sys/firmware/devicetree/base/compatible" + fi + overridden=$(OMARCHY_PROC_ROOT="$fixture/proc" OMARCHY_SYS_ROOT="$fixture/sys" PATH="$fixture/bin:$ROOT/bin:$PATH" \ + "${root_runner[@]}" "$detector") || fail "root detects the live platform with a $platform fixture in its environment" + [[ $overridden == "$live" ]] || + fail "root ignores a $platform fixture in its environment" "live: $live +with fixture: $overridden" + done + pass "root ignores fixture roots and PATH when detecting the platform" +else + skip "no unprivileged user namespace; skipping the root override probe" +fi + +# -p in the shebang is what keeps exported functions and BASH_ENV out, so an +# ordinary Bash launch with a decoy -p argument is refused before it reads anything. +for command in omarchy-hw-platform omarchy-hw-apple-silicon; do + if /usr/bin/bash "$ROOT/bin/$command" -p >/dev/null 2>"$test_tmp/error"; then + fail "$command refuses an ordinary Bash launch" + fi + grep -Fq "Refusing an unsafe Bash startup" "$test_tmp/error" || fail "$command explains the refusal" "$(cat "$test_tmp/error")" +done +pass "the detector and the Apple predicate refuse an ordinary Bash launch with a decoy -p" + +require_platform_fixtures "the platform fixtures" + +# The four platforms every caller is written against. +for platform in apple-silicon qualcomm generic-aarch64 generic; do + fixture="$test_tmp/$platform" + actual=$(OMARCHY_PROC_ROOT="$fixture/proc" PATH="$fixture/bin:$ROOT/bin:$PATH" "$detector") || + fail "the $platform fixture is detected" + [[ $actual == "$platform" ]] || fail "the $platform fixture is detected" "actual: $actual" + + apple_status=0 + OMARCHY_PROC_ROOT="$fixture/proc" PATH="$fixture/bin:$ROOT/bin:$PATH" "$ROOT/bin/omarchy-hw-apple-silicon" || apple_status=$? + if [[ $platform == "apple-silicon" ]]; then + (( apple_status == 0 )) || fail "the Apple predicate accepts the Apple fixture" + else + (( apple_status != 0 )) || fail "the Apple predicate rejects the $platform fixture" + fi + pass "the $platform fixture is detected and the Apple predicate agrees" +done + +# Systemd and the shell run the predicate by absolute path with whatever PATH +# they have; it must use the detector shipped beside it. +fixture="$test_tmp/apple-silicon" +OMARCHY_PROC_ROOT="$fixture/proc" PATH="$fixture/bin:/usr/bin:/bin" "$ROOT/bin/omarchy-hw-apple-silicon" || + fail "the Apple predicate finds its detector without Omarchy on PATH" +pass "the Apple predicate finds its detector without Omarchy on PATH" + +stub_bin="$test_tmp/bin" +mkdir -p "$stub_bin" +cat >"$stub_bin/uname" <<'SH' +#!/bin/bash +[[ ${1:-} == -m ]] && { printf '%s\n' "${TEST_ARCH:-aarch64}"; exit 0; } +exec /usr/bin/uname "$@" +SH +chmod +x "$stub_bin/uname" + +# $1 names a case directory holding proc/ and sys/ roots; $2 is the CPU. +detect() { + local case_dir="$test_tmp/cases/$1" + TEST_ARCH="${2:-aarch64}" OMARCHY_PROC_ROOT="$case_dir/proc" OMARCHY_SYS_ROOT="$case_dir/sys" \ + PATH="$stub_bin:$ROOT/bin:$PATH" "$detector" 2>"$test_tmp/error" +} + +# $1 case, $2 proc|sys, then the compatible tokens. +write_tree() { + local case_dir="$test_tmp/cases/$1" tree="$2" file + shift 2 + mkdir -p "$case_dir/proc" "$case_dir/sys" + if [[ $tree == "proc" ]]; then + file="$case_dir/proc/device-tree/compatible" + else + file="$case_dir/sys/firmware/devicetree/base/compatible" + fi + mkdir -p "$(dirname "$file")" + printf '%s\0' "$@" >"$file" +} + +expect() { + local name="$1" machine="$2" expected="$3" description="$4" actual + actual=$(detect "$name" "$machine") || fail "$description" "$(cat "$test_tmp/error")" + [[ $actual == "$expected" ]] || fail "$description" "expected: $expected +actual: $actual" +} + +expect_contradiction() { + local name="$1" machine="$2" description="$3" + if detect "$name" "$machine" >/dev/null; then + fail "$description" + fi + grep -Fq "contradictory platform identity" "$test_tmp/error" || fail "$description explains itself" "$(cat "$test_tmp/error")" +} + +# Real boards: the M1 Pro and M2 Max test Macs, an M1 Mac mini, and the +# Snapdragon X laptops Dragon supports. +write_tree m1-pro proc apple,j314s apple,t6000 apple,arm-platform +write_tree m2-max proc apple,j416c apple,t6021 apple,arm-platform +write_tree m1-mini proc apple,j274 apple,t8103 apple,arm-platform +write_tree yoga-slim7x proc lenovo,yoga-slim7x qcom,x1e80100 +write_tree xps13-9345 proc dell,xps13-9345 qcom,x1e80100 +write_tree t14s proc lenovo,thinkpad-t14s qcom,x1e78100 qcom,x1e80100 +for board in m1-pro m2-max m1-mini; do + expect "$board" aarch64 apple-silicon "the $board device tree is Apple Silicon" +done +for board in yoga-slim7x xps13-9345 t14s; do + expect "$board" aarch64 qualcomm "the $board device tree is Qualcomm" +done +pass "real Apple and Snapdragon device trees are recognised" + +write_tree qemu-virt proc linux,dummy-virt +write_tree raspberry-pi proc raspberrypi,5-model-b brcm,bcm2712 +mkdir -p "$test_tmp/cases/acpi/proc" "$test_tmp/cases/acpi/sys" +expect qemu-virt aarch64 generic-aarch64 "a QEMU virt board is generic aarch64" +expect raspberry-pi aarch64 generic-aarch64 "a Raspberry Pi is generic aarch64" +expect acpi aarch64 generic-aarch64 "an aarch64 machine without a device tree is generic aarch64" +expect acpi x86_64 generic "an x86 machine without a device tree is generic" +pass "unknown aarch64 and x86 machines are generic" + +# Only a token's vendor prefix identifies the board; the old detector matched +# "apple," anywhere in the file. +write_tree substring proc pineapple,board acmeqcom,soc vendor,apple,x vendor,qcom,y +expect substring aarch64 generic-aarch64 "vendor names inside other tokens do not identify the board" +pass "only a token's vendor prefix identifies the board" + +# /proc/device-tree is a link into sysfs; read sysfs when it is missing. +write_tree sysfs-qualcomm sys lenovo,yoga-slim7x qcom,x1e80100 +write_tree sysfs-apple sys apple,j314s apple,t6000 apple,arm-platform +expect sysfs-qualcomm aarch64 qualcomm "sysfs identifies Qualcomm without /proc/device-tree" +expect sysfs-apple aarch64 apple-silicon "sysfs identifies Apple Silicon without /proc/device-tree" +write_tree agree proc apple,j314s apple,t6000 apple,arm-platform +write_tree agree sys apple,j314s apple,t6000 apple,arm-platform +expect agree aarch64 apple-silicon "matching proc and sysfs trees agree" +pass "sysfs is the fallback for the device tree" + +write_tree both-vendors proc apple,j314s qcom,x1e80100 +expect_contradiction both-vendors aarch64 "a device tree naming Apple and Qualcomm fails" +write_tree sources-disagree proc apple,j314s apple,t6000 apple,arm-platform +write_tree sources-disagree sys lenovo,yoga-slim7x qcom,x1e80100 +expect_contradiction sources-disagree aarch64 "proc and sysfs naming different vendors fails" +write_tree vendor-vs-none proc apple,j314s apple,t6000 apple,arm-platform +write_tree vendor-vs-none sys linux,dummy-virt +expect_contradiction vendor-vs-none aarch64 "proc naming Apple while sysfs names nobody fails" +expect_contradiction m1-pro x86_64 "an Apple device tree on an x86 CPU fails" +expect_contradiction yoga-slim7x x86_64 "a Qualcomm device tree on an x86 CPU fails" +if TEST_ARCH=x86_64 OMARCHY_PROC_ROOT="$test_tmp/cases/m1-pro/proc" PATH="$stub_bin:$ROOT/bin:$PATH" \ + "$ROOT/bin/omarchy-hw-apple-silicon" 2>/dev/null; then + fail "the Apple predicate fails closed on contradictory identity" +fi +pass "contradictory identity fails with an explanation" + +failing_uname="$test_tmp/failing-uname" +mkdir -p "$failing_uname" +printf '#!/bin/bash\nexit 1\n' >"$failing_uname/uname" +chmod +x "$failing_uname/uname" +if OMARCHY_PROC_ROOT="$test_tmp/cases/acpi/proc" PATH="$failing_uname:$PATH" "$detector" 2>"$test_tmp/error"; then + fail "an unreadable CPU architecture fails instead of guessing generic" +fi +grep -Fq "cannot read the CPU architecture" "$test_tmp/error" || fail "an unreadable CPU architecture explains itself" "$(cat "$test_tmp/error")" +pass "an unreadable CPU architecture fails instead of guessing generic" + +# Dragon's omarchy-hw-qualcomm-soc: any "qcom," token in the boot device tree. +dragon_qualcomm() { + tr '\0' '\n' <"$1" | grep '^qcom,' >/dev/null +} +for case_dir in "$test_tmp"/cases/*; do + name=$(basename "$case_dir") + compatible="$case_dir/proc/device-tree/compatible" + [[ -f $compatible && ! -f $case_dir/sys/firmware/devicetree/base/compatible ]] || continue + platform=$(detect "$name" aarch64 2>/dev/null) || continue + if dragon_qualcomm "$compatible"; then + [[ $platform == "qualcomm" ]] || fail "Qualcomm detection matches Dragon on $name" "platform: $platform" + else + [[ $platform != "qualcomm" ]] || fail "Qualcomm detection matches Dragon on $name" + fi +done +pass "Qualcomm detection matches Dragon's omarchy-hw-qualcomm-soc on every aarch64 fixture" diff --git a/test/shell.d/image-target-platform-test.sh b/test/shell.d/image-target-platform-test.sh new file mode 100644 index 00000000000..3e06ffcdda7 --- /dev/null +++ b/test/shell.d/image-target-platform-test.sh @@ -0,0 +1,410 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +# omarchy-hw-platform answers with the image-target manifest while a root is +# being built, and with the hardware once it boots. Each world below is what one +# detector run sees: image/ is the root, proc/ its /proc (the host's device tree +# and PID 1), bin/ a uname for the CPU it runs on. + +detector="$ROOT/bin/omarchy-hw-platform" +umask 022 +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +apple_manifest=$'format=1\nplatform=apple-silicon\n' + +# $1 world, $2 CPU, then the host's device-tree tokens (none: no device tree). +world() { + local dir="$test_tmp/worlds/$1" cpu=$2 + shift 2 + rm -rf "$dir" + mkdir -p "$dir/image" "$dir/proc" "$dir/bin" "$dir/host" + if (( $# )); then + mkdir -p "$dir/proc/device-tree" + printf '%s\0' "$@" >"$dir/proc/device-tree/compatible" + fi + cat >"$dir/bin/uname" <