move wasi-c2 into wasi-common
This commit is contained in:
48
crates/wasi-common/Cargo.toml
Normal file
48
crates/wasi-common/Cargo.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "wasi-c2"
|
||||
version = "0.22.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "WASI implementation in Rust"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
categories = ["wasm"]
|
||||
keywords = ["webassembly", "wasm"]
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
include = ["src/**/*", "LICENSE", "build.rs"]
|
||||
build = "build.rs"
|
||||
publish = false
|
||||
|
||||
# This doesn't actually link to a native library, but it allows us to set env
|
||||
# vars like `DEP_WASI_C2_19_*` for crates that have build scripts and depend
|
||||
# on this crate, allowing other crates to use the same witx files.
|
||||
links = "wasi-c2-19"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
wiggle = { path = "../wiggle", default-features = false, version = "0.22.0" }
|
||||
tracing = "0.1.19"
|
||||
system-interface = { version = "0.5.4", features = ["cap_std_impls"] }
|
||||
cap-std = "0.11"
|
||||
cap-rand = "0.11"
|
||||
bitflags = "1.2"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[features]
|
||||
default = ["trace_log"]
|
||||
# This feature enables the `tracing` logs in the calls to target the `log`
|
||||
# ecosystem of backends (e.g. `env_logger`. Disable this if you want to use
|
||||
# `tracing-subscriber`.
|
||||
trace_log = [ "wiggle/tracing_log", "tracing/log" ]
|
||||
# Need to make the wiggle_metadata feature available to consumers of this
|
||||
# crate if they want the snapshots to have metadata available.
|
||||
wiggle_metadata = ["wiggle/wiggle_metadata"]
|
||||
@@ -2,7 +2,9 @@
|
||||
// use the same witx files if they want.
|
||||
fn main() {
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let wasi = cwd.join("WASI");
|
||||
let wasi = cwd.join("..").join("wasi-common").join("WASI");
|
||||
// this will be available to dependent crates via the DEP_WASI_C2_19_WASI env var:
|
||||
println!("cargo:wasi={}", wasi.display());
|
||||
// and available to our own crate as WASI_ROOT:
|
||||
println!("cargo:rustc-env=WASI_ROOT={}", wasi.display());
|
||||
}
|
||||
|
||||
33
crates/wasi-common/cap-std-sync/Cargo.toml
Normal file
33
crates/wasi-common/cap-std-sync/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "wasi-c2-cap-std-sync"
|
||||
version = "0.22.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "WASI implementation in Rust"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
categories = ["wasm"]
|
||||
keywords = ["webassembly", "wasm"]
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
include = ["src/**/*", "LICENSE" ]
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
wasi-c2 = { path = "../" }
|
||||
anyhow = "1.0"
|
||||
cap-std = "0.11"
|
||||
cap-fs-ext = "0.11"
|
||||
cap-time-ext = "0.11"
|
||||
cap-rand = "0.11"
|
||||
fs-set-times = "0.2.2"
|
||||
unsafe-io = "0.3"
|
||||
system-interface = { version = "0.5.4", features = ["cap_std_impls"] }
|
||||
tracing = "0.1.19"
|
||||
bitflags = "1.2"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3"
|
||||
lazy_static = "1.4"
|
||||
46
crates/wasi-common/cap-std-sync/src/clocks.rs
Normal file
46
crates/wasi-common/cap-std-sync/src/clocks.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use cap_std::time::{Duration, Instant, SystemTime};
|
||||
use cap_time_ext::{MonotonicClockExt, SystemClockExt};
|
||||
use wasi_c2::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock};
|
||||
|
||||
pub struct SystemClock(cap_std::time::SystemClock);
|
||||
|
||||
impl SystemClock {
|
||||
pub unsafe fn new() -> Self {
|
||||
SystemClock(cap_std::time::SystemClock::new())
|
||||
}
|
||||
}
|
||||
impl WasiSystemClock for SystemClock {
|
||||
fn resolution(&self) -> Duration {
|
||||
self.0.resolution()
|
||||
}
|
||||
fn now(&self, precision: Duration) -> SystemTime {
|
||||
self.0.now_with(precision)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MonotonicClock(cap_std::time::MonotonicClock);
|
||||
impl MonotonicClock {
|
||||
pub unsafe fn new() -> Self {
|
||||
MonotonicClock(cap_std::time::MonotonicClock::new())
|
||||
}
|
||||
}
|
||||
impl WasiMonotonicClock for MonotonicClock {
|
||||
fn resolution(&self) -> Duration {
|
||||
self.0.resolution()
|
||||
}
|
||||
fn now(&self, precision: Duration) -> Instant {
|
||||
self.0.now_with(precision)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clocks() -> WasiClocks {
|
||||
let system = Box::new(unsafe { SystemClock::new() });
|
||||
let monotonic = unsafe { cap_std::time::MonotonicClock::new() };
|
||||
let creation_time = monotonic.now();
|
||||
let monotonic = Box::new(MonotonicClock(monotonic));
|
||||
WasiClocks {
|
||||
system,
|
||||
monotonic,
|
||||
creation_time,
|
||||
}
|
||||
}
|
||||
249
crates/wasi-common/cap-std-sync/src/dir.rs
Normal file
249
crates/wasi-common/cap-std-sync/src/dir.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use crate::file::{filetype_from, File};
|
||||
use cap_fs_ext::{DirExt, MetadataExt, SystemTimeSpec};
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::path::{Path, PathBuf};
|
||||
use wasi_c2::{
|
||||
dir::{ReaddirCursor, ReaddirEntity, WasiDir},
|
||||
file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile},
|
||||
Error, ErrorExt,
|
||||
};
|
||||
|
||||
pub struct Dir(cap_std::fs::Dir);
|
||||
|
||||
impl Dir {
|
||||
pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self {
|
||||
Dir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiDir for Dir {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn open_file(
|
||||
&self,
|
||||
symlink_follow: bool,
|
||||
path: &str,
|
||||
oflags: OFlags,
|
||||
caps: FileCaps,
|
||||
fdflags: FdFlags,
|
||||
) -> Result<Box<dyn WasiFile>, Error> {
|
||||
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt};
|
||||
|
||||
let mut opts = cap_std::fs::OpenOptions::new();
|
||||
|
||||
if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) {
|
||||
opts.create_new(true);
|
||||
opts.write(true);
|
||||
} else if oflags.contains(OFlags::CREATE) {
|
||||
opts.create(true);
|
||||
opts.write(true);
|
||||
}
|
||||
if oflags.contains(OFlags::TRUNCATE) {
|
||||
opts.truncate(true);
|
||||
}
|
||||
if caps.contains(FileCaps::WRITE)
|
||||
|| caps.contains(FileCaps::DATASYNC)
|
||||
|| caps.contains(FileCaps::ALLOCATE)
|
||||
|| caps.contains(FileCaps::FILESTAT_SET_SIZE)
|
||||
{
|
||||
opts.write(true);
|
||||
} else {
|
||||
// If not opened write, open read. This way the OS lets us open the file.
|
||||
// If FileCaps::READ is not set, read calls will be rejected at the
|
||||
// get_cap check.
|
||||
opts.read(true);
|
||||
}
|
||||
if caps.contains(FileCaps::READ) {
|
||||
opts.read(true);
|
||||
}
|
||||
if fdflags.contains(FdFlags::APPEND) {
|
||||
opts.append(true);
|
||||
}
|
||||
// XXX what about rest of fdflags - dsync, sync become oflags.
|
||||
// what do we do with nonblock?
|
||||
// what do we do with rsync?
|
||||
|
||||
if symlink_follow {
|
||||
opts.follow(FollowSymlinks::Yes);
|
||||
} else {
|
||||
opts.follow(FollowSymlinks::No);
|
||||
}
|
||||
|
||||
let f = self.0.open_with(Path::new(path), &opts)?;
|
||||
Ok(Box::new(File::from_cap_std(f)))
|
||||
}
|
||||
|
||||
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error> {
|
||||
let d = if symlink_follow {
|
||||
self.0.open_dir(Path::new(path))?
|
||||
} else {
|
||||
self.0.open_dir_nofollow(Path::new(path))?
|
||||
};
|
||||
Ok(Box::new(Dir::from_cap_std(d)))
|
||||
}
|
||||
|
||||
fn create_dir(&self, path: &str) -> Result<(), Error> {
|
||||
self.0.create_dir(Path::new(path))?;
|
||||
Ok(())
|
||||
}
|
||||
fn readdir(
|
||||
&self,
|
||||
cursor: ReaddirCursor,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(ReaddirEntity, String), Error>>>, Error> {
|
||||
// cap_std's read_dir does not include . and .., we should prepend these.
|
||||
// Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't
|
||||
// have enough info to make a ReaddirEntity yet.
|
||||
let dir_meta = self.0.dir_metadata()?;
|
||||
let rd = vec![
|
||||
{
|
||||
let name = ".".to_owned();
|
||||
let namelen = name.as_bytes().len().try_into().expect("1 wont overflow");
|
||||
Ok((FileType::Directory, dir_meta.ino(), namelen, name))
|
||||
},
|
||||
{
|
||||
// XXX if parent dir is mounted it *might* be possible to give its inode, but we
|
||||
// don't know that in this context.
|
||||
let name = "..".to_owned();
|
||||
let namelen = name.as_bytes().len().try_into().expect("2 wont overflow");
|
||||
Ok((FileType::Directory, dir_meta.ino(), namelen, name))
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
// Now process the `DirEntry`s:
|
||||
self.0.entries()?.map(|entry| {
|
||||
let entry = entry?;
|
||||
let meta = entry.metadata()?;
|
||||
let inode = meta.ino();
|
||||
let filetype = filetype_from(&meta.file_type());
|
||||
let name = entry
|
||||
.file_name()
|
||||
.into_string()
|
||||
.map_err(|_| Error::illegal_byte_sequence().context("filename"))?;
|
||||
let namelen = name.as_bytes().len().try_into()?;
|
||||
Ok((filetype, inode, namelen, name))
|
||||
}),
|
||||
)
|
||||
// Enumeration of the iterator makes it possible to define the ReaddirCursor
|
||||
.enumerate()
|
||||
.map(|(ix, r)| match r {
|
||||
Ok((filetype, inode, namelen, name)) => Ok((
|
||||
ReaddirEntity {
|
||||
next: ReaddirCursor::from(ix as u64 + 1),
|
||||
filetype,
|
||||
inode,
|
||||
namelen,
|
||||
},
|
||||
name,
|
||||
)),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.skip(u64::from(cursor) as usize);
|
||||
|
||||
Ok(Box::new(rd))
|
||||
}
|
||||
|
||||
fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> {
|
||||
self.0.symlink(src_path, dest_path)?;
|
||||
Ok(())
|
||||
}
|
||||
fn remove_dir(&self, path: &str) -> Result<(), Error> {
|
||||
self.0.remove_dir(Path::new(path))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlink_file(&self, path: &str) -> Result<(), Error> {
|
||||
self.0.remove_file(Path::new(path))?;
|
||||
Ok(())
|
||||
}
|
||||
fn read_link(&self, path: &str) -> Result<PathBuf, Error> {
|
||||
let link = self.0.read_link(Path::new(path))?;
|
||||
Ok(link)
|
||||
}
|
||||
fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
let meta = self.0.dir_metadata()?;
|
||||
Ok(Filestat {
|
||||
device_id: meta.dev(),
|
||||
inode: meta.ino(),
|
||||
filetype: filetype_from(&meta.file_type()),
|
||||
nlink: meta.nlink(),
|
||||
size: meta.len(),
|
||||
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
})
|
||||
}
|
||||
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error> {
|
||||
let meta = if follow_symlinks {
|
||||
self.0.metadata(Path::new(path))?
|
||||
} else {
|
||||
self.0.symlink_metadata(Path::new(path))?
|
||||
};
|
||||
Ok(Filestat {
|
||||
device_id: meta.dev(),
|
||||
inode: meta.ino(),
|
||||
filetype: filetype_from(&meta.file_type()),
|
||||
nlink: meta.nlink(),
|
||||
size: meta.len(),
|
||||
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
})
|
||||
}
|
||||
fn rename(&self, src_path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error> {
|
||||
let dest_dir = dest_dir
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
|
||||
self.0
|
||||
.rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?;
|
||||
Ok(())
|
||||
}
|
||||
fn hard_link(
|
||||
&self,
|
||||
src_path: &str,
|
||||
target_dir: &dyn WasiDir,
|
||||
target_path: &str,
|
||||
) -> Result<(), Error> {
|
||||
let target_dir = target_dir
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
|
||||
let src_path = Path::new(src_path);
|
||||
let target_path = Path::new(target_path);
|
||||
self.0.hard_link(src_path, &target_dir.0, target_path)?;
|
||||
Ok(())
|
||||
}
|
||||
fn set_times(
|
||||
&self,
|
||||
path: &str,
|
||||
atime: Option<wasi_c2::SystemTimeSpec>,
|
||||
mtime: Option<wasi_c2::SystemTimeSpec>,
|
||||
follow_symlinks: bool,
|
||||
) -> Result<(), Error> {
|
||||
if follow_symlinks {
|
||||
self.0.set_times(
|
||||
Path::new(path),
|
||||
convert_systimespec(atime),
|
||||
convert_systimespec(mtime),
|
||||
)?;
|
||||
} else {
|
||||
self.0.set_symlink_times(
|
||||
Path::new(path),
|
||||
convert_systimespec(atime),
|
||||
convert_systimespec(mtime),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_systimespec(t: Option<wasi_c2::SystemTimeSpec>) -> Option<SystemTimeSpec> {
|
||||
match t {
|
||||
Some(wasi_c2::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)),
|
||||
Some(wasi_c2::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
193
crates/wasi-common/cap-std-sync/src/file.rs
Normal file
193
crates/wasi-common/cap-std-sync/src/file.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
use cap_fs_ext::MetadataExt;
|
||||
use fs_set_times::{SetTimes, SystemTimeSpec};
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use system_interface::{
|
||||
fs::{Advice, FileIoExt, GetSetFdFlags},
|
||||
io::ReadReady,
|
||||
};
|
||||
use wasi_c2::{
|
||||
file::{FdFlags, FileType, Filestat, WasiFile},
|
||||
Error,
|
||||
};
|
||||
|
||||
pub struct File(cap_std::fs::File);
|
||||
|
||||
impl File {
|
||||
pub fn from_cap_std(file: cap_std::fs::File) -> Self {
|
||||
File(file)
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiFile for File {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn datasync(&self) -> Result<(), Error> {
|
||||
self.0.sync_data()?;
|
||||
Ok(())
|
||||
}
|
||||
fn sync(&self) -> Result<(), Error> {
|
||||
self.0.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
let meta = self.0.metadata()?;
|
||||
Ok(filetype_from(&meta.file_type()))
|
||||
}
|
||||
fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
let fdflags = self.0.get_fd_flags()?;
|
||||
Ok(from_sysif_fdflags(fdflags))
|
||||
}
|
||||
fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
|
||||
Ok(self.0.set_fd_flags(to_sysif_fdflags(fdflags))?)
|
||||
}
|
||||
fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
let meta = self.0.metadata()?;
|
||||
Ok(Filestat {
|
||||
device_id: meta.dev(),
|
||||
inode: meta.ino(),
|
||||
filetype: filetype_from(&meta.file_type()),
|
||||
nlink: meta.nlink(),
|
||||
size: meta.len(),
|
||||
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
|
||||
})
|
||||
}
|
||||
fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
|
||||
self.0.set_len(size)?;
|
||||
Ok(())
|
||||
}
|
||||
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
|
||||
self.0.advise(offset, len, advice)?;
|
||||
Ok(())
|
||||
}
|
||||
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
|
||||
self.0.allocate(offset, len)?;
|
||||
Ok(())
|
||||
}
|
||||
fn set_times(
|
||||
&self,
|
||||
atime: Option<wasi_c2::SystemTimeSpec>,
|
||||
mtime: Option<wasi_c2::SystemTimeSpec>,
|
||||
) -> Result<(), Error> {
|
||||
self.0
|
||||
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
|
||||
Ok(())
|
||||
}
|
||||
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
|
||||
let n = self.0.read_vectored(bufs)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
|
||||
let n = self.0.read_vectored_at(bufs, offset)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
|
||||
let n = self.0.write_vectored(bufs)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
|
||||
let n = self.0.write_vectored_at(bufs, offset)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
Ok(self.0.seek(pos)?)
|
||||
}
|
||||
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
|
||||
let n = self.0.peek(buf)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
Ok(self.0.num_ready_bytes()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType {
|
||||
use cap_fs_ext::FileTypeExt;
|
||||
if ft.is_dir() {
|
||||
FileType::Directory
|
||||
} else if ft.is_symlink() {
|
||||
FileType::SymbolicLink
|
||||
} else if ft.is_socket() {
|
||||
if ft.is_block_device() {
|
||||
FileType::SocketDgram
|
||||
} else {
|
||||
FileType::SocketStream
|
||||
}
|
||||
} else if ft.is_block_device() {
|
||||
FileType::BlockDevice
|
||||
} else if ft.is_char_device() {
|
||||
FileType::CharacterDevice
|
||||
} else if ft.is_file() {
|
||||
FileType::RegularFile
|
||||
} else {
|
||||
FileType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
#[cfg(windows)]
|
||||
impl AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.0.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
#[cfg(unix)]
|
||||
impl AsRawFd for File {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
pub fn convert_systimespec(t: Option<wasi_c2::SystemTimeSpec>) -> Option<SystemTimeSpec> {
|
||||
match t {
|
||||
Some(wasi_c2::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())),
|
||||
Some(wasi_c2::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_sysif_fdflags(f: wasi_c2::file::FdFlags) -> system_interface::fs::FdFlags {
|
||||
let mut out = system_interface::fs::FdFlags::empty();
|
||||
if f.contains(wasi_c2::file::FdFlags::APPEND) {
|
||||
out |= system_interface::fs::FdFlags::APPEND;
|
||||
}
|
||||
if f.contains(wasi_c2::file::FdFlags::DSYNC) {
|
||||
out |= system_interface::fs::FdFlags::DSYNC;
|
||||
}
|
||||
if f.contains(wasi_c2::file::FdFlags::NONBLOCK) {
|
||||
out |= system_interface::fs::FdFlags::NONBLOCK;
|
||||
}
|
||||
if f.contains(wasi_c2::file::FdFlags::RSYNC) {
|
||||
out |= system_interface::fs::FdFlags::RSYNC;
|
||||
}
|
||||
if f.contains(wasi_c2::file::FdFlags::SYNC) {
|
||||
out |= system_interface::fs::FdFlags::SYNC;
|
||||
}
|
||||
out
|
||||
}
|
||||
pub fn from_sysif_fdflags(f: system_interface::fs::FdFlags) -> wasi_c2::file::FdFlags {
|
||||
let mut out = wasi_c2::file::FdFlags::empty();
|
||||
if f.contains(system_interface::fs::FdFlags::APPEND) {
|
||||
out |= wasi_c2::file::FdFlags::APPEND;
|
||||
}
|
||||
if f.contains(system_interface::fs::FdFlags::DSYNC) {
|
||||
out |= wasi_c2::file::FdFlags::DSYNC;
|
||||
}
|
||||
if f.contains(system_interface::fs::FdFlags::NONBLOCK) {
|
||||
out |= wasi_c2::file::FdFlags::NONBLOCK;
|
||||
}
|
||||
if f.contains(system_interface::fs::FdFlags::RSYNC) {
|
||||
out |= wasi_c2::file::FdFlags::RSYNC;
|
||||
}
|
||||
if f.contains(system_interface::fs::FdFlags::SYNC) {
|
||||
out |= wasi_c2::file::FdFlags::SYNC;
|
||||
}
|
||||
out
|
||||
}
|
||||
61
crates/wasi-common/cap-std-sync/src/lib.rs
Normal file
61
crates/wasi-common/cap-std-sync/src/lib.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
pub mod clocks;
|
||||
pub mod dir;
|
||||
pub mod file;
|
||||
pub mod sched;
|
||||
pub mod stdio;
|
||||
|
||||
use cap_rand::RngCore;
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use wasi_c2::{table::Table, Error, WasiCtx, WasiFile};
|
||||
|
||||
pub struct WasiCtxBuilder(wasi_c2::WasiCtxBuilder);
|
||||
|
||||
impl WasiCtxBuilder {
|
||||
pub fn new() -> Self {
|
||||
WasiCtxBuilder(WasiCtx::builder(
|
||||
random(),
|
||||
clocks::clocks(),
|
||||
Box::new(sched::SyncSched::new()),
|
||||
Rc::new(RefCell::new(Table::new())),
|
||||
))
|
||||
}
|
||||
pub fn env(self, var: &str, value: &str) -> Result<Self, wasi_c2::StringArrayError> {
|
||||
let s = self.0.env(var, value)?;
|
||||
Ok(WasiCtxBuilder(s))
|
||||
}
|
||||
pub fn arg(self, arg: &str) -> Result<Self, wasi_c2::StringArrayError> {
|
||||
let s = self.0.arg(arg)?;
|
||||
Ok(WasiCtxBuilder(s))
|
||||
}
|
||||
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
|
||||
WasiCtxBuilder(self.0.stdin(f))
|
||||
}
|
||||
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
|
||||
WasiCtxBuilder(self.0.stdout(f))
|
||||
}
|
||||
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
|
||||
WasiCtxBuilder(self.0.stderr(f))
|
||||
}
|
||||
pub fn inherit_stdio(self) -> Self {
|
||||
self.stdin(Box::new(crate::stdio::stdin()))
|
||||
.stdout(Box::new(crate::stdio::stdout()))
|
||||
.stderr(Box::new(crate::stdio::stderr()))
|
||||
}
|
||||
pub fn preopened_dir(
|
||||
self,
|
||||
dir: cap_std::fs::Dir,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<Self, Error> {
|
||||
let dir = Box::new(crate::dir::Dir::from_cap_std(dir));
|
||||
Ok(WasiCtxBuilder(self.0.preopened_dir(dir, path)?))
|
||||
}
|
||||
pub fn build(self) -> Result<WasiCtx, Error> {
|
||||
self.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random() -> RefCell<Box<dyn RngCore>> {
|
||||
RefCell::new(Box::new(unsafe { cap_rand::rngs::OsRng::default() }))
|
||||
}
|
||||
9
crates/wasi-common/cap-std-sync/src/sched.rs
Normal file
9
crates/wasi-common/cap-std-sync/src/sched.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(unix)]
|
||||
pub use unix::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
#[cfg(windows)]
|
||||
pub use windows::*;
|
||||
190
crates/wasi-common/cap-std-sync/src/sched/unix.rs
Normal file
190
crates/wasi-common/cap-std-sync/src/sched/unix.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
use cap_std::time::Duration;
|
||||
use std::convert::TryInto;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use wasi_c2::{
|
||||
file::WasiFile,
|
||||
sched::{
|
||||
subscription::{RwEventFlags, Subscription},
|
||||
Poll, WasiSched,
|
||||
},
|
||||
Error, ErrorExt,
|
||||
};
|
||||
|
||||
use poll::{PollFd, PollFlags};
|
||||
|
||||
pub struct SyncSched;
|
||||
|
||||
impl SyncSched {
|
||||
pub fn new() -> Self {
|
||||
SyncSched
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiSched for SyncSched {
|
||||
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
|
||||
if poll.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut pollfds = Vec::new();
|
||||
let timeout = poll.earliest_clock_deadline();
|
||||
for s in poll.rw_subscriptions() {
|
||||
match s {
|
||||
Subscription::Read(f) => {
|
||||
let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or(
|
||||
Error::invalid_argument().context("read subscription fd downcast failed"),
|
||||
)?;
|
||||
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) });
|
||||
}
|
||||
|
||||
Subscription::Write(f) => {
|
||||
let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or(
|
||||
Error::invalid_argument().context("write subscription fd downcast failed"),
|
||||
)?;
|
||||
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) });
|
||||
}
|
||||
Subscription::MonotonicClock { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let ready = loop {
|
||||
let poll_timeout = if let Some(t) = timeout {
|
||||
let duration = t.duration_until().unwrap_or(Duration::from_secs(0));
|
||||
(duration.as_millis() + 1) // XXX try always rounding up?
|
||||
.try_into()
|
||||
.map_err(|_| Error::overflow().context("poll timeout"))?
|
||||
} else {
|
||||
libc::c_int::max_value()
|
||||
};
|
||||
tracing::debug!(
|
||||
poll_timeout = tracing::field::debug(poll_timeout),
|
||||
poll_fds = tracing::field::debug(&pollfds),
|
||||
"poll"
|
||||
);
|
||||
match poll::poll(&mut pollfds, poll_timeout) {
|
||||
Ok(ready) => break ready,
|
||||
Err(_) => {
|
||||
let last_err = std::io::Error::last_os_error();
|
||||
if last_err.raw_os_error().unwrap() == libc::EINTR {
|
||||
continue;
|
||||
} else {
|
||||
return Err(last_err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if ready > 0 {
|
||||
for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) {
|
||||
if let Some(revents) = pollfd.revents() {
|
||||
let (nbytes, rwsub) = match rwsub {
|
||||
Subscription::Read(sub) => {
|
||||
let ready = sub.file.num_ready_bytes()?;
|
||||
(std::cmp::max(ready, 1), sub)
|
||||
}
|
||||
Subscription::Write(sub) => (0, sub),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if revents.contains(PollFlags::POLLNVAL) {
|
||||
rwsub.error(Error::badf());
|
||||
} else if revents.contains(PollFlags::POLLERR) {
|
||||
rwsub.error(Error::io());
|
||||
} else if revents.contains(PollFlags::POLLHUP) {
|
||||
rwsub.complete(nbytes, RwEventFlags::HANGUP);
|
||||
} else {
|
||||
rwsub.complete(nbytes, RwEventFlags::empty());
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
timeout
|
||||
.expect("timed out")
|
||||
.result()
|
||||
.expect("timer deadline is past")
|
||||
.unwrap()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn sched_yield(&self) -> Result<(), Error> {
|
||||
std::thread::yield_now();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option<RawFd> {
|
||||
let a = f.as_any();
|
||||
if a.is::<crate::file::File>() {
|
||||
Some(a.downcast_ref::<crate::file::File>().unwrap().as_raw_fd())
|
||||
} else if a.is::<crate::stdio::Stdin>() {
|
||||
Some(a.downcast_ref::<crate::stdio::Stdin>().unwrap().as_raw_fd())
|
||||
} else if a.is::<crate::stdio::Stdout>() {
|
||||
Some(
|
||||
a.downcast_ref::<crate::stdio::Stdout>()
|
||||
.unwrap()
|
||||
.as_raw_fd(),
|
||||
)
|
||||
} else if a.is::<crate::stdio::Stderr>() {
|
||||
Some(
|
||||
a.downcast_ref::<crate::stdio::Stderr>()
|
||||
.unwrap()
|
||||
.as_raw_fd(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
mod poll {
|
||||
use bitflags::bitflags;
|
||||
use std::convert::TryInto;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
bitflags! {
|
||||
pub struct PollFlags: libc::c_short {
|
||||
const POLLIN = libc::POLLIN;
|
||||
const POLLPRI = libc::POLLPRI;
|
||||
const POLLOUT = libc::POLLOUT;
|
||||
const POLLRDNORM = libc::POLLRDNORM;
|
||||
const POLLWRNORM = libc::POLLWRNORM;
|
||||
const POLLRDBAND = libc::POLLRDBAND;
|
||||
const POLLWRBAND = libc::POLLWRBAND;
|
||||
const POLLERR = libc::POLLERR;
|
||||
const POLLHUP = libc::POLLHUP;
|
||||
const POLLNVAL = libc::POLLNVAL;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct PollFd(libc::pollfd);
|
||||
|
||||
impl PollFd {
|
||||
pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self {
|
||||
Self(libc::pollfd {
|
||||
fd,
|
||||
events: events.bits(),
|
||||
revents: PollFlags::empty().bits(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn revents(self) -> Option<PollFlags> {
|
||||
PollFlags::from_bits(self.0.revents)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<usize, std::io::Error> {
|
||||
let nready = unsafe {
|
||||
libc::poll(
|
||||
fds.as_mut_ptr() as *mut libc::pollfd,
|
||||
fds.len() as libc::nfds_t,
|
||||
timeout,
|
||||
)
|
||||
};
|
||||
if nready == -1 {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
// When poll doesn't fail, its return value is a non-negative int, which will
|
||||
// always be convertable to usize, so we can unwrap() here.
|
||||
Ok(nready.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
256
crates/wasi-common/cap-std-sync/src/sched/windows.rs
Normal file
256
crates/wasi-common/cap-std-sync/src/sched/windows.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
use anyhow::Context;
|
||||
use std::ops::Deref;
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use wasi_c2::{
|
||||
file::WasiFile,
|
||||
sched::{
|
||||
subscription::{RwEventFlags, Subscription},
|
||||
Poll, WasiSched,
|
||||
},
|
||||
Error, ErrorExt,
|
||||
};
|
||||
pub struct SyncSched {}
|
||||
|
||||
impl SyncSched {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiSched for SyncSched {
|
||||
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
|
||||
if poll.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut ready = false;
|
||||
let timeout = poll.earliest_clock_deadline();
|
||||
|
||||
let mut stdin_read_subs = Vec::new();
|
||||
let mut immediate_subs = Vec::new();
|
||||
for s in poll.rw_subscriptions() {
|
||||
match s {
|
||||
Subscription::Read(r) if r.file.as_any().is::<crate::stdio::Stdin>() => {
|
||||
stdin_read_subs.push(r);
|
||||
}
|
||||
Subscription::Read(rw) | Subscription::Write(rw) => {
|
||||
if wasi_file_raw_handle(rw.file.deref()).is_some() {
|
||||
immediate_subs.push(s);
|
||||
} else {
|
||||
return Err(Error::invalid_argument()
|
||||
.context("read/write subscription fd downcast failed"));
|
||||
}
|
||||
}
|
||||
Subscription::MonotonicClock { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if !stdin_read_subs.is_empty() {
|
||||
let waitmode = if let Some(t) = timeout {
|
||||
if let Some(duration) = t.duration_until() {
|
||||
WaitMode::Timeout(duration)
|
||||
} else {
|
||||
WaitMode::Immediate
|
||||
}
|
||||
} else {
|
||||
if ready {
|
||||
WaitMode::Immediate
|
||||
} else {
|
||||
WaitMode::Infinite
|
||||
}
|
||||
};
|
||||
let state = STDIN_POLL
|
||||
.lock()
|
||||
.map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))?
|
||||
.poll(waitmode)?;
|
||||
for readsub in stdin_read_subs.into_iter() {
|
||||
match state {
|
||||
PollState::Ready => {
|
||||
readsub.complete(1, RwEventFlags::empty());
|
||||
ready = true;
|
||||
}
|
||||
PollState::NotReady | PollState::TimedOut => {}
|
||||
PollState::Error(ref e) => {
|
||||
// Unfortunately, we need to deliver the Error to each of the
|
||||
// subscriptions, but there is no Clone on std::io::Error. So, we convert it to the
|
||||
// kind, and then back to std::io::Error, and finally to anyhow::Error.
|
||||
// When its time to turn this into an errno elsewhere, the error kind will
|
||||
// be inspected.
|
||||
let ekind = e.kind();
|
||||
let ioerror = std::io::Error::from(ekind);
|
||||
readsub.error(ioerror.into());
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for sub in immediate_subs {
|
||||
match sub {
|
||||
Subscription::Read(r) => {
|
||||
// XXX This doesnt strictly preserve the behavior in the earlier
|
||||
// implementation, which would always do complete(0) for reads from
|
||||
// stdout/err.
|
||||
match r.file.num_ready_bytes() {
|
||||
Ok(ready_bytes) => {
|
||||
r.complete(ready_bytes, RwEventFlags::empty());
|
||||
ready = true;
|
||||
}
|
||||
Err(e) => {
|
||||
r.error(e);
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Subscription::Write(w) => {
|
||||
// Everything is always ready for writing, apparently?
|
||||
w.complete(0, RwEventFlags::empty());
|
||||
ready = true;
|
||||
}
|
||||
Subscription::MonotonicClock { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if !ready {
|
||||
if let Some(t) = timeout {
|
||||
if let Some(duration) = t.duration_until() {
|
||||
thread::sleep(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn sched_yield(&self) -> Result<(), Error> {
|
||||
thread::yield_now();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
|
||||
let a = f.as_any();
|
||||
if a.is::<crate::file::File>() {
|
||||
Some(
|
||||
a.downcast_ref::<crate::file::File>()
|
||||
.unwrap()
|
||||
.as_raw_handle(),
|
||||
)
|
||||
} else if a.is::<crate::stdio::Stdin>() {
|
||||
Some(
|
||||
a.downcast_ref::<crate::stdio::Stdin>()
|
||||
.unwrap()
|
||||
.as_raw_handle(),
|
||||
)
|
||||
} else if a.is::<crate::stdio::Stdout>() {
|
||||
Some(
|
||||
a.downcast_ref::<crate::stdio::Stdout>()
|
||||
.unwrap()
|
||||
.as_raw_handle(),
|
||||
)
|
||||
} else if a.is::<crate::stdio::Stderr>() {
|
||||
Some(
|
||||
a.downcast_ref::<crate::stdio::Stderr>()
|
||||
.unwrap()
|
||||
.as_raw_handle(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
enum PollState {
|
||||
Ready,
|
||||
NotReady, // Not ready, but did not wait
|
||||
TimedOut, // Not ready, waited until timeout
|
||||
Error(std::io::Error),
|
||||
}
|
||||
|
||||
enum WaitMode {
|
||||
Timeout(Duration),
|
||||
Infinite,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
struct StdinPoll {
|
||||
request_tx: Sender<()>,
|
||||
notify_rx: Receiver<PollState>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref STDIN_POLL: Mutex<StdinPoll> = StdinPoll::new();
|
||||
}
|
||||
|
||||
impl StdinPoll {
|
||||
pub fn new() -> Mutex<Self> {
|
||||
let (request_tx, request_rx) = mpsc::channel();
|
||||
let (notify_tx, notify_rx) = mpsc::channel();
|
||||
thread::spawn(move || Self::event_loop(request_rx, notify_tx));
|
||||
Mutex::new(StdinPoll {
|
||||
request_tx,
|
||||
notify_rx,
|
||||
})
|
||||
}
|
||||
|
||||
// This function should not be used directly.
|
||||
// Correctness of this function crucially depends on the fact that
|
||||
// mpsc::Receiver is !Sync.
|
||||
fn poll(&self, wait_mode: WaitMode) -> Result<PollState, Error> {
|
||||
match self.notify_rx.try_recv() {
|
||||
// Clean up possibly unread result from previous poll.
|
||||
Ok(_) | Err(TryRecvError::Empty) => {}
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
return Err(Error::trap("StdinPoll notify_rx channel closed"))
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the worker thread to poll stdin
|
||||
self.request_tx
|
||||
.send(())
|
||||
.context("request_tx channel closed")?;
|
||||
|
||||
// Wait for the worker thread to send a readiness notification
|
||||
match wait_mode {
|
||||
WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) {
|
||||
Ok(r) => Ok(r),
|
||||
Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut),
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
Err(Error::trap("StdinPoll notify_rx channel closed"))
|
||||
}
|
||||
},
|
||||
WaitMode::Infinite => self
|
||||
.notify_rx
|
||||
.recv()
|
||||
.context("StdinPoll notify_rx channel closed"),
|
||||
WaitMode::Immediate => match self.notify_rx.try_recv() {
|
||||
Ok(r) => Ok(r),
|
||||
Err(TryRecvError::Empty) => Ok(PollState::NotReady),
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
Err(Error::trap("StdinPoll notify_rx channel closed"))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {
|
||||
use std::io::BufRead;
|
||||
loop {
|
||||
// Wait on a request:
|
||||
request_rx.recv().expect("request_rx channel");
|
||||
// Wait for data to appear in stdin. If fill_buf returns any slice, it means
|
||||
// that either:
|
||||
// (a) there is some data in stdin, if non-empty,
|
||||
// (b) EOF was recieved, if its empty
|
||||
// Linux returns `POLLIN` in both cases, so we imitate this behavior.
|
||||
let resp = match std::io::stdin().lock().fill_buf() {
|
||||
Ok(_) => PollState::Ready,
|
||||
Err(e) => PollState::Error(e),
|
||||
};
|
||||
// Notify about data in stdin. If the read on this channel has timed out, the
|
||||
// next poller will have to clean the channel.
|
||||
notify_tx.send(resp).expect("notify_tx channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
217
crates/wasi-common/cap-std-sync/src/stdio.rs
Normal file
217
crates/wasi-common/cap-std-sync/src/stdio.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use crate::file::convert_systimespec;
|
||||
use fs_set_times::SetTimes;
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use system_interface::{fs::Advice, io::ReadReady};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
use unsafe_io::AsUnsafeFile;
|
||||
use wasi_c2::{
|
||||
file::{FdFlags, FileType, Filestat, WasiFile},
|
||||
Error, ErrorExt,
|
||||
};
|
||||
|
||||
pub struct Stdin(std::io::Stdin);
|
||||
|
||||
pub fn stdin() -> Stdin {
|
||||
Stdin(std::io::stdin())
|
||||
}
|
||||
|
||||
impl WasiFile for Stdin {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn datasync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn sync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
Ok(FileType::Unknown)
|
||||
}
|
||||
fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
Ok(FdFlags::empty())
|
||||
}
|
||||
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
let meta = self.0.as_file_view().metadata()?;
|
||||
Ok(Filestat {
|
||||
device_id: 0,
|
||||
inode: 0,
|
||||
filetype: self.get_filetype()?,
|
||||
nlink: 0,
|
||||
size: meta.len(),
|
||||
atim: meta.accessed().ok(),
|
||||
mtim: meta.modified().ok(),
|
||||
ctim: meta.created().ok(),
|
||||
})
|
||||
}
|
||||
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
|
||||
let n = self.0.as_file_view().read_vectored(bufs)?;
|
||||
Ok(n.try_into().map_err(|_| Error::range())?)
|
||||
}
|
||||
fn read_vectored_at(&self, _bufs: &mut [io::IoSliceMut], _offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::seek_pipe())
|
||||
}
|
||||
fn write_vectored(&self, _bufs: &[io::IoSlice]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
Err(Error::seek_pipe())
|
||||
}
|
||||
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
|
||||
Err(Error::seek_pipe())
|
||||
}
|
||||
fn set_times(
|
||||
&self,
|
||||
atime: Option<wasi_c2::SystemTimeSpec>,
|
||||
mtime: Option<wasi_c2::SystemTimeSpec>,
|
||||
) -> Result<(), Error> {
|
||||
self.0
|
||||
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
|
||||
Ok(())
|
||||
}
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
Ok(self.0.num_ready_bytes()?)
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
impl AsRawHandle for Stdin {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.0.as_raw_handle()
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
impl AsRawFd for Stdin {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! wasi_file_write_impl {
|
||||
($ty:ty) => {
|
||||
impl WasiFile for $ty {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn datasync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn sync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
Ok(FileType::Unknown)
|
||||
}
|
||||
fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
Ok(FdFlags::APPEND)
|
||||
}
|
||||
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
let meta = self.0.as_file_view().metadata()?;
|
||||
Ok(Filestat {
|
||||
device_id: 0,
|
||||
inode: 0,
|
||||
filetype: self.get_filetype()?,
|
||||
nlink: 0,
|
||||
size: meta.len(),
|
||||
atim: meta.accessed().ok(),
|
||||
mtim: meta.modified().ok(),
|
||||
ctim: meta.created().ok(),
|
||||
})
|
||||
}
|
||||
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn read_vectored(&self, _bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn read_vectored_at(
|
||||
&self,
|
||||
_bufs: &mut [io::IoSliceMut],
|
||||
_offset: u64,
|
||||
) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
|
||||
let n = self.0.as_file_view().write_vectored(bufs)?;
|
||||
Ok(n.try_into().map_err(|c| Error::range().context(c))?)
|
||||
}
|
||||
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::seek_pipe())
|
||||
}
|
||||
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
Err(Error::seek_pipe())
|
||||
}
|
||||
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn set_times(
|
||||
&self,
|
||||
atime: Option<wasi_c2::SystemTimeSpec>,
|
||||
mtime: Option<wasi_c2::SystemTimeSpec>,
|
||||
) -> Result<(), Error> {
|
||||
self.0
|
||||
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
|
||||
Ok(())
|
||||
}
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
impl AsRawHandle for $ty {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.0.as_raw_handle()
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
impl AsRawFd for $ty {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Stdout(std::io::Stdout);
|
||||
|
||||
pub fn stdout() -> Stdout {
|
||||
Stdout(std::io::stdout())
|
||||
}
|
||||
wasi_file_write_impl!(Stdout);
|
||||
|
||||
pub struct Stderr(std::io::Stderr);
|
||||
|
||||
pub fn stderr() -> Stderr {
|
||||
Stderr(std::io::stderr())
|
||||
}
|
||||
wasi_file_write_impl!(Stderr);
|
||||
22
crates/wasi-common/src/clocks.rs
Normal file
22
crates/wasi-common/src/clocks.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use cap_std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
pub enum SystemTimeSpec {
|
||||
SymbolicNow,
|
||||
Absolute(SystemTime),
|
||||
}
|
||||
|
||||
pub trait WasiSystemClock {
|
||||
fn resolution(&self) -> Duration;
|
||||
fn now(&self, precision: Duration) -> SystemTime;
|
||||
}
|
||||
|
||||
pub trait WasiMonotonicClock {
|
||||
fn resolution(&self) -> Duration;
|
||||
fn now(&self, precision: Duration) -> Instant;
|
||||
}
|
||||
|
||||
pub struct WasiClocks {
|
||||
pub system: Box<dyn WasiSystemClock>,
|
||||
pub monotonic: Box<dyn WasiMonotonicClock>,
|
||||
pub creation_time: cap_std::time::Instant,
|
||||
}
|
||||
122
crates/wasi-common/src/ctx.rs
Normal file
122
crates/wasi-common/src/ctx.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
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::table::Table;
|
||||
use crate::Error;
|
||||
use cap_rand::RngCore;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct WasiCtx {
|
||||
pub args: StringArray,
|
||||
pub env: StringArray,
|
||||
pub random: RefCell<Box<dyn RngCore>>,
|
||||
pub clocks: WasiClocks,
|
||||
pub sched: Box<dyn WasiSched>,
|
||||
pub table: Rc<RefCell<Table>>,
|
||||
}
|
||||
|
||||
impl WasiCtx {
|
||||
pub fn builder(
|
||||
random: RefCell<Box<dyn RngCore>>,
|
||||
clocks: WasiClocks,
|
||||
sched: Box<dyn WasiSched>,
|
||||
table: Rc<RefCell<Table>>,
|
||||
) -> WasiCtxBuilder {
|
||||
WasiCtxBuilder(WasiCtx {
|
||||
args: StringArray::new(),
|
||||
env: StringArray::new(),
|
||||
random,
|
||||
clocks,
|
||||
sched,
|
||||
table,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_file(&self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
|
||||
self.table()
|
||||
.insert_at(fd, Box::new(FileEntry::new(caps, file)));
|
||||
}
|
||||
|
||||
pub fn insert_dir(
|
||||
&self,
|
||||
fd: u32,
|
||||
dir: Box<dyn WasiDir>,
|
||||
caps: DirCaps,
|
||||
file_caps: FileCaps,
|
||||
path: PathBuf,
|
||||
) {
|
||||
self.table().insert_at(
|
||||
fd,
|
||||
Box::new(DirEntry::new(caps, file_caps, Some(path), dir)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn table(&self) -> RefMut<Table> {
|
||||
self.table.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WasiCtxBuilder(WasiCtx);
|
||||
|
||||
impl WasiCtxBuilder {
|
||||
pub fn build(self) -> Result<WasiCtx, Error> {
|
||||
Ok(self.0)
|
||||
}
|
||||
|
||||
pub fn arg(mut self, arg: &str) -> Result<Self, StringArrayError> {
|
||||
self.0.args.push(arg.to_owned())?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn env(mut self, var: &str, value: &str) -> Result<Self, StringArrayError> {
|
||||
self.0.env.push(format!("{}={}", var, value))?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
|
||||
self.0.insert_file(
|
||||
0,
|
||||
f,
|
||||
FileCaps::READ | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is read-only
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
|
||||
self.0.insert_file(
|
||||
1,
|
||||
f,
|
||||
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
|
||||
self.0.insert_file(
|
||||
2,
|
||||
f,
|
||||
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn preopened_dir(
|
||||
self,
|
||||
dir: Box<dyn WasiDir>,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<Self, Error> {
|
||||
let caps = DirCaps::all();
|
||||
let file_caps = FileCaps::all();
|
||||
self.0.table().push(Box::new(DirEntry::new(
|
||||
caps,
|
||||
file_caps,
|
||||
Some(path.as_ref().to_owned()),
|
||||
dir,
|
||||
)))?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
182
crates/wasi-common/src/dir.rs
Normal file
182
crates/wasi-common/src/dir.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use crate::file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile};
|
||||
use crate::{Error, ErrorExt, SystemTimeSpec};
|
||||
use bitflags::bitflags;
|
||||
use std::any::Any;
|
||||
use std::cell::Ref;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait WasiDir {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn open_file(
|
||||
&self,
|
||||
symlink_follow: bool,
|
||||
path: &str,
|
||||
oflags: OFlags,
|
||||
caps: FileCaps,
|
||||
fdflags: FdFlags,
|
||||
) -> Result<Box<dyn WasiFile>, Error>;
|
||||
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error>;
|
||||
fn create_dir(&self, path: &str) -> Result<(), Error>;
|
||||
fn readdir(
|
||||
&self,
|
||||
cursor: ReaddirCursor,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(ReaddirEntity, String), Error>>>, Error>;
|
||||
fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>;
|
||||
fn remove_dir(&self, path: &str) -> Result<(), Error>;
|
||||
fn unlink_file(&self, path: &str) -> Result<(), Error>;
|
||||
fn read_link(&self, path: &str) -> Result<PathBuf, Error>;
|
||||
fn get_filestat(&self) -> Result<Filestat, Error>;
|
||||
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error>;
|
||||
fn rename(&self, path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error>;
|
||||
fn hard_link(
|
||||
&self,
|
||||
path: &str,
|
||||
target_dir: &dyn WasiDir,
|
||||
target_path: &str,
|
||||
) -> Result<(), Error>;
|
||||
fn set_times(
|
||||
&self,
|
||||
path: &str,
|
||||
atime: Option<SystemTimeSpec>,
|
||||
mtime: Option<SystemTimeSpec>,
|
||||
follow_symlinks: bool,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub(crate) struct DirEntry {
|
||||
caps: DirCaps,
|
||||
file_caps: FileCaps,
|
||||
preopen_path: Option<PathBuf>, // precondition: PathBuf is valid unicode
|
||||
dir: Box<dyn WasiDir>,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn new(
|
||||
caps: DirCaps,
|
||||
file_caps: FileCaps,
|
||||
preopen_path: Option<PathBuf>,
|
||||
dir: Box<dyn WasiDir>,
|
||||
) -> Self {
|
||||
DirEntry {
|
||||
caps,
|
||||
file_caps,
|
||||
preopen_path,
|
||||
dir,
|
||||
}
|
||||
}
|
||||
pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> {
|
||||
if self.caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
|
||||
}
|
||||
}
|
||||
pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> {
|
||||
if self.file_caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::not_capable()
|
||||
.context(format!("desired {:?}, 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;
|
||||
Ok(())
|
||||
}
|
||||
pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps {
|
||||
self.caps & desired_caps
|
||||
}
|
||||
pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps {
|
||||
self.file_caps & desired_caps
|
||||
}
|
||||
pub fn get_dir_fdstat(&self) -> DirFdStat {
|
||||
DirFdStat {
|
||||
dir_caps: self.caps,
|
||||
file_caps: self.file_caps,
|
||||
}
|
||||
}
|
||||
pub fn preopen_path(&self) -> &Option<PathBuf> {
|
||||
&self.preopen_path
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DirEntryExt<'a> {
|
||||
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error>;
|
||||
}
|
||||
|
||||
impl<'a> DirEntryExt<'a> for Ref<'a, DirEntry> {
|
||||
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error> {
|
||||
self.capable_of_dir(caps)?;
|
||||
Ok(Ref::map(self, |r| r.dir.deref()))
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct DirCaps: u32 {
|
||||
const CREATE_DIRECTORY = 0b1;
|
||||
const CREATE_FILE = 0b10;
|
||||
const LINK_SOURCE = 0b100;
|
||||
const LINK_TARGET = 0b1000;
|
||||
const OPEN = 0b10000;
|
||||
const READDIR = 0b100000;
|
||||
const READLINK = 0b1000000;
|
||||
const RENAME_SOURCE = 0b10000000;
|
||||
const RENAME_TARGET = 0b100000000;
|
||||
const SYMLINK = 0b1000000000;
|
||||
const REMOVE_DIRECTORY = 0b10000000000;
|
||||
const UNLINK_FILE = 0b100000000000;
|
||||
const PATH_FILESTAT_GET = 0b1000000000000;
|
||||
const PATH_FILESTAT_SET_TIMES = 0b10000000000000;
|
||||
const FILESTAT_GET = 0b100000000000000;
|
||||
const FILESTAT_SET_TIMES = 0b1000000000000000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirFdStat {
|
||||
pub file_caps: FileCaps,
|
||||
pub dir_caps: DirCaps,
|
||||
}
|
||||
|
||||
pub(crate) trait TableDirExt {
|
||||
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error>;
|
||||
fn is_preopen(&self, fd: u32) -> bool;
|
||||
}
|
||||
|
||||
impl TableDirExt for crate::table::Table {
|
||||
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error> {
|
||||
self.get(fd)
|
||||
}
|
||||
fn is_preopen(&self, fd: u32) -> bool {
|
||||
if self.is::<DirEntry>(fd) {
|
||||
let dir_entry: std::cell::Ref<DirEntry> = self.get(fd).unwrap();
|
||||
dir_entry.preopen_path.is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReaddirEntity {
|
||||
pub next: ReaddirCursor,
|
||||
pub inode: u64,
|
||||
pub namelen: u32,
|
||||
pub filetype: FileType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ReaddirCursor(u64);
|
||||
impl From<u64> for ReaddirCursor {
|
||||
fn from(c: u64) -> ReaddirCursor {
|
||||
ReaddirCursor(c)
|
||||
}
|
||||
}
|
||||
impl From<ReaddirCursor> for u64 {
|
||||
fn from(c: ReaddirCursor) -> u64 {
|
||||
c.0
|
||||
}
|
||||
}
|
||||
110
crates/wasi-common/src/error.rs
Normal file
110
crates/wasi-common/src/error.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
pub use anyhow::Error;
|
||||
|
||||
/// Internal error type for the `wasi-common` crate.
|
||||
/// Contains variants of the WASI `$errno` type are added according to what is actually used internally by
|
||||
/// the crate. Not all values are represented presently.
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ErrorKind {
|
||||
/// Errno::TooBig: Argument list too long
|
||||
#[error("TooBig: Argument list too long")]
|
||||
TooBig,
|
||||
/// Errno::Badf: Bad file descriptor
|
||||
#[error("Badf: Bad file descriptor")]
|
||||
Badf,
|
||||
/// Errno::Exist: File exists
|
||||
#[error("Exist: File exists")]
|
||||
Exist,
|
||||
/// Errno::Ilseq: Illegal byte sequence
|
||||
#[error("Ilseq: Illegal byte sequence")]
|
||||
Ilseq,
|
||||
/// Errno::Inval: Invalid argument
|
||||
#[error("Inval: Invalid argument")]
|
||||
Inval,
|
||||
/// Errno::Io: I/O error
|
||||
#[error("Io: I/O error")]
|
||||
Io,
|
||||
/// Errno::Nametoolong: Filename too long
|
||||
#[error("Nametoolong: Filename too long")]
|
||||
Nametoolong,
|
||||
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
|
||||
#[error("Notdir: Not a directory or a symbolic link to a directory")]
|
||||
Notdir,
|
||||
/// Errno::Notsup: Not supported, or operation not supported on socket.
|
||||
#[error("Notsup: Not supported, or operation not supported on socket")]
|
||||
Notsup,
|
||||
/// Errno::Overflow: Value too large to be stored in data type.
|
||||
#[error("Overflow: Value too large to be stored in data type")]
|
||||
Overflow,
|
||||
/// Errno::Range: Result too large
|
||||
#[error("Range: Result too large")]
|
||||
Range,
|
||||
/// Errno::Spipe: Invalid seek
|
||||
#[error("Spipe: Invalid seek")]
|
||||
Spipe,
|
||||
/// Errno::NotCapable: Not capable
|
||||
#[error("Not capable")]
|
||||
NotCapable,
|
||||
}
|
||||
|
||||
pub trait ErrorExt {
|
||||
fn trap(msg: impl Into<String>) -> Self;
|
||||
fn too_big() -> Self;
|
||||
fn badf() -> Self;
|
||||
fn exist() -> Self;
|
||||
fn illegal_byte_sequence() -> Self;
|
||||
fn invalid_argument() -> Self;
|
||||
fn io() -> Self;
|
||||
fn name_too_long() -> Self;
|
||||
fn not_dir() -> Self;
|
||||
fn not_supported() -> Self;
|
||||
fn overflow() -> Self;
|
||||
fn range() -> Self;
|
||||
fn seek_pipe() -> Self;
|
||||
fn not_capable() -> Self;
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {
|
||||
fn trap(msg: impl Into<String>) -> Self {
|
||||
anyhow::anyhow!(msg.into())
|
||||
}
|
||||
fn too_big() -> Self {
|
||||
ErrorKind::TooBig.into()
|
||||
}
|
||||
fn badf() -> Self {
|
||||
ErrorKind::Badf.into()
|
||||
}
|
||||
fn exist() -> Self {
|
||||
ErrorKind::Exist.into()
|
||||
}
|
||||
fn illegal_byte_sequence() -> Self {
|
||||
ErrorKind::Ilseq.into()
|
||||
}
|
||||
fn invalid_argument() -> Self {
|
||||
ErrorKind::Inval.into()
|
||||
}
|
||||
fn io() -> Self {
|
||||
ErrorKind::Io.into()
|
||||
}
|
||||
fn name_too_long() -> Self {
|
||||
ErrorKind::Nametoolong.into()
|
||||
}
|
||||
fn not_dir() -> Self {
|
||||
ErrorKind::Notdir.into()
|
||||
}
|
||||
fn not_supported() -> Self {
|
||||
ErrorKind::Notsup.into()
|
||||
}
|
||||
fn overflow() -> Self {
|
||||
ErrorKind::Overflow.into()
|
||||
}
|
||||
fn range() -> Self {
|
||||
ErrorKind::Range.into()
|
||||
}
|
||||
fn seek_pipe() -> Self {
|
||||
ErrorKind::Spipe.into()
|
||||
}
|
||||
fn not_capable() -> Self {
|
||||
ErrorKind::NotCapable.into()
|
||||
}
|
||||
}
|
||||
172
crates/wasi-common/src/file.rs
Normal file
172
crates/wasi-common/src/file.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use crate::{Error, ErrorExt, SystemTimeSpec};
|
||||
use bitflags::bitflags;
|
||||
use std::any::Any;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub trait WasiFile {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn datasync(&self) -> Result<(), Error>; // write op
|
||||
fn sync(&self) -> Result<(), Error>; // file op
|
||||
fn get_filetype(&self) -> Result<FileType, Error>; // file op
|
||||
fn get_fdflags(&self) -> Result<FdFlags, Error>; // file op
|
||||
fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op
|
||||
fn get_filestat(&self) -> Result<Filestat, Error>; // split out get_length as a read & write op, rest is a file op
|
||||
fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op
|
||||
fn advise(
|
||||
&self,
|
||||
offset: u64,
|
||||
len: u64,
|
||||
advice: system_interface::fs::Advice,
|
||||
) -> Result<(), Error>; // file op
|
||||
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op
|
||||
fn set_times(
|
||||
&self,
|
||||
atime: Option<SystemTimeSpec>,
|
||||
mtime: Option<SystemTimeSpec>,
|
||||
) -> Result<(), Error>;
|
||||
fn read_vectored(&self, bufs: &mut [std::io::IoSliceMut]) -> Result<u64, Error>; // read op
|
||||
fn read_vectored_at(&self, bufs: &mut [std::io::IoSliceMut], offset: u64)
|
||||
-> Result<u64, Error>; // file op
|
||||
fn write_vectored(&self, bufs: &[std::io::IoSlice]) -> Result<u64, Error>; // write op
|
||||
fn write_vectored_at(&self, bufs: &[std::io::IoSlice], offset: u64) -> Result<u64, Error>; // file op
|
||||
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
|
||||
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum FileType {
|
||||
Unknown,
|
||||
BlockDevice,
|
||||
CharacterDevice,
|
||||
Directory,
|
||||
RegularFile,
|
||||
SocketDgram,
|
||||
SocketStream,
|
||||
SymbolicLink,
|
||||
Pipe,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct FdFlags: u32 {
|
||||
const APPEND = 0b1;
|
||||
const DSYNC = 0b10;
|
||||
const NONBLOCK = 0b100;
|
||||
const RSYNC = 0b1000;
|
||||
const SYNC = 0b10000;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct OFlags: u32 {
|
||||
const CREATE = 0b1;
|
||||
const DIRECTORY = 0b10;
|
||||
const EXCLUSIVE = 0b100;
|
||||
const TRUNCATE = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Filestat {
|
||||
pub device_id: u64,
|
||||
pub inode: u64,
|
||||
pub filetype: FileType,
|
||||
pub nlink: u64,
|
||||
pub size: u64, // this is a read field, the rest are file fields
|
||||
pub atim: Option<std::time::SystemTime>,
|
||||
pub mtim: Option<std::time::SystemTime>,
|
||||
pub ctim: Option<std::time::SystemTime>,
|
||||
}
|
||||
|
||||
pub(crate) trait TableFileExt {
|
||||
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error>;
|
||||
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error>;
|
||||
}
|
||||
impl TableFileExt for crate::table::Table {
|
||||
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error> {
|
||||
self.get(fd)
|
||||
}
|
||||
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error> {
|
||||
self.get_mut(fd)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FileEntry {
|
||||
caps: FileCaps,
|
||||
file: Box<dyn WasiFile>,
|
||||
}
|
||||
|
||||
impl FileEntry {
|
||||
pub fn new(caps: FileCaps, file: Box<dyn WasiFile>) -> Self {
|
||||
FileEntry { caps, file }
|
||||
}
|
||||
|
||||
pub fn capable_of(&self, caps: FileCaps) -> Result<(), Error> {
|
||||
if self.caps.contains(caps) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_caps_to(&mut self, caps: FileCaps) -> Result<(), Error> {
|
||||
self.capable_of(caps)?;
|
||||
self.caps = caps;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_fdstat(&self) -> Result<FdStat, Error> {
|
||||
Ok(FdStat {
|
||||
filetype: self.file.get_filetype()?,
|
||||
caps: self.caps,
|
||||
flags: self.file.get_fdflags()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FileEntryExt<'a> {
|
||||
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error>;
|
||||
}
|
||||
|
||||
impl<'a> FileEntryExt<'a> for Ref<'a, FileEntry> {
|
||||
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error> {
|
||||
self.capable_of(caps)?;
|
||||
Ok(Ref::map(self, |r| r.file.deref()))
|
||||
}
|
||||
}
|
||||
pub trait FileEntryMutExt<'a> {
|
||||
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error>;
|
||||
}
|
||||
|
||||
impl<'a> FileEntryMutExt<'a> for RefMut<'a, FileEntry> {
|
||||
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error> {
|
||||
self.capable_of(caps)?;
|
||||
Ok(RefMut::map(self, |r| r.file.deref_mut()))
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct FileCaps : u32 {
|
||||
const DATASYNC = 0b1;
|
||||
const READ = 0b10;
|
||||
const SEEK = 0b100;
|
||||
const FDSTAT_SET_FLAGS = 0b1000;
|
||||
const SYNC = 0b10000;
|
||||
const TELL = 0b100000;
|
||||
const WRITE = 0b1000000;
|
||||
const ADVISE = 0b10000000;
|
||||
const ALLOCATE = 0b100000000;
|
||||
const FILESTAT_GET = 0b1000000000;
|
||||
const FILESTAT_SET_SIZE = 0b10000000000;
|
||||
const FILESTAT_SET_TIMES = 0b100000000000;
|
||||
const POLL_READWRITE = 0b1000000000000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FdStat {
|
||||
pub filetype: FileType,
|
||||
pub caps: FileCaps,
|
||||
pub flags: FdFlags,
|
||||
}
|
||||
20
crates/wasi-common/src/lib.rs
Normal file
20
crates/wasi-common/src/lib.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#![cfg_attr(feature = "nightly", feature(windows_by_handle))]
|
||||
|
||||
pub mod clocks;
|
||||
mod ctx;
|
||||
pub mod dir;
|
||||
mod error;
|
||||
pub mod file;
|
||||
pub mod pipe;
|
||||
pub mod random;
|
||||
pub mod sched;
|
||||
pub mod snapshots;
|
||||
mod string_array;
|
||||
pub mod table;
|
||||
|
||||
pub use clocks::SystemTimeSpec;
|
||||
pub use ctx::{WasiCtx, WasiCtxBuilder};
|
||||
pub use dir::{DirCaps, ReaddirCursor, ReaddirEntity, WasiDir};
|
||||
pub use error::{Error, ErrorExt, ErrorKind};
|
||||
pub use file::{FdFlags, FileCaps, Filestat, OFlags, WasiFile};
|
||||
pub use string_array::StringArrayError;
|
||||
306
crates/wasi-common/src/pipe.rs
Normal file
306
crates/wasi-common/src/pipe.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
// This is mostly stubs
|
||||
#![allow(unused_variables, dead_code)]
|
||||
//! Virtual pipes.
|
||||
//!
|
||||
//! These types provide easy implementations of `WasiFile` that mimic much of the behavior of Unix
|
||||
//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other
|
||||
//! than OS files.
|
||||
//!
|
||||
//! Some convenience constructors are included for common backing types like `Vec<u8>` and `String`,
|
||||
//! but the virtual pipes can be instantiated with any `Read` or `Write` type.
|
||||
//!
|
||||
use crate::{
|
||||
file::{FdFlags, FileType, Filestat, WasiFile},
|
||||
Error, ErrorExt, SystemTimeSpec,
|
||||
};
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use system_interface::fs::Advice;
|
||||
|
||||
/// A virtual pipe read end.
|
||||
///
|
||||
/// A variety of `From` impls are provided so that common pipe types are easy to create. For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use wasi_c2::WasiCtx;
|
||||
/// # use wasi_c2::virt::pipe::ReadPipe;
|
||||
/// let stdin = ReadPipe::from("hello from stdin!");
|
||||
/// let ctx = WasiCtx::builder().stdin(Box::new(stdin)).build();
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct ReadPipe<R: Read> {
|
||||
reader: Arc<RwLock<R>>,
|
||||
}
|
||||
|
||||
impl<R: Read> Clone for ReadPipe<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
reader: self.reader.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> ReadPipe<R> {
|
||||
/// Create a new pipe from a `Read` type.
|
||||
///
|
||||
/// All `Handle` read operations delegate to reading from this underlying reader.
|
||||
pub fn new(r: R) -> Self {
|
||||
Self::from_shared(Arc::new(RwLock::new(r)))
|
||||
}
|
||||
|
||||
/// Create a new pipe from a shareable `Read` type.
|
||||
///
|
||||
/// All `Handle` read operations delegate to reading from this underlying reader.
|
||||
pub fn from_shared(reader: Arc<RwLock<R>>) -> Self {
|
||||
Self { reader }
|
||||
}
|
||||
|
||||
/// Try to convert this `ReadPipe<R>` back to the underlying `R` type.
|
||||
///
|
||||
/// This will fail with `Err(self)` if multiple references to the underlying `R` exist.
|
||||
pub fn try_into_inner(mut self) -> Result<R, Self> {
|
||||
match Arc::try_unwrap(self.reader) {
|
||||
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
|
||||
Err(reader) => {
|
||||
self.reader = reader;
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn borrow(&self) -> std::sync::RwLockWriteGuard<R> {
|
||||
RwLock::write(&self.reader).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ReadPipe<io::Cursor<Vec<u8>>> {
|
||||
fn from(r: Vec<u8>) -> Self {
|
||||
Self::new(io::Cursor::new(r))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for ReadPipe<io::Cursor<Vec<u8>>> {
|
||||
fn from(r: &[u8]) -> Self {
|
||||
Self::from(r.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ReadPipe<io::Cursor<String>> {
|
||||
fn from(r: String) -> Self {
|
||||
Self::new(io::Cursor::new(r))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ReadPipe<io::Cursor<String>> {
|
||||
fn from(r: &str) -> Self {
|
||||
Self::from(r.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Any> WasiFile for ReadPipe<R> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn datasync(&self) -> Result<(), Error> {
|
||||
Ok(()) // trivial: no implementation needed
|
||||
}
|
||||
fn sync(&self) -> Result<(), Error> {
|
||||
Ok(()) // trivial
|
||||
}
|
||||
fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
Ok(FileType::Pipe)
|
||||
}
|
||||
fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
Ok(FdFlags::empty())
|
||||
}
|
||||
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
Ok(Filestat {
|
||||
device_id: 0,
|
||||
inode: 0,
|
||||
filetype: self.get_filetype()?,
|
||||
nlink: 0,
|
||||
size: 0, // XXX no way to get a size out of a Read :(
|
||||
atim: None,
|
||||
mtim: None,
|
||||
ctim: None,
|
||||
})
|
||||
}
|
||||
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
|
||||
let n = self.borrow().read_vectored(bufs)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn set_times(
|
||||
&self,
|
||||
atime: Option<SystemTimeSpec>,
|
||||
mtime: Option<SystemTimeSpec>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A virtual pipe write end.
|
||||
///
|
||||
/// ```
|
||||
/// # use wasi_c2::WasiCtx;
|
||||
/// # use wasi_c2::virt::pipe::WritePipe;
|
||||
/// let stdout = WritePipe::new_in_memory();
|
||||
/// let ctx = WasiCtx::builder().stdout(Box::new(stdout.clone())).build();
|
||||
/// // use ctx in an instance, then make sure it is dropped:
|
||||
/// drop(ctx);
|
||||
/// let contents: Vec<u8> = stdout.try_into_inner().expect("sole remaining reference to WritePipe").into_inner();
|
||||
/// println!("contents of stdout: {:?}", contents);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct WritePipe<W: Write> {
|
||||
writer: Arc<RwLock<W>>,
|
||||
}
|
||||
|
||||
impl<W: Write> Clone for WritePipe<W> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
writer: self.writer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WritePipe<W> {
|
||||
/// Create a new pipe from a `Write` type.
|
||||
///
|
||||
/// All `Handle` write operations delegate to writing to this underlying writer.
|
||||
pub fn new(w: W) -> Self {
|
||||
Self::from_shared(Arc::new(RwLock::new(w)))
|
||||
}
|
||||
|
||||
/// Create a new pipe from a shareable `Write` type.
|
||||
///
|
||||
/// All `Handle` write operations delegate to writing to this underlying writer.
|
||||
pub fn from_shared(writer: Arc<RwLock<W>>) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
|
||||
/// Try to convert this `WritePipe<W>` back to the underlying `W` type.
|
||||
///
|
||||
/// This will fail with `Err(self)` if multiple references to the underlying `W` exist.
|
||||
pub fn try_into_inner(mut self) -> Result<W, Self> {
|
||||
match Arc::try_unwrap(self.writer) {
|
||||
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
|
||||
Err(writer) => {
|
||||
self.writer = writer;
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn borrow(&self) -> std::sync::RwLockWriteGuard<W> {
|
||||
RwLock::write(&self.writer).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl WritePipe<io::Cursor<Vec<u8>>> {
|
||||
/// Create a new writable virtual pipe backed by a `Vec<u8>` buffer.
|
||||
pub fn new_in_memory() -> Self {
|
||||
Self::new(io::Cursor::new(vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write + Any> WasiFile for WritePipe<W> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn datasync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn sync(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn get_filetype(&self) -> Result<FileType, Error> {
|
||||
Ok(FileType::Pipe)
|
||||
}
|
||||
fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||
Ok(FdFlags::APPEND)
|
||||
}
|
||||
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn get_filestat(&self) -> Result<Filestat, Error> {
|
||||
Ok(Filestat {
|
||||
device_id: 0,
|
||||
inode: 0,
|
||||
filetype: self.get_filetype()?,
|
||||
nlink: 0,
|
||||
size: 0, // XXX no way to get a size out of a Write :(
|
||||
atim: None,
|
||||
mtim: None,
|
||||
ctim: None,
|
||||
})
|
||||
}
|
||||
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
|
||||
let n = self.borrow().write_vectored(bufs)?;
|
||||
Ok(n.try_into()?)
|
||||
}
|
||||
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn set_times(
|
||||
&self,
|
||||
atime: Option<SystemTimeSpec>,
|
||||
mtime: Option<SystemTimeSpec>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::badf())
|
||||
}
|
||||
fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
46
crates/wasi-common/src/random.rs
Normal file
46
crates/wasi-common/src/random.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use cap_rand::RngCore;
|
||||
|
||||
/// Implement `WasiRandom` using a deterministic cycle of bytes.
|
||||
pub struct Deterministic {
|
||||
cycle: std::iter::Cycle<std::vec::IntoIter<u8>>,
|
||||
}
|
||||
|
||||
impl Deterministic {
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
Deterministic {
|
||||
cycle: bytes.into_iter().cycle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RngCore for Deterministic {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
fn fill_bytes(&mut self, buf: &mut [u8]) {
|
||||
for b in buf.iter_mut() {
|
||||
*b = self.cycle.next().expect("infinite sequence");
|
||||
}
|
||||
}
|
||||
fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), cap_rand::Error> {
|
||||
self.fill_bytes(buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn deterministic() {
|
||||
let mut det = Deterministic::new(vec![1, 2, 3, 4]);
|
||||
let mut buf = vec![0; 1024];
|
||||
det.try_fill_bytes(&mut buf).expect("get randomness");
|
||||
for (ix, b) in buf.iter().enumerate() {
|
||||
assert_eq!(*b, (ix % 4) as u8 + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
87
crates/wasi-common/src/sched.rs
Normal file
87
crates/wasi-common/src/sched.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::clocks::WasiMonotonicClock;
|
||||
use crate::file::WasiFile;
|
||||
use crate::Error;
|
||||
use cap_std::time::{Duration, Instant};
|
||||
use std::cell::Ref;
|
||||
pub mod subscription;
|
||||
|
||||
use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult};
|
||||
|
||||
pub trait WasiSched {
|
||||
fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>;
|
||||
fn sched_yield(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub struct Userdata(u64);
|
||||
impl From<u64> for Userdata {
|
||||
fn from(u: u64) -> Userdata {
|
||||
Userdata(u)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Userdata> for u64 {
|
||||
fn from(u: Userdata) -> u64 {
|
||||
u.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Poll<'a> {
|
||||
subs: Vec<(Subscription<'a>, Userdata)>,
|
||||
}
|
||||
|
||||
impl<'a> Poll<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self { subs: Vec::new() }
|
||||
}
|
||||
pub fn subscribe_monotonic_clock(
|
||||
&mut self,
|
||||
clock: &'a dyn WasiMonotonicClock,
|
||||
deadline: Instant,
|
||||
precision: Duration,
|
||||
ud: Userdata,
|
||||
) {
|
||||
self.subs.push((
|
||||
Subscription::MonotonicClock(MonotonicClockSubscription {
|
||||
clock,
|
||||
deadline,
|
||||
precision,
|
||||
}),
|
||||
ud,
|
||||
));
|
||||
}
|
||||
pub fn subscribe_read(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
|
||||
self.subs
|
||||
.push((Subscription::Read(RwSubscription::new(file)), ud));
|
||||
}
|
||||
pub fn subscribe_write(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
|
||||
self.subs
|
||||
.push((Subscription::Write(RwSubscription::new(file)), ud));
|
||||
}
|
||||
pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> {
|
||||
self.subs
|
||||
.into_iter()
|
||||
.filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud)))
|
||||
.collect()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.subs.is_empty()
|
||||
}
|
||||
pub fn earliest_clock_deadline(&'a self) -> Option<&MonotonicClockSubscription<'a>> {
|
||||
let mut subs = self
|
||||
.subs
|
||||
.iter()
|
||||
.filter_map(|(s, _ud)| match s {
|
||||
Subscription::MonotonicClock(t) => Some(t),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<&MonotonicClockSubscription<'a>>>();
|
||||
subs.sort_by(|a, b| a.deadline.cmp(&b.deadline));
|
||||
subs.into_iter().next() // First element is earliest
|
||||
}
|
||||
pub fn rw_subscriptions(&'a self) -> impl Iterator<Item = &Subscription<'a>> {
|
||||
self.subs.iter().filter_map(|(s, _ud)| match s {
|
||||
Subscription::Read { .. } | Subscription::Write { .. } => Some(s),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
79
crates/wasi-common/src/sched/subscription.rs
Normal file
79
crates/wasi-common/src/sched/subscription.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use crate::clocks::WasiMonotonicClock;
|
||||
use crate::file::WasiFile;
|
||||
use crate::Error;
|
||||
use bitflags::bitflags;
|
||||
use cap_std::time::{Duration, Instant};
|
||||
use std::cell::{Cell, Ref};
|
||||
|
||||
bitflags! {
|
||||
pub struct RwEventFlags: u32 {
|
||||
const HANGUP = 0b1;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RwSubscription<'a> {
|
||||
pub file: Ref<'a, dyn WasiFile>,
|
||||
status: Cell<Option<Result<(u64, RwEventFlags), Error>>>,
|
||||
}
|
||||
|
||||
impl<'a> RwSubscription<'a> {
|
||||
pub fn new(file: Ref<'a, dyn WasiFile>) -> Self {
|
||||
Self {
|
||||
file,
|
||||
status: Cell::new(None),
|
||||
}
|
||||
}
|
||||
pub fn complete(&self, size: u64, flags: RwEventFlags) {
|
||||
self.status.set(Some(Ok((size, flags))))
|
||||
}
|
||||
pub fn error(&self, error: Error) {
|
||||
self.status.set(Some(Err(error)))
|
||||
}
|
||||
pub fn result(self) -> Option<Result<(u64, RwEventFlags), Error>> {
|
||||
self.status.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MonotonicClockSubscription<'a> {
|
||||
pub clock: &'a dyn WasiMonotonicClock,
|
||||
pub deadline: Instant,
|
||||
pub precision: Duration,
|
||||
}
|
||||
|
||||
impl<'a> MonotonicClockSubscription<'a> {
|
||||
pub fn now(&self) -> Instant {
|
||||
self.clock.now(self.precision)
|
||||
}
|
||||
pub fn duration_until(&self) -> Option<Duration> {
|
||||
self.deadline.checked_duration_since(self.now())
|
||||
}
|
||||
pub fn result(&self) -> Option<Result<(), Error>> {
|
||||
if self.now().checked_duration_since(self.deadline).is_some() {
|
||||
Some(Ok(()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Subscription<'a> {
|
||||
Read(RwSubscription<'a>),
|
||||
Write(RwSubscription<'a>),
|
||||
MonotonicClock(MonotonicClockSubscription<'a>),
|
||||
}
|
||||
|
||||
pub enum SubscriptionResult {
|
||||
Read(Result<(u64, RwEventFlags), Error>),
|
||||
Write(Result<(u64, RwEventFlags), Error>),
|
||||
MonotonicClock(Result<(), Error>),
|
||||
}
|
||||
|
||||
impl SubscriptionResult {
|
||||
pub fn from_subscription(s: Subscription) -> Option<SubscriptionResult> {
|
||||
match s {
|
||||
Subscription::Read(s) => s.result().map(SubscriptionResult::Read),
|
||||
Subscription::Write(s) => s.result().map(SubscriptionResult::Write),
|
||||
Subscription::MonotonicClock(s) => s.result().map(SubscriptionResult::MonotonicClock),
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/wasi-common/src/snapshots/mod.rs
Normal file
1
crates/wasi-common/src/snapshots/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod preview_1;
|
||||
1505
crates/wasi-common/src/snapshots/preview_1.rs
Normal file
1505
crates/wasi-common/src/snapshots/preview_1.rs
Normal file
File diff suppressed because it is too large
Load Diff
74
crates/wasi-common/src/string_array.rs
Normal file
74
crates/wasi-common/src/string_array.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use crate::{Error, ErrorExt};
|
||||
use wiggle::GuestPtr;
|
||||
|
||||
pub struct StringArray {
|
||||
elems: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum StringArrayError {
|
||||
#[error("Number of elements exceeds 2^32")]
|
||||
NumberElements,
|
||||
#[error("Element size exceeds 2^32")]
|
||||
ElementSize,
|
||||
#[error("Cumulative size exceeds 2^32")]
|
||||
CumulativeSize,
|
||||
}
|
||||
|
||||
impl StringArray {
|
||||
pub fn new() -> Self {
|
||||
StringArray { elems: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, elem: String) -> Result<(), StringArrayError> {
|
||||
if self.elems.len() + 1 > std::u32::MAX as usize {
|
||||
return Err(StringArrayError::NumberElements);
|
||||
}
|
||||
if elem.as_bytes().len() + 1 > std::u32::MAX as usize {
|
||||
return Err(StringArrayError::ElementSize);
|
||||
}
|
||||
if self.cumulative_size() as usize + elem.as_bytes().len() + 1 > std::u32::MAX as usize {
|
||||
return Err(StringArrayError::CumulativeSize);
|
||||
}
|
||||
self.elems.push(elem);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn number_elements(&self) -> u32 {
|
||||
self.elems.len() as u32
|
||||
}
|
||||
|
||||
pub fn cumulative_size(&self) -> u32 {
|
||||
self.elems
|
||||
.iter()
|
||||
.map(|e| e.as_bytes().len() + 1)
|
||||
.sum::<usize>() as u32
|
||||
}
|
||||
|
||||
pub fn write_to_guest<'a>(
|
||||
&self,
|
||||
buffer: &GuestPtr<'a, u8>,
|
||||
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
|
||||
) -> Result<(), Error> {
|
||||
let element_heads = element_heads.as_array(self.number_elements());
|
||||
let buffer = buffer.as_array(self.cumulative_size());
|
||||
let mut cursor = 0;
|
||||
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
|
||||
let bytes = elem.as_bytes();
|
||||
let len = bytes.len() as u32;
|
||||
{
|
||||
let elem_buffer = buffer
|
||||
.get_range(cursor..(cursor + len))
|
||||
.ok_or(Error::invalid_argument())?; // Elements don't fit in buffer provided
|
||||
elem_buffer.copy_from_slice(bytes)?;
|
||||
}
|
||||
buffer
|
||||
.get(cursor + len)
|
||||
.ok_or(Error::invalid_argument())?
|
||||
.write(0)?; // 0 terminate
|
||||
head?.write(buffer.get(cursor).expect("already validated"))?;
|
||||
cursor += len + 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
105
crates/wasi-common/src/table.rs
Normal file
105
crates/wasi-common/src/table.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use crate::{Error, ErrorExt};
|
||||
use std::any::Any;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Table {
|
||||
map: HashMap<u32, RefCell<Box<dyn Any>>>,
|
||||
next_key: u32,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
pub fn new() -> Self {
|
||||
Table {
|
||||
map: HashMap::new(),
|
||||
next_key: 3, // 0, 1 and 2 are reserved for stdio
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_at(&mut self, key: u32, a: Box<dyn Any>) {
|
||||
self.map.insert(key, RefCell::new(a));
|
||||
}
|
||||
|
||||
pub fn push(&mut self, a: Box<dyn Any>) -> Result<u32, Error> {
|
||||
loop {
|
||||
let key = self.next_key;
|
||||
// XXX this is not correct. The table may still have empty entries, but our
|
||||
// linear search strategy is quite bad
|
||||
self.next_key = self
|
||||
.next_key
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| Error::trap("out of keys in table"))?;
|
||||
if self.map.contains_key(&key) {
|
||||
continue;
|
||||
}
|
||||
self.map.insert(key, RefCell::new(a));
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: u32) -> bool {
|
||||
self.map.contains_key(&key)
|
||||
}
|
||||
|
||||
pub fn is<T: Any + Sized>(&self, key: u32) -> bool {
|
||||
if let Some(refcell) = self.map.get(&key) {
|
||||
if let Ok(refmut) = refcell.try_borrow_mut() {
|
||||
refmut.is::<T>()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<T: Any + Sized>(&self, key: u32) -> Result<Ref<T>, Error> {
|
||||
if let Some(refcell) = self.map.get(&key) {
|
||||
if let Ok(r) = refcell.try_borrow() {
|
||||
if r.is::<T>() {
|
||||
Ok(Ref::map(r, |r| r.downcast_ref::<T>().unwrap()))
|
||||
} else {
|
||||
Err(Error::badf().context("element is a different type"))
|
||||
}
|
||||
} else {
|
||||
Err(Error::trap("table get of mutably borrowed element"))
|
||||
}
|
||||
} else {
|
||||
Err(Error::badf().context("key not in table"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut<T: Any + Sized>(&self, key: u32) -> Result<RefMut<T>, Error> {
|
||||
if let Some(refcell) = self.map.get(&key) {
|
||||
if let Ok(r) = refcell.try_borrow_mut() {
|
||||
if r.is::<T>() {
|
||||
Ok(RefMut::map(r, |r| r.downcast_mut::<T>().unwrap()))
|
||||
} else {
|
||||
Err(Error::badf().context("element is a different type"))
|
||||
}
|
||||
} else {
|
||||
Err(Error::trap("table get_mut of borrowed element"))
|
||||
}
|
||||
} else {
|
||||
Err(Error::badf().context("key not in table"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, key: u32) -> Option<Box<dyn Any>> {
|
||||
self.map.remove(&key).map(|rc| RefCell::into_inner(rc))
|
||||
}
|
||||
|
||||
pub fn update_in_place<T, F>(&mut self, key: u32, f: F) -> Result<(), Error>
|
||||
where
|
||||
T: Any + Sized,
|
||||
F: FnOnce(T) -> Result<T, Error>,
|
||||
{
|
||||
let entry = self.delete(key).ok_or(Error::badf())?;
|
||||
let downcast = entry
|
||||
.downcast::<T>()
|
||||
.map_err(|_| Error::exist().context("element is a different type"))?;
|
||||
let new = f(*downcast)?;
|
||||
self.insert_at(key, Box::new(new));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
21
crates/wasi-common/wasmtime/Cargo.toml
Normal file
21
crates/wasi-common/wasmtime/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "wasi-c2-wasmtime"
|
||||
version = "0.22.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "WASI implementation in Rust"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
categories = ["wasm"]
|
||||
keywords = ["webassembly", "wasm"]
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
include = ["src/**/*", "LICENSE", "build.rs"]
|
||||
build = "build.rs"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
wasi-c2 = { path = "../" }
|
||||
wiggle = { path = "../../wiggle", default-features = false, version = "0.22.0" }
|
||||
wasmtime-wiggle = { path = "../../wiggle/wasmtime", default-features = false, version = "0.22.0" }
|
||||
wasmtime = { path = "../../wasmtime" }
|
||||
anyhow = "1.0"
|
||||
6
crates/wasi-common/wasmtime/build.rs
Normal file
6
crates/wasi-common/wasmtime/build.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
// wasi-c2's links & build.rs ensure this variable points to the wasi root:
|
||||
let wasi_root = std::env::var("DEP_WASI_C2_19_WASI").unwrap();
|
||||
// Make it available as WASI_ROOT:
|
||||
println!("cargo:rustc-env=WASI_ROOT={}", wasi_root);
|
||||
}
|
||||
29
crates/wasi-common/wasmtime/src/lib.rs
Normal file
29
crates/wasi-common/wasmtime/src/lib.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
pub use wasi_c2::{
|
||||
Error, FdFlags, FileCaps, Filestat, OFlags, ReaddirCursor, ReaddirEntity, SystemTimeSpec,
|
||||
WasiCtx, WasiCtxBuilder, WasiDir, WasiFile,
|
||||
};
|
||||
|
||||
// Defines a `struct Wasi` with member fields and appropriate APIs for dealing
|
||||
// with all the various WASI exports.
|
||||
wasmtime_wiggle::wasmtime_integration!({
|
||||
// The wiggle code to integrate with lives here:
|
||||
target: wasi_c2::snapshots::preview_1,
|
||||
// This must be the same witx document as used above. This should be ensured by
|
||||
// the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`.
|
||||
witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
|
||||
// This must be the same ctx type as used for the target:
|
||||
ctx: WasiCtx,
|
||||
// This macro will emit a struct to represent the instance,
|
||||
// with this name and docs:
|
||||
modules: { wasi_snapshot_preview1 =>
|
||||
{ name: Wasi,
|
||||
docs: "An instantiated instance of the wasi exports.
|
||||
|
||||
This represents a wasi module which can be used to instantiate other wasm
|
||||
modules. This structure exports all that various fields of the wasi instance
|
||||
as fields which can be used to implement your own instantiation logic, if
|
||||
necessary. Additionally [`Wasi::get_export`] can be used to do name-based
|
||||
resolution.",
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user