gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1574Shell 204Rust 111Markdown 993763 Definitions

tasas H4: un test hace regla lo que era suerte — cero pre_exec en el workspace

pre_exec apaga el camino posix_spawn de Rust y devuelve el spawn a fork+exec: 231 µs contra
3 949 µs con 256 MB tocados en el padre (SDD 25 T1). hammer no usa ninguno hoy, pero nadie lo
comprobaba.

El test se comprobó en los dos sentidos: inyectando un pre_exec en sandbox.rs se pone rojo, y
si el barrido no encuentra fuentes también falla — un guardián que aprueba por vacío es el
fallo de CLAUDE.md §3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih

697d3f21a1d944a061876720113f245c9caa436e · by Sergio · (1 month ago) · parent 292e3ed6c · browse the tree at this commit

Semantic delta · 3

crates/hammer-build/tests/sin_pre_exec.rs

2 files · +80 −3

diff --git a/crates/hammer-build/tests/sin_pre_exec.rs b/crates/hammer-build/tests/sin_pre_exec.rs
new file mode 100644
index 00000000..e376ba62
--- /dev/null
+++ b/crates/hammer-build/tests/sin_pre_exec.rs
@@ -0,0 +1,73 @@
+//! Guardián de SDD 25 §7-H4: **ningún camino de spawn de hammer usa `pre_exec`**.
+//!
+//! POR QUÉ. `std::process::Command` de Rust usa `posix_spawn` cuando puede, y `posix_spawn` es
+//! PLANO respecto al tamaño del padre: a 256 MB tocados, `fork+exec` costó 3 949 µs contra 231 µs
+//! (SDD 25 T1, **17×**). En cuanto se le pone un `pre_exec`, Rust apaga ese camino y cae a
+//! `fork+exec`, que copia el espacio de direcciones del padre. hammer lanza builds desde un
+//! proceso que puede tener cientos de MB mapeados.
+//!
+//! Hoy `hammer-build::sandbox` lanza `bwrap` sin `pre_exec` — y hasta ahora eso era SUERTE, no una
+//! regla: nadie lo comprobaba. Esto lo vuelve una regla.
+//!
+//! CUÁNDO SE PUEDE APAGAR. Si algún día hace falta de verdad (un `setsid`, un `unshare` que bwrap
+//! no cubra), la respuesta no es borrar el test: es escribir acá por qué ese caso paga los 17× y
+//! ponerlo en la lista de excepciones, para que el precio quede a la vista de quien lo lea.
+
+use std::path::{Path, PathBuf};
+
+fn raiz_del_workspace() -> PathBuf {
+    // CARGO_MANIFEST_DIR = crates/hammer-build
+    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+        .parent()
+        .and_then(Path::parent)
+        .expect("crates/<crate> tiene dos ancestros")
+        .to_path_buf()
+}
+
+fn rs_de(dir: &Path, out: &mut Vec<PathBuf>) {
+    let Ok(rd) = std::fs::read_dir(dir) else { return };
+    for e in rd.flatten() {
+        let p = e.path();
+        if p.is_dir() {
+            rs_de(&p, out);
+        } else if p.extension().is_some_and(|x| x == "rs") {
+            out.push(p);
+        }
+    }
+}
+
+#[test]
+fn ningun_spawn_de_hammer_usa_pre_exec() {
+    let raiz = raiz_del_workspace();
+    let crates = raiz.join("crates");
+    assert!(crates.is_dir(), "no encontré {} — ¿se movió el workspace?", crates.display());
+
+    let mut fuentes = Vec::new();
+    rs_de(&crates, &mut fuentes);
+    // Un barrido que no encuentra fuentes aprueba por vacío, que es el fallo de CLAUDE.md §3.
+    assert!(fuentes.len() > 20, "sólo {} fuentes: el barrido no miró nada", fuentes.len());
+
+    let mut culpables = Vec::new();
+    for f in &fuentes {
+        if f == Path::new(file!()) || f.ends_with("tests/sin_pre_exec.rs") {
+            continue;
+        }
+        let Ok(txt) = std::fs::read_to_string(f) else { continue };
+        for (i, l) in txt.lines().enumerate() {
+            let limpia = l.trim_start();
+            if limpia.starts_with("//") || limpia.starts_with("*") {
+                continue; // documentarlo no es usarlo
+            }
+            if l.contains("pre_exec") {
+                culpables.push(format!("{}:{}: {}", f.display(), i + 1, l.trim()));
+            }
+        }
+    }
+
+    assert!(
+        culpables.is_empty(),
+        "SDD 25 §7-H4: `pre_exec` apaga el camino `posix_spawn` de Rust y devuelve el spawn a \
+         `fork+exec` (17× más caro con 256 MB en el padre, SDD 25 T1). Aparece en:\n  {}",
+        culpables.join("\n  ")
+    );
+}
diff --git a/docs/25-tasas-del-kernel.md b/docs/25-tasas-del-kernel.md
index 417632d6..5cb52330 100644
--- a/docs/25-tasas-del-kernel.md
+++ b/docs/25-tasas-del-kernel.md
@@ -403,9 +403,13 @@ Su precio, concreto:
   parcheado en la receta. Queda escrito para no rediscutirlo.
 - **H3 · `harkaq` usa `openat2 RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS`** donde hoy valida rutas
   a mano (T9, ~gratis).
-- **H4 · Auditar los `Command` de hammer que ponen `pre_exec`**: Rust apaga el camino
-  `posix_spawn` en cuanto hay `pre_exec`, y cae a `fork+exec` (T1). `hammer-build/sandbox.rs`
-  lanza `bwrap` sin `pre_exec` ⇒ está bien hoy; el punto es que sea una regla, no una suerte.
+- **H4 · Ningún `Command` de hammer pone `pre_exec`** — Rust apaga el camino `posix_spawn` en
+  cuanto hay uno, y cae a `fork+exec` (T1: 17× a 256 MB tocados). `hammer-build/sandbox.rs` lanza
+  `bwrap` sin `pre_exec`, o sea que estaba bien **por suerte**: nadie lo comprobaba. **HECHO**: el
+  test `crates/hammer-build/tests/sin_pre_exec.rs` barre las fuentes del workspace y falla si
+  aparece uno (comprobado en los dos sentidos: inyectando un `pre_exec` el test se pone rojo, y un
+  barrido que no encuentra fuentes también, para que no apruebe por vacío). Si algún día hace falta
+  de verdad, la salida es escribir en el test por qué ese caso paga los 17×, no borrarlo.
 - **H5 · La materialización de dependencias se queda en hardlink** (T10) y el experimento de
   reflink (§9) alimenta SDD 18 (wawafs), donde el store como FS vivo cambiaría el cociente.
 - **H6 · No `madvise(MADV_HUGEPAGE)` en el camino de build** mientras `defrag=madvise` y la