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/.
606 lines
18 KiB
Rust
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())
|
|
}
|
|
}
|