diff --git a/README.md b/README.md index 1b561e3..97e17b1 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,15 @@ Supports all of the current WASI subsystems: - [x] Environment: Set environment variables, configure host environment variable permissions - [x] Filesystem: Mount a read-only filesystem, configure host filesystem pass-through -- [x] Stdio: Currently only supports disabling -- [ ] Sockets -- [ ] Clocks -- [x] Exit -- [ ] Random +- [x] Stdio: Allow / Ignore +- [ ] Sockets: Allow / Ignore +- [ ] Clocks: Allow / Ignore +- [x] Exit: Allow / Ignore +- [ ] Random: Allow / Ignore While current virtualization support is limited, the goal for this project is to support a wide range of WASI virtualization use cases. -Have an unhandled use case? Post a [virtualization suggestion](https://github.com/bytecodealliance/WASI-Virt/issues/new). +Have an unhandled use case? Post a virtualization [suggestion](https://github.com/bytecodealliance/WASI-Virt/issues/new). ## Explainer diff --git a/lib/virtual_adapter.wasm b/lib/virtual_adapter.wasm index f781f41..be3d159 100755 Binary files a/lib/virtual_adapter.wasm and b/lib/virtual_adapter.wasm differ diff --git a/src/bin/wasi-virt.rs b/src/bin/wasi-virt.rs index 25c7025..35333c2 100644 --- a/src/bin/wasi-virt.rs +++ b/src/bin/wasi-virt.rs @@ -25,6 +25,20 @@ struct Args { #[arg(long)] allow_exit: Option, + // STDIO + /// Enable all stdio + #[arg(long)] + allow_stdio: Option, + /// Enable stdin + #[arg(long)] + allow_stdin: Option, + /// Enable stdout + #[arg(long)] + allow_stdout: Option, + /// Enable stderr + #[arg(long)] + allow_stderr: Option, + // ENV /// Allow host access to all environment variables, or to a specific comma-separated list of variable names. #[arg(long, num_args(0..), use_value_delimiter(true), require_equals(true), value_name("ENV_VAR"))] @@ -40,6 +54,11 @@ struct Args { #[arg(long, value_name("preopen=virtualdir"), value_parser = parse_key_val::)] mount: Option>, + // CLOCKS + + // SOCKETS + + // /// Wasm binary to compose the virtualization with /// If not provided, the virtualization component itself will only generated. #[arg(required(false))] @@ -83,6 +102,20 @@ fn main() -> Result<()> { // By default, we virtualize all subsystems // This ensures full encapsulation in the default (no argument) case + // stdio + virt_opts.stdio().stdin( + args.allow_stdin + .unwrap_or(args.allow_stdio.unwrap_or(false)), + ); + virt_opts.stdio().stdout( + args.allow_stdout + .unwrap_or(args.allow_stdio.unwrap_or(false)), + ); + virt_opts.stdio().stderr( + args.allow_stderr + .unwrap_or(args.allow_stdio.unwrap_or(false)), + ); + // exit virt_opts.exit(if args.allow_exit.unwrap_or_default() { VirtExit::Passthrough diff --git a/src/lib.rs b/src/lib.rs index 9ddb120..bc16fec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,14 @@ use std::env; use std::fs; use std::time::SystemTime; use virt_env::{create_env_virt, strip_env_virt}; -use virt_io::create_io_virt; -use virt_io::stub_io_virt; +use virt_io::strip_clocks_virt; +use virt_io::strip_fs_virt; +use virt_io::strip_http_virt; +use virt_io::strip_stdio_virt; use virt_io::VirtStdio; -use walrus::Module; +use virt_io::{create_io_virt, strip_io_virt}; use walrus::ValType; use walrus_ops::add_stub_exported_func; -use walrus_ops::remove_exported_func; use wasm_metadata::Producers; use wasm_opt::Feature; use wasm_opt::OptimizationOptions; @@ -54,6 +55,12 @@ pub struct WasiVirt { pub stdio: Option, /// Exit virtualization pub exit: Option, + /// Clocks virtualization + #[serde(default)] + pub clocks: bool, + /// Http virtualization + #[serde(default)] + pub http: bool, /// Disable wasm-opt run if desired pub wasm_opt: Option, } @@ -68,6 +75,14 @@ impl WasiVirt { Self::default() } + pub fn clocks(&mut self) { + self.clocks = true; + } + + pub fn http(&mut self) { + self.http = true; + } + pub fn exit(&mut self, virt_exit: VirtExit) { self.exit = Some(virt_exit); } @@ -95,29 +110,10 @@ impl WasiVirt { let mut module = config.parse(virt_adapter)?; module.name = Some("wasi_virt".into()); - let mut has_io = false; - + // very few subsystems are fully independent of io, these are them if let Some(env) = &self.env { create_env_virt(&mut module, env)?; - } else { - strip_env_virt(&mut module)?; } - - let virtual_files = if self.fs.is_some() || self.stdio.is_some() { - has_io = true; - // pull in one io subsystem -> pull in all io subsystems - // (due to virtualization wrapping required for streams + poll) - self.fs(); - self.stdio(); - create_io_virt( - &mut module, - self.fs.as_ref().unwrap(), - self.stdio.as_ref().unwrap(), - )? - } else { - Default::default() - }; - if matches!(self.exit, Some(VirtExit::Unreachable)) { add_stub_exported_func( &mut module, @@ -127,9 +123,14 @@ impl WasiVirt { )?; } - if !has_io { - strip_io_virt(&mut module)?; - } + let has_io = self.fs.is_some() || self.stdio.is_some(); + + let virtual_files = if has_io { + // io virt is managed through a singular io configuration + create_io_virt(&mut module, self.fs.as_ref(), self.stdio.as_ref())? + } else { + Default::default() + }; // decode the component custom section to strip out the unused world exports // before reencoding. @@ -152,12 +153,51 @@ impl WasiVirt { let env_world = bindgen.resolve.select_world(*pkg_id, Some("virtual-env"))?; let io_world = bindgen.resolve.select_world(*pkg_id, Some("virtual-io"))?; + // let exit_world = bindgen + // .resolve + // .select_world(*pkg_id, Some("virtual-exit"))?; + let fs_world = bindgen.resolve.select_world(*pkg_id, Some("virtual-fs"))?; + let stdio_world = bindgen + .resolve + .select_world(*pkg_id, Some("virtual-stdio"))?; + let clocks_world = bindgen + .resolve + .select_world(*pkg_id, Some("virtual-clocks"))?; + let http_world = bindgen + .resolve + .select_world(*pkg_id, Some("virtual-http"))?; if self.env.is_some() { bindgen.resolve.merge_worlds(env_world, base_world)?; + } else { + strip_env_virt(&mut module)?; } if has_io { bindgen.resolve.merge_worlds(io_world, base_world)?; + + // io subsystems have io dependence due to streams + poll + if self.clocks { + bindgen.resolve.merge_worlds(clocks_world, base_world)?; + } else { + strip_clocks_virt(&mut module)?; + } + if self.http { + bindgen.resolve.merge_worlds(http_world, base_world)?; + } else { + strip_http_virt(&mut module)?; + } + if self.stdio.is_some() { + bindgen.resolve.merge_worlds(stdio_world, base_world)?; + } else { + strip_stdio_virt(&mut module)?; + } + if self.fs.is_some() { + bindgen.resolve.merge_worlds(fs_world, base_world)?; + } else { + strip_fs_virt(&mut module)?; + } + } else { + strip_io_virt(&mut module)?; } let mut producers = Producers::default(); @@ -213,72 +253,3 @@ fn timestamp() -> u64 { Err(_) => panic!(), } } - -fn strip_io_virt(module: &mut Module) -> Result<()> { - stub_io_virt(module)?; - - remove_exported_func(module, "wasi:cli-base/preopens#get-directories")?; - - remove_exported_func(module, "wasi:filesystem/filesystem#read-via-stream")?; - remove_exported_func(module, "wasi:filesystem/filesystem#write-via-stream")?; - remove_exported_func(module, "wasi:filesystem/filesystem#append-via-stream")?; - remove_exported_func(module, "wasi:filesystem/filesystem#advise")?; - remove_exported_func(module, "wasi:filesystem/filesystem#sync-data")?; - remove_exported_func(module, "wasi:filesystem/filesystem#get-flags")?; - remove_exported_func(module, "wasi:filesystem/filesystem#get-type")?; - remove_exported_func(module, "wasi:filesystem/filesystem#set-size")?; - remove_exported_func(module, "wasi:filesystem/filesystem#set-times")?; - remove_exported_func(module, "wasi:filesystem/filesystem#read")?; - remove_exported_func(module, "wasi:filesystem/filesystem#write")?; - remove_exported_func(module, "wasi:filesystem/filesystem#read-directory")?; - remove_exported_func(module, "wasi:filesystem/filesystem#sync")?; - remove_exported_func(module, "wasi:filesystem/filesystem#create-directory-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#stat")?; - remove_exported_func(module, "wasi:filesystem/filesystem#stat-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#set-times-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#link-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#open-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#readlink-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#remove-directory-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#rename-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#symlink-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#access-at")?; - remove_exported_func(module, "wasi:filesystem/filesystem#unlink-file-at")?; - remove_exported_func( - module, - "wasi:filesystem/filesystem#change-file-permissions-at", - )?; - remove_exported_func( - module, - "wasi:filesystem/filesystem#change-directory-permissions-at", - )?; - remove_exported_func(module, "wasi:filesystem/filesystem#lock-shared")?; - remove_exported_func(module, "wasi:filesystem/filesystem#lock-exclusive")?; - remove_exported_func(module, "wasi:filesystem/filesystem#try-lock-shared")?; - remove_exported_func(module, "wasi:filesystem/filesystem#try-lock-exclusive")?; - remove_exported_func(module, "wasi:filesystem/filesystem#unlock")?; - remove_exported_func(module, "wasi:filesystem/filesystem#drop-descriptor")?; - remove_exported_func(module, "wasi:filesystem/filesystem#read-directory-entry")?; - remove_exported_func( - module, - "wasi:filesystem/filesystem#drop-directory-entry-stream", - )?; - - remove_exported_func(module, "wasi:io/streams#read")?; - remove_exported_func(module, "wasi:io/streams#blocking-read")?; - remove_exported_func(module, "wasi:io/streams#skip")?; - remove_exported_func(module, "wasi:io/streams#blocking-skip")?; - remove_exported_func(module, "wasi:io/streams#subscribe-to-input-stream")?; - remove_exported_func(module, "wasi:io/streams#drop-input-stream")?; - remove_exported_func(module, "wasi:io/streams#write")?; - remove_exported_func(module, "wasi:io/streams#blocking-write")?; - remove_exported_func(module, "wasi:io/streams#write-zeroes")?; - remove_exported_func(module, "wasi:io/streams#blocking-write-zeroes")?; - remove_exported_func(module, "wasi:io/streams#splice")?; - remove_exported_func(module, "wasi:io/streams#blocking-splice")?; - remove_exported_func(module, "wasi:io/streams#forward")?; - remove_exported_func(module, "wasi:io/streams#subscribe-to-output-stream")?; - remove_exported_func(module, "wasi:io/streams#drop-output-stream")?; - - Ok(()) -} diff --git a/src/virt_io.rs b/src/virt_io.rs index d347f3e..720c274 100644 --- a/src/virt_io.rs +++ b/src/virt_io.rs @@ -5,6 +5,7 @@ use anyhow::{bail, Context, Result}; use serde::Deserialize; use walrus::{ir::Value, ExportItem, GlobalKind, InitExpr, Module}; +use crate::walrus_ops::remove_exported_func; use crate::{ data::{Data, WasmEncode}, walrus_ops::{get_active_data_segment, get_stack_global, stub_imported_func}, @@ -240,80 +241,93 @@ impl FsEntry { } // io flags -const FLAGS_ENABLE_STDIN: u32 = 1; -const FLAGS_ENABLE_STDOUT: u32 = 2; -const FLAGS_ENABLE_STDERR: u32 = 4; -const FLAGS_HOST_PREOPENS: u32 = 8; -const FLAGS_HOST_PASSTHROUGH: u32 = 16; +const FLAGS_ENABLE_STDIN: u32 = 1 << 0; +const FLAGS_ENABLE_STDOUT: u32 = 1 << 1; +const FLAGS_ENABLE_STDERR: u32 = 1 << 2; +const FLAGS_HOST_PREOPENS: u32 = 1 << 3; +const FLAGS_HOST_PASSTHROUGH: u32 = 1 << 4; pub(crate) fn create_io_virt<'a>( module: &'a mut Module, - fs: &VirtFs, - stdio: &VirtStdio, + fs: Option<&VirtFs>, + stdio: Option<&VirtStdio>, ) -> Result { let mut virtual_files = BTreeMap::new(); let mut flags: u32 = 0; - if fs.host_preopens { - flags |= FLAGS_HOST_PREOPENS; - } - if stdio.stdin { - flags |= FLAGS_ENABLE_STDIN; - } - if stdio.stdout { - flags |= FLAGS_ENABLE_STDOUT; - } - if stdio.stderr { - flags |= FLAGS_ENABLE_STDERR; + if let Some(fs) = fs { + if fs.host_preopens { + flags |= FLAGS_HOST_PREOPENS; + } } - if !stdio.stdin && !stdio.stdout && !stdio.stderr { - stub_stdio_virt(module)?; + if let Some(stdio) = stdio { + if stdio.stdin { + flags |= FLAGS_ENABLE_STDIN; + } + if stdio.stdout { + flags |= FLAGS_ENABLE_STDOUT; + } + if stdio.stderr { + flags |= FLAGS_ENABLE_STDERR; + } + if !stdio.stdin && !stdio.stdout && !stdio.stderr { + stub_stdio_virt(module)?; + } } // First we iterate the options and fill in all HostDir and HostFile entries // With inline directory and file entries - let mut fs = fs.clone(); - for (name, entry) in fs.preopens.iter_mut() { - entry.visit_pre_mut(name, &mut |entry, name, path| { - match entry { - FsEntry::Source(source) => { - *entry = FsEntry::File(source.as_bytes().to_vec()) - }, - FsEntry::Virtualize(host_path) => { - // read a directory or file path from the host - let metadata = fs::metadata(&host_path)?; - if metadata.is_dir() { - let mut entries: BTreeMap = BTreeMap::new(); - for entry in fs::read_dir(&host_path)? { - let entry = entry?; - let file_name = entry.file_name(); - let file_name_str = file_name.to_str().unwrap(); - let mut full_path = host_path.clone(); - if !full_path.ends_with('/') { - full_path.push('/'); + let fs = if let Some(fs) = fs { + let mut fs = fs.clone(); + for (name, entry) in fs.preopens.iter_mut() { + entry.visit_pre_mut(name, &mut |entry, name, path| { + match entry { + FsEntry::Source(source) => { + *entry = FsEntry::File(source.as_bytes().to_vec()) + }, + FsEntry::Virtualize(host_path) => { + // read a directory or file path from the host + let metadata = fs::metadata(&host_path)?; + if metadata.is_dir() { + let mut entries: BTreeMap = BTreeMap::new(); + for entry in fs::read_dir(&host_path)? { + let entry = entry?; + let file_name = entry.file_name(); + let file_name_str = file_name.to_str().unwrap(); + let mut full_path = host_path.clone(); + if !full_path.ends_with('/') { + full_path.push('/'); + } + full_path.push_str(file_name_str); + virtual_files.insert(format!("{path}{}{name}/{file_name_str}", if path.len() > 0 { "/" } else { "" }), full_path.to_string()); + entries.insert(file_name_str.into(), FsEntry::Virtualize(full_path)); } - full_path.push_str(file_name_str); - virtual_files.insert(format!("{path}{}{name}/{file_name_str}", if path.len() > 0 { "/" } else { "" }), full_path.to_string()); - entries.insert(file_name_str.into(), FsEntry::Virtualize(full_path)); - } - *entry = FsEntry::Dir(entries); - } else { - if !metadata.is_file() { - bail!("Only files and directories are currently supported for host paths to virtualize"); + *entry = FsEntry::Dir(entries); + } else { + if !metadata.is_file() { + bail!("Only files and directories are currently supported for host paths to virtualize"); + } + let bytes = fs::read(&host_path)?; + *entry = FsEntry::File(bytes) } - let bytes = fs::read(&host_path)?; - *entry = FsEntry::File(bytes) } + FsEntry::File(_) | FsEntry::RuntimeFile(_) | FsEntry::RuntimeDir(_) | FsEntry::Symlink(_) | FsEntry::Dir(_) => {} } - FsEntry::File(_) | FsEntry::RuntimeFile(_) | FsEntry::RuntimeDir(_) | FsEntry::Symlink(_) | FsEntry::Dir(_) => {} - } - Ok(()) - })?; - } + Ok(()) + })?; + } + Some(fs) + } else { + None + }; // Create the data section bytes let mut data_section = Data::new(get_stack_global(module)? as usize); - let mut fs_passthrough = fs.host_preopens; + let mut fs_passthrough = if let Some(fs) = &fs { + fs.host_preopens + } else { + false + }; // Next we linearize the pre-order directory graph as the static file data // Using a pre-order traversal @@ -321,72 +335,74 @@ pub(crate) fn create_io_virt<'a>( // length. let mut static_fs_data: Vec = Vec::new(); let mut preopen_indices: Vec = Vec::new(); - for (name, entry) in &fs.preopens { - preopen_indices.push(static_fs_data.len() as u32); - entry.visit_pre(name, &mut |entry, name, _path, remaining_siblings| { - let name_str_ptr = data_section.string(name)?; - let (ty, data) = match &entry { - // removed during previous step - FsEntry::Virtualize(_) | FsEntry::Source(_) => unreachable!(), - FsEntry::Symlink(_) => todo!("symlink support"), - FsEntry::RuntimeFile(path) => { - fs_passthrough = true; - let str = data_section.string(path)?; - ( - StaticIndexType::RuntimeHostFile, - StaticFileData { host_path: str }, - ) - } - FsEntry::RuntimeDir(path) => { - fs_passthrough = true; - let str = data_section.string(path)?; - ( - StaticIndexType::RuntimeHostDir, - StaticFileData { host_path: str }, - ) - } - FsEntry::Dir(dir) => { - let child_cnt = dir.len() as u32; - // children will be visited next in preorder and contiguously - // therefore the child index in the static fs data is known - // to be the next index - let start_idx = static_fs_data.len() as u32 + 1; - let child_idx = start_idx + remaining_siblings as u32; - ( - StaticIndexType::Dir, - StaticFileData { - dir: (child_idx, child_cnt), - }, - ) - } - FsEntry::File(bytes) => { - let byte_len = bytes.len(); - if byte_len > fs.passive_cutoff.unwrap_or(1024) as usize { - let passive_idx = data_section.passive_bytes(bytes); + if let Some(fs) = &fs { + for (name, entry) in &fs.preopens { + preopen_indices.push(static_fs_data.len() as u32); + entry.visit_pre(name, &mut |entry, name, _path, remaining_siblings| { + let name_str_ptr = data_section.string(name)?; + let (ty, data) = match &entry { + // removed during previous step + FsEntry::Virtualize(_) | FsEntry::Source(_) => unreachable!(), + FsEntry::Symlink(_) => todo!("symlink support"), + FsEntry::RuntimeFile(path) => { + fs_passthrough = true; + let str = data_section.string(path)?; ( - StaticIndexType::PassiveFile, - StaticFileData { - passive: (passive_idx, bytes.len() as u32), - }, + StaticIndexType::RuntimeHostFile, + StaticFileData { host_path: str }, ) - } else { - let ptr = data_section.stack_bytes(bytes)?; + } + FsEntry::RuntimeDir(path) => { + fs_passthrough = true; + let str = data_section.string(path)?; + ( + StaticIndexType::RuntimeHostDir, + StaticFileData { host_path: str }, + ) + } + FsEntry::Dir(dir) => { + let child_cnt = dir.len() as u32; + // children will be visited next in preorder and contiguously + // therefore the child index in the static fs data is known + // to be the next index + let start_idx = static_fs_data.len() as u32 + 1; + let child_idx = start_idx + remaining_siblings as u32; ( - StaticIndexType::ActiveFile, + StaticIndexType::Dir, StaticFileData { - active: (ptr, bytes.len() as u32), + dir: (child_idx, child_cnt), }, ) } - } - }; - static_fs_data.push(StaticIndexEntry { - name: name_str_ptr as u32, - ty, - data, - }); - Ok(()) - })?; + FsEntry::File(bytes) => { + let byte_len = bytes.len(); + if byte_len > fs.passive_cutoff.unwrap_or(1024) as usize { + let passive_idx = data_section.passive_bytes(bytes); + ( + StaticIndexType::PassiveFile, + StaticFileData { + passive: (passive_idx, bytes.len() as u32), + }, + ) + } else { + let ptr = data_section.stack_bytes(bytes)?; + ( + StaticIndexType::ActiveFile, + StaticFileData { + active: (ptr, bytes.len() as u32), + }, + ) + } + } + }; + static_fs_data.push(StaticIndexEntry { + name: name_str_ptr as u32, + ty, + data, + }); + Ok(()) + })?; + } } // now write the linearized static index entry section into the data @@ -443,7 +459,10 @@ pub(crate) fn create_io_virt<'a>( // static_index: 0 as *const StaticIndexEntry, // [byte 12] // flags: 0 // [byte 16] // }; - bytes[data_offset..data_offset + 4].copy_from_slice(&(fs.preopens.len() as u32).to_le_bytes()); + if let Some(fs) = &fs { + bytes[data_offset..data_offset + 4] + .copy_from_slice(&(fs.preopens.len() as u32).to_le_bytes()); + } bytes[data_offset + 4..data_offset + 8].copy_from_slice(&(preopen_addr as u32).to_le_bytes()); bytes[data_offset + 8..data_offset + 12] .copy_from_slice(&(static_fs_data.len() as u32).to_le_bytes()); @@ -458,7 +477,7 @@ pub(crate) fn create_io_virt<'a>( Ok(virtual_files) } -fn stub_fs_virt(module: &mut Module) -> Result<()> { +pub(crate) fn stub_fs_virt(module: &mut Module) -> Result<()> { stub_imported_func(module, "wasi:cli-base/preopens", "get-directories", false)?; stub_imported_func( module, @@ -570,7 +589,12 @@ fn stub_fs_virt(module: &mut Module) -> Result<()> { "drop-directory-entry-stream", false, )?; + Ok(()) +} +pub(crate) fn stub_poll_streams_virt(module: &mut Module) -> Result<()> { + stub_imported_func(module, "wasi:poll/poll", "drop-pollable", false)?; + stub_imported_func(module, "wasi:poll/poll", "poll-oneoff", false)?; stub_imported_func( module, "wasi:io/streams", @@ -605,7 +629,14 @@ fn stub_fs_virt(module: &mut Module) -> Result<()> { Ok(()) } -fn stub_stdio_virt(module: &mut Module) -> Result<()> { +pub(crate) fn stub_clocks_virt(module: &mut Module) -> Result<()> { + stub_imported_func(module, "wasi:clocks/monotonic-clock", "now", false)?; + stub_imported_func(module, "wasi:clocks/monotonic-clock", "resolution", false)?; + stub_imported_func(module, "wasi:clocks/monotonic-clock", "subscribe", false)?; + Ok(()) +} + +pub(crate) fn stub_stdio_virt(module: &mut Module) -> Result<()> { stub_imported_func(module, "wasi:cli-base/stdin", "get-stdin", false)?; stub_imported_func(module, "wasi:cli-base/stdout", "get-stdout", false)?; stub_imported_func(module, "wasi:cli-base/stderr", "get-stderr", false)?; @@ -614,6 +645,262 @@ fn stub_stdio_virt(module: &mut Module) -> Result<()> { pub(crate) fn stub_io_virt(module: &mut Module) -> Result<()> { stub_stdio_virt(module)?; + stub_poll_streams_virt(module)?; + stub_fs_virt(module)?; + stub_clocks_virt(module)?; + Ok(()) +} + +pub(crate) fn strip_fs_virt(module: &mut Module) -> Result<()> { stub_fs_virt(module)?; + remove_exported_func(module, "wasi:cli-base/preopens#get-directories")?; + + remove_exported_func(module, "wasi:filesystem/filesystem#read-via-stream")?; + remove_exported_func(module, "wasi:filesystem/filesystem#write-via-stream")?; + remove_exported_func(module, "wasi:filesystem/filesystem#append-via-stream")?; + remove_exported_func(module, "wasi:filesystem/filesystem#advise")?; + remove_exported_func(module, "wasi:filesystem/filesystem#sync-data")?; + remove_exported_func(module, "wasi:filesystem/filesystem#get-flags")?; + remove_exported_func(module, "wasi:filesystem/filesystem#get-type")?; + remove_exported_func(module, "wasi:filesystem/filesystem#set-size")?; + remove_exported_func(module, "wasi:filesystem/filesystem#set-times")?; + remove_exported_func(module, "wasi:filesystem/filesystem#read")?; + remove_exported_func(module, "wasi:filesystem/filesystem#write")?; + remove_exported_func(module, "wasi:filesystem/filesystem#read-directory")?; + remove_exported_func(module, "wasi:filesystem/filesystem#sync")?; + remove_exported_func(module, "wasi:filesystem/filesystem#create-directory-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#stat")?; + remove_exported_func(module, "wasi:filesystem/filesystem#stat-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#set-times-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#link-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#open-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#readlink-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#remove-directory-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#rename-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#symlink-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#access-at")?; + remove_exported_func(module, "wasi:filesystem/filesystem#unlink-file-at")?; + remove_exported_func( + module, + "wasi:filesystem/filesystem#change-file-permissions-at", + )?; + remove_exported_func( + module, + "wasi:filesystem/filesystem#change-directory-permissions-at", + )?; + remove_exported_func(module, "wasi:filesystem/filesystem#lock-shared")?; + remove_exported_func(module, "wasi:filesystem/filesystem#lock-exclusive")?; + remove_exported_func(module, "wasi:filesystem/filesystem#try-lock-shared")?; + remove_exported_func(module, "wasi:filesystem/filesystem#try-lock-exclusive")?; + remove_exported_func(module, "wasi:filesystem/filesystem#unlock")?; + remove_exported_func(module, "wasi:filesystem/filesystem#drop-descriptor")?; + remove_exported_func(module, "wasi:filesystem/filesystem#read-directory-entry")?; + remove_exported_func( + module, + "wasi:filesystem/filesystem#drop-directory-entry-stream", + )?; + Ok(()) +} + +pub(crate) fn strip_clocks_virt(module: &mut Module) -> Result<()> { + stub_clocks_virt(module)?; + remove_exported_func(module, "wasi:clocks/monotonic-clock#now")?; + remove_exported_func(module, "wasi:clocks/monotonic-clock#resolution")?; + remove_exported_func(module, "wasi:clocks/monotonic-clock#subscribe")?; + Ok(()) +} + +pub(crate) fn strip_http_virt(module: &mut Module) -> Result<()> { + stub_http_virt(module)?; + remove_exported_func(module, "wasi:http/types#drop-fields")?; + remove_exported_func(module, "wasi:http/types#new-fields")?; + remove_exported_func(module, "wasi:http/types#fields-get")?; + remove_exported_func(module, "wasi:http/types#fields-set")?; + remove_exported_func(module, "wasi:http/types#fields-delete")?; + remove_exported_func(module, "wasi:http/types#fields-append")?; + remove_exported_func(module, "wasi:http/types#fields-entries")?; + remove_exported_func(module, "wasi:http/types#fields-clone")?; + remove_exported_func(module, "wasi:http/types#finish-incoming-stream")?; + remove_exported_func(module, "wasi:http/types#finish-outgoing-stream")?; + remove_exported_func(module, "wasi:http/types#drop-incoming-request")?; + remove_exported_func(module, "wasi:http/types#drop-outgoing-request")?; + remove_exported_func(module, "wasi:http/types#incoming-request-method")?; + remove_exported_func(module, "wasi:http/types#incoming-request-path")?; + remove_exported_func(module, "wasi:http/types#incoming-request-query")?; + remove_exported_func(module, "wasi:http/types#incoming-request-scheme")?; + remove_exported_func(module, "wasi:http/types#incoming-request-authority")?; + remove_exported_func(module, "wasi:http/types#incoming-request-headers")?; + remove_exported_func(module, "wasi:http/types#incoming-request-consume")?; + remove_exported_func(module, "wasi:http/types#new-outgoing-request")?; + remove_exported_func(module, "wasi:http/types#outgoing-request-write")?; + remove_exported_func(module, "wasi:http/types#drop-response-outparam")?; + remove_exported_func(module, "wasi:http/types#set-response-outparam")?; + remove_exported_func(module, "wasi:http/types#drop-incoming-response")?; + remove_exported_func(module, "wasi:http/types#drop-outgoing-response")?; + remove_exported_func(module, "wasi:http/types#incoming-response-status")?; + remove_exported_func(module, "wasi:http/types#incoming-response-headers")?; + remove_exported_func(module, "wasi:http/types#incoming-response-consume")?; + remove_exported_func(module, "wasi:http/types#new-outgoing-response")?; + remove_exported_func(module, "wasi:http/types#outgoing-response-write")?; + remove_exported_func(module, "wasi:http/types#drop-future-incoming-response")?; + remove_exported_func(module, "wasi:http/types#future-incoming-response-get")?; + remove_exported_func(module, "wasi:http/types#listen-to-future-incoming-response")?; + Ok(()) +} + +pub(crate) fn stub_http_virt(module: &mut Module) -> Result<()> { + stub_imported_func(module, "wasi:http/types", "drop-fields", false)?; + stub_imported_func(module, "wasi:http/types", "new-fields", false)?; + stub_imported_func(module, "wasi:http/types", "fields-get", false)?; + stub_imported_func(module, "wasi:http/types", "fields-set", false)?; + stub_imported_func(module, "wasi:http/types", "fields-delete", false)?; + stub_imported_func(module, "wasi:http/types", "fields-append", false)?; + stub_imported_func(module, "wasi:http/types", "fields-entries", false)?; + stub_imported_func(module, "wasi:http/types", "fields-clone", false)?; + stub_imported_func(module, "wasi:http/types", "finish-incoming-stream", false)?; + stub_imported_func(module, "wasi:http/types", "finish-outgoing-stream", false)?; + stub_imported_func(module, "wasi:http/types", "drop-incoming-request", false)?; + stub_imported_func(module, "wasi:http/types", "drop-outgoing-request", false)?; + stub_imported_func(module, "wasi:http/types", "incoming-request-method", false)?; + stub_imported_func(module, "wasi:http/types", "incoming-request-path", false)?; + stub_imported_func(module, "wasi:http/types", "incoming-request-query", false)?; + stub_imported_func(module, "wasi:http/types", "incoming-request-scheme", false)?; + stub_imported_func( + module, + "wasi:http/types", + "incoming-request-authority", + false, + )?; + stub_imported_func(module, "wasi:http/types", "incoming-request-headers", false)?; + stub_imported_func(module, "wasi:http/types", "incoming-request-consume", false)?; + stub_imported_func(module, "wasi:http/types", "new-outgoing-request", false)?; + stub_imported_func(module, "wasi:http/types", "outgoing-request-write", false)?; + stub_imported_func(module, "wasi:http/types", "drop-response-outparam", false)?; + stub_imported_func(module, "wasi:http/types", "set-response-outparam", false)?; + stub_imported_func(module, "wasi:http/types", "drop-incoming-response", false)?; + stub_imported_func(module, "wasi:http/types", "drop-outgoing-response", false)?; + stub_imported_func(module, "wasi:http/types", "incoming-response-status", false)?; + stub_imported_func( + module, + "wasi:http/types", + "incoming-response-headers", + false, + )?; + stub_imported_func( + module, + "wasi:http/types", + "incoming-response-consume", + false, + )?; + stub_imported_func(module, "wasi:http/types", "new-outgoing-response", false)?; + stub_imported_func(module, "wasi:http/types", "outgoing-response-write", false)?; + stub_imported_func( + module, + "wasi:http/types", + "drop-future-incoming-response", + false, + )?; + stub_imported_func( + module, + "wasi:http/types", + "future-incoming-response-get", + false, + )?; + stub_imported_func( + module, + "wasi:http/types", + "listen-to-future-incoming-response", + false, + )?; + Ok(()) +} + +pub(crate) fn strip_stdio_virt(module: &mut Module) -> Result<()> { + stub_stdio_virt(module)?; + remove_exported_func(module, "wasi:cli-base/stdin#get-stdin")?; + remove_exported_func(module, "wasi:cli-base/stdout#get-stdout")?; + remove_exported_func(module, "wasi:cli-base/stderr#get-stderr")?; + Ok(()) +} + +pub(crate) fn strip_io_virt(module: &mut Module) -> Result<()> { + strip_fs_virt(module)?; + strip_clocks_virt(module)?; + strip_http_virt(module)?; + strip_stdio_virt(module)?; + + remove_exported_func(module, "wasi:io/streams#read")?; + remove_exported_func(module, "wasi:io/streams#blocking-read")?; + remove_exported_func(module, "wasi:io/streams#skip")?; + remove_exported_func(module, "wasi:io/streams#blocking-skip")?; + remove_exported_func(module, "wasi:io/streams#subscribe-to-input-stream")?; + remove_exported_func(module, "wasi:io/streams#drop-input-stream")?; + remove_exported_func(module, "wasi:io/streams#write")?; + remove_exported_func(module, "wasi:io/streams#blocking-write")?; + remove_exported_func(module, "wasi:io/streams#write-zeroes")?; + remove_exported_func(module, "wasi:io/streams#blocking-write-zeroes")?; + remove_exported_func(module, "wasi:io/streams#splice")?; + remove_exported_func(module, "wasi:io/streams#blocking-splice")?; + remove_exported_func(module, "wasi:io/streams#forward")?; + remove_exported_func(module, "wasi:io/streams#subscribe-to-output-stream")?; + remove_exported_func(module, "wasi:io/streams#drop-output-stream")?; + + remove_exported_func(module, "wasi:poll/poll#drop-pollable")?; + remove_exported_func(module, "wasi:poll/poll#poll-oneoff")?; + + // remove_exported_func(module, "wasi:sockets/ip-name-lookup#resolve-addresses")?; + // remove_exported_func(module, "wasi:sockets/ip-name-lookup#resolve-next-address")?; + // remove_exported_func( + // module, + // "wasi:sockets/ip-name-lookup#drop-resolve-address-stream", + // )?; + // remove_exported_func(module, "wasi:sockets/ip-name-lookup#subscribe")?; + + // remove_exported_func(module, "wasi:sockets/tcp#start-bind")?; + // remove_exported_func(module, "wasi:sockets/tcp#finish-bind")?; + // remove_exported_func(module, "wasi:sockets/tcp#start-connect")?; + // remove_exported_func(module, "wasi:sockets/tcp#finish-connect")?; + // remove_exported_func(module, "wasi:sockets/tcp#start-listen")?; + // remove_exported_func(module, "wasi:sockets/tcp#finish-listen")?; + // remove_exported_func(module, "wasi:sockets/tcp#accept")?; + // remove_exported_func(module, "wasi:sockets/tcp#local-address")?; + // remove_exported_func(module, "wasi:sockets/tcp#remote-address")?; + // remove_exported_func(module, "wasi:sockets/tcp#address-family")?; + // remove_exported_func(module, "wasi:sockets/tcp#ipv6-only")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-ipv6-only")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-listen-backlog-size")?; + // remove_exported_func(module, "wasi:sockets/tcp#keep-alive")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-keep-alive")?; + // remove_exported_func(module, "wasi:sockets/tcp#no-delay")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-no-delay")?; + // remove_exported_func(module, "wasi:sockets/tcp#unicast-hop-limit")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-unicast-hop-limit")?; + // remove_exported_func(module, "wasi:sockets/tcp#receive-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-receive-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/tcp#send-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/tcp#set-send-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/tcp#subscribe")?; + // remove_exported_func(module, "wasi:sockets/tcp#shutdown")?; + // remove_exported_func(module, "wasi:sockets/tcp#drop-tcp-socket")?; + + // remove_exported_func(module, "wasi:sockets/udp#start-bind")?; + // remove_exported_func(module, "wasi:sockets/udp#finish-bind")?; + // remove_exported_func(module, "wasi:sockets/udp#start-connect")?; + // remove_exported_func(module, "wasi:sockets/udp#finish-connect")?; + // remove_exported_func(module, "wasi:sockets/udp#receive")?; + // remove_exported_func(module, "wasi:sockets/udp#send")?; + // remove_exported_func(module, "wasi:sockets/udp#local-address")?; + // remove_exported_func(module, "wasi:sockets/udp#remote-address")?; + // remove_exported_func(module, "wasi:sockets/udp#address-family")?; + // remove_exported_func(module, "wasi:sockets/udp#ipv6-only")?; + // remove_exported_func(module, "wasi:sockets/udp#set-ipv6-only")?; + // remove_exported_func(module, "wasi:sockets/udp#unicast-hop-limit")?; + // remove_exported_func(module, "wasi:sockets/udp#set-unicast-hop-limit")?; + // remove_exported_func(module, "wasi:sockets/udp#receive-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/udp#set-receive-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/udp#send-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/udp#set-send-buffer-size")?; + // remove_exported_func(module, "wasi:sockets/udp#subscribe")?; + // remove_exported_func(module, "wasi:sockets/udp#drop-udp-socket")?; Ok(()) } diff --git a/tests/virt.rs b/tests/virt.rs index c9454b3..0c8c70e 100644 --- a/tests/virt.rs +++ b/tests/virt.rs @@ -70,9 +70,10 @@ async fn virt_test() -> Result<()> { let test_case_path = test_case.path(); let test_case_file_name = test_case.file_name().to_string_lossy().to_string(); let test_case_name = test_case_file_name.strip_suffix(".toml").unwrap(); + println!("- {:?}", test_case_path); // Filtering... - // if test_case_name != "env-allow" { + // if test_case_name != "passthrough" { // continue; // } @@ -108,6 +109,7 @@ async fn virt_test() -> Result<()> { virt_component_path.set_extension("virt.wasm"); let mut virt_opts = test.virt_opts.clone().unwrap_or_default(); virt_opts.exit(Default::default()); + // virt_opts.wasm_opt = Some(false); let virt_component = virt_opts .finish() diff --git a/virtual-adapter/src/io.rs b/virtual-adapter/src/io.rs index 3b28704..fa38fc9 100644 --- a/virtual-adapter/src/io.rs +++ b/virtual-adapter/src/io.rs @@ -2,11 +2,22 @@ use crate::exports::wasi::cli_base::preopens::Preopens; use crate::exports::wasi::cli_base::stderr::Stderr; use crate::exports::wasi::cli_base::stdin::Stdin; use crate::exports::wasi::cli_base::stdout::Stdout; +use crate::exports::wasi::clocks::monotonic_clock::MonotonicClock; use crate::exports::wasi::filesystem::filesystem::{ AccessType, Advice, Datetime, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, ErrorCode, Filesystem, Modes, NewTimestamp, OpenFlags, PathFlags, }; -use crate::exports::wasi::io::streams::{StreamError, Streams}; +use crate::exports::wasi::http::types::{ + Error, Fields, Headers, Method, Scheme, StatusCode, Trailers, Types, +}; +use crate::exports::wasi::io::streams::{InputStream, OutputStream, StreamError, Streams}; +use crate::exports::wasi::poll::poll::Poll; +// use crate::exports::wasi::sockets::ip_name_lookup::{ +// IpAddressFamily, IpNameLookup, Network, ResolveAddressStream, +// }; +// use crate::exports::wasi::sockets::tcp::ErrorCode as NetworkErrorCode; +// use crate::exports::wasi::sockets::tcp::{IpSocketAddress, ShutdownType, Tcp, TcpSocket}; +// use crate::exports::wasi::sockets::udp::{Datagram, Udp, UdpSocket}; use crate::wasi::cli_base::preopens; use crate::wasi::cli_base::stderr; @@ -15,6 +26,15 @@ use crate::wasi::cli_base::stdout; use crate::wasi::filesystem::filesystem; use crate::wasi::io::streams; +// these are all the subsystems which touch streams + poll +use crate::wasi::clocks::monotonic_clock; +use crate::wasi::http::types; +use crate::wasi::poll::poll; +//use crate::wasi::sockets::ip_name_lookup; +//use crate::wasi::sockets::network; +//use crate::wasi::sockets::tcp; +//use crate::wasi::sockets::udp; + use crate::VirtAdapter; // for debugging @@ -28,11 +48,11 @@ use std::ffi::CStr; use std::slice; // io flags -const FLAGS_ENABLE_STDIN: u32 = 1; -const FLAGS_ENABLE_STDOUT: u32 = 2; -const FLAGS_ENABLE_STDERR: u32 = 4; -const FLAGS_HOST_PREOPENS: u32 = 8; -const FLAGS_HOST_PASSTHROUGH: u32 = 16; +const FLAGS_ENABLE_STDIN: u32 = 1 << 0; +const FLAGS_ENABLE_STDOUT: u32 = 1 << 1; +const FLAGS_ENABLE_STDERR: u32 = 1 << 2; +const FLAGS_HOST_PREOPENS: u32 = 1 << 3; +const FLAGS_HOST_PASSTHROUGH: u32 = 1 << 4; // static fs config #[repr(C)] @@ -379,6 +399,13 @@ pub struct IoState { descriptor_table: BTreeMap, stream_cnt: u32, stream_table: BTreeMap, + poll_cnt: u32, + poll_table: BTreeMap, +} + +enum PollTarget { + Null, + Host(u32), } static mut STATE: IoState = IoState { @@ -389,6 +416,8 @@ static mut STATE: IoState = IoState { descriptor_table: BTreeMap::new(), stream_cnt: 0, stream_table: BTreeMap::new(), + poll_cnt: 0, + poll_table: BTreeMap::new(), }; enum Stream { @@ -507,6 +536,11 @@ impl IoState { unsafe { STATE.preopen_directories.push(entry) } } + // we have one virtual pollable at poll 0 which is a null pollable + // this is just an immediately resolving pollable + unsafe { STATE.poll_cnt += 1 }; + unsafe { STATE.poll_table.insert(0, PollTarget::Null) }; + unsafe { STATE.initialized = true }; } fn get_host_preopen<'a>(path: &'a str) -> Option<(u32, &'a str)> { @@ -572,6 +606,15 @@ impl IoState { None => Err(StreamError {}), } } + fn new_poll(target: PollTarget) -> u32 { + let pid = unsafe { STATE.poll_cnt }; + unsafe { STATE.poll_cnt += 1 }; + unsafe { STATE.poll_table.insert(pid, target) }; + pid + } + fn get_poll<'a>(pid: u32) -> Option<&'a mut PollTarget> { + unsafe { STATE.poll_table.get_mut(&pid) } + } } impl Preopens for VirtAdapter { @@ -907,7 +950,9 @@ impl Streams for VirtAdapter { match stream { Stream::Null => 0, Stream::StaticFile(_) | Stream::StaticDir(_) => 0, - Stream::Host(sid) => streams::subscribe_to_input_stream(*sid), + Stream::Host(sid) => { + IoState::new_poll(PollTarget::Host(streams::subscribe_to_input_stream(*sid))) + } } } fn drop_input_stream(sid: u32) { @@ -1020,7 +1065,9 @@ impl Streams for VirtAdapter { match stream { Stream::Null => 0, Stream::StaticFile(_) | Stream::StaticDir(_) => 0, - Stream::Host(sid) => streams::subscribe_to_output_stream(*sid), + Stream::Host(sid) => { + IoState::new_poll(PollTarget::Host(streams::subscribe_to_output_stream(*sid))) + } } } fn drop_output_stream(sid: u32) { @@ -1058,3 +1105,492 @@ impl Stderr for VirtAdapter { 2 } } + +impl Poll for VirtAdapter { + fn drop_pollable(pid: u32) { + let Some(poll) = IoState::get_poll(pid) else { + return; + }; + match poll { + PollTarget::Null => {} + PollTarget::Host(host_pid) => poll::drop_pollable(*host_pid), + } + unsafe { STATE.poll_table.remove(&pid) }; + } + fn poll_oneoff(list: Vec) -> Vec { + let has_host_polls = list + .iter() + .find(|&&pid| matches!(IoState::get_poll(pid), Some(PollTarget::Host(_)))) + .is_some(); + let has_virt_polls = list + .iter() + .find(|&&pid| matches!(IoState::get_poll(pid), Some(PollTarget::Null))) + .is_some(); + if has_host_polls && !has_virt_polls { + return poll::poll_oneoff(&list); + } + if has_virt_polls { + return std::iter::repeat(1).take(list.len()).collect(); + } + let mut host_polls = Vec::new(); + for pid in &list { + if let Some(PollTarget::Host(host_pid)) = IoState::get_poll(*pid) { + host_polls.push(*host_pid); + } + } + let host_ready = poll::poll_oneoff(&host_polls); + let mut ready = Vec::with_capacity(list.len()); + let mut host_idx = 0; + for pid in &list { + match IoState::get_poll(*pid).unwrap() { + PollTarget::Null => { + ready.push(1); + } + PollTarget::Host(_) => { + ready.push(host_ready[host_idx]); + host_idx += 1; + } + } + } + ready + } +} + +impl MonotonicClock for VirtAdapter { + fn now() -> u64 { + monotonic_clock::now() + } + fn resolution() -> u64 { + monotonic_clock::resolution() + } + fn subscribe(when: u64, absolute: bool) -> u32 { + let host_pid = monotonic_clock::subscribe(when, absolute); + IoState::new_poll(PollTarget::Host(host_pid)) + } +} + +impl Types for VirtAdapter { + fn drop_fields(fields: Fields) { + types::drop_fields(fields) + } + fn new_fields(entries: Vec<(String, String)>) -> Fields { + types::new_fields(&entries) + } + fn fields_get(fields: Fields, name: String) -> Vec { + types::fields_get(fields, &name) + } + fn fields_set(fields: Fields, name: String, value: Vec) { + types::fields_set(fields, &name, value.as_slice()) + } + fn fields_delete(fields: Fields, name: String) { + types::fields_delete(fields, &name) + } + fn fields_append(fields: Fields, name: String, value: String) { + types::fields_append(fields, &name, &value) + } + fn fields_entries(fields: Fields) -> Vec<(String, String)> { + types::fields_entries(fields) + } + fn fields_clone(fields: Fields) -> Fields { + types::fields_clone(fields) + } + fn finish_incoming_stream(s: InputStream) -> Option { + types::finish_incoming_stream(s) + } + fn finish_outgoing_stream(s: OutputStream, trailers: Option) { + types::finish_outgoing_stream(s, trailers) + } + fn drop_incoming_request(request: u32) { + types::drop_incoming_request(request) + } + fn drop_outgoing_request(request: u32) { + types::drop_outgoing_request(request) + } + fn incoming_request_method(request: u32) -> Method { + method_map_rev(types::incoming_request_method(request)) + } + fn incoming_request_path(request: u32) -> String { + types::incoming_request_path(request) + } + fn incoming_request_query(request: u32) -> String { + types::incoming_request_query(request) + } + fn incoming_request_scheme(request: u32) -> Option { + types::incoming_request_scheme(request).map(scheme_map_rev) + } + fn incoming_request_authority(request: u32) -> String { + types::incoming_request_authority(request) + } + fn incoming_request_headers(request: u32) -> Headers { + types::incoming_request_headers(request) + } + fn incoming_request_consume(request: u32) -> Result { + types::incoming_request_consume(request) + } + fn new_outgoing_request( + method: Method, + path: String, + query: String, + scheme: Option, + authority: String, + headers: Headers, + ) -> u32 { + types::new_outgoing_request( + &method_map(method), + &path, + &query, + scheme.map(|s| scheme_map(s)).as_ref(), + &authority, + headers, + ) + } + fn outgoing_request_write(request: u32) -> Result { + types::outgoing_request_write(request) + } + fn drop_response_outparam(response: u32) { + types::drop_response_outparam(response) + } + fn set_response_outparam(response: Result) -> Result<(), ()> { + match response { + Ok(res) => types::set_response_outparam(Ok(res)), + Err(err) => { + let err = http_err_map(err); + types::set_response_outparam(Err(&err)) + } + } + } + fn drop_incoming_response(response: u32) { + types::drop_incoming_response(response) + } + fn drop_outgoing_response(response: u32) { + types::drop_outgoing_response(response) + } + fn incoming_response_status(response: u32) -> StatusCode { + types::incoming_response_status(response) + } + fn incoming_response_headers(response: u32) -> Headers { + types::incoming_response_headers(response) + } + fn incoming_response_consume(response: u32) -> Result { + types::incoming_response_consume(response) + } + fn new_outgoing_response(status_code: StatusCode, headers: Headers) -> u32 { + types::new_outgoing_response(status_code, headers) + } + fn outgoing_response_write(response: u32) -> Result { + types::outgoing_response_write(response) + } + fn drop_future_incoming_response(f: u32) { + types::drop_future_incoming_response(f) + } + fn future_incoming_response_get(f: u32) -> Option> { + types::future_incoming_response_get(f).map(|o| o.map_err(http_err_map_rev)) + } + fn listen_to_future_incoming_response(f: u32) -> u32 { + types::listen_to_future_incoming_response(f) + } +} + +fn scheme_map(scheme: Scheme) -> types::Scheme { + match scheme { + Scheme::Http => types::Scheme::Http, + Scheme::Https => types::Scheme::Https, + Scheme::Other(s) => types::Scheme::Other(s), + } +} + +fn scheme_map_rev(scheme: types::Scheme) -> Scheme { + match scheme { + types::Scheme::Http => Scheme::Http, + types::Scheme::Https => Scheme::Https, + types::Scheme::Other(s) => Scheme::Other(s), + } +} + +fn method_map_rev(method: types::Method) -> Method { + match method { + types::Method::Get => Method::Get, + types::Method::Head => Method::Head, + types::Method::Post => Method::Post, + types::Method::Put => Method::Put, + types::Method::Delete => Method::Delete, + types::Method::Connect => Method::Connect, + types::Method::Options => Method::Options, + types::Method::Trace => Method::Trace, + types::Method::Patch => Method::Patch, + types::Method::Other(s) => Method::Other(s), + } +} + +fn method_map(method: Method) -> types::Method { + match method { + Method::Get => types::Method::Get, + Method::Head => types::Method::Head, + Method::Post => types::Method::Post, + Method::Put => types::Method::Put, + Method::Delete => types::Method::Delete, + Method::Connect => types::Method::Connect, + Method::Options => types::Method::Options, + Method::Trace => types::Method::Trace, + Method::Patch => types::Method::Patch, + Method::Other(s) => types::Method::Other(s), + } +} + +fn http_err_map(err: Error) -> types::Error { + match err { + Error::InvalidUrl(s) => types::Error::InvalidUrl(s), + Error::TimeoutError(s) => types::Error::TimeoutError(s), + Error::ProtocolError(s) => types::Error::ProtocolError(s), + Error::UnexpectedError(s) => types::Error::UnexpectedError(s), + } +} + +fn http_err_map_rev(err: types::Error) -> Error { + match err { + types::Error::InvalidUrl(s) => Error::InvalidUrl(s), + types::Error::TimeoutError(s) => Error::TimeoutError(s), + types::Error::ProtocolError(s) => Error::ProtocolError(s), + types::Error::UnexpectedError(s) => Error::UnexpectedError(s), + } +} + +// impl IpNameLookup for VirtAdapter { +// fn resolve_addresses( +// network: Network, +// name: String, +// address_family: Option, +// include_unavailable: bool, +// ) -> Result { +// ip_name_lookup::resolve_addresses(network, &name, address_family, include_unavailable) +// } +// fn resolve_next_address( +// this: ResolveAddressStream, +// ) -> Result, network::ErrorCode> { +// ip_name_lookup::resolve_next_address(this) +// } +// fn drop_resolve_address_stream(this: ResolveAddressStream) { +// ip_name_lookup::drop_resolve_address_stream(this) +// } +// fn subscribe(this: ResolveAddressStream) -> u32 { +// ip_name_lookup::subscribe(this) +// } +// } + +// impl Tcp for VirtAdapter { +// fn start_bind( +// this: TcpSocket, +// network: Network, +// local_address: IpSocketAddress, +// ) -> Result<(), NetworkErrorCode> { +// tcp::start_bind(this, network, local_address) +// } +// fn finish_bind(this: TcpSocket) -> Result<(), NetworkErrorCode> { +// tcp::finish_bind(this) +// } +// fn start_connect( +// this: TcpSocket, +// network: Network, +// remote_address: IpSocketAddress, +// ) -> Result<(), NetworkErrorCode> { +// tcp::start_connect(this, network, remote_address) +// } +// fn finish_connect(this: TcpSocket) -> Result<(InputStream, OutputStream), NetworkErrorCode> { +// tcp::finish_connect(this) +// } +// fn start_listen(this: TcpSocket, network: Network) -> Result<(), NetworkErrorCode> { +// tcp::start_listen(this, network) +// } +// fn finish_listen(this: TcpSocket) -> Result<(), NetworkErrorCode> { +// tcp::finish_listen(this) +// } +// fn accept( +// this: TcpSocket, +// ) -> Result<(tcp::TcpSocket, InputStream, OutputStream), NetworkErrorCode> { +// tcp::accept(this) +// } +// fn local_address(this: TcpSocket) -> Result { +// tcp::local_address(this) +// } +// fn remote_address(this: TcpSocket) -> Result { +// tcp::remote_address(this) +// } +// fn address_family(this: TcpSocket) -> IpAddressFamily { +// tcp::address_family(this) +// } +// fn ipv6_only(this: TcpSocket) -> Result { +// tcp::ipv6_only(this) +// } +// fn set_ipv6_only(this: TcpSocket, value: bool) -> Result<(), NetworkErrorCode> { +// tcp::set_ipv6_only(this, value) +// } +// fn set_listen_backlog_size(this: TcpSocket, value: u64) -> Result<(), NetworkErrorCode> { +// tcp::set_listen_backlog_size(this, value) +// } +// fn keep_alive(this: TcpSocket) -> Result { +// tcp::keep_alive(this) +// } +// fn set_keep_alive(this: TcpSocket, value: bool) -> Result<(), NetworkErrorCode> { +// tcp::set_keep_alive(this, value) +// } +// fn no_delay(this: TcpSocket) -> Result { +// tcp::no_delay(this) +// } +// fn set_no_delay(this: TcpSocket, value: bool) -> Result<(), NetworkErrorCode> { +// tcp::set_no_delay(this, value) +// } +// fn unicast_hop_limit(this: TcpSocket) -> Result { +// tcp::unicast_hop_limit(this) +// } +// fn set_unicast_hop_limit(this: TcpSocket, value: u8) -> Result<(), NetworkErrorCode> { +// tcp::set_unicast_hop_limit(this, value) +// } +// fn receive_buffer_size(this: TcpSocket) -> Result { +// tcp::receive_buffer_size(this) +// } +// fn set_receive_buffer_size(this: TcpSocket, value: u64) -> Result<(), NetworkErrorCode> { +// tcp::set_receive_buffer_size(this, value) +// } +// fn send_buffer_size(this: TcpSocket) -> Result { +// tcp::send_buffer_size(this) +// } +// fn set_send_buffer_size(this: TcpSocket, value: u64) -> Result<(), NetworkErrorCode> { +// tcp::set_send_buffer_size(this, value) +// } +// fn subscribe(this: TcpSocket) -> u32 { +// tcp::subscribe(this) +// } +// fn shutdown(this: TcpSocket, shutdown_type: ShutdownType) -> Result<(), NetworkErrorCode> { +// tcp::shutdown( +// this, +// match shutdown_type { +// ShutdownType::Receive => tcp::ShutdownType::Receive, +// ShutdownType::Send => tcp::ShutdownType::Send, +// ShutdownType::Both => tcp::ShutdownType::Both, +// }, +// ) +// } +// fn drop_tcp_socket(this: TcpSocket) { +// tcp::drop_tcp_socket(this) +// } +// } + +// fn network_err_map(err: NetworkErrorCode) -> network::ErrorCode { +// match err { +// NetworkErrorCode::Unknown => network::ErrorCode::Unknown, +// NetworkErrorCode::AccessDenied => network::ErrorCode::AccessDenied, +// NetworkErrorCode::NotSupported => network::ErrorCode::NotSupported, +// NetworkErrorCode::OutOfMemory => network::ErrorCode::OutOfMemory, +// NetworkErrorCode::Timeout => network::ErrorCode::Timeout, +// NetworkErrorCode::ConcurrencyConflict => network::ErrorCode::ConcurrencyConflict, +// NetworkErrorCode::NotInProgress => network::ErrorCode::NotInProgress, +// NetworkErrorCode::WouldBlock => network::ErrorCode::WouldBlock, +// NetworkErrorCode::AddressFamilyNotSupported => { +// network::ErrorCode::AddressFamilyNotSupported +// } +// NetworkErrorCode::AddressFamilyMismatch => network::ErrorCode::AddressFamilyMismatch, +// NetworkErrorCode::InvalidRemoteAddress => network::ErrorCode::InvalidRemoteAddress, +// NetworkErrorCode::Ipv4OnlyOperation => network::ErrorCode::Ipv4OnlyOperation, +// NetworkErrorCode::Ipv6OnlyOperation => network::ErrorCode::Ipv6OnlyOperation, +// NetworkErrorCode::NewSocketLimit => network::ErrorCode::NewSocketLimit, +// NetworkErrorCode::AlreadyAttached => network::ErrorCode::AlreadyAttached, +// NetworkErrorCode::AlreadyBound => network::ErrorCode::AlreadyBound, +// NetworkErrorCode::AlreadyConnected => network::ErrorCode::AlreadyConnected, +// NetworkErrorCode::NotBound => network::ErrorCode::NotBound, +// NetworkErrorCode::NotConnected => network::ErrorCode::NotConnected, +// NetworkErrorCode::AddressNotBindable => network::ErrorCode::AddressNotBindable, +// NetworkErrorCode::AddressInUse => network::ErrorCode::AddressInUse, +// NetworkErrorCode::EphemeralPortsExhausted => network::ErrorCode::EphemeralPortsExhausted, +// NetworkErrorCode::RemoteUnreachable => network::ErrorCode::RemoteUnreachable, +// NetworkErrorCode::AlreadyListening => network::ErrorCode::AlreadyListening, +// NetworkErrorCode::NotListening => network::ErrorCode::NotListening, +// NetworkErrorCode::ConnectionRefused => network::ErrorCode::ConnectionRefused, +// NetworkErrorCode::ConnectionReset => network::ErrorCode::ConnectionReset, +// NetworkErrorCode::DatagramTooLarge => network::ErrorCode::DatagramTooLarge, +// NetworkErrorCode::InvalidName => network::ErrorCode::InvalidName, +// NetworkErrorCode::NameUnresolvable => network::ErrorCode::NameUnresolvable, +// NetworkErrorCode::TemporaryResolverFailure => network::ErrorCode::TemporaryResolverFailure, +// NetworkErrorCode::PermanentResolverFailure => network::ErrorCode::PermanentResolverFailure, +// } +// } + +// impl Udp for VirtAdapter { +// fn start_bind( +// this: UdpSocket, +// network: Network, +// local_address: IpSocketAddress, +// ) -> Result<(), NetworkErrorCode> { +// udp::start_bind(this, network, local_address) +// } +// fn finish_bind(this: UdpSocket) -> Result<(), NetworkErrorCode> { +// udp::finish_bind(this) +// } +// fn start_connect( +// this: UdpSocket, +// network: Network, +// remote_address: IpSocketAddress, +// ) -> Result<(), NetworkErrorCode> { +// udp::start_connect(this, network, remote_address) +// } +// fn finish_connect(this: UdpSocket) -> Result<(), NetworkErrorCode> { +// udp::finish_connect(this) +// } +// fn receive(this: UdpSocket) -> Result { +// match udp::receive(this) { +// Ok(datagram) => Ok(Datagram { +// data: datagram.data, +// remote_address: datagram.remote_address, +// }), +// Err(err) => Err(network_err_map(err)), +// } +// } +// fn send(this: UdpSocket, datagram: Datagram) -> Result<(), NetworkErrorCode> { +// udp::send( +// this, +// &udp::Datagram { +// data: datagram.data, +// remote_address: datagram.remote_address, +// }, +// ) +// .map_err(network_err_map) +// } +// fn local_address(this: UdpSocket) -> Result { +// udp::local_address(this) +// } +// fn remote_address(this: UdpSocket) -> Result { +// udp::remote_address(this) +// } +// fn address_family(this: UdpSocket) -> IpAddressFamily { +// udp::address_family(this) +// } +// fn ipv6_only(this: UdpSocket) -> Result { +// udp::ipv6_only(this) +// } +// fn set_ipv6_only(this: UdpSocket, value: bool) -> Result<(), NetworkErrorCode> { +// udp::set_ipv6_only(this, value) +// } +// fn unicast_hop_limit(this: UdpSocket) -> Result { +// udp::unicast_hop_limit(this) +// } +// fn set_unicast_hop_limit(this: UdpSocket, value: u8) -> Result<(), NetworkErrorCode> { +// udp::set_unicast_hop_limit(this, value) +// } +// fn receive_buffer_size(this: UdpSocket) -> Result { +// udp::receive_buffer_size(this) +// } +// fn set_receive_buffer_size(this: UdpSocket, value: u64) -> Result<(), NetworkErrorCode> { +// udp::set_receive_buffer_size(this, value) +// } +// fn send_buffer_size(this: UdpSocket) -> Result { +// udp::send_buffer_size(this) +// } +// fn set_send_buffer_size(this: UdpSocket, value: u64) -> Result<(), NetworkErrorCode> { +// udp::set_send_buffer_size(this, value) +// } +// fn subscribe(this: UdpSocket) -> u32 { +// udp::subscribe(this) +// } +// fn drop_udp_socket(this: UdpSocket) { +// udp::drop_udp_socket(this) +// } +// } diff --git a/wit/virt.wit b/wit/virt.wit index cf0358a..0b4c629 100644 --- a/wit/virt.wit +++ b/wit/virt.wit @@ -20,6 +20,18 @@ world virtual-adapter { export wasi:cli-base/stdin export wasi:cli-base/stdout export wasi:cli-base/stderr + import wasi:poll/poll + export wasi:poll/poll + import wasi:clocks/monotonic-clock + export wasi:clocks/monotonic-clock + import wasi:http/types + export wasi:http/types + // import wasi:sockets/ip-name-lookup + // export wasi:sockets/ip-name-lookup + // import wasi:sockets/tcp + // export wasi:sockets/tcp + // import wasi:sockets/udp + // export wasi:sockets/udp } world virtual-base { @@ -34,19 +46,47 @@ world virtual-env { } world virtual-io { - import wasi:cli-base/preopens - import wasi:filesystem/filesystem import wasi:io/streams export wasi:io/streams + import wasi:poll/poll + export wasi:poll/poll + // import wasi:sockets/ip-name-lookup + // export wasi:sockets/ip-name-lookup + // import wasi:sockets/tcp + // export wasi:sockets/tcp + // import wasi:sockets/udp + // export wasi:sockets/udp +} + +world virtual-exit { + import wasi:cli-base/exit + export wasi:cli-base/exit +} + +world virtual-fs { + import wasi:cli-base/preopens + import wasi:filesystem/filesystem export wasi:filesystem/filesystem export wasi:cli-base/preopens +} + +world virtual-stdio { import wasi:cli-base/stdin import wasi:cli-base/stdout import wasi:cli-base/stderr export wasi:cli-base/stdin export wasi:cli-base/stdout export wasi:cli-base/stderr - import wasi:poll/poll +} + +world virtual-clocks { + import wasi:clocks/monotonic-clock + export wasi:clocks/monotonic-clock +} + +world virtual-http { + import wasi:http/types + export wasi:http/types } world virt-test {