⎇ e77a32f4d6a09fe29701811f4d2e60dd2cb678db
crates/modules/semantic_dht/minga-vfs/src/lib.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(minga): minga-vfs — proyecta el repo como filesystem FUSE
1//! `minga-vfs`: proyecta el repositorio de Minga —direccionado por
2//! contenido semántico— como un filesystem FUSE de **sólo lectura**.
3//!
4//! Minga guarda código como un grafo de `StoredNode`s identificados por
5//! `ContentHash`; los archivos ingeridos son las raíces del MST. Este
6//! crate convierte ese grafo en algo que cualquier herramienta Unix
7//! (`ls`, `cat`, `grep`, un editor) puede recorrer, sin exponer `sled`
8//! ni la API del store.
9//!
10//! ## Layout del filesystem
11//!
12//! ```text
13//! <punto-de-montaje>/
14//! ├── README explicación del propio VFS
15//! ├── roots/ un archivo por raíz del MST (cada archivo ingerido)
16//! │ └── <hash64> código fuente reconstruido, formato normalizado
17//! └── cas/ cualquier nodo del store, resuelto bajo demanda
18//! └── <hash64> S-expression del subárbol con ese hash
19//! ```
20//!
21//! `roots/` **se enumera** (`ls` lista todas las raíces). `cas/` no se
22//! enumera —son potencialmente decenas de miles de nodos— pero
23//! `cas/<hash>` resuelve cualquier hash conocido: ése es el "blob por
24//! hash bajo demanda". El mismo hash bajo `roots/` y bajo `cas/` da dos
25//! vistas del mismo nodo: fuente reconstruida vs. árbol literal.
26//!
27//! ## Arquitectura (separabilidad)
28//!
29//! - [`render`] — `SemanticNode` → texto. Lógica pura, sin IO ni FUSE;
30//! reutilizable por un frontend web o TUI.
31//! - [`source`] — el contrato [`NodeSource`] y sus backends (`sled`
32//! vía [`RepoSource`], memoria vía [`MemSource`]).
33//! - `fs` — el único módulo acoplado a `fuser`: traduce el contrato a
34//! la `Filesystem` trait.
35
36mod fs;
37pub mod render;
38pub mod source;
39
40pub use fs::MingaFs;
41pub use source::{reconstruct, MemSource, NodeSource, RepoSource};
42
43use std::io;
44use std::path::Path;
45
46use fuser::MountOption;
47
48/// Opciones de montaje comunes: sólo lectura, etiquetado como `minga`
49/// para que aparezca legible en `mount` / `df`.
50fn mount_options() -> Vec<MountOption> {
51 vec![
52 MountOption::RO,
53 MountOption::FSName("minga".to_string()),
54 MountOption::Subtype("minga".to_string()),
55 ]
56}
57
58/// Monta el VFS en `mountpoint` y **bloquea** hasta que se desmonte
59/// (`fusermount -u <punto>`, `umount`, o una señal al proceso).
60///
61/// El punto de montaje debe ser un directorio existente. El filesystem
62/// es de sólo lectura: toda escritura falla con `EROFS`/`EACCES`.
63pub fn mount<S, P>(source: S, mountpoint: P) -> io::Result<()>
64where
65 S: NodeSource,
66 P: AsRef<Path>,
67{
68 fuser::mount2(MingaFs::new(source), mountpoint, &mount_options())
69}
70
71/// Como [`mount`] pero spawnea un hilo de fondo y retorna de inmediato.
72/// La sesión queda viva mientras el `BackgroundSession` no se dropee;
73/// dropearlo desmonta el filesystem.
74pub fn spawn_mount<S, P>(source: S, mountpoint: P) -> io::Result<fuser::BackgroundSession>
75where
76 S: NodeSource + Send + 'static,
77 P: AsRef<Path>,
78{
79 fuser::spawn_mount2(MingaFs::new(source), mountpoint, &mount_options())
80}