boot: restore mini ISO to a working login prompt + build-system fixes
Mini now boots (q35/KVM) all the way to the getty "Red Bear login:" prompt
on VT2 with zero kernel panics. Root-cause fixes across submodules + config:
- config: 00_base.service zsh->mkdir (a full shell startup hangs an init
oneshot; the earlier 'echo/mkdir also hang' result was a silent config
override running zsh). 00_ptyd.service notify->{scheme=pty} (ptyd signals
readiness via its scheme fd; notify made init block on a byte forever).
- kernel: wrmsr_safe/rdmsr_safe #GP recovery so /scheme/sys/msr can't panic
the kernel (cpufreqd's P-state MSR write #GP'd on KVM, halting the boot
before login). base: ptyd scheme type + hwd ACPI enum -> debug (noise).
installer: warn on config [[files]] override. relibc/bootloader: prior
getrlimit + warning-demotion pointers.
- build-redbear.sh: fix host-fstools staleness (installer/redoxfs never
rebuilt on source change), prefix rebuild-every-build (touch libc.a), add
concurrent-build flock.
Submodule pointers: base 8aa8616d, installer 460d9530, kernel 155d01b1,
relibc a7663b3a, bootloader 6e119641.
This commit is contained in:
+3
-2
@@ -29,8 +29,9 @@ data = """
|
||||
description = "Base environment setup (tmpdir)"
|
||||
|
||||
[service]
|
||||
cmd = "zsh"
|
||||
args = ["-c", "rm -rf /tmp; mkdir -m a=rwxt /tmp"]
|
||||
# Create the world-writable, sticky /tmp with mkdir (uutils, Rust).
|
||||
cmd = "mkdir"
|
||||
args = ["-p", "-m", "a=rwxt", "/tmp"]
|
||||
type = "oneshot"
|
||||
"""
|
||||
|
||||
|
||||
@@ -22,8 +22,14 @@ data = """
|
||||
description = "Base environment setup (tmpdir)"
|
||||
|
||||
[service]
|
||||
cmd = "zsh"
|
||||
args = ["-c", "rm -rf /tmp; mkdir -m a=rwxt /tmp"]
|
||||
# Create the world-writable, sticky /tmp with mkdir (uutils, Rust). This must
|
||||
# NOT use zsh: a full interactive shell startup hangs the init oneshot here
|
||||
# (before the login stack even starts), whereas a simple dynamically-linked
|
||||
# /usr binary like mkdir/echo completes cleanly after switchroot. See the
|
||||
# usr-dynamic-service-spawn-hang investigation — the hang was zsh-specific,
|
||||
# not a general /usr spawn failure.
|
||||
cmd = "mkdir"
|
||||
args = ["-p", "-m", "a=rwxt", "/tmp"]
|
||||
type = "oneshot"
|
||||
"""
|
||||
|
||||
|
||||
@@ -597,9 +597,16 @@ type = "oneshot"
|
||||
path = "/etc/init.d/00_ptyd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Pseudo-terminal daemon (non-blocking on live-mini)"
|
||||
description = "Pseudo-terminal daemon"
|
||||
|
||||
[service]
|
||||
cmd = "ptyd"
|
||||
type = "notify"
|
||||
# ptyd registers the "pty" scheme and signals readiness by sending its scheme
|
||||
# cap fd back to init (redox_scheme ready_sync_scheme / ready_with_fd). init's
|
||||
# scheme-service startup does a call_ro(FD) to receive that fd and register the
|
||||
# scheme. It MUST therefore be type={scheme="pty"} — NOT "notify". With "notify"
|
||||
# init does a plain readiness-byte read_exact that ptyd never sends, so init
|
||||
# blocks forever here (the observed post-switchroot boot hang before the console
|
||||
# stack). See ptyd/src/main.rs: it is on the SchemeDaemon/ready_with_fd path.
|
||||
type = { scheme = "pty" }
|
||||
"""
|
||||
|
||||
@@ -3,6 +3,22 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Concurrent-build guard. Two builds running at once corrupt each other's
|
||||
# cookbook caches, repo/ artefacts and prefix, and produce non-deterministic
|
||||
# ISOs. Take an exclusive, non-blocking flock on a lockfile; if another build
|
||||
# already holds it, refuse rather than silently racing. Set REDBEAR_ALLOW_
|
||||
# CONCURRENT=1 to override (e.g. builds targeting isolated output dirs).
|
||||
if [ -z "${REDBEAR_ALLOW_CONCURRENT:-}" ] && command -v flock >/dev/null 2>&1; then
|
||||
REDBEAR_BUILD_LOCK="${TMPDIR:-/tmp}/redbear-build.lock"
|
||||
exec 9>"$REDBEAR_BUILD_LOCK"
|
||||
if ! flock -n 9; then
|
||||
echo ">>> ERROR: another RedBear build is already running (lock: $REDBEAR_BUILD_LOCK)." >&2
|
||||
echo ">>> Wait for it to finish, or set REDBEAR_ALLOW_CONCURRENT=1 to override." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/lib/relibc-surface.sh"
|
||||
|
||||
# =============================================================================
|
||||
@@ -564,6 +580,7 @@ echo ">>> This may take 30-60 minutes on first build..."
|
||||
# packages that don't use autotools.
|
||||
if [ "$NO_CACHE" != "1" ]; then
|
||||
STALE_DETECTED=0
|
||||
RELIBC_INVALIDATED=0
|
||||
for src in relibc kernel base bootloader installer; do
|
||||
src_dir="$PROJECT_ROOT/local/sources/$src"
|
||||
pkgar="$PROJECT_ROOT/repo/x86_64-unknown-redox/$src.pkgar"
|
||||
@@ -594,6 +611,7 @@ if [ "$NO_CACHE" != "1" ]; then
|
||||
rm -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/$src".*
|
||||
find "$PROJECT_ROOT/recipes" -path "*/$src/target" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
STALE_DETECTED=1
|
||||
[ "$src" = "relibc" ] && RELIBC_INVALIDATED=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -605,6 +623,97 @@ if [ "$NO_CACHE" != "1" ]; then
|
||||
-o -path "*/target/x86_64-unknown-redox/sysroot" \) \
|
||||
-type d -exec rm -rf {} + 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Downstream ABI-staleness prevention (relibc consumers).
|
||||
#
|
||||
# The loop above rebuilds relibc/kernel/base when THEIR OWN sources
|
||||
# change, but statically-linked consumers keep their pkgars as long as
|
||||
# THEIR sources are unchanged. relibc is both the C runtime AND the
|
||||
# Redox scheme/syscall protocol shim, so a consumer built against an
|
||||
# older relibc bakes in the OLD protocol. For boot-critical binaries
|
||||
# copied into the initfs from the sysroot by
|
||||
# recipes/core/base-initfs/recipe.toml this skew is FATAL, not cosmetic:
|
||||
# - a stale getty issued the pre-"userspace fd allocation" two-step
|
||||
# scheme-open and got EACCES opening /scheme/rand (login dead);
|
||||
# - a stale redoxfs panicked "failed to create pipe".
|
||||
# Both shipped in an ISO whose freshly-built daemons (randd/ptyd/...)
|
||||
# already spoke the new protocol — a silent, hard-to-trace mismatch.
|
||||
#
|
||||
# Rule: invalidate an ABI-critical consumer when it would link an older
|
||||
# relibc than the one this build will ship. That happens either because
|
||||
# relibc is about to rebuild (RELIBC_INVALIDATED=1, or its pkgar is
|
||||
# already gone) or because a prior run left relibc.pkgar newer than the
|
||||
# consumer's pkgar (the exact getty/redoxfs case: relibc rebuilt hours
|
||||
# after userutils/redoxfs).
|
||||
#
|
||||
# NB: if you add a binary to the base-initfs sysroot copy-set, add its
|
||||
# recipe name to ABI_CRITICAL_PKGS below.
|
||||
ABI_CRITICAL_PKGS="redoxfs userutils ion driver-manager"
|
||||
RELIBC_PKGAR="$PROJECT_ROOT/repo/x86_64-unknown-redox/relibc.pkgar"
|
||||
relibc_rebuilding=0
|
||||
if [ "$RELIBC_INVALIDATED" = "1" ] || [ ! -f "$RELIBC_PKGAR" ]; then
|
||||
relibc_rebuilding=1
|
||||
fi
|
||||
relibc_ts=0
|
||||
[ -f "$RELIBC_PKGAR" ] && relibc_ts=$(stat -c %Y "$RELIBC_PKGAR" 2>/dev/null || echo "0")
|
||||
ABI_STALE=0
|
||||
for pkg in $ABI_CRITICAL_PKGS; do
|
||||
pkg_pkgar="$PROJECT_ROOT/repo/x86_64-unknown-redox/$pkg.pkgar"
|
||||
[ -f "$pkg_pkgar" ] || continue
|
||||
pkg_ts=$(stat -c %Y "$pkg_pkgar" 2>/dev/null || echo "0")
|
||||
if [ "$relibc_rebuilding" = "1" ] || { [ "$relibc_ts" != "0" ] && [ "$relibc_ts" -gt "$pkg_ts" ]; }; then
|
||||
echo ">>> ABI-stale $pkg detected (would link an older relibc than this build ships); invalidating..."
|
||||
rm -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/$pkg".*
|
||||
find "$PROJECT_ROOT/recipes" "$PROJECT_ROOT/local/recipes" \
|
||||
-path "*/$pkg/target" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
ABI_STALE=1
|
||||
fi
|
||||
done
|
||||
# base-initfs bundles the (now-invalidated) binaries into the initfs
|
||||
# image, so it must re-cook to pick up the freshly relinked copies.
|
||||
if [ "$ABI_STALE" = "1" ]; then
|
||||
echo ">>> Invalidating base-initfs so it re-bundles the refreshed initfs binaries..."
|
||||
rm -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/base-initfs".*
|
||||
find "$PROJECT_ROOT/recipes" -path "*/base-initfs/target" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Host fstools staleness (redox_installer / redoxfs FUSE used by `make live`).
|
||||
#
|
||||
# `make live` assembles the image with the HOST-compiled redox_installer from
|
||||
# build/fstools/bin, built once by the `fstools` make target via
|
||||
# `cargo install --path local/sources/installer`. That target is a DIRECTORY
|
||||
# target (build/fstools) with NO prerequisite on the installer/redoxfs sources,
|
||||
# so once build/fstools exists make never re-runs the cargo install — a source
|
||||
# change to either fork silently never reaches image assembly. This bit us: an
|
||||
# installer change to config-override handling appeared to "do nothing" because
|
||||
# make live kept running a 6-day-old installer binary. Mirror the recipe
|
||||
# staleness logic: if either fork's committed HEAD or working tree is newer
|
||||
# than build/fstools, remove it so make rebuilds the host tools. Runs
|
||||
# unconditionally (the NO_CACHE repo clean above does not touch build/fstools).
|
||||
FSTOOLS_DIR="$PROJECT_ROOT/build/fstools"
|
||||
if [ -d "$FSTOOLS_DIR" ]; then
|
||||
fstools_ts=$(stat -c %Y "$FSTOOLS_DIR" 2>/dev/null || echo "0")
|
||||
fstools_stale=0
|
||||
for src in installer redoxfs; do
|
||||
src_dir="$PROJECT_ROOT/local/sources/$src"
|
||||
[ -d "$src_dir/.git" ] || continue
|
||||
src_ts=$(git -C "$src_dir" log -1 --format=%ct HEAD 2>/dev/null || echo "0")
|
||||
src_dirty=0
|
||||
if ! git -C "$src_dir" diff --quiet HEAD 2>/dev/null \
|
||||
|| ! git -C "$src_dir" diff --cached --quiet HEAD 2>/dev/null \
|
||||
|| [ -n "$(git -C "$src_dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
||||
src_dirty=1
|
||||
fi
|
||||
if { [ "$src_ts" != "0" ] && [ "$fstools_ts" != "0" ] && [ "$src_ts" -gt "$fstools_ts" ]; } \
|
||||
|| [ "$src_dirty" = "1" ]; then
|
||||
echo ">>> Stale host fstools detected ($src newer than build/fstools); rebuilding host installer/redoxfs..."
|
||||
fstools_stale=1
|
||||
fi
|
||||
done
|
||||
if [ "$fstools_stale" = "1" ]; then
|
||||
rm -rf "$FSTOOLS_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
PREFIX_LIBC="$PROJECT_ROOT/prefix/x86_64-unknown-redox/sysroot/x86_64-unknown-redox/lib/libc.a"
|
||||
@@ -657,6 +766,16 @@ if [ "$STALE_PREFIX" = "1" ] && [ -z "${REDBEAR_SKIP_PREFIX_REBUILD:-}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo ">>> Prefix rebuilt successfully."
|
||||
# Stamp libc.a to now so the staleness check above does not re-fire on
|
||||
# every subsequent build. `make prefix` extracts a prebuilt toolchain
|
||||
# tarball and is a no-op when the prefix is already present, so without
|
||||
# this touch libc.a keeps its old mtime, stays "older" than the fork's
|
||||
# git commit timestamp, and the (expensive) prefix step runs on every
|
||||
# build forever. Touching after a successful rebuild means we only rebuild
|
||||
# again when the fork actually advances past this point. The uncommitted-
|
||||
# changes (src_dirty) path above is unaffected: it intentionally rebuilds
|
||||
# whenever the working tree is dirty, regardless of mtime.
|
||||
touch "$PREFIX_LIBC"
|
||||
STALE_PREFIX=0
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
Submodule local/sources/base updated: db724cda70...8aa8616d5e
+1
-1
Submodule local/sources/bootloader updated: c78c3a63c0...6e11964121
+1
-1
Submodule local/sources/installer updated: 1e9dcdee98...460d9530a5
+1
-1
Submodule local/sources/kernel updated: b5a7bf5581...155d01b11e
+1
-1
Submodule local/sources/relibc updated: f46e2a31a7...a7663b3adf
@@ -10,6 +10,11 @@ dependencies = [
|
||||
"redoxfs",
|
||||
"ion",
|
||||
"driver-manager",
|
||||
# userutils provides `getty`, copied into the bare initfs from the
|
||||
# sysroot below. Without this dependency getty is absent from the
|
||||
# sysroot at cook time, the copy is silently skipped, and the bare
|
||||
# ISO has no login (init fails to exec /scheme/initfs/bin/getty).
|
||||
"userutils",
|
||||
]
|
||||
script = """
|
||||
set -eo pipefail
|
||||
@@ -60,17 +65,19 @@ aarch64_bins()
|
||||
|
||||
bare_initfs()
|
||||
{
|
||||
BINS=(
|
||||
init
|
||||
logd
|
||||
ramfs
|
||||
randd
|
||||
zerod
|
||||
ptyd
|
||||
)
|
||||
# `getty` is not a member of the base workspace — it lives in
|
||||
# userutils. We copy the prebuilt binary from the sysroot after
|
||||
# the cargo build step (see the EXTRA_SYSROOT_BINS copy below).
|
||||
# Per user directive, the bare initfs ships the FULL driver set —
|
||||
# identical to the standard x86 initfs (pcid, pcid-spawner, ahcid, ided,
|
||||
# nvmed, virtio-*, ps2d, usb*, e1000d, rtl8168d, acpid, hwd, rtcd, lived)
|
||||
# plus the framebuffer console stack (inputd, vesad, fbcond, fbbootlogd).
|
||||
# Every driver initialises at boot and redoxfs can find/mount a real disk.
|
||||
#
|
||||
# The interactive login is NOT run from the initfs: after the initfs
|
||||
# brings up the drivers + framebuffer console it switchroots to /usr,
|
||||
# where the rootfs config (config/minimal.toml, included by redbear-bare)
|
||||
# provides 00_ptyd.service + 29_activate_console + 30_console.service
|
||||
# (getty -> login -> zsh) with a real /etc/passwd and shell. So the bare
|
||||
# initfs matches the mainline initfs exactly — no getty/login/ptyd here.
|
||||
x86_common_bins
|
||||
}
|
||||
|
||||
case "${TARGET}" in
|
||||
@@ -94,61 +101,11 @@ esac
|
||||
rm -rf "${COOKBOOK_BUILD}/initfs"
|
||||
mkdir -p "${COOKBOOK_BUILD}/initfs/lib/init.d"
|
||||
|
||||
if [ "${REDBEAR_BARE_INITFS:-0}" = "1" ]; then
|
||||
# Bare initfs boots the absolute minimum: logd, ramfs, randd,
|
||||
# zerod, ptyd, getty. Only copy the init.d units that reference
|
||||
# binaries we actually built (no fbcond, vesad, pcid, acpid,
|
||||
# fbbootlogd, hwd, inputd, etc. — those daemons aren't in the
|
||||
# bare BINS, and the init runtime would otherwise try to start
|
||||
# them and hit "binary not found" or busy-loop on the debug
|
||||
# console scheme).
|
||||
for unit in "${COOKBOOK_SOURCE}/init.initfs.d"/*; do
|
||||
case "$(basename "$unit")" in
|
||||
00_logd.service|00_randd.service|00_zerod.service|00_runtime.target|ramfs@.service|90_initfs.target|50_rootfs.service)
|
||||
cp -v "$unit" "${COOKBOOK_BUILD}/initfs/lib/init.d/"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Bare initfs also needs ptyd (built from BINS) and getty (copied
|
||||
# from sysroot below). Provide minimal unit files for both.
|
||||
cat > "${COOKBOOK_BUILD}/initfs/lib/init.d/00_ptyd.service" <<'EOF'
|
||||
[unit]
|
||||
description = "Pseudo-terminal daemon"
|
||||
default_dependencies = false
|
||||
[service]
|
||||
cmd = "ptyd"
|
||||
type = { scheme = "pty" }
|
||||
EOF
|
||||
cat > "${COOKBOOK_BUILD}/initfs/lib/init.d/30_getty.service" <<'EOF'
|
||||
[unit]
|
||||
description = "Console getty (login on /scheme/console/2)"
|
||||
default_dependencies = false
|
||||
requires_weak = ["00_ptyd.service"]
|
||||
[service]
|
||||
cmd = "getty"
|
||||
args = ["2"]
|
||||
type = "oneshot_async"
|
||||
EOF
|
||||
# Bare runtime.target — ptyd and getty are not pulled in by the
|
||||
# mainline 00_runtime.target (which references nulld/rtcd that we
|
||||
# filtered out). Add them as weak requires so init schedules them
|
||||
# before reaching 90_initfs.target.
|
||||
cat > "${COOKBOOK_BUILD}/initfs/lib/init.d/00_runtime.target" <<'EOF'
|
||||
[unit]
|
||||
description = "Services that relibc needs to function"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"00_logd.service",
|
||||
"00_randd.service",
|
||||
"00_zerod.service",
|
||||
"00_ptyd.service",
|
||||
"30_getty.service",
|
||||
"ramfs@logging.service",
|
||||
]
|
||||
EOF
|
||||
else
|
||||
cp "${COOKBOOK_SOURCE}/init.initfs.d"/* "${COOKBOOK_BUILD}/initfs/lib/init.d/"
|
||||
fi
|
||||
# The bare initfs now uses the full mainline unit graph (all driver +
|
||||
# console + rootfs units). The interactive login runs from /usr after
|
||||
# switchroot (see bare_initfs() above), so no initfs-specific getty/ptyd
|
||||
# units are injected — bare and non-bare copy the identical unit set.
|
||||
cp "${COOKBOOK_SOURCE}/init.initfs.d"/* "${COOKBOOK_BUILD}/initfs/lib/init.d/"
|
||||
|
||||
mkdir -pv "${COOKBOOK_BUILD}/initfs/lib/drivers.d"
|
||||
cp -v "${COOKBOOK_SOURCE}/drivers/initfs-storage.toml" "${COOKBOOK_BUILD}/initfs/lib/drivers.d/00-storage.toml"
|
||||
@@ -178,11 +135,9 @@ done
|
||||
cp "${COOKBOOK_SYSROOT}/usr/bin/redoxfs" "${COOKBOOK_BUILD}/initfs/bin"
|
||||
cp "${COOKBOOK_SYSROOT}/usr/bin/ion" "${COOKBOOK_BUILD}/initfs/bin"
|
||||
cp "${COOKBOOK_SYSROOT}/usr/bin/driver-manager" "${COOKBOOK_BUILD}/initfs/bin"
|
||||
# Bare initfs needs `getty` (built by userutils, not the base
|
||||
# workspace) so the initfs shell can launch a login prompt.
|
||||
if [ "${REDBEAR_BARE_INITFS:-0}" = "1" ] && [ -f "${COOKBOOK_SYSROOT}/usr/bin/getty" ]; then
|
||||
cp "${COOKBOOK_SYSROOT}/usr/bin/getty" "${COOKBOOK_BUILD}/initfs/bin"
|
||||
fi
|
||||
# NB: getty/login are intentionally NOT copied into the initfs. The
|
||||
# interactive login runs from /usr after switchroot (config/minimal.toml's
|
||||
# 30_console.service), where a real /etc/passwd, login and shell exist.
|
||||
|
||||
ARCH="$(echo "${GNU_TARGET}" | cut -d - -f1)"
|
||||
RUSTFLAGS="$RUSTFLAGS -Ctarget-feature=+crt-static -Clink-arg=-nostartfiles -Clink-arg=-nostdlib" cargo \
|
||||
|
||||
Reference in New Issue
Block a user