Files
sausage/vfs/src/preview1/calls.rs
T
surma 5ce2a2f2bd vfs: host-side WASI preview1 over sausage-vfs (T11)
Preview1 state lives in the host, so Go modules (esbuild) work. esbuild bundles
byte-identical to native; uutils and QuickJS-ng file tests pass. The failed
guest-side adapter attempt (T7) moves to spikes/s7-preview1-adapter/.
2026-11-06 16:34:33 +00:00

606 lines
18 KiB
Rust

//! Preview1 ABI operations over host-owned descriptors and async streams.
use super::*;
use types::*;
impl wasi_snapshot_preview1::WasiSnapshotPreview1 for State {
fn args_get(
&mut self,
m: &mut GuestMemory<'_>,
argv: GuestPtr<GuestPtr<u8>>,
buf: GuestPtr<u8>,
) -> Result<(), Error> {
strings(m, &self.argv, argv, buf)
}
fn args_sizes_get(&mut self, _: &mut GuestMemory<'_>) -> Result<(Size, Size), Error> {
sizes(&self.argv)
}
fn environ_get(
&mut self,
m: &mut GuestMemory<'_>,
env: GuestPtr<GuestPtr<u8>>,
buf: GuestPtr<u8>,
) -> Result<(), Error> {
strings(m, &self.env, env, buf)
}
fn environ_sizes_get(&mut self, _: &mut GuestMemory<'_>) -> Result<(Size, Size), Error> {
sizes(&self.env)
}
fn clock_res_get(&mut self, _: &mut GuestMemory<'_>, id: Clockid) -> Result<Timestamp, Error> {
self.clock(id)?;
Ok(1)
}
fn clock_time_get(
&mut self,
_: &mut GuestMemory<'_>,
id: Clockid,
_: Timestamp,
) -> Result<Timestamp, Error> {
self.clock(id)
}
async fn fd_advise(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
_: Filesize,
_: Filesize,
_: Advice,
) -> Result<(), Error> {
self.fd(fd)?;
Ok(())
}
fn fd_allocate(
&mut self,
_: &mut GuestMemory<'_>,
_: Fd,
_: Filesize,
_: Filesize,
) -> Result<(), Error> {
Err(Errno::Notsup.into())
}
async fn fd_close(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<(), Error> {
self.descriptors.remove(&u32::from(fd)).ok_or(Errno::Badf)?;
Ok(())
}
async fn fd_datasync(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<(), Error> {
self.file(fd)?.sync().await?;
Ok(())
}
async fn fd_sync(&mut self, m: &mut GuestMemory<'_>, fd: Fd) -> Result<(), Error> {
self.fd_datasync(m, fd).await
}
async fn fd_fdstat_get(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<Fdstat, Error> {
let (ty, flags) = if u32::from(fd) <= 2 {
(Filetype::Unknown, Fdflags::empty())
} else {
let d = self.fd(fd)?;
(
if d.directory {
Filetype::Directory
} else {
Filetype::RegularFile
},
d.flags,
)
};
Ok(Fdstat {
fs_filetype: ty,
fs_flags: flags,
fs_rights_base: Rights::all(),
fs_rights_inheriting: Rights::all(),
})
}
fn fd_fdstat_set_flags(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
flags: Fdflags,
) -> Result<(), Error> {
if u32::from(fd) > 2 {
self.descriptors
.get_mut(&u32::from(fd))
.ok_or(Errno::Badf)?
.flags = flags;
}
Ok(())
}
fn fd_fdstat_set_rights(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
_: Rights,
_: Rights,
) -> Result<(), Error> {
self.fd(fd)?;
Ok(())
}
async fn fd_filestat_get(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
) -> Result<Filestat, Error> {
if u32::from(fd) <= 2 {
return Ok(Filestat {
dev: 0,
ino: u32::from(fd) as u64,
filetype: Filetype::Unknown,
nlink: 1,
size: 0,
atim: 0,
mtim: 0,
ctim: 0,
});
}
let d = self.fd(fd)?.clone();
Ok(stat(
if let Some(file) = d.file {
file.stat().await?
} else {
self.root.stat(&d.path, true).await?
},
&d.path,
))
}
async fn fd_filestat_set_size(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
size: Filesize,
) -> Result<(), Error> {
self.file(fd)?.set_size(size).await?;
Ok(())
}
async fn fd_filestat_set_times(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
atim: Timestamp,
mtim: Timestamp,
flags: Fstflags,
) -> Result<(), Error> {
let (a, m) = times(atim, mtim, flags);
self.file(fd)?.set_times(a, m).await?;
Ok(())
}
async fn fd_read(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
iovs: IovecArray,
) -> Result<Size, Error> {
self.read(m, fd, iovs, None).await
}
async fn fd_pread(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
iovs: IovecArray,
offset: Filesize,
) -> Result<Size, Error> {
self.read(m, fd, iovs, Some(offset)).await
}
async fn fd_write(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
iovs: CiovecArray,
) -> Result<Size, Error> {
self.write(m, fd, iovs, None).await
}
async fn fd_pwrite(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
iovs: CiovecArray,
offset: Filesize,
) -> Result<Size, Error> {
self.write(m, fd, iovs, Some(offset)).await
}
fn fd_prestat_get(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<Prestat, Error> {
if u32::from(fd) != 3 {
return Err(Errno::Badf.into());
}
Ok(Prestat::Dir(PrestatDir { pr_name_len: 1 }))
}
fn fd_prestat_dir_name(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
path: GuestPtr<u8>,
len: Size,
) -> Result<(), Error> {
if u32::from(fd) != 3 {
return Err(Errno::Badf.into());
}
if len < 1 {
return Err(Errno::Nametoolong.into());
}
m.write(path, b'/')?;
Ok(())
}
async fn fd_renumber(
&mut self,
_: &mut GuestMemory<'_>,
from: Fd,
to: Fd,
) -> Result<(), Error> {
let d = self
.descriptors
.remove(&u32::from(from))
.ok_or(Errno::Badf)?;
self.descriptors.insert(u32::from(to), d);
Ok(())
}
async fn fd_seek(
&mut self,
_: &mut GuestMemory<'_>,
fd: Fd,
offset: Filedelta,
whence: Whence,
) -> Result<Filesize, Error> {
if u32::from(fd) <= 2 {
return Err(Errno::Spipe.into());
}
let base = match whence {
Whence::Set => 0,
Whence::Cur => self.fd(fd)?.offset,
Whence::End => self.file(fd)?.stat().await?.size,
};
let value = base.checked_add_signed(offset).ok_or(Errno::Inval)?;
self.descriptors
.get_mut(&u32::from(fd))
.ok_or(Errno::Badf)?
.offset = value;
Ok(value)
}
fn fd_tell(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<Filesize, Error> {
Ok(self.fd(fd)?.offset)
}
async fn fd_readdir(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
buf: GuestPtr<u8>,
len: Size,
cookie: Dircookie,
) -> Result<Size, Error> {
let d = self.fd(fd)?;
if !d.directory {
return Err(Errno::Notdir.into());
}
let mut entries = vec![
crate::Entry {
name: ".".into(),
directory: true,
symlink: false,
},
crate::Entry {
name: "..".into(),
directory: true,
symlink: false,
},
];
entries.extend(self.root.list(&d.path).await?);
let mut bytes = Vec::new();
for (i, entry) in entries.iter().enumerate().skip(cookie as usize) {
// Preview1 dirent: next cookie, inode, name length, type, padding.
bytes.extend_from_slice(&((i + 1) as u64).to_le_bytes());
bytes.extend_from_slice(&((i + 1) as u64).to_le_bytes());
bytes.extend_from_slice(&(entry.name.len() as u32).to_le_bytes());
bytes.push(if entry.symlink {
7
} else if entry.directory {
3
} else {
4
});
bytes.extend_from_slice(&[0; 3]);
bytes.extend_from_slice(entry.name.as_bytes());
if bytes.len() >= len as usize {
break;
}
}
bytes.truncate(len as usize);
m.copy_from_slice(&bytes, buf.as_array(bytes.len() as u32))?;
Ok(bytes.len() as u32)
}
async fn path_create_directory(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
path: GuestPtr<str>,
) -> Result<(), Error> {
self.root.mkdir(&self.path(m, fd, path)?).await?;
Ok(())
}
async fn path_filestat_get(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
flags: Lookupflags,
path: GuestPtr<str>,
) -> Result<Filestat, Error> {
let path = self.path(m, fd, path)?;
Ok(stat(
self.root
.stat(&path, flags.contains(Lookupflags::SYMLINK_FOLLOW))
.await?,
&path,
))
}
async fn path_filestat_set_times(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
flags: Lookupflags,
path: GuestPtr<str>,
atim: Timestamp,
mtim: Timestamp,
f: Fstflags,
) -> Result<(), Error> {
let path = self.path(m, fd, path)?;
let file = self
.root
.open(
&path,
Open {
follow: flags.contains(Lookupflags::SYMLINK_FOLLOW),
..Open::default()
},
)
.await?;
let (a, b) = times(atim, mtim, f);
file.set_times(a, b).await?;
Ok(())
}
async fn path_open(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
flags: Lookupflags,
path: GuestPtr<str>,
oflags: Oflags,
rights: Rights,
_: Rights,
fdflags: Fdflags,
) -> Result<Fd, Error> {
let path = self.path(m, fd, path)?;
let file = self
.root
.open(
&path,
Open {
follow: flags.contains(Lookupflags::SYMLINK_FOLLOW),
write: rights.contains(Rights::FD_WRITE),
create: oflags.contains(Oflags::CREAT),
exclusive: oflags.contains(Oflags::EXCL),
truncate: oflags.contains(Oflags::TRUNC),
directory: oflags.contains(Oflags::DIRECTORY),
},
)
.await?;
let directory = file.stat().await?.directory;
let next = (4..)
.find(|n| !self.descriptors.contains_key(n))
.ok_or(Errno::Mfile)?;
self.descriptors.insert(
next,
Descriptor {
path,
file: Some(file),
directory,
offset: 0,
flags: fdflags,
},
);
Ok(next.into())
}
async fn path_readlink(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
path: GuestPtr<str>,
buf: GuestPtr<u8>,
len: Size,
) -> Result<Size, Error> {
let target = self.root.readlink(&self.path(m, fd, path)?).await?;
let bytes = &target.as_bytes()[..target.len().min(len as usize)];
m.copy_from_slice(bytes, buf.as_array(bytes.len() as u32))?;
Ok(bytes.len() as u32)
}
async fn path_remove_directory(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
path: GuestPtr<str>,
) -> Result<(), Error> {
self.root.remove_dir(&self.path(m, fd, path)?).await?;
Ok(())
}
async fn path_unlink_file(
&mut self,
m: &mut GuestMemory<'_>,
fd: Fd,
path: GuestPtr<str>,
) -> Result<(), Error> {
self.root.remove_file(&self.path(m, fd, path)?).await?;
Ok(())
}
async fn path_rename(
&mut self,
m: &mut GuestMemory<'_>,
from: Fd,
src: GuestPtr<str>,
to: Fd,
dst: GuestPtr<str>,
) -> Result<(), Error> {
self.root
.rename(&self.path(m, from, src)?, &self.path(m, to, dst)?)
.await?;
Ok(())
}
async fn path_link(
&mut self,
_: &mut GuestMemory<'_>,
_: Fd,
_: Lookupflags,
_: GuestPtr<str>,
_: Fd,
_: GuestPtr<str>,
) -> Result<(), Error> {
Err(Errno::Notsup.into())
}
async fn path_symlink(
&mut self,
_: &mut GuestMemory<'_>,
_: GuestPtr<str>,
_: Fd,
_: GuestPtr<str>,
) -> Result<(), Error> {
Err(Errno::Notsup.into())
}
async fn poll_oneoff(
&mut self,
m: &mut GuestMemory<'_>,
subs: GuestPtr<Subscription>,
events: GuestPtr<Event>,
count: Size,
) -> Result<Size, Error> {
if count == 0 {
return Err(Errno::Inval.into());
}
let mut pending = Vec::new();
for ptr in subs.as_array(count).iter() {
let sub = m.read(ptr?)?;
let (delay, stream, ty, error) = match sub.u {
SubscriptionU::Clock(c) => {
let now = self.clock(c.id)?;
let delay = if c.flags.contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) {
c.timeout.saturating_sub(now)
} else {
c.timeout
};
(Some(delay), None, Eventtype::Clock, Errno::Success)
}
SubscriptionU::FdRead(f) => {
let fd = u32::from(f.file_descriptor);
if fd == 0 {
(None, Some(fd), Eventtype::FdRead, Errno::Success)
} else {
(
Some(0),
None,
Eventtype::FdRead,
if self.fd(f.file_descriptor).is_ok() {
Errno::Success
} else {
Errno::Badf
},
)
}
}
SubscriptionU::FdWrite(f) => {
let fd = u32::from(f.file_descriptor);
if fd == 1 || fd == 2 {
(None, Some(fd), Eventtype::FdWrite, Errno::Success)
} else {
(
Some(0),
None,
Eventtype::FdWrite,
if self.fd(f.file_descriptor).is_ok() {
Errno::Success
} else {
Errno::Badf
},
)
}
}
};
pending.push((
delay,
stream,
Event {
userdata: sub.userdata,
error,
type_: ty,
fd_readwrite: EventFdReadwrite {
nbytes: 1,
flags: Eventrwflags::empty(),
},
},
));
}
let delay = pending.iter().filter_map(|(d, _, _)| *d).min();
let has = |fd| pending.iter().any(|(_, s, _)| *s == Some(fd));
let wait_start = Instant::now();
let stream = tokio::select! {
_ = self.stdin.ready(), if has(0) => Some(0),
_ = self.stdout.ready(), if has(1) => Some(1),
_ = self.stderr.ready(), if has(2) => Some(2),
_ = tokio::time::sleep(Duration::from_nanos(delay.unwrap_or(0))), if delay.is_some() => None,
};
let elapsed = wait_start.elapsed().as_nanos() as u64;
let mut written = 0;
for (delay, fd, mut event) in pending {
if delay.is_some_and(|d| d <= elapsed) || fd.is_some() && fd == stream {
if fd == Some(0) && matches!(self.stdin.read(0), Err(StreamError::Closed)) {
event.fd_readwrite.nbytes = 0;
event.fd_readwrite.flags = Eventrwflags::FD_READWRITE_HANGUP;
}
if fd == Some(1) && self.stdout.check_write().is_err()
|| fd == Some(2) && self.stderr.check_write().is_err()
{
event.error = Errno::Io;
}
m.write(events.add(written)?, event)?;
written += 1;
}
}
Ok(written)
}
fn proc_exit(&mut self, _: &mut GuestMemory<'_>, status: Exitcode) -> wasmtime::Error {
wasmtime_wasi::I32Exit(status as i32).into()
}
fn proc_raise(&mut self, _: &mut GuestMemory<'_>, _: Signal) -> Result<(), Error> {
Err(Errno::Notsup.into())
}
fn sched_yield(&mut self, _: &mut GuestMemory<'_>) -> Result<(), Error> {
Ok(())
}
fn random_get(
&mut self,
m: &mut GuestMemory<'_>,
buf: GuestPtr<u8>,
len: Size,
) -> Result<(), Error> {
let _ = m.as_slice(buf.as_array(len))?;
let mut bytes = vec![0; len as usize];
getrandom::fill(&mut bytes).map_err(|_| Errno::Io)?;
m.copy_from_slice(&bytes, buf.as_array(len))?;
Ok(())
}
fn sock_accept(&mut self, _: &mut GuestMemory<'_>, _: Fd, _: Fdflags) -> Result<Fd, Error> {
Err(Errno::Notsup.into())
}
fn sock_recv(
&mut self,
_: &mut GuestMemory<'_>,
_: Fd,
_: IovecArray,
_: Riflags,
) -> Result<(Size, Roflags), Error> {
Err(Errno::Notsup.into())
}
fn sock_send(
&mut self,
_: &mut GuestMemory<'_>,
_: Fd,
_: CiovecArray,
_: Siflags,
) -> Result<Size, Error> {
Err(Errno::Notsup.into())
}
fn sock_shutdown(&mut self, _: &mut GuestMemory<'_>, _: Fd, _: Sdflags) -> Result<(), Error> {
Err(Errno::Notsup.into())
}
}