//! `minga-vfs`: proyecta el repositorio de Minga —direccionado por //! contenido semántico— como un filesystem FUSE de **sólo lectura**. //! //! Minga guarda código como un grafo de `StoredNode`s identificados por //! `ContentHash`; los archivos ingeridos son las raíces del MST. Este //! crate convierte ese grafo en algo que cualquier herramienta Unix //! (`ls`, `cat`, `grep`, un editor) puede recorrer, sin exponer `sled` //! ni la API del store. //! //! ## Layout del filesystem //! //! ```text //! / //! ├── README explicación del propio VFS //! ├── roots/ un archivo por raíz del MST (cada archivo ingerido) //! │ └── código fuente reconstruido, formato normalizado //! └── cas/ cualquier nodo del store, resuelto bajo demanda //! └── S-expression del subárbol con ese hash //! ``` //! //! `roots/` **se enumera** (`ls` lista todas las raíces). `cas/` no se //! enumera —son potencialmente decenas de miles de nodos— pero //! `cas/` resuelve cualquier hash conocido: ése es el "blob por //! hash bajo demanda". El mismo hash bajo `roots/` y bajo `cas/` da dos //! vistas del mismo nodo: fuente reconstruida vs. árbol literal. //! //! ## Arquitectura (separabilidad) //! //! - [`render`] — `SemanticNode` → texto. Lógica pura, sin IO ni FUSE; //! reutilizable por un frontend web o TUI. //! - [`source`] — el contrato [`NodeSource`] y sus backends (`sled` //! vía [`RepoSource`], memoria vía [`MemSource`]). //! - `fs` — el único módulo acoplado a `fuser`: traduce el contrato a //! la `Filesystem` trait. mod fs; pub mod render; pub mod source; pub use fs::MingaFs; pub use source::{reconstruct, MemSource, NodeSource, RepoSource}; use std::io; use std::path::Path; use fuser::MountOption; /// Opciones de montaje comunes: sólo lectura, etiquetado como `minga` /// para que aparezca legible en `mount` / `df`. fn mount_options() -> Vec { vec![ MountOption::RO, MountOption::FSName("minga".to_string()), MountOption::Subtype("minga".to_string()), ] } /// Monta el VFS en `mountpoint` y **bloquea** hasta que se desmonte /// (`fusermount -u `, `umount`, o una señal al proceso). /// /// El punto de montaje debe ser un directorio existente. El filesystem /// es de sólo lectura: toda escritura falla con `EROFS`/`EACCES`. pub fn mount(source: S, mountpoint: P) -> io::Result<()> where S: NodeSource, P: AsRef, { fuser::mount2(MingaFs::new(source), mountpoint, &mount_options()) } /// Como [`mount`] pero spawnea un hilo de fondo y retorna de inmediato. /// La sesión queda viva mientras el `BackgroundSession` no se dropee; /// dropearlo desmonta el filesystem. pub fn spawn_mount(source: S, mountpoint: P) -> io::Result where S: NodeSource + Send + 'static, P: AsRef, { fuser::spawn_mount2(MingaFs::new(source), mountpoint, &mount_options()) }