Files
takana/crates/takana-core/src/kernel/config.rs
T
Sergio 24bcf1783c takana etapa 4: los 10 crates de librería y el CLI pasan a takana-*
hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.

VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.

DOS BINARIOS SE CONGELAN, y no por prolijidad:

- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
  busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
  `PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
  of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.

- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
  hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
  instalados y hornea un hook de arranque que lo invoca por ese nombre:
  renombrarlo rompe máquinas instaladas, no el repo.

Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.

Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
2026-09-09 18:46:41 +00:00

146 lines
4.6 KiB
Rust

//! Lectura de un `.config` de kernel (o del `/proc/config.gz` ya descomprimido).
//!
//! Es el sustrato del **modo reversa** (#9 del handoff): antes de compilar nada, leer el kernel que
//! ya corre y mirarlo a través del lente de bundles. Cero riesgo, cero build.
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
/// Valor de un símbolo en un `.config` materializado.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConfigValue {
/// `=y`
Yes,
/// `=m`
Module,
/// `# CONFIG_X is not set` — presencia explícita del apagado, que **no** es lo mismo que
/// ausencia: un símbolo ausente puede no existir en esta versión del kernel.
No,
/// `="texto"`
Str(String),
/// `=42`, `=0x10`
Num(String),
}
impl ConfigValue {
/// ¿Está presente en el kernel, como built-in o como módulo?
pub fn is_on(&self) -> bool {
matches!(self, ConfigValue::Yes | ConfigValue::Module)
}
}
/// Un `.config` leído. Las claves van **sin** el prefijo `CONFIG_`, igual que en los ficheros
/// Kconfig, para que un símbolo se llame igual en los dos lados del análisis.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KernelConfig {
pub values: BTreeMap<String, ConfigValue>,
/// La línea `# Linux/x86 6.16.12 Kernel Configuration`, si está.
pub banner: Option<String>,
}
impl KernelConfig {
pub fn parse(text: &str) -> KernelConfig {
let mut cfg = KernelConfig::default();
for line in text.lines() {
let t = line.trim();
if t.is_empty() {
continue;
}
if let Some(rest) = t.strip_prefix("# CONFIG_") {
if let Some(name) = rest.strip_suffix(" is not set") {
cfg.values.insert(name.to_string(), ConfigValue::No);
}
continue;
}
if let Some(rest) = t.strip_prefix('#') {
let rest = rest.trim();
if cfg.banner.is_none() && rest.contains("Kernel Configuration") {
cfg.banner = Some(rest.to_string());
}
continue;
}
let Some(rest) = t.strip_prefix("CONFIG_") else {
continue;
};
let Some((name, val)) = rest.split_once('=') else {
continue;
};
let v = match val {
"y" => ConfigValue::Yes,
"m" => ConfigValue::Module,
"n" => ConfigValue::No,
_ if val.starts_with('"') => {
ConfigValue::Str(val.trim_matches('"').to_string())
}
_ => ConfigValue::Num(val.to_string()),
};
cfg.values.insert(name.to_string(), v);
}
cfg
}
pub fn get(&self, name: &str) -> Option<&ConfigValue> {
self.values.get(name)
}
/// `true` sólo si el símbolo está `=y` o `=m`. Ausente cuenta como apagado.
pub fn is_on(&self, name: &str) -> bool {
self.values.get(name).is_some_and(|v| v.is_on())
}
/// Los símbolos encendidos (`=y` o `=m`), en orden.
pub fn enabled(&self) -> impl Iterator<Item = &str> {
self.values
.iter()
.filter(|(_, v)| v.is_on())
.map(|(k, _)| k.as_str())
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = r#"
#
# Automatically generated file; DO NOT EDIT.
# Linux/x86 6.16.12 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="gcc (GCC) 15.1.1"
CONFIG_64BIT=y
CONFIG_WLAN=m
# CONFIG_WIRELESS is not set
CONFIG_NR_CPUS=64
"#;
#[test]
fn parsea_las_cuatro_formas() {
let c = KernelConfig::parse(SAMPLE);
assert_eq!(c.get("64BIT"), Some(&ConfigValue::Yes));
assert_eq!(c.get("WLAN"), Some(&ConfigValue::Module));
assert_eq!(c.get("WIRELESS"), Some(&ConfigValue::No));
assert_eq!(c.get("NR_CPUS"), Some(&ConfigValue::Num("64".into())));
assert!(matches!(c.get("CC_VERSION_TEXT"), Some(ConfigValue::Str(_))));
assert_eq!(c.banner.as_deref(), Some("Linux/x86 6.16.12 Kernel Configuration"));
}
#[test]
fn modulo_cuenta_como_encendido() {
let c = KernelConfig::parse(SAMPLE);
assert!(c.is_on("WLAN"));
assert!(!c.is_on("WIRELESS"));
// Ausente ≠ apagado explícito, pero para `is_on` los dos son «no está».
assert!(!c.is_on("NO_EXISTE"));
}
}