gioser

tawasuyu/brahman

Rust 521TOML 202Markdown 57JSON 197505 Definitions

⎇ 43e6b32e15dea99bd57a9a11fdafa7bd9a2a236f renaser/kernel/src/almacen.rs Rust · 317 lines · 12.7 KB History Who touched what raw

feat(renaser): Fase 7a — el userspace nace del Grafo de Objetos bce4abd8c · 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. 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>(&sector0) {
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, &sector0)
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, &registro)?;
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}