⎇ 7695dbf3ceda3ab6114a460bbae238eae3d91dd8
renaser/kernel/src/almacen.rs
Historia
Quién tocó qué
crudo
feat(renaser): Fase 7b — boot siembra la imagen, muere el include_bytes!
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(§or0) {
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, §or0)
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, ®istro)?;
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}