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:
+68
-25
@@ -3,6 +3,8 @@
|
||||
//! Directory descriptors retain their absolute virtual path after a directory rename.
|
||||
//! Relative operations through those descriptors use the old path, not the moved folder.
|
||||
//! Reopen the moved folder before further relative operations.
|
||||
//! Object comparisons and metadata hashes use paths, not inode identity (review F12).
|
||||
//! Hard links remain unsupported, including tempfile persist_noclobber.
|
||||
use crate::streams::{Reader, Writer};
|
||||
use crate::*;
|
||||
use wasmtime::component::{HasData, Linker, Resource, ResourceTable};
|
||||
@@ -43,8 +45,12 @@ pub fn add_to_linker<T: Send + 'static>(
|
||||
view: fn(&mut T) -> View<'_>,
|
||||
) -> wasmtime::Result<()> {
|
||||
linker.allow_shadowing(true);
|
||||
types::add_to_linker::<T, Vfs>(linker, view)?;
|
||||
preopens::add_to_linker::<T, Vfs>(linker, view)?;
|
||||
let result = (|| {
|
||||
types::add_to_linker::<T, Vfs>(linker, view)?;
|
||||
preopens::add_to_linker::<T, Vfs>(linker, view)
|
||||
})();
|
||||
linker.allow_shadowing(false);
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
#[derive(Clone)]
|
||||
@@ -63,6 +69,8 @@ fn code(e: &io::Error) -> ErrorCode {
|
||||
ReadOnlyFilesystem => ErrorCode::ReadOnly,
|
||||
CrossesDevices => ErrorCode::CrossDevice,
|
||||
Unsupported => ErrorCode::Unsupported,
|
||||
FileTooLarge => ErrorCode::FileTooLarge,
|
||||
ResourceBusy => ErrorCode::Busy,
|
||||
_ => ErrorCode::from(e),
|
||||
}
|
||||
}
|
||||
@@ -78,7 +86,9 @@ fn stat(s: Stat) -> types::DescriptorStat {
|
||||
})
|
||||
}
|
||||
types::DescriptorStat {
|
||||
type_: if s.directory {
|
||||
type_: if s.symlink {
|
||||
types::DescriptorType::SymbolicLink
|
||||
} else if s.directory {
|
||||
types::DescriptorType::Directory
|
||||
} else {
|
||||
types::DescriptorType::RegularFile
|
||||
@@ -90,12 +100,19 @@ fn stat(s: Stat) -> types::DescriptorStat {
|
||||
status_change_timestamp: None,
|
||||
}
|
||||
}
|
||||
fn time(t: types::NewTimestamp) -> Option<SystemTime> {
|
||||
fn time(t: types::NewTimestamp) -> FsResult<Option<SystemTime>> {
|
||||
match t {
|
||||
types::NewTimestamp::NoChange => None,
|
||||
types::NewTimestamp::Now => Some(SystemTime::now()),
|
||||
types::NewTimestamp::NoChange => Ok(None),
|
||||
types::NewTimestamp::Now => Ok(Some(SystemTime::now())),
|
||||
types::NewTimestamp::Timestamp(t) => {
|
||||
Some(SystemTime::UNIX_EPOCH + std::time::Duration::new(t.seconds, t.nanoseconds))
|
||||
let duration = std::time::Duration::from_secs(t.seconds)
|
||||
.checked_add(std::time::Duration::from_nanos(t.nanoseconds as u64))
|
||||
.ok_or(ErrorCode::Overflow)?;
|
||||
Ok(Some(
|
||||
SystemTime::UNIX_EPOCH
|
||||
.checked_add(duration)
|
||||
.ok_or(ErrorCode::Overflow)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +157,7 @@ impl File for Root {
|
||||
async fn stat(&self) -> Result<Stat> {
|
||||
Ok(Stat {
|
||||
directory: true,
|
||||
symlink: false,
|
||||
size: 0,
|
||||
accessed: None,
|
||||
modified: None,
|
||||
@@ -211,7 +229,7 @@ impl HostDescriptor for View<'_> {
|
||||
) -> FsResult<()> {
|
||||
self.fd(&fd)?
|
||||
.file
|
||||
.set_times(time(atim), time(mtim))
|
||||
.set_times(time(atim)?, time(mtim)?)
|
||||
.await
|
||||
.map_err(errno)
|
||||
}
|
||||
@@ -257,7 +275,9 @@ impl HostDescriptor for View<'_> {
|
||||
.into_iter()
|
||||
.map(|e| DirectoryEntry {
|
||||
name: e.name,
|
||||
type_: if e.directory {
|
||||
type_: if e.symlink {
|
||||
types::DescriptorType::SymbolicLink
|
||||
} else if e.directory {
|
||||
types::DescriptorType::Directory
|
||||
} else {
|
||||
types::DescriptorType::RegularFile
|
||||
@@ -288,9 +308,11 @@ impl HostDescriptor for View<'_> {
|
||||
path_flags: types::PathFlags,
|
||||
path: String,
|
||||
) -> FsResult<types::DescriptorStat> {
|
||||
let _ = path_flags;
|
||||
let follow = path_flags.contains(types::PathFlags::SYMLINK_FOLLOW);
|
||||
let p = self.path(&fd, &path)?;
|
||||
Ok(stat(self.adapter.mounts.stat(&p).await.map_err(errno)?))
|
||||
Ok(stat(
|
||||
self.adapter.mounts.stat(&p, follow).await.map_err(errno)?,
|
||||
))
|
||||
}
|
||||
async fn set_times_at(
|
||||
&mut self,
|
||||
@@ -300,20 +322,14 @@ impl HostDescriptor for View<'_> {
|
||||
atim: types::NewTimestamp,
|
||||
mtim: types::NewTimestamp,
|
||||
) -> FsResult<()> {
|
||||
let _ = path_flags;
|
||||
let follow = path_flags.contains(types::PathFlags::SYMLINK_FOLLOW);
|
||||
let p = self.path(&fd, &path)?;
|
||||
self.adapter
|
||||
.mounts
|
||||
.open(
|
||||
&p,
|
||||
Open {
|
||||
write: true,
|
||||
..Open::default()
|
||||
},
|
||||
)
|
||||
.metadata_file(&p, follow)
|
||||
.await
|
||||
.map_err(errno)?
|
||||
.set_times(time(atim), time(mtim))
|
||||
.set_times(time(atim)?, time(mtim)?)
|
||||
.await
|
||||
.map_err(errno)
|
||||
}
|
||||
@@ -336,7 +352,7 @@ impl HostDescriptor for View<'_> {
|
||||
oflags: OpenFlags,
|
||||
flags: DescriptorFlags,
|
||||
) -> FsResult<Resource<types::Descriptor>> {
|
||||
let _ = path_flags;
|
||||
let follow = path_flags.contains(types::PathFlags::SYMLINK_FOLLOW);
|
||||
let path = self.path(&fd, &path)?;
|
||||
let file = self
|
||||
.adapter
|
||||
@@ -344,6 +360,7 @@ impl HostDescriptor for View<'_> {
|
||||
.open(
|
||||
&path,
|
||||
Open {
|
||||
follow,
|
||||
write: flags.contains(DescriptorFlags::WRITE),
|
||||
create: oflags.contains(OpenFlags::CREATE),
|
||||
exclusive: oflags.contains(OpenFlags::EXCLUSIVE),
|
||||
@@ -365,8 +382,8 @@ impl HostDescriptor for View<'_> {
|
||||
fd: Resource<types::Descriptor>,
|
||||
path: String,
|
||||
) -> FsResult<String> {
|
||||
let _ = (fd, path);
|
||||
Err(ErrorCode::Unsupported.into())
|
||||
let p = self.path(&fd, &path)?;
|
||||
self.adapter.mounts.readlink(&p).await.map_err(errno)
|
||||
}
|
||||
async fn remove_directory_at(
|
||||
&mut self,
|
||||
@@ -449,9 +466,9 @@ impl HostDescriptor for View<'_> {
|
||||
path_flags: types::PathFlags,
|
||||
path: String,
|
||||
) -> FsResult<types::MetadataHashValue> {
|
||||
let _ = path_flags;
|
||||
let follow = path_flags.contains(types::PathFlags::SYMLINK_FOLLOW);
|
||||
let p = self.path(&fd, &path)?;
|
||||
self.adapter.mounts.stat(&p).await.map_err(errno)?;
|
||||
self.adapter.mounts.stat(&p, follow).await.map_err(errno)?;
|
||||
Ok(hash(&p))
|
||||
}
|
||||
}
|
||||
@@ -472,3 +489,29 @@ impl HostDirectoryEntryStream for View<'_> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
/// Timestamp arithmetic returns overflow instead of panicking on either addition.
|
||||
#[test]
|
||||
fn timestamp_overflow() {
|
||||
for nanoseconds in [0, u32::MAX] {
|
||||
let error = time(types::NewTimestamp::Timestamp(types::Datetime {
|
||||
seconds: u64::MAX,
|
||||
nanoseconds,
|
||||
}))
|
||||
.unwrap_err();
|
||||
assert_eq!(error.downcast().unwrap(), ErrorCode::Overflow);
|
||||
}
|
||||
}
|
||||
/// Portable memory errors preserve their WASI error codes.
|
||||
#[test]
|
||||
fn portable_error_codes() {
|
||||
assert_eq!(
|
||||
code(&error(io::ErrorKind::FileTooLarge)),
|
||||
ErrorCode::FileTooLarge
|
||||
);
|
||||
assert_eq!(code(&error(io::ErrorKind::ResourceBusy)), ErrorCode::Busy);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user