// ============================================================================= // renaser :: kernel/src/almacen.rs — Fase 6.1c :: el grafo de objetos // ----------------------------------------------------------------------------- // renaser rompe con POSIX tambien en el almacenamiento: aqui no hay un sistema // de archivos plano —rutas, directorios, inodos—. Hay un GRAFO DIRIGIDO ACICLICO // de objetos DIRECCIONADOS POR CONTENIDO. // // Un objeto es una carga util de bytes y una lista de aristas hacia otros // objetos. Su IDENTIDAD no es un nombre ni un numero: es el hash BLAKE3 de su // forma serializada. De ello se siguen dos propiedades que un FS jamas regala: // // * INTEGRIDAD — el hash verifica el contenido; un objeto corrupto se delata. // * DEDUPLICACION — contenido identico produce hash identico; se almacena // una sola vez, aunque mil aristas apunten a el. // // El disco se organiza como un LOG: el sector 0 es el superbloque —el ancla // del grafo—, y tras el se anexan los registros de objetos, uno tras otro. Un // indice en memoria (hash -> sector) se reconstruye al arrancar recorriendo el // log. // // El FORMATO en disco —los tipos `Objeto`/`SuperBloque`, su (de)serializacion // `postcard`, el hash BLAKE3 y el trazado de cada registro— ya no vive aqui: // habita la crate `formato` (Fase 7b), un nucleo `no_std` COMPARTIDO con el // constructor de imagen `boot`. Este modulo es solo el almacen VIVO: el // cursor, el indice y la E/S contra el disco virtio-blk. // ============================================================================= use alloc::collections::BTreeMap; use alloc::vec; use alloc::vec::Vec; use spin::{Mutex, Once}; use crate::drivers::disco; // El identificador y el objeto del grafo los define `formato`; se reexportan // para que el resto del kernel siga nombrandolos `almacen::Hash` y // `almacen::Objeto`, sin enterarse de donde viven realmente. pub use formato::{Hash, Objeto}; /// El estado vivo del almacen: el cursor del log, la raiz, el manifiesto y el /// indice en memoria que traduce cada hash al sector donde habita su registro. struct Almacen { /// Proximo sector libre del log. cursor: u64, /// El objeto raiz del DAG. raiz: Option, /// El objeto del Manifiesto de Genesis (Fase 7). manifiesto: Option, /// Indice hash -> sector del registro. Se reconstruye al arrancar. indice: BTreeMap, /// Capacidad del disco, en sectores. capacidad: u64, } /// El almacen global de renaser. Se funde una sola vez, en `init`. static ALMACEN: Once> = Once::new(); /// El fruto de fundar el almacen — para que el arranque deje constancia visual. pub struct Resumen { /// Capacidad del disco, en sectores. pub capacidad: u64, /// Numero de objetos hallados en el grafo. pub objetos: usize, /// ¿Tiene el grafo un objeto raiz? pub raiz: bool, /// ¿Se reformateo el disco (estaba virgen o era ajeno)? pub formateado: bool, } /// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el /// disco ya es de renaser, reconstruye el indice recorriendo el log; si es /// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`. pub fn init() -> Result { let capacidad = disco::montar()?; if capacidad < 2 { return Err("el disco es demasiado pequeño para un grafo"); } // Leer el sector 0 e intentar interpretarlo como superbloque de renaser. let mut sector0 = [0u8; formato::TAM_SECTOR]; disco::leer_sectores(0, &mut sector0)?; let (cursor, raiz, manifiesto, indice, formateado) = match formato::SuperBloque::deserializar(§or0) { // Disco de renaser, con la version corriente: adoptar su grafo. Ok(sb) if sb.magia == formato::MAGIA && sb.version == formato::VERSION_SUPERBLOQUE => { let indice = reconstruir_indice(sb.cursor)?; (sb.cursor, sb.raiz, sb.manifiesto, indice, false) } // Disco virgen, ajeno o de otra version: empezar de cero. El log // arranca en el sector 1, justo despues del superbloque. _ => (1, None, None, BTreeMap::new(), true), }; let objetos = indice.len(); let tiene_raiz = raiz.is_some(); let almacen = Almacen { cursor, raiz, manifiesto, indice, capacidad, }; // Un disco recien formateado necesita su superbloque grabado de inmediato. if formateado { persistir(&almacen)?; } ALMACEN.call_once(|| Mutex::new(almacen)); Ok(Resumen { capacidad, objetos, raiz: tiene_raiz, formateado, }) } /// Recorre el log —del sector 1 al `cursor`— y reconstruye el indice /// hash -> sector. Cada registro se rehashea: el indice se reconstruye, no se /// confia. Un registro corrupto detiene el escaneo sin incendiar nada. fn reconstruir_indice(cursor: u64) -> Result, &'static str> { let mut indice = BTreeMap::new(); let mut sector: u64 = 1; while sector < cursor { match leer_registro(sector)? { // Un payload valido: hashearlo e indexarlo. Some(payload) => { let n = formato::sectores_registro(payload.len()); indice.insert(formato::hash(&payload), sector); sector += n; } // Cabecera a cero o longitud imposible: fin (o corrupcion) del log. None => break, } } Ok(indice) } /// Lee el registro que arranca en `sector` y devuelve su payload postcard /// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice /// longitud cero —fin del log— o una longitud imposible —corrupcion—. fn leer_registro(sector: u64) -> Result>, &'static str> { let mut cabecera = [0u8; formato::TAM_SECTOR]; disco::leer_sectores(sector, &mut cabecera)?; let longitud = match formato::longitud_registro(&cabecera) { Some(longitud) => longitud, None => return Ok(None), }; let n = formato::sectores_registro(longitud) as usize; // Si el registro cabe en el sector ya leido, evitar una segunda lectura. let payload = if n == 1 { cabecera[4..4 + longitud].to_vec() } else { let mut buf = vec![0u8; n * formato::TAM_SECTOR]; disco::leer_sectores(sector, &mut buf)?; buf[4..4 + longitud].to_vec() }; Ok(Some(payload)) } /// Graba el superbloque —el ancla del grafo— en el sector 0. fn persistir(almacen: &Almacen) -> Result<(), &'static str> { let sb = formato::SuperBloque { magia: formato::MAGIA, version: formato::VERSION_SUPERBLOQUE, cursor: almacen.cursor, raiz: almacen.raiz, manifiesto: almacen.manifiesto, }; let bytes = sb.serializar()?; if bytes.len() > formato::TAM_SECTOR { return Err("el superbloque no cabe en un sector"); } let mut sector0 = [0u8; formato::TAM_SECTOR]; sector0[..bytes.len()].copy_from_slice(&bytes); disco::escribir_sectores(0, §or0) } /// Almacena un objeto y devuelve su hash. Direccionamiento por contenido en /// estado puro: si un objeto de contenido identico ya existe, NO se reescribe — /// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo. pub fn almacenar(datos: Vec, hijos: Vec) -> Result { let objeto = Objeto { datos, hijos }; let bytes = objeto.serializar()?; if bytes.is_empty() || bytes.len() > formato::MAX_OBJETO { return Err("el objeto tiene un tamaño invalido"); } // La identidad del objeto: el hash de su forma serializada. let hash = formato::hash(&bytes); let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; let mut almacen = mutex.lock(); // ¿Ya esta en el grafo? Entonces no hay nada que grabar. if almacen.indice.contains_key(&hash) { return Ok(hash); } // Reservar los sectores del registro al final del log. let n = formato::sectores_registro(bytes.len()); if almacen.cursor + n > almacen.capacidad { return Err("el grafo de objetos esta lleno"); } let sector = almacen.cursor; // Componer el registro —[longitud][payload][relleno]— y grabarlo. let registro = formato::componer_registro(&bytes); disco::escribir_sectores(sector, ®istro)?; // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el // superbloque. El orden importa — el superbloque se graba el ultimo, de // modo que jamas apunte a un registro a medio escribir. almacen.cursor += n; almacen.indice.insert(hash, sector); persistir(&almacen)?; Ok(hash) } /// Recupera un objeto por su hash. `Ok(None)` si el hash no esta en el grafo. pub fn recuperar(hash: &Hash) -> Result, &'static str> { let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; // Soltar el cerrojo del almacen ANTES de la E/S de disco —lenta, por // sondeo—: el indice ya entrego el sector, y nada mas reclama el cerrojo. let sector = match mutex.lock().indice.get(hash) { Some(&s) => s, None => return Ok(None), }; let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?; // Verificacion de integridad: el contenido leido DEBE rehashear al hash // pedido. Si no, el disco ha mentido — y se delata. if formato::hash(&payload) != *hash { return Err("el objeto no supero la verificacion de integridad"); } Ok(Some(Objeto::deserializar(&payload)?)) } /// El hash del objeto raiz del grafo, si lo hay. pub fn raiz() -> Option { ALMACEN.get().and_then(|mutex| mutex.lock().raiz) } /// Corona un objeto como raiz del grafo y ancla el cambio en el superbloque. pub fn fijar_raiz(hash: Hash) -> Result<(), &'static str> { let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; let mut almacen = mutex.lock(); almacen.raiz = Some(hash); persistir(&almacen) } /// El hash del objeto del Manifiesto de Genesis, si el disco tiene uno /// anclado. Gemelo de [`raiz`], pero del lado del kernel: lo lee la Fase 7 /// para descubrir que apps poblar al arrancar. pub fn manifiesto() -> Option { ALMACEN.get().and_then(|mutex| mutex.lock().manifiesto) } /// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el /// superbloque. Gemelo de [`fijar_raiz`]. La Fase 7c lo invoca cada vez que una /// app persiste su estado y el manifiesto debe re-anclarse. pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> { let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; let mut almacen = mutex.lock(); almacen.manifiesto = Some(hash); persistir(&almacen) }