vfs: host-side WASI preview1 over sausage-vfs (T11)
Preview1 state lives in the host, so Go modules (esbuild) work. esbuild bundles byte-identical to native; uutils and QuickJS-ng file tests pass. The failed guest-side adapter attempt (T7) moves to spikes/s7-preview1-adapter/.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Preview1 command adapter experiment
|
||||
|
||||
This directory tests D39 with Go esbuild, uutils, and QuickJS.
|
||||
The patch reserves the adapter state before the guest calls `_start`.
|
||||
It retains both state sentinels and all WASI semantics.
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
nix develop -c nu spikes/s7-preview1-adapter/build.nu
|
||||
nix develop -c nu spikes/s7-preview1-adapter/test.nu
|
||||
```
|
||||
|
||||
The build pins Wasmtime v49.0.2 to commit
|
||||
`3c8a3e79aa3188a1a12b3aebbaa097abccceac92` and uses its Cargo.lock with `--locked`.
|
||||
The repository flake pins the Rust toolchain through flake.lock.
|
||||
The build requires the `wasm32-unknown-unknown` standard library.
|
||||
T11 removed this target from the production flake after T7 disproved the patch.
|
||||
To repeat this historical experiment, supply that target in a separate Rust toolchain.
|
||||
It places sources, a persistent target directory, and the adapter under `/tmp/p1-adapter/`.
|
||||
|
||||
The test uses the S6 esbuild v0.27.7 module at `/tmp/s6-esbuild/esbuild.wasm`.
|
||||
The S6 build recipe can create this input.
|
||||
The test copies the S6 fixture into its own scratch directory and installs its locked dependencies.
|
||||
It compares module and component bundles byte-for-byte and checks rendered elements with LinkeDOM.
|
||||
It also adapts the lab's preview1 uutils and QuickJS modules.
|
||||
These smoke tests check command arguments and stdout.
|
||||
They do not check every utility or JavaScript feature.
|
||||
|
||||
The test requires these existing lab inputs:
|
||||
|
||||
- `/tmp/wasi-userland-lab/uu/coreutils-0.12.0-wasm32-wasip1/coreutils.wasm`
|
||||
- `/tmp/wasi-userland-lab/qjs-wasi.wasm`
|
||||
|
||||
## Result
|
||||
|
||||
The eager-state patch does not fix esbuild.
|
||||
The component traps during `--version` after Go overwrites the adapter state sentinels.
|
||||
The uutils and QuickJS smoke tests pass.
|
||||
The task report records the sentinel addresses and values.
|
||||
|
||||
`diagnostic.patch` is an alternative patch against the same pristine Wasmtime revision.
|
||||
It includes the eager-state change and prints state sentinels before `_start` and upon corruption.
|
||||
It does not disable assertions.
|
||||
Do not apply both patches to one source tree.
|
||||
The diagnostic build uses the same Cargo command as the normal build.
|
||||
Use `wasm-tools component new` with its resulting adapter to reproduce the trace.
|
||||
|
||||
The eager-state change does not reserve its memory from Go's allocator.
|
||||
Go starts its allocation cursor at the end of its static data, not at the current memory size.
|
||||
The host-side adapter is the next planned approach under D39.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Build the command adapter from pinned Wasmtime sources and a local patch.
|
||||
def main [] {
|
||||
let root = '/tmp/p1-adapter'
|
||||
let source = $root | path join wasmtime
|
||||
let revision = '3c8a3e79aa3188a1a12b3aebbaa097abccceac92'
|
||||
mkdir $root
|
||||
if not ($source | path exists) {
|
||||
^git clone --depth 1 --branch v49.0.2 https://github.com/bytecodealliance/wasmtime.git $source
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'clone failed'} }
|
||||
}
|
||||
if ((^git -C $source rev-parse HEAD | str trim) != $revision) {
|
||||
error make {msg: 'unexpected Wasmtime revision'}
|
||||
}
|
||||
let patch = $env.FILE_PWD | path join eager-state.patch
|
||||
let applied = (^git -C $source apply --reverse --check $patch | complete)
|
||||
if $applied.exit_code != 0 {
|
||||
^git -C $source apply $patch
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'patch failed'} }
|
||||
}
|
||||
cd $source
|
||||
with-env {CARGO_TARGET_DIR: '/tmp/p1-adapter/target', CARGO_BUILD_JOBS: '2'} {
|
||||
^cargo build --locked -p wasi-preview1-component-adapter --target wasm32-unknown-unknown --release --features command --no-default-features
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'adapter build failed'} }
|
||||
}
|
||||
cp ($root | path join target wasm32-unknown-unknown release wasi_snapshot_preview1.wasm) ($root | path join adapter-command.wasm)
|
||||
^wasm-tools validate ($root | path join adapter-command.wasm)
|
||||
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'adapter validation failed'} }
|
||||
print 'PASS patched command adapter build'
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
|
||||
index 7ce52c6..ed23010 100644
|
||||
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
|
||||
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
|
||||
@@ -129,6 +129,11 @@ pub extern "C" fn run() -> u32 {
|
||||
unsafe extern "C" {
|
||||
safe fn _start();
|
||||
}
|
||||
+ // Reserve adapter state before Go snapshots the linear-memory heap extent.
|
||||
+ // Lazy memory.grow allocations otherwise overlap Go's sbrk allocator.
|
||||
+ let state = State::ptr();
|
||||
+ eprint!("before _start: ");
|
||||
+ state.trace();
|
||||
_start();
|
||||
0
|
||||
}
|
||||
@@ -2852,8 +2857,24 @@ unsafe extern "C" {
|
||||
}
|
||||
|
||||
impl State {
|
||||
+ fn trace(&self) {
|
||||
+ eprint!("ptr=");
|
||||
+ crate::macros::eprint_u32(self as *const Self as u32);
|
||||
+ eprint!(" magic1=");
|
||||
+ crate::macros::eprint_u32(self.magic1);
|
||||
+ eprint!(" magic2=");
|
||||
+ crate::macros::eprint_u32(self.magic2);
|
||||
+ eprint!(" memory_pages=");
|
||||
+ crate::macros::eprint_u32(core::arch::wasm32::memory_size::<0>() as u32);
|
||||
+ eprintln!("");
|
||||
+ }
|
||||
+
|
||||
fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno {
|
||||
let state_ref = State::ptr();
|
||||
+ if state_ref.magic1 != MAGIC || state_ref.magic2 != MAGIC {
|
||||
+ eprint!("corrupt state: ");
|
||||
+ state_ref.trace();
|
||||
+ }
|
||||
assert_eq!(state_ref.magic1, MAGIC);
|
||||
assert_eq!(state_ref.magic2, MAGIC);
|
||||
let ret = f(state_ref);
|
||||
diff --git a/crates/wasi-preview1-component-adapter/src/macros.rs b/crates/wasi-preview1-component-adapter/src/macros.rs
|
||||
index d5649f4..3b5adf4 100644
|
||||
--- a/crates/wasi-preview1-component-adapter/src/macros.rs
|
||||
+++ b/crates/wasi-preview1-component-adapter/src/macros.rs
|
||||
@@ -40,7 +40,7 @@ pub fn eprint_unreachable(line: u32) {
|
||||
crate::macros::eprint_u32(line);
|
||||
}
|
||||
|
||||
-fn eprint_u32(x: u32) {
|
||||
+pub(crate) fn eprint_u32(x: u32) {
|
||||
if x == 0 {
|
||||
eprint!("0");
|
||||
} else {
|
||||
@@ -0,0 +1,14 @@
|
||||
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
|
||||
index 7ce52c6..55a8480 100644
|
||||
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
|
||||
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
|
||||
@@ -129,6 +129,9 @@ pub extern "C" fn run() -> u32 {
|
||||
unsafe extern "C" {
|
||||
safe fn _start();
|
||||
}
|
||||
+ // Reserve adapter state before Go snapshots the linear-memory heap extent.
|
||||
+ // Lazy memory.grow allocations otherwise overlap Go's sbrk allocator.
|
||||
+ State::ptr();
|
||||
_start();
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# Compare esbuild bundles and test preview1 uutils and QuickJS with this adapter.
|
||||
use std/assert
|
||||
def checked [program: string, args: list<string>] {
|
||||
let result = (run-external $program ...$args | complete)
|
||||
if $result.exit_code != 0 {
|
||||
print $result.stdout
|
||||
print --stderr $result.stderr
|
||||
error make {msg: $'Failed: ($program) ($args | str join " ")'}
|
||||
}
|
||||
$result.stdout | str trim
|
||||
}
|
||||
def main [] {
|
||||
let root = '/tmp/p1-adapter'
|
||||
let adapter = $root | path join adapter-command.wasm
|
||||
for guest in [
|
||||
{name: coreutils, path: '/tmp/wasi-userland-lab/uu/coreutils-0.12.0-wasm32-wasip1/coreutils.wasm', args: [echo adapter-ok], expected: adapter-ok},
|
||||
{name: QuickJS, path: '/tmp/wasi-userland-lab/qjs-wasi.wasm', args: [-e 'console.log(6 * 7)'], expected: '42'}
|
||||
] {
|
||||
let binary = $root | path join $'($guest.name).wasm'
|
||||
checked wasm-tools [component new $guest.path --adapt $'wasi_snapshot_preview1=($adapter)' -o $binary] | ignore
|
||||
assert equal (checked wasmtime ([run $binary] | append $guest.args)) $guest.expected
|
||||
print $'PASS ($guest.name): ($guest.expected)'
|
||||
}
|
||||
let fixture = $env.FILE_PWD | path join ../s6-esbuild | path expand
|
||||
let app = $root | path join app
|
||||
mkdir $app
|
||||
cp ($fixture | path join app.tsx) ($fixture | path join check.mjs) ($fixture | path join package.json) ($fixture | path join package-lock.json) $app
|
||||
cd $app
|
||||
print (checked npm [ci --ignore-scripts --no-audit --no-fund])
|
||||
let flags = [app.tsx --bundle --minify --format=iife --platform=browser --jsx=automatic --jsx-import-source=preact --legal-comments=none --log-level=error]
|
||||
for variant in [module component] {
|
||||
let binary = if $variant == module { '/tmp/s6-esbuild/esbuild.wasm' } else { $root | path join esbuild.component.wasm }
|
||||
if $variant == component {
|
||||
checked wasm-tools [component new /tmp/s6-esbuild/esbuild.wasm --adapt $'wasi_snapshot_preview1=($adapter)' -o $binary] | ignore
|
||||
}
|
||||
assert equal (checked wasmtime [run --dir .::/ $binary --version]) '0.27.7'
|
||||
checked wasmtime ([run --dir .::/ $binary] | append $flags | append $'--outfile=($variant).js') | ignore
|
||||
print (checked node [check.mjs $'($variant).js'])
|
||||
}
|
||||
assert equal (open --raw module.js) (open --raw component.js)
|
||||
print 'PASS byte-identical module/component bundles'
|
||||
print 'PASS all adapter checks'
|
||||
}
|
||||
+3
-1
@@ -11,7 +11,9 @@ cap-fs-ext = "4"
|
||||
wasmtime = "49"
|
||||
wasmtime-wasi = "49"
|
||||
bytes = "1"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros", "io-util", "time"] }
|
||||
wiggle = "49"
|
||||
getrandom = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
wasip2 = "1"
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod host;
|
||||
pub mod memory;
|
||||
pub mod mounts;
|
||||
pub mod observe;
|
||||
pub mod preview1;
|
||||
pub mod process;
|
||||
mod readonly;
|
||||
mod streams;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Preview1 over the virtual filesystem
|
||||
|
||||
`State` owns a process's preview1 descriptors outside guest memory.
|
||||
It uses the caller's `Arc<dyn Layer>` for every filesystem operation.
|
||||
The caller can supply the same mount namespace that its components use.
|
||||
The namespace has one preopen, `/`, at descriptor 3.
|
||||
Relative paths through descriptor 3 resolve against the process cwd.
|
||||
Paths through other directory descriptors resolve against that descriptor's virtual path.
|
||||
The resolver normalizes `..` before the mount namespace routes the operation.
|
||||
|
||||
Construct `State::new(root, cwd, argv, env, stdin, stdout, stderr)`.
|
||||
The stream types accept boxed Tokio `AsyncRead` and `AsyncWrite` implementations.
|
||||
The state wraps them with Wasmtime's existing pollable streams.
|
||||
These streams supply readiness without a guest-memory buffer for adapter state.
|
||||
Register imports with `add_to_linker(&mut linker, |store| &mut store.preview1)`.
|
||||
Instantiate the core module asynchronously and call its `_start` export.
|
||||
A `wasmtime_wasi::I32Exit` error carries the process exit status.
|
||||
|
||||
Wiggle generates the ABI from the copied preview1 WITX files.
|
||||
Their source is Wasmtime v49.0.2, commit `3c8a3e79aa3188a1a12b3aebbaa097abccceac92`.
|
||||
Wiggle checks guest pointers before the implementation accesses their contents.
|
||||
The implementation maps filesystem errors to preview1 errno values.
|
||||
|
||||
The Layer interface does not expose inode identity.
|
||||
Metadata therefore hashes the virtual path, as the component adapter does.
|
||||
Directory descriptors retain their original paths after a rename.
|
||||
Hard links, symlink creation, file preallocation, signals, and sockets return `NOTSUP`.
|
||||
Preview1 does not expose the platform's WASI 0.2 sockets.
|
||||
|
||||
The integration test uses the existing S6 and lab guests:
|
||||
|
||||
- esbuild v0.27.7: `/tmp/s6-esbuild/esbuild.wasm` and `/tmp/s6-esbuild/esbuild-native`.
|
||||
- uutils 0.12.0: `/tmp/wasi-userland-lab/uu/coreutils-0.12.0-wasm32-wasip1/coreutils.wasm`.
|
||||
- QuickJS-ng v0.18.0: `/tmp/wasi-userland-lab/qjs-wasi.wasm`.
|
||||
|
||||
The QuickJS-ng input matches its recorded release SHA-256.
|
||||
The S6 fixture also requires `/tmp/s6-esbuild/app/node_modules`.
|
||||
It stores local Wasmtime compilation artifacts in a persistent test cache.
|
||||
Run it from the repository root:
|
||||
|
||||
```sh
|
||||
CARGO_TARGET_DIR=target/preview1 nix develop -c cargo test -p sausage-vfs --test preview1 -- --nocapture
|
||||
```
|
||||
@@ -0,0 +1,605 @@
|
||||
//! Preview1 ABI operations over host-owned descriptors and async streams.
|
||||
use super::*;
|
||||
use types::*;
|
||||
impl wasi_snapshot_preview1::WasiSnapshotPreview1 for State {
|
||||
fn args_get(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
argv: GuestPtr<GuestPtr<u8>>,
|
||||
buf: GuestPtr<u8>,
|
||||
) -> Result<(), Error> {
|
||||
strings(m, &self.argv, argv, buf)
|
||||
}
|
||||
fn args_sizes_get(&mut self, _: &mut GuestMemory<'_>) -> Result<(Size, Size), Error> {
|
||||
sizes(&self.argv)
|
||||
}
|
||||
fn environ_get(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
env: GuestPtr<GuestPtr<u8>>,
|
||||
buf: GuestPtr<u8>,
|
||||
) -> Result<(), Error> {
|
||||
strings(m, &self.env, env, buf)
|
||||
}
|
||||
fn environ_sizes_get(&mut self, _: &mut GuestMemory<'_>) -> Result<(Size, Size), Error> {
|
||||
sizes(&self.env)
|
||||
}
|
||||
fn clock_res_get(&mut self, _: &mut GuestMemory<'_>, id: Clockid) -> Result<Timestamp, Error> {
|
||||
self.clock(id)?;
|
||||
Ok(1)
|
||||
}
|
||||
fn clock_time_get(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
id: Clockid,
|
||||
_: Timestamp,
|
||||
) -> Result<Timestamp, Error> {
|
||||
self.clock(id)
|
||||
}
|
||||
async fn fd_advise(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
_: Filesize,
|
||||
_: Filesize,
|
||||
_: Advice,
|
||||
) -> Result<(), Error> {
|
||||
self.fd(fd)?;
|
||||
Ok(())
|
||||
}
|
||||
fn fd_allocate(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
_: Fd,
|
||||
_: Filesize,
|
||||
_: Filesize,
|
||||
) -> Result<(), Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
async fn fd_close(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<(), Error> {
|
||||
self.descriptors.remove(&u32::from(fd)).ok_or(Errno::Badf)?;
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_datasync(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<(), Error> {
|
||||
self.file(fd)?.sync().await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_sync(&mut self, m: &mut GuestMemory<'_>, fd: Fd) -> Result<(), Error> {
|
||||
self.fd_datasync(m, fd).await
|
||||
}
|
||||
async fn fd_fdstat_get(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<Fdstat, Error> {
|
||||
let (ty, flags) = if u32::from(fd) <= 2 {
|
||||
(Filetype::Unknown, Fdflags::empty())
|
||||
} else {
|
||||
let d = self.fd(fd)?;
|
||||
(
|
||||
if d.directory {
|
||||
Filetype::Directory
|
||||
} else {
|
||||
Filetype::RegularFile
|
||||
},
|
||||
d.flags,
|
||||
)
|
||||
};
|
||||
Ok(Fdstat {
|
||||
fs_filetype: ty,
|
||||
fs_flags: flags,
|
||||
fs_rights_base: Rights::all(),
|
||||
fs_rights_inheriting: Rights::all(),
|
||||
})
|
||||
}
|
||||
fn fd_fdstat_set_flags(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
flags: Fdflags,
|
||||
) -> Result<(), Error> {
|
||||
if u32::from(fd) > 2 {
|
||||
self.descriptors
|
||||
.get_mut(&u32::from(fd))
|
||||
.ok_or(Errno::Badf)?
|
||||
.flags = flags;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn fd_fdstat_set_rights(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
_: Rights,
|
||||
_: Rights,
|
||||
) -> Result<(), Error> {
|
||||
self.fd(fd)?;
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_filestat_get(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
) -> Result<Filestat, Error> {
|
||||
if u32::from(fd) <= 2 {
|
||||
return Ok(Filestat {
|
||||
dev: 0,
|
||||
ino: u32::from(fd) as u64,
|
||||
filetype: Filetype::Unknown,
|
||||
nlink: 1,
|
||||
size: 0,
|
||||
atim: 0,
|
||||
mtim: 0,
|
||||
ctim: 0,
|
||||
});
|
||||
}
|
||||
let d = self.fd(fd)?.clone();
|
||||
Ok(stat(
|
||||
if let Some(file) = d.file {
|
||||
file.stat().await?
|
||||
} else {
|
||||
self.root.stat(&d.path, true).await?
|
||||
},
|
||||
&d.path,
|
||||
))
|
||||
}
|
||||
async fn fd_filestat_set_size(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
size: Filesize,
|
||||
) -> Result<(), Error> {
|
||||
self.file(fd)?.set_size(size).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_filestat_set_times(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
atim: Timestamp,
|
||||
mtim: Timestamp,
|
||||
flags: Fstflags,
|
||||
) -> Result<(), Error> {
|
||||
let (a, m) = times(atim, mtim, flags);
|
||||
self.file(fd)?.set_times(a, m).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_read(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
iovs: IovecArray,
|
||||
) -> Result<Size, Error> {
|
||||
self.read(m, fd, iovs, None).await
|
||||
}
|
||||
async fn fd_pread(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
iovs: IovecArray,
|
||||
offset: Filesize,
|
||||
) -> Result<Size, Error> {
|
||||
self.read(m, fd, iovs, Some(offset)).await
|
||||
}
|
||||
async fn fd_write(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
iovs: CiovecArray,
|
||||
) -> Result<Size, Error> {
|
||||
self.write(m, fd, iovs, None).await
|
||||
}
|
||||
async fn fd_pwrite(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
iovs: CiovecArray,
|
||||
offset: Filesize,
|
||||
) -> Result<Size, Error> {
|
||||
self.write(m, fd, iovs, Some(offset)).await
|
||||
}
|
||||
fn fd_prestat_get(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<Prestat, Error> {
|
||||
if u32::from(fd) != 3 {
|
||||
return Err(Errno::Badf.into());
|
||||
}
|
||||
Ok(Prestat::Dir(PrestatDir { pr_name_len: 1 }))
|
||||
}
|
||||
fn fd_prestat_dir_name(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
path: GuestPtr<u8>,
|
||||
len: Size,
|
||||
) -> Result<(), Error> {
|
||||
if u32::from(fd) != 3 {
|
||||
return Err(Errno::Badf.into());
|
||||
}
|
||||
if len < 1 {
|
||||
return Err(Errno::Nametoolong.into());
|
||||
}
|
||||
m.write(path, b'/')?;
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_renumber(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
from: Fd,
|
||||
to: Fd,
|
||||
) -> Result<(), Error> {
|
||||
let d = self
|
||||
.descriptors
|
||||
.remove(&u32::from(from))
|
||||
.ok_or(Errno::Badf)?;
|
||||
self.descriptors.insert(u32::from(to), d);
|
||||
Ok(())
|
||||
}
|
||||
async fn fd_seek(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
offset: Filedelta,
|
||||
whence: Whence,
|
||||
) -> Result<Filesize, Error> {
|
||||
if u32::from(fd) <= 2 {
|
||||
return Err(Errno::Spipe.into());
|
||||
}
|
||||
let base = match whence {
|
||||
Whence::Set => 0,
|
||||
Whence::Cur => self.fd(fd)?.offset,
|
||||
Whence::End => self.file(fd)?.stat().await?.size,
|
||||
};
|
||||
let value = base.checked_add_signed(offset).ok_or(Errno::Inval)?;
|
||||
self.descriptors
|
||||
.get_mut(&u32::from(fd))
|
||||
.ok_or(Errno::Badf)?
|
||||
.offset = value;
|
||||
Ok(value)
|
||||
}
|
||||
fn fd_tell(&mut self, _: &mut GuestMemory<'_>, fd: Fd) -> Result<Filesize, Error> {
|
||||
Ok(self.fd(fd)?.offset)
|
||||
}
|
||||
async fn fd_readdir(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
buf: GuestPtr<u8>,
|
||||
len: Size,
|
||||
cookie: Dircookie,
|
||||
) -> Result<Size, Error> {
|
||||
let d = self.fd(fd)?;
|
||||
if !d.directory {
|
||||
return Err(Errno::Notdir.into());
|
||||
}
|
||||
let mut entries = vec![
|
||||
crate::Entry {
|
||||
name: ".".into(),
|
||||
directory: true,
|
||||
symlink: false,
|
||||
},
|
||||
crate::Entry {
|
||||
name: "..".into(),
|
||||
directory: true,
|
||||
symlink: false,
|
||||
},
|
||||
];
|
||||
entries.extend(self.root.list(&d.path).await?);
|
||||
let mut bytes = Vec::new();
|
||||
for (i, entry) in entries.iter().enumerate().skip(cookie as usize) {
|
||||
// Preview1 dirent: next cookie, inode, name length, type, padding.
|
||||
bytes.extend_from_slice(&((i + 1) as u64).to_le_bytes());
|
||||
bytes.extend_from_slice(&((i + 1) as u64).to_le_bytes());
|
||||
bytes.extend_from_slice(&(entry.name.len() as u32).to_le_bytes());
|
||||
bytes.push(if entry.symlink {
|
||||
7
|
||||
} else if entry.directory {
|
||||
3
|
||||
} else {
|
||||
4
|
||||
});
|
||||
bytes.extend_from_slice(&[0; 3]);
|
||||
bytes.extend_from_slice(entry.name.as_bytes());
|
||||
if bytes.len() >= len as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
bytes.truncate(len as usize);
|
||||
m.copy_from_slice(&bytes, buf.as_array(bytes.len() as u32))?;
|
||||
Ok(bytes.len() as u32)
|
||||
}
|
||||
async fn path_create_directory(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
path: GuestPtr<str>,
|
||||
) -> Result<(), Error> {
|
||||
self.root.mkdir(&self.path(m, fd, path)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn path_filestat_get(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
flags: Lookupflags,
|
||||
path: GuestPtr<str>,
|
||||
) -> Result<Filestat, Error> {
|
||||
let path = self.path(m, fd, path)?;
|
||||
Ok(stat(
|
||||
self.root
|
||||
.stat(&path, flags.contains(Lookupflags::SYMLINK_FOLLOW))
|
||||
.await?,
|
||||
&path,
|
||||
))
|
||||
}
|
||||
async fn path_filestat_set_times(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
flags: Lookupflags,
|
||||
path: GuestPtr<str>,
|
||||
atim: Timestamp,
|
||||
mtim: Timestamp,
|
||||
f: Fstflags,
|
||||
) -> Result<(), Error> {
|
||||
let path = self.path(m, fd, path)?;
|
||||
let file = self
|
||||
.root
|
||||
.open(
|
||||
&path,
|
||||
Open {
|
||||
follow: flags.contains(Lookupflags::SYMLINK_FOLLOW),
|
||||
..Open::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let (a, b) = times(atim, mtim, f);
|
||||
file.set_times(a, b).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn path_open(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
flags: Lookupflags,
|
||||
path: GuestPtr<str>,
|
||||
oflags: Oflags,
|
||||
rights: Rights,
|
||||
_: Rights,
|
||||
fdflags: Fdflags,
|
||||
) -> Result<Fd, Error> {
|
||||
let path = self.path(m, fd, path)?;
|
||||
let file = self
|
||||
.root
|
||||
.open(
|
||||
&path,
|
||||
Open {
|
||||
follow: flags.contains(Lookupflags::SYMLINK_FOLLOW),
|
||||
write: rights.contains(Rights::FD_WRITE),
|
||||
create: oflags.contains(Oflags::CREAT),
|
||||
exclusive: oflags.contains(Oflags::EXCL),
|
||||
truncate: oflags.contains(Oflags::TRUNC),
|
||||
directory: oflags.contains(Oflags::DIRECTORY),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let directory = file.stat().await?.directory;
|
||||
let next = (4..)
|
||||
.find(|n| !self.descriptors.contains_key(n))
|
||||
.ok_or(Errno::Mfile)?;
|
||||
self.descriptors.insert(
|
||||
next,
|
||||
Descriptor {
|
||||
path,
|
||||
file: Some(file),
|
||||
directory,
|
||||
offset: 0,
|
||||
flags: fdflags,
|
||||
},
|
||||
);
|
||||
Ok(next.into())
|
||||
}
|
||||
async fn path_readlink(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
path: GuestPtr<str>,
|
||||
buf: GuestPtr<u8>,
|
||||
len: Size,
|
||||
) -> Result<Size, Error> {
|
||||
let target = self.root.readlink(&self.path(m, fd, path)?).await?;
|
||||
let bytes = &target.as_bytes()[..target.len().min(len as usize)];
|
||||
m.copy_from_slice(bytes, buf.as_array(bytes.len() as u32))?;
|
||||
Ok(bytes.len() as u32)
|
||||
}
|
||||
async fn path_remove_directory(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
path: GuestPtr<str>,
|
||||
) -> Result<(), Error> {
|
||||
self.root.remove_dir(&self.path(m, fd, path)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn path_unlink_file(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
fd: Fd,
|
||||
path: GuestPtr<str>,
|
||||
) -> Result<(), Error> {
|
||||
self.root.remove_file(&self.path(m, fd, path)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn path_rename(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
from: Fd,
|
||||
src: GuestPtr<str>,
|
||||
to: Fd,
|
||||
dst: GuestPtr<str>,
|
||||
) -> Result<(), Error> {
|
||||
self.root
|
||||
.rename(&self.path(m, from, src)?, &self.path(m, to, dst)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn path_link(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
_: Fd,
|
||||
_: Lookupflags,
|
||||
_: GuestPtr<str>,
|
||||
_: Fd,
|
||||
_: GuestPtr<str>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
async fn path_symlink(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
_: GuestPtr<str>,
|
||||
_: Fd,
|
||||
_: GuestPtr<str>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
async fn poll_oneoff(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
subs: GuestPtr<Subscription>,
|
||||
events: GuestPtr<Event>,
|
||||
count: Size,
|
||||
) -> Result<Size, Error> {
|
||||
if count == 0 {
|
||||
return Err(Errno::Inval.into());
|
||||
}
|
||||
let mut pending = Vec::new();
|
||||
for ptr in subs.as_array(count).iter() {
|
||||
let sub = m.read(ptr?)?;
|
||||
let (delay, stream, ty, error) = match sub.u {
|
||||
SubscriptionU::Clock(c) => {
|
||||
let now = self.clock(c.id)?;
|
||||
let delay = if c.flags.contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) {
|
||||
c.timeout.saturating_sub(now)
|
||||
} else {
|
||||
c.timeout
|
||||
};
|
||||
(Some(delay), None, Eventtype::Clock, Errno::Success)
|
||||
}
|
||||
SubscriptionU::FdRead(f) => {
|
||||
let fd = u32::from(f.file_descriptor);
|
||||
if fd == 0 {
|
||||
(None, Some(fd), Eventtype::FdRead, Errno::Success)
|
||||
} else {
|
||||
(
|
||||
Some(0),
|
||||
None,
|
||||
Eventtype::FdRead,
|
||||
if self.fd(f.file_descriptor).is_ok() {
|
||||
Errno::Success
|
||||
} else {
|
||||
Errno::Badf
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
SubscriptionU::FdWrite(f) => {
|
||||
let fd = u32::from(f.file_descriptor);
|
||||
if fd == 1 || fd == 2 {
|
||||
(None, Some(fd), Eventtype::FdWrite, Errno::Success)
|
||||
} else {
|
||||
(
|
||||
Some(0),
|
||||
None,
|
||||
Eventtype::FdWrite,
|
||||
if self.fd(f.file_descriptor).is_ok() {
|
||||
Errno::Success
|
||||
} else {
|
||||
Errno::Badf
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
pending.push((
|
||||
delay,
|
||||
stream,
|
||||
Event {
|
||||
userdata: sub.userdata,
|
||||
error,
|
||||
type_: ty,
|
||||
fd_readwrite: EventFdReadwrite {
|
||||
nbytes: 1,
|
||||
flags: Eventrwflags::empty(),
|
||||
},
|
||||
},
|
||||
));
|
||||
}
|
||||
let delay = pending.iter().filter_map(|(d, _, _)| *d).min();
|
||||
let has = |fd| pending.iter().any(|(_, s, _)| *s == Some(fd));
|
||||
let wait_start = Instant::now();
|
||||
let stream = tokio::select! {
|
||||
_ = self.stdin.ready(), if has(0) => Some(0),
|
||||
_ = self.stdout.ready(), if has(1) => Some(1),
|
||||
_ = self.stderr.ready(), if has(2) => Some(2),
|
||||
_ = tokio::time::sleep(Duration::from_nanos(delay.unwrap_or(0))), if delay.is_some() => None,
|
||||
};
|
||||
let elapsed = wait_start.elapsed().as_nanos() as u64;
|
||||
let mut written = 0;
|
||||
for (delay, fd, mut event) in pending {
|
||||
if delay.is_some_and(|d| d <= elapsed) || fd.is_some() && fd == stream {
|
||||
if fd == Some(0) && matches!(self.stdin.read(0), Err(StreamError::Closed)) {
|
||||
event.fd_readwrite.nbytes = 0;
|
||||
event.fd_readwrite.flags = Eventrwflags::FD_READWRITE_HANGUP;
|
||||
}
|
||||
if fd == Some(1) && self.stdout.check_write().is_err()
|
||||
|| fd == Some(2) && self.stderr.check_write().is_err()
|
||||
{
|
||||
event.error = Errno::Io;
|
||||
}
|
||||
m.write(events.add(written)?, event)?;
|
||||
written += 1;
|
||||
}
|
||||
}
|
||||
Ok(written)
|
||||
}
|
||||
fn proc_exit(&mut self, _: &mut GuestMemory<'_>, status: Exitcode) -> wasmtime::Error {
|
||||
wasmtime_wasi::I32Exit(status as i32).into()
|
||||
}
|
||||
fn proc_raise(&mut self, _: &mut GuestMemory<'_>, _: Signal) -> Result<(), Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
fn sched_yield(&mut self, _: &mut GuestMemory<'_>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn random_get(
|
||||
&mut self,
|
||||
m: &mut GuestMemory<'_>,
|
||||
buf: GuestPtr<u8>,
|
||||
len: Size,
|
||||
) -> Result<(), Error> {
|
||||
let _ = m.as_slice(buf.as_array(len))?;
|
||||
let mut bytes = vec![0; len as usize];
|
||||
getrandom::fill(&mut bytes).map_err(|_| Errno::Io)?;
|
||||
m.copy_from_slice(&bytes, buf.as_array(len))?;
|
||||
Ok(())
|
||||
}
|
||||
fn sock_accept(&mut self, _: &mut GuestMemory<'_>, _: Fd, _: Fdflags) -> Result<Fd, Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
fn sock_recv(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
_: Fd,
|
||||
_: IovecArray,
|
||||
_: Riflags,
|
||||
) -> Result<(Size, Roflags), Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
fn sock_send(
|
||||
&mut self,
|
||||
_: &mut GuestMemory<'_>,
|
||||
_: Fd,
|
||||
_: CiovecArray,
|
||||
_: Siflags,
|
||||
) -> Result<Size, Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
fn sock_shutdown(&mut self, _: &mut GuestMemory<'_>, _: Fd, _: Sdflags) -> Result<(), Error> {
|
||||
Err(Errno::Notsup.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
//! Host-owned preview1 descriptors and WASI calls over a process filesystem layer.
|
||||
//! Guests retain only their ABI buffers. The host owns all descriptor state.
|
||||
#![allow(
|
||||
clippy::too_many_arguments,
|
||||
reason = "The preview1 ABI fixes the generated parameter lists."
|
||||
)]
|
||||
use crate::{File, Layer, Open, Stat, resolve};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use wasmtime_wasi::p2::{
|
||||
DynInputStream, DynOutputStream, StreamError,
|
||||
pipe::{AsyncReadStream, AsyncWriteStream},
|
||||
};
|
||||
use wiggle::{GuestMemory, GuestPtr};
|
||||
mod calls;
|
||||
|
||||
wiggle::from_witx!({
|
||||
witx: ["src/preview1/witx/wasi_snapshot_preview1.witx"],
|
||||
async: { wasi_snapshot_preview1::{
|
||||
fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get,
|
||||
fd_filestat_set_size, fd_filestat_set_times, fd_read, fd_pread, fd_seek,
|
||||
fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory,
|
||||
path_filestat_get, path_filestat_set_times, path_link, path_open,
|
||||
path_readlink, path_remove_directory, path_rename, path_symlink,
|
||||
path_unlink_file, fd_renumber
|
||||
} },
|
||||
errors: { errno => trappable Error },
|
||||
});
|
||||
|
||||
/// Async input supplied by the process table.
|
||||
pub type Input = Box<dyn AsyncRead + Unpin + Send + Sync>;
|
||||
/// Async output supplied by the process table.
|
||||
pub type Output = Box<dyn AsyncWrite + Unpin + Send + Sync>;
|
||||
/// A process context with one preopen `/` and a virtual current directory.
|
||||
pub struct State {
|
||||
root: Arc<dyn Layer>,
|
||||
cwd: String,
|
||||
argv: Vec<String>,
|
||||
env: Vec<String>,
|
||||
stdin: DynInputStream,
|
||||
stdout: DynOutputStream,
|
||||
stderr: DynOutputStream,
|
||||
descriptors: BTreeMap<u32, Descriptor>,
|
||||
start: Instant,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct Descriptor {
|
||||
path: String,
|
||||
file: Option<Arc<dyn File>>,
|
||||
offset: u64,
|
||||
flags: types::Fdflags,
|
||||
directory: bool,
|
||||
}
|
||||
impl State {
|
||||
pub fn new(
|
||||
root: Arc<dyn Layer>,
|
||||
cwd: &str,
|
||||
argv: Vec<String>,
|
||||
env: Vec<(String, String)>,
|
||||
stdin: Input,
|
||||
stdout: Output,
|
||||
stderr: Output,
|
||||
) -> Self {
|
||||
let descriptors = BTreeMap::from([(
|
||||
3,
|
||||
Descriptor {
|
||||
path: "/".into(),
|
||||
file: None,
|
||||
offset: 0,
|
||||
flags: types::Fdflags::empty(),
|
||||
directory: true,
|
||||
},
|
||||
)]);
|
||||
Self {
|
||||
root,
|
||||
cwd: resolve("/", cwd),
|
||||
argv,
|
||||
env: env.into_iter().map(|(k, v)| format!("{k}={v}")).collect(),
|
||||
stdin: Box::new(AsyncReadStream::new(stdin)),
|
||||
stdout: Box::new(AsyncWriteStream::new(8192, stdout)),
|
||||
stderr: Box::new(AsyncWriteStream::new(8192, stderr)),
|
||||
descriptors,
|
||||
start: Instant::now(),
|
||||
}
|
||||
}
|
||||
fn fd(&self, fd: types::Fd) -> Result<&Descriptor, types::Error> {
|
||||
self.descriptors
|
||||
.get(&u32::from(fd))
|
||||
.ok_or_else(|| types::Errno::Badf.into())
|
||||
}
|
||||
fn file(&self, fd: types::Fd) -> Result<Arc<dyn File>, types::Error> {
|
||||
self.fd(fd)?
|
||||
.file
|
||||
.clone()
|
||||
.ok_or_else(|| types::Errno::Isdir.into())
|
||||
}
|
||||
fn path(
|
||||
&self,
|
||||
memory: &GuestMemory<'_>,
|
||||
fd: types::Fd,
|
||||
path: GuestPtr<str>,
|
||||
) -> Result<String, types::Error> {
|
||||
let descriptor = self.fd(fd)?;
|
||||
if !descriptor.directory {
|
||||
return Err(types::Errno::Notdir.into());
|
||||
}
|
||||
let path = memory.as_str(path)?.ok_or(types::Errno::Fault)?.to_owned();
|
||||
// The root preopen resolves relative paths against the process cwd.
|
||||
let base = if u32::from(fd) == 3 {
|
||||
&self.cwd
|
||||
} else {
|
||||
&descriptor.path
|
||||
};
|
||||
Ok(resolve(base, &path))
|
||||
}
|
||||
fn clock(&self, id: types::Clockid) -> Result<u64, types::Error> {
|
||||
match id {
|
||||
types::Clockid::Realtime => Ok(SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map_err(|_| types::Errno::Overflow)?
|
||||
.as_nanos() as u64),
|
||||
types::Clockid::Monotonic => Ok(self.start.elapsed().as_nanos() as u64),
|
||||
_ => Err(types::Errno::Inval.into()),
|
||||
}
|
||||
}
|
||||
async fn read(
|
||||
&mut self,
|
||||
memory: &mut GuestMemory<'_>,
|
||||
fd: types::Fd,
|
||||
iovs: types::IovecArray,
|
||||
position: Option<u64>,
|
||||
) -> Result<u32, types::Error> {
|
||||
let mut total = 0;
|
||||
let mut offset = position.unwrap_or_else(|| self.fd(fd).map(|d| d.offset).unwrap_or(0));
|
||||
for ptr in iovs.iter() {
|
||||
let iov = memory.read(ptr?)?;
|
||||
if iov.buf_len == 0 {
|
||||
continue;
|
||||
}
|
||||
let _ = memory.as_slice(iov.buf.as_array(iov.buf_len))?;
|
||||
let bytes = if u32::from(fd) == 0 {
|
||||
match self.stdin.blocking_read(iov.buf_len as usize).await {
|
||||
Ok(bytes) => bytes.to_vec(),
|
||||
Err(StreamError::Closed) => vec![],
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
} else {
|
||||
self.file(fd)?.read(offset, iov.buf_len as usize).await?
|
||||
};
|
||||
memory.copy_from_slice(&bytes, iov.buf.as_array(bytes.len() as u32))?;
|
||||
total += bytes.len() as u32;
|
||||
offset += bytes.len() as u64;
|
||||
if bytes.len() < iov.buf_len as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if position.is_none() && u32::from(fd) > 2 {
|
||||
self.descriptors
|
||||
.get_mut(&u32::from(fd))
|
||||
.ok_or(types::Errno::Badf)?
|
||||
.offset = offset;
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
async fn write(
|
||||
&mut self,
|
||||
memory: &GuestMemory<'_>,
|
||||
fd: types::Fd,
|
||||
iovs: types::CiovecArray,
|
||||
position: Option<u64>,
|
||||
) -> Result<u32, types::Error> {
|
||||
let mut total = 0;
|
||||
let mut offset = position.unwrap_or_else(|| self.fd(fd).map(|d| d.offset).unwrap_or(0));
|
||||
for ptr in iovs.iter() {
|
||||
let iov = memory.read(ptr?)?;
|
||||
let bytes = memory.to_vec(iov.buf.as_array(iov.buf_len))?;
|
||||
let n = match u32::from(fd) {
|
||||
1 => {
|
||||
let len = bytes.len();
|
||||
self.stdout.blocking_write_and_flush(bytes.into()).await?;
|
||||
len
|
||||
}
|
||||
2 => {
|
||||
let len = bytes.len();
|
||||
self.stderr.blocking_write_and_flush(bytes.into()).await?;
|
||||
len
|
||||
}
|
||||
_ => {
|
||||
let descriptor = self.fd(fd)?;
|
||||
if position.is_none() && descriptor.flags.contains(types::Fdflags::APPEND) {
|
||||
self.file(fd)?.append(bytes).await?
|
||||
} else {
|
||||
self.file(fd)?.write(offset, bytes).await?
|
||||
}
|
||||
}
|
||||
};
|
||||
total += n as u32;
|
||||
offset += n as u64;
|
||||
if n < iov.buf_len as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if position.is_none() && u32::from(fd) > 2 {
|
||||
self.descriptors
|
||||
.get_mut(&u32::from(fd))
|
||||
.ok_or(types::Errno::Badf)?
|
||||
.offset = offset;
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
}
|
||||
/// Add async preview1 imports to a core-module linker.
|
||||
pub fn add_to_linker<T: Send + 'static>(
|
||||
linker: &mut wasmtime::Linker<T>,
|
||||
accessor: impl Fn(&mut T) -> &mut State + Copy + Send + Sync + 'static,
|
||||
) -> wasmtime::Result<()> {
|
||||
wasi_snapshot_preview1::add_to_linker(linker, accessor)
|
||||
}
|
||||
impl wiggle::GuestErrorType for types::Errno {
|
||||
fn success() -> Self {
|
||||
Self::Success
|
||||
}
|
||||
}
|
||||
impl From<wiggle::GuestError> for types::Error {
|
||||
fn from(_: wiggle::GuestError) -> Self {
|
||||
types::Errno::Fault.into()
|
||||
}
|
||||
}
|
||||
impl From<StreamError> for types::Error {
|
||||
fn from(error: StreamError) -> Self {
|
||||
match error {
|
||||
StreamError::Closed => types::Errno::Pipe.into(),
|
||||
StreamError::LastOperationFailed(error) => match error.downcast::<io::Error>() {
|
||||
Ok(error) => error.into(),
|
||||
Err(error) => types::Error::trap(error),
|
||||
},
|
||||
StreamError::Trap(error) => types::Error::trap(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<io::Error> for types::Error {
|
||||
fn from(e: io::Error) -> Self {
|
||||
errno(&e).into()
|
||||
}
|
||||
}
|
||||
fn errno(e: &io::Error) -> types::Errno {
|
||||
use io::ErrorKind::*;
|
||||
match e.kind() {
|
||||
NotFound => types::Errno::Noent,
|
||||
AlreadyExists => types::Errno::Exist,
|
||||
PermissionDenied => types::Errno::Acces,
|
||||
NotADirectory => types::Errno::Notdir,
|
||||
IsADirectory => types::Errno::Isdir,
|
||||
DirectoryNotEmpty => types::Errno::Notempty,
|
||||
InvalidInput => types::Errno::Inval,
|
||||
Unsupported => types::Errno::Notsup,
|
||||
ReadOnlyFilesystem => types::Errno::Rofs,
|
||||
CrossesDevices => types::Errno::Xdev,
|
||||
WouldBlock => types::Errno::Again,
|
||||
Interrupted => types::Errno::Intr,
|
||||
_ => types::Errno::Io,
|
||||
}
|
||||
}
|
||||
fn filetype(s: &Stat) -> types::Filetype {
|
||||
if s.symlink {
|
||||
types::Filetype::SymbolicLink
|
||||
} else if s.directory {
|
||||
types::Filetype::Directory
|
||||
} else {
|
||||
types::Filetype::RegularFile
|
||||
}
|
||||
}
|
||||
fn timestamp(t: Option<SystemTime>) -> u64 {
|
||||
t.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
|
||||
.map(|d| d.as_nanos() as u64)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
fn stat(s: Stat, path: &str) -> types::Filestat {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||
path.hash(&mut hash);
|
||||
types::Filestat {
|
||||
dev: 1,
|
||||
ino: hash.finish(),
|
||||
filetype: filetype(&s),
|
||||
nlink: 1,
|
||||
size: s.size,
|
||||
atim: timestamp(s.accessed),
|
||||
mtim: timestamp(s.modified),
|
||||
ctim: timestamp(s.modified),
|
||||
}
|
||||
}
|
||||
fn times(atim: u64, mtim: u64, flags: types::Fstflags) -> (Option<SystemTime>, Option<SystemTime>) {
|
||||
let time = |value, set, now| {
|
||||
if flags.contains(now) {
|
||||
Some(SystemTime::now())
|
||||
} else if flags.contains(set) {
|
||||
SystemTime::UNIX_EPOCH.checked_add(Duration::from_nanos(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
(
|
||||
time(atim, types::Fstflags::ATIM, types::Fstflags::ATIM_NOW),
|
||||
time(mtim, types::Fstflags::MTIM, types::Fstflags::MTIM_NOW),
|
||||
)
|
||||
}
|
||||
fn strings(
|
||||
memory: &mut GuestMemory<'_>,
|
||||
values: &[String],
|
||||
mut ptr: GuestPtr<GuestPtr<u8>>,
|
||||
mut buf: GuestPtr<u8>,
|
||||
) -> Result<(), types::Error> {
|
||||
for value in values {
|
||||
memory.write(ptr, buf)?;
|
||||
ptr = ptr.add(1)?;
|
||||
let mut bytes = value.as_bytes().to_vec();
|
||||
bytes.push(0);
|
||||
memory.copy_from_slice(&bytes, buf.as_array(bytes.len() as u32))?;
|
||||
buf = buf.add(bytes.len() as u32)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn sizes(values: &[String]) -> Result<(u32, u32), types::Error> {
|
||||
Ok((
|
||||
values
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| types::Errno::Overflow)?,
|
||||
values
|
||||
.iter()
|
||||
.map(|s| s.len() + 1)
|
||||
.sum::<usize>()
|
||||
.try_into()
|
||||
.map_err(|_| types::Errno::Overflow)?,
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -0,0 +1,750 @@
|
||||
;; Type names used by low-level WASI interfaces.
|
||||
;;
|
||||
;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi).
|
||||
;;
|
||||
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/blob/main/legacy/tools/witx-docs.md)
|
||||
;; for an explanation of what that means.
|
||||
|
||||
(typename $size u32)
|
||||
|
||||
;;; Non-negative file size or length of a region within a file.
|
||||
(typename $filesize u64)
|
||||
|
||||
;;; Timestamp in nanoseconds.
|
||||
(typename $timestamp u64)
|
||||
|
||||
;;; Identifiers for clocks.
|
||||
(typename $clockid
|
||||
(enum (@witx tag u32)
|
||||
;;; The clock measuring real time. Time value zero corresponds with
|
||||
;;; 1970-01-01T00:00:00Z.
|
||||
$realtime
|
||||
;;; The store-wide monotonic clock, which is defined as a clock measuring
|
||||
;;; real time, whose value cannot be adjusted and which cannot have negative
|
||||
;;; clock jumps. The epoch of this clock is undefined. The absolute time
|
||||
;;; value of this clock therefore has no meaning.
|
||||
$monotonic
|
||||
;;; The CPU-time clock associated with the current process.
|
||||
$process_cputime_id
|
||||
;;; The CPU-time clock associated with the current thread.
|
||||
$thread_cputime_id
|
||||
)
|
||||
)
|
||||
|
||||
;;; Error codes returned by functions.
|
||||
;;; Not all of these error codes are returned by the functions provided by this
|
||||
;;; API; some are used in higher-level library layers, and others are provided
|
||||
;;; merely for alignment with POSIX.
|
||||
(typename $errno
|
||||
(enum (@witx tag u16)
|
||||
;;; No error occurred. System call completed successfully.
|
||||
$success
|
||||
;;; Argument list too long.
|
||||
$2big
|
||||
;;; Permission denied.
|
||||
$acces
|
||||
;;; Address in use.
|
||||
$addrinuse
|
||||
;;; Address not available.
|
||||
$addrnotavail
|
||||
;;; Address family not supported.
|
||||
$afnosupport
|
||||
;;; Resource unavailable, or operation would block.
|
||||
$again
|
||||
;;; Connection already in progress.
|
||||
$already
|
||||
;;; Bad file descriptor.
|
||||
$badf
|
||||
;;; Bad message.
|
||||
$badmsg
|
||||
;;; Device or resource busy.
|
||||
$busy
|
||||
;;; Operation canceled.
|
||||
$canceled
|
||||
;;; No child processes.
|
||||
$child
|
||||
;;; Connection aborted.
|
||||
$connaborted
|
||||
;;; Connection refused.
|
||||
$connrefused
|
||||
;;; Connection reset.
|
||||
$connreset
|
||||
;;; Resource deadlock would occur.
|
||||
$deadlk
|
||||
;;; Destination address required.
|
||||
$destaddrreq
|
||||
;;; Mathematics argument out of domain of function.
|
||||
$dom
|
||||
;;; Reserved.
|
||||
$dquot
|
||||
;;; File exists.
|
||||
$exist
|
||||
;;; Bad address.
|
||||
$fault
|
||||
;;; File too large.
|
||||
$fbig
|
||||
;;; Host is unreachable.
|
||||
$hostunreach
|
||||
;;; Identifier removed.
|
||||
$idrm
|
||||
;;; Illegal byte sequence.
|
||||
$ilseq
|
||||
;;; Operation in progress.
|
||||
$inprogress
|
||||
;;; Interrupted function.
|
||||
$intr
|
||||
;;; Invalid argument.
|
||||
$inval
|
||||
;;; I/O error.
|
||||
$io
|
||||
;;; Socket is connected.
|
||||
$isconn
|
||||
;;; Is a directory.
|
||||
$isdir
|
||||
;;; Too many levels of symbolic links.
|
||||
$loop
|
||||
;;; File descriptor value too large.
|
||||
$mfile
|
||||
;;; Too many links.
|
||||
$mlink
|
||||
;;; Message too large.
|
||||
$msgsize
|
||||
;;; Reserved.
|
||||
$multihop
|
||||
;;; Filename too long.
|
||||
$nametoolong
|
||||
;;; Network is down.
|
||||
$netdown
|
||||
;;; Connection aborted by network.
|
||||
$netreset
|
||||
;;; Network unreachable.
|
||||
$netunreach
|
||||
;;; Too many files open in system.
|
||||
$nfile
|
||||
;;; No buffer space available.
|
||||
$nobufs
|
||||
;;; No such device.
|
||||
$nodev
|
||||
;;; No such file or directory.
|
||||
$noent
|
||||
;;; Executable file format error.
|
||||
$noexec
|
||||
;;; No locks available.
|
||||
$nolck
|
||||
;;; Reserved.
|
||||
$nolink
|
||||
;;; Not enough space.
|
||||
$nomem
|
||||
;;; No message of the desired type.
|
||||
$nomsg
|
||||
;;; Protocol not available.
|
||||
$noprotoopt
|
||||
;;; No space left on device.
|
||||
$nospc
|
||||
;;; Function not supported.
|
||||
$nosys
|
||||
;;; The socket is not connected.
|
||||
$notconn
|
||||
;;; Not a directory or a symbolic link to a directory.
|
||||
$notdir
|
||||
;;; Directory not empty.
|
||||
$notempty
|
||||
;;; State not recoverable.
|
||||
$notrecoverable
|
||||
;;; Not a socket.
|
||||
$notsock
|
||||
;;; Not supported, or operation not supported on socket.
|
||||
$notsup
|
||||
;;; Inappropriate I/O control operation.
|
||||
$notty
|
||||
;;; No such device or address.
|
||||
$nxio
|
||||
;;; Value too large to be stored in data type.
|
||||
$overflow
|
||||
;;; Previous owner died.
|
||||
$ownerdead
|
||||
;;; Operation not permitted.
|
||||
$perm
|
||||
;;; Broken pipe.
|
||||
$pipe
|
||||
;;; Protocol error.
|
||||
$proto
|
||||
;;; Protocol not supported.
|
||||
$protonosupport
|
||||
;;; Protocol wrong type for socket.
|
||||
$prototype
|
||||
;;; Result too large.
|
||||
$range
|
||||
;;; Read-only file system.
|
||||
$rofs
|
||||
;;; Invalid seek.
|
||||
$spipe
|
||||
;;; No such process.
|
||||
$srch
|
||||
;;; Reserved.
|
||||
$stale
|
||||
;;; Connection timed out.
|
||||
$timedout
|
||||
;;; Text file busy.
|
||||
$txtbsy
|
||||
;;; Cross-device link.
|
||||
$xdev
|
||||
;;; Extension: Capabilities insufficient.
|
||||
$notcapable
|
||||
)
|
||||
)
|
||||
|
||||
;;; File descriptor rights, determining which actions may be performed.
|
||||
(typename $rights
|
||||
(flags (@witx repr u64)
|
||||
;;; The right to invoke `fd_datasync`.
|
||||
;;
|
||||
;;; If `path_open` is set, includes the right to invoke
|
||||
;;; `path_open` with `fdflags::dsync`.
|
||||
$fd_datasync
|
||||
;;; The right to invoke `fd_read` and `sock_recv`.
|
||||
;;
|
||||
;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pread`.
|
||||
$fd_read
|
||||
;;; The right to invoke `fd_seek`. This flag implies `rights::fd_tell`.
|
||||
$fd_seek
|
||||
;;; The right to invoke `fd_fdstat_set_flags`.
|
||||
$fd_fdstat_set_flags
|
||||
;;; The right to invoke `fd_sync`.
|
||||
;;
|
||||
;;; If `path_open` is set, includes the right to invoke
|
||||
;;; `path_open` with `fdflags::rsync` and `fdflags::dsync`.
|
||||
$fd_sync
|
||||
;;; The right to invoke `fd_seek` in such a way that the file offset
|
||||
;;; remains unaltered (i.e., `whence::cur` with offset zero), or to
|
||||
;;; invoke `fd_tell`.
|
||||
$fd_tell
|
||||
;;; The right to invoke `fd_write` and `sock_send`.
|
||||
;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`.
|
||||
$fd_write
|
||||
;;; The right to invoke `fd_advise`.
|
||||
$fd_advise
|
||||
;;; The right to invoke `fd_allocate`.
|
||||
$fd_allocate
|
||||
;;; The right to invoke `path_create_directory`.
|
||||
$path_create_directory
|
||||
;;; If `path_open` is set, the right to invoke `path_open` with `oflags::creat`.
|
||||
$path_create_file
|
||||
;;; The right to invoke `path_link` with the file descriptor as the
|
||||
;;; source directory.
|
||||
$path_link_source
|
||||
;;; The right to invoke `path_link` with the file descriptor as the
|
||||
;;; target directory.
|
||||
$path_link_target
|
||||
;;; The right to invoke `path_open`.
|
||||
$path_open
|
||||
;;; The right to invoke `fd_readdir`.
|
||||
$fd_readdir
|
||||
;;; The right to invoke `path_readlink`.
|
||||
$path_readlink
|
||||
;;; The right to invoke `path_rename` with the file descriptor as the source directory.
|
||||
$path_rename_source
|
||||
;;; The right to invoke `path_rename` with the file descriptor as the target directory.
|
||||
$path_rename_target
|
||||
;;; The right to invoke `path_filestat_get`.
|
||||
$path_filestat_get
|
||||
;;; The right to change a file's size (there is no `path_filestat_set_size`).
|
||||
;;; If `path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`.
|
||||
$path_filestat_set_size
|
||||
;;; The right to invoke `path_filestat_set_times`.
|
||||
$path_filestat_set_times
|
||||
;;; The right to invoke `fd_filestat_get`.
|
||||
$fd_filestat_get
|
||||
;;; The right to invoke `fd_filestat_set_size`.
|
||||
$fd_filestat_set_size
|
||||
;;; The right to invoke `fd_filestat_set_times`.
|
||||
$fd_filestat_set_times
|
||||
;;; The right to invoke `path_symlink`.
|
||||
$path_symlink
|
||||
;;; The right to invoke `path_remove_directory`.
|
||||
$path_remove_directory
|
||||
;;; The right to invoke `path_unlink_file`.
|
||||
$path_unlink_file
|
||||
;;; If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`.
|
||||
;;; If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`.
|
||||
$poll_fd_readwrite
|
||||
;;; The right to invoke `sock_shutdown`.
|
||||
$sock_shutdown
|
||||
;;; The right to invoke `sock_accept`.
|
||||
$sock_accept
|
||||
)
|
||||
)
|
||||
|
||||
;;; A file descriptor handle.
|
||||
(typename $fd (handle))
|
||||
|
||||
;;; A region of memory for scatter/gather reads.
|
||||
(typename $iovec
|
||||
(record
|
||||
;;; The address of the buffer to be filled.
|
||||
(field $buf (@witx pointer u8))
|
||||
;;; The length of the buffer to be filled.
|
||||
(field $buf_len $size)
|
||||
)
|
||||
)
|
||||
|
||||
;;; A region of memory for scatter/gather writes.
|
||||
(typename $ciovec
|
||||
(record
|
||||
;;; The address of the buffer to be written.
|
||||
(field $buf (@witx const_pointer u8))
|
||||
;;; The length of the buffer to be written.
|
||||
(field $buf_len $size)
|
||||
)
|
||||
)
|
||||
|
||||
(typename $iovec_array (list $iovec))
|
||||
(typename $ciovec_array (list $ciovec))
|
||||
|
||||
;;; Relative offset within a file.
|
||||
(typename $filedelta s64)
|
||||
|
||||
;;; The position relative to which to set the offset of the file descriptor.
|
||||
(typename $whence
|
||||
(enum (@witx tag u8)
|
||||
;;; Seek relative to start-of-file.
|
||||
$set
|
||||
;;; Seek relative to current position.
|
||||
$cur
|
||||
;;; Seek relative to end-of-file.
|
||||
$end
|
||||
)
|
||||
)
|
||||
|
||||
;;; A reference to the offset of a directory entry.
|
||||
;;;
|
||||
;;; The value 0 signifies the start of the directory.
|
||||
(typename $dircookie u64)
|
||||
|
||||
;;; The type for the `dirent::d_namlen` field of `dirent` struct.
|
||||
(typename $dirnamlen u32)
|
||||
|
||||
;;; File serial number that is unique within its file system.
|
||||
(typename $inode u64)
|
||||
|
||||
;;; The type of a file descriptor or file.
|
||||
(typename $filetype
|
||||
(enum (@witx tag u8)
|
||||
;;; The type of the file descriptor or file is unknown or is different from any of the other types specified.
|
||||
$unknown
|
||||
;;; The file descriptor or file refers to a block device inode.
|
||||
$block_device
|
||||
;;; The file descriptor or file refers to a character device inode.
|
||||
$character_device
|
||||
;;; The file descriptor or file refers to a directory inode.
|
||||
$directory
|
||||
;;; The file descriptor or file refers to a regular file inode.
|
||||
$regular_file
|
||||
;;; The file descriptor or file refers to a datagram socket.
|
||||
$socket_dgram
|
||||
;;; The file descriptor or file refers to a byte-stream socket.
|
||||
$socket_stream
|
||||
;;; The file refers to a symbolic link inode.
|
||||
$symbolic_link
|
||||
)
|
||||
)
|
||||
|
||||
;;; A directory entry.
|
||||
(typename $dirent
|
||||
(record
|
||||
;;; The offset of the next directory entry stored in this directory.
|
||||
(field $d_next $dircookie)
|
||||
;;; The serial number of the file referred to by this directory entry.
|
||||
(field $d_ino $inode)
|
||||
;;; The length of the name of the directory entry.
|
||||
(field $d_namlen $dirnamlen)
|
||||
;;; The type of the file referred to by this directory entry.
|
||||
(field $d_type $filetype)
|
||||
)
|
||||
)
|
||||
|
||||
;;; File or memory access pattern advisory information.
|
||||
(typename $advice
|
||||
(enum (@witx tag u8)
|
||||
;;; The application has no advice to give on its behavior with respect to the specified data.
|
||||
$normal
|
||||
;;; The application expects to access the specified data sequentially from lower offsets to higher offsets.
|
||||
$sequential
|
||||
;;; The application expects to access the specified data in a random order.
|
||||
$random
|
||||
;;; The application expects to access the specified data in the near future.
|
||||
$willneed
|
||||
;;; The application expects that it will not access the specified data in the near future.
|
||||
$dontneed
|
||||
;;; The application expects to access the specified data once and then not reuse it thereafter.
|
||||
$noreuse
|
||||
)
|
||||
)
|
||||
|
||||
;;; File descriptor flags.
|
||||
(typename $fdflags
|
||||
(flags (@witx repr u16)
|
||||
;;; Append mode: Data written to the file is always appended to the file's end.
|
||||
$append
|
||||
;;; Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized.
|
||||
$dsync
|
||||
;;; Non-blocking mode.
|
||||
$nonblock
|
||||
;;; Synchronized read I/O operations.
|
||||
$rsync
|
||||
;;; Write according to synchronized I/O file integrity completion. In
|
||||
;;; addition to synchronizing the data stored in the file, the implementation
|
||||
;;; may also synchronously update the file's metadata.
|
||||
$sync
|
||||
)
|
||||
)
|
||||
|
||||
;;; File descriptor attributes.
|
||||
(typename $fdstat
|
||||
(record
|
||||
;;; File type.
|
||||
(field $fs_filetype $filetype)
|
||||
;;; File descriptor flags.
|
||||
(field $fs_flags $fdflags)
|
||||
;;; Rights that apply to this file descriptor.
|
||||
(field $fs_rights_base $rights)
|
||||
;;; Maximum set of rights that may be installed on new file descriptors that
|
||||
;;; are created through this file descriptor, e.g., through `path_open`.
|
||||
(field $fs_rights_inheriting $rights)
|
||||
)
|
||||
)
|
||||
|
||||
;;; Identifier for a device containing a file system. Can be used in combination
|
||||
;;; with `inode` to uniquely identify a file or directory in the filesystem.
|
||||
(typename $device u64)
|
||||
|
||||
;;; Which file time attributes to adjust.
|
||||
(typename $fstflags
|
||||
(flags (@witx repr u16)
|
||||
;;; Adjust the last data access timestamp to the value stored in `filestat::atim`.
|
||||
$atim
|
||||
;;; Adjust the last data access timestamp to the time of clock `clockid::realtime`.
|
||||
$atim_now
|
||||
;;; Adjust the last data modification timestamp to the value stored in `filestat::mtim`.
|
||||
$mtim
|
||||
;;; Adjust the last data modification timestamp to the time of clock `clockid::realtime`.
|
||||
$mtim_now
|
||||
)
|
||||
)
|
||||
|
||||
;;; Flags determining the method of how paths are resolved.
|
||||
(typename $lookupflags
|
||||
(flags (@witx repr u32)
|
||||
;;; As long as the resolved path corresponds to a symbolic link, it is expanded.
|
||||
$symlink_follow
|
||||
)
|
||||
)
|
||||
|
||||
;;; Open flags used by `path_open`.
|
||||
(typename $oflags
|
||||
(flags (@witx repr u16)
|
||||
;;; Create file if it does not exist.
|
||||
$creat
|
||||
;;; Fail if not a directory.
|
||||
$directory
|
||||
;;; Fail if file already exists.
|
||||
$excl
|
||||
;;; Truncate file to size 0.
|
||||
$trunc
|
||||
)
|
||||
)
|
||||
|
||||
;;; Number of hard links to an inode.
|
||||
(typename $linkcount u64)
|
||||
|
||||
;;; File attributes.
|
||||
(typename $filestat
|
||||
(record
|
||||
;;; Device ID of device containing the file.
|
||||
(field $dev $device)
|
||||
;;; File serial number.
|
||||
(field $ino $inode)
|
||||
;;; File type.
|
||||
(field $filetype $filetype)
|
||||
;;; Number of hard links to the file.
|
||||
(field $nlink $linkcount)
|
||||
;;; For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link.
|
||||
(field $size $filesize)
|
||||
;;; Last data access timestamp.
|
||||
(field $atim $timestamp)
|
||||
;;; Last data modification timestamp.
|
||||
(field $mtim $timestamp)
|
||||
;;; Last file status change timestamp.
|
||||
(field $ctim $timestamp)
|
||||
)
|
||||
)
|
||||
|
||||
;;; User-provided value that may be attached to objects that is retained when
|
||||
;;; extracted from the implementation.
|
||||
(typename $userdata u64)
|
||||
|
||||
;;; Type of a subscription to an event or its occurrence.
|
||||
(typename $eventtype
|
||||
(enum (@witx tag u8)
|
||||
;;; The time value of clock `subscription_clock::id` has
|
||||
;;; reached timestamp `subscription_clock::timeout`.
|
||||
$clock
|
||||
;;; File descriptor `subscription_fd_readwrite::file_descriptor` has data
|
||||
;;; available for reading. This event always triggers for regular files.
|
||||
$fd_read
|
||||
;;; File descriptor `subscription_fd_readwrite::file_descriptor` has capacity
|
||||
;;; available for writing. This event always triggers for regular files.
|
||||
$fd_write
|
||||
)
|
||||
)
|
||||
|
||||
;;; The state of the file descriptor subscribed to with
|
||||
;;; `eventtype::fd_read` or `eventtype::fd_write`.
|
||||
(typename $eventrwflags
|
||||
(flags (@witx repr u16)
|
||||
;;; The peer of this socket has closed or disconnected.
|
||||
$fd_readwrite_hangup
|
||||
)
|
||||
)
|
||||
|
||||
;;; The contents of an `event` when type is `eventtype::fd_read` or
|
||||
;;; `eventtype::fd_write`.
|
||||
(typename $event_fd_readwrite
|
||||
(record
|
||||
;;; The number of bytes available for reading or writing.
|
||||
(field $nbytes $filesize)
|
||||
;;; The state of the file descriptor.
|
||||
(field $flags $eventrwflags)
|
||||
)
|
||||
)
|
||||
|
||||
;;; An event that occurred.
|
||||
(typename $event
|
||||
(record
|
||||
;;; User-provided value that got attached to `subscription::userdata`.
|
||||
(field $userdata $userdata)
|
||||
;;; If non-zero, an error that occurred while processing the subscription request.
|
||||
(field $error $errno)
|
||||
;;; The type of event that occurred
|
||||
(field $type $eventtype)
|
||||
;;; The contents of the event, if it is an `eventtype::fd_read` or
|
||||
;;; `eventtype::fd_write`. `eventtype::clock` events ignore this field.
|
||||
(field $fd_readwrite $event_fd_readwrite)
|
||||
)
|
||||
)
|
||||
|
||||
;;; Flags determining how to interpret the timestamp provided in
|
||||
;;; `subscription_clock::timeout`.
|
||||
(typename $subclockflags
|
||||
(flags (@witx repr u16)
|
||||
;;; If set, treat the timestamp provided in
|
||||
;;; `subscription_clock::timeout` as an absolute timestamp of clock
|
||||
;;; `subscription_clock::id`. If clear, treat the timestamp
|
||||
;;; provided in `subscription_clock::timeout` relative to the
|
||||
;;; current time value of clock `subscription_clock::id`.
|
||||
$subscription_clock_abstime
|
||||
)
|
||||
)
|
||||
|
||||
;;; The contents of a `subscription` when type is `eventtype::clock`.
|
||||
(typename $subscription_clock
|
||||
(record
|
||||
;;; The clock against which to compare the timestamp.
|
||||
(field $id $clockid)
|
||||
;;; The absolute or relative timestamp.
|
||||
(field $timeout $timestamp)
|
||||
;;; The amount of time that the implementation may wait additionally
|
||||
;;; to coalesce with other events.
|
||||
(field $precision $timestamp)
|
||||
;;; Flags specifying whether the timeout is absolute or relative
|
||||
(field $flags $subclockflags)
|
||||
)
|
||||
)
|
||||
|
||||
;;; The contents of a `subscription` when type is type is
|
||||
;;; `eventtype::fd_read` or `eventtype::fd_write`.
|
||||
(typename $subscription_fd_readwrite
|
||||
(record
|
||||
;;; The file descriptor on which to wait for it to become ready for reading or writing.
|
||||
(field $file_descriptor $fd)
|
||||
)
|
||||
)
|
||||
|
||||
;;; The contents of a `subscription`.
|
||||
(typename $subscription_u
|
||||
(union
|
||||
(@witx tag $eventtype)
|
||||
$subscription_clock
|
||||
$subscription_fd_readwrite
|
||||
$subscription_fd_readwrite
|
||||
)
|
||||
)
|
||||
|
||||
;;; Subscription to an event.
|
||||
(typename $subscription
|
||||
(record
|
||||
;;; User-provided value that is attached to the subscription in the
|
||||
;;; implementation and returned through `event::userdata`.
|
||||
(field $userdata $userdata)
|
||||
;;; The type of the event to which to subscribe, and its contents
|
||||
(field $u $subscription_u)
|
||||
)
|
||||
)
|
||||
|
||||
;;; Exit code generated by a process when exiting.
|
||||
(typename $exitcode u32)
|
||||
|
||||
;;; Signal condition.
|
||||
(typename $signal
|
||||
(enum (@witx tag u8)
|
||||
;;; No signal. Note that POSIX has special semantics for `kill(pid, 0)`,
|
||||
;;; so this value is reserved.
|
||||
$none
|
||||
;;; Hangup.
|
||||
;;; Action: Terminates the process.
|
||||
$hup
|
||||
;;; Terminate interrupt signal.
|
||||
;;; Action: Terminates the process.
|
||||
$int
|
||||
;;; Terminal quit signal.
|
||||
;;; Action: Terminates the process.
|
||||
$quit
|
||||
;;; Illegal instruction.
|
||||
;;; Action: Terminates the process.
|
||||
$ill
|
||||
;;; Trace/breakpoint trap.
|
||||
;;; Action: Terminates the process.
|
||||
$trap
|
||||
;;; Process abort signal.
|
||||
;;; Action: Terminates the process.
|
||||
$abrt
|
||||
;;; Access to an undefined portion of a memory object.
|
||||
;;; Action: Terminates the process.
|
||||
$bus
|
||||
;;; Erroneous arithmetic operation.
|
||||
;;; Action: Terminates the process.
|
||||
$fpe
|
||||
;;; Kill.
|
||||
;;; Action: Terminates the process.
|
||||
$kill
|
||||
;;; User-defined signal 1.
|
||||
;;; Action: Terminates the process.
|
||||
$usr1
|
||||
;;; Invalid memory reference.
|
||||
;;; Action: Terminates the process.
|
||||
$segv
|
||||
;;; User-defined signal 2.
|
||||
;;; Action: Terminates the process.
|
||||
$usr2
|
||||
;;; Write on a pipe with no one to read it.
|
||||
;;; Action: Ignored.
|
||||
$pipe
|
||||
;;; Alarm clock.
|
||||
;;; Action: Terminates the process.
|
||||
$alrm
|
||||
;;; Termination signal.
|
||||
;;; Action: Terminates the process.
|
||||
$term
|
||||
;;; Child process terminated, stopped, or continued.
|
||||
;;; Action: Ignored.
|
||||
$chld
|
||||
;;; Continue executing, if stopped.
|
||||
;;; Action: Continues executing, if stopped.
|
||||
$cont
|
||||
;;; Stop executing.
|
||||
;;; Action: Stops executing.
|
||||
$stop
|
||||
;;; Terminal stop signal.
|
||||
;;; Action: Stops executing.
|
||||
$tstp
|
||||
;;; Background process attempting read.
|
||||
;;; Action: Stops executing.
|
||||
$ttin
|
||||
;;; Background process attempting write.
|
||||
;;; Action: Stops executing.
|
||||
$ttou
|
||||
;;; High bandwidth data is available at a socket.
|
||||
;;; Action: Ignored.
|
||||
$urg
|
||||
;;; CPU time limit exceeded.
|
||||
;;; Action: Terminates the process.
|
||||
$xcpu
|
||||
;;; File size limit exceeded.
|
||||
;;; Action: Terminates the process.
|
||||
$xfsz
|
||||
;;; Virtual timer expired.
|
||||
;;; Action: Terminates the process.
|
||||
$vtalrm
|
||||
;;; Profiling timer expired.
|
||||
;;; Action: Terminates the process.
|
||||
$prof
|
||||
;;; Window changed.
|
||||
;;; Action: Ignored.
|
||||
$winch
|
||||
;;; I/O possible.
|
||||
;;; Action: Terminates the process.
|
||||
$poll
|
||||
;;; Power failure.
|
||||
;;; Action: Terminates the process.
|
||||
$pwr
|
||||
;;; Bad system call.
|
||||
;;; Action: Terminates the process.
|
||||
$sys
|
||||
)
|
||||
)
|
||||
|
||||
;;; Flags provided to `sock_recv`.
|
||||
(typename $riflags
|
||||
(flags (@witx repr u16)
|
||||
;;; Returns the message without removing it from the socket's receive queue.
|
||||
$recv_peek
|
||||
;;; On byte-stream sockets, block until the full amount of data can be returned.
|
||||
$recv_waitall
|
||||
)
|
||||
)
|
||||
|
||||
;;; Flags returned by `sock_recv`.
|
||||
(typename $roflags
|
||||
(flags (@witx repr u16)
|
||||
;;; Returned by `sock_recv`: Message data has been truncated.
|
||||
$recv_data_truncated
|
||||
)
|
||||
)
|
||||
|
||||
;;; Flags provided to `sock_send`. As there are currently no flags
|
||||
;;; defined, it must be set to zero.
|
||||
(typename $siflags u16)
|
||||
|
||||
;;; Which channels on a socket to shut down.
|
||||
(typename $sdflags
|
||||
(flags (@witx repr u8)
|
||||
;;; Disables further receive operations.
|
||||
$rd
|
||||
;;; Disables further send operations.
|
||||
$wr
|
||||
)
|
||||
)
|
||||
|
||||
;;; Identifiers for preopened capabilities.
|
||||
(typename $preopentype
|
||||
(enum (@witx tag u8)
|
||||
;;; A pre-opened directory.
|
||||
$dir
|
||||
)
|
||||
)
|
||||
|
||||
;;; The contents of a $prestat when type is `preopentype::dir`.
|
||||
(typename $prestat_dir
|
||||
(record
|
||||
;;; The length of the directory name for use with `fd_prestat_dir_name`.
|
||||
(field $pr_name_len $size)
|
||||
)
|
||||
)
|
||||
|
||||
;;; Information about a pre-opened capability.
|
||||
(typename $prestat
|
||||
(union (@witx tag $preopentype)
|
||||
$prestat_dir
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,521 @@
|
||||
;; WASI Preview. This is an evolution of the API that WASI initially
|
||||
;; launched with.
|
||||
;;
|
||||
;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi).
|
||||
;;
|
||||
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/blob/main/legacy/tools/witx-docs.md)
|
||||
;; for an explanation of what that means.
|
||||
|
||||
(use "typenames.witx")
|
||||
|
||||
(module $wasi_snapshot_preview1
|
||||
;;; Linear memory to be accessed by WASI functions that need it.
|
||||
(import "memory" (memory))
|
||||
|
||||
;;; Read command-line argument data.
|
||||
;;; The size of the array should match that returned by `args_sizes_get`.
|
||||
;;; Each argument is expected to be `\0` terminated.
|
||||
(@interface func (export "args_get")
|
||||
(param $argv (@witx pointer (@witx pointer u8)))
|
||||
(param $argv_buf (@witx pointer u8))
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
;;; Return command-line argument data sizes.
|
||||
(@interface func (export "args_sizes_get")
|
||||
;;; Returns the number of arguments and the size of the argument string
|
||||
;;; data, or an error.
|
||||
(result $error (expected (tuple $size $size) (error $errno)))
|
||||
)
|
||||
|
||||
;;; Read environment variable data.
|
||||
;;; The sizes of the buffers should match that returned by `environ_sizes_get`.
|
||||
;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
|
||||
(@interface func (export "environ_get")
|
||||
(param $environ (@witx pointer (@witx pointer u8)))
|
||||
(param $environ_buf (@witx pointer u8))
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
;;; Return environment variable data sizes.
|
||||
(@interface func (export "environ_sizes_get")
|
||||
;;; Returns the number of environment variable arguments and the size of the
|
||||
;;; environment variable data.
|
||||
(result $error (expected (tuple $size $size) (error $errno)))
|
||||
)
|
||||
|
||||
;;; Return the resolution of a clock.
|
||||
;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks,
|
||||
;;; return `errno::inval`.
|
||||
;;; Note: This is similar to `clock_getres` in POSIX.
|
||||
(@interface func (export "clock_res_get")
|
||||
;;; The clock for which to return the resolution.
|
||||
(param $id $clockid)
|
||||
;;; The resolution of the clock, or an error if one happened.
|
||||
(result $error (expected $timestamp (error $errno)))
|
||||
)
|
||||
;;; Return the time value of a clock.
|
||||
;;; Note: This is similar to `clock_gettime` in POSIX.
|
||||
(@interface func (export "clock_time_get")
|
||||
;;; The clock for which to return the time.
|
||||
(param $id $clockid)
|
||||
;;; The maximum lag (exclusive) that the returned time value may have, compared to its actual value.
|
||||
(param $precision $timestamp)
|
||||
;;; The time value of the clock.
|
||||
(result $error (expected $timestamp (error $errno)))
|
||||
)
|
||||
|
||||
;;; Provide file advisory information on a file descriptor.
|
||||
;;; Note: This is similar to `posix_fadvise` in POSIX.
|
||||
(@interface func (export "fd_advise")
|
||||
(param $fd $fd)
|
||||
;;; The offset within the file to which the advisory applies.
|
||||
(param $offset $filesize)
|
||||
;;; The length of the region to which the advisory applies.
|
||||
(param $len $filesize)
|
||||
;;; The advice.
|
||||
(param $advice $advice)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Force the allocation of space in a file.
|
||||
;;; Note: This is similar to `posix_fallocate` in POSIX.
|
||||
(@interface func (export "fd_allocate")
|
||||
(param $fd $fd)
|
||||
;;; The offset at which to start the allocation.
|
||||
(param $offset $filesize)
|
||||
;;; The length of the area that is allocated.
|
||||
(param $len $filesize)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Close a file descriptor.
|
||||
;;; Note: This is similar to `close` in POSIX.
|
||||
(@interface func (export "fd_close")
|
||||
(param $fd $fd)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Synchronize the data of a file to disk.
|
||||
;;; Note: This is similar to `fdatasync` in POSIX.
|
||||
(@interface func (export "fd_datasync")
|
||||
(param $fd $fd)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Get the attributes of a file descriptor.
|
||||
;;; Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
|
||||
(@interface func (export "fd_fdstat_get")
|
||||
(param $fd $fd)
|
||||
;;; The buffer where the file descriptor's attributes are stored.
|
||||
(result $error (expected $fdstat (error $errno)))
|
||||
)
|
||||
|
||||
;;; Adjust the flags associated with a file descriptor.
|
||||
;;; Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
|
||||
(@interface func (export "fd_fdstat_set_flags")
|
||||
(param $fd $fd)
|
||||
;;; The desired values of the file descriptor flags.
|
||||
(param $flags $fdflags)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Adjust the rights associated with a file descriptor.
|
||||
;;; This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights
|
||||
(@interface func (export "fd_fdstat_set_rights")
|
||||
(param $fd $fd)
|
||||
;;; The desired rights of the file descriptor.
|
||||
(param $fs_rights_base $rights)
|
||||
(param $fs_rights_inheriting $rights)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Return the attributes of an open file.
|
||||
(@interface func (export "fd_filestat_get")
|
||||
(param $fd $fd)
|
||||
;;; The buffer where the file's attributes are stored.
|
||||
(result $error (expected $filestat (error $errno)))
|
||||
)
|
||||
|
||||
;;; Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
|
||||
;;; Note: This is similar to `ftruncate` in POSIX.
|
||||
(@interface func (export "fd_filestat_set_size")
|
||||
(param $fd $fd)
|
||||
;;; The desired file size.
|
||||
(param $size $filesize)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Adjust the timestamps of an open file or directory.
|
||||
;;; Note: This is similar to `futimens` in POSIX.
|
||||
(@interface func (export "fd_filestat_set_times")
|
||||
(param $fd $fd)
|
||||
;;; The desired values of the data access timestamp.
|
||||
(param $atim $timestamp)
|
||||
;;; The desired values of the data modification timestamp.
|
||||
(param $mtim $timestamp)
|
||||
;;; A bitmask indicating which timestamps to adjust.
|
||||
(param $fst_flags $fstflags)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Read from a file descriptor, without using and updating the file descriptor's offset.
|
||||
;;; Note: This is similar to `preadv` in POSIX.
|
||||
(@interface func (export "fd_pread")
|
||||
(param $fd $fd)
|
||||
;;; List of scatter/gather vectors in which to store data.
|
||||
(param $iovs $iovec_array)
|
||||
;;; The offset within the file at which to read.
|
||||
(param $offset $filesize)
|
||||
;;; The number of bytes read.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Return a description of the given preopened file descriptor.
|
||||
(@interface func (export "fd_prestat_get")
|
||||
(param $fd $fd)
|
||||
;;; The buffer where the description is stored.
|
||||
(result $error (expected $prestat (error $errno)))
|
||||
)
|
||||
|
||||
;;; Return a description of the given preopened file descriptor.
|
||||
(@interface func (export "fd_prestat_dir_name")
|
||||
(param $fd $fd)
|
||||
;;; A buffer into which to write the preopened directory name.
|
||||
(param $path (@witx pointer u8))
|
||||
(param $path_len $size)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Write to a file descriptor, without using and updating the file descriptor's offset.
|
||||
;;; Note: This is similar to `pwritev` in POSIX.
|
||||
(@interface func (export "fd_pwrite")
|
||||
(param $fd $fd)
|
||||
;;; List of scatter/gather vectors from which to retrieve data.
|
||||
(param $iovs $ciovec_array)
|
||||
;;; The offset within the file at which to write.
|
||||
(param $offset $filesize)
|
||||
;;; The number of bytes written.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Read from a file descriptor.
|
||||
;;; Note: This is similar to `readv` in POSIX.
|
||||
(@interface func (export "fd_read")
|
||||
(param $fd $fd)
|
||||
;;; List of scatter/gather vectors to which to store data.
|
||||
(param $iovs $iovec_array)
|
||||
;;; The number of bytes read.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Read directory entries from a directory.
|
||||
;;; When successful, the contents of the output buffer consist of a sequence of
|
||||
;;; directory entries. Each directory entry consists of a `dirent` object,
|
||||
;;; followed by `dirent::d_namlen` bytes holding the name of the directory
|
||||
;;; entry.
|
||||
;;
|
||||
;;; This function fills the output buffer as much as possible, potentially
|
||||
;;; truncating the last directory entry. This allows the caller to grow its
|
||||
;;; read buffer size in case it's too small to fit a single large directory
|
||||
;;; entry, or skip the oversized directory entry.
|
||||
(@interface func (export "fd_readdir")
|
||||
(param $fd $fd)
|
||||
;;; The buffer where directory entries are stored
|
||||
(param $buf (@witx pointer u8))
|
||||
(param $buf_len $size)
|
||||
;;; The location within the directory to start reading
|
||||
(param $cookie $dircookie)
|
||||
;;; The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Atomically replace a file descriptor by renumbering another file descriptor.
|
||||
;;
|
||||
;;; Due to the strong focus on thread safety, this environment does not provide
|
||||
;;; a mechanism to duplicate or renumber a file descriptor to an arbitrary
|
||||
;;; number, like `dup2()`. This would be prone to race conditions, as an actual
|
||||
;;; file descriptor with the same number could be allocated by a different
|
||||
;;; thread at the same time.
|
||||
;;
|
||||
;;; This function provides a way to atomically renumber file descriptors, which
|
||||
;;; would disappear if `dup2()` were to be removed entirely.
|
||||
(@interface func (export "fd_renumber")
|
||||
(param $fd $fd)
|
||||
;;; The file descriptor to overwrite.
|
||||
(param $to $fd)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Move the offset of a file descriptor.
|
||||
;;; Note: This is similar to `lseek` in POSIX.
|
||||
(@interface func (export "fd_seek")
|
||||
(param $fd $fd)
|
||||
;;; The number of bytes to move.
|
||||
(param $offset $filedelta)
|
||||
;;; The base from which the offset is relative.
|
||||
(param $whence $whence)
|
||||
;;; The new offset of the file descriptor, relative to the start of the file.
|
||||
(result $error (expected $filesize (error $errno)))
|
||||
)
|
||||
|
||||
;;; Synchronize the data and metadata of a file to disk.
|
||||
;;; Note: This is similar to `fsync` in POSIX.
|
||||
(@interface func (export "fd_sync")
|
||||
(param $fd $fd)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Return the current offset of a file descriptor.
|
||||
;;; Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
|
||||
(@interface func (export "fd_tell")
|
||||
(param $fd $fd)
|
||||
;;; The current offset of the file descriptor, relative to the start of the file.
|
||||
(result $error (expected $filesize (error $errno)))
|
||||
)
|
||||
|
||||
;;; Write to a file descriptor.
|
||||
;;; Note: This is similar to `writev` in POSIX.
|
||||
(@interface func (export "fd_write")
|
||||
(param $fd $fd)
|
||||
;;; List of scatter/gather vectors from which to retrieve data.
|
||||
(param $iovs $ciovec_array)
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Create a directory.
|
||||
;;; Note: This is similar to `mkdirat` in POSIX.
|
||||
(@interface func (export "path_create_directory")
|
||||
(param $fd $fd)
|
||||
;;; The path at which to create the directory.
|
||||
(param $path string)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Return the attributes of a file or directory.
|
||||
;;; Note: This is similar to `stat` in POSIX.
|
||||
(@interface func (export "path_filestat_get")
|
||||
(param $fd $fd)
|
||||
;;; Flags determining the method of how the path is resolved.
|
||||
(param $flags $lookupflags)
|
||||
;;; The path of the file or directory to inspect.
|
||||
(param $path string)
|
||||
;;; The buffer where the file's attributes are stored.
|
||||
(result $error (expected $filestat (error $errno)))
|
||||
)
|
||||
|
||||
;;; Adjust the timestamps of a file or directory.
|
||||
;;; Note: This is similar to `utimensat` in POSIX.
|
||||
(@interface func (export "path_filestat_set_times")
|
||||
(param $fd $fd)
|
||||
;;; Flags determining the method of how the path is resolved.
|
||||
(param $flags $lookupflags)
|
||||
;;; The path of the file or directory to operate on.
|
||||
(param $path string)
|
||||
;;; The desired values of the data access timestamp.
|
||||
(param $atim $timestamp)
|
||||
;;; The desired values of the data modification timestamp.
|
||||
(param $mtim $timestamp)
|
||||
;;; A bitmask indicating which timestamps to adjust.
|
||||
(param $fst_flags $fstflags)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Create a hard link.
|
||||
;;; Note: This is similar to `linkat` in POSIX.
|
||||
(@interface func (export "path_link")
|
||||
(param $old_fd $fd)
|
||||
;;; Flags determining the method of how the path is resolved.
|
||||
(param $old_flags $lookupflags)
|
||||
;;; The source path from which to link.
|
||||
(param $old_path string)
|
||||
;;; The working directory at which the resolution of the new path starts.
|
||||
(param $new_fd $fd)
|
||||
;;; The destination path at which to create the hard link.
|
||||
(param $new_path string)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Open a file or directory.
|
||||
;;
|
||||
;;; The returned file descriptor is not guaranteed to be the lowest-numbered
|
||||
;;; file descriptor not currently open; it is randomized to prevent
|
||||
;;; applications from depending on making assumptions about indexes, since this
|
||||
;;; is error-prone in multi-threaded contexts. The returned file descriptor is
|
||||
;;; guaranteed to be less than 2**31.
|
||||
;;
|
||||
;;; Note: This is similar to `openat` in POSIX.
|
||||
(@interface func (export "path_open")
|
||||
(param $fd $fd)
|
||||
;;; Flags determining the method of how the path is resolved.
|
||||
(param $dirflags $lookupflags)
|
||||
;;; The relative path of the file or directory to open, relative to the
|
||||
;;; `path_open::fd` directory.
|
||||
(param $path string)
|
||||
;;; The method by which to open the file.
|
||||
(param $oflags $oflags)
|
||||
;;; The initial rights of the newly created file descriptor. The
|
||||
;;; implementation is allowed to return a file descriptor with fewer rights
|
||||
;;; than specified, if and only if those rights do not apply to the type of
|
||||
;;; file being opened.
|
||||
;;
|
||||
;;; The *base* rights are rights that will apply to operations using the file
|
||||
;;; descriptor itself, while the *inheriting* rights are rights that apply to
|
||||
;;; file descriptors derived from it.
|
||||
(param $fs_rights_base $rights)
|
||||
(param $fs_rights_inheriting $rights)
|
||||
(param $fdflags $fdflags)
|
||||
;;; The file descriptor of the file that has been opened.
|
||||
(result $error (expected $fd (error $errno)))
|
||||
)
|
||||
|
||||
;;; Read the contents of a symbolic link.
|
||||
;;; Note: This is similar to `readlinkat` in POSIX.
|
||||
(@interface func (export "path_readlink")
|
||||
(param $fd $fd)
|
||||
;;; The path of the symbolic link from which to read.
|
||||
(param $path string)
|
||||
;;; The buffer to which to write the contents of the symbolic link.
|
||||
(param $buf (@witx pointer u8))
|
||||
(param $buf_len $size)
|
||||
;;; The number of bytes placed in the buffer.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Remove a directory.
|
||||
;;; Return `errno::notempty` if the directory is not empty.
|
||||
;;; Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
|
||||
(@interface func (export "path_remove_directory")
|
||||
(param $fd $fd)
|
||||
;;; The path to a directory to remove.
|
||||
(param $path string)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Rename a file or directory.
|
||||
;;; Note: This is similar to `renameat` in POSIX.
|
||||
(@interface func (export "path_rename")
|
||||
(param $fd $fd)
|
||||
;;; The source path of the file or directory to rename.
|
||||
(param $old_path string)
|
||||
;;; The working directory at which the resolution of the new path starts.
|
||||
(param $new_fd $fd)
|
||||
;;; The destination path to which to rename the file or directory.
|
||||
(param $new_path string)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Create a symbolic link.
|
||||
;;; Note: This is similar to `symlinkat` in POSIX.
|
||||
(@interface func (export "path_symlink")
|
||||
;;; The contents of the symbolic link.
|
||||
(param $old_path string)
|
||||
(param $fd $fd)
|
||||
;;; The destination path at which to create the symbolic link.
|
||||
(param $new_path string)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
|
||||
;;; Unlink a file.
|
||||
;;; Return `errno::isdir` if the path refers to a directory.
|
||||
;;; Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
|
||||
(@interface func (export "path_unlink_file")
|
||||
(param $fd $fd)
|
||||
;;; The path to a file to unlink.
|
||||
(param $path string)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Concurrently poll for the occurrence of a set of events.
|
||||
(@interface func (export "poll_oneoff")
|
||||
;;; The events to which to subscribe.
|
||||
(param $in (@witx const_pointer $subscription))
|
||||
;;; The events that have occurred.
|
||||
(param $out (@witx pointer $event))
|
||||
;;; Both the number of subscriptions and events.
|
||||
(param $nsubscriptions $size)
|
||||
;;; The number of events stored.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Terminate the process normally. An exit code of 0 indicates successful
|
||||
;;; termination of the program. The meanings of other values is dependent on
|
||||
;;; the environment.
|
||||
(@interface func (export "proc_exit")
|
||||
;;; The exit code returned by the process.
|
||||
(param $rval $exitcode)
|
||||
(@witx noreturn)
|
||||
)
|
||||
|
||||
;;; Send a signal to the process of the calling thread.
|
||||
;;; Note: This is similar to `raise` in POSIX.
|
||||
(@interface func (export "proc_raise")
|
||||
;;; The signal condition to trigger.
|
||||
(param $sig $signal)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Temporarily yield execution of the calling thread.
|
||||
;;; Note: This is similar to `sched_yield` in POSIX.
|
||||
(@interface func (export "sched_yield")
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Write high-quality random data into a buffer.
|
||||
;;; This function blocks when the implementation is unable to immediately
|
||||
;;; provide sufficient high-quality random data.
|
||||
;;; This function may execute slowly, so when large mounts of random data are
|
||||
;;; required, it's advisable to use this function to seed a pseudo-random
|
||||
;;; number generator, rather than to provide the random data directly.
|
||||
(@interface func (export "random_get")
|
||||
;;; The buffer to fill with random data.
|
||||
(param $buf (@witx pointer u8))
|
||||
(param $buf_len $size)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
|
||||
;;; Accept a new incoming connection.
|
||||
;;; Note: This is similar to `accept` in POSIX.
|
||||
(@interface func (export "sock_accept")
|
||||
;;; The listening socket.
|
||||
(param $fd $fd)
|
||||
;;; The desired values of the file descriptor flags.
|
||||
(param $flags $fdflags)
|
||||
;;; New socket connection
|
||||
(result $error (expected $fd (error $errno)))
|
||||
)
|
||||
|
||||
;;; Receive a message from a socket.
|
||||
;;; Note: This is similar to `recv` in POSIX, though it also supports reading
|
||||
;;; the data into multiple buffers in the manner of `readv`.
|
||||
(@interface func (export "sock_recv")
|
||||
(param $fd $fd)
|
||||
;;; List of scatter/gather vectors to which to store data.
|
||||
(param $ri_data $iovec_array)
|
||||
;;; Message flags.
|
||||
(param $ri_flags $riflags)
|
||||
;;; Number of bytes stored in ri_data and message flags.
|
||||
(result $error (expected (tuple $size $roflags) (error $errno)))
|
||||
)
|
||||
|
||||
;;; Send a message on a socket.
|
||||
;;; Note: This is similar to `send` in POSIX, though it also supports writing
|
||||
;;; the data from multiple buffers in the manner of `writev`.
|
||||
(@interface func (export "sock_send")
|
||||
(param $fd $fd)
|
||||
;;; List of scatter/gather vectors to which to retrieve data
|
||||
(param $si_data $ciovec_array)
|
||||
;;; Message flags.
|
||||
(param $si_flags $siflags)
|
||||
;;; Number of bytes transmitted.
|
||||
(result $error (expected $size (error $errno)))
|
||||
)
|
||||
|
||||
;;; Shut down socket send and receive channels.
|
||||
;;; Note: This is similar to `shutdown` in POSIX.
|
||||
(@interface func (export "sock_shutdown")
|
||||
(param $fd $fd)
|
||||
;;; Which channels on the socket to shut down.
|
||||
(param $how $sdflags)
|
||||
(result $error (expected (error $errno)))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,360 @@
|
||||
//! Core-module tests for preview1 filesystem routing and real userland programs.
|
||||
use sausage_vfs::{
|
||||
preview1::{self, State},
|
||||
*,
|
||||
};
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
path::Path,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use wasmtime::{Engine, Linker, Module, Store};
|
||||
|
||||
fn engine() -> &'static Engine {
|
||||
static E: OnceLock<Engine> = OnceLock::new();
|
||||
E.get_or_init(Engine::default)
|
||||
}
|
||||
fn module(path: &str) -> Module {
|
||||
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("required guest {path}: {e}"));
|
||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||
bytes.hash(&mut hash);
|
||||
engine().precompile_compatibility_hash().hash(&mut hash);
|
||||
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join("preview1-guests");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let cache = dir.join(format!("{:x}.cwasm", hash.finish()));
|
||||
if !cache.exists() {
|
||||
std::fs::write(&cache, engine().precompile_module(&bytes).unwrap()).unwrap();
|
||||
}
|
||||
// This cache contains only modules compiled locally by this test with this engine.
|
||||
unsafe { Module::deserialize_file(engine(), cache) }.unwrap()
|
||||
}
|
||||
async fn run(module: &Module, root: Arc<dyn Layer>, cwd: &str, args: &[&str]) -> (String, String) {
|
||||
let (stdout, mut out) = tokio::io::duplex(1024 * 1024);
|
||||
let (stderr, mut err) = tokio::io::duplex(1024 * 1024);
|
||||
let mut linker = Linker::new(engine());
|
||||
preview1::add_to_linker(&mut linker, |s: &mut State| s).unwrap();
|
||||
let mut store = Store::new(
|
||||
engine(),
|
||||
State::new(
|
||||
root,
|
||||
cwd,
|
||||
args.iter().map(|s| (*s).into()).collect(),
|
||||
vec![],
|
||||
Box::new(tokio::io::empty()),
|
||||
Box::new(stdout),
|
||||
Box::new(stderr),
|
||||
),
|
||||
);
|
||||
let instance = linker.instantiate_async(&mut store, module).await.unwrap();
|
||||
let start = instance
|
||||
.get_typed_func::<(), ()>(&mut store, "_start")
|
||||
.unwrap();
|
||||
let result = start.call_async(&mut store, ()).await;
|
||||
drop(store);
|
||||
use tokio::io::AsyncReadExt;
|
||||
let mut stdout = String::new();
|
||||
out.read_to_string(&mut stdout).await.unwrap();
|
||||
let mut stderr = String::new();
|
||||
err.read_to_string(&mut stderr).await.unwrap();
|
||||
if let Err(e) = result {
|
||||
assert_eq!(
|
||||
e.downcast_ref::<wasmtime_wasi::I32Exit>().map(|e| e.0),
|
||||
Some(0),
|
||||
"guest failed: {e:?}\n{stderr}"
|
||||
);
|
||||
}
|
||||
(stdout, stderr)
|
||||
}
|
||||
fn wat(path: &str, body: &str) -> Module {
|
||||
Module::new(engine(), format!(r#"(module
|
||||
(import "wasi_snapshot_preview1" "path_filestat_get" (func $stat (param i32 i32 i32 i32 i32) (result i32)))
|
||||
(memory (export "memory") 1)
|
||||
(data (i32.const 0) "{path}")
|
||||
(func (export "_start") {body}))"#)).unwrap()
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn cwd_and_parent_across_mounts() {
|
||||
let base = Arc::new(Memory::default());
|
||||
base.mkdir("/work").await.unwrap();
|
||||
let shared = Arc::new(Memory::default());
|
||||
shared.mkdir("/inner").await.unwrap();
|
||||
base.open(
|
||||
"/value",
|
||||
Open {
|
||||
create: true,
|
||||
write: true,
|
||||
..Open::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let file = shared
|
||||
.open(
|
||||
"/value",
|
||||
Open {
|
||||
create: true,
|
||||
write: true,
|
||||
..Open::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
file.write(0, b"hello".to_vec()).await.unwrap();
|
||||
let mut mounts = Mounts::default();
|
||||
mounts.mount("/", base, false);
|
||||
mounts.mount("/shared", shared, false);
|
||||
let mounts = Arc::new(mounts);
|
||||
let guest = wat(
|
||||
"../shared/value",
|
||||
"i32.const 3 i32.const 1 i32.const 0 i32.const 15 i32.const 64 call $stat if unreachable end i32.const 96 i64.load i64.const 5 i64.ne if unreachable end",
|
||||
);
|
||||
run(&guest, mounts.clone(), "/work", &["probe"]).await;
|
||||
let parent = wat(
|
||||
"../../value",
|
||||
"i32.const 3 i32.const 1 i32.const 0 i32.const 11 i32.const 64 call $stat if unreachable end i32.const 96 i64.load i64.const 0 i64.ne if unreachable end",
|
||||
);
|
||||
run(&parent, mounts, "/shared/inner", &["probe"]).await;
|
||||
println!("PASS cwd and .. across mounts");
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn missing_file_errno() {
|
||||
let guest = wat(
|
||||
"../shared/value",
|
||||
"i32.const 3 i32.const 1 i32.const 0 i32.const 15 i32.const 64 call $stat i32.const 44 i32.ne if unreachable end",
|
||||
);
|
||||
run(&guest, Arc::new(Memory::default()), "/work", &["probe"]).await;
|
||||
println!("PASS missing path returns NOENT=44");
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn poll_preserves_input_and_returns_clock() {
|
||||
use preview1::{types::*, wasi_snapshot_preview1::WasiSnapshotPreview1};
|
||||
use wiggle::{GuestMemory, GuestPtr};
|
||||
let mut state = State::new(
|
||||
Arc::new(Memory::default()),
|
||||
"/",
|
||||
vec![],
|
||||
vec![],
|
||||
Box::new(&b"x"[..]),
|
||||
Box::new(tokio::io::sink()),
|
||||
Box::new(tokio::io::sink()),
|
||||
);
|
||||
let mut bytes = [0; 256];
|
||||
let mut memory = GuestMemory::Unshared(&mut bytes);
|
||||
let sub = GuestPtr::new(0);
|
||||
let event = GuestPtr::new(64);
|
||||
memory
|
||||
.write(
|
||||
sub,
|
||||
Subscription {
|
||||
userdata: 7,
|
||||
u: SubscriptionU::FdRead(SubscriptionFdReadwrite {
|
||||
file_descriptor: 0.into(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
state.poll_oneoff(&mut memory, sub, event, 1).await.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(memory.read(event).unwrap().userdata, 7);
|
||||
let iov = GuestPtr::new(128);
|
||||
memory
|
||||
.write(
|
||||
iov,
|
||||
Iovec {
|
||||
buf: GuestPtr::new(192),
|
||||
buf_len: 100,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
state
|
||||
.fd_read(&mut memory, 0.into(), iov.as_array(1))
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
memory
|
||||
.write(
|
||||
iov,
|
||||
Iovec {
|
||||
buf: GuestPtr::new(192),
|
||||
buf_len: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
state
|
||||
.fd_read(&mut memory, 0.into(), iov.as_array(1))
|
||||
.await
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(memory.read(GuestPtr::<u8>::new(192)).unwrap(), b'x');
|
||||
memory
|
||||
.write(
|
||||
sub,
|
||||
Subscription {
|
||||
userdata: 8,
|
||||
u: SubscriptionU::Clock(SubscriptionClock {
|
||||
id: Clockid::Monotonic,
|
||||
timeout: 0,
|
||||
precision: 0,
|
||||
flags: Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME,
|
||||
}),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
state.poll_oneoff(&mut memory, sub, event, 1).await.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(memory.read(event).unwrap().type_, Eventtype::Clock);
|
||||
memory
|
||||
.write(
|
||||
sub,
|
||||
Subscription {
|
||||
userdata: 9,
|
||||
u: SubscriptionU::FdWrite(SubscriptionFdReadwrite {
|
||||
file_descriptor: 1.into(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
state.poll_oneoff(&mut memory, sub, event, 1).await.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(memory.read(event).unwrap().type_, Eventtype::FdWrite);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn userland_files() {
|
||||
let guest = module("/tmp/wasi-userland-lab/uu/coreutils-0.12.0-wasm32-wasip1/coreutils.wasm");
|
||||
let root = Arc::new(Memory::default());
|
||||
run(&guest, root.clone(), "/", &["coreutils", "mkdir", "/work"]).await;
|
||||
run(
|
||||
&guest,
|
||||
root.clone(),
|
||||
"/work",
|
||||
&["coreutils", "touch", "input"],
|
||||
)
|
||||
.await;
|
||||
let input = root
|
||||
.open(
|
||||
"/work/input",
|
||||
Open {
|
||||
write: true,
|
||||
..Open::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
input.write(0, b"hello preview1\n".to_vec()).await.unwrap();
|
||||
run(
|
||||
&guest,
|
||||
root.clone(),
|
||||
"/work",
|
||||
&["coreutils", "cp", "input", "copy"],
|
||||
)
|
||||
.await;
|
||||
let (cat, _) = run(&guest, root.clone(), "/work", &["coreutils", "cat", "copy"]).await;
|
||||
assert_eq!(cat, "hello preview1\n");
|
||||
let (ls, _) = run(
|
||||
&guest,
|
||||
root.clone(),
|
||||
"/work",
|
||||
&["coreutils", "ls", "-la", "."],
|
||||
)
|
||||
.await;
|
||||
assert!(ls.contains("copy") && ls.contains("input"), "{ls}");
|
||||
run(&guest, root.clone(), "/work", &["coreutils", "rm", "copy"]).await;
|
||||
assert_eq!(
|
||||
root.stat("/work/copy", true).await.unwrap_err().kind(),
|
||||
std::io::ErrorKind::NotFound
|
||||
);
|
||||
println!("PASS uutils mkdir touch cp cat ls -la rm");
|
||||
let qjs = module("/tmp/wasi-userland-lab/qjs-wasi.wasm");
|
||||
let script = b"let f = std.open('input', 'r'); let text = f.readAsString(); f.close(); let out = std.open('js-output', 'w'); out.puts(text.toUpperCase()); out.close(); console.log('QuickJS files ok');";
|
||||
let file = root
|
||||
.open(
|
||||
"/work/check.mjs",
|
||||
Open {
|
||||
create: true,
|
||||
write: true,
|
||||
..Open::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
file.write(0, script.to_vec()).await.unwrap();
|
||||
let (out, _) = run(
|
||||
&qjs,
|
||||
root.clone(),
|
||||
"/work",
|
||||
&["qjs", "--std", "--module", "check.mjs"],
|
||||
)
|
||||
.await;
|
||||
assert!(out.contains("QuickJS files ok"));
|
||||
let output = root.open("/work/js-output", Open::default()).await.unwrap();
|
||||
assert_eq!(output.read(0, 100).await.unwrap(), b"HELLO PREVIEW1\n");
|
||||
println!("PASS QuickJS reads and writes files");
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn esbuild_bundle() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../spikes/s6-esbuild");
|
||||
for name in ["app.tsx", "check.mjs", "package.json", "package-lock.json"] {
|
||||
std::fs::copy(fixture.join(name), dir.path().join(name)).unwrap();
|
||||
}
|
||||
assert!(
|
||||
std::process::Command::new("cp")
|
||||
.args(["-r", "/tmp/s6-esbuild/app/node_modules"])
|
||||
.arg(dir.path())
|
||||
.status()
|
||||
.unwrap()
|
||||
.success()
|
||||
);
|
||||
let flags = [
|
||||
"app.tsx",
|
||||
"--bundle",
|
||||
"--minify",
|
||||
"--format=iife",
|
||||
"--platform=browser",
|
||||
"--jsx=automatic",
|
||||
"--jsx-import-source=preact",
|
||||
"--legal-comments=none",
|
||||
"--log-level=error",
|
||||
];
|
||||
assert!(
|
||||
std::process::Command::new("/tmp/s6-esbuild/esbuild-native")
|
||||
.current_dir(dir.path())
|
||||
.args(flags)
|
||||
.arg("--outfile=native.js")
|
||||
.status()
|
||||
.unwrap()
|
||||
.success()
|
||||
);
|
||||
let root = Arc::new(Host::new(dir.path(), Process::default()).unwrap());
|
||||
let guest = module("/tmp/s6-esbuild/esbuild.wasm");
|
||||
let mut args = vec!["esbuild"];
|
||||
args.extend(flags);
|
||||
args.push("--outfile=host.js");
|
||||
run(&guest, root, "/", &args).await;
|
||||
assert_eq!(
|
||||
std::fs::read(dir.path().join("native.js")).unwrap(),
|
||||
std::fs::read(dir.path().join("host.js")).unwrap()
|
||||
);
|
||||
let render = std::process::Command::new("node")
|
||||
.current_dir(dir.path())
|
||||
.args(["check.mjs", "host.js"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
render.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&render.stderr)
|
||||
);
|
||||
println!("{}", String::from_utf8_lossy(&render.stdout));
|
||||
println!("PASS esbuild through sausage-vfs: byte-identical to native");
|
||||
}
|
||||
Reference in New Issue
Block a user