use crate::bindings::{
cli::{
stderr::Host as _, stdin::Host as _, stdout::Host as _, terminal_input, terminal_output,
terminal_stderr::Host as _, terminal_stdin::Host as _, terminal_stdout::Host as _,
},
clocks::{monotonic_clock, wall_clock},
filesystem::{preopens::Host as _, types as filesystem},
io::streams,
};
use crate::{
FsError, IsATTY, ResourceTable, StreamError, StreamResult, WasiCtx, WasiImpl, WasiView,
};
use anyhow::{bail, Context};
use std::collections::{BTreeMap, HashSet};
use std::mem::{self, size_of, size_of_val};
use std::ops::{Deref, DerefMut};
use std::slice;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use system_interface::fs::FileIoExt;
use wasmtime::component::Resource;
use wiggle::tracing::instrument;
use wiggle::{GuestError, GuestMemory, GuestPtr, GuestType};
use crate::bindings::cli::environment::Host as _;
use crate::bindings::filesystem::types::HostDescriptor as _;
use crate::bindings::io::poll::Host as _;
use crate::bindings::random::random::Host as _;
pub struct WasiP1Ctx {
table: ResourceTable,
wasi: WasiCtx,
adapter: WasiPreview1Adapter,
}
impl WasiP1Ctx {
pub(crate) fn new(wasi: WasiCtx) -> Self {
Self {
table: ResourceTable::new(),
wasi,
adapter: WasiPreview1Adapter::new(),
}
}
fn as_wasi_impl(&mut self) -> WasiImpl<&mut Self> {
WasiImpl(self)
}
}
impl WasiView for WasiP1Ctx {
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
}
#[derive(Debug)]
struct File {
fd: Resource<filesystem::Descriptor>,
position: Arc<AtomicU64>,
append: bool,
blocking_mode: BlockingMode,
}
#[derive(Clone, Copy, Debug)]
enum BlockingMode {
Blocking,
NonBlocking,
}
impl BlockingMode {
fn from_fdflags(flags: &types::Fdflags) -> Self {
if flags.contains(types::Fdflags::NONBLOCK) {
BlockingMode::NonBlocking
} else {
BlockingMode::Blocking
}
}
async fn read(
&self,
host: &mut impl streams::HostInputStream,
input_stream: Resource<streams::InputStream>,
max_size: usize,
) -> Result<Vec<u8>, types::Error> {
let max_size = max_size.try_into().unwrap_or(u64::MAX);
match streams::HostInputStream::blocking_read(host, input_stream, max_size).await {
Ok(r) if r.is_empty() => Err(types::Errno::Intr.into()),
Ok(r) => Ok(r),
Err(StreamError::Closed) => Ok(Vec::new()),
Err(e) => Err(e.into()),
}
}
async fn write(
&self,
memory: &mut GuestMemory<'_>,
host: &mut impl streams::HostOutputStream,
output_stream: Resource<streams::OutputStream>,
bytes: GuestPtr<[u8]>,
) -> StreamResult<usize> {
use streams::HostOutputStream as Streams;
let bytes = memory
.as_cow(bytes)
.map_err(|e| StreamError::Trap(e.into()))?;
let mut bytes = &bytes[..];
let total = bytes.len();
while !bytes.is_empty() {
let len = bytes.len().min(4096);
let (chunk, rest) = bytes.split_at(len);
bytes = rest;
Streams::blocking_write_and_flush(host, output_stream.borrowed(), Vec::from(chunk))
.await?
}
Ok(total)
}
}
#[derive(Debug)]
enum Descriptor {
Stdin {
stream: Resource<streams::InputStream>,
isatty: IsATTY,
},
Stdout {
stream: Resource<streams::OutputStream>,
isatty: IsATTY,
},
Stderr {
stream: Resource<streams::OutputStream>,
isatty: IsATTY,
},
Directory {
fd: Resource<filesystem::Descriptor>,
preopen_path: Option<String>,
},
File(File),
}
#[derive(Debug, Default)]
struct WasiPreview1Adapter {
descriptors: Option<Descriptors>,
}
#[derive(Debug, Default)]
struct Descriptors {
used: BTreeMap<u32, Descriptor>,
free: Vec<u32>,
}
impl Deref for Descriptors {
type Target = BTreeMap<u32, Descriptor>;
fn deref(&self) -> &Self::Target {
&self.used
}
}
impl DerefMut for Descriptors {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.used
}
}
impl Descriptors {
fn new(mut host: WasiImpl<&mut WasiP1Ctx>) -> Result<Self, types::Error> {
let mut descriptors = Self::default();
descriptors.push(Descriptor::Stdin {
stream: host
.get_stdin()
.context("failed to call `get-stdin`")
.map_err(types::Error::trap)?,
isatty: if let Some(term_in) = host
.get_terminal_stdin()
.context("failed to call `get-terminal-stdin`")
.map_err(types::Error::trap)?
{
terminal_input::HostTerminalInput::drop(&mut host, term_in)
.context("failed to call `drop-terminal-input`")
.map_err(types::Error::trap)?;
IsATTY::Yes
} else {
IsATTY::No
},
})?;
descriptors.push(Descriptor::Stdout {
stream: host
.get_stdout()
.context("failed to call `get-stdout`")
.map_err(types::Error::trap)?,
isatty: if let Some(term_out) = host
.get_terminal_stdout()
.context("failed to call `get-terminal-stdout`")
.map_err(types::Error::trap)?
{
terminal_output::HostTerminalOutput::drop(&mut host, term_out)
.context("failed to call `drop-terminal-output`")
.map_err(types::Error::trap)?;
IsATTY::Yes
} else {
IsATTY::No
},
})?;
descriptors.push(Descriptor::Stderr {
stream: host
.get_stderr()
.context("failed to call `get-stderr`")
.map_err(types::Error::trap)?,
isatty: if let Some(term_out) = host
.get_terminal_stderr()
.context("failed to call `get-terminal-stderr`")
.map_err(types::Error::trap)?
{
terminal_output::HostTerminalOutput::drop(&mut host, term_out)
.context("failed to call `drop-terminal-output`")
.map_err(types::Error::trap)?;
IsATTY::Yes
} else {
IsATTY::No
},
})?;
for dir in host
.get_directories()
.context("failed to call `get-directories`")
.map_err(types::Error::trap)?
{
descriptors.push(Descriptor::Directory {
fd: dir.0,
preopen_path: Some(dir.1),
})?;
}
Ok(descriptors)
}
fn unused(&self) -> Result<u32> {
match self.last_key_value() {
Some((fd, _)) => {
if let Some(fd) = fd.checked_add(1) {
return Ok(fd);
}
if self.len() == u32::MAX as usize {
return Err(types::Errno::Loop.into());
}
Ok((0..u32::MAX)
.rev()
.find(|fd| !self.contains_key(fd))
.expect("failed to find an unused file descriptor"))
}
None => Ok(0),
}
}
fn remove(&mut self, fd: types::Fd) -> Option<Descriptor> {
let fd = fd.into();
let desc = self.used.remove(&fd)?;
self.free.push(fd);
Some(desc)
}
fn push(&mut self, desc: Descriptor) -> Result<u32> {
let fd = if let Some(fd) = self.free.pop() {
fd
} else {
self.unused()?
};
assert!(self.insert(fd, desc).is_none());
Ok(fd)
}
}
impl WasiPreview1Adapter {
fn new() -> Self {
Self::default()
}
}
struct Transaction<'a> {
view: &'a mut WasiP1Ctx,
descriptors: Descriptors,
}
impl Drop for Transaction<'_> {
fn drop(&mut self) {
let descriptors = mem::take(&mut self.descriptors);
self.view.adapter.descriptors = Some(descriptors);
}
}
impl Transaction<'_> {
fn get_descriptor(&self, fd: types::Fd) -> Result<&Descriptor> {
let fd = fd.into();
let desc = self.descriptors.get(&fd).ok_or(types::Errno::Badf)?;
Ok(desc)
}
fn get_file(&self, fd: types::Fd) -> Result<&File> {
let fd = fd.into();
match self.descriptors.get(&fd) {
Some(Descriptor::File(file)) => Ok(file),
_ => Err(types::Errno::Badf.into()),
}
}
fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> {
let fd = fd.into();
match self.descriptors.get_mut(&fd) {
Some(Descriptor::File(file)) => Ok(file),
_ => Err(types::Errno::Badf.into()),
}
}
fn get_seekable(&self, fd: types::Fd) -> Result<&File> {
let fd = fd.into();
match self.descriptors.get(&fd) {
Some(Descriptor::File(file)) => Ok(file),
Some(
Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. },
) => {
Err(types::Errno::Spipe.into())
}
_ => Err(types::Errno::Badf.into()),
}
}
fn get_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
match self.get_descriptor(fd)? {
Descriptor::File(File { fd, .. }) => Ok(fd.borrowed()),
Descriptor::Directory { fd, .. } => Ok(fd.borrowed()),
Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => {
Err(types::Errno::Badf.into())
}
}
}
fn get_file_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
self.get_file(fd).map(|File { fd, .. }| fd.borrowed())
}
fn get_dir_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
let fd = fd.into();
match self.descriptors.get(&fd) {
Some(Descriptor::Directory { fd, .. }) => Ok(fd.borrowed()),
_ => Err(types::Errno::Badf.into()),
}
}
}
impl WasiP1Ctx {
fn transact(&mut self) -> Result<Transaction<'_>, types::Error> {
let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() {
descriptors
} else {
Descriptors::new(self.as_wasi_impl())?
}
.into();
Ok(Transaction {
view: self,
descriptors,
})
}
fn get_fd(&mut self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>, types::Error> {
let st = self.transact()?;
let fd = st.get_fd(fd)?;
Ok(fd)
}
fn get_file_fd(
&mut self,
fd: types::Fd,
) -> Result<Resource<filesystem::Descriptor>, types::Error> {
let st = self.transact()?;
let fd = st.get_file_fd(fd)?;
Ok(fd)
}
fn get_dir_fd(
&mut self,
fd: types::Fd,
) -> Result<Resource<filesystem::Descriptor>, types::Error> {
let st = self.transact()?;
let fd = st.get_dir_fd(fd)?;
Ok(fd)
}
async fn fd_write_impl(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
ciovs: types::CiovecArray,
write: FdWrite,
) -> Result<types::Size, types::Error> {
let t = self.transact()?;
let desc = t.get_descriptor(fd)?;
match desc {
Descriptor::File(File {
fd,
append,
position,
blocking_mode: _,
}) => {
let fd = fd.borrowed();
let position = position.clone();
let pos = position.load(Ordering::Relaxed);
let append = *append;
drop(t);
let f = self.table().get(&fd)?.file()?;
let buf = first_non_empty_ciovec(memory, ciovs)?;
let do_write = move |f: &cap_std::fs::File, buf: &[u8]| match (append, write) {
(true, _) => f.append(&buf),
(false, FdWrite::At(pos)) => f.write_at(&buf, pos),
(false, FdWrite::AtCur) => f.write_at(&buf, pos),
};
let nwritten = match f.as_blocking_file() {
Some(f) => do_write(f, &memory.as_cow(buf)?),
None => {
let buf = memory.to_vec(buf)?;
f.run_blocking(move |f| do_write(f, &buf)).await
}
};
let nwritten = nwritten.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
if let FdWrite::AtCur = write {
if append {
let len = self.as_wasi_impl().stat(fd).await?;
position.store(len.size, Ordering::Relaxed);
} else {
let pos = pos
.checked_add(nwritten as u64)
.ok_or(types::Errno::Overflow)?;
position.store(pos, Ordering::Relaxed);
}
}
Ok(nwritten.try_into()?)
}
Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
match write {
FdWrite::At(_) => return Err(types::Errno::Spipe.into()),
FdWrite::AtCur => {}
}
let stream = stream.borrowed();
drop(t);
let buf = first_non_empty_ciovec(memory, ciovs)?;
let n = BlockingMode::Blocking
.write(memory, &mut self.as_wasi_impl(), stream, buf)
.await?
.try_into()?;
Ok(n)
}
_ => Err(types::Errno::Badf.into()),
}
}
}
#[derive(Copy, Clone)]
enum FdWrite {
At(u64),
AtCur,
}
pub fn add_to_linker_async<T: Send>(
linker: &mut wasmtime::Linker<T>,
f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
) -> anyhow::Result<()> {
crate::preview1::wasi_snapshot_preview1::add_to_linker(linker, f)
}
pub fn add_to_linker_sync<T: Send>(
linker: &mut wasmtime::Linker<T>,
f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
) -> anyhow::Result<()> {
crate::preview1::sync::add_wasi_snapshot_preview1_to_linker(linker, f)
}
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"],
async: {
wasi_snapshot_preview1::{
fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
path_rename, path_symlink, path_unlink_file
}
},
errors: { errno => trappable Error },
});
pub(crate) mod sync {
use anyhow::Result;
use std::future::Future;
wiggle::wasmtime_integration!({
witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"],
target: super,
block_on[in_tokio]: {
wasi_snapshot_preview1::{
fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
path_rename, path_symlink, path_unlink_file
}
},
errors: { errno => trappable Error },
});
fn in_tokio<F: Future>(future: F) -> Result<F::Output> {
Ok(crate::runtime::in_tokio(future))
}
}
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
Self::Success
}
}
impl From<StreamError> for types::Error {
fn from(err: StreamError) -> Self {
match err {
StreamError::Closed => types::Errno::Io.into(),
StreamError::LastOperationFailed(e) => match e.downcast::<std::io::Error>() {
Ok(err) => filesystem::ErrorCode::from(err).into(),
Err(e) => {
tracing::debug!("dropping error {e:?}");
types::Errno::Io.into()
}
},
StreamError::Trap(e) => types::Error::trap(e),
}
}
}
impl From<FsError> for types::Error {
fn from(err: FsError) -> Self {
match err.downcast() {
Ok(code) => code.into(),
Err(e) => types::Error::trap(e),
}
}
}
fn systimespec(set: bool, ts: types::Timestamp, now: bool) -> Result<filesystem::NewTimestamp> {
if set && now {
Err(types::Errno::Inval.into())
} else if set {
Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime {
seconds: ts / 1_000_000_000,
nanoseconds: (ts % 1_000_000_000) as _,
}))
} else if now {
Ok(filesystem::NewTimestamp::Now)
} else {
Ok(filesystem::NewTimestamp::NoChange)
}
}
impl TryFrom<wall_clock::Datetime> for types::Timestamp {
type Error = types::Errno;
fn try_from(
wall_clock::Datetime {
seconds,
nanoseconds,
}: wall_clock::Datetime,
) -> Result<Self, Self::Error> {
types::Timestamp::from(seconds)
.checked_mul(1_000_000_000)
.and_then(|ns| ns.checked_add(nanoseconds.into()))
.ok_or(types::Errno::Overflow)
}
}
impl From<types::Lookupflags> for filesystem::PathFlags {
fn from(flags: types::Lookupflags) -> Self {
if flags.contains(types::Lookupflags::SYMLINK_FOLLOW) {
filesystem::PathFlags::SYMLINK_FOLLOW
} else {
filesystem::PathFlags::empty()
}
}
}
impl From<types::Oflags> for filesystem::OpenFlags {
fn from(flags: types::Oflags) -> Self {
let mut out = filesystem::OpenFlags::empty();
if flags.contains(types::Oflags::CREAT) {
out |= filesystem::OpenFlags::CREATE;
}
if flags.contains(types::Oflags::DIRECTORY) {
out |= filesystem::OpenFlags::DIRECTORY;
}
if flags.contains(types::Oflags::EXCL) {
out |= filesystem::OpenFlags::EXCLUSIVE;
}
if flags.contains(types::Oflags::TRUNC) {
out |= filesystem::OpenFlags::TRUNCATE;
}
out
}
}
impl From<types::Advice> for filesystem::Advice {
fn from(advice: types::Advice) -> Self {
match advice {
types::Advice::Normal => filesystem::Advice::Normal,
types::Advice::Sequential => filesystem::Advice::Sequential,
types::Advice::Random => filesystem::Advice::Random,
types::Advice::Willneed => filesystem::Advice::WillNeed,
types::Advice::Dontneed => filesystem::Advice::DontNeed,
types::Advice::Noreuse => filesystem::Advice::NoReuse,
}
}
}
impl TryFrom<filesystem::DescriptorType> for types::Filetype {
type Error = anyhow::Error;
fn try_from(ty: filesystem::DescriptorType) -> Result<Self, Self::Error> {
match ty {
filesystem::DescriptorType::RegularFile => Ok(types::Filetype::RegularFile),
filesystem::DescriptorType::Directory => Ok(types::Filetype::Directory),
filesystem::DescriptorType::BlockDevice => Ok(types::Filetype::BlockDevice),
filesystem::DescriptorType::CharacterDevice => Ok(types::Filetype::CharacterDevice),
filesystem::DescriptorType::Fifo => Ok(types::Filetype::Unknown),
filesystem::DescriptorType::Socket => {
bail!("sockets are not currently supported")
}
filesystem::DescriptorType::SymbolicLink => Ok(types::Filetype::SymbolicLink),
filesystem::DescriptorType::Unknown => Ok(types::Filetype::Unknown),
}
}
}
impl From<IsATTY> for types::Filetype {
fn from(isatty: IsATTY) -> Self {
match isatty {
IsATTY::Yes => types::Filetype::CharacterDevice,
IsATTY::No => types::Filetype::Unknown,
}
}
}
impl From<filesystem::ErrorCode> for types::Errno {
fn from(code: filesystem::ErrorCode) -> Self {
match code {
filesystem::ErrorCode::Access => types::Errno::Acces,
filesystem::ErrorCode::WouldBlock => types::Errno::Again,
filesystem::ErrorCode::Already => types::Errno::Already,
filesystem::ErrorCode::BadDescriptor => types::Errno::Badf,
filesystem::ErrorCode::Busy => types::Errno::Busy,
filesystem::ErrorCode::Deadlock => types::Errno::Deadlk,
filesystem::ErrorCode::Quota => types::Errno::Dquot,
filesystem::ErrorCode::Exist => types::Errno::Exist,
filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig,
filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq,
filesystem::ErrorCode::InProgress => types::Errno::Inprogress,
filesystem::ErrorCode::Interrupted => types::Errno::Intr,
filesystem::ErrorCode::Invalid => types::Errno::Inval,
filesystem::ErrorCode::Io => types::Errno::Io,
filesystem::ErrorCode::IsDirectory => types::Errno::Isdir,
filesystem::ErrorCode::Loop => types::Errno::Loop,
filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink,
filesystem::ErrorCode::MessageSize => types::Errno::Msgsize,
filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong,
filesystem::ErrorCode::NoDevice => types::Errno::Nodev,
filesystem::ErrorCode::NoEntry => types::Errno::Noent,
filesystem::ErrorCode::NoLock => types::Errno::Nolck,
filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem,
filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc,
filesystem::ErrorCode::Unsupported => types::Errno::Notsup,
filesystem::ErrorCode::NotDirectory => types::Errno::Notdir,
filesystem::ErrorCode::NotEmpty => types::Errno::Notempty,
filesystem::ErrorCode::NotRecoverable => types::Errno::Notrecoverable,
filesystem::ErrorCode::NoTty => types::Errno::Notty,
filesystem::ErrorCode::NoSuchDevice => types::Errno::Nxio,
filesystem::ErrorCode::Overflow => types::Errno::Overflow,
filesystem::ErrorCode::NotPermitted => types::Errno::Perm,
filesystem::ErrorCode::Pipe => types::Errno::Pipe,
filesystem::ErrorCode::ReadOnly => types::Errno::Rofs,
filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe,
filesystem::ErrorCode::TextFileBusy => types::Errno::Txtbsy,
filesystem::ErrorCode::CrossDevice => types::Errno::Xdev,
}
}
}
impl From<std::num::TryFromIntError> for types::Error {
fn from(_: std::num::TryFromIntError) -> Self {
types::Errno::Overflow.into()
}
}
impl From<GuestError> for types::Error {
fn from(err: GuestError) -> Self {
use wiggle::GuestError::*;
match err {
InvalidFlagValue { .. } => types::Errno::Inval.into(),
InvalidEnumValue { .. } => types::Errno::Inval.into(),
PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => {
types::Error::trap(err.into())
}
PtrBorrowed { .. } => types::Errno::Fault.into(),
InvalidUtf8 { .. } => types::Errno::Ilseq.into(),
TryFromIntError { .. } => types::Errno::Overflow.into(),
SliceLengthsDiffer { .. } => types::Errno::Fault.into(),
BorrowCheckerOutOfHandles { .. } => types::Errno::Fault.into(),
InFunc { err, .. } => types::Error::from(*err),
}
}
}
impl From<filesystem::ErrorCode> for types::Error {
fn from(code: filesystem::ErrorCode) -> Self {
types::Errno::from(code).into()
}
}
impl From<wasmtime::component::ResourceTableError> for types::Error {
fn from(err: wasmtime::component::ResourceTableError) -> Self {
types::Error::trap(err.into())
}
}
type Result<T, E = types::Error> = std::result::Result<T, E>;
fn write_bytes(
memory: &mut GuestMemory<'_>,
ptr: GuestPtr<u8>,
buf: &[u8],
) -> Result<GuestPtr<u8>, types::Error> {
let len = u32::try_from(buf.len())?;
memory.copy_from_slice(buf, ptr.as_array(len))?;
let next = ptr.add(len)?;
Ok(next)
}
fn write_byte(memory: &mut GuestMemory<'_>, ptr: GuestPtr<u8>, byte: u8) -> Result<GuestPtr<u8>> {
memory.write(ptr, byte)?;
let next = ptr.add(1)?;
Ok(next)
}
fn read_string<'a>(memory: &'a GuestMemory<'_>, ptr: GuestPtr<str>) -> Result<String> {
Ok(memory.as_cow_str(ptr)?.into_owned())
}
fn first_non_empty_ciovec(
memory: &GuestMemory<'_>,
ciovs: types::CiovecArray,
) -> Result<GuestPtr<[u8]>> {
for iov in ciovs.iter() {
let iov = memory.read(iov?)?;
if iov.buf_len == 0 {
continue;
}
return Ok(iov.buf.as_array(iov.buf_len));
}
Ok(GuestPtr::new((0, 0)))
}
fn first_non_empty_iovec(
memory: &GuestMemory<'_>,
iovs: types::IovecArray,
) -> Result<GuestPtr<[u8]>> {
for iov in iovs.iter() {
let iov = memory.read(iov?)?;
if iov.buf_len == 0 {
continue;
}
return Ok(iov.buf.as_array(iov.buf_len));
}
Ok(GuestPtr::new((0, 0)))
}
#[async_trait::async_trait]
impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
#[instrument(skip(self, memory))]
fn args_get(
&mut self,
memory: &mut GuestMemory<'_>,
argv: GuestPtr<GuestPtr<u8>>,
argv_buf: GuestPtr<u8>,
) -> Result<(), types::Error> {
self.as_wasi_impl()
.get_arguments()
.context("failed to call `get-arguments`")
.map_err(types::Error::trap)?
.into_iter()
.try_fold((argv, argv_buf), |(argv, argv_buf), arg| -> Result<_> {
memory.write(argv, argv_buf)?;
let argv = argv.add(1)?;
let argv_buf = write_bytes(memory, argv_buf, arg.as_bytes())?;
let argv_buf = write_byte(memory, argv_buf, 0)?;
Ok((argv, argv_buf))
})?;
Ok(())
}
#[instrument(skip(self, _memory))]
fn args_sizes_get(
&mut self,
_memory: &mut GuestMemory<'_>,
) -> Result<(types::Size, types::Size), types::Error> {
let args = self
.as_wasi_impl()
.get_arguments()
.context("failed to call `get-arguments`")
.map_err(types::Error::trap)?;
let num = args.len().try_into().map_err(|_| types::Errno::Overflow)?;
let len = args
.iter()
.map(|buf| buf.len() + 1) .sum::<usize>()
.try_into()
.map_err(|_| types::Errno::Overflow)?;
Ok((num, len))
}
#[instrument(skip(self, memory))]
fn environ_get(
&mut self,
memory: &mut GuestMemory<'_>,
environ: GuestPtr<GuestPtr<u8>>,
environ_buf: GuestPtr<u8>,
) -> Result<(), types::Error> {
self.as_wasi_impl()
.get_environment()
.context("failed to call `get-environment`")
.map_err(types::Error::trap)?
.into_iter()
.try_fold(
(environ, environ_buf),
|(environ, environ_buf), (k, v)| -> Result<_, types::Error> {
memory.write(environ, environ_buf)?;
let environ = environ.add(1)?;
let environ_buf = write_bytes(memory, environ_buf, k.as_bytes())?;
let environ_buf = write_byte(memory, environ_buf, b'=')?;
let environ_buf = write_bytes(memory, environ_buf, v.as_bytes())?;
let environ_buf = write_byte(memory, environ_buf, 0)?;
Ok((environ, environ_buf))
},
)?;
Ok(())
}
#[instrument(skip(self, _memory))]
fn environ_sizes_get(
&mut self,
_memory: &mut GuestMemory<'_>,
) -> Result<(types::Size, types::Size), types::Error> {
let environ = self
.as_wasi_impl()
.get_environment()
.context("failed to call `get-environment`")
.map_err(types::Error::trap)?;
let num = environ.len().try_into()?;
let len = environ
.iter()
.map(|(k, v)| k.len() + 1 + v.len() + 1) .sum::<usize>()
.try_into()?;
Ok((num, len))
}
#[instrument(skip(self, _memory))]
fn clock_res_get(
&mut self,
_memory: &mut GuestMemory<'_>,
id: types::Clockid,
) -> Result<types::Timestamp, types::Error> {
let res = match id {
types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.as_wasi_impl())
.context("failed to call `wall_clock::resolution`")
.map_err(types::Error::trap)?
.try_into()?,
types::Clockid::Monotonic => {
monotonic_clock::Host::resolution(&mut self.as_wasi_impl())
.context("failed to call `monotonic_clock::resolution`")
.map_err(types::Error::trap)?
}
types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
return Err(types::Errno::Badf.into())
}
};
Ok(res)
}
#[instrument(skip(self, _memory))]
fn clock_time_get(
&mut self,
_memory: &mut GuestMemory<'_>,
id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp, types::Error> {
let now = match id {
types::Clockid::Realtime => wall_clock::Host::now(&mut self.as_wasi_impl())
.context("failed to call `wall_clock::now`")
.map_err(types::Error::trap)?
.try_into()?,
types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.as_wasi_impl())
.context("failed to call `monotonic_clock::now`")
.map_err(types::Error::trap)?,
types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
return Err(types::Errno::Badf.into())
}
};
Ok(now)
}
#[instrument(skip(self, _memory))]
async fn fd_advise(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<(), types::Error> {
let fd = self.get_file_fd(fd)?;
self.as_wasi_impl()
.advise(fd, offset, len, advice.into())
.await?;
Ok(())
}
#[instrument(skip(self, _memory))]
fn fd_allocate(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
) -> Result<(), types::Error> {
self.get_file_fd(fd)?;
Err(types::Errno::Notsup.into())
}
#[instrument(skip(self, _memory))]
async fn fd_close(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<(), types::Error> {
let desc = self
.transact()?
.descriptors
.remove(fd)
.ok_or(types::Errno::Badf)?;
match desc {
Descriptor::Stdin { stream, .. } => {
streams::HostInputStream::drop(&mut self.as_wasi_impl(), stream)
.await
.context("failed to call `drop` on `input-stream`")
}
Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
streams::HostOutputStream::drop(&mut self.as_wasi_impl(), stream)
.await
.context("failed to call `drop` on `output-stream`")
}
Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => {
filesystem::HostDescriptor::drop(&mut self.as_wasi_impl(), fd)
.context("failed to call `drop`")
}
}
.map_err(types::Error::trap)
}
#[instrument(skip(self, _memory))]
async fn fd_datasync(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<(), types::Error> {
let fd = self.get_file_fd(fd)?;
self.as_wasi_impl().sync_data(fd).await?;
Ok(())
}
#[instrument(skip(self, _memory))]
async fn fd_fdstat_get(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Fdstat, types::Error> {
let (fd, blocking, append) = match self.transact()?.get_descriptor(fd)? {
Descriptor::Stdin { isatty, .. } => {
let fs_rights_base = types::Rights::FD_READ;
return Ok(types::Fdstat {
fs_filetype: (*isatty).into(),
fs_flags: types::Fdflags::empty(),
fs_rights_base,
fs_rights_inheriting: fs_rights_base,
});
}
Descriptor::Stdout { isatty, .. } | Descriptor::Stderr { isatty, .. } => {
let fs_rights_base = types::Rights::FD_WRITE;
return Ok(types::Fdstat {
fs_filetype: (*isatty).into(),
fs_flags: types::Fdflags::empty(),
fs_rights_base,
fs_rights_inheriting: fs_rights_base,
});
}
Descriptor::Directory {
preopen_path: Some(_),
..
} => {
let fs_rights_base = 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;
let fs_rights_inheriting = fs_rights_base
| 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;
return Ok(types::Fdstat {
fs_filetype: types::Filetype::Directory,
fs_flags: types::Fdflags::empty(),
fs_rights_base,
fs_rights_inheriting,
});
}
Descriptor::Directory { fd, .. } => (fd.borrowed(), BlockingMode::Blocking, false),
Descriptor::File(File {
fd,
blocking_mode,
append,
..
}) => (fd.borrowed(), *blocking_mode, *append),
};
let flags = self.as_wasi_impl().get_flags(fd.borrowed()).await?;
let fs_filetype = self
.as_wasi_impl()
.get_type(fd.borrowed())
.await?
.try_into()
.map_err(types::Error::trap)?;
let mut fs_flags = types::Fdflags::empty();
let mut fs_rights_base = types::Rights::all();
if let types::Filetype::Directory = fs_filetype {
fs_rights_base &= !types::Rights::FD_SEEK;
fs_rights_base &= !types::Rights::FD_FILESTAT_SET_SIZE;
}
if !flags.contains(filesystem::DescriptorFlags::READ) {
fs_rights_base &= !types::Rights::FD_READ;
fs_rights_base &= !types::Rights::FD_READDIR;
}
if !flags.contains(filesystem::DescriptorFlags::WRITE) {
fs_rights_base &= !types::Rights::FD_WRITE;
}
if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
fs_flags |= types::Fdflags::DSYNC;
}
if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
fs_flags |= types::Fdflags::RSYNC;
}
if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
fs_flags |= types::Fdflags::SYNC;
}
if append {
fs_flags |= types::Fdflags::APPEND;
}
if matches!(blocking, BlockingMode::NonBlocking) {
fs_flags |= types::Fdflags::NONBLOCK;
}
Ok(types::Fdstat {
fs_filetype,
fs_flags,
fs_rights_base,
fs_rights_inheriting: fs_rights_base,
})
}
#[instrument(skip(self, _memory))]
fn fd_fdstat_set_flags(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
flags: types::Fdflags,
) -> Result<(), types::Error> {
let mut st = self.transact()?;
let File {
append,
blocking_mode,
..
} = st.get_file_mut(fd)?;
if flags.contains(types::Fdflags::DSYNC)
|| flags.contains(types::Fdflags::SYNC)
|| flags.contains(types::Fdflags::RSYNC)
{
return Err(types::Errno::Inval.into());
}
*append = flags.contains(types::Fdflags::APPEND);
*blocking_mode = BlockingMode::from_fdflags(&flags);
Ok(())
}
#[instrument(skip(self, _memory))]
fn fd_fdstat_set_rights(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
_fs_rights_base: types::Rights,
_fs_rights_inheriting: types::Rights,
) -> Result<(), types::Error> {
self.get_fd(fd)?;
Ok(())
}
#[instrument(skip(self, _memory))]
async fn fd_filestat_get(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Filestat, types::Error> {
let t = self.transact()?;
let desc = t.get_descriptor(fd)?;
match desc {
Descriptor::Stdin { isatty, .. }
| Descriptor::Stdout { isatty, .. }
| Descriptor::Stderr { isatty, .. } => Ok(types::Filestat {
dev: 0,
ino: 0,
filetype: (*isatty).into(),
nlink: 0,
size: 0,
atim: 0,
mtim: 0,
ctim: 0,
}),
Descriptor::Directory { fd, .. } | Descriptor::File(File { fd, .. }) => {
let fd = fd.borrowed();
drop(t);
let filesystem::DescriptorStat {
type_,
link_count: nlink,
size,
data_access_timestamp,
data_modification_timestamp,
status_change_timestamp,
} = self.as_wasi_impl().stat(fd.borrowed()).await?;
let metadata_hash = self.as_wasi_impl().metadata_hash(fd).await?;
let filetype = type_.try_into().map_err(types::Error::trap)?;
let zero = wall_clock::Datetime {
seconds: 0,
nanoseconds: 0,
};
let atim = data_access_timestamp.unwrap_or(zero).try_into()?;
let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?;
let ctim = status_change_timestamp.unwrap_or(zero).try_into()?;
Ok(types::Filestat {
dev: 1,
ino: metadata_hash.lower,
filetype,
nlink,
size,
atim,
mtim,
ctim,
})
}
}
}
#[instrument(skip(self, _memory))]
async fn fd_filestat_set_size(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
size: types::Filesize,
) -> Result<(), types::Error> {
let fd = self.get_file_fd(fd)?;
self.as_wasi_impl().set_size(fd, size).await?;
Ok(())
}
#[instrument(skip(self, _memory))]
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<(), types::Error> {
let atim = systimespec(
fst_flags.contains(types::Fstflags::ATIM),
atim,
fst_flags.contains(types::Fstflags::ATIM_NOW),
)?;
let mtim = systimespec(
fst_flags.contains(types::Fstflags::MTIM),
mtim,
fst_flags.contains(types::Fstflags::MTIM_NOW),
)?;
let fd = self.get_fd(fd)?;
self.as_wasi_impl().set_times(fd, atim, mtim).await?;
Ok(())
}
#[instrument(skip(self, memory))]
async fn fd_read(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
iovs: types::IovecArray,
) -> Result<types::Size, types::Error> {
let t = self.transact()?;
let desc = t.get_descriptor(fd)?;
match desc {
Descriptor::File(File {
fd,
position,
blocking_mode: _,
..
}) => {
let fd = fd.borrowed();
let position = position.clone();
drop(t);
let pos = position.load(Ordering::Relaxed);
let file = self.table().get(&fd)?.file()?;
let iov = first_non_empty_iovec(memory, iovs)?;
let bytes_read = match (file.as_blocking_file(), memory.as_slice_mut(iov)?) {
(Some(file), Some(mut buf)) => file
.read_at(&mut buf, pos)
.map_err(|e| StreamError::LastOperationFailed(e.into()))?,
(_, buf) => {
drop(buf);
let mut buf = vec![0; iov.len() as usize];
let buf = file
.run_blocking(move |file| -> Result<_, types::Error> {
let bytes_read = file
.read_at(&mut buf, pos)
.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
buf.truncate(bytes_read);
Ok(buf)
})
.await?;
let iov = iov.get_range(0..u32::try_from(buf.len())?).unwrap();
memory.copy_from_slice(&buf, iov)?;
buf.len()
}
};
let pos = pos
.checked_add(bytes_read.try_into()?)
.ok_or(types::Errno::Overflow)?;
position.store(pos, Ordering::Relaxed);
Ok(bytes_read.try_into()?)
}
Descriptor::Stdin { stream, .. } => {
let stream = stream.borrowed();
drop(t);
let buf = first_non_empty_iovec(memory, iovs)?;
let read = BlockingMode::Blocking
.read(&mut self.as_wasi_impl(), stream, buf.len().try_into()?)
.await?;
if read.len() > buf.len().try_into()? {
return Err(types::Errno::Range.into());
}
let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
memory.copy_from_slice(&read, buf)?;
let n = read.len().try_into()?;
Ok(n)
}
_ => return Err(types::Errno::Badf.into()),
}
}
#[instrument(skip(self, memory))]
async fn fd_pread(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
iovs: types::IovecArray,
offset: types::Filesize,
) -> Result<types::Size, types::Error> {
let t = self.transact()?;
let desc = t.get_descriptor(fd)?;
let (buf, read) = match desc {
Descriptor::File(File {
fd, blocking_mode, ..
}) => {
let fd = fd.borrowed();
let blocking_mode = *blocking_mode;
drop(t);
let buf = first_non_empty_iovec(memory, iovs)?;
let stream = self.as_wasi_impl().read_via_stream(fd, offset)?;
let read = blocking_mode
.read(
&mut self.as_wasi_impl(),
stream.borrowed(),
buf.len().try_into()?,
)
.await;
streams::HostInputStream::drop(&mut self.as_wasi_impl(), stream)
.await
.map_err(|e| types::Error::trap(e))?;
(buf, read?)
}
Descriptor::Stdin { .. } => {
return Err(types::Errno::Spipe.into());
}
_ => return Err(types::Errno::Badf.into()),
};
if read.len() > buf.len().try_into()? {
return Err(types::Errno::Range.into());
}
let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
memory.copy_from_slice(&read, buf)?;
let n = read.len().try_into()?;
Ok(n)
}
#[instrument(skip(self, memory))]
async fn fd_write(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
ciovs: types::CiovecArray,
) -> Result<types::Size, types::Error> {
self.fd_write_impl(memory, fd, ciovs, FdWrite::AtCur).await
}
#[instrument(skip(self, memory))]
async fn fd_pwrite(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
ciovs: types::CiovecArray,
offset: types::Filesize,
) -> Result<types::Size, types::Error> {
self.fd_write_impl(memory, fd, ciovs, FdWrite::At(offset))
.await
}
#[instrument(skip(self, _memory))]
fn fd_prestat_get(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Prestat, types::Error> {
if let Descriptor::Directory {
preopen_path: Some(p),
..
} = self.transact()?.get_descriptor(fd)?
{
let pr_name_len = p.len().try_into()?;
return Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len }));
}
Err(types::Errno::Badf.into()) }
#[instrument(skip(self, memory))]
fn fd_prestat_dir_name(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
path: GuestPtr<u8>,
path_max_len: types::Size,
) -> Result<(), types::Error> {
let path_max_len = path_max_len.try_into()?;
if let Descriptor::Directory {
preopen_path: Some(p),
..
} = self.transact()?.get_descriptor(fd)?
{
if p.len() > path_max_len {
return Err(types::Errno::Nametoolong.into());
}
write_bytes(memory, path, p.as_bytes())?;
return Ok(());
}
Err(types::Errno::Notdir.into()) }
#[instrument(skip(self, _memory))]
fn fd_renumber(
&mut self,
_memory: &mut GuestMemory<'_>,
from: types::Fd,
to: types::Fd,
) -> Result<(), types::Error> {
let mut st = self.transact()?;
let desc = st.descriptors.remove(from).ok_or(types::Errno::Badf)?;
st.descriptors.insert(to.into(), desc);
Ok(())
}
#[instrument(skip(self, _memory))]
async fn fd_seek(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize, types::Error> {
let t = self.transact()?;
let File { fd, position, .. } = t.get_seekable(fd)?;
let fd = fd.borrowed();
let position = position.clone();
drop(t);
let pos = match whence {
types::Whence::Set if offset >= 0 => {
offset.try_into().map_err(|_| types::Errno::Inval)?
}
types::Whence::Cur => position
.load(Ordering::Relaxed)
.checked_add_signed(offset)
.ok_or(types::Errno::Inval)?,
types::Whence::End => {
let filesystem::DescriptorStat { size, .. } = self.as_wasi_impl().stat(fd).await?;
size.checked_add_signed(offset).ok_or(types::Errno::Inval)?
}
_ => return Err(types::Errno::Inval.into()),
};
position.store(pos, Ordering::Relaxed);
Ok(pos)
}
#[instrument(skip(self, _memory))]
async fn fd_sync(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<(), types::Error> {
let fd = self.get_file_fd(fd)?;
self.as_wasi_impl().sync(fd).await?;
Ok(())
}
#[instrument(skip(self, _memory))]
fn fd_tell(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
) -> Result<types::Filesize, types::Error> {
let pos = self
.transact()?
.get_seekable(fd)
.map(|File { position, .. }| position.load(Ordering::Relaxed))?;
Ok(pos)
}
#[instrument(skip(self, memory))]
async fn fd_readdir(
&mut self,
memory: &mut GuestMemory<'_>,
fd: types::Fd,
buf: GuestPtr<u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size, types::Error> {
let fd = self.get_dir_fd(fd)?;
let stream = self.as_wasi_impl().read_directory(fd.borrowed()).await?;
let dir_metadata_hash = self.as_wasi_impl().metadata_hash(fd.borrowed()).await?;
let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?;
let head = [
(
types::Dirent {
d_next: 1u64.to_le(),
d_ino: dir_metadata_hash.lower.to_le(),
d_type: types::Filetype::Directory,
d_namlen: 1u32.to_le(),
},
".".into(),
),
(
types::Dirent {
d_next: 2u64.to_le(),
d_ino: dir_metadata_hash.lower.to_le(), d_type: types::Filetype::Directory,
d_namlen: 2u32.to_le(),
},
"..".into(),
),
];
let mut dir = Vec::new();
for (entry, d_next) in self
.table()
.delete(stream)?
.into_iter()
.zip(3u64..)
{
let filesystem::DirectoryEntry { type_, name } = entry?;
let metadata_hash = self
.as_wasi_impl()
.metadata_hash_at(fd.borrowed(), filesystem::PathFlags::empty(), name.clone())
.await?;
let d_type = type_.try_into().map_err(types::Error::trap)?;
let d_namlen: u32 = name.len().try_into().map_err(|_| types::Errno::Overflow)?;
dir.push((
types::Dirent {
d_next: d_next.to_le(),
d_ino: metadata_hash.lower.to_le(),
d_type, d_namlen: d_namlen.to_le(),
},
name,
))
}
const DIRENT_SIZE: u32 = size_of::<types::Dirent>() as _;
assert_eq!(
types::Dirent::guest_size(),
DIRENT_SIZE,
"Dirent guest repr and host repr should match"
);
let mut buf = buf;
let mut cap = buf_len;
for (ref entry, path) in head.into_iter().chain(dir.into_iter()).skip(cookie) {
let mut path = path.into_bytes();
assert_eq!(
1,
size_of_val(&entry.d_type),
"Dirent member d_type should be endian-invariant"
);
let entry_len = cap.min(DIRENT_SIZE);
let entry = entry as *const _ as _;
let entry = unsafe { slice::from_raw_parts(entry, entry_len as _) };
cap = cap.checked_sub(entry_len).unwrap();
buf = write_bytes(memory, buf, entry)?;
if cap == 0 {
return Ok(buf_len);
}
if let Ok(cap) = cap.try_into() {
path.truncate(cap);
}
cap = cap.checked_sub(path.len() as _).unwrap();
buf = write_bytes(memory, buf, &path)?;
if cap == 0 {
return Ok(buf_len);
}
}
Ok(buf_len.checked_sub(cap).unwrap())
}
#[instrument(skip(self, memory))]
async fn path_create_directory(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
) -> Result<(), types::Error> {
let dirfd = self.get_dir_fd(dirfd)?;
let path = read_string(memory, path)?;
self.as_wasi_impl()
.create_directory_at(dirfd.borrowed(), path)
.await?;
Ok(())
}
#[instrument(skip(self, memory))]
async fn path_filestat_get(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
flags: types::Lookupflags,
path: GuestPtr<str>,
) -> Result<types::Filestat, types::Error> {
let dirfd = self.get_dir_fd(dirfd)?;
let path = read_string(memory, path)?;
let filesystem::DescriptorStat {
type_,
link_count: nlink,
size,
data_access_timestamp,
data_modification_timestamp,
status_change_timestamp,
} = self
.as_wasi_impl()
.stat_at(dirfd.borrowed(), flags.into(), path.clone())
.await?;
let metadata_hash = self
.as_wasi_impl()
.metadata_hash_at(dirfd, flags.into(), path)
.await?;
let filetype = type_.try_into().map_err(types::Error::trap)?;
let zero = wall_clock::Datetime {
seconds: 0,
nanoseconds: 0,
};
let atim = data_access_timestamp.unwrap_or(zero).try_into()?;
let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?;
let ctim = status_change_timestamp.unwrap_or(zero).try_into()?;
Ok(types::Filestat {
dev: 1,
ino: metadata_hash.lower,
filetype,
nlink,
size,
atim,
mtim,
ctim,
})
}
#[instrument(skip(self, memory))]
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<(), types::Error> {
let atim = systimespec(
fst_flags.contains(types::Fstflags::ATIM),
atim,
fst_flags.contains(types::Fstflags::ATIM_NOW),
)?;
let mtim = systimespec(
fst_flags.contains(types::Fstflags::MTIM),
mtim,
fst_flags.contains(types::Fstflags::MTIM_NOW),
)?;
let dirfd = self.get_dir_fd(dirfd)?;
let path = read_string(memory, path)?;
self.as_wasi_impl()
.set_times_at(dirfd, flags.into(), path, atim, mtim)
.await?;
Ok(())
}
#[instrument(skip(self, memory))]
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<(), types::Error> {
let src_fd = self.get_dir_fd(src_fd)?;
let target_fd = self.get_dir_fd(target_fd)?;
let src_path = read_string(memory, src_path)?;
let target_path = read_string(memory, target_path)?;
self.as_wasi_impl()
.link_at(src_fd, src_flags.into(), src_path, target_fd, target_path)
.await?;
Ok(())
}
#[instrument(skip(self, memory))]
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, types::Error> {
let path = read_string(memory, path)?;
let mut flags = filesystem::DescriptorFlags::empty();
if fs_rights_base.contains(types::Rights::FD_READ) {
flags |= filesystem::DescriptorFlags::READ;
}
if fs_rights_base.contains(types::Rights::FD_WRITE) {
flags |= filesystem::DescriptorFlags::WRITE;
}
if fdflags.contains(types::Fdflags::SYNC) {
flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;
}
if fdflags.contains(types::Fdflags::DSYNC) {
flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;
}
if fdflags.contains(types::Fdflags::RSYNC) {
flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;
}
let t = self.transact()?;
let dirfd = match t.get_descriptor(dirfd)? {
Descriptor::Directory { fd, .. } => fd.borrowed(),
Descriptor::File(_) => return Err(types::Errno::Notdir.into()),
_ => return Err(types::Errno::Badf.into()),
};
drop(t);
let fd = self
.as_wasi_impl()
.open_at(dirfd, dirflags.into(), path, oflags.into(), flags)
.await?;
let mut t = self.transact()?;
let desc = match t.view.table().get(&fd)? {
crate::filesystem::Descriptor::Dir(_) => Descriptor::Directory {
fd,
preopen_path: None,
},
crate::filesystem::Descriptor::File(_) => Descriptor::File(File {
fd,
position: Default::default(),
append: fdflags.contains(types::Fdflags::APPEND),
blocking_mode: BlockingMode::from_fdflags(&fdflags),
}),
};
let fd = t.descriptors.push(desc)?;
Ok(fd.into())
}
#[instrument(skip(self, memory))]
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, types::Error> {
let dirfd = self.get_dir_fd(dirfd)?;
let path = read_string(memory, path)?;
let mut path = self
.as_wasi_impl()
.readlink_at(dirfd, path)
.await?
.into_bytes();
if let Ok(buf_len) = buf_len.try_into() {
path.truncate(buf_len);
}
let n = path.len().try_into().map_err(|_| types::Errno::Overflow)?;
write_bytes(memory, buf, &path)?;
Ok(n)
}
#[instrument(skip(self, memory))]
async fn path_remove_directory(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
) -> Result<(), types::Error> {
let dirfd = self.get_dir_fd(dirfd)?;
let path = read_string(memory, path)?;
self.as_wasi_impl().remove_directory_at(dirfd, path).await?;
Ok(())
}
#[instrument(skip(self, memory))]
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<(), types::Error> {
let src_fd = self.get_dir_fd(src_fd)?;
let dest_fd = self.get_dir_fd(dest_fd)?;
let src_path = read_string(memory, src_path)?;
let dest_path = read_string(memory, dest_path)?;
self.as_wasi_impl()
.rename_at(src_fd, src_path, dest_fd, dest_path)
.await?;
Ok(())
}
#[instrument(skip(self, memory))]
async fn path_symlink(
&mut self,
memory: &mut GuestMemory<'_>,
src_path: GuestPtr<str>,
dirfd: types::Fd,
dest_path: GuestPtr<str>,
) -> Result<(), types::Error> {
let dirfd = self.get_dir_fd(dirfd)?;
let src_path = read_string(memory, src_path)?;
let dest_path = read_string(memory, dest_path)?;
self.as_wasi_impl()
.symlink_at(dirfd.borrowed(), src_path, dest_path)
.await?;
Ok(())
}
#[instrument(skip(self, memory))]
async fn path_unlink_file(
&mut self,
memory: &mut GuestMemory<'_>,
dirfd: types::Fd,
path: GuestPtr<str>,
) -> Result<(), types::Error> {
let dirfd = self.get_dir_fd(dirfd)?;
let path = memory.as_cow_str(path)?.into_owned();
self.as_wasi_impl()
.unlink_file_at(dirfd.borrowed(), path)
.await?;
Ok(())
}
#[instrument(skip(self, memory))]
async fn poll_oneoff(
&mut self,
memory: &mut GuestMemory<'_>,
subs: GuestPtr<types::Subscription>,
events: GuestPtr<types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size, types::Error> {
if nsubscriptions == 0 {
return Err(types::Errno::Inval.into());
}
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.ctx().allow_blocking_current_thread
{
std::thread::sleep(std::time::Duration::from_nanos(clocksub.timeout));
memory.write(
events,
types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::Clock,
fd_readwrite: types::EventFdReadwrite {
flags: types::Eventrwflags::empty(),
nbytes: 1,
},
},
)?;
return Ok(1);
}
}
}
let subs = subs.as_array(nsubscriptions);
let events = events.as_array(nsubscriptions);
let n = usize::try_from(nsubscriptions).unwrap_or(usize::MAX);
let mut pollables = Vec::with_capacity(n);
for sub in subs.iter() {
let sub = memory.read(sub?)?;
let p = match sub.u {
types::SubscriptionU::Clock(types::SubscriptionClock {
id,
timeout,
flags,
..
}) => {
let absolute = flags.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME);
let (timeout, absolute) = match id {
types::Clockid::Monotonic => (timeout, absolute),
types::Clockid::Realtime if !absolute => (timeout, false),
types::Clockid::Realtime => {
let now = wall_clock::Host::now(&mut self.as_wasi_impl())
.context("failed to call `wall_clock::now`")
.map_err(types::Error::trap)?;
let seconds = timeout / 1_000_000_000;
let nanoseconds = timeout % 1_000_000_000;
let timeout = if now.seconds < seconds
|| now.seconds == seconds
&& u64::from(now.nanoseconds) < nanoseconds
{
now.seconds * 1_000_000_000 + u64::from(now.nanoseconds) - timeout
} else {
0
};
(timeout, false)
}
_ => return Err(types::Errno::Inval.into()),
};
if absolute {
monotonic_clock::Host::subscribe_instant(&mut self.as_wasi_impl(), timeout)
.context("failed to call `monotonic_clock::subscribe_instant`")
.map_err(types::Error::trap)?
} else {
monotonic_clock::Host::subscribe_duration(&mut self.as_wasi_impl(), timeout)
.context("failed to call `monotonic_clock::subscribe_duration`")
.map_err(types::Error::trap)?
}
}
types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
file_descriptor,
}) => {
let stream = {
let t = self.transact()?;
let desc = t.get_descriptor(file_descriptor)?;
match desc {
Descriptor::Stdin { stream, .. } => stream.borrowed(),
Descriptor::File(File { fd, position, .. }) => {
let pos = position.load(Ordering::Relaxed);
let fd = fd.borrowed();
drop(t);
self.as_wasi_impl().read_via_stream(fd, pos)?
}
_ => return Err(types::Errno::Badf.into()),
}
};
streams::HostInputStream::subscribe(&mut self.as_wasi_impl(), stream)
.context("failed to call `subscribe` on `input-stream`")
.map_err(types::Error::trap)?
}
types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite {
file_descriptor,
}) => {
let stream = {
let t = self.transact()?;
let desc = t.get_descriptor(file_descriptor)?;
match desc {
Descriptor::Stdout { stream, .. }
| Descriptor::Stderr { stream, .. } => stream.borrowed(),
Descriptor::File(File {
fd,
position,
append,
..
}) => {
let fd = fd.borrowed();
let position = position.clone();
let append = *append;
drop(t);
if append {
self.as_wasi_impl().append_via_stream(fd)?
} else {
let pos = position.load(Ordering::Relaxed);
self.as_wasi_impl().write_via_stream(fd, pos)?
}
}
_ => return Err(types::Errno::Badf.into()),
}
};
streams::HostOutputStream::subscribe(&mut self.as_wasi_impl(), stream)
.context("failed to call `subscribe` on `output-stream`")
.map_err(types::Error::trap)?
}
};
pollables.push(p);
}
let ready: HashSet<_> = self
.as_wasi_impl()
.poll(pollables)
.await
.context("failed to call `poll-oneoff`")
.map_err(types::Error::trap)?
.into_iter()
.collect();
let mut count: types::Size = 0;
for (sub, event) in (0..)
.zip(subs.iter())
.filter_map(|(idx, sub)| ready.contains(&idx).then_some(sub))
.zip(events.iter())
{
let sub = memory.read(sub?)?;
let event = event?;
let e = match sub.u {
types::SubscriptionU::Clock(..) => types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::Clock,
fd_readwrite: types::EventFdReadwrite {
flags: types::Eventrwflags::empty(),
nbytes: 0,
},
},
types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
file_descriptor,
}) => {
let t = self.transact()?;
let desc = t.get_descriptor(file_descriptor)?;
match desc {
Descriptor::Stdin { .. } => types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::FdRead,
fd_readwrite: types::EventFdReadwrite {
flags: types::Eventrwflags::empty(),
nbytes: 1,
},
},
Descriptor::File(File { fd, position, .. }) => {
let fd = fd.borrowed();
let position = position.clone();
drop(t);
match self.as_wasi_impl().stat(fd).await? {
filesystem::DescriptorStat { size, .. } => {
let pos = position.load(Ordering::Relaxed);
let nbytes = size.saturating_sub(pos);
types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::FdRead,
fd_readwrite: types::EventFdReadwrite {
flags: if nbytes == 0 {
types::Eventrwflags::FD_READWRITE_HANGUP
} else {
types::Eventrwflags::empty()
},
nbytes: 1,
},
}
}
}
}
_ => return Err(types::Errno::Badf.into()),
}
}
types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite {
file_descriptor,
}) => {
let t = self.transact()?;
let desc = t.get_descriptor(file_descriptor)?;
match desc {
Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::FdWrite,
fd_readwrite: types::EventFdReadwrite {
flags: types::Eventrwflags::empty(),
nbytes: 1,
},
},
Descriptor::File(_) => types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
type_: types::Eventtype::FdWrite,
fd_readwrite: types::EventFdReadwrite {
flags: types::Eventrwflags::empty(),
nbytes: 1,
},
},
_ => return Err(types::Errno::Badf.into()),
}
}
};
memory.write(event, e)?;
count = count
.checked_add(1)
.ok_or_else(|| types::Error::from(types::Errno::Overflow))?
}
Ok(count)
}
#[instrument(skip(self, _memory))]
fn proc_exit(
&mut self,
_memory: &mut GuestMemory<'_>,
status: types::Exitcode,
) -> anyhow::Error {
if status >= 126 {
return anyhow::Error::msg("exit with invalid exit status outside of [0..126)");
}
crate::I32Exit(status as i32).into()
}
#[instrument(skip(self, _memory))]
fn proc_raise(
&mut self,
_memory: &mut GuestMemory<'_>,
_sig: types::Signal,
) -> Result<(), types::Error> {
Err(types::Errno::Notsup.into())
}
#[instrument(skip(self, _memory))]
fn sched_yield(&mut self, _memory: &mut GuestMemory<'_>) -> Result<(), types::Error> {
Ok(())
}
#[instrument(skip(self, memory))]
fn random_get(
&mut self,
memory: &mut GuestMemory<'_>,
buf: GuestPtr<u8>,
buf_len: types::Size,
) -> Result<(), types::Error> {
let rand = self
.as_wasi_impl()
.get_random_bytes(buf_len.into())
.context("failed to call `get-random-bytes`")
.map_err(types::Error::trap)?;
write_bytes(memory, buf, &rand)?;
Ok(())
}
#[allow(unused_variables)]
#[instrument(skip(self, _memory))]
fn sock_accept(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
flags: types::Fdflags,
) -> Result<types::Fd, types::Error> {
tracing::warn!("preview1 sock_accept is not implemented");
self.transact()?.get_descriptor(fd)?;
Err(types::Errno::Notsock.into())
}
#[allow(unused_variables)]
#[instrument(skip(self, _memory))]
fn sock_recv(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
ri_data: types::IovecArray,
ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags), types::Error> {
tracing::warn!("preview1 sock_recv is not implemented");
self.transact()?.get_descriptor(fd)?;
Err(types::Errno::Notsock.into())
}
#[allow(unused_variables)]
#[instrument(skip(self, _memory))]
fn sock_send(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
si_data: types::CiovecArray,
_si_flags: types::Siflags,
) -> Result<types::Size, types::Error> {
tracing::warn!("preview1 sock_send is not implemented");
self.transact()?.get_descriptor(fd)?;
Err(types::Errno::Notsock.into())
}
#[allow(unused_variables)]
#[instrument(skip(self, _memory))]
fn sock_shutdown(
&mut self,
_memory: &mut GuestMemory<'_>,
fd: types::Fd,
how: types::Sdflags,
) -> Result<(), types::Error> {
tracing::warn!("preview1 sock_shutdown is not implemented");
self.transact()?.get_descriptor(fd)?;
Err(types::Errno::Notsock.into())
}
}
trait ResourceExt<T> {
fn borrowed(&self) -> Resource<T>;
}
impl<T: 'static> ResourceExt<T> for Resource<T> {
fn borrowed(&self) -> Resource<T> {
Resource::new_borrow(self.rep())
}
}