selfhost-verify: módulos del kernel booteado + build a stderr (E2BIG)

Dos bugs latentes que sólo aparecían en un build real (in-VM); en el host
quedan tapados porque todo sale de caché.

1. scripts/selfhost-verify.sh: los módulos se sacaban de /lib/modules/$(uname -r)
   pero la VM bootea $KERNEL (/boot/vmlinuz-linux), que puede ser otra versión.
   Mismatch de version-magic ⇒ overlay.ko no carga ⇒ bwrap muere "No such device".
   Ahora la versión se deriva del bzImage booteado (file -bL "$KERNEL"; fallback uname -r).

2. crates/hammer-build/src/sandbox.rs: spawn_pump teeaba el stdout del build al
   stdout del padre. rebuild-stage1 hace PRIME=$(hammer ... bootstrap stage1); en
   un build real son miles de líneas de configure/make capturadas en $PRIME ⇒ el
   paso siguiente --rootfs "$PRIME" exec con un arg gigante ⇒ E2BIG (Argument list
   too long). Ahora el tee va a stderr; stdout queda para el hash legible-por-máquina.

Con esto el verify corre end-to-end in-VM: los 4 componentes reproducen bit a bit,
pero of_tree(stage1) diverge (host vs VM vs baseline) — no-determinismo del ensamblado
a cazar (SDD 09 §2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 03:04:45 -04:00
co-authored by Claude Opus 4.8
parent 4d8c1c754f
commit 1ab53a650f
2 changed files with 22 additions and 13 deletions
+13 -10
View File
@@ -90,8 +90,8 @@ impl Sandbox {
let tail = Arc::new(Mutex::new(VecDeque::<String>::with_capacity(TAIL_LINES)));
let out = child.stdout.take().expect("stdout piped");
let err = child.stderr.take().expect("stderr piped");
let h_out = spawn_pump(out, tail.clone(), false);
let h_err = spawn_pump(err, tail.clone(), true);
let h_out = spawn_pump(out, tail.clone());
let h_err = spawn_pump(err, tail.clone());
let status = child
.wait()
@@ -201,12 +201,19 @@ impl Sandbox {
}
}
/// Lanza un thread que lee `reader` línea a línea, la reenvía al stream del padre
/// (`to_stderr` decide cuál) y la acumula en el ring `tail`. Devuelve el handle para join.
/// Lanza un thread que lee `reader` línea a línea, la reenvía a stderr del padre y la
/// acumula en el ring `tail`. Devuelve el handle para join.
///
/// El tee va SIEMPRE a stderr (tanto el stdout como el stderr del build): stdout del proceso
/// queda reservado para el resultado legible-por-máquina (el content-hash que imprime la CLI,
/// p. ej. `bootstrap stage1`). Antes el stdout del build se teeaba al stdout del padre y se
/// colaba en capturas tipo `PRIME=$(hammer … bootstrap stage1)`; con builds reales (no cacheados,
/// p. ej. el rebuild in-VM) eso son miles de líneas de `configure`/`make` ⇒ el argumento crece y
/// la fase de verificación siguiente (`--rootfs "$PRIME"`) revienta el exec con E2BIG
/// (Argument list too long). Va a stderr, junto a los logs de tracing. Ver runbook §8c.
fn spawn_pump<R: std::io::Read + Send + 'static>(
reader: R,
tail: Arc<Mutex<VecDeque<String>>>,
to_stderr: bool,
) -> std::thread::JoinHandle<()> {
std::thread::spawn(move || {
let buf = BufReader::new(reader);
@@ -215,12 +222,8 @@ fn spawn_pump<R: std::io::Read + Send + 'static>(
Ok(l) => l,
Err(_) => break,
};
// Tee en vivo al padre (sin bufferizar de más: el build puede tardar).
if to_stderr {
// Tee en vivo al padre por stderr (sin bufferizar de más: el build puede tardar).
let _ = writeln!(std::io::stderr(), "{line}");
} else {
let _ = writeln!(std::io::stdout(), "{line}");
}
if let Ok(mut g) = tail.lock() {
push_tail(&mut g, line, TAIL_LINES);
}
+8 -2
View File
@@ -83,8 +83,14 @@ fi
# 3) Inyectar los módulos del kernel DE ESTA máquina al toolchain (el builder los carga: overlay para
# el sandbox bwrap, e1000 para la red del vendoring). Deben matchear el kernel que bootea la VM.
say "inyectar overlay.ko + e1000.ko (kernel $(uname -r)) al toolchain"
moddir="/lib/modules/$(uname -r)"
# La versión de los módulos DEBE matchear el kernel que bootea la VM ($KERNEL), no el del host
# corriendo el script ($(uname -r)) — en máquinas con varios kernels instalados difieren y el
# insmod falla con "version magic mismatch" → sin overlayfs, bwrap revienta. Sacamos la versión
# del propio bzImage; si no se puede, caemos a uname -r.
KVER="$(file -bL "$KERNEL" 2>/dev/null | grep -oE 'version [0-9][^ ]*' | awk '{print $2}')"
[[ -n "$KVER" && -d "/lib/modules/$KVER" ]] || KVER="$(uname -r)"
say "inyectar overlay.ko + e1000.ko (kernel $KVER) al toolchain"
moddir="/lib/modules/$KVER"
for m in overlay e1000; do
src="$(find "$moddir" -name "$m.ko*" 2>/dev/null | head -1)"
if [[ -n "$src" ]]; then