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:
2026-10-23 00:49:46 +01:00
parent 7bbcd47837
commit 151550242b
14 changed files with 673 additions and 97 deletions
+123
View File
@@ -9,6 +9,11 @@ use std::{
fn main() {
match std::env::var("VFS_TOPIC").unwrap().as_str() {
"basic" => basic(),
"review_streams" => review_streams(),
"review_times" => review_times(),
"review_paths" => review_paths(),
"review_links" => review_links(),
"review_preopen" => review_preopen(),
"trees" => trees(),
"options" => options(),
"rename" => rename(),
@@ -255,3 +260,121 @@ fn temporary() {
assert!(!original.exists());
assert_eq!(fs::read("/persisted").unwrap(), b"persisted");
}
/// Dropped output streams finish writes and small reads do not cache later bytes.
fn review_streams() {
use wasip2::filesystem::{
preopens,
types::{DescriptorFlags, OpenFlags, PathFlags},
};
let root = preopens::get_directories().remove(0).0;
let file = root
.open_at(
PathFlags::empty(),
"stream-review",
OpenFlags::CREATE,
DescriptorFlags::READ | DescriptorFlags::WRITE,
)
.unwrap();
let output = file.write_via_stream(0).unwrap();
assert!(output.check_write().unwrap() > 0);
output.write(b"abcd").unwrap();
drop(output);
assert_eq!(fs::read("/stream-review").unwrap(), b"abcd");
let input = file.read_via_stream(0).unwrap();
assert_eq!(input.blocking_read(1).unwrap(), b"a");
file.write_via_stream(1)
.unwrap()
.blocking_write_and_flush(b"Z")
.unwrap();
assert_eq!(input.blocking_read(1).unwrap(), b"Z");
}
/// Timestamp operations accept directories and return overflow rather than panicking.
fn review_times() {
use wasip2::clocks::wall_clock::Datetime;
use wasip2::filesystem::{
preopens,
types::{ErrorCode, NewTimestamp, PathFlags},
};
let root = preopens::get_directories().remove(0).0;
fs::create_dir("/times").unwrap();
root.set_times_at(
PathFlags::SYMLINK_FOLLOW,
"times",
NewTimestamp::NoChange,
NewTimestamp::Timestamp(Datetime {
seconds: 123,
nanoseconds: 0,
}),
)
.unwrap();
assert_eq!(
fs::metadata("/times").unwrap().modified().unwrap(),
std::time::UNIX_EPOCH + std::time::Duration::from_secs(123)
);
assert_eq!(
root.set_times(
NewTimestamp::Timestamp(Datetime {
seconds: u64::MAX,
nanoseconds: u32::MAX
}),
NewTimestamp::NoChange
),
Err(ErrorCode::Overflow)
);
}
/// Trailing slashes require folders and readlink rejects ordinary files with invalid.
fn review_paths() {
use wasip2::filesystem::{preopens, types::ErrorCode};
fs::write("/file", b"x").unwrap();
assert_eq!(
fs::read("/file/").unwrap_err().kind(),
ErrorKind::NotADirectory
);
let root = preopens::get_directories().remove(0).0;
assert_eq!(root.readlink_at("file"), Err(ErrorCode::Invalid));
assert_eq!(root.remove_directory_at("run"), Err(ErrorCode::Busy));
}
/// Removing an internal link leaves its target intact and external links cannot escape.
fn review_links() {
use wasip2::filesystem::{
preopens,
types::{DescriptorFlags, DescriptorType, OpenFlags, PathFlags},
};
let root = preopens::get_directories().remove(0).0;
assert_eq!(
root.stat_at(PathFlags::empty(), "link").unwrap().type_,
DescriptorType::SymbolicLink
);
assert_eq!(root.readlink_at("link").unwrap(), "target");
assert!(
root.open_at(
PathFlags::empty(),
"link",
OpenFlags::DIRECTORY,
DescriptorFlags::READ
)
.is_err()
);
assert_eq!(
root.open_at(
PathFlags::SYMLINK_FOLLOW,
"link",
OpenFlags::DIRECTORY,
DescriptorFlags::READ
)
.unwrap()
.get_type()
.unwrap(),
DescriptorType::Directory
);
fs::remove_dir_all("/link").unwrap();
assert_eq!(fs::read("/target/keep").unwrap(), b"keep");
assert!(fs::read("/outside-link/secret").is_err());
}
/// The component sees exactly one virtual preopen rather than native WASI preopens.
fn review_preopen() {
let dirs = wasip2::filesystem::preopens::get_directories();
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].1, "/");
}