From 09b056cc4070002d18d6a5133ac2eaaae80bf88d Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 13 Aug 2019 16:59:01 +0900 Subject: [PATCH 1/4] Add wip wasi::poll_oneoff work --- Cargo.lock | 1 - lib/wasi/src/state/mod.rs | 26 +++++++- lib/wasi/src/syscalls/mod.rs | 84 ++++++++++++++++++++++-- lib/wasi/src/syscalls/types.rs | 102 +++++++++++++++++++++++++++++- lib/wasi/src/syscalls/unix/mod.rs | 36 ++++++++++- 5 files changed, 239 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c81bdc3cb2..049ee21d612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1516,7 +1516,6 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.6.0", "wasmparser 0.35.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index dfcf5274109..56f02843a78 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -8,7 +8,7 @@ pub use self::types::*; use crate::syscalls::types::*; use generational_arena::Arena; pub use generational_arena::Index as Inode; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::{ borrow::Borrow, cell::Cell, @@ -115,6 +115,26 @@ pub struct Fd { pub inode: Inode, } +#[derive(Default)] +pub(crate) struct AsyncReadState { + pub(crate) closed: bool, + pub(crate) nbytes_read: __wasi_filesize_t, + pub(crate) buffer: VecDeque, +} + +impl AsyncReadState { + pub const MAX_SIZE: usize = 1024; +} + +impl std::fmt::Debug for AsyncReadState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsyncReadState") + .field("closed", &self.closed) + .field("nbytes_read", &self.nbytes_read) + .finish() + } +} + #[derive(Debug)] /// Warning, modifying these fields directly may cause invariants to break and /// should be considered unsafe. These fields may be made private in a future release @@ -132,6 +152,8 @@ pub struct WasiFs { pub stdout: Box, pub stderr: Box, pub stdin: Box, + + pub(crate) async_readers: HashMap, } impl WasiFs { @@ -153,6 +175,8 @@ impl WasiFs { stdin: Box::new(Stdin(io::stdin())), stdout: Box::new(Stdout(io::stdout())), stderr: Box::new(Stderr(io::stderr())), + + async_readers: HashMap::new(), }; // create virtual root let root_inode = { diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 8e403537a80..0005361aad4 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -9,15 +9,15 @@ use self::types::*; use crate::{ ptr::{Array, WasmPtr}, state::{ - self, host_file_type_to_wasi_file_type, Fd, HostFile, Inode, InodeVal, Kind, WasiFile, - WasiFsError, WasiState, MAX_SYMLINKS, + self, host_file_type_to_wasi_file_type, AsyncReadState, Fd, HostFile, Inode, InodeVal, + Kind, WasiFile, WasiFsError, WasiState, MAX_SYMLINKS, }, ExitCode, }; use rand::{thread_rng, Rng}; use std::borrow::Borrow; use std::cell::Cell; -use std::convert::Infallible; +use std::convert::{Infallible, TryInto}; use std::io::{self, Read, Seek, Write}; use wasmer_runtime_core::{debug, memory::Memory, vm::Ctx}; @@ -2242,7 +2242,83 @@ pub fn poll_oneoff( nevents: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::poll_oneoff"); - unimplemented!("wasi::poll_oneoff") + debug!(" => nsubscriptions = {}", nsubscriptions); + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + + let subscription_array = wasi_try!(in_.deref(memory, 0, nsubscriptions)); + let event_array = wasi_try!(out_.deref(memory, 0, nsubscriptions)); + let mut events_seen = 0; + let out_ptr = wasi_try!(nevents.deref(memory)); + + /*dbg!(&event_array + .iter() + .map(|e| e.get().tagged()) + .collect::>>());*/ + + for sub in subscription_array.iter() { + let s: WasiSubscription = wasi_try!(sub.get().try_into()); + match s.event_type { + EventType::Read(__wasi_subscription_fs_readwrite_t { fd }) => { + let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let inode = fd_entry.inode; + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) { + return __WASI_EACCES; + } + let mut async_reader_state = state.fs.async_readers.entry(fd).or_default(); + debug!("Calling fd_read directly"); + let size = async_reader_state.buffer.len(); + if size < AsyncReadState::MAX_SIZE { + async_reader_state.buffer.reserve_exact( + AsyncReadState::MAX_SIZE - async_reader_state.buffer.capacity(), + ); + match &mut state.fs.inodes[inode].kind { + Kind::File { handle, .. } => { + if let Some(h) = handle { + let (slice_1, slice_2) = async_reader_state.buffer.as_mut_slices(); + h.read(&mut slice_1[size..]); + h.read(&mut slice_2[(size - slice_1.len())..]); + } else { + return __WASI_EBADF; + } + } + Kind::Dir { .. } + | Kind::Root { .. } + | Kind::Buffer { .. } + | Kind::Symlink { .. } => { + unimplemented!("polling read on non-files not yet supported") + } + } + } + //fd_read(ctx, fd, ${3:iovs: WasmPtr<__wasi_iovec_t, Array>}, ${4:iovs_len: u32}, ${5:nread: WasmPtr}) + //read_bytes(${1:mut reader: T}, ${2:memory: &Memory}, ${3:iovs_arr_cell: &[Cell<__wasi_iovec_t>]}) + let event = __wasi_event_t { + userdata: s.user_data, + error: __WASI_ESUCCESS, + type_: s.event_type.raw_tag(), + u: unsafe { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: async_reader_state.nbytes_read, + flags: if async_reader_state.closed { + __WASI_EVENT_FD_READWRITE_HANGUP + } else { + 0 + }, + }, + } + }, + }; + event_array[events_seen].set(event); + events_seen += 1; + } + _ => unimplemented!("Clock or write eventtypes in wasi::poll_oneoff"), + } + //dbg!(s); + } + out_ptr.set(events_seen as u32); + __WASI_ESUCCESS + //unimplemented!("wasi::poll_oneoff") } pub fn proc_exit(ctx: &mut Ctx, code: __wasi_exitcode_t) -> Result { diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index 10c4d24c912..60bc4fddc00 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -157,10 +157,10 @@ pub struct __wasi_event_fd_readwrite_t { #[derive(Copy, Clone)] #[repr(C)] pub union __wasi_event_u { - fd_readwrite: __wasi_event_fd_readwrite_t, + pub fd_readwrite: __wasi_event_fd_readwrite_t, } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub enum EventEnum { FdReadWrite { nbytes: __wasi_filesize_t, @@ -199,6 +199,8 @@ impl __wasi_event_t { } } +unsafe impl ValueType for __wasi_event_t {} + pub type __wasi_eventrwflags_t = u16; pub const __WASI_EVENT_FD_READWRITE_HANGUP: u16 = 1 << 0; @@ -207,6 +209,15 @@ pub const __WASI_EVENTTYPE_CLOCK: u8 = 0; pub const __WASI_EVENTTYPE_FD_READ: u8 = 1; pub const __WASI_EVENTTYPE_FD_WRITE: u8 = 2; +pub fn eventtype_to_str(event_type: __wasi_eventtype_t) -> &'static str { + match event_type { + __WASI_EVENTTYPE_CLOCK => "__WASI_EVENTTYPE_CLOCK", + __WASI_EVENTTYPE_FD_READ => "__WASI_EVENTTYPE_FD_READ", + __WASI_EVENTTYPE_FD_WRITE => "__WASI_EVENTTYPE_FD_WRITE", + _ => "INVALID EVENTTYPE", + } +} + pub type __wasi_exitcode_t = u32; pub type __wasi_fd_t = u32; @@ -562,6 +573,93 @@ pub struct __wasi_subscription_t { pub u: __wasi_subscription_u, } +/// Safe Rust wrapper around `__wasi_subscription_t::type_` and `__wasi_subscription_t::u` +#[derive(Debug, Clone)] +pub enum EventType { + Clock(__wasi_subscription_clock_t), + Read(__wasi_subscription_fs_readwrite_t), + Write(__wasi_subscription_fs_readwrite_t), +} + +impl EventType { + pub fn raw_tag(&self) -> __wasi_eventtype_t { + match self { + EventType::Clock(_) => __WASI_EVENTTYPE_CLOCK, + EventType::Read(_) => __WASI_EVENTTYPE_FD_READ, + EventType::Write(_) => __WASI_EVENTTYPE_FD_WRITE, + } + } +} + +/// Safe Rust wrapper around `__wasi_subscription_t` +#[derive(Debug, Clone)] +pub struct WasiSubscription { + pub user_data: __wasi_userdata_t, + pub event_type: EventType, +} + +impl std::convert::TryFrom<__wasi_subscription_t> for WasiSubscription { + type Error = __wasi_errno_t; + + fn try_from(ws: __wasi_subscription_t) -> Result { + Ok(Self { + user_data: ws.userdata, + event_type: match ws.type_ { + __WASI_EVENTTYPE_CLOCK => EventType::Clock(unsafe { ws.u.clock }), + __WASI_EVENTTYPE_FD_READ => EventType::Read(unsafe { ws.u.fd_readwrite }), + __WASI_EVENTTYPE_FD_WRITE => EventType::Write(unsafe { ws.u.fd_readwrite }), + _ => return Err(__WASI_EINVAL), + }, + }) + } +} + +impl std::convert::TryFrom for __wasi_subscription_t { + type Error = __wasi_errno_t; + + fn try_from(ws: WasiSubscription) -> Result { + let (type_, u) = match ws.event_type { + EventType::Clock(c) => (__WASI_EVENTTYPE_CLOCK, __wasi_subscription_u { clock: c }), + EventType::Read(rw) => ( + __WASI_EVENTTYPE_FD_READ, + __wasi_subscription_u { fd_readwrite: rw }, + ), + EventType::Write(rw) => ( + __WASI_EVENTTYPE_FD_WRITE, + __wasi_subscription_u { fd_readwrite: rw }, + ), + _ => return Err(__WASI_EINVAL), + }; + + Ok(Self { + userdata: ws.user_data, + type_, + u, + }) + } +} + +impl std::fmt::Debug for __wasi_subscription_t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("__wasi_subscription_t") + .field("userdata", &self.userdata) + .field("type", &eventtype_to_str(self.type_)) + .field( + "u", + match self.type_ { + __WASI_EVENTTYPE_CLOCK => unsafe { &self.u.clock }, + __WASI_EVENTTYPE_FD_READ | __WASI_EVENTTYPE_FD_WRITE => unsafe { + &self.u.fd_readwrite + }, + _ => &"INVALID EVENTTYPE", + }, + ) + .finish() + } +} + +unsafe impl ValueType for __wasi_subscription_t {} + pub enum SubscriptionEnum { Clock(__wasi_subscription_clock_t), FdReadWrite(__wasi_subscription_fs_readwrite_t), diff --git a/lib/wasi/src/syscalls/unix/mod.rs b/lib/wasi/src/syscalls/unix/mod.rs index 9b1e224c795..6f8e26d03b9 100644 --- a/lib/wasi/src/syscalls/unix/mod.rs +++ b/lib/wasi/src/syscalls/unix/mod.rs @@ -1,7 +1,8 @@ +use crate::state::{Kind, WasiFile, WasiFs}; use crate::syscalls::types::*; use libc::{ - clock_getres, clock_gettime, timespec, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, - CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID, + c_int, clock_getres, clock_gettime, nfds_t, poll, timespec, CLOCK_MONOTONIC, + CLOCK_PROCESS_CPUTIME_ID, CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID, }; use std::cell::Cell; use std::mem; @@ -63,3 +64,34 @@ pub fn platform_clock_time_get( // TODO: map output of clock_gettime to __wasi_errno_t __WASI_ESUCCESS } + +pub fn poll_for_fds() -> Result<(), __wasi_errno_t> { + //let result = unsafe { poll( , libc::POLLIN, 0 as c_int) }; + unimplemented!() +} + +pub fn read_from_fd( + wasi_fs: &mut WasiFs, + fd: __wasi_fd_t, + buffer: &mut [u8], +) -> Result<(), __wasi_errno_t> { + let fd_entry = wasi_fs.get_fd(fd)?; + let inode = fd_entry.inode; + match &mut wasi_fs.inodes[inode].kind { + Kind::File { handle, .. } => { + if let Some(h) = handle { + + } else { + return Err(__WASI_EINVAL); + } + unimplemented!() + } + Kind::Dir { .. } | Kind::Root { .. } | Kind::Buffer { .. } | Kind::Symlink { .. } => { + return Err(__WASI_EINVAL) + } + } + let host_fd = unimplemented!(); + + let result = unsafe { libc::ioctl(host_fd, libc::FIONREAD, buffer) }; + unimplemented!() +} From 8c03338330026b1951db17f7a631c3605e281404 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 14 Aug 2019 15:51:39 +0900 Subject: [PATCH 2/4] Implement wasi::poll_oneoff more properly for Unix --- lib/wasi/src/state/mod.rs | 26 +-- lib/wasi/src/state/types.rs | 263 ++++++++++++++++++++++++++++++ lib/wasi/src/syscalls/mod.rs | 127 ++++++++++----- lib/wasi/src/syscalls/unix/mod.rs | 35 +--- 4 files changed, 350 insertions(+), 101 deletions(-) diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index 56f02843a78..dfcf5274109 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -8,7 +8,7 @@ pub use self::types::*; use crate::syscalls::types::*; use generational_arena::Arena; pub use generational_arena::Index as Inode; -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use std::{ borrow::Borrow, cell::Cell, @@ -115,26 +115,6 @@ pub struct Fd { pub inode: Inode, } -#[derive(Default)] -pub(crate) struct AsyncReadState { - pub(crate) closed: bool, - pub(crate) nbytes_read: __wasi_filesize_t, - pub(crate) buffer: VecDeque, -} - -impl AsyncReadState { - pub const MAX_SIZE: usize = 1024; -} - -impl std::fmt::Debug for AsyncReadState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AsyncReadState") - .field("closed", &self.closed) - .field("nbytes_read", &self.nbytes_read) - .finish() - } -} - #[derive(Debug)] /// Warning, modifying these fields directly may cause invariants to break and /// should be considered unsafe. These fields may be made private in a future release @@ -152,8 +132,6 @@ pub struct WasiFs { pub stdout: Box, pub stderr: Box, pub stdin: Box, - - pub(crate) async_readers: HashMap, } impl WasiFs { @@ -175,8 +153,6 @@ impl WasiFs { stdin: Box::new(Stdin(io::stdin())), stdout: Box::new(Stdout(io::stdout())), stderr: Box::new(Stderr(io::stderr())), - - async_readers: HashMap::new(), }; // create virtual root let root_inode = { diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 64f7d079392..e0289a7485c 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -165,6 +165,170 @@ pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { fn rename_file(&self, _new_name: &std::path::Path) -> Result<(), WasiFsError> { panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0 or 0.8.0. Please implement WasiFile::rename_file for your type before then"); } + + /// Returns the number of bytes available. This function must not block + fn bytes_available(&self) -> Result { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0 or 0.8.0. Please implement WasiFile::bytes_available for your type before then"); + } + + /// Used for polling. Default returns `None` because this method cannot be implemented for most types + /// Returns the underlying host fd + fn get_raw_fd(&self) -> Option { + None + } +} + +#[derive(Debug, Clone)] +pub enum PollEvent { + /// Data available to read + PollIn = 1, + /// Data available to write (will still block if data is greater than space available unless + /// the fd is configured to not block) + PollOut = 2, + /// Something didn't work. ignored as input + PollError = 4, + /// Connection closed. ignored as input + PollHangUp = 8, + /// Invalid request. ignored as input + PollInvalid = 16, +} + +impl PollEvent { + fn from_i16(raw_num: i16) -> Option { + Some(match raw_num { + 1 => PollEvent::PollIn, + 2 => PollEvent::PollOut, + 4 => PollEvent::PollError, + 8 => PollEvent::PollHangUp, + 16 => PollEvent::PollInvalid, + _ => return None, + }) + } +} + +#[derive(Debug, Clone)] +pub struct PollEventBuilder { + inner: PollEventSet, +} + +pub type PollEventSet = i16; + +#[derive(Debug)] +pub struct PollEventIter { + pes: PollEventSet, + i: usize, +} + +impl Iterator for PollEventIter { + type Item = PollEvent; + + fn next(&mut self) -> Option { + if self.pes == 0 || self.i > 15 { + None + } else { + while self.i < 16 { + let result = PollEvent::from_i16(self.pes & (1 << self.i)); + self.pes &= !(1 << self.i); + self.i += 1; + if let Some(r) = result { + return Some(r); + } + } + unreachable!("Internal logic error in PollEventIter"); + } + } +} + +pub fn iterate_poll_events(pes: PollEventSet) -> PollEventIter { + PollEventIter { pes, i: 0 } +} + +fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 { + let mut out = 0; + for i in 0..16 { + out |= match PollEvent::from_i16(pes & (1 << i)) { + Some(PollEvent::PollIn) => libc::POLLIN, + Some(PollEvent::PollOut) => libc::POLLOUT, + Some(PollEvent::PollError) => libc::POLLERR, + Some(PollEvent::PollHangUp) => libc::POLLHUP, + Some(PollEvent::PollInvalid) => libc::POLLNVAL, + _ => 0, + }; + pes &= !(1 << i); + } + out +} + +fn platform_poll_events_to_pollevent_set(mut num: i16) -> PollEventSet { + let mut peb = PollEventBuilder::new(); + for i in 0..16 { + peb = match num & (1 << i) { + libc::POLLIN => peb.add(PollEvent::PollIn), + libc::POLLOUT => peb.add(PollEvent::PollOut), + libc::POLLERR => peb.add(PollEvent::PollError), + libc::POLLHUP => peb.add(PollEvent::PollHangUp), + libc::POLLNVAL => peb.add(PollEvent::PollInvalid), + _ => peb, + }; + num &= !(1 << i); + } + peb.build() +} + +impl PollEventBuilder { + pub fn new() -> PollEventBuilder { + PollEventBuilder { inner: 0 } + } + + pub fn add(mut self, event: PollEvent) -> PollEventBuilder { + self.inner |= event as PollEventSet; + self + } + + pub fn build(self) -> PollEventSet { + self.inner + } +} + +#[cfg(unix)] +pub(crate) fn poll( + selfs: &[&dyn WasiFile], + events: &[PollEventSet], + seen_events: &mut [PollEventSet], +) -> Result<(), WasiFsError> { + if !(selfs.len() == events.len() && events.len() == seen_events.len()) { + return Err(WasiFsError::InvalidInput); + } + let mut fds = selfs + .iter() + .enumerate() + .filter_map(|(i, s)| s.get_raw_fd().map(|rfd| (i, rfd))) + .map(|(i, host_fd)| libc::pollfd { + fd: host_fd, + events: poll_event_set_to_platform_poll_events(events[i]), + revents: 0, + }) + .collect::>(); + let result = unsafe { libc::poll(fds.as_mut_ptr(), selfs.len() as _, 1) }; + + if result != 0 { + // TODO: check errno and return value + return Err(WasiFsError::IOError); + } + // convert result and write back values + for (i, fd) in fds.into_iter().enumerate() { + seen_events[i] = platform_poll_events_to_pollevent_set(fd.revents); + } + Ok(()) +} + +#[cfg(not(unix))] +pub(crate) fn poll( + selfs: &[&dyn WasiFile], + events: &[PollEventSet], + seen_events: &mut [PollEventSet], +) -> Result<(), WasiFsError> { + unimplemented!("HostFile::poll in WasiFile is not implemented for non-Unix-like targets yet"); } pub trait WasiPath {} @@ -282,6 +446,41 @@ impl WasiFile for HostFile { fn rename_file(&self, new_name: &std::path::Path) -> Result<(), WasiFsError> { std::fs::rename(&self.host_path, new_name).map_err(Into::into) } + + #[cfg(unix)] + fn bytes_available(&self) -> Result { + use std::convert::TryInto; + use std::os::unix::io::AsRawFd; + let host_fd = self.inner.as_raw_fd(); + + let mut bytes_found = 0 as libc::c_int; + let result = unsafe { libc::ioctl(host_fd, libc::FIONREAD, &mut bytes_found) }; + + match result { + // success + 0 => Ok(bytes_found.try_into().unwrap_or(0)), + libc::EBADF => Err(WasiFsError::InvalidFd), + libc::EFAULT => Err(WasiFsError::InvalidData), + libc::EINVAL => Err(WasiFsError::InvalidInput), + _ => Err(WasiFsError::IOError), + } + } + #[cfg(not(unix))] + fn bytes_available(&self) -> Result { + unimplemented!("HostFile::bytes_available in WasiFile is not implemented for non-Unix-like targets yet"); + } + + #[cfg(unix)] + fn get_raw_fd(&self) -> Option { + use std::os::unix::io::AsRawFd; + Some(self.inner.as_raw_fd()) + } + #[cfg(not(unix))] + fn get_raw_fd(&self) -> Option { + unimplemented!( + "HostFile::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" + ); + } } impl From for WasiFsError { @@ -375,6 +574,19 @@ impl WasiFile for Stdout { fn size(&self) -> u64 { 0 } + + #[cfg(unix)] + fn get_raw_fd(&self) -> Option { + use std::os::unix::io::AsRawFd; + Some(self.0.as_raw_fd()) + } + + #[cfg(not(unix))] + fn get_raw_fd(&self) -> Option { + unimplemented!( + "Stdout::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" + ); + } } #[derive(Debug)] @@ -441,6 +653,19 @@ impl WasiFile for Stderr { fn size(&self) -> u64 { 0 } + + #[cfg(unix)] + fn get_raw_fd(&self) -> Option { + use std::os::unix::io::AsRawFd; + Some(self.0.as_raw_fd()) + } + + #[cfg(not(unix))] + fn get_raw_fd(&self) -> Option { + unimplemented!( + "Stderr::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" + ); + } } #[derive(Debug)] @@ -507,6 +732,44 @@ impl WasiFile for Stdin { fn size(&self) -> u64 { 0 } + + #[cfg(unix)] + fn bytes_available(&self) -> Result { + use std::convert::TryInto; + use std::os::unix::io::AsRawFd; + let host_fd = self.0.as_raw_fd(); + + let mut bytes_found = 0 as libc::c_int; + let result = unsafe { libc::ioctl(host_fd, libc::FIONREAD, &mut bytes_found) }; + + match result { + // success + 0 => Ok(bytes_found.try_into().unwrap_or(0)), + libc::EBADF => Err(WasiFsError::InvalidFd), + libc::EFAULT => Err(WasiFsError::InvalidData), + libc::EINVAL => Err(WasiFsError::InvalidInput), + _ => Err(WasiFsError::IOError), + } + } + #[cfg(not(unix))] + fn bytes_available(&self) -> Result { + unimplemented!( + "Stdin::bytes_available in WasiFile is not implemented for non-Unix-like targets yet" + ); + } + + #[cfg(unix)] + fn get_raw_fd(&self) -> Option { + use std::os::unix::io::AsRawFd; + Some(self.0.as_raw_fd()) + } + + #[cfg(not(unix))] + fn get_raw_fd(&self) -> Option { + unimplemented!( + "Stdin::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" + ); + } } /* diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 0005361aad4..e31cd21959b 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -9,8 +9,9 @@ use self::types::*; use crate::{ ptr::{Array, WasmPtr}, state::{ - self, host_file_type_to_wasi_file_type, AsyncReadState, Fd, HostFile, Inode, InodeVal, - Kind, WasiFile, WasiFsError, WasiState, MAX_SYMLINKS, + self, host_file_type_to_wasi_file_type, iterate_poll_events, poll, Fd, HostFile, Inode, + InodeVal, Kind, PollEvent, PollEventBuilder, WasiFile, WasiFsError, WasiState, + MAX_SYMLINKS, }, ExitCode, }; @@ -2251,33 +2252,49 @@ pub fn poll_oneoff( let mut events_seen = 0; let out_ptr = wasi_try!(nevents.deref(memory)); - /*dbg!(&event_array - .iter() - .map(|e| e.get().tagged()) - .collect::>>());*/ + let mut fds = vec![]; + let mut in_events = vec![]; for sub in subscription_array.iter() { let s: WasiSubscription = wasi_try!(sub.get().try_into()); - match s.event_type { + let mut peb = PollEventBuilder::new(); + + let fd = match s.event_type { EventType::Read(__wasi_subscription_fs_readwrite_t { fd }) => { let fd_entry = wasi_try!(state.fs.get_fd(fd)); - let inode = fd_entry.inode; if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) { return __WASI_EACCES; } - let mut async_reader_state = state.fs.async_readers.entry(fd).or_default(); - debug!("Calling fd_read directly"); - let size = async_reader_state.buffer.len(); - if size < AsyncReadState::MAX_SIZE { - async_reader_state.buffer.reserve_exact( - AsyncReadState::MAX_SIZE - async_reader_state.buffer.capacity(), - ); - match &mut state.fs.inodes[inode].kind { + in_events.push(peb.add(PollEvent::PollIn).build()); + Some(fd) + } + EventType::Write(__wasi_subscription_fs_readwrite_t { fd }) => { + let fd_entry = wasi_try!(state.fs.get_fd(fd)); + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { + return __WASI_EACCES; + } + in_events.push(peb.add(PollEvent::PollOut).build()); + Some(fd) + } + _ => unimplemented!("Clock eventtypes in wasi::poll_oneoff"), + }; + + if let Some(fd) = fd { + let wasi_file_ref: &dyn WasiFile = match fd { + __WASI_STDERR_FILENO => state.fs.stderr.as_ref(), + __WASI_STDIN_FILENO => state.fs.stdin.as_ref(), + __WASI_STDOUT_FILENO => state.fs.stdout.as_ref(), + _ => { + let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let inode = fd_entry.inode; + if !has_rights(fd_entry.rights, __WASI_RIGHT_POLL_FD_READWRITE) { + return __WASI_EACCES; + } + + match &state.fs.inodes[inode].kind { Kind::File { handle, .. } => { if let Some(h) = handle { - let (slice_1, slice_2) = async_reader_state.buffer.as_mut_slices(); - h.read(&mut slice_1[size..]); - h.read(&mut slice_2[(size - slice_1.len())..]); + h.as_ref() } else { return __WASI_EBADF; } @@ -2290,35 +2307,59 @@ pub fn poll_oneoff( } } } - //fd_read(ctx, fd, ${3:iovs: WasmPtr<__wasi_iovec_t, Array>}, ${4:iovs_len: u32}, ${5:nread: WasmPtr}) - //read_bytes(${1:mut reader: T}, ${2:memory: &Memory}, ${3:iovs_arr_cell: &[Cell<__wasi_iovec_t>]}) - let event = __wasi_event_t { - userdata: s.user_data, - error: __WASI_ESUCCESS, - type_: s.event_type.raw_tag(), - u: unsafe { - __wasi_event_u { - fd_readwrite: __wasi_event_fd_readwrite_t { - nbytes: async_reader_state.nbytes_read, - flags: if async_reader_state.closed { - __WASI_EVENT_FD_READWRITE_HANGUP - } else { - 0 - }, - }, - } - }, - }; - event_array[events_seen].set(event); - events_seen += 1; + }; + fds.push(wasi_file_ref); + } else { + unimplemented!("Clock events are not yet implemented!"); + } + let mut seen_events = vec![Default::default(); in_events.len()]; + wasi_try!(poll( + fds.as_slice(), + in_events.as_slice(), + seen_events.as_mut_slice() + ) + .map_err(|e| e.into_wasi_err())); + + for (i, seen_event) in seen_events.into_iter().enumerate() { + let mut flags = 0; + let mut error = __WASI_EAGAIN; + let mut bytes_available = 0; + let event_iter = iterate_poll_events(seen_event); + for event in event_iter { + match event { + PollEvent::PollError => error = __WASI_EIO, + PollEvent::PollHangUp => flags = __WASI_EVENT_FD_READWRITE_HANGUP, + PollEvent::PollInvalid => error = __WASI_EINVAL, + PollEvent::PollIn => { + bytes_available = + wasi_try!(fds[i].bytes_available().map_err(|e| e.into_wasi_err())); + error = __WASI_ESUCCESS; + } + PollEvent::PollOut => { + error = __WASI_ESUCCESS; + unimplemented!("Write in wasi::poll_oneoff not yet supported!") + } + } } - _ => unimplemented!("Clock or write eventtypes in wasi::poll_oneoff"), + let event = __wasi_event_t { + userdata: subscription_array[i].get().userdata, + error, + type_: subscription_array[i].get().type_, + u: unsafe { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: bytes_available as u64, + flags, + }, + } + }, + }; + event_array[events_seen].set(event); + events_seen += 1; } - //dbg!(s); } out_ptr.set(events_seen as u32); __WASI_ESUCCESS - //unimplemented!("wasi::poll_oneoff") } pub fn proc_exit(ctx: &mut Ctx, code: __wasi_exitcode_t) -> Result { diff --git a/lib/wasi/src/syscalls/unix/mod.rs b/lib/wasi/src/syscalls/unix/mod.rs index 6f8e26d03b9..2d95d0826f9 100644 --- a/lib/wasi/src/syscalls/unix/mod.rs +++ b/lib/wasi/src/syscalls/unix/mod.rs @@ -1,8 +1,8 @@ use crate::state::{Kind, WasiFile, WasiFs}; use crate::syscalls::types::*; use libc::{ - c_int, clock_getres, clock_gettime, nfds_t, poll, timespec, CLOCK_MONOTONIC, - CLOCK_PROCESS_CPUTIME_ID, CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID, + clock_getres, clock_gettime, timespec, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, + CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID, }; use std::cell::Cell; use std::mem; @@ -64,34 +64,3 @@ pub fn platform_clock_time_get( // TODO: map output of clock_gettime to __wasi_errno_t __WASI_ESUCCESS } - -pub fn poll_for_fds() -> Result<(), __wasi_errno_t> { - //let result = unsafe { poll( , libc::POLLIN, 0 as c_int) }; - unimplemented!() -} - -pub fn read_from_fd( - wasi_fs: &mut WasiFs, - fd: __wasi_fd_t, - buffer: &mut [u8], -) -> Result<(), __wasi_errno_t> { - let fd_entry = wasi_fs.get_fd(fd)?; - let inode = fd_entry.inode; - match &mut wasi_fs.inodes[inode].kind { - Kind::File { handle, .. } => { - if let Some(h) = handle { - - } else { - return Err(__WASI_EINVAL); - } - unimplemented!() - } - Kind::Dir { .. } | Kind::Root { .. } | Kind::Buffer { .. } | Kind::Symlink { .. } => { - return Err(__WASI_EINVAL) - } - } - let host_fd = unimplemented!(); - - let result = unsafe { libc::ioctl(host_fd, libc::FIONREAD, buffer) }; - unimplemented!() -} From ec20e325fbc258cb61cd04f4a93504a115efd5c9 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 14 Aug 2019 16:22:32 +0900 Subject: [PATCH 3/4] Feature gate Unix-specific polling code --- lib/wasi/src/state/types.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index e0289a7485c..ca20303b4d6 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -243,6 +243,7 @@ pub fn iterate_poll_events(pes: PollEventSet) -> PollEventIter { PollEventIter { pes, i: 0 } } +#[cfg(unix)] fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 { let mut out = 0; for i in 0..16 { @@ -259,6 +260,7 @@ fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 { out } +#[cfg(unix)] fn platform_poll_events_to_pollevent_set(mut num: i16) -> PollEventSet { let mut peb = PollEventBuilder::new(); for i in 0..16 { @@ -324,9 +326,9 @@ pub(crate) fn poll( #[cfg(not(unix))] pub(crate) fn poll( - selfs: &[&dyn WasiFile], - events: &[PollEventSet], - seen_events: &mut [PollEventSet], + _selfs: &[&dyn WasiFile], + _events: &[PollEventSet], + _seen_events: &mut [PollEventSet], ) -> Result<(), WasiFsError> { unimplemented!("HostFile::poll in WasiFile is not implemented for non-Unix-like targets yet"); } From d733989657dc3bf2c06bddb53a6880b3bc68b12f Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 15 Aug 2019 13:13:20 +0900 Subject: [PATCH 4/4] Add wasi::poll_oneoff file read test --- lib/wasi-tests/tests/wasitests/mod.rs | 1 + lib/wasi-tests/tests/wasitests/poll_oneoff.rs | 14 ++ lib/wasi-tests/wasitests/poll_oneoff.out | 1 + lib/wasi-tests/wasitests/poll_oneoff.rs | 150 ++++++++++++++++++ lib/wasi-tests/wasitests/poll_oneoff.wasm | Bin 0 -> 82430 bytes lib/wasi/src/state/types.rs | 11 +- lib/wasi/src/syscalls/unix/mod.rs | 1 - 7 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 lib/wasi-tests/tests/wasitests/poll_oneoff.rs create mode 100644 lib/wasi-tests/wasitests/poll_oneoff.out create mode 100644 lib/wasi-tests/wasitests/poll_oneoff.rs create mode 100755 lib/wasi-tests/wasitests/poll_oneoff.wasm diff --git a/lib/wasi-tests/tests/wasitests/mod.rs b/lib/wasi-tests/tests/wasitests/mod.rs index 954d80bb4c3..a1b1d378174 100644 --- a/lib/wasi-tests/tests/wasitests/mod.rs +++ b/lib/wasi-tests/tests/wasitests/mod.rs @@ -18,6 +18,7 @@ mod mapdir; mod path_link; mod path_rename; mod path_symlink; +mod poll_oneoff; mod quine; mod readlink; mod wasi_sees_virtual_root; diff --git a/lib/wasi-tests/tests/wasitests/poll_oneoff.rs b/lib/wasi-tests/tests/wasitests/poll_oneoff.rs new file mode 100644 index 00000000000..cbde23d0d1c --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/poll_oneoff.rs @@ -0,0 +1,14 @@ +#[test] +fn test_poll_oneoff() { + assert_wasi_output!( + "../../wasitests/poll_oneoff.wasm", + "poll_oneoff", + vec![], + vec![( + "hamlet".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/poll_oneoff.out" + ); +} diff --git a/lib/wasi-tests/wasitests/poll_oneoff.out b/lib/wasi-tests/wasitests/poll_oneoff.out new file mode 100644 index 00000000000..53efa812a57 --- /dev/null +++ b/lib/wasi-tests/wasitests/poll_oneoff.out @@ -0,0 +1 @@ +__wasi_event_t { userdata: 1193046, error: 0, type_: 1, u: __wasi_event_u { __wasi_event_fd_readwrite_t { nbytes: 2259, flags: 0 } } } diff --git a/lib/wasi-tests/wasitests/poll_oneoff.rs b/lib/wasi-tests/wasitests/poll_oneoff.rs new file mode 100644 index 00000000000..60f31ddfb85 --- /dev/null +++ b/lib/wasi-tests/wasitests/poll_oneoff.rs @@ -0,0 +1,150 @@ +// Args: +// mapdir: hamlet:wasitests/test_fs/hamlet + +use std::fs; +use std::io::{Read, Seek, SeekFrom}; +#[cfg(target_os = "wasi")] +use std::os::wasi::prelude::AsRawFd; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn poll_oneoff(subscriptons: u32, events: u32, nsubscriptons: u32, nevents: u32) -> u16; +} + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct __wasi_event_t { + pub userdata: u64, + pub error: u16, + pub type_: __wasi_eventtype_t, + pub u: __wasi_event_u, +} + +impl Default for __wasi_event_t { + fn default() -> Self { + __wasi_event_t { + userdata: 0, + error: 0, + type_: 0, + u: __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: 0, + flags: 0, + }, + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct __wasi_event_fd_readwrite_t { + pub nbytes: u64, + pub flags: u16, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union __wasi_event_u { + pub fd_readwrite: __wasi_event_fd_readwrite_t, +} + +impl std::fmt::Debug for __wasi_event_u { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "__wasi_event_u {{ ")?; + write!(f, "{:?} }}", unsafe { self.fd_readwrite }) + } +} + +pub type __wasi_eventtype_t = u8; +pub const __WASI_EVENTTYPE_CLOCK: u8 = 0; +pub const __WASI_EVENTTYPE_FD_READ: u8 = 1; +pub const __WASI_EVENTTYPE_FD_WRITE: u8 = 2; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct __wasi_subscription_clock_t { + pub userdata: u64, + pub clock_id: u32, + pub timeout: u64, + pub precision: u64, + pub flags: u16, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct __wasi_subscription_fs_readwrite_t { + pub fd: u32, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union __wasi_subscription_u { + clock: __wasi_subscription_clock_t, + fd_readwrite: __wasi_subscription_fs_readwrite_t, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __wasi_subscription_t { + pub userdata: u64, + pub type_: __wasi_eventtype_t, + pub u: __wasi_subscription_u, +} + +#[cfg(target_os = "wasi")] +fn poll_read(fds: &[u32]) -> Result, u16> { + let mut in_ = fds + .iter() + .map(|n| __wasi_subscription_t { + userdata: 0x123456, + type_: __WASI_EVENTTYPE_FD_READ, + u: __wasi_subscription_u { + fd_readwrite: __wasi_subscription_fs_readwrite_t { fd: *n as u32 }, + }, + }) + .collect::>(); + let mut out_ = vec![Default::default(); in_.len()]; + let mut nsubscriptions: u32 = 0; + let result = unsafe { + poll_oneoff( + in_.as_ptr() as usize as u32, + out_.as_mut_ptr() as usize as u32, + in_.len() as u32, + (&mut nsubscriptions as *mut u32) as usize as u32, + ) + }; + + if result == 0 { + Ok(out_) + } else { + Err(result) + } +} + +fn main() { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("/"); + + let path = base.join("hamlet/act4/scene1.txt"); + let mut file = fs::File::open(&path).expect("Could not open file"); + let mut buffer = [0u8; 64]; + + #[cfg(target_os = "wasi")] + { + let fds = vec![file.as_raw_fd()]; + let mut result = poll_read(fds.as_slice()).expect("First poll"); + while result[0].error != 0 { + result = poll_read(fds.as_slice()).expect("subsequent polls"); + } + println!("{:?}", result[0]); + } + #[cfg(not(target_os = "wasi"))] + { + println!("{}", "__wasi_event_t { userdata: 1193046, error: 0, type_: 1, u: __wasi_event_u { __wasi_event_fd_readwrite_t { nbytes: 2259, flags: 0 } } }"); + } +} diff --git a/lib/wasi-tests/wasitests/poll_oneoff.wasm b/lib/wasi-tests/wasitests/poll_oneoff.wasm new file mode 100755 index 0000000000000000000000000000000000000000..a07b17c215f342c1a58c612918dee46099535285 GIT binary patch literal 82430 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 literal 0 HcmV?d00001 diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index ca20303b4d6..fda6b1c74b5 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -1,5 +1,7 @@ /// types for use in the WASI filesystem use crate::syscalls::types::*; +#[cfg(unix)] +use std::convert::TryInto; use std::{ fs, io::{self, Read, Seek, Write}, @@ -297,7 +299,7 @@ pub(crate) fn poll( selfs: &[&dyn WasiFile], events: &[PollEventSet], seen_events: &mut [PollEventSet], -) -> Result<(), WasiFsError> { +) -> Result { if !(selfs.len() == events.len() && events.len() == seen_events.len()) { return Err(WasiFsError::InvalidInput); } @@ -313,7 +315,7 @@ pub(crate) fn poll( .collect::>(); let result = unsafe { libc::poll(fds.as_mut_ptr(), selfs.len() as _, 1) }; - if result != 0 { + if result < 0 { // TODO: check errno and return value return Err(WasiFsError::IOError); } @@ -321,7 +323,8 @@ pub(crate) fn poll( for (i, fd) in fds.into_iter().enumerate() { seen_events[i] = platform_poll_events_to_pollevent_set(fd.revents); } - Ok(()) + // unwrap is safe because we check the error above + Ok(result.try_into().unwrap()) } #[cfg(not(unix))] @@ -451,7 +454,6 @@ impl WasiFile for HostFile { #[cfg(unix)] fn bytes_available(&self) -> Result { - use std::convert::TryInto; use std::os::unix::io::AsRawFd; let host_fd = self.inner.as_raw_fd(); @@ -737,7 +739,6 @@ impl WasiFile for Stdin { #[cfg(unix)] fn bytes_available(&self) -> Result { - use std::convert::TryInto; use std::os::unix::io::AsRawFd; let host_fd = self.0.as_raw_fd(); diff --git a/lib/wasi/src/syscalls/unix/mod.rs b/lib/wasi/src/syscalls/unix/mod.rs index 2d95d0826f9..9b1e224c795 100644 --- a/lib/wasi/src/syscalls/unix/mod.rs +++ b/lib/wasi/src/syscalls/unix/mod.rs @@ -1,4 +1,3 @@ -use crate::state::{Kind, WasiFile, WasiFs}; use crate::syscalls::types::*; use libc::{ clock_getres, clock_gettime, timespec, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID,