tawasuyu

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 1572Shell 203Rust 109Markdown 993728 Definiciones

build/sandbox: fusionar deps en UNA capa overlay cuando son muchas (fix límite lowerdir)

overlayfs concatena todas las lowerdir en un string de opciones acotado a ~1 página (4096B). KIO
(~43 deps) desbordaba → bwrap 'Can't make overlay mount ... No such file or directory' (truncado).
bwrap CANONICALIZA cada --overlay-src a su ruta real, así que symlinks cortos no ayudan (probado).
Fix: cuando la suma de rutas supera ~3000B, se funden todas las deps en un dir real por hardlinks
(cp -alf, last-wins = misma semántica que el apilado overlay) y se pasa esa ÚNICA capa ⇒ lowerdir
constante, escala a cientos de deps (todo Plasma/apps). Cacheado por build con marker .done (una vez
aunque bwrap_args corra por fase). Pocas deps = ruta directa sin I/O (sandbox idéntico, tests intactos).
Validado: KIO configura con 43 deps visibles y compila. 57 tests verdes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

e69eaaaa3fcfb7105aa8cbb983ecc5debf53e82e · por sergio · (hace 3 meses) · padre 54d8b1953 · ver el árbol en este commit

Delta semántico · 2

crates/hammer-build/src/sandbox.rs

1 archivos · +79 −3

diff --git a/crates/hammer-build/src/sandbox.rs b/crates/hammer-build/src/sandbox.rs
index c33e46a6..f10cebdf 100644
--- a/crates/hammer-build/src/sandbox.rs
+++ b/crates/hammer-build/src/sandbox.rs
@@ -173,6 +173,54 @@ impl Sandbox {
         })
     }
 
+    /// Funde todas las `deps` en UN solo directorio real por hardlinks (`cp -al`, last-wins) y lo
+    /// devuelve para usarlo como capa overlay única. Ver [`Self::bwrap_args`]: bwrap canonicaliza los
+    /// `--overlay-src`, así que la única forma de no desbordar el `lowerdir` con muchas deps es
+    /// colapsarlas a una capa. El merge vive junto al store (mismo filesystem ⇒ hardlinks válidos) y
+    /// es único-por-build (id derivado del `src_dir`). Cacheado con marcador `.done`: se hace UNA vez
+    /// aunque `bwrap_args` se llame por cada fase (configure/compile/install). Devuelve `None` si
+    /// falla (el caller cae al apilado directo).
+    fn merge_deps_layer(&self) -> Option<PathBuf> {
+        use std::hash::{Hash, Hasher};
+        let mut h = std::collections::hash_map::DefaultHasher::new();
+        self.src_dir.hash(&mut h);
+        for d in &self.deps {
+            d.hash(&mut h);
+        }
+        // Junto al store para compartir filesystem (hardlinks no cruzan fs). El store suele estar en
+        // `<base>/store`; ubicamos el merge en `<base>/.dmerge/<id>`. Derivamos <base> del primer dep.
+        let base = self
+            .deps
+            .first()
+            .and_then(|d| std::fs::canonicalize(d).ok())
+            .and_then(|d| d.parent().and_then(|p| p.parent()).map(|p| p.to_path_buf()))
+            .unwrap_or_else(|| PathBuf::from("."));
+        let merged = base.join(".dmerge").join(format!("{:x}", h.finish()));
+        let done = merged.join(".done");
+        if done.exists() {
+            return Some(merged);
+        }
+        let _ = std::fs::remove_dir_all(&merged);
+        std::fs::create_dir_all(&merged).ok()?;
+        // `cp -al <dep>/. <merged>/` hardlinkea el árbol recursivamente; `-f` fuerza el last-wins en
+        // los pocos ficheros que coincidan (semántica de capa superior del overlay). Orden = orden de
+        // deps (la última gana), igual que el apilado `--overlay-src`.
+        for dep in &self.deps {
+            let real = std::fs::canonicalize(dep).ok()?;
+            let status = Command::new("cp")
+                .arg("-alf")
+                .arg(format!("{}/.", real.display()))
+                .arg(&merged)
+                .status()
+                .ok()?;
+            if !status.success() {
+                return None;
+            }
+        }
+        std::fs::write(&done, b"").ok()?;
+        Some(merged)
+    }
+
     fn bwrap_args(&self, cmd: &str) -> Vec<String> {
         let mut args: Vec<String> = vec![
             "--overlay-src".into(),
@@ -181,9 +229,37 @@ impl Sandbox {
         // Capas overlay de las deps de build, apiladas bajo el rootfs (mismo `--tmp-overlay /`). bwrap
         // admite múltiples `--overlay-src` para un overlay; los `usr/{include,lib}` de cada dep se
         // funden con los del rootfs en `/usr`. Sólo se emiten si hay deps (compat hacia atrás).
-        for dep in &self.deps {
-            args.push("--overlay-src".into());
-            args.push(dep.display().to_string());
+        //
+        // LÍMITE DE PÁGINA (overlayfs): el kernel concatena todas las lowerdir en un string de
+        // opciones de mount acotado a ~1 página (4096B). Con muchas deps (KIO ~45, Plasma más), la
+        // suma de rutas largas `<store>/<64hex>-<name>` DESBORDA y el mount falla (bwrap: "Can't make
+        // overlay mount ... No such file or directory" por truncado). Mitigación: cuando la suma de
+        // longitudes se acerca al tope, symlinkeamos cada dep a un nombre corto (`<tmp>/hdl-<id>/<N>`)
+        // por-build y pasamos ESAS rutas → lowerdir compacto que escala a cientos de deps. Con pocas
+        // deps (caso común) usamos la ruta directa: cero I/O y sandbox idéntico al previo.
+        // bwrap CANONICALIZA cada `--overlay-src` a su ruta real para armar el `lowerdir=`, así que
+        // acortar con symlinks no sirve: el string sigue siendo la suma de rutas reales del store.
+        // La única forma de escalar a muchas deps es FUNDIRLAS en UN solo directorio real (hardlinks,
+        // `cp -al`, last-wins = misma semántica que el apilado overlay) y pasar esa ÚNICA capa ⇒
+        // lowerdir = rootfs + 1 merge, constante sin importar cuántas deps haya.
+        let total_len: usize = self.deps.iter().map(|d| d.as_os_str().len() + 16).sum();
+        if total_len > 3000 && self.deps.len() > 1 {
+            if let Some(merged) = self.merge_deps_layer() {
+                args.push("--overlay-src".into());
+                args.push(merged.display().to_string());
+            } else {
+                // Fallback: si la fusión falló, volvemos al apilado directo (puede desbordar, pero es
+                // mejor que abortar sin intentar).
+                for dep in &self.deps {
+                    args.push("--overlay-src".into());
+                    args.push(dep.display().to_string());
+                }
+            }
+        } else {
+            for dep in &self.deps {
+                args.push("--overlay-src".into());
+                args.push(dep.display().to_string());
+            }
         }
         args.extend([
             "--tmp-overlay".into(),