//! Composable filesystem layers, open files, mount routing, and process IO guards. pub mod adapter; pub mod generated; pub mod host; pub mod memory; pub mod mounts; pub mod observe; pub mod preview1; pub mod process; mod readonly; mod streams; pub use async_trait::async_trait; pub use generated::Generated; pub use host::Host; pub use memory::Memory; pub use mounts::Mounts; pub use process::Process; use std::{io, sync::Arc, time::SystemTime}; /// A filesystem result. Error kinds preserve portable filesystem failures. pub type Result = io::Result; /// The type and timestamps of a node. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Stat { pub directory: bool, pub symlink: bool, pub size: u64, pub accessed: Option, pub modified: Option, } /// One child of a folder. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { pub name: String, pub directory: bool, pub symlink: bool, } /// Options for an open operation. Exclusive applies only with create. #[derive(Clone, Copy, Debug, Default)] pub struct Open { /// Follow the final symbolic link when true. pub follow: bool, pub write: bool, pub create: bool, pub exclusive: bool, pub truncate: bool, pub directory: bool, } /// An open file keeps its identity after rename or unlink. #[async_trait] pub trait File: Send + Sync { async fn stat(&self) -> Result; async fn read(&self, offset: u64, length: usize) -> Result>; async fn write(&self, offset: u64, bytes: Vec) -> Result; async fn append(&self, bytes: Vec) -> Result; async fn set_size(&self, size: u64) -> Result<()>; async fn sync(&self) -> Result<()>; async fn set_times( &self, accessed: Option, modified: Option, ) -> Result<()>; } /// One filesystem layer. Paths are normalized, absolute, and relative to its root. /// Implementations must retain open file identity and return portable IO errors. #[async_trait] pub trait Layer: Send + Sync { async fn stat(&self, path: &str, follow: bool) -> Result; /// Return the target of a symbolic link, or InvalidInput for another node. async fn readlink(&self, _path: &str) -> Result { Err(error(io::ErrorKind::InvalidInput)) } async fn list(&self, path: &str) -> Result>; async fn open(&self, path: &str, options: Open) -> Result>; async fn mkdir(&self, path: &str) -> Result<()>; async fn remove_file(&self, path: &str) -> Result<()>; async fn remove_dir(&self, path: &str) -> Result<()>; async fn rename(&self, from: &str, to: &str) -> Result<()>; } pub(crate) fn error(kind: io::ErrorKind) -> io::Error { kind.into() } /// Resolve a path in the virtual namespace, including traversal across mounts. pub fn resolve(base: &str, path: &str) -> String { let mut parts = Vec::new(); if !path.starts_with('/') { parts.extend(base.split('/').filter(|s| !s.is_empty())); } for part in path.split('/') { match part { "" | "." => {} ".." => { parts.pop(); } p => parts.push(p), } } let mut result = format!("/{}", parts.join("/")); if path.ends_with('/') && result != "/" { result.push('/'); } result }