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.
146 lines
4.6 KiB
Rust
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"));
|
|
}
|
|
}
|