gioser

tawasuyu/brahman

Rust 334TOML 129Markdown 39JSON 174745 Definiciones

⎇ 7695dbf3ceda3ab6114a460bbae238eae3d91dd8 renaser/kernel/src/almacen.rs Rust · 268 líneas · 10.6 KB Historia Quién tocó qué crudo

feat(renaser): Fase 7b — boot siembra la imagen, muere el include_bytes! 7695dbf3c · sergio ·

1// =============================================================================
2//  renaser :: kernel/src/almacen.rs — Fase 6.1c :: el grafo de objetos
3// -----------------------------------------------------------------------------
4//  renaser rompe con POSIX tambien en el almacenamiento: aqui no hay un sistema
5//  de archivos plano —rutas, directorios, inodos—. Hay un GRAFO DIRIGIDO ACICLICO
6//  de objetos DIRECCIONADOS POR CONTENIDO.
7//
8//  Un objeto es una carga util de bytes y una lista de aristas hacia otros
9//  objetos. Su IDENTIDAD no es un nombre ni un numero: es el hash BLAKE3 de su
10//  forma serializada. De ello se siguen dos propiedades que un FS jamas regala:
11//
12//    * INTEGRIDAD — el hash verifica el contenido; un objeto corrupto se delata.
13//    * DEDUPLICACION — contenido identico produce hash identico; se almacena
14//      una sola vez, aunque mil aristas apunten a el.
15//
16//  El disco se organiza como un LOG: el sector 0 es el superbloque —el ancla
17//  del grafo—, y tras el se anexan los registros de objetos, uno tras otro. Un
18//  indice en memoria (hash -> sector) se reconstruye al arrancar recorriendo el
19//  log.
20//
21//  El FORMATO en disco —los tipos `Objeto`/`SuperBloque`, su (de)serializacion
22//  `postcard`, el hash BLAKE3 y el trazado de cada registro— ya no vive aqui:
23//  habita la crate `formato` (Fase 7b), un nucleo `no_std` COMPARTIDO con el
24//  constructor de imagen `boot`. Este modulo es solo el almacen VIVO: el
25//  cursor, el indice y la E/S contra el disco virtio-blk.
26// =============================================================================
27
28use alloc::collections::BTreeMap;
29use alloc::vec;
30use alloc::vec::Vec;
31
32use spin::{Mutex, Once};
33
34use crate::drivers::disco;
35
36// El identificador y el objeto del grafo los define `formato`; se reexportan
37// para que el resto del kernel siga nombrandolos `almacen::Hash` y
38// `almacen::Objeto`, sin enterarse de donde viven realmente.
39pub use formato::{Hash, Objeto};
40
41/// El estado vivo del almacen: el cursor del log, la raiz, el manifiesto y el
42/// indice en memoria que traduce cada hash al sector donde habita su registro.
43struct Almacen {
44    /// Proximo sector libre del log.
45    cursor: u64,
46    /// El objeto raiz del DAG.
47    raiz: Option<Hash>,
48    /// El objeto del Manifiesto de Genesis (Fase 7).
49    manifiesto: Option<Hash>,
50    /// Indice hash -> sector del registro. Se reconstruye al arrancar.
51    indice: BTreeMap<Hash, u64>,
52    /// Capacidad del disco, en sectores.
53    capacidad: u64,
54}
55
56/// El almacen global de renaser. Se funde una sola vez, en `init`.
57static ALMACEN: Once<Mutex<Almacen>> = Once::new();
58
59/// El fruto de fundar el almacen — para que el arranque deje constancia visual.
60pub struct Resumen {
61    /// Capacidad del disco, en sectores.
62    pub capacidad: u64,
63    /// Numero de objetos hallados en el grafo.
64    pub objetos: usize,
65    /// ¿Tiene el grafo un objeto raiz?
66    pub raiz: bool,
67    /// ¿Se reformateo el disco (estaba virgen o era ajeno)?
68    pub formateado: bool,
69}
70
71/// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el
72/// disco ya es de renaser, reconstruye el indice recorriendo el log; si es
73/// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`.
74pub fn init() -> Result<Resumen, &'static str> {
75    let capacidad = disco::montar()?;
76    if capacidad < 2 {
77        return Err("el disco es demasiado pequeño para un grafo");
78    }
79
80    // Leer el sector 0 e intentar interpretarlo como superbloque de renaser.
81    let mut sector0 = [0u8; formato::TAM_SECTOR];
82    disco::leer_sectores(0, &mut sector0)?;
83
84    let (cursor, raiz, manifiesto, indice, formateado) =
85        match formato::SuperBloque::deserializar(&sector0) {
86            // Disco de renaser, con la version corriente: adoptar su grafo.
87            Ok(sb) if sb.magia == formato::MAGIA && sb.version == formato::VERSION_SUPERBLOQUE => {
88                let indice = reconstruir_indice(sb.cursor)?;
89                (sb.cursor, sb.raiz, sb.manifiesto, indice, false)
90            }
91            // Disco virgen, ajeno o de otra version: empezar de cero. El log
92            // arranca en el sector 1, justo despues del superbloque.
93            _ => (1, None, None, BTreeMap::new(), true),
94        };
95
96    let objetos = indice.len();
97    let tiene_raiz = raiz.is_some();
98    let almacen = Almacen {
99        cursor,
100        raiz,
101        manifiesto,
102        indice,
103        capacidad,
104    };
105
106    // Un disco recien formateado necesita su superbloque grabado de inmediato.
107    if formateado {
108        persistir(&almacen)?;
109    }
110    ALMACEN.call_once(|| Mutex::new(almacen));
111
112    Ok(Resumen {
113        capacidad,
114        objetos,
115        raiz: tiene_raiz,
116        formateado,
117    })
118}
119
120/// Recorre el log —del sector 1 al `cursor`— y reconstruye el indice
121/// hash -> sector. Cada registro se rehashea: el indice se reconstruye, no se
122/// confia. Un registro corrupto detiene el escaneo sin incendiar nada.
123fn reconstruir_indice(cursor: u64) -> Result<BTreeMap<Hash, u64>, &'static str> {
124    let mut indice = BTreeMap::new();
125    let mut sector: u64 = 1;
126    while sector < cursor {
127        match leer_registro(sector)? {
128            // Un payload valido: hashearlo e indexarlo.
129            Some(payload) => {
130                let n = formato::sectores_registro(payload.len());
131                indice.insert(formato::hash(&payload), sector);
132                sector += n;
133            }
134            // Cabecera a cero o longitud imposible: fin (o corrupcion) del log.
135            None => break,
136        }
137    }
138    Ok(indice)
139}
140
141/// Lee el registro que arranca en `sector` y devuelve su payload postcard
142/// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice
143/// longitud cero —fin del log— o una longitud imposible —corrupcion—.
144fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> {
145    let mut cabecera = [0u8; formato::TAM_SECTOR];
146    disco::leer_sectores(sector, &mut cabecera)?;
147    let longitud = match formato::longitud_registro(&cabecera) {
148        Some(longitud) => longitud,
149        None => return Ok(None),
150    };
151    let n = formato::sectores_registro(longitud) as usize;
152    // Si el registro cabe en el sector ya leido, evitar una segunda lectura.
153    let payload = if n == 1 {
154        cabecera[4..4 + longitud].to_vec()
155    } else {
156        let mut buf = vec![0u8; n * formato::TAM_SECTOR];
157        disco::leer_sectores(sector, &mut buf)?;
158        buf[4..4 + longitud].to_vec()
159    };
160    Ok(Some(payload))
161}
162
163/// Graba el superbloque —el ancla del grafo— en el sector 0.
164fn persistir(almacen: &Almacen) -> Result<(), &'static str> {
165    let sb = formato::SuperBloque {
166        magia: formato::MAGIA,
167        version: formato::VERSION_SUPERBLOQUE,
168        cursor: almacen.cursor,
169        raiz: almacen.raiz,
170        manifiesto: almacen.manifiesto,
171    };
172    let bytes = sb.serializar()?;
173    if bytes.len() > formato::TAM_SECTOR {
174        return Err("el superbloque no cabe en un sector");
175    }
176    let mut sector0 = [0u8; formato::TAM_SECTOR];
177    sector0[..bytes.len()].copy_from_slice(&bytes);
178    disco::escribir_sectores(0, &sector0)
179}
180
181/// Almacena un objeto y devuelve su hash. Direccionamiento por contenido en
182/// estado puro: si un objeto de contenido identico ya existe, NO se reescribe —
183/// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo.
184pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> {
185    let objeto = Objeto { datos, hijos };
186    let bytes = objeto.serializar()?;
187    if bytes.is_empty() || bytes.len() > formato::MAX_OBJETO {
188        return Err("el objeto tiene un tamaño invalido");
189    }
190    // La identidad del objeto: el hash de su forma serializada.
191    let hash = formato::hash(&bytes);
192
193    let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
194    let mut almacen = mutex.lock();
195
196    // ¿Ya esta en el grafo? Entonces no hay nada que grabar.
197    if almacen.indice.contains_key(&hash) {
198        return Ok(hash);
199    }
200
201    // Reservar los sectores del registro al final del log.
202    let n = formato::sectores_registro(bytes.len());
203    if almacen.cursor + n > almacen.capacidad {
204        return Err("el grafo de objetos esta lleno");
205    }
206    let sector = almacen.cursor;
207
208    // Componer el registro —[longitud][payload][relleno]— y grabarlo.
209    let registro = formato::componer_registro(&bytes);
210    disco::escribir_sectores(sector, &registro)?;
211
212    // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el
213    // superbloque. El orden importa — el superbloque se graba el ultimo, de
214    // modo que jamas apunte a un registro a medio escribir.
215    almacen.cursor += n;
216    almacen.indice.insert(hash, sector);
217    persistir(&almacen)?;
218
219    Ok(hash)
220}
221
222/// Recupera un objeto por su hash. `Ok(None)` si el hash no esta en el grafo.
223pub fn recuperar(hash: &Hash) -> Result<Option<Objeto>, &'static str> {
224    let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
225    // Soltar el cerrojo del almacen ANTES de la E/S de disco —lenta, por
226    // sondeo—: el indice ya entrego el sector, y nada mas reclama el cerrojo.
227    let sector = match mutex.lock().indice.get(hash) {
228        Some(&s) => s,
229        None => return Ok(None),
230    };
231    let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?;
232    // Verificacion de integridad: el contenido leido DEBE rehashear al hash
233    // pedido. Si no, el disco ha mentido — y se delata.
234    if formato::hash(&payload) != *hash {
235        return Err("el objeto no supero la verificacion de integridad");
236    }
237    Ok(Some(Objeto::deserializar(&payload)?))
238}
239
240/// El hash del objeto raiz del grafo, si lo hay.
241pub fn raiz() -> Option<Hash> {
242    ALMACEN.get().and_then(|mutex| mutex.lock().raiz)
243}
244
245/// Corona un objeto como raiz del grafo y ancla el cambio en el superbloque.
246pub fn fijar_raiz(hash: Hash) -> Result<(), &'static str> {
247    let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
248    let mut almacen = mutex.lock();
249    almacen.raiz = Some(hash);
250    persistir(&almacen)
251}
252
253/// El hash del objeto del Manifiesto de Genesis, si el disco tiene uno
254/// anclado. Gemelo de [`raiz`], pero del lado del kernel: lo lee la Fase 7
255/// para descubrir que apps poblar al arrancar.
256pub fn manifiesto() -> Option<Hash> {
257    ALMACEN.get().and_then(|mutex| mutex.lock().manifiesto)
258}
259
260/// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el
261/// superbloque. Gemelo de [`fijar_raiz`].
262#[allow(dead_code)] // Lo usara la Fase 7c (persistencia inter-sesion).
263pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> {
264    let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
265    let mut almacen = mutex.lock();
266    almacen.manifiesto = Some(hash);
267    persistir(&almacen)
268}