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:
+75
-15
@@ -1,11 +1,10 @@
|
||||
//! Capability-sandboxed host files. Every system call runs on the blocking pool.
|
||||
//! Writable files currently open a second append descriptor by path. A concurrent rename
|
||||
//! between those opens can make append refer to a different file (review F9).
|
||||
use crate::*;
|
||||
use cap_std::fs::{Dir, OpenOptions};
|
||||
use std::{
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
path::Path,
|
||||
sync::Mutex,
|
||||
};
|
||||
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt};
|
||||
use cap_std::fs::{Dir, FileExt, OpenOptions};
|
||||
use std::{io::Write, path::Path, sync::Mutex};
|
||||
|
||||
/// A host directory bound to one process tracker.
|
||||
#[derive(Clone)]
|
||||
@@ -36,6 +35,7 @@ fn rel(path: &str) -> String {
|
||||
fn stat(m: cap_std::fs::Metadata) -> Stat {
|
||||
Stat {
|
||||
directory: m.is_dir(),
|
||||
symlink: m.file_type().is_symlink(),
|
||||
size: m.len(),
|
||||
accessed: m.accessed().ok().map(|t| t.into_std()),
|
||||
modified: m.modified().ok().map(|t| t.into_std()),
|
||||
@@ -48,9 +48,24 @@ struct Handle {
|
||||
}
|
||||
#[async_trait]
|
||||
impl Layer for Host {
|
||||
async fn stat(&self, path: &str) -> Result<Stat> {
|
||||
async fn stat(&self, path: &str, follow: bool) -> Result<Stat> {
|
||||
let (d, p) = (self.dir.clone(), rel(path));
|
||||
self.process.blocking(move || d.metadata(p).map(stat)).await
|
||||
self.process
|
||||
.blocking(move || {
|
||||
if follow {
|
||||
d.metadata(p)
|
||||
} else {
|
||||
d.symlink_metadata(p)
|
||||
}
|
||||
.map(stat)
|
||||
})
|
||||
.await
|
||||
}
|
||||
async fn readlink(&self, path: &str) -> Result<String> {
|
||||
let (d, p) = (self.dir.clone(), rel(path));
|
||||
self.process
|
||||
.blocking(move || d.read_link(p).map(|p| p.to_string_lossy().into_owned()))
|
||||
.await
|
||||
}
|
||||
async fn list(&self, path: &str) -> Result<Vec<Entry>> {
|
||||
let (d, p) = (self.dir.clone(), rel(path));
|
||||
@@ -63,6 +78,7 @@ impl Layer for Host {
|
||||
Ok(Entry {
|
||||
name: e.file_name().to_string_lossy().into_owned(),
|
||||
directory: e.file_type()?.is_dir(),
|
||||
symlink: e.file_type()?.is_symlink(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
@@ -83,13 +99,26 @@ impl Layer for Host {
|
||||
.create(o.create)
|
||||
.create_new(o.create && o.exclusive)
|
||||
.truncate(o.truncate);
|
||||
options.follow(if o.follow {
|
||||
FollowSymlinks::Yes
|
||||
} else {
|
||||
FollowSymlinks::No
|
||||
});
|
||||
let f = d.open_with(&p, &options)?;
|
||||
let directory = f.metadata()?.is_dir();
|
||||
if o.directory && !directory {
|
||||
return Err(error(io::ErrorKind::NotADirectory));
|
||||
}
|
||||
let append = if o.write && !directory {
|
||||
d.open_with(&p, OpenOptions::new().append(true))?
|
||||
{
|
||||
let mut append = OpenOptions::new();
|
||||
append.append(true).follow(if o.follow {
|
||||
FollowSymlinks::Yes
|
||||
} else {
|
||||
FollowSymlinks::No
|
||||
});
|
||||
d.open_with(&p, &append)?
|
||||
}
|
||||
} else {
|
||||
f.try_clone()?
|
||||
};
|
||||
@@ -131,10 +160,14 @@ impl File for Handle {
|
||||
let f = self.file.clone();
|
||||
self.process
|
||||
.blocking(move || {
|
||||
let mut f = f.lock().unwrap();
|
||||
f.seek(SeekFrom::Start(offset))?;
|
||||
let f = f.lock().unwrap();
|
||||
let mut bytes = Vec::new();
|
||||
(&mut *f).take(length as u64).read_to_end(&mut bytes)?;
|
||||
bytes
|
||||
.try_reserve(length)
|
||||
.map_err(|_| error(io::ErrorKind::FileTooLarge))?;
|
||||
bytes.resize(length, 0);
|
||||
let n = f.read_at(&mut bytes, offset)?;
|
||||
bytes.truncate(n);
|
||||
Ok(bytes)
|
||||
})
|
||||
.await
|
||||
@@ -143,9 +176,8 @@ impl File for Handle {
|
||||
let f = self.file.clone();
|
||||
self.process
|
||||
.blocking(move || {
|
||||
let mut f = f.lock().unwrap();
|
||||
f.seek(SeekFrom::Start(offset))?;
|
||||
f.write(&bytes)
|
||||
let f = f.lock().unwrap();
|
||||
f.write_at(&bytes, offset)
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -188,3 +220,31 @@ impl File for Handle {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
/// Positional reads and writes do not change the descriptor's cursor.
|
||||
#[tokio::test]
|
||||
async fn positional_io_keeps_cursor() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
std::fs::write(temp.path().join("file"), b"abcd").unwrap();
|
||||
let host = Host::new(temp.path(), Process::default()).unwrap();
|
||||
let file = host
|
||||
.dir
|
||||
.open_with("file", OpenOptions::new().read(true).write(true))
|
||||
.unwrap();
|
||||
let append = file.try_clone().unwrap();
|
||||
let file = Arc::new(Mutex::new(file));
|
||||
file.lock().unwrap().seek(SeekFrom::Start(3)).unwrap();
|
||||
let handle = Handle {
|
||||
file: file.clone(),
|
||||
append: Arc::new(Mutex::new(append)),
|
||||
process: Process::default(),
|
||||
};
|
||||
assert_eq!(handle.read(0, 1).await.unwrap(), b"a");
|
||||
handle.write(1, b"Z".to_vec()).await.unwrap();
|
||||
assert_eq!(file.lock().unwrap().stream_position().unwrap(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user