sausage-vfs: review fixes (fallible growth, symlink flags, stream cancel, sized reads, timestamps, error kinds, linker shadowing)
API: Layer::stat(path, follow), Layer::readlink, Open::follow, Stat::symlink and Entry::symlink. Process::idle documents the shutdown order: abort, await the task, await idle, release locks.
This commit is contained in:
+100
-33
@@ -4,9 +4,10 @@ use std::{collections::BTreeMap, io::ErrorKind as E, sync::Mutex};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Node {
|
||||
Dir,
|
||||
Dir(Arc<Mutex<Data>>),
|
||||
File(Arc<Mutex<Data>>),
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct Data {
|
||||
bytes: Vec<u8>,
|
||||
accessed: Option<SystemTime>,
|
||||
@@ -20,7 +21,10 @@ pub struct Memory {
|
||||
impl Default for Memory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
nodes: Arc::new(Mutex::new(BTreeMap::from([("/".into(), Node::Dir)]))),
|
||||
nodes: Arc::new(Mutex::new(BTreeMap::from([(
|
||||
"/".into(),
|
||||
Node::Dir(Arc::default()),
|
||||
)]))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,25 +33,54 @@ fn parent(path: &str) -> &str {
|
||||
.map(|(p, _)| if p.is_empty() { "/" } else { p })
|
||||
.unwrap_or("/")
|
||||
}
|
||||
fn validate(nodes: &BTreeMap<String, Node>, 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<String, Node>, path: &str) -> Result<()> {
|
||||
match nodes.get(path) {
|
||||
Some(Node::Dir) => Ok(()),
|
||||
Some(Node::Dir(_)) => Ok(()),
|
||||
Some(_) => Err(error(E::NotADirectory)),
|
||||
None => Err(error(E::NotFound)),
|
||||
}
|
||||
}
|
||||
fn metadata(node: &Node) -> Stat {
|
||||
match node {
|
||||
Node::Dir => Stat {
|
||||
directory: true,
|
||||
size: 0,
|
||||
accessed: None,
|
||||
modified: None,
|
||||
},
|
||||
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,
|
||||
@@ -61,28 +94,34 @@ struct Handle {
|
||||
}
|
||||
#[async_trait]
|
||||
impl Layer for Memory {
|
||||
async fn stat(&self, path: &str) -> Result<Stat> {
|
||||
self.nodes
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(path)
|
||||
async fn stat(&self, path: &str, follow: bool) -> Result<Stat> {
|
||||
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<Vec<Entry>> {
|
||||
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),
|
||||
directory: matches!(n, Node::Dir(_)),
|
||||
symlink: false,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
async fn open(&self, path: &str, o: Open) -> Result<Arc<dyn File>> {
|
||||
let mut nodes = self.nodes.lock().unwrap();
|
||||
validate(&nodes, path)?;
|
||||
let path = clean(path);
|
||||
if o.truncate && !o.write {
|
||||
return Err(error(E::InvalidInput));
|
||||
}
|
||||
@@ -105,7 +144,7 @@ impl Layer for Memory {
|
||||
}
|
||||
let node = nodes[path].clone();
|
||||
match &node {
|
||||
Node::Dir if o.write || o.truncate => return Err(error(E::IsADirectory)),
|
||||
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(),
|
||||
_ => {}
|
||||
@@ -117,18 +156,22 @@ impl Layer for Memory {
|
||||
}
|
||||
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);
|
||||
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)),
|
||||
Some(Node::Dir(_)) => return Err(error(E::IsADirectory)),
|
||||
_ => {}
|
||||
}
|
||||
nodes.remove(path);
|
||||
@@ -136,6 +179,8 @@ impl Layer for Memory {
|
||||
}
|
||||
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));
|
||||
@@ -148,6 +193,10 @@ impl Layer for Memory {
|
||||
}
|
||||
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 {
|
||||
@@ -161,9 +210,14 @@ impl Layer for Memory {
|
||||
}
|
||||
if let Some(target) = nodes.get(to) {
|
||||
match (&node, target) {
|
||||
(Node::Dir, Node::File(_)) => return Err(error(E::NotADirectory)),
|
||||
(Node::File(_), Node::Dir) => return Err(error(E::IsADirectory)),
|
||||
(Node::Dir, Node::Dir) if nodes.keys().any(|p| p != to && parent(p) == to) => {
|
||||
(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));
|
||||
}
|
||||
_ => {}
|
||||
@@ -210,31 +264,41 @@ impl File for Handle {
|
||||
}
|
||||
async fn write(&self, offset: u64, bytes: Vec<u8>) -> Result<usize> {
|
||||
let mut d = self.writable()?.lock().unwrap();
|
||||
let start = usize::try_from(offset).map_err(|_| error(E::InvalidInput))?;
|
||||
let start = usize::try_from(offset).map_err(|_| error(E::FileTooLarge))?;
|
||||
let end = start
|
||||
.checked_add(bytes.len())
|
||||
.ok_or_else(|| error(E::InvalidInput))?;
|
||||
.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<u8>) -> Result<usize> {
|
||||
self.writable()?
|
||||
.lock()
|
||||
.unwrap()
|
||||
.bytes
|
||||
.extend_from_slice(&bytes);
|
||||
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<()> {
|
||||
self.writable()?.lock().unwrap().bytes.resize(
|
||||
usize::try_from(size).map_err(|_| error(E::InvalidInput))?,
|
||||
0,
|
||||
);
|
||||
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<()> {
|
||||
@@ -245,7 +309,10 @@ impl File for Handle {
|
||||
accessed: Option<SystemTime>,
|
||||
modified: Option<SystemTime>,
|
||||
) -> Result<()> {
|
||||
let mut d = self.writable()?.lock().unwrap();
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user