//! Shared in-memory directories and identity-preserving open files. use crate::*; use std::{collections::BTreeMap, io::ErrorKind as E, sync::Mutex}; #[derive(Clone)] enum Node { Dir(Arc>), File(Arc>), } #[derive(Default)] struct Data { bytes: Vec, accessed: Option, modified: Option, } /// A shareable memory filesystem. Clones refer to the same tree. #[derive(Clone)] pub struct Memory { nodes: Arc>>, } impl Default for Memory { fn default() -> Self { Self { nodes: Arc::new(Mutex::new(BTreeMap::from([( "/".into(), Node::Dir(Arc::default()), )]))), } } } fn parent(path: &str) -> &str { path.rsplit_once('/') .map(|(p, _)| if p.is_empty() { "/" } else { p }) .unwrap_or("/") } fn validate(nodes: &BTreeMap, path: &str) -> Result<()> { let mut prefix = String::new(); let parts: Vec<_> = path .trim_end_matches('/') .split('/') .filter(|p| !p.is_empty()) .collect(); for part in parts.iter().take(parts.len().saturating_sub(1)) { prefix.push('/'); prefix.push_str(part); directory(nodes, &prefix)?; } if path.ends_with('/') && path != "/" { directory(nodes, path.trim_end_matches('/'))?; } Ok(()) } fn clean(path: &str) -> &str { if path == "/" { path } else { path.trim_end_matches('/') } } fn directory(nodes: &BTreeMap, path: &str) -> Result<()> { match nodes.get(path) { Some(Node::Dir(_)) => Ok(()), Some(_) => Err(error(E::NotADirectory)), None => Err(error(E::NotFound)), } } fn metadata(node: &Node) -> Stat { match node { Node::Dir(data) => { let data = data.lock().unwrap(); Stat { directory: true, symlink: false, size: 0, accessed: data.accessed, modified: data.modified, } } Node::File(data) => { let d = data.lock().unwrap(); Stat { directory: false, symlink: false, size: d.bytes.len() as u64, accessed: d.accessed, modified: d.modified, } } } } struct Handle { node: Node, write: bool, } #[async_trait] impl Layer for Memory { async fn stat(&self, path: &str, follow: bool) -> Result { let _ = follow; let nodes = self.nodes.lock().unwrap(); validate(&nodes, path)?; nodes .get(clean(path)) .map(metadata) .ok_or_else(|| error(E::NotFound)) } async fn list(&self, path: &str) -> Result> { let nodes = self.nodes.lock().unwrap(); validate(&nodes, path)?; let path = clean(path); directory(&nodes, path)?; Ok(nodes .iter() .filter(|(p, _)| p.as_str() != path && parent(p) == path) .map(|(p, n)| Entry { name: p.rsplit('/').next().unwrap().into(), directory: matches!(n, Node::Dir(_)), symlink: false, }) .collect()) } async fn open(&self, path: &str, o: Open) -> Result> { let mut nodes = self.nodes.lock().unwrap(); validate(&nodes, path)?; let path = clean(path); if o.truncate && !o.write { return Err(error(E::InvalidInput)); } if nodes.contains_key(path) && o.create && o.exclusive { return Err(error(E::AlreadyExists)); } if !nodes.contains_key(path) { if !o.create || o.directory { return Err(error(E::NotFound)); } directory(&nodes, parent(path))?; nodes.insert( path.into(), Node::File(Arc::new(Mutex::new(Data { bytes: vec![], accessed: None, modified: None, }))), ); } let node = nodes[path].clone(); match &node { Node::Dir(_) if o.write || o.truncate => return Err(error(E::IsADirectory)), Node::File(_) if o.directory => return Err(error(E::NotADirectory)), Node::File(d) if o.truncate => d.lock().unwrap().bytes.clear(), _ => {} } Ok(Arc::new(Handle { node, write: o.write, })) } async fn mkdir(&self, path: &str) -> Result<()> { let mut nodes = self.nodes.lock().unwrap(); validate(&nodes, path)?; let path = clean(path); if nodes.contains_key(path) { return Err(error(E::AlreadyExists)); } directory(&nodes, parent(path))?; nodes.insert(path.into(), Node::Dir(Arc::default())); Ok(()) } async fn remove_file(&self, path: &str) -> Result<()> { let mut nodes = self.nodes.lock().unwrap(); validate(&nodes, path)?; let path = clean(path); match nodes.get(path) { None => return Err(error(E::NotFound)), Some(Node::Dir(_)) => return Err(error(E::IsADirectory)), _ => {} } nodes.remove(path); Ok(()) } async fn remove_dir(&self, path: &str) -> Result<()> { let mut nodes = self.nodes.lock().unwrap(); validate(&nodes, path)?; let path = clean(path); directory(&nodes, path)?; if path == "/" { return Err(error(E::ResourceBusy)); } if nodes.keys().any(|p| p != path && parent(p) == path) { return Err(error(E::DirectoryNotEmpty)); } nodes.remove(path); Ok(()) } async fn rename(&self, from: &str, to: &str) -> Result<()> { let mut nodes = self.nodes.lock().unwrap(); validate(&nodes, from)?; validate(&nodes, to)?; let from = clean(from); let to = clean(to); let node = nodes.get(from).cloned().ok_or_else(|| error(E::NotFound))?; directory(&nodes, parent(to))?; if from == to { return Ok(()); } if from == "/" || to == "/" { return Err(error(E::ResourceBusy)); } if to.starts_with(&format!("{from}/")) { return Err(error(E::InvalidInput)); } if let Some(target) = nodes.get(to) { match (&node, target) { (Node::Dir(_), Node::File(_)) => return Err(error(E::NotADirectory)), (Node::File(_), Node::Dir(_)) if from.starts_with(&format!("{to}/")) => { return Err(error(E::DirectoryNotEmpty)); } (Node::File(_), Node::Dir(_)) => return Err(error(E::IsADirectory)), (Node::Dir(_), Node::Dir(_)) if nodes.keys().any(|p| p != to && parent(p) == to) => { return Err(error(E::DirectoryNotEmpty)); } _ => {} } } let prefix = format!("{from}/"); let moved: Vec<_> = nodes .keys() .filter(|p| p.as_str() == from || p.starts_with(&prefix)) .cloned() .collect(); for p in moved { let n = nodes.remove(&p).unwrap(); nodes.insert(format!("{to}{}", &p[from.len()..]), n); } Ok(()) } } impl Handle { fn data(&self) -> Result<&Mutex> { match &self.node { Node::File(d) => Ok(d), _ => Err(error(E::IsADirectory)), } } fn writable(&self) -> Result<&Mutex> { if !self.write { return Err(error(E::PermissionDenied)); } self.data() } } #[async_trait] impl File for Handle { async fn stat(&self) -> Result { Ok(metadata(&self.node)) } async fn read(&self, offset: u64, length: usize) -> Result> { let d = self.data()?.lock().unwrap(); let start = usize::try_from(offset) .unwrap_or(usize::MAX) .min(d.bytes.len()); Ok(d.bytes[start..start.saturating_add(length).min(d.bytes.len())].to_vec()) } async fn write(&self, offset: u64, bytes: Vec) -> Result { let mut d = self.writable()?.lock().unwrap(); let start = usize::try_from(offset).map_err(|_| error(E::FileTooLarge))?; let end = start .checked_add(bytes.len()) .ok_or_else(|| error(E::FileTooLarge))?; if !bytes.is_empty() { if end > d.bytes.len() { let additional = end - d.bytes.len(); d.bytes .try_reserve(additional) .map_err(|_| error(E::FileTooLarge))?; d.bytes.resize(end, 0); } d.bytes[start..end].copy_from_slice(&bytes); d.modified = Some(SystemTime::now()); } Ok(bytes.len()) } async fn append(&self, bytes: Vec) -> Result { let mut d = self.writable()?.lock().unwrap(); d.bytes .try_reserve(bytes.len()) .map_err(|_| error(E::FileTooLarge))?; d.bytes.extend_from_slice(&bytes); d.modified = Some(SystemTime::now()); Ok(bytes.len()) } async fn set_size(&self, size: u64) -> Result<()> { let mut d = self.writable()?.lock().unwrap(); let size = usize::try_from(size).map_err(|_| error(E::FileTooLarge))?; let additional = size.saturating_sub(d.bytes.len()); d.bytes .try_reserve(additional) .map_err(|_| error(E::FileTooLarge))?; d.bytes.resize(size, 0); d.modified = Some(SystemTime::now()); Ok(()) } async fn sync(&self) -> Result<()> { Ok(()) } async fn set_times( &self, accessed: Option, modified: Option, ) -> Result<()> { let data = match &self.node { Node::Dir(d) | Node::File(d) => d, }; let mut d = data.lock().unwrap(); if accessed.is_some() { d.accessed = accessed; } if modified.is_some() { d.modified = modified; } Ok(()) } }