From fb35afc1858421ad14c6bce5806b16085bc7ee74 Mon Sep 17 00:00:00 2001 From: Stefan Lankes Date: Mon, 15 Jul 2024 22:21:00 +0200 Subject: [PATCH] extend WASI preview1 support Following interfaces are now available: - fd_read - fd_write - fd_seek - fd_close - clock_time_get - path_open - path_unlink_file - fd_prestat_get - fd_prestat_dir_name - fd_filestat_get - args_sizes_get - environ_get - environ_sizes_get --- examples/wasmtime/Cargo.toml | 4 +- examples/wasmtime/src/preview1.rs | 183 +++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 5 deletions(-) diff --git a/examples/wasmtime/Cargo.toml b/examples/wasmtime/Cargo.toml index ba90ad967..08d89c04c 100644 --- a/examples/wasmtime/Cargo.toml +++ b/examples/wasmtime/Cargo.toml @@ -14,9 +14,9 @@ cfg-if = "1" log = { version = "0.4" } #, features = ["kv_unstable"]} simple_logger = { version = "5.0", default-features = false } wasmtime = { version = "22.0", default-features = false, features = ["std", "runtime", "cranelift", "threads", "parallel-compilation"] } #"pooling-allocator", "incremental-cache", "wat", "gc", "component-model"] } -zerocopy = { version = "0.7" } +zerocopy = { version = "0.7", default-features = false, features = ["alloc", "derive", "simd-nightly"] } [target.'cfg(target_os = "hermit")'.dependencies] -hermit = { path = "../../hermit", default-features = false, features = ["smp", "acpi", "pci", "fsgsbase", "fs", "tcp", "dhcpv4", "mmap"] } +hermit = { path = "../../hermit", default-features = false, features = ["acpi", "pci", "fsgsbase", "fs", "tcp", "dhcpv4", "mmap"] } hermit-abi = { path = "../../hermit-abi", default-features = false } wasi = { version = "0.11" } \ No newline at end of file diff --git a/examples/wasmtime/src/preview1.rs b/examples/wasmtime/src/preview1.rs index 8e8679c65..3c64b46d4 100644 --- a/examples/wasmtime/src/preview1.rs +++ b/examples/wasmtime/src/preview1.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use std::sync::{Mutex, OnceLock}; use std::time::{Instant, SystemTime}; @@ -13,6 +14,7 @@ static FD: Mutex> = Mutex::new(Vec::new()); #[derive(Debug, Clone, PartialEq)] struct FileStream { pub raw_fd: i32, + pub path: String, } #[derive(Debug, Clone, PartialEq)] @@ -47,6 +49,34 @@ bitflags! { } } +/// The type of the file descriptor or file is unknown or is different from any of the other types specified. +const UNKNOWN: u64 = 0; +/// The file descriptor or file refers to a block device inode. +const BLOCK_DEVICE: u64 = 1 << 0; +/// The file descriptor or file refers to a directory inode. +const DIRECTORY: u64 = 1 << 1; +/// The file descriptor or file refers to a regular file inode. +const REGULAR_FILE: u64 = 1 << 2; +/// The file descriptor or file refers to a datagram socket. +const SOCKET_DGRAM: u64 = 1 << 3; +/// The file descriptor or file refers to a byte-stream socket. +const SOCKET_STREAM: u64 = 1 << 4; +/// The file refers to a symbolic link inode. +const SYMBOLIC_LINK: u64 = 1 << 5; + +#[derive(Debug, Copy, Clone, Default, AsBytes)] +#[repr(C)] +pub(crate) struct FileStat { + pub dev: u64, + pub ino: u64, + pub filetype: u64, + pub nlink: u64, + pub size: u64, + pub atim: u64, + pub mtim: u64, + pub ctim: u64, +} + fn cvt(err: i32) -> i32 { match err { hermit_abi::EINVAL => ERRNO_INVAL.raw() as i32, @@ -158,7 +188,7 @@ pub(crate) fn init(linker: &mut wasmtime::Linker) -> Result<()> { let mut guard = FD.lock().unwrap(); for (i, entry) in guard.iter_mut().enumerate() { if entry.is_none() { - *entry = Descriptor::File(FileStream { raw_fd }); + *entry = Descriptor::File(FileStream { raw_fd, path }); let _ = mem.write( caller.as_context_mut(), fd_ptr.try_into().unwrap(), @@ -168,7 +198,7 @@ pub(crate) fn init(linker: &mut wasmtime::Linker) -> Result<()> { return ERRNO_SUCCESS.raw() as i32; } } - guard.push(Descriptor::File(FileStream { raw_fd })); + guard.push(Descriptor::File(FileStream { raw_fd, path })); let new_fd: i32 = (guard.len() - 1).try_into().unwrap(); let _ = mem.write( @@ -291,6 +321,149 @@ pub(crate) fn init(linker: &mut wasmtime::Linker) -> Result<()> { ERRNO_SUCCESS.raw() as i32 }) .unwrap(); + linker + .func_wrap( + "wasi_snapshot_preview1", + "fd_filestat_get", + |mut caller: Caller<'_, _>, fd: i32, filestat_ptr: i32| { + let guard = FD.lock().unwrap(); + if fd >= guard.len().try_into().unwrap() { + return ERRNO_INVAL.raw() as i32; + } + + if let Descriptor::File(file) = &guard[fd as usize] { + let metadata = std::fs::metadata(file.path.clone()).unwrap(); + let filestat = FileStat { + filetype: REGULAR_FILE, + size: metadata.len(), + ..Default::default() + }; + + if let Some(Extern::Memory(mem)) = caller.get_export("memory") { + let _ = mem.write( + caller.as_context_mut(), + filestat_ptr.try_into().unwrap(), + filestat.as_bytes(), + ); + + return ERRNO_SUCCESS.raw() as i32; + } + } + + ERRNO_INVAL.raw() as i32 + }, + ) + .unwrap(); + linker + .func_wrap( + "wasi_snapshot_preview1", + "fd_read", + |mut caller: Caller<'_, _>, fd: i32, iovs_ptr: i32, iovs_len: i32, nread_ptr: i32| { + let fd = if fd <= 2 { + fd + } else { + let guard = FD.lock().unwrap(); + if fd >= guard.len().try_into().unwrap() { + return ERRNO_INVAL.raw() as i32; + } + + if let Descriptor::File(file) = &guard[fd as usize] { + file.raw_fd + } else { + return ERRNO_INVAL.raw() as i32; + } + }; + + if let Some(Extern::Memory(mem)) = caller.get_export("memory") { + let mut iovs = vec![0i32; (2 * iovs_len).try_into().unwrap()]; + let _ = mem.read( + caller.as_context(), + iovs_ptr.try_into().unwrap(), + iovs.as_bytes_mut(), + ); + + let mut nread_bytes: i32 = 0; + let mut i = 0; + while i < iovs.len() { + let len = iovs[i + 1]; + let mut data: Vec = Vec::with_capacity(len.try_into().unwrap()); + + let result = unsafe { + hermit_abi::read(fd, data.as_mut_ptr(), len.try_into().unwrap()) + }; + + if result > 0 { + unsafe { + data.set_len(result as usize); + } + let _ = mem.write( + caller.as_context_mut(), + iovs[i].try_into().unwrap(), + &data[..result as usize], + ); + + nread_bytes += result as i32; + if result < len.try_into().unwrap() { + break; + } + } else if result == 0 { + if result < len.try_into().unwrap() { + break; + } + } else { + return (-result).try_into().unwrap(); + } + + i += 2; + } + + let _ = mem.write( + caller.as_context_mut(), + nread_ptr.try_into().unwrap(), + nread_bytes.as_bytes(), + ); + + return ERRNO_SUCCESS.raw() as i32; + } + + ERRNO_INVAL.raw() as i32 + }, + ) + .unwrap(); + linker + .func_wrap( + "wasi_snapshot_preview1", + "fd_seek", + |mut caller: Caller<'_, _>, fd: i32, offset: i64, whence: i32, pos_ptr: i32| { + let fd = if fd <= 2 { + fd + } else { + let guard = FD.lock().unwrap(); + if fd >= guard.len().try_into().unwrap() { + return ERRNO_INVAL.raw() as i32; + } + + if let Descriptor::File(file) = &guard[fd as usize] { + file.raw_fd + } else { + return ERRNO_INVAL.raw() as i32; + } + }; + + let result = unsafe { hermit_abi::lseek(fd, offset.try_into().unwrap(), whence) }; + + if let Some(Extern::Memory(mem)) = caller.get_export("memory") { + let _ = mem.write( + caller.as_context_mut(), + pos_ptr.try_into().unwrap(), + result.as_bytes(), + ); + } + + ERRNO_SUCCESS.raw() as i32 + }, + ) + .unwrap(); linker .func_wrap( "wasi_snapshot_preview1", @@ -327,7 +500,10 @@ pub(crate) fn init(linker: &mut wasmtime::Linker) -> Result<()> { let mut i = 0; while i < iovs.len() { let len = iovs[i + 1]; - let mut data = vec![0u8; len.try_into().unwrap()]; + let mut data: Vec = Vec::with_capacity(len.try_into().unwrap()); + unsafe { + data.set_len(len as usize); + } let _ = mem.read( caller.as_context(), @@ -337,6 +513,7 @@ pub(crate) fn init(linker: &mut wasmtime::Linker) -> Result<()> { let result = unsafe { hermit_abi::write(fd, data.as_ptr(), len.try_into().unwrap()) }; + if result > 0 { nwritten_bytes += result as i32; if result < len.try_into().unwrap() {