⎇ 43e6b32e15dea99bd57a9a11fdafa7bd9a2a236f
renaser/kernel/src/almacen.rs
History
Who touched what
raw
feat(renaser): Fase 7a — el userspace nace del Grafo de Objetos
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. La serializacion la hace `postcard`: binaria, compacta, determinista.
20// =============================================================================
21
22use alloc::collections::BTreeMap;
23use alloc::vec;
24use alloc::vec::Vec;
25
26use serde::{Deserialize, Serialize};
27use spin::{Mutex, Once};
28
29use crate::drivers::disco::{self, TAM_SECTOR};
30
31/// Firma magica del superbloque — «RENASer GRaFo». Distingue un disco de
32/// renaser de uno virgen o ajeno.
33const MAGIA: [u8; 8] = *b"RENASGRF";
34
35/// Version del formato en disco. Un disco con otra version se reformatea.
36/// v2 (Fase 7) — el superbloque gana el ancla `manifiesto`; un disco v1 se
37/// reformatea al arrancar, como cualquier disco ajeno.
38const VERSION: u32 = 2;
39
40/// Techo del tamaño de un objeto serializado: 1 MiB. Acota los buferes de E/S
41/// y permite descartar un registro corrupto sin intentar leer un disparate.
42const MAX_OBJETO: usize = 1024 * 1024;
43
44/// El identificador de un objeto: el hash BLAKE3 de su forma serializada. En un
45/// almacen direccionado por contenido, la identidad ES el contenido.
46pub type Hash = [u8; 32];
47
48/// Un objeto del grafo: una carga util opaca y las aristas que lo enlazan con
49/// otros objetos. Los `hijos` hacen del almacen un DAG —no un arbol, no una
50/// lista—: un objeto puede ser hijo de muchos, y el direccionamiento por
51/// contenido garantiza que cada contenido distinto se guarda una sola vez.
52#[derive(Serialize, Deserialize, Clone)]
53pub struct Objeto {
54 /// La carga util del objeto: bytes crudos, que el kernel no interpreta.
55 pub datos: Vec<u8>,
56 /// Los hashes de los objetos hijos: las aristas salientes del DAG.
57 pub hijos: Vec<Hash>,
58}
59
60/// El superbloque: el sector 0 del disco. Ancla el grafo entero — dice por
61/// donde continua el log, cual es el objeto raiz y cual el manifiesto.
62#[derive(Serialize, Deserialize)]
63struct SuperBloque {
64 /// Firma magica: debe ser [`MAGIA`].
65 magia: [u8; 8],
66 /// Version del formato: debe ser [`VERSION`].
67 version: u32,
68 /// Proximo sector libre del log — donde se anexara el siguiente objeto.
69 cursor: u64,
70 /// El objeto raiz del DAG: el punto de entrada que el userspace fija y lee.
71 raiz: Option<Hash>,
72 /// El Manifiesto de Genesis (Fase 7): el objeto que dicta que apps nacen
73 /// del grafo al arrancar. Ancla del kernel, gemela de `raiz` (del userspace).
74 manifiesto: Option<Hash>,
75}
76
77/// El estado vivo del almacen: el cursor del log, la raiz, el manifiesto y el
78/// indice en memoria que traduce cada hash al sector donde habita su registro.
79struct Almacen {
80 /// Proximo sector libre del log.
81 cursor: u64,
82 /// El objeto raiz del DAG.
83 raiz: Option<Hash>,
84 /// El objeto del Manifiesto de Genesis (Fase 7).
85 manifiesto: Option<Hash>,
86 /// Indice hash -> sector del registro. Se reconstruye al arrancar.
87 indice: BTreeMap<Hash, u64>,
88 /// Capacidad del disco, en sectores.
89 capacidad: u64,
90}
91
92/// El almacen global de renaser. Se funde una sola vez, en `init`.
93static ALMACEN: Once<Mutex<Almacen>> = Once::new();
94
95/// El fruto de fundar el almacen — para que el arranque deje constancia visual.
96pub struct Resumen {
97 /// Capacidad del disco, en sectores.
98 pub capacidad: u64,
99 /// Numero de objetos hallados en el grafo.
100 pub objetos: usize,
101 /// ¿Tiene el grafo un objeto raiz?
102 pub raiz: bool,
103 /// ¿Se reformateo el disco (estaba virgen o era ajeno)?
104 pub formateado: bool,
105}
106
107/// Numero de sectores que ocupa un registro cuyo payload mide `longitud` bytes.
108/// Cada registro en disco es: `[longitud: u32 LE][payload postcard][relleno 0]`.
109fn sectores_registro(longitud: usize) -> u64 {
110 (4 + longitud).div_ceil(TAM_SECTOR) as u64
111}
112
113/// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el
114/// disco ya es de renaser, reconstruye el indice recorriendo el log; si es
115/// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`.
116pub fn init() -> Result<Resumen, &'static str> {
117 let capacidad = disco::montar()?;
118 if capacidad < 2 {
119 return Err("el disco es demasiado pequeño para un grafo");
120 }
121
122 // Leer el sector 0 e intentar interpretarlo como superbloque de renaser.
123 let mut sector0 = [0u8; TAM_SECTOR];
124 disco::leer_sectores(0, &mut sector0)?;
125
126 let (cursor, raiz, manifiesto, indice, formateado) =
127 match postcard::take_from_bytes::<SuperBloque>(§or0) {
128 // Disco de renaser, con la version corriente: adoptar su grafo.
129 Ok((sb, _)) if sb.magia == MAGIA && sb.version == VERSION => {
130 let indice = reconstruir_indice(sb.cursor)?;
131 (sb.cursor, sb.raiz, sb.manifiesto, indice, false)
132 }
133 // Disco virgen, ajeno o de otra version: empezar de cero. El log
134 // arranca en el sector 1, justo despues del superbloque.
135 _ => (1, None, None, BTreeMap::new(), true),
136 };
137
138 let objetos = indice.len();
139 let tiene_raiz = raiz.is_some();
140 let almacen = Almacen {
141 cursor,
142 raiz,
143 manifiesto,
144 indice,
145 capacidad,
146 };
147
148 // Un disco recien formateado necesita su superbloque grabado de inmediato.
149 if formateado {
150 persistir(&almacen)?;
151 }
152 ALMACEN.call_once(|| Mutex::new(almacen));
153
154 Ok(Resumen {
155 capacidad,
156 objetos,
157 raiz: tiene_raiz,
158 formateado,
159 })
160}
161
162/// Recorre el log —del sector 1 al `cursor`— y reconstruye el indice
163/// hash -> sector. Cada registro se rehashea: el indice se reconstruye, no se
164/// confia. Un registro corrupto detiene el escaneo sin incendiar nada.
165fn reconstruir_indice(cursor: u64) -> Result<BTreeMap<Hash, u64>, &'static str> {
166 let mut indice = BTreeMap::new();
167 let mut sector: u64 = 1;
168 while sector < cursor {
169 let payload = leer_registro(sector)?;
170 match payload {
171 // Un payload valido: hashearlo e indexarlo.
172 Some(payload) => {
173 let n = sectores_registro(payload.len());
174 let hash = *blake3::hash(&payload).as_bytes();
175 indice.insert(hash, sector);
176 sector += n;
177 }
178 // Cabecera a cero o longitud imposible: fin (o corrupcion) del log.
179 None => break,
180 }
181 }
182 Ok(indice)
183}
184
185/// Lee el registro que arranca en `sector` y devuelve su payload postcard
186/// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice
187/// longitud cero —fin del log— o una longitud imposible —corrupcion—.
188fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> {
189 let mut cabecera = [0u8; TAM_SECTOR];
190 disco::leer_sectores(sector, &mut cabecera)?;
191 let longitud =
192 u32::from_le_bytes([cabecera[0], cabecera[1], cabecera[2], cabecera[3]]) as usize;
193 if longitud == 0 || longitud > MAX_OBJETO {
194 return Ok(None);
195 }
196 let n = sectores_registro(longitud) as usize;
197 // Si el registro cabe en el sector ya leido, evitar una segunda lectura.
198 let payload = if n == 1 {
199 cabecera[4..4 + longitud].to_vec()
200 } else {
201 let mut buf = vec![0u8; n * TAM_SECTOR];
202 disco::leer_sectores(sector, &mut buf)?;
203 buf[4..4 + longitud].to_vec()
204 };
205 Ok(Some(payload))
206}
207
208/// Graba el superbloque —el ancla del grafo— en el sector 0.
209fn persistir(almacen: &Almacen) -> Result<(), &'static str> {
210 let sb = SuperBloque {
211 magia: MAGIA,
212 version: VERSION,
213 cursor: almacen.cursor,
214 raiz: almacen.raiz,
215 manifiesto: almacen.manifiesto,
216 };
217 let bytes = postcard::to_allocvec(&sb).map_err(|_| "no se pudo serializar el superbloque")?;
218 if bytes.len() > TAM_SECTOR {
219 return Err("el superbloque no cabe en un sector");
220 }
221 let mut sector0 = [0u8; TAM_SECTOR];
222 sector0[..bytes.len()].copy_from_slice(&bytes);
223 disco::escribir_sectores(0, §or0)
224}
225
226/// Almacena un objeto y devuelve su hash. Direccionamiento por contenido en
227/// estado puro: si un objeto de contenido identico ya existe, NO se reescribe —
228/// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo.
229pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> {
230 let objeto = Objeto { datos, hijos };
231 let bytes =
232 postcard::to_allocvec(&objeto).map_err(|_| "no se pudo serializar el objeto")?;
233 if bytes.is_empty() || bytes.len() > MAX_OBJETO {
234 return Err("el objeto tiene un tamaño invalido");
235 }
236 // La identidad del objeto: el hash de su forma serializada.
237 let hash = *blake3::hash(&bytes).as_bytes();
238
239 let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
240 let mut almacen = mutex.lock();
241
242 // ¿Ya esta en el grafo? Entonces no hay nada que grabar.
243 if almacen.indice.contains_key(&hash) {
244 return Ok(hash);
245 }
246
247 // Reservar los sectores del registro al final del log.
248 let n = sectores_registro(bytes.len());
249 if almacen.cursor + n > almacen.capacidad {
250 return Err("el grafo de objetos esta lleno");
251 }
252 let sector = almacen.cursor;
253
254 // Componer el registro: [longitud][payload][relleno a cero] y grabarlo.
255 let mut registro = vec![0u8; n as usize * TAM_SECTOR];
256 registro[0..4].copy_from_slice(&(bytes.len() as u32).to_le_bytes());
257 registro[4..4 + bytes.len()].copy_from_slice(&bytes);
258 disco::escribir_sectores(sector, ®istro)?;
259
260 // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el
261 // superbloque. El orden importa — el superbloque se graba el ultimo, de
262 // modo que jamas apunte a un registro a medio escribir.
263 almacen.cursor += n;
264 almacen.indice.insert(hash, sector);
265 persistir(&almacen)?;
266
267 Ok(hash)
268}
269
270/// Recupera un objeto por su hash. `Ok(None)` si el hash no esta en el grafo.
271pub fn recuperar(hash: &Hash) -> Result<Option<Objeto>, &'static str> {
272 let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
273 // Soltar el cerrojo del almacen ANTES de la E/S de disco —lenta, por
274 // sondeo—: el indice ya entrego el sector, y nada mas reclama el cerrojo.
275 let sector = match mutex.lock().indice.get(hash) {
276 Some(&s) => s,
277 None => return Ok(None),
278 };
279 let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?;
280 // Verificacion de integridad: el contenido leido DEBE rehashear al hash
281 // pedido. Si no, el disco ha mentido — y se delata.
282 if *blake3::hash(&payload).as_bytes() != *hash {
283 return Err("el objeto no supero la verificacion de integridad");
284 }
285 let (objeto, _) = postcard::take_from_bytes::<Objeto>(&payload)
286 .map_err(|_| "no se pudo deserializar el objeto")?;
287 Ok(Some(objeto))
288}
289
290/// El hash del objeto raiz del grafo, si lo hay.
291pub fn raiz() -> Option<Hash> {
292 ALMACEN.get().and_then(|mutex| mutex.lock().raiz)
293}
294
295/// Corona un objeto como raiz del grafo y ancla el cambio en el superbloque.
296pub fn fijar_raiz(hash: Hash) -> Result<(), &'static str> {
297 let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
298 let mut almacen = mutex.lock();
299 almacen.raiz = Some(hash);
300 persistir(&almacen)
301}
302
303/// El hash del objeto del Manifiesto de Genesis, si el disco tiene uno
304/// anclado. Gemelo de [`raiz`], pero del lado del kernel: lo lee la Fase 7
305/// para descubrir que apps poblar al arrancar.
306pub fn manifiesto() -> Option<Hash> {
307 ALMACEN.get().and_then(|mutex| mutex.lock().manifiesto)
308}
309
310/// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el
311/// superbloque. Gemelo de [`fijar_raiz`].
312pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> {
313 let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
314 let mut almacen = mutex.lock();
315 almacen.manifiesto = Some(hash);
316 persistir(&almacen)
317}