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

⎇ d797fc472d7c648b433d956ade3fe7713e4e93e4 crates/hammer-core/src/kernel/modmap.rs Rust · 207 siq'ikuna · 6.8 KB Ñawpa willay Pi imata llamkarqan ch'uya

kernel: el gate contra un .config producido — ve los huecos que la receta base ya traía d797fc472 · Sergio ·

1//! Mapa **driver → símbolo de Kconfig**, leído de los Makefiles de kbuild.
2//!
3//! Es la pieza que le faltaba al gate de no-regresión (#6 del handoff). El kernel sabe qué driver
4//! tiene bindeado cada dispositivo (`/sys/bus/*/devices/*/driver`), pero **no** dice de qué
5//! `CONFIG_*` salió: esa relación sólo existe en los Makefiles, en la forma
6//!
7//! ```make
8//! obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
9//! ```
10//!
11//! ## Dos trampas de nombres
12//! 1. **El módulo cargado usa `_` donde el fichero usa `-`**: `snd-hda-intel.o` se ve en `/sys` como
13//!    `snd_hda_intel`. Se indexa normalizado, o el gate no encontraría nada y diría que todo está
14//!    bien — que es el peor resultado posible para un portón.
15//! 2. **Un módulo puede salir de más de un símbolo** (se declara en varios Makefiles o bajo varias
16//!    condiciones). Se guardan todos: el driver sobrevive si sobrevive **cualquiera** de ellos.
17//!
18//! ## Lo que este mapa NO sabe
19//! `obj-y += foo.o` (built-in incondicional) no lleva símbolo: ese driver está siempre y no hay
20//! nada que proteger. Y hay drivers que ningún `obj-$(CONFIG_…)` nombra — los que el mapa no
21//! cubre se reportan como **hueco**, nunca como «está bien». Un gate que calla lo que no pudo
22//! comprobar no es un gate.
23
24use std::collections::{BTreeMap, BTreeSet};
25use std::path::Path;
26
27/// `driver normalizado → símbolos que lo construyen`.
28#[derive(Debug, Clone, Default)]
29pub struct ModMap {
30    pub by_module: BTreeMap<String, BTreeSet<String>>,
31    /// Cuántos Makefiles se leyeron.
32    pub files: usize,
33}
34
35impl ModMap {
36    /// Recorre el árbol buscando `Makefile*` y extrae las reglas `obj-$(CONFIG_X) += y.o`.
37    pub fn parse(root: &Path) -> std::io::Result<ModMap> {
38        let mut m = ModMap::default();
39        walk(root, &mut m)?;
40        Ok(m)
41    }
42
43    /// Símbolos que construyen este driver. Acepta el nombre tal cual sale de `/sys` (con `_`).
44    pub fn symbols_for(&self, driver: &str) -> Option<&BTreeSet<String>> {
45        self.by_module.get(&normalize(driver))
46    }
47
48    pub fn len(&self) -> usize {
49        self.by_module.len()
50    }
51
52    pub fn is_empty(&self) -> bool {
53        self.by_module.is_empty()
54    }
55}
56
57fn walk(dir: &Path, m: &mut ModMap) -> std::io::Result<()> {
58    let Ok(entries) = std::fs::read_dir(dir) else {
59        return Ok(());
60    };
61    for e in entries.flatten() {
62        let p = e.path();
63        if p.is_dir() {
64            walk(&p, m)?;
65            continue;
66        }
67        let name = e.file_name();
68        let name = name.to_string_lossy();
69        if !name.starts_with("Makefile") && !name.starts_with("Kbuild") {
70            continue;
71        }
72        if let Ok(text) = std::fs::read_to_string(&p) {
73            m.files += 1;
74            parse_makefile(&text, m);
75        }
76    }
77    Ok(())
78}
79
80fn parse_makefile(text: &str, m: &mut ModMap) {
81    for line in joined_lines(text) {
82        let t = line.trim();
83        let Some(rest) = t.strip_prefix("obj-$(CONFIG_") else {
84            continue;
85        };
86        let Some(close) = rest.find(')') else { continue };
87        let symbol = &rest[..close];
88        if symbol.is_empty() || !symbol.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_') {
89            continue;
90        }
91        // `+=`, `:=` o `=`.
92        let after = &rest[close + 1..];
93        let Some(eq) = after.find('=') else { continue };
94        let targets = &after[eq + 1..];
95        for tok in targets.split_whitespace() {
96            // Los directorios (`foo/`) los cubre el Makefile de dentro.
97            let Some(obj) = tok.strip_suffix(".o") else {
98                continue;
99            };
100            // `subdir/mod.o` — el nombre del módulo es el último componente.
101            let obj = obj.rsplit('/').next().unwrap_or(obj);
102            if obj.is_empty() || obj.contains('$') {
103                continue;
104            }
105            m.by_module
106                .entry(normalize(obj))
107                .or_default()
108                .insert(symbol.to_string());
109        }
110    }
111}
112
113/// Une las continuaciones `\` de make.
114fn joined_lines(text: &str) -> Vec<String> {
115    let mut out = Vec::new();
116    let mut acc = String::new();
117    for line in text.lines() {
118        match line.strip_suffix('\\') {
119            Some(head) => {
120                acc.push_str(head);
121                acc.push(' ');
122            }
123            None => {
124                acc.push_str(line);
125                out.push(std::mem::take(&mut acc));
126            }
127        }
128    }
129    if !acc.is_empty() {
130        out.push(acc);
131    }
132    out
133}
134
135/// El nombre con el que el módulo aparece en `/sys`: guiones a guiones bajos.
136fn normalize(s: &str) -> String {
137    s.replace('-', "_")
138}
139
140/// Alimentar un `ModMap` desde texto, para los tests de otros módulos.
141#[cfg(test)]
142pub(crate) mod tests_support {
143    pub fn feed(m: &mut super::ModMap, texto: &str) {
144        super::parse_makefile(texto, m);
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn extrae_las_tres_formas_de_asignacion() {
154        let mut m = ModMap::default();
155        parse_makefile(
156            "obj-$(CONFIG_SND_HDA) := snd-hda-codec.o\n\
157             obj-$(CONFIG_E1000) += e1000.o\n\
158             obj-$(CONFIG_FOO)=foo.o\n",
159            &mut m,
160        );
161        assert!(m.symbols_for("snd_hda_codec").unwrap().contains("SND_HDA"));
162        assert!(m.symbols_for("e1000").unwrap().contains("E1000"));
163        assert!(m.symbols_for("foo").unwrap().contains("FOO"));
164    }
165
166    #[test]
167    fn el_guion_bajo_de_sys_encuentra_al_guion_del_fichero() {
168        let mut m = ModMap::default();
169        parse_makefile("obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o\n", &mut m);
170        // Así es como sale de /sys/bus/pci/devices/*/driver.
171        assert!(m.symbols_for("snd_hda_intel").is_some());
172    }
173
174    #[test]
175    fn un_modulo_puede_venir_de_varios_simbolos() {
176        let mut m = ModMap::default();
177        parse_makefile("obj-$(CONFIG_A) += x.o\n", &mut m);
178        parse_makefile("obj-$(CONFIG_B) += x.o\n", &mut m);
179        let s = m.symbols_for("x").unwrap();
180        assert_eq!(s.len(), 2);
181    }
182
183    #[test]
184    fn los_directorios_y_los_obj_y_no_entran() {
185        let mut m = ModMap::default();
186        parse_makefile(
187            "obj-$(CONFIG_NET) += core/\nobj-y += siempre.o\nobj-$(CONFIG_X) += $(VAR).o\n",
188            &mut m,
189        );
190        assert!(m.is_empty(), "{:?}", m.by_module);
191    }
192
193    #[test]
194    fn la_continuacion_de_barra_se_une() {
195        let mut m = ModMap::default();
196        parse_makefile("obj-$(CONFIG_X) += a.o \\\n\tb.o\n", &mut m);
197        assert!(m.symbols_for("a").is_some());
198        assert!(m.symbols_for("b").is_some());
199    }
200
201    #[test]
202    fn el_modulo_en_subdirectorio_se_nombra_por_el_ultimo_componente() {
203        let mut m = ModMap::default();
204        parse_makefile("obj-$(CONFIG_X) += sub/dir/mod.o\n", &mut m);
205        assert!(m.symbols_for("mod").is_some());
206    }
207}