use crate::{
dir::{DirEntry, OpenResult, ReaddirCursor, ReaddirEntity, TableDirExt},
file::{
Advice, FdFlags, FdStat, FileAccessMode, FileEntry, FileType, Filestat, OFlags, RiFlags,
RoFlags, SdFlags, SiFlags, TableFileExt, WasiFile,
},
sched::{
subscription::{RwEventFlags, SubscriptionResult},
Poll, Userdata,
},
I32Exit, SystemTimeSpec, WasiCtx,
};
use cap_std::time::{Duration, SystemClock};
use std::borrow::Cow;
use std::io::{IoSlice, IoSliceMut};
use std::ops::Deref;
use std::sync::Arc;
use wiggle::GuestMemory;
use wiggle::GuestPtr;
pub mod error;
use error::{Error, ErrorExt};
pub(crate) const MAX_SHARED_BUFFER_SIZE: usize = 1 << 16;
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"],
errors: { errno => trappable Error },
async: *,
wasmtime: false,
});
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
Self::Success
}
}
#[wiggle::async_trait]
impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
async fn args_get(
&mut self,
memory: &mut GuestMemory<'_>,
argv: GuestPtr<GuestPtr<u8>>,
argv_buf: GuestPtr<u8>,
) -> Result<(), Error> {
self.args.write_to_guest(memory, argv_buf, argv)
}
async fn args_sizes_get(
&mut self,
_memory: &mut GuestMemory<'_>,
) -> Result<(types::Size, types::Size), Error> {
Ok((self.args.number_elements(), self.args.cumulative_size()))
}
async fn environ_get(
&mut self,
memory: &mut GuestMemory<'_>,
environ: GuestPtr<GuestPtr<u8>>,
environ_buf: GuestPtr<u8>,
) -> Result<(), Error> {
self.env.write_to_guest(memory, environ_buf, environ)
}
async fn environ_sizes_get(
&mut self,
_memory: &mut GuestMemory<'_>,
) -> Result<(types::Size, types::Size), Error> {
Ok((self.env.number_elements(), self.env.cumulative_size()))
}
async fn clock_res_get(
&mut self,
_memory: &mut GuestMemory<'_>,
id: types::Clockid,
) -> Result<types::Timestamp, Error> {
let resolution = match id {
types::Clockid::Realtime => Ok(self.clocks.system()?.resolution()),
types::Clockid::Monotonic => Ok(self.clocks.monotonic()?.abs_clock.resolution()),
types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
Err(Error::badf().context("process and thread clocks are not supported"))
}
}?;
Ok(resolution.as_nanos().try_into()?)
}
async fn clock_time_get(
&mut self,
_memory: &mut GuestMemory<'_>,
id: types::Clockid,
precision: types::Timestamp,
) -> Result<types::Timestamp, Error> {
let precision = Duration::from_nanos(precision);
match id {
types::Clockid::Realtime => {
let now = self.clocks.system()?.now(precision).into_std();
let d = now
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map_err(|_| {
Error::trap(anyhow::Error::msg("current time before unix epoch"))
})?;
Ok(d.as_nanos().try_into()?)
}
types::Clockid::Monotonic => {
let clock = self.clocks.monotonic()?;
let now = clock.abs_clock.now(precision);
let d = now.duration_since(clock.creation_time);
Ok(d.as_nanos().try_into()?)
}
types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
Err(Error::badf().context("process and thread clocks are not supported"))
}
}
}
async fn fd_advise(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<(), Error> {
self.table()
.get_file(u32::from(fd))?
.file
.advise(offset, len, advice.into())
.await?;
Ok(())
}
async fn fd_allocate(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
) -> Result<(), Error> {
let _ = self.table().get_file(u32::from(fd))?;
Err(Error::not_supported())
}
async fn fd_close(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<(), Error> {
let table = self.table();
let fd = u32::from(fd);
if !table.contains_key(fd) {
return Err(Error::badf().context("key not in table"));
}
if table.is::<FileEntry>(fd) {
let _ = table.delete::<FileEntry>(fd);
} else if table.is::<DirEntry>(fd) {
let _ = table.delete::<DirEntry>(fd);
} else {
return Err(Error::badf().context("key does not refer to file or directory"));
}
Ok(())
}
async fn fd_datasync(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<(), Error> {
self.table()
.get_file(u32::from(fd))?
.file
.datasync()
.await?;
Ok(())
}
async fn fd_fdstat_get(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Fdstat, Error> {
let table = self.table();
let fd = u32::from(fd);
if table.is::<FileEntry>(fd) {
let file_entry: Arc<FileEntry> = table.get(fd)?;
let fdstat = file_entry.get_fdstat().await?;
Ok(types::Fdstat::from(&fdstat))
} else if table.is::<DirEntry>(fd) {
let _dir_entry: Arc<DirEntry> = table.get(fd)?;
let dir_fdstat = types::Fdstat {
fs_filetype: types::Filetype::Directory,
fs_rights_base: directory_base_rights(),
fs_rights_inheriting: directory_inheriting_rights(),
fs_flags: types::Fdflags::empty(),
};
Ok(dir_fdstat)
} else {
Err(Error::badf())
}
}
async fn fd_fdstat_set_flags(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
flags: types::Fdflags,
) -> Result<(), Error> {
if let Some(table) = self.table_mut() {
table
.get_file_mut(u32::from(fd))?
.file
.set_fdflags(FdFlags::from(flags))
.await
} else {
log::warn!("`fd_fdstat_set_flags` does not work with wasi-threads enabled; see https://github.com/bytecodealliance/wasmtime/issues/5643");
Err(Error::not_supported())
}
}
async fn fd_fdstat_set_rights(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
_fs_rights_base: types::Rights,
_fs_rights_inheriting: types::Rights,
) -> Result<(), Error> {
let table = self.table();
let fd = u32::from(fd);
if table.is::<FileEntry>(fd) {
let _file_entry: Arc<FileEntry> = table.get(fd)?;
Ok(())
} else if table.is::<DirEntry>(fd) {
let _dir_entry: Arc<DirEntry> = table.get(fd)?;
Ok(())
} else {
Err(Error::badf())
}
}
async fn fd_filestat_get(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Filestat, Error> {
let table = self.table();
let fd = u32::from(fd);
if table.is::<FileEntry>(fd) {
let filestat = table.get_file(fd)?.file.get_filestat().await?;
Ok(filestat.into())
} else if table.is::<DirEntry>(fd) {
let filestat = table.get_dir(fd)?.dir.get_filestat().await?;
Ok(filestat.into())
} else {
Err(Error::badf())
}
}
async fn fd_filestat_set_size(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
size: types::Filesize,
) -> Result<(), Error> {
self.table()
.get_file(u32::from(fd))?
.file
.set_filestat_size(size)
.await?;
Ok(())
}
async fn fd_filestat_set_times(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<(), Error> {
let fd = u32::from(fd);
let table = self.table();
let set_atim = fst_flags.contains(types::Fstflags::ATIM);
let set_atim_now = fst_flags.contains(types::Fstflags::ATIM_NOW);
let set_mtim = fst_flags.contains(types::Fstflags::MTIM);
let set_mtim_now = fst_flags.contains(types::Fstflags::MTIM_NOW);
let atim = systimespec(set_atim, atim, set_atim_now).map_err(|e| e.context("atim"))?;
let mtim = systimespec(set_mtim, mtim, set_mtim_now).map_err(|e| e.context("mtim"))?;
if table.is::<FileEntry>(fd) {
table
.get_file(fd)
.expect("checked that entry is file")
.file
.set_times(atim, mtim)
.await
} else if table.is::<DirEntry>(fd) {
table
.get_dir(fd)
.expect("checked that entry is dir")
.dir
.set_times(".", atim, mtim, false)
.await
} else {
Err(Error::badf())
}
}
async fn fd_read(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
iovs: types::IovecArray,
) -> Result<types::Size, Error> {
let f = self.table().get_file(u32::from(fd))?;
if !f.access_mode.contains(FileAccessMode::READ) {
Err(types::Errno::Badf)?
}
let f = &f.file;
let iovs: Vec<wiggle::GuestPtr<[u8]>> = iovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = memory.read(iov_ptr)?;
Ok(iov.buf.as_array(iov.buf_len))
})
.collect::<Result<_, Error>>()?;
let is_shared_memory = memory.is_shared_memory();
let bytes_read: u64 = if is_shared_memory {
let iov = iovs.into_iter().next();
if let Some(iov) = iov {
let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)];
let bytes_read = f.read_vectored(&mut [IoSliceMut::new(&mut buffer)]).await?;
let iov = iov
.get_range(0..bytes_read.try_into()?)
.expect("it should always be possible to slice the iov smaller");
memory.copy_from_slice(&buffer[0..bytes_read.try_into()?], iov)?;
bytes_read
} else {
return Ok(0);
}
} else {
let guest_slice: &mut [u8] = match iovs.into_iter().filter(|iov| iov.len() > 0).next() {
Some(iov) => memory.as_slice_mut(iov)?.unwrap(),
None => return Ok(0),
};
f.read_vectored(&mut [IoSliceMut::new(guest_slice)]).await?
};
Ok(types::Size::try_from(bytes_read)?)
}
async fn fd_pread(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
iovs: types::IovecArray,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let f = self.table().get_file(u32::from(fd))?;
if !f.access_mode.contains(FileAccessMode::READ) {
Err(types::Errno::Badf)?
}
let f = &f.file;
let iovs: Vec<wiggle::GuestPtr<[u8]>> = iovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = memory.read(iov_ptr)?;
Ok(iov.buf.as_array(iov.buf_len))
})
.collect::<Result<_, Error>>()?;
let is_shared_memory = memory.is_shared_memory();
let bytes_read: u64 = if is_shared_memory {
let iov = iovs.into_iter().next();
if let Some(iov) = iov {
let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)];
let bytes_read = f
.read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset)
.await?;
let iov = iov
.get_range(0..bytes_read.try_into()?)
.expect("it should always be possible to slice the iov smaller");
memory.copy_from_slice(&buffer[0..bytes_read.try_into()?], iov)?;
bytes_read
} else {
return Ok(0);
}
} else {
let guest_slice: &mut [u8] = match iovs.into_iter().filter(|iov| iov.len() > 0).next() {
Some(iov) => memory.as_slice_mut(iov)?.unwrap(),
None => return Ok(0),
};
f.read_vectored_at(&mut [IoSliceMut::new(guest_slice)], offset)
.await?
};
Ok(types::Size::try_from(bytes_read)?)
}
async fn fd_write(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
ciovs: types::CiovecArray,
) -> Result<types::Size, Error> {
let f = self.table().get_file(u32::from(fd))?;
if !f.access_mode.contains(FileAccessMode::WRITE) {
Err(types::Errno::Badf)?
}
let f = &f.file;
let guest_slices: Vec<Cow<[u8]>> = ciovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Ciovec = memory.read(iov_ptr)?;
Ok(memory.as_cow(iov.buf.as_array(iov.buf_len))?)
})
.collect::<Result<_, Error>>()?;
let ioslices: Vec<IoSlice> = guest_slices
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored(&ioslices).await?;
Ok(types::Size::try_from(bytes_written)?)
}
async fn fd_pwrite(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
ciovs: types::CiovecArray,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let f = self.table().get_file(u32::from(fd))?;
if !f.access_mode.contains(FileAccessMode::WRITE) {
Err(types::Errno::Badf)?
}
let f = &f.file;
let guest_slices: Vec<Cow<[u8]>> = ciovs
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Ciovec = memory.read(iov_ptr)?;
Ok(memory.as_cow(iov.buf.as_array(iov.buf_len))?)
})
.collect::<Result<_, Error>>()?;
let ioslices: Vec<IoSlice> = guest_slices
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored_at(&ioslices, offset).await?;
Ok(types::Size::try_from(bytes_written)?)
}
async fn fd_prestat_get(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Prestat, Error> {
let table = self.table();
let dir_entry: Arc<DirEntry> = table.get(u32::from(fd)).map_err(|_| Error::badf())?;
if let Some(ref preopen) = dir_entry.preopen_path() {
let path_str = preopen.to_str().ok_or_else(|| Error::not_supported())?;
let pr_name_len = u32::try_from(path_str.as_bytes().len())?;
Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len }))
} else {
Err(Error::not_supported().context("file is not a preopen"))
}
}
async fn fd_prestat_dir_name(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
path: GuestPtr<u8>,
path_max_len: types::Size,
) -> Result<(), Error> {
let table = self.table();
let dir_entry: Arc<DirEntry> = table.get(u32::from(fd)).map_err(|_| Error::not_dir())?;
if let Some(ref preopen) = dir_entry.preopen_path() {
let path_bytes = preopen
.to_str()
.ok_or_else(|| Error::not_supported())?
.as_bytes();
let path_len = path_bytes.len();
if path_len > path_max_len as usize {
return Err(Error::name_too_long());
}
let path = path.as_array(path_len as u32);
memory.copy_from_slice(path_bytes, path)?;
Ok(())
} else {
Err(Error::not_supported())
}
}
async fn fd_renumber(
&mut self,
_memory: &mut GuestMemory<'_>,
from: types::Fd,
to: types::Fd,
) -> Result<(), Error> {
let table = self.table();
let from = u32::from(from);
let to = u32::from(to);
if !table.contains_key(from) {
return Err(Error::badf());
}
table.renumber(from, to)
}
async fn fd_seek(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize, Error> {
use std::io::SeekFrom;
let whence = match whence {
types::Whence::Cur => SeekFrom::Current(offset),
types::Whence::End => SeekFrom::End(offset),
types::Whence::Set => {
SeekFrom::Start(offset.try_into().map_err(|_| Error::invalid_argument())?)
}
};
let newoffset = self
.table()
.get_file(u32::from(fd))?
.file
.seek(whence)
.await?;
Ok(newoffset)
}
async fn fd_sync(&mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd) -> Result<(), Error> {
self.table().get_file(u32::from(fd))?.file.sync().await?;
Ok(())
}
async fn fd_tell(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Filesize, Error> {
let offset = self
.table()
.get_file(u32::from(fd))?
.file
.seek(std::io::SeekFrom::Current(0))
.await?;
Ok(offset)
}
async fn fd_readdir(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
mut buf: GuestPtr<u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size, Error> {
let mut bufused = 0;
for entity in self
.table()
.get_dir(u32::from(fd))?
.dir
.readdir(ReaddirCursor::from(cookie))
.await?
{
let entity = entity?;
let dirent_raw = dirent_bytes(types::Dirent::try_from(&entity)?);
let dirent_len: types::Size = dirent_raw.len().try_into()?;
let name_raw = entity.name.as_bytes();
let name_len: types::Size = name_raw.len().try_into()?;
let dirent_copy_len = std::cmp::min(dirent_len, buf_len - bufused);
let raw = buf.as_array(dirent_copy_len);
memory.copy_from_slice(&dirent_raw[..dirent_copy_len as usize], raw)?;
if dirent_copy_len < dirent_len {
return Ok(buf_len);
}
buf = buf.add(dirent_copy_len)?;
bufused += dirent_copy_len;
let name_copy_len = std::cmp::min(name_len, buf_len - bufused);
let raw = buf.as_array(name_copy_len);
memory.copy_from_slice(&name_raw[..name_copy_len as usize], raw)?;
if name_copy_len < name_len {
return Ok(buf_len);
}
buf = buf.add(name_copy_len)?;
bufused += name_copy_len;
}
Ok(bufused)
}
async fn path_create_directory(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.dir
.create_dir(memory.as_cow_str(path)?.deref())
.await
}
async fn path_filestat_get(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
flags: types::Lookupflags,
path: GuestPtr<str>,
) -> Result<types::Filestat, Error> {
let filestat = self
.table()
.get_dir(u32::from(dirfd))?
.dir
.get_path_filestat(
memory.as_cow_str(path)?.deref(),
flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)
.await?;
Ok(types::Filestat::from(filestat))
}
async fn path_filestat_set_times(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
flags: types::Lookupflags,
path: GuestPtr<str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<(), Error> {
let set_atim = fst_flags.contains(types::Fstflags::ATIM);
let set_atim_now = fst_flags.contains(types::Fstflags::ATIM_NOW);
let set_mtim = fst_flags.contains(types::Fstflags::MTIM);
let set_mtim_now = fst_flags.contains(types::Fstflags::MTIM_NOW);
let atim = systimespec(set_atim, atim, set_atim_now).map_err(|e| e.context("atim"))?;
let mtim = systimespec(set_mtim, mtim, set_mtim_now).map_err(|e| e.context("mtim"))?;
self.table()
.get_dir(u32::from(dirfd))?
.dir
.set_times(
memory.as_cow_str(path)?.deref(),
atim,
mtim,
flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)
.await
}
async fn path_link(
&mut self,
memory: &mut GuestMemory<'_>,
src_fd: types::Fd,
src_flags: types::Lookupflags,
src_path: GuestPtr<str>,
target_fd: types::Fd,
target_path: GuestPtr<str>,
) -> Result<(), Error> {
let table = self.table();
let src_dir = table.get_dir(u32::from(src_fd))?;
let target_dir = table.get_dir(u32::from(target_fd))?;
let symlink_follow = src_flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
if symlink_follow {
return Err(Error::invalid_argument()
.context("symlink following on path_link is not supported"));
}
src_dir
.dir
.hard_link(
memory.as_cow_str(src_path)?.deref(),
target_dir.dir.deref(),
memory.as_cow_str(target_path)?.deref(),
)
.await
}
async fn path_open(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: GuestPtr<str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
_fs_rights_inheriting: types::Rights,
fdflags: types::Fdflags,
) -> Result<types::Fd, Error> {
let table = self.table();
let dirfd = u32::from(dirfd);
if table.is::<FileEntry>(dirfd) {
return Err(Error::not_dir());
}
let dir_entry = table.get_dir(dirfd)?;
let symlink_follow = dirflags.contains(types::Lookupflags::SYMLINK_FOLLOW);
let oflags = OFlags::from(&oflags);
let fdflags = FdFlags::from(fdflags);
let path = memory.as_cow_str(path)?;
let read = fs_rights_base.contains(types::Rights::FD_READ);
let write = fs_rights_base.contains(types::Rights::FD_WRITE);
let access_mode = if read {
FileAccessMode::READ
} else {
FileAccessMode::empty()
} | if write {
FileAccessMode::WRITE
} else {
FileAccessMode::empty()
};
let file = dir_entry
.dir
.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)
.await?;
drop(dir_entry);
let fd = match file {
OpenResult::File(file) => table.push(Arc::new(FileEntry::new(file, access_mode)))?,
OpenResult::Dir(child_dir) => table.push(Arc::new(DirEntry::new(None, child_dir)))?,
};
Ok(types::Fd::from(fd))
}
async fn path_readlink(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
buf: GuestPtr<u8>,
buf_len: types::Size,
) -> Result<types::Size, Error> {
let link = self
.table()
.get_dir(u32::from(dirfd))?
.dir
.read_link(memory.as_cow_str(path)?.deref())
.await?
.into_os_string()
.into_string()
.map_err(|_| Error::illegal_byte_sequence().context("link contents"))?;
let link_bytes = link.as_bytes();
let link_len = std::cmp::min(link_bytes.len(), buf_len as usize);
let buf = buf.as_array(link_len as u32);
memory.copy_from_slice(&link_bytes[..link_len], buf)?;
Ok(link_len as types::Size)
}
async fn path_remove_directory(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.dir
.remove_dir(memory.as_cow_str(path)?.deref())
.await
}
async fn path_rename(
&mut self,
memory: &mut GuestMemory<'_>,
src_fd: types::Fd,
src_path: GuestPtr<str>,
dest_fd: types::Fd,
dest_path: GuestPtr<str>,
) -> Result<(), Error> {
let table = self.table();
let src_dir = table.get_dir(u32::from(src_fd))?;
let dest_dir = table.get_dir(u32::from(dest_fd))?;
src_dir
.dir
.rename(
memory.as_cow_str(src_path)?.deref(),
dest_dir.dir.deref(),
memory.as_cow_str(dest_path)?.deref(),
)
.await
}
async fn path_symlink(
&mut self,
memory: &mut GuestMemory<'_>,
src_path: GuestPtr<str>,
dirfd: types::Fd,
dest_path: GuestPtr<str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.dir
.symlink(
memory.as_cow_str(src_path)?.deref(),
memory.as_cow_str(dest_path)?.deref(),
)
.await
}
async fn path_unlink_file(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.dir
.unlink_file(memory.as_cow_str(path)?.deref())
.await
}
async fn poll_oneoff(
&mut self,
memory: &mut GuestMemory<'_>,
subs: GuestPtr<types::Subscription>,
events: GuestPtr<types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size, Error> {
if nsubscriptions == 0 {
return Err(Error::invalid_argument().context("nsubscriptions must be nonzero"));
}
if nsubscriptions == 1 {
let sub = memory.read(subs)?;
if let types::SubscriptionU::Clock(clocksub) = sub.u {
if !clocksub
.flags
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
{
self.sched
.sleep(Duration::from_nanos(clocksub.timeout))
.await?;
memory.write(
events,
types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::Clock,
fd_readwrite: fd_readwrite_empty(),
},
)?;
return Ok(1);
}
}
}
let table = &self.table;
let mut read_refs: Vec<(Arc<FileEntry>, Option<Userdata>)> = Vec::new();
let mut write_refs: Vec<(Arc<FileEntry>, Option<Userdata>)> = Vec::new();
let mut poll = Poll::new();
let subs = subs.as_array(nsubscriptions);
for sub_elem in subs.iter() {
let sub_ptr = sub_elem?;
let sub = memory.read(sub_ptr)?;
match sub.u {
types::SubscriptionU::Clock(clocksub) => match clocksub.id {
types::Clockid::Monotonic => {
let clock = self.clocks.monotonic()?;
let precision = Duration::from_nanos(clocksub.precision);
let duration = Duration::from_nanos(clocksub.timeout);
let start = if clocksub
.flags
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
{
clock.creation_time
} else {
clock.abs_clock.now(precision)
};
let deadline = start
.checked_add(duration)
.ok_or_else(|| Error::overflow().context("deadline"))?;
poll.subscribe_monotonic_clock(
&*clock.abs_clock,
deadline,
precision,
sub.userdata.into(),
)
}
types::Clockid::Realtime => {
let clock = self.clocks.monotonic()?;
let precision = Duration::from_nanos(clocksub.precision);
let duration = Duration::from_nanos(clocksub.timeout);
let deadline = if clocksub
.flags
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
{
return Err(Error::not_supported());
} else {
clock
.abs_clock
.now(precision)
.checked_add(duration)
.ok_or_else(|| Error::overflow().context("deadline"))?
};
poll.subscribe_monotonic_clock(
&*clock.abs_clock,
deadline,
precision,
sub.userdata.into(),
)
}
_ => Err(Error::invalid_argument()
.context("timer subscriptions only support monotonic timer"))?,
},
types::SubscriptionU::FdRead(readsub) => {
let fd = readsub.file_descriptor;
let file_ref = table.get_file(u32::from(fd))?;
read_refs.push((file_ref, Some(sub.userdata.into())));
}
types::SubscriptionU::FdWrite(writesub) => {
let fd = writesub.file_descriptor;
let file_ref = table.get_file(u32::from(fd))?;
write_refs.push((file_ref, Some(sub.userdata.into())));
}
}
}
let mut read_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new();
for (file_lock, userdata) in read_refs.iter_mut() {
read_mut_refs.push((file_lock.file.deref(), userdata.take().unwrap()));
}
for (f, ud) in read_mut_refs.iter_mut() {
poll.subscribe_read(*f, *ud);
}
let mut write_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new();
for (file_lock, userdata) in write_refs.iter_mut() {
write_mut_refs.push((file_lock.file.deref(), userdata.take().unwrap()));
}
for (f, ud) in write_mut_refs.iter_mut() {
poll.subscribe_write(*f, *ud);
}
self.sched.poll_oneoff(&mut poll).await?;
let results = poll.results();
let num_results = results.len();
assert!(
num_results <= nsubscriptions as usize,
"results exceeds subscriptions"
);
let events = events.as_array(
num_results
.try_into()
.expect("not greater than nsubscriptions"),
);
for ((result, userdata), event_elem) in results.into_iter().zip(events.iter()) {
let event_ptr = event_elem?;
let userdata: types::Userdata = userdata.into();
memory.write(
event_ptr,
match result {
SubscriptionResult::Read(r) => {
let type_ = types::Eventtype::FdRead;
match r {
Ok((nbytes, flags)) => types::Event {
userdata,
error: types::Errno::Success,
type_,
fd_readwrite: types::EventFdReadwrite {
nbytes,
flags: types::Eventrwflags::from(&flags),
},
},
Err(e) => types::Event {
userdata,
error: e.downcast().map_err(Error::trap)?,
type_,
fd_readwrite: fd_readwrite_empty(),
},
}
}
SubscriptionResult::Write(r) => {
let type_ = types::Eventtype::FdWrite;
match r {
Ok((nbytes, flags)) => types::Event {
userdata,
error: types::Errno::Success,
type_,
fd_readwrite: types::EventFdReadwrite {
nbytes,
flags: types::Eventrwflags::from(&flags),
},
},
Err(e) => types::Event {
userdata,
error: e.downcast().map_err(Error::trap)?,
type_,
fd_readwrite: fd_readwrite_empty(),
},
}
}
SubscriptionResult::MonotonicClock(r) => {
let type_ = types::Eventtype::Clock;
types::Event {
userdata,
error: match r {
Ok(()) => types::Errno::Success,
Err(e) => e.downcast().map_err(Error::trap)?,
},
type_,
fd_readwrite: fd_readwrite_empty(),
}
}
},
)?;
}
Ok(num_results.try_into().expect("results fit into memory"))
}
async fn proc_exit(
&mut self,
_memory: &mut GuestMemory<'_>,
status: types::Exitcode,
) -> anyhow::Error {
if status < 126 {
I32Exit(status as i32).into()
} else {
anyhow::Error::msg("exit with invalid exit status outside of [0..126)")
}
}
async fn proc_raise(
&mut self,
_memory: &mut GuestMemory<'_>,
_sig: types::Signal,
) -> Result<(), Error> {
Err(Error::trap(anyhow::Error::msg("proc_raise unsupported")))
}
async fn sched_yield(&mut self, _memory: &mut GuestMemory<'_>) -> Result<(), Error> {
self.sched.sched_yield().await
}
async fn random_get(
&mut self,
memory: &mut GuestMemory<'_>,
buf: GuestPtr<u8>,
buf_len: types::Size,
) -> Result<(), Error> {
let buf = buf.as_array(buf_len);
if memory.is_shared_memory() {
let mut copied: u32 = 0;
while copied < buf.len() {
let len = (buf.len() - copied).min(MAX_SHARED_BUFFER_SIZE as u32);
let mut tmp = vec![0; len as usize];
self.random.lock().unwrap().try_fill_bytes(&mut tmp)?;
let dest = buf.get_range(copied..copied + len).unwrap();
memory.copy_from_slice(&tmp, dest)?;
copied += len;
}
} else {
let mem = &mut memory.as_slice_mut(buf)?.unwrap();
self.random.lock().unwrap().try_fill_bytes(mem)?;
}
Ok(())
}
async fn sock_accept(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
flags: types::Fdflags,
) -> Result<types::Fd, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?;
let file = f.file.sock_accept(FdFlags::from(flags)).await?;
let fd = table.push(Arc::new(FileEntry::new(file, FileAccessMode::all())))?;
Ok(types::Fd::from(fd))
}
async fn sock_recv(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
ri_data: types::IovecArray,
ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags), Error> {
let f = self.table().get_file(u32::from(fd))?;
let iovs: Vec<wiggle::GuestPtr<[u8]>> = ri_data
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = memory.read(iov_ptr)?;
Ok(iov.buf.as_array(iov.buf_len))
})
.collect::<Result<_, Error>>()?;
let is_shared_memory = memory.is_shared_memory();
let (bytes_read, ro_flags) = if is_shared_memory {
let iov = iovs.into_iter().next();
if let Some(iov) = iov {
let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)];
let (bytes_read, ro_flags) = f
.file
.sock_recv(&mut [IoSliceMut::new(&mut buffer)], RiFlags::from(ri_flags))
.await?;
let iov = iov
.get_range(0..bytes_read.try_into()?)
.expect("it should always be possible to slice the iov smaller");
memory.copy_from_slice(&buffer[0..bytes_read.try_into()?], iov)?;
(bytes_read, ro_flags)
} else {
return Ok((0, RoFlags::empty().into()));
}
} else {
let guest_slice: &mut [u8] = match iovs.into_iter().filter(|iov| iov.len() > 0).next() {
Some(iov) => memory.as_slice_mut(iov)?.unwrap(),
None => &mut [],
};
f.file
.sock_recv(&mut [IoSliceMut::new(guest_slice)], RiFlags::from(ri_flags))
.await?
};
Ok((types::Size::try_from(bytes_read)?, ro_flags.into()))
}
async fn sock_send(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
si_data: types::CiovecArray,
_si_flags: types::Siflags,
) -> Result<types::Size, Error> {
let f = self.table().get_file(u32::from(fd))?;
let guest_slices: Vec<Cow<[u8]>> = si_data
.iter()
.map(|iov_ptr| {
let iov_ptr = iov_ptr?;
let iov: types::Ciovec = memory.read(iov_ptr)?;
Ok(memory.as_cow(iov.buf.as_array(iov.buf_len))?)
})
.collect::<Result<_, Error>>()?;
let ioslices: Vec<IoSlice> = guest_slices
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.file.sock_send(&ioslices, SiFlags::empty()).await?;
Ok(types::Size::try_from(bytes_written)?)
}
async fn sock_shutdown(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
how: types::Sdflags,
) -> Result<(), Error> {
let f = self.table().get_file(u32::from(fd))?;
f.file.sock_shutdown(SdFlags::from(how)).await
}
}
impl From<types::Advice> for Advice {
fn from(advice: types::Advice) -> Advice {
match advice {
types::Advice::Normal => Advice::Normal,
types::Advice::Sequential => Advice::Sequential,
types::Advice::Random => Advice::Random,
types::Advice::Willneed => Advice::WillNeed,
types::Advice::Dontneed => Advice::DontNeed,
types::Advice::Noreuse => Advice::NoReuse,
}
}
}
impl From<&FdStat> for types::Fdstat {
fn from(fdstat: &FdStat) -> types::Fdstat {
let mut fs_rights_base = types::Rights::empty();
if fdstat.access_mode.contains(FileAccessMode::READ) {
fs_rights_base |= types::Rights::FD_READ;
}
if fdstat.access_mode.contains(FileAccessMode::WRITE) {
fs_rights_base |= types::Rights::FD_WRITE;
}
types::Fdstat {
fs_filetype: types::Filetype::from(&fdstat.filetype),
fs_rights_base,
fs_rights_inheriting: types::Rights::empty(),
fs_flags: types::Fdflags::from(fdstat.flags),
}
}
}
impl From<&FileType> for types::Filetype {
fn from(ft: &FileType) -> types::Filetype {
match ft {
FileType::Directory => types::Filetype::Directory,
FileType::BlockDevice => types::Filetype::BlockDevice,
FileType::CharacterDevice => types::Filetype::CharacterDevice,
FileType::RegularFile => types::Filetype::RegularFile,
FileType::SocketDgram => types::Filetype::SocketDgram,
FileType::SocketStream => types::Filetype::SocketStream,
FileType::SymbolicLink => types::Filetype::SymbolicLink,
FileType::Unknown => types::Filetype::Unknown,
FileType::Pipe => types::Filetype::Unknown,
}
}
}
macro_rules! convert_flags {
($from:ty, $to:ty, $($flag:ident),+) => {
impl From<$from> for $to {
fn from(f: $from) -> $to {
let mut out = <$to>::empty();
$(
if f.contains(<$from>::$flag) {
out |= <$to>::$flag;
}
)+
out
}
}
}
}
macro_rules! convert_flags_bidirectional {
($from:ty, $to:ty, $($rest:tt)*) => {
convert_flags!($from, $to, $($rest)*);
convert_flags!($to, $from, $($rest)*);
}
}
convert_flags_bidirectional!(
FdFlags,
types::Fdflags,
APPEND,
DSYNC,
NONBLOCK,
RSYNC,
SYNC
);
convert_flags_bidirectional!(RiFlags, types::Riflags, RECV_PEEK, RECV_WAITALL);
convert_flags_bidirectional!(RoFlags, types::Roflags, RECV_DATA_TRUNCATED);
convert_flags_bidirectional!(SdFlags, types::Sdflags, RD, WR);
impl From<&types::Oflags> for OFlags {
fn from(oflags: &types::Oflags) -> OFlags {
let mut out = OFlags::empty();
if oflags.contains(types::Oflags::CREAT) {
out = out | OFlags::CREATE;
}
if oflags.contains(types::Oflags::DIRECTORY) {
out = out | OFlags::DIRECTORY;
}
if oflags.contains(types::Oflags::EXCL) {
out = out | OFlags::EXCLUSIVE;
}
if oflags.contains(types::Oflags::TRUNC) {
out = out | OFlags::TRUNCATE;
}
out
}
}
impl From<&OFlags> for types::Oflags {
fn from(oflags: &OFlags) -> types::Oflags {
let mut out = types::Oflags::empty();
if oflags.contains(OFlags::CREATE) {
out = out | types::Oflags::CREAT;
}
if oflags.contains(OFlags::DIRECTORY) {
out = out | types::Oflags::DIRECTORY;
}
if oflags.contains(OFlags::EXCLUSIVE) {
out = out | types::Oflags::EXCL;
}
if oflags.contains(OFlags::TRUNCATE) {
out = out | types::Oflags::TRUNC;
}
out
}
}
impl From<Filestat> for types::Filestat {
fn from(stat: Filestat) -> types::Filestat {
types::Filestat {
dev: stat.device_id,
ino: stat.inode,
filetype: types::Filetype::from(&stat.filetype),
nlink: stat.nlink,
size: stat.size,
atim: stat
.atim
.map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64)
.unwrap_or(0),
mtim: stat
.mtim
.map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64)
.unwrap_or(0),
ctim: stat
.ctim
.map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64)
.unwrap_or(0),
}
}
}
impl TryFrom<&ReaddirEntity> for types::Dirent {
type Error = Error;
fn try_from(e: &ReaddirEntity) -> Result<types::Dirent, Error> {
Ok(types::Dirent {
d_ino: e.inode,
d_namlen: e.name.as_bytes().len().try_into()?,
d_type: types::Filetype::from(&e.filetype),
d_next: e.next.into(),
})
}
}
fn dirent_bytes(dirent: types::Dirent) -> Vec<u8> {
use wiggle::GuestType;
assert_eq!(
types::Dirent::guest_size(),
std::mem::size_of::<types::Dirent>() as u32,
"Dirent guest repr and host repr should match"
);
assert_eq!(
1,
std::mem::size_of_val(&dirent.d_type),
"Dirent member d_type should be endian-invariant"
);
let size = types::Dirent::guest_size()
.try_into()
.expect("Dirent is smaller than 2^32");
let mut bytes = Vec::with_capacity(size);
bytes.resize(size, 0);
let ptr = bytes.as_mut_ptr().cast::<types::Dirent>();
let guest_dirent = types::Dirent {
d_ino: dirent.d_ino.to_le(),
d_namlen: dirent.d_namlen.to_le(),
d_type: dirent.d_type, d_next: dirent.d_next.to_le(),
};
unsafe { ptr.write_unaligned(guest_dirent) };
bytes
}
impl From<&RwEventFlags> for types::Eventrwflags {
fn from(flags: &RwEventFlags) -> types::Eventrwflags {
let mut out = types::Eventrwflags::empty();
if flags.contains(RwEventFlags::HANGUP) {
out = out | types::Eventrwflags::FD_READWRITE_HANGUP;
}
out
}
}
fn fd_readwrite_empty() -> types::EventFdReadwrite {
types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
}
}
fn systimespec(
set: bool,
ts: types::Timestamp,
now: bool,
) -> Result<Option<SystemTimeSpec>, Error> {
if set && now {
Err(Error::invalid_argument())
} else if set {
Ok(Some(SystemTimeSpec::Absolute(
SystemClock::UNIX_EPOCH + Duration::from_nanos(ts),
)))
} else if now {
Ok(Some(SystemTimeSpec::SymbolicNow))
} else {
Ok(None)
}
}
pub(crate) fn directory_base_rights() -> types::Rights {
types::Rights::PATH_CREATE_DIRECTORY
| types::Rights::PATH_CREATE_FILE
| types::Rights::PATH_LINK_SOURCE
| types::Rights::PATH_LINK_TARGET
| types::Rights::PATH_OPEN
| types::Rights::FD_READDIR
| types::Rights::PATH_READLINK
| types::Rights::PATH_RENAME_SOURCE
| types::Rights::PATH_RENAME_TARGET
| types::Rights::PATH_SYMLINK
| types::Rights::PATH_REMOVE_DIRECTORY
| types::Rights::PATH_UNLINK_FILE
| types::Rights::PATH_FILESTAT_GET
| types::Rights::PATH_FILESTAT_SET_TIMES
| types::Rights::FD_FILESTAT_GET
| types::Rights::FD_FILESTAT_SET_TIMES
}
pub(crate) fn directory_inheriting_rights() -> types::Rights {
types::Rights::FD_DATASYNC
| types::Rights::FD_READ
| types::Rights::FD_SEEK
| types::Rights::FD_FDSTAT_SET_FLAGS
| types::Rights::FD_SYNC
| types::Rights::FD_TELL
| types::Rights::FD_WRITE
| types::Rights::FD_ADVISE
| types::Rights::FD_ALLOCATE
| types::Rights::FD_FILESTAT_GET
| types::Rights::FD_FILESTAT_SET_SIZE
| types::Rights::FD_FILESTAT_SET_TIMES
| types::Rights::POLL_FD_READWRITE
| directory_base_rights()
}