wasi-threads: an initial implementation (#5484)
This commit includes a set of changes that add initial support for `wasi-threads` to Wasmtime:
* feat: remove mutability from the WasiCtx Table
This patch adds interior mutability to the WasiCtx Table and the Table elements.
Major pain points:
* `File` only needs `RwLock<cap_std::fs::File>` to implement
`File::set_fdflags()` on Windows, because of [1]
* Because `File` needs a `RwLock` and `RwLock*Guard` cannot
be hold across an `.await`, The `async` from
`async fn num_ready_bytes(&self)` had to be removed
* Because `File` needs a `RwLock` and `RwLock*Guard` cannot
be dereferenced in `pollable`, the signature of
`fn pollable(&self) -> Option<rustix::fd::BorrowedFd>`
changed to `fn pollable(&self) -> Option<Arc<dyn AsFd + '_>>`
[1] da238e324e/src/fs/fd_flags.rs (L210-L217)
* wasi-threads: add an initial implementation
This change is a first step toward implementing `wasi-threads` in
Wasmtime. We may find that it has some missing pieces, but the core
functionality is there: when `wasi::thread_spawn` is called by a running
WebAssembly module, a function named `wasi_thread_start` is found in the
module's exports and called in a new instance. The shared memory of the
original instance is reused in the new instance.
This new WASI proposal is in its early stages and details are still
being hashed out in the [spec] and [wasi-libc] repositories. Due to its
experimental state, the `wasi-threads` functionality is hidden behind
both a compile-time and runtime flag: one must build with `--features
wasi-threads` but also run the Wasmtime CLI with `--wasm-features
threads` and `--wasi-modules experimental-wasi-threads`. One can
experiment with `wasi-threads` by running:
```console
$ cargo run --features wasi-threads -- \
--wasm-features threads --wasi-modules experimental-wasi-threads \
<a threads-enabled module>
```
Threads-enabled Wasm modules are not yet easy to build. Hopefully this
is resolved soon, but in the meantime see the use of
`THREAD_MODEL=posix` in the [wasi-libc] repository for some clues on
what is necessary. Wiggle complicates things by requiring the Wasm
memory to be exported with a certain name and `wasi-threads` also
expects that memory to be imported; this build-time obstacle can be
overcome with the `--import-memory --export-memory` flags only available
in the latest Clang tree. Due to all of this, the included tests are
written directly in WAT--run these with:
```console
$ cargo test --features wasi-threads -p wasmtime-cli -- cli_tests
```
[spec]: https://github.com/WebAssembly/wasi-threads
[wasi-libc]: https://github.com/WebAssembly/wasi-libc
This change does not protect the WASI implementations themselves from
concurrent access. This is already complete in previous commits or left
for future commits in certain cases (e.g., wasi-nn).
* wasi-threads: factor out process exit logic
As is being discussed [elsewhere], either calling `proc_exit` or
trapping in any thread should halt execution of all threads. The
Wasmtime CLI already has logic for adapting a WebAssembly error code to
a code expected in each OS. This change factors out this logic to a new
function, `maybe_exit_on_error`, for use within the `wasi-threads`
implementation.
This will work reasonably well for CLI users of Wasmtime +
`wasi-threads`, but embedders will want something better in the future:
when a `wasi-threads` threads fails, they may not want their application
to exit. Handling this is tricky, because it will require cancelling the
threads spawned by the `wasi-threads` implementation, something that is
not trivial to do in Rust. With this change, we defer that work until
later in order to provide a working implementation of `wasi-threads` for
experimentation.
[elsewhere]: https://github.com/WebAssembly/wasi-threads/pull/17
* review: work around `fd_fdstat_set_flags`
In order to make progress with wasi-threads, this change temporarily
works around limitations induced by `wasi-common`'s
`fd_fdstat_set_flags` to allow `&mut self` use in the implementation.
Eventual resolution is tracked in
https://github.com/bytecodealliance/wasmtime/issues/5643. This change
makes several related helper functions (e.g., `set_fdflags`) take `&mut
self` as well.
* test: use `wait`/`notify` to improve `threads.wat` test
Previously, the test simply executed in a loop for some hardcoded number
of iterations. This changes uses `wait` and `notify` and atomic
operations to keep track of when the spawned threads are done and join
on the main thread appropriately.
* various fixes and tweaks due to the PR review
---------
Signed-off-by: Harald Hoyer <harald@profian.com>
Co-authored-by: Harald Hoyer <harald@profian.com>
Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
@@ -2,16 +2,29 @@ use crate::clocks::WasiClocks;
|
||||
use crate::dir::{DirCaps, DirEntry, WasiDir};
|
||||
use crate::file::{FileCaps, FileEntry, WasiFile};
|
||||
use crate::sched::WasiSched;
|
||||
use crate::string_array::{StringArray, StringArrayError};
|
||||
use crate::string_array::StringArray;
|
||||
use crate::table::Table;
|
||||
use crate::Error;
|
||||
use crate::{Error, StringArrayError};
|
||||
use cap_rand::RngCore;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct WasiCtx {
|
||||
/// An `Arc`-wrapper around the wasi-common context to allow mutable access to
|
||||
/// the file descriptor table. This wrapper is only necessary due to the
|
||||
/// signature of `fd_fdstat_set_flags`; if that changes, there are a variety of
|
||||
/// improvements that can be made (TODO:
|
||||
/// https://github.com/bytecodealliance/wasmtime/issues/5643).
|
||||
#[derive(Clone)]
|
||||
pub struct WasiCtx(Arc<WasiCtxInner>);
|
||||
|
||||
pub struct WasiCtxInner {
|
||||
pub args: StringArray,
|
||||
pub env: StringArray,
|
||||
pub random: Box<dyn RngCore + Send + Sync>,
|
||||
// TODO: this mutex should not be necessary, it forces threads to serialize
|
||||
// their access to randomness unnecessarily
|
||||
// (https://github.com/bytecodealliance/wasmtime/issues/5660).
|
||||
pub random: Mutex<Box<dyn RngCore + Send + Sync>>,
|
||||
pub clocks: WasiClocks,
|
||||
pub sched: Box<dyn WasiSched>,
|
||||
pub table: Table,
|
||||
@@ -24,31 +37,31 @@ impl WasiCtx {
|
||||
sched: Box<dyn WasiSched>,
|
||||
table: Table,
|
||||
) -> Self {
|
||||
let mut s = WasiCtx {
|
||||
let s = WasiCtx(Arc::new(WasiCtxInner {
|
||||
args: StringArray::new(),
|
||||
env: StringArray::new(),
|
||||
random,
|
||||
random: Mutex::new(random),
|
||||
clocks,
|
||||
sched,
|
||||
table,
|
||||
};
|
||||
}));
|
||||
s.set_stdin(Box::new(crate::pipe::ReadPipe::new(std::io::empty())));
|
||||
s.set_stdout(Box::new(crate::pipe::WritePipe::new(std::io::sink())));
|
||||
s.set_stderr(Box::new(crate::pipe::WritePipe::new(std::io::sink())));
|
||||
s
|
||||
}
|
||||
|
||||
pub fn insert_file(&mut self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
|
||||
pub fn insert_file(&self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
|
||||
self.table()
|
||||
.insert_at(fd, Box::new(FileEntry::new(caps, file)));
|
||||
.insert_at(fd, Arc::new(FileEntry::new(caps, file)));
|
||||
}
|
||||
|
||||
pub fn push_file(&mut self, file: Box<dyn WasiFile>, caps: FileCaps) -> Result<u32, Error> {
|
||||
self.table().push(Box::new(FileEntry::new(caps, file)))
|
||||
pub fn push_file(&self, file: Box<dyn WasiFile>, caps: FileCaps) -> Result<u32, Error> {
|
||||
self.table().push(Arc::new(FileEntry::new(caps, file)))
|
||||
}
|
||||
|
||||
pub fn insert_dir(
|
||||
&mut self,
|
||||
&self,
|
||||
fd: u32,
|
||||
dir: Box<dyn WasiDir>,
|
||||
caps: DirCaps,
|
||||
@@ -57,45 +70,55 @@ impl WasiCtx {
|
||||
) {
|
||||
self.table().insert_at(
|
||||
fd,
|
||||
Box::new(DirEntry::new(caps, file_caps, Some(path), dir)),
|
||||
Arc::new(DirEntry::new(caps, file_caps, Some(path), dir)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn push_dir(
|
||||
&mut self,
|
||||
&self,
|
||||
dir: Box<dyn WasiDir>,
|
||||
caps: DirCaps,
|
||||
file_caps: FileCaps,
|
||||
path: PathBuf,
|
||||
) -> Result<u32, Error> {
|
||||
self.table()
|
||||
.push(Box::new(DirEntry::new(caps, file_caps, Some(path), dir)))
|
||||
.push(Arc::new(DirEntry::new(caps, file_caps, Some(path), dir)))
|
||||
}
|
||||
|
||||
pub fn table(&mut self) -> &mut Table {
|
||||
&mut self.table
|
||||
pub fn table(&self) -> &Table {
|
||||
&self.table
|
||||
}
|
||||
|
||||
pub fn table_mut(&mut self) -> Option<&mut Table> {
|
||||
Arc::get_mut(&mut self.0).map(|c| &mut c.table)
|
||||
}
|
||||
|
||||
pub fn push_arg(&mut self, arg: &str) -> Result<(), StringArrayError> {
|
||||
self.args.push(arg.to_owned())
|
||||
let s = Arc::get_mut(&mut self.0).expect(
|
||||
"`push_arg` should only be used during initialization before the context is cloned",
|
||||
);
|
||||
s.args.push(arg.to_owned())
|
||||
}
|
||||
|
||||
pub fn push_env(&mut self, var: &str, value: &str) -> Result<(), StringArrayError> {
|
||||
self.env.push(format!("{}={}", var, value))?;
|
||||
let s = Arc::get_mut(&mut self.0).expect(
|
||||
"`push_env` should only be used during initialization before the context is cloned",
|
||||
);
|
||||
s.env.push(format!("{}={}", var, value))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_stdin(&mut self, mut f: Box<dyn WasiFile>) {
|
||||
pub fn set_stdin(&self, mut f: Box<dyn WasiFile>) {
|
||||
let rights = Self::stdio_rights(&mut *f);
|
||||
self.insert_file(0, f, rights);
|
||||
}
|
||||
|
||||
pub fn set_stdout(&mut self, mut f: Box<dyn WasiFile>) {
|
||||
pub fn set_stdout(&self, mut f: Box<dyn WasiFile>) {
|
||||
let rights = Self::stdio_rights(&mut *f);
|
||||
self.insert_file(1, f, rights);
|
||||
}
|
||||
|
||||
pub fn set_stderr(&mut self, mut f: Box<dyn WasiFile>) {
|
||||
pub fn set_stderr(&self, mut f: Box<dyn WasiFile>) {
|
||||
let rights = Self::stdio_rights(&mut *f);
|
||||
self.insert_file(2, f, rights);
|
||||
}
|
||||
@@ -114,13 +137,13 @@ impl WasiCtx {
|
||||
}
|
||||
|
||||
pub fn push_preopened_dir(
|
||||
&mut self,
|
||||
&self,
|
||||
dir: Box<dyn WasiDir>,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<(), Error> {
|
||||
let caps = DirCaps::all();
|
||||
let file_caps = FileCaps::all();
|
||||
self.table().push(Box::new(DirEntry::new(
|
||||
self.table().push(Arc::new(DirEntry::new(
|
||||
caps,
|
||||
file_caps,
|
||||
Some(path.as_ref().to_owned()),
|
||||
@@ -129,3 +152,10 @@ impl WasiCtx {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for WasiCtx {
|
||||
type Target = WasiCtxInner;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::{Error, ErrorExt, SystemTimeSpec};
|
||||
use bitflags::bitflags;
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[wiggle::async_trait]
|
||||
pub trait WasiDir: Send + Sync {
|
||||
@@ -98,67 +99,50 @@ pub trait WasiDir: Send + Sync {
|
||||
}
|
||||
|
||||
pub(crate) struct DirEntry {
|
||||
caps: DirCaps,
|
||||
file_caps: FileCaps,
|
||||
caps: RwLock<DirFdStat>,
|
||||
preopen_path: Option<PathBuf>, // precondition: PathBuf is valid unicode
|
||||
dir: Box<dyn WasiDir>,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn new(
|
||||
caps: DirCaps,
|
||||
dir_caps: DirCaps,
|
||||
file_caps: FileCaps,
|
||||
preopen_path: Option<PathBuf>,
|
||||
dir: Box<dyn WasiDir>,
|
||||
) -> Self {
|
||||
DirEntry {
|
||||
caps,
|
||||
file_caps,
|
||||
caps: RwLock::new(DirFdStat {
|
||||
dir_caps,
|
||||
file_caps,
|
||||
}),
|
||||
preopen_path,
|
||||
dir,
|
||||
}
|
||||
}
|
||||
pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> {
|
||||
if self.caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
let missing = caps & !self.caps;
|
||||
let err = if missing.intersects(DirCaps::READDIR) {
|
||||
Error::not_dir()
|
||||
} else {
|
||||
Error::perm()
|
||||
};
|
||||
Err(err.context(format!("desired rights {:?}, has {:?}", caps, self.caps)))
|
||||
}
|
||||
let fdstat = self.caps.read().unwrap();
|
||||
fdstat.capable_of_dir(caps)
|
||||
}
|
||||
pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> {
|
||||
if self.file_caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::perm().context(format!(
|
||||
"desired rights {:?}, has {:?}",
|
||||
caps, self.file_caps
|
||||
)))
|
||||
}
|
||||
}
|
||||
pub fn drop_caps_to(&mut self, caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> {
|
||||
self.capable_of_dir(caps)?;
|
||||
self.capable_of_file(file_caps)?;
|
||||
self.caps = caps;
|
||||
self.file_caps = file_caps;
|
||||
|
||||
pub fn drop_caps_to(&self, dir_caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> {
|
||||
let mut fdstat = self.caps.write().unwrap();
|
||||
fdstat.capable_of_dir(dir_caps)?;
|
||||
fdstat.capable_of_file(file_caps)?;
|
||||
*fdstat = DirFdStat {
|
||||
dir_caps,
|
||||
file_caps,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps {
|
||||
self.caps & desired_caps
|
||||
self.caps.read().unwrap().dir_caps & desired_caps
|
||||
}
|
||||
pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps {
|
||||
self.file_caps & desired_caps
|
||||
self.caps.read().unwrap().file_caps & desired_caps
|
||||
}
|
||||
pub fn get_dir_fdstat(&self) -> DirFdStat {
|
||||
DirFdStat {
|
||||
dir_caps: self.caps,
|
||||
file_caps: self.file_caps,
|
||||
}
|
||||
self.caps.read().unwrap().clone()
|
||||
}
|
||||
pub fn preopen_path(&self) -> &Option<PathBuf> {
|
||||
&self.preopen_path
|
||||
@@ -203,18 +187,47 @@ pub struct DirFdStat {
|
||||
pub dir_caps: DirCaps,
|
||||
}
|
||||
|
||||
impl DirFdStat {
|
||||
pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> {
|
||||
if self.dir_caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
let missing = caps & !self.dir_caps;
|
||||
let err = if missing.intersects(DirCaps::READDIR) {
|
||||
Error::not_dir()
|
||||
} else {
|
||||
Error::perm()
|
||||
};
|
||||
Err(err.context(format!(
|
||||
"desired rights {:?}, has {:?}",
|
||||
caps, self.dir_caps
|
||||
)))
|
||||
}
|
||||
}
|
||||
pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> {
|
||||
if self.file_caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::perm().context(format!(
|
||||
"desired rights {:?}, has {:?}",
|
||||
caps, self.file_caps
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait TableDirExt {
|
||||
fn get_dir(&self, fd: u32) -> Result<&DirEntry, Error>;
|
||||
fn get_dir(&self, fd: u32) -> Result<Arc<DirEntry>, Error>;
|
||||
fn is_preopen(&self, fd: u32) -> bool;
|
||||
}
|
||||
|
||||
impl TableDirExt for crate::table::Table {
|
||||
fn get_dir(&self, fd: u32) -> Result<&DirEntry, Error> {
|
||||
fn get_dir(&self, fd: u32) -> Result<Arc<DirEntry>, Error> {
|
||||
self.get(fd)
|
||||
}
|
||||
fn is_preopen(&self, fd: u32) -> bool {
|
||||
if self.is::<DirEntry>(fd) {
|
||||
let dir_entry: &DirEntry = self.get(fd).unwrap();
|
||||
let dir_entry: Arc<DirEntry> = self.get(fd).unwrap();
|
||||
dir_entry.preopen_path.is_some()
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::{Error, ErrorExt, SystemTimeSpec};
|
||||
use bitflags::bitflags;
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[wiggle::async_trait]
|
||||
pub trait WasiFile: Send + Sync {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
async fn get_filetype(&mut self) -> Result<FileType, Error>;
|
||||
async fn get_filetype(&self) -> Result<FileType, Error>;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn pollable(&self) -> Option<rustix::fd::BorrowedFd> {
|
||||
@@ -17,16 +18,16 @@ pub trait WasiFile: Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
fn isatty(&mut self) -> bool {
|
||||
fn isatty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn sock_accept(&mut self, _fdflags: FdFlags) -> Result<Box<dyn WasiFile>, Error> {
|
||||
async fn sock_accept(&self, _fdflags: FdFlags) -> Result<Box<dyn WasiFile>, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn sock_recv<'a>(
|
||||
&mut self,
|
||||
&self,
|
||||
_ri_data: &mut [std::io::IoSliceMut<'a>],
|
||||
_ri_flags: RiFlags,
|
||||
) -> Result<(u64, RoFlags), Error> {
|
||||
@@ -34,26 +35,26 @@ pub trait WasiFile: Send + Sync {
|
||||
}
|
||||
|
||||
async fn sock_send<'a>(
|
||||
&mut self,
|
||||
&self,
|
||||
_si_data: &[std::io::IoSlice<'a>],
|
||||
_si_flags: SiFlags,
|
||||
) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn sock_shutdown(&mut self, _how: SdFlags) -> Result<(), Error> {
|
||||
async fn sock_shutdown(&self, _how: SdFlags) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn datasync(&mut self) -> Result<(), Error> {
|
||||
async fn datasync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sync(&mut self) -> Result<(), Error> {
|
||||
async fn sync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_fdflags(&mut self) -> Result<FdFlags, Error> {
|
||||
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
Ok(FdFlags::empty())
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ pub trait WasiFile: Send + Sync {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn get_filestat(&mut self) -> Result<Filestat, Error> {
|
||||
async fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
Ok(Filestat {
|
||||
device_id: 0,
|
||||
inode: 0,
|
||||
@@ -74,62 +75,59 @@ pub trait WasiFile: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_filestat_size(&mut self, _size: u64) -> Result<(), Error> {
|
||||
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn advise(&mut self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
|
||||
async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn allocate(&mut self, _offset: u64, _len: u64) -> Result<(), Error> {
|
||||
async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn set_times(
|
||||
&mut self,
|
||||
&self,
|
||||
_atime: Option<SystemTimeSpec>,
|
||||
_mtime: Option<SystemTimeSpec>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn read_vectored<'a>(
|
||||
&mut self,
|
||||
_bufs: &mut [std::io::IoSliceMut<'a>],
|
||||
) -> Result<u64, Error> {
|
||||
async fn read_vectored<'a>(&self, _bufs: &mut [std::io::IoSliceMut<'a>]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn read_vectored_at<'a>(
|
||||
&mut self,
|
||||
&self,
|
||||
_bufs: &mut [std::io::IoSliceMut<'a>],
|
||||
_offset: u64,
|
||||
) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn write_vectored<'a>(&mut self, _bufs: &[std::io::IoSlice<'a>]) -> Result<u64, Error> {
|
||||
async fn write_vectored<'a>(&self, _bufs: &[std::io::IoSlice<'a>]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn write_vectored_at<'a>(
|
||||
&mut self,
|
||||
&self,
|
||||
_bufs: &[std::io::IoSlice<'a>],
|
||||
_offset: u64,
|
||||
) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn seek(&mut self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
async fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn peek(&mut self, _buf: &mut [u8]) -> Result<u64, Error> {
|
||||
async fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
|
||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
@@ -212,11 +210,11 @@ pub struct Filestat {
|
||||
}
|
||||
|
||||
pub(crate) trait TableFileExt {
|
||||
fn get_file(&self, fd: u32) -> Result<&FileEntry, Error>;
|
||||
fn get_file(&self, fd: u32) -> Result<Arc<FileEntry>, Error>;
|
||||
fn get_file_mut(&mut self, fd: u32) -> Result<&mut FileEntry, Error>;
|
||||
}
|
||||
impl TableFileExt for crate::table::Table {
|
||||
fn get_file(&self, fd: u32) -> Result<&FileEntry, Error> {
|
||||
fn get_file(&self, fd: u32) -> Result<Arc<FileEntry>, Error> {
|
||||
self.get(fd)
|
||||
}
|
||||
fn get_file_mut(&mut self, fd: u32) -> Result<&mut FileEntry, Error> {
|
||||
@@ -225,20 +223,23 @@ impl TableFileExt for crate::table::Table {
|
||||
}
|
||||
|
||||
pub(crate) struct FileEntry {
|
||||
caps: FileCaps,
|
||||
caps: RwLock<FileCaps>,
|
||||
file: Box<dyn WasiFile>,
|
||||
}
|
||||
|
||||
impl FileEntry {
|
||||
pub fn new(caps: FileCaps, file: Box<dyn WasiFile>) -> Self {
|
||||
FileEntry { caps, file }
|
||||
FileEntry {
|
||||
caps: RwLock::new(caps),
|
||||
file,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capable_of(&self, caps: FileCaps) -> Result<(), Error> {
|
||||
if self.caps.contains(caps) {
|
||||
if self.caps.read().unwrap().contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
let missing = caps & !self.caps;
|
||||
let missing = caps & !(*self.caps.read().unwrap());
|
||||
let err = if missing.intersects(FileCaps::READ | FileCaps::WRITE) {
|
||||
// `EBADF` is a little surprising here because it's also used
|
||||
// for unknown-file-descriptor errors, but it's what POSIX uses
|
||||
@@ -251,16 +252,17 @@ impl FileEntry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_caps_to(&mut self, caps: FileCaps) -> Result<(), Error> {
|
||||
pub fn drop_caps_to(&self, caps: FileCaps) -> Result<(), Error> {
|
||||
self.capable_of(caps)?;
|
||||
self.caps = caps;
|
||||
*self.caps.write().unwrap() = caps;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_fdstat(&mut self) -> Result<FdStat, Error> {
|
||||
pub async fn get_fdstat(&self) -> Result<FdStat, Error> {
|
||||
let caps = self.caps.read().unwrap().clone();
|
||||
Ok(FdStat {
|
||||
filetype: self.file.get_filetype().await?,
|
||||
caps: self.caps,
|
||||
caps,
|
||||
flags: self.file.get_fdflags().await?,
|
||||
})
|
||||
}
|
||||
@@ -276,7 +278,6 @@ impl FileEntryExt for FileEntry {
|
||||
self.capable_of(caps)?;
|
||||
Ok(&*self.file)
|
||||
}
|
||||
|
||||
fn get_cap_mut(&mut self, caps: FileCaps) -> Result<&mut dyn WasiFile, Error> {
|
||||
self.capable_of(caps)?;
|
||||
Ok(&mut *self.file)
|
||||
|
||||
@@ -105,10 +105,10 @@ impl<R: Read + Any + Send + Sync> WasiFile for ReadPipe<R> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
async fn get_filetype(&mut self) -> Result<FileType, Error> {
|
||||
async fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
Ok(FileType::Pipe)
|
||||
}
|
||||
async fn read_vectored<'a>(&mut self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
|
||||
async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
|
||||
let n = self.borrow().read_vectored(bufs)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
@@ -189,13 +189,13 @@ impl<W: Write + Any + Send + Sync> WasiFile for WritePipe<W> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
async fn get_filetype(&mut self) -> Result<FileType, Error> {
|
||||
async fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
Ok(FileType::Pipe)
|
||||
}
|
||||
async fn get_fdflags(&mut self) -> Result<FdFlags, Error> {
|
||||
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
Ok(FdFlags::APPEND)
|
||||
}
|
||||
async fn write_vectored<'a>(&mut self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
|
||||
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
|
||||
let n = self.borrow().write_vectored(bufs)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
|
||||
@@ -528,10 +528,8 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
fd: types::Fd,
|
||||
iovs: &types::IovecArray<'a>,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::READ)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::READ)?;
|
||||
|
||||
let iovs: Vec<wiggle::GuestPtr<[u8]>> = iovs
|
||||
.iter()
|
||||
@@ -601,10 +599,8 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
iovs: &types::IovecArray<'a>,
|
||||
offset: types::Filesize,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::READ | FileCaps::SEEK)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::READ | FileCaps::SEEK)?;
|
||||
|
||||
let iovs: Vec<wiggle::GuestPtr<[u8]>> = iovs
|
||||
.iter()
|
||||
@@ -675,10 +671,8 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
fd: types::Fd,
|
||||
ciovs: &types::CiovecArray<'a>,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::WRITE)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::WRITE)?;
|
||||
|
||||
let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
|
||||
.iter()
|
||||
@@ -704,10 +698,8 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
ciovs: &types::CiovecArray<'a>,
|
||||
offset: types::Filesize,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::WRITE | FileCaps::SEEK)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::WRITE | FileCaps::SEEK)?;
|
||||
|
||||
let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
|
||||
.iter()
|
||||
@@ -953,7 +945,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
}
|
||||
}
|
||||
|
||||
let table = &mut self.table;
|
||||
let table = &self.table;
|
||||
let mut sub_fds: HashSet<types::Fd> = HashSet::new();
|
||||
// We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside
|
||||
let mut reads: Vec<(u32, Userdata)> = Vec::new();
|
||||
@@ -1003,8 +995,8 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
sub_fds.insert(fd);
|
||||
}
|
||||
table
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::POLL_READWRITE)?;
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
reads.push((u32::from(fd), sub.userdata.into()));
|
||||
}
|
||||
types::SubscriptionU::FdWrite(writesub) => {
|
||||
@@ -1016,8 +1008,8 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
|
||||
sub_fds.insert(fd);
|
||||
}
|
||||
table
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::POLL_READWRITE)?;
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
writes.push((u32::from(fd), sub.userdata.into()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use cap_std::time::{Duration, SystemClock};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{IoSlice, IoSliceMut};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use wiggle::GuestPtr;
|
||||
|
||||
pub mod error;
|
||||
@@ -111,8 +112,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
advice: types::Advice,
|
||||
) -> Result<(), Error> {
|
||||
self.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::ADVISE)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::ADVISE)?
|
||||
.advise(offset, len, advice.into())
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -125,8 +126,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
len: types::Filesize,
|
||||
) -> Result<(), Error> {
|
||||
self.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::ALLOCATE)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::ALLOCATE)?
|
||||
.allocate(offset, len)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -142,15 +143,15 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
}
|
||||
// fd_close must close either a File or a Dir handle
|
||||
if table.is::<FileEntry>(fd) {
|
||||
let _ = table.delete(fd);
|
||||
let _ = table.delete::<FileEntry>(fd);
|
||||
} else if table.is::<DirEntry>(fd) {
|
||||
// We cannot close preopened directories
|
||||
let dir_entry: &DirEntry = table.get(fd).unwrap();
|
||||
let dir_entry: Arc<DirEntry> = table.get(fd).unwrap();
|
||||
if dir_entry.preopen_path().is_some() {
|
||||
return Err(Error::not_supported().context("cannot close propened directory"));
|
||||
}
|
||||
drop(dir_entry);
|
||||
let _ = table.delete(fd);
|
||||
let _ = table.delete::<DirEntry>(fd);
|
||||
} else {
|
||||
return Err(Error::badf().context("key does not refer to file or directory"));
|
||||
}
|
||||
@@ -160,8 +161,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
|
||||
async fn fd_datasync(&mut self, fd: types::Fd) -> Result<(), Error> {
|
||||
self.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::DATASYNC)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::DATASYNC)?
|
||||
.datasync()
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -171,11 +172,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
let table = self.table();
|
||||
let fd = u32::from(fd);
|
||||
if table.is::<FileEntry>(fd) {
|
||||
let file_entry: &mut FileEntry = table.get_mut(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: &DirEntry = table.get(fd)?;
|
||||
let dir_entry: Arc<DirEntry> = table.get(fd)?;
|
||||
let dir_fdstat = dir_entry.get_dir_fdstat();
|
||||
Ok(types::Fdstat::from(&dir_fdstat))
|
||||
} else {
|
||||
@@ -188,11 +189,16 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
fd: types::Fd,
|
||||
flags: types::Fdflags,
|
||||
) -> Result<(), Error> {
|
||||
self.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::FDSTAT_SET_FLAGS)?
|
||||
.set_fdflags(FdFlags::from(flags))
|
||||
.await
|
||||
if let Some(table) = self.table_mut() {
|
||||
table
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::FDSTAT_SET_FLAGS)?
|
||||
.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(
|
||||
@@ -204,11 +210,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
let table = self.table();
|
||||
let fd = u32::from(fd);
|
||||
if table.is::<FileEntry>(fd) {
|
||||
let file_entry: &mut FileEntry = table.get_mut(fd)?;
|
||||
let file_entry: Arc<FileEntry> = table.get(fd)?;
|
||||
let file_caps = FileCaps::from(&fs_rights_base);
|
||||
file_entry.drop_caps_to(file_caps)
|
||||
} else if table.is::<DirEntry>(fd) {
|
||||
let dir_entry: &mut DirEntry = table.get_mut(fd)?;
|
||||
let dir_entry: Arc<DirEntry> = table.get(fd)?;
|
||||
let dir_caps = DirCaps::from(&fs_rights_base);
|
||||
let file_caps = FileCaps::from(&fs_rights_inheriting);
|
||||
dir_entry.drop_caps_to(dir_caps, file_caps)
|
||||
@@ -222,8 +228,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
let fd = u32::from(fd);
|
||||
if table.is::<FileEntry>(fd) {
|
||||
let filestat = table
|
||||
.get_file_mut(fd)?
|
||||
.get_cap_mut(FileCaps::FILESTAT_GET)?
|
||||
.get_file(fd)?
|
||||
.get_cap(FileCaps::FILESTAT_GET)?
|
||||
.get_filestat()
|
||||
.await?;
|
||||
Ok(filestat.into())
|
||||
@@ -245,8 +251,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
size: types::Filesize,
|
||||
) -> Result<(), Error> {
|
||||
self.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::FILESTAT_SET_SIZE)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::FILESTAT_SET_SIZE)?
|
||||
.set_filestat_size(size)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -272,9 +278,9 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
|
||||
if table.is::<FileEntry>(fd) {
|
||||
table
|
||||
.get_file_mut(fd)
|
||||
.get_file(fd)
|
||||
.expect("checked that entry is file")
|
||||
.get_cap_mut(FileCaps::FILESTAT_SET_TIMES)?
|
||||
.get_cap(FileCaps::FILESTAT_SET_TIMES)?
|
||||
.set_times(atim, mtim)
|
||||
.await
|
||||
} else if table.is::<DirEntry>(fd) {
|
||||
@@ -294,10 +300,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
fd: types::Fd,
|
||||
iovs: &types::IovecArray<'a>,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::READ)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::READ)?;
|
||||
|
||||
let iovs: Vec<wiggle::GuestPtr<[u8]>> = iovs
|
||||
.iter()
|
||||
@@ -367,10 +371,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
iovs: &types::IovecArray<'a>,
|
||||
offset: types::Filesize,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::READ | FileCaps::SEEK)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::READ | FileCaps::SEEK)?;
|
||||
|
||||
let iovs: Vec<wiggle::GuestPtr<[u8]>> = iovs
|
||||
.iter()
|
||||
@@ -441,10 +443,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
fd: types::Fd,
|
||||
ciovs: &types::CiovecArray<'a>,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::WRITE)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::WRITE)?;
|
||||
|
||||
let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
|
||||
.iter()
|
||||
@@ -470,10 +470,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
ciovs: &types::CiovecArray<'a>,
|
||||
offset: types::Filesize,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::WRITE | FileCaps::SEEK)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::WRITE | FileCaps::SEEK)?;
|
||||
|
||||
let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
|
||||
.iter()
|
||||
@@ -495,7 +493,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
|
||||
async fn fd_prestat_get(&mut self, fd: types::Fd) -> Result<types::Prestat, Error> {
|
||||
let table = self.table();
|
||||
let dir_entry: &DirEntry = table.get(u32::from(fd)).map_err(|_| Error::badf())?;
|
||||
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())?;
|
||||
@@ -512,7 +510,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
path_max_len: types::Size,
|
||||
) -> Result<(), Error> {
|
||||
let table = self.table();
|
||||
let dir_entry: &DirEntry = table.get(u32::from(fd)).map_err(|_| Error::not_dir())?;
|
||||
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()
|
||||
@@ -538,11 +536,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
if table.is_preopen(from) || table.is_preopen(to) {
|
||||
return Err(Error::not_supported().context("cannot renumber a preopen"));
|
||||
}
|
||||
let from_entry = table
|
||||
.delete(from)
|
||||
.expect("we checked that table contains from");
|
||||
table.insert_at(to, from_entry);
|
||||
Ok(())
|
||||
table.renumber(from, to)
|
||||
}
|
||||
|
||||
async fn fd_seek(
|
||||
@@ -566,8 +560,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
};
|
||||
let newoffset = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(required_caps)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(required_caps)?
|
||||
.seek(whence)
|
||||
.await?;
|
||||
Ok(newoffset)
|
||||
@@ -575,8 +569,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
|
||||
async fn fd_sync(&mut self, fd: types::Fd) -> Result<(), Error> {
|
||||
self.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::SYNC)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::SYNC)?
|
||||
.sync()
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -586,8 +580,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
// XXX should this be stream_position?
|
||||
let offset = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::TELL)?
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::TELL)?
|
||||
.seek(std::io::SeekFrom::Current(0))
|
||||
.await?;
|
||||
Ok(offset)
|
||||
@@ -714,12 +708,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
target_path: &GuestPtr<'a, str>,
|
||||
) -> Result<(), Error> {
|
||||
let table = self.table();
|
||||
let src_dir = table
|
||||
.get_dir(u32::from(src_fd))?
|
||||
.get_cap(DirCaps::LINK_SOURCE)?;
|
||||
let target_dir = table
|
||||
.get_dir(u32::from(target_fd))?
|
||||
.get_cap(DirCaps::LINK_TARGET)?;
|
||||
let src_dir = table.get_dir(u32::from(src_fd))?;
|
||||
let src_dir = src_dir.get_cap(DirCaps::LINK_SOURCE)?;
|
||||
let target_dir = table.get_dir(u32::from(target_fd))?;
|
||||
let target_dir = target_dir.get_cap(DirCaps::LINK_TARGET)?;
|
||||
let symlink_follow = src_flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
|
||||
if symlink_follow {
|
||||
return Err(Error::invalid_argument()
|
||||
@@ -769,7 +761,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
let dir = dir_entry.get_cap(DirCaps::OPEN)?;
|
||||
let child_dir = dir.open_dir(symlink_follow, path.deref()).await?;
|
||||
drop(dir);
|
||||
let fd = table.push(Box::new(DirEntry::new(
|
||||
let fd = table.push(Arc::new(DirEntry::new(
|
||||
dir_caps, file_caps, None, child_dir,
|
||||
)))?;
|
||||
Ok(types::Fd::from(fd))
|
||||
@@ -789,7 +781,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)
|
||||
.await?;
|
||||
drop(dir);
|
||||
let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?;
|
||||
let fd = table.push(Arc::new(FileEntry::new(file_caps, file)))?;
|
||||
Ok(types::Fd::from(fd))
|
||||
}
|
||||
}
|
||||
@@ -839,12 +831,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
dest_path: &GuestPtr<'a, str>,
|
||||
) -> Result<(), Error> {
|
||||
let table = self.table();
|
||||
let src_dir = table
|
||||
.get_dir(u32::from(src_fd))?
|
||||
.get_cap(DirCaps::RENAME_SOURCE)?;
|
||||
let dest_dir = table
|
||||
.get_dir(u32::from(dest_fd))?
|
||||
.get_cap(DirCaps::RENAME_TARGET)?;
|
||||
let src_dir = table.get_dir(u32::from(src_fd))?;
|
||||
let src_dir = src_dir.get_cap(DirCaps::RENAME_SOURCE)?;
|
||||
let dest_dir = table.get_dir(u32::from(dest_fd))?;
|
||||
let dest_dir = dest_dir.get_cap(DirCaps::RENAME_TARGET)?;
|
||||
src_dir
|
||||
.rename(
|
||||
src_path.as_cow()?.deref(),
|
||||
@@ -914,10 +904,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
}
|
||||
}
|
||||
|
||||
let table = &mut self.table;
|
||||
let table = &self.table;
|
||||
// We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside
|
||||
let mut read_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new();
|
||||
let mut write_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new();
|
||||
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);
|
||||
@@ -983,25 +974,37 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
},
|
||||
types::SubscriptionU::FdRead(readsub) => {
|
||||
let fd = readsub.file_descriptor;
|
||||
let file_ref = table
|
||||
.get_file(u32::from(fd))?
|
||||
.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
read_refs.push((file_ref, sub.userdata.into()));
|
||||
let file_ref = table.get_file(u32::from(fd))?;
|
||||
let _file = file_ref.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
|
||||
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))?
|
||||
.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
write_refs.push((file_ref, sub.userdata.into()));
|
||||
let file_ref = table.get_file(u32::from(fd))?;
|
||||
let _file = file_ref.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
write_refs.push((file_ref, Some(sub.userdata.into())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (f, ud) in read_refs.iter_mut() {
|
||||
let mut read_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new();
|
||||
for (file_lock, userdata) in read_refs.iter_mut() {
|
||||
let file = file_lock.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
read_mut_refs.push((file, userdata.take().unwrap()));
|
||||
}
|
||||
|
||||
for (f, ud) in read_mut_refs.iter_mut() {
|
||||
poll.subscribe_read(*f, *ud);
|
||||
}
|
||||
for (f, ud) in write_refs.iter_mut() {
|
||||
|
||||
let mut write_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new();
|
||||
for (file_lock, userdata) in write_refs.iter_mut() {
|
||||
let file = file_lock.get_cap(FileCaps::POLL_READWRITE)?;
|
||||
write_mut_refs.push((file, userdata.take().unwrap()));
|
||||
}
|
||||
|
||||
for (f, ud) in write_mut_refs.iter_mut() {
|
||||
poll.subscribe_write(*f, *ud);
|
||||
}
|
||||
|
||||
@@ -1112,7 +1115,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
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.try_fill_bytes(&mut tmp)?;
|
||||
self.random.lock().unwrap().try_fill_bytes(&mut tmp)?;
|
||||
let dest = buf
|
||||
.get_range(copied..copied + len)
|
||||
.unwrap()
|
||||
@@ -1124,7 +1127,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
// If the Wasm memory is non-shared, copy directly into the linear
|
||||
// memory.
|
||||
let mem = &mut buf.as_slice_mut()?.unwrap();
|
||||
self.random.try_fill_bytes(mem)?;
|
||||
self.random.lock().unwrap().try_fill_bytes(mem)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1135,9 +1138,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
flags: types::Fdflags,
|
||||
) -> Result<types::Fd, Error> {
|
||||
let table = self.table();
|
||||
let f = table
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::READ)?;
|
||||
let f = table.get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::READ)?;
|
||||
|
||||
let file = f.sock_accept(FdFlags::from(flags)).await?;
|
||||
let file_caps = FileCaps::READ
|
||||
@@ -1146,7 +1148,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
| FileCaps::POLL_READWRITE
|
||||
| FileCaps::FILESTAT_GET;
|
||||
|
||||
let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?;
|
||||
let fd = table.push(Arc::new(FileEntry::new(file_caps, file)))?;
|
||||
Ok(types::Fd::from(fd))
|
||||
}
|
||||
|
||||
@@ -1156,10 +1158,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
ri_data: &types::IovecArray<'a>,
|
||||
ri_flags: types::Riflags,
|
||||
) -> Result<(types::Size, types::Roflags), Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::READ)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::READ)?;
|
||||
|
||||
let iovs: Vec<wiggle::GuestPtr<[u8]>> = ri_data
|
||||
.iter()
|
||||
@@ -1231,10 +1231,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
si_data: &types::CiovecArray<'a>,
|
||||
_si_flags: types::Siflags,
|
||||
) -> Result<types::Size, Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::WRITE)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::WRITE)?;
|
||||
|
||||
let guest_slices: Vec<wiggle::GuestCow<u8>> = si_data
|
||||
.iter()
|
||||
@@ -1255,10 +1253,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
|
||||
}
|
||||
|
||||
async fn sock_shutdown(&mut self, fd: types::Fd, how: types::Sdflags) -> Result<(), Error> {
|
||||
let f = self
|
||||
.table()
|
||||
.get_file_mut(u32::from(fd))?
|
||||
.get_cap_mut(FileCaps::FDSTAT_SET_FLAGS)?;
|
||||
let f = self.table().get_file(u32::from(fd))?;
|
||||
let f = f.get_cap(FileCaps::FDSTAT_SET_FLAGS)?;
|
||||
|
||||
f.sock_shutdown(SdFlags::from(how)).await
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{Error, ErrorExt};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// The `Table` type is designed to map u32 handles to resources. The table is now part of the
|
||||
/// public interface to a `WasiCtx` - it is reference counted so that it can be shared beyond a
|
||||
@@ -9,84 +10,105 @@ use std::collections::HashMap;
|
||||
///
|
||||
/// The `Table` type is intended to model how the Interface Types concept of Resources is shaping
|
||||
/// up. Right now it is just an approximation.
|
||||
pub struct Table {
|
||||
map: HashMap<u32, Box<dyn Any + Send + Sync>>,
|
||||
pub struct Table(RwLock<Inner>);
|
||||
|
||||
struct Inner {
|
||||
map: HashMap<u32, Arc<dyn Any + Send + Sync>>,
|
||||
next_key: u32,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
/// Create an empty table. New insertions will begin at 3, above stdio.
|
||||
pub fn new() -> Self {
|
||||
Table {
|
||||
Table(RwLock::new(Inner {
|
||||
map: HashMap::new(),
|
||||
next_key: 3, // 0, 1 and 2 are reserved for stdio
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Insert a resource at a certain index.
|
||||
pub fn insert_at(&mut self, key: u32, a: Box<dyn Any + Send + Sync>) {
|
||||
self.map.insert(key, a);
|
||||
pub fn insert_at<T: Any + Send + Sync>(&self, key: u32, a: Arc<T>) {
|
||||
self.0.write().unwrap().map.insert(key, a);
|
||||
}
|
||||
|
||||
/// Insert a resource at the next available index.
|
||||
pub fn push(&mut self, a: Box<dyn Any + Send + Sync>) -> Result<u32, Error> {
|
||||
pub fn push<T: Any + Send + Sync>(&self, a: Arc<T>) -> Result<u32, Error> {
|
||||
let mut inner = self.0.write().unwrap();
|
||||
// NOTE: The performance of this new key calculation could be very bad once keys wrap
|
||||
// around.
|
||||
if self.map.len() == u32::MAX as usize {
|
||||
if inner.map.len() == u32::MAX as usize {
|
||||
return Err(Error::trap(anyhow::Error::msg("table has no free keys")));
|
||||
}
|
||||
loop {
|
||||
let key = self.next_key;
|
||||
self.next_key = self.next_key.wrapping_add(1);
|
||||
if self.map.contains_key(&key) {
|
||||
let key = inner.next_key;
|
||||
inner.next_key += 1;
|
||||
if inner.map.contains_key(&key) {
|
||||
continue;
|
||||
}
|
||||
self.map.insert(key, a);
|
||||
inner.map.insert(key, a);
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the table has a resource at the given index.
|
||||
pub fn contains_key(&self, key: u32) -> bool {
|
||||
self.map.contains_key(&key)
|
||||
self.0.read().unwrap().map.contains_key(&key)
|
||||
}
|
||||
|
||||
/// Check if the resource at a given index can be downcast to a given type.
|
||||
/// Note: this will always fail if the resource is already borrowed.
|
||||
pub fn is<T: Any + Sized>(&self, key: u32) -> bool {
|
||||
if let Some(r) = self.map.get(&key) {
|
||||
if let Some(r) = self.0.read().unwrap().map.get(&key) {
|
||||
r.is::<T>()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an immutable reference to a resource of a given type at a given index. Multiple
|
||||
/// immutable references can be borrowed at any given time. Borrow failure
|
||||
/// results in a trapping error.
|
||||
pub fn get<T: Any + Sized>(&self, key: u32) -> Result<&T, Error> {
|
||||
if let Some(r) = self.map.get(&key) {
|
||||
r.downcast_ref::<T>()
|
||||
.ok_or_else(|| Error::badf().context("element is a different type"))
|
||||
/// Get an Arc reference to a resource of a given type at a given index. Multiple
|
||||
/// immutable references can be borrowed at any given time.
|
||||
pub fn get<T: Any + Send + Sync + Sized>(&self, key: u32) -> Result<Arc<T>, Error> {
|
||||
if let Some(r) = self.0.read().unwrap().map.get(&key).cloned() {
|
||||
r.downcast::<T>()
|
||||
.map_err(|_| Error::badf().context("element is a different type"))
|
||||
} else {
|
||||
Err(Error::badf().context("key not in table"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a resource of a given type at a given index. Only one mutable
|
||||
/// reference can be borrowed at any given time. Borrow failure results in a trapping error.
|
||||
pub fn get_mut<T: Any + Sized>(&mut self, key: u32) -> Result<&mut T, Error> {
|
||||
if let Some(r) = self.map.get_mut(&key) {
|
||||
r.downcast_mut::<T>()
|
||||
.ok_or_else(|| Error::badf().context("element is a different type"))
|
||||
} else {
|
||||
Err(Error::badf().context("key not in table"))
|
||||
}
|
||||
/// Get a mutable reference to a resource of a given type at a given index.
|
||||
/// Only one such reference can be borrowed at any given time.
|
||||
pub fn get_mut<T: Any>(&mut self, key: u32) -> Result<&mut T, Error> {
|
||||
let entry = match self.0.get_mut().unwrap().map.get_mut(&key) {
|
||||
Some(entry) => entry,
|
||||
None => return Err(Error::badf().context("key not in table")),
|
||||
};
|
||||
let entry = match Arc::get_mut(entry) {
|
||||
Some(entry) => entry,
|
||||
None => return Err(Error::badf().context("cannot mutably borrow shared file")),
|
||||
};
|
||||
entry
|
||||
.downcast_mut::<T>()
|
||||
.ok_or_else(|| Error::badf().context("element is a different type"))
|
||||
}
|
||||
|
||||
/// Remove a resource at a given index from the table. Returns the resource
|
||||
/// if it was present.
|
||||
pub fn delete(&mut self, key: u32) -> Option<Box<dyn Any + Send + Sync>> {
|
||||
self.map.remove(&key)
|
||||
pub fn delete<T: Any + Send + Sync>(&self, key: u32) -> Option<Arc<T>> {
|
||||
self.0
|
||||
.write()
|
||||
.unwrap()
|
||||
.map
|
||||
.remove(&key)
|
||||
.map(|r| r.downcast::<T>().unwrap())
|
||||
}
|
||||
|
||||
/// Remove a resource at a given index from the table. Returns the resource
|
||||
/// if it was present.
|
||||
pub fn renumber(&self, from: u32, to: u32) -> Result<(), Error> {
|
||||
let map = &mut self.0.write().unwrap().map;
|
||||
let from_entry = map.remove(&from).ok_or(Error::badf())?;
|
||||
map.insert(to, from_entry);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user