use std::{
collections::HashMap,
io,
path::{Component, Path},
sync::{Arc, Mutex},
time::SystemTime,
};
use crate::{
BoxFuture,
fs::{Dirent, DirentKind, FileSystem, Stat},
lock::lock,
};
const MAX_FILE_SIZE: u64 = 1 << 30;
fn checked_end(offset: u64, len: usize) -> io::Result<usize> {
match offset.checked_add(len as u64) {
Some(end) if end <= MAX_FILE_SIZE => Ok(end as usize),
_ => Err(io::ErrorKind::FileTooLarge.into()),
}
}
type Link = Arc<Mutex<Node>>;
enum Node {
Dir {
children: HashMap<String, Link>,
mtime: SystemTime,
created: SystemTime,
},
File {
bytes: Vec<u8>,
mtime: SystemTime,
created: SystemTime,
},
}
impl Node {
fn new_dir() -> Link {
let now = SystemTime::now();
Arc::new(Mutex::new(Node::Dir {
children: HashMap::new(),
mtime: now,
created: now,
}))
}
fn new_file() -> Link {
let now = SystemTime::now();
Arc::new(Mutex::new(Node::File {
bytes: Vec::new(),
mtime: now,
created: now,
}))
}
fn stat(&self) -> Stat {
match self {
Node::Dir { mtime, created, .. } => Stat {
mtime: Some(*mtime),
created: Some(*created),
..Stat::new(DirentKind::Dir, 0)
},
Node::File {
bytes,
mtime,
created,
} => Stat {
mtime: Some(*mtime),
created: Some(*created),
..Stat::new(DirentKind::File, bytes.len() as u64)
},
}
}
fn touch(&mut self) {
match self {
Node::Dir { mtime, .. } | Node::File { mtime, .. } => *mtime = SystemTime::now(),
}
}
}
pub struct InMemFs {
root: Link,
}
impl InMemFs {
pub fn new() -> Self {
InMemFs {
root: Node::new_dir(),
}
}
pub(crate) fn contains(&self, path: &Path) -> io::Result<bool> {
match self.navigate(&components(path)?) {
Ok(_) => Ok(true),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}
pub(crate) fn mkdir_all(&self, path: &Path) -> io::Result<()> {
self.dir_all(&components(path)?).map(drop)
}
pub(crate) fn put_file(&self, path: &Path, content: impl io::Read) -> io::Result<()> {
let comps = components(path)?;
let (parent, name) = split_last(&comps)?;
let mut bytes = Vec::new();
io::Read::read_to_end(&mut content.take(MAX_FILE_SIZE + 1), &mut bytes)?;
if bytes.len() as u64 > MAX_FILE_SIZE {
return Err(io::ErrorKind::FileTooLarge.into());
}
let dir = self.dir_all(parent)?;
let mut node = lock(&dir);
let Node::Dir { children, .. } = &mut *node else {
unreachable!("`dir_all` only returns directories");
};
if let Some(existing) = children.get(name)
&& matches!(&*lock(existing), Node::Dir { .. })
{
return Err(io::ErrorKind::IsADirectory.into());
}
let now = SystemTime::now();
children.insert(
name.clone(),
Arc::new(Mutex::new(Node::File {
bytes,
mtime: now,
created: now,
})),
);
node.touch();
Ok(())
}
pub(crate) fn remove_file(&self, path: &Path) -> io::Result<()> {
self.remove(path, DirentKind::File)
}
fn dir_all(&self, comps: &[String]) -> io::Result<Link> {
let mut cur = self.root.clone();
for name in comps {
let next = {
let mut node = lock(&cur);
let Node::Dir { children, .. } = &mut *node else {
return Err(io::ErrorKind::NotADirectory.into());
};
match children.get(name) {
Some(child) => child.clone(),
None => {
let child = Node::new_dir();
children.insert(name.clone(), child.clone());
node.touch();
child
}
}
};
cur = next;
}
if matches!(&*lock(&cur), Node::File { .. }) {
return Err(io::ErrorKind::NotADirectory.into());
}
Ok(cur)
}
fn navigate(&self, comps: &[String]) -> io::Result<Link> {
let mut cur = self.root.clone();
for name in comps {
let next = match &*lock(&cur) {
Node::Dir { children, .. } => children.get(name).cloned().ok_or_else(not_found)?,
Node::File { .. } => return Err(io::ErrorKind::NotADirectory.into()),
};
cur = next;
}
Ok(cur)
}
fn file_at(&self, path: &Path) -> io::Result<Link> {
let link = self.navigate(&components(path)?)?;
let is_dir = matches!(&*lock(&link), Node::Dir { .. });
if is_dir {
return Err(io::ErrorKind::IsADirectory.into());
}
Ok(link)
}
fn parent_of(&self, path: &Path) -> io::Result<(Link, String)> {
let comps = components(path)?;
let (parent, name) = split_last(&comps)?;
Ok((self.navigate(parent)?, name.clone()))
}
fn insert(&self, path: &Path, node: Link) -> io::Result<Stat> {
let (dir, name) = self.parent_of(path)?;
let mut parent = lock(&dir);
let Node::Dir { children, .. } = &mut *parent else {
return Err(io::ErrorKind::NotADirectory.into());
};
if children.contains_key(&name) {
return Err(io::ErrorKind::AlreadyExists.into());
}
let stat = lock(&node).stat();
children.insert(name, node);
parent.touch();
Ok(stat)
}
fn remove(&self, path: &Path, expect: DirentKind) -> io::Result<()> {
let comps = components(path)?;
let (parent, name) = split_last(&comps)?;
let dir = self.navigate(parent)?;
let mut node = lock(&dir);
{
let Node::Dir { children, .. } = &mut *node else {
return Err(io::ErrorKind::NotADirectory.into());
};
let target = children.get(name).ok_or_else(not_found)?;
match (&*lock(target), expect) {
(Node::Dir { children, .. }, DirentKind::Dir) if !children.is_empty() => {
return Err(io::ErrorKind::DirectoryNotEmpty.into());
}
(Node::Dir { .. }, DirentKind::File) => {
return Err(io::ErrorKind::IsADirectory.into());
}
(Node::File { .. }, DirentKind::Dir) => {
return Err(io::ErrorKind::NotADirectory.into());
}
_ => {}
}
children.remove(name);
}
node.touch();
Ok(())
}
}
impl Default for InMemFs {
fn default() -> Self {
Self::new()
}
}
impl FileSystem for InMemFs {
fn stat<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<Stat>> {
Box::pin(async move {
let link = self.navigate(&components(path)?)?;
Ok(lock(&link).stat())
})
}
fn list<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<Vec<Dirent>>> {
Box::pin(async move {
let link = self.navigate(&components(path)?)?;
let node = lock(&link);
match &*node {
Node::Dir { children, .. } => Ok(children
.iter()
.map(|(name, child)| Dirent::with_stat(name, lock(child).stat()))
.collect()),
Node::File { .. } => Err(io::ErrorKind::NotADirectory.into()),
}
})
}
fn read_at<'a>(
&'a self,
path: &'a Path,
buf: &'a mut [u8],
offset: u64,
) -> BoxFuture<'a, io::Result<usize>> {
Box::pin(async move {
let link = self.file_at(path)?;
let node = lock(&link);
let Node::File { bytes, .. } = &*node else {
unreachable!("`file_at` refused a directory");
};
let offset = offset as usize;
if offset >= bytes.len() {
return Ok(0);
}
let n = (bytes.len() - offset).min(buf.len());
buf[..n].copy_from_slice(&bytes[offset..offset + n]);
Ok(n)
})
}
fn create<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<Stat>> {
Box::pin(async move { self.insert(path, Node::new_file()) })
}
fn mkdir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<Stat>> {
Box::pin(async move { self.insert(path, Node::new_dir()) })
}
fn unlink<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<()>> {
Box::pin(async move { self.remove(path, DirentKind::File) })
}
fn rmdir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<()>> {
Box::pin(async move { self.remove(path, DirentKind::Dir) })
}
fn write_at<'a>(
&'a self,
path: &'a Path,
buf: &'a [u8],
offset: u64,
) -> BoxFuture<'a, io::Result<usize>> {
Box::pin(async move {
let end = checked_end(offset, buf.len())?;
let link = self.file_at(path)?;
let mut node = lock(&link);
let Node::File { bytes, .. } = &mut *node else {
unreachable!("`file_at` refused a directory");
};
if end > bytes.len() {
bytes.resize(end, 0);
}
bytes[end - buf.len()..end].copy_from_slice(buf);
node.touch();
Ok(buf.len())
})
}
fn truncate<'a>(&'a self, path: &'a Path, size: u64) -> BoxFuture<'a, io::Result<()>> {
Box::pin(async move {
let size = checked_end(size, 0)?;
let link = self.file_at(path)?;
let mut node = lock(&link);
let Node::File { bytes, .. } = &mut *node else {
unreachable!("`file_at` refused a directory");
};
if bytes.len() == size {
return Ok(());
}
bytes.resize(size, 0);
node.touch();
Ok(())
})
}
fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, io::Result<()>> {
Box::pin(async move {
let (from_comps, to_comps) = (components(from)?, components(to)?);
if from_comps == to_comps {
return Ok(());
}
if to_comps.starts_with(&from_comps) {
return Err(io::ErrorKind::InvalidInput.into());
}
let (from_dir, from_name) = self.parent_of(from)?;
let (to_dir, to_name) = self.parent_of(to)?;
let moving = {
let node = lock(&from_dir);
let Node::Dir { children, .. } = &*node else {
return Err(io::ErrorKind::NotADirectory.into());
};
children.get(&from_name).cloned().ok_or_else(not_found)?
};
let moving_is_dir = matches!(&*lock(&moving), Node::Dir { .. });
{
let mut node = lock(&to_dir);
let Node::Dir { children, .. } = &mut *node else {
return Err(io::ErrorKind::NotADirectory.into());
};
if let Some(existing) = children.get(&to_name) {
match (&*lock(existing), moving_is_dir) {
(Node::Dir { children, .. }, true) if !children.is_empty() => {
return Err(io::ErrorKind::DirectoryNotEmpty.into());
}
(Node::Dir { .. }, false) => return Err(io::ErrorKind::IsADirectory.into()),
(Node::File { .. }, true) => {
return Err(io::ErrorKind::NotADirectory.into());
}
_ => {}
}
}
children.insert(to_name, Arc::clone(&moving));
node.touch();
}
let mut node = lock(&from_dir);
if let Node::Dir { children, .. } = &mut *node {
children.remove(&from_name);
}
node.touch();
Ok(())
})
}
}
fn not_found() -> io::Error {
io::ErrorKind::NotFound.into()
}
fn components(path: &Path) -> io::Result<Vec<String>> {
let mut out = Vec::new();
for comp in path.components() {
match comp {
Component::RootDir | Component::CurDir => {}
Component::Normal(name) => {
let name = name.to_str().ok_or(io::ErrorKind::InvalidFilename)?;
out.push(name.to_string());
}
Component::ParentDir | Component::Prefix(_) => {
return Err(io::ErrorKind::InvalidFilename.into());
}
}
}
Ok(out)
}
fn split_last(comps: &[String]) -> io::Result<(&[String], &String)> {
match comps.split_last() {
Some((name, parent)) => Ok((parent, name)),
None => Err(io::ErrorKind::InvalidFilename.into()),
}
}