From 14ba585edff4950e71de085737b7aadb7de577d3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 17 May 2019 23:29:19 +0200 Subject: [PATCH] Reorganise hostcalls into submodules --- src/{hostcalls.rs => hostcalls/fs.rs} | 811 +------------------------- src/hostcalls/fs_helpers.rs | 282 +++++++++ src/hostcalls/misc.rs | 470 +++++++++++++++ src/hostcalls/mod.rs | 10 + src/hostcalls/sock.rs | 44 ++ 5 files changed, 814 insertions(+), 803 deletions(-) rename src/{hostcalls.rs => hostcalls/fs.rs} (59%) create mode 100644 src/hostcalls/fs_helpers.rs create mode 100644 src/hostcalls/misc.rs create mode 100644 src/hostcalls/mod.rs create mode 100644 src/hostcalls/sock.rs diff --git a/src/hostcalls.rs b/src/hostcalls/fs.rs similarity index 59% rename from src/hostcalls.rs rename to src/hostcalls/fs.rs index d35b8a2f4c..e041b54835 100644 --- a/src/hostcalls.rs +++ b/src/hostcalls/fs.rs @@ -1,224 +1,18 @@ -//! Hostcalls that implement -//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). -//! -//! This code borrows heavily from [wasmtime-wasi](https://github.com/CraneStation/wasmtime-wasi), -//! which in turn borrows from cloudabi-utils. See `LICENSE.wasmtime-wasi` for license information. -//! -//! This is currently a very incomplete prototype, only supporting the hostcalls required to run -//! `/examples/hello.c`, and using a bare-bones translation of the capabilities system rather than -//! something nice. - #![allow(non_camel_case_types)] #![allow(unused_unsafe)] -#![allow(unused)] + use crate::ctx::WasiCtx; use crate::fdentry::{determine_type_rights, FdEntry}; use crate::memory::*; use crate::{host, wasm32}; -use cast::From as _0; +use super::fs_helpers::*; -use nix::convert_ioctl_res; -use nix::libc::{self, c_int, c_long, c_void, off_t}; -use std::ffi::{OsStr, OsString}; -use std::os::unix::prelude::{FromRawFd, OsStrExt, OsStringExt, RawFd}; -use std::time::SystemTime; -use std::{cmp, slice}; +use nix::libc::{self, c_long, c_void, off_t}; +use std::ffi::OsStr; +use std::os::unix::prelude::{FromRawFd, OsStrExt}; use wasi_common_cbindgen::wasi_common_cbindgen; -#[wasi_common_cbindgen] -pub fn args_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - argv_ptr: wasm32::uintptr_t, - argv_buf: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut argv_buf_offset = 0; - let mut argv = vec![]; - - for arg in wasi_ctx.args.iter() { - let arg_bytes = arg.as_bytes_with_nul(); - let arg_ptr = argv_buf + argv_buf_offset; - - if let Err(e) = enc_slice_of(memory, arg_bytes, arg_ptr) { - return enc_errno(e); - } - - argv.push(arg_ptr); - - argv_buf_offset = if let Some(new_offset) = argv_buf_offset.checked_add( - wasm32::uintptr_t::cast(arg_bytes.len()) - .expect("cast overflow would have been caught by `enc_slice_of` above"), - ) { - new_offset - } else { - return wasm32::__WASI_EOVERFLOW; - } - } - - enc_slice_of(memory, argv.as_slice(), argv_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) -} - -#[wasi_common_cbindgen] -pub fn args_sizes_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - argc_ptr: wasm32::uintptr_t, - argv_buf_size_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let argc = wasi_ctx.args.len(); - let argv_size = wasi_ctx - .args - .iter() - .map(|arg| arg.as_bytes_with_nul().len()) - .sum(); - - if let Err(e) = enc_usize_byref(memory, argc_ptr, argc) { - return enc_errno(e); - } - if let Err(e) = enc_usize_byref(memory, argv_buf_size_ptr, argv_size) { - return enc_errno(e); - } - wasm32::__WASI_ESUCCESS -} - -#[wasi_common_cbindgen] -pub fn clock_res_get( - memory: &mut [u8], - clock_id: wasm32::__wasi_clockid_t, - resolution_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - // convert the supported clocks to the libc types, or return EINVAL - let clock_id = match dec_clockid(clock_id) { - host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, - host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, - host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, - host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, - _ => return wasm32::__WASI_EINVAL, - }; - - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = unsafe { std::mem::uninitialized::() }; - let res = unsafe { libc::clock_getres(clock_id, &mut timespec as *mut libc::timespec) }; - if res != 0 { - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } - - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as host::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) - .map_or(wasm32::__WASI_EOVERFLOW, |resolution| { - // a supported clock can never return zero; this case will probably never get hit, but - // make sure we follow the spec - if resolution == 0 { - wasm32::__WASI_EINVAL - } else { - enc_timestamp_byref(memory, resolution_ptr, resolution) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } - }) -} - -#[wasi_common_cbindgen] -pub fn clock_time_get( - memory: &mut [u8], - clock_id: wasm32::__wasi_clockid_t, - // ignored for now, but will be useful once we put optional limits on precision to reduce side - // channels - _precision: wasm32::__wasi_timestamp_t, - time_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - // convert the supported clocks to the libc types, or return EINVAL - let clock_id = match dec_clockid(clock_id) { - host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, - host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, - host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, - host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, - _ => return wasm32::__WASI_EINVAL, - }; - - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = unsafe { std::mem::uninitialized::() }; - let res = unsafe { libc::clock_gettime(clock_id, &mut timespec as *mut libc::timespec) }; - if res != 0 { - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } - - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as host::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) - .map_or(wasm32::__WASI_EOVERFLOW, |time| { - enc_timestamp_byref(memory, time_ptr, time) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - }) -} - -#[wasi_common_cbindgen] -pub fn environ_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - environ_ptr: wasm32::uintptr_t, - environ_buf: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut environ_buf_offset = 0; - let mut environ = vec![]; - - for pair in wasi_ctx.env.iter() { - let env_bytes = pair.as_bytes_with_nul(); - let env_ptr = environ_buf + environ_buf_offset; - - if let Err(e) = enc_slice_of(memory, env_bytes, env_ptr) { - return enc_errno(e); - } - - environ.push(env_ptr); - - environ_buf_offset = if let Some(new_offset) = environ_buf_offset.checked_add( - wasm32::uintptr_t::cast(env_bytes.len()) - .expect("cast overflow would have been caught by `enc_slice_of` above"), - ) { - new_offset - } else { - return wasm32::__WASI_EOVERFLOW; - } - } - - enc_slice_of(memory, environ.as_slice(), environ_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) -} - -#[wasi_common_cbindgen] -pub fn environ_sizes_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - environ_count_ptr: wasm32::uintptr_t, - environ_size_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let environ_count = wasi_ctx.env.len(); - if let Some(environ_size) = wasi_ctx.env.iter().try_fold(0, |acc: u32, pair| { - acc.checked_add(pair.as_bytes_with_nul().len() as u32) - }) { - if let Err(e) = enc_usize_byref(memory, environ_count_ptr, environ_count) { - return enc_errno(e); - } - if let Err(e) = enc_usize_byref(memory, environ_size_ptr, environ_size as usize) { - return enc_errno(e); - } - wasm32::__WASI_ESUCCESS - } else { - wasm32::__WASI_EOVERFLOW - } -} - #[wasi_common_cbindgen] pub fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasm32::__wasi_fd_t) -> wasm32::__wasi_errno_t { let fd = dec_fd(fd); @@ -240,11 +34,7 @@ pub fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasm32::__wasi_fd_t) -> wasm32::__wa } #[wasi_common_cbindgen] -pub fn fd_datasync( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasm32::__wasi_fd_t, -) -> wasm32::__wasi_errno_t { +pub fn fd_datasync(wasi_ctx: &WasiCtx, fd: wasm32::__wasi_fd_t) -> wasm32::__wasi_errno_t { let host_fd = dec_fd(fd); let rights = host::__WASI_RIGHT_FD_DATASYNC; let fe = match wasi_ctx.get_fd_entry(host_fd, rights.into(), 0) { @@ -407,7 +197,6 @@ pub fn fd_read( #[wasi_common_cbindgen] pub fn fd_renumber( wasi_ctx: &mut WasiCtx, - memory: &mut [u8], from: wasm32::__wasi_fd_t, to: wasm32::__wasi_fd_t, ) -> wasm32::__wasi_errno_t { @@ -556,7 +345,6 @@ pub fn fd_fdstat_set_flags( #[wasi_common_cbindgen] pub fn fd_fdstat_set_rights( wasi_ctx: &WasiCtx, - memory: &mut [u8], fd: wasm32::__wasi_fd_t, fs_rights_base: wasm32::__wasi_rights_t, fs_rights_inheriting: wasm32::__wasi_rights_t, @@ -575,11 +363,7 @@ pub fn fd_fdstat_set_rights( } #[wasi_common_cbindgen] -pub fn fd_sync( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasm32::__wasi_fd_t, -) -> wasm32::__wasi_errno_t { +pub fn fd_sync(wasi_ctx: &WasiCtx, fd: wasm32::__wasi_fd_t) -> wasm32::__wasi_errno_t { let host_fd = dec_fd(fd); let rights = host::__WASI_RIGHT_FD_SYNC; let fe = match wasi_ctx.get_fd_entry(host_fd, rights.into(), 0) { @@ -633,7 +417,6 @@ pub fn fd_write( #[wasi_common_cbindgen] pub fn fd_advise( wasi_ctx: &WasiCtx, - memory: &mut [u8], fd: wasm32::__wasi_fd_t, offset: wasm32::__wasi_filesize_t, len: wasm32::__wasi_filesize_t, @@ -693,7 +476,6 @@ pub fn fd_advise( #[wasi_common_cbindgen] pub fn fd_allocate( wasi_ctx: &WasiCtx, - memory: &mut [u8], fd: wasm32::__wasi_fd_t, offset: wasm32::__wasi_filesize_t, len: wasm32::__wasi_filesize_t, @@ -1127,7 +909,7 @@ pub fn path_rename( Err(e) => return enc_errno(e), }; let new_path = match dec_slice_of::(memory, new_path_ptr, new_path_len) { - Ok((slice)) => OsStr::from_bytes(slice), + Ok(slice) => OsStr::from_bytes(slice), Err(e) => return enc_errno(e), }; let rights = host::__WASI_RIGHT_PATH_RENAME_SOURCE; @@ -1194,7 +976,6 @@ pub fn fd_filestat_get( #[wasi_common_cbindgen] pub fn fd_filestat_set_times( wasi_ctx: &WasiCtx, - memory: &mut [u8], fd: wasm32::__wasi_fd_t, st_atim: wasm32::__wasi_timestamp_t, st_mtim: wasm32::__wasi_timestamp_t, @@ -1252,7 +1033,6 @@ pub fn fd_filestat_set_times( #[wasi_common_cbindgen] pub fn fd_filestat_set_size( wasi_ctx: &WasiCtx, - memory: &mut [u8], fd: wasm32::__wasi_fd_t, st_size: wasm32::__wasi_filesize_t, ) -> wasm32::__wasi_errno_t { @@ -1506,98 +1286,6 @@ pub fn path_remove_directory( } } -#[wasi_common_cbindgen] -pub fn poll_oneoff( - memory: &mut [u8], - input: wasm32::uintptr_t, - output: wasm32::uintptr_t, - nsubscriptions: wasm32::size_t, - nevents: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - if nsubscriptions as u64 > wasm32::__wasi_filesize_t::max_value() { - return wasm32::__WASI_EINVAL; - } - enc_pointee(memory, nevents, 0).unwrap(); - let input_slice = - dec_slice_of::(memory, input, nsubscriptions).unwrap(); - - let input: Vec<_> = input_slice.iter().map(|x| dec_subscription(x)).collect(); - - let output_slice = - dec_slice_of_mut::(memory, output, nsubscriptions).unwrap(); - - let timeout = input - .iter() - .filter_map(|event| match event { - Ok(event) if event.type_ == wasm32::__WASI_EVENTTYPE_CLOCK => Some(ClockEventData { - delay: wasi_clock_to_relative_ns_delay(unsafe { event.u.clock }) / 1_000_000, - userdata: event.userdata, - }), - _ => None, - }) - .min_by_key(|event| event.delay); - let fd_events: Vec<_> = input - .iter() - .filter_map(|event| match event { - Ok(event) - if event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ - || event.type_ == wasm32::__WASI_EVENTTYPE_FD_WRITE => - { - Some(FdEventData { - fd: unsafe { event.u.fd_readwrite.fd } as c_int, - type_: event.type_, - userdata: event.userdata, - }) - } - _ => None, - }) - .collect(); - if fd_events.is_empty() && timeout.is_none() { - return wasm32::__WASI_ESUCCESS; - } - let mut poll_fds: Vec<_> = fd_events - .iter() - .map(|event| { - let mut flags = nix::poll::EventFlags::empty(); - match event.type_ { - wasm32::__WASI_EVENTTYPE_FD_READ => flags.insert(nix::poll::EventFlags::POLLIN), - wasm32::__WASI_EVENTTYPE_FD_WRITE => flags.insert(nix::poll::EventFlags::POLLOUT), - // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE - // Nothing else has been defined in the specification, and these are also the only two - // events we filtered before. If we get something else here, the code has a serious bug. - _ => unreachable!(), - }; - nix::poll::PollFd::new(event.fd, flags) - }) - .collect(); - let timeout = timeout.map(|ClockEventData { delay, userdata }| ClockEventData { - delay: cmp::min(delay, c_int::max_value() as u128), - userdata, - }); - let poll_timeout = timeout.map_or(-1, |timeout| timeout.delay as c_int); - let ready = loop { - match nix::poll::poll(&mut poll_fds, poll_timeout) { - Err(_) => { - if nix::errno::Errno::last() == nix::errno::Errno::EINTR { - continue; - } - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } - Ok(ready) => break ready as usize, - } - }; - let events_count = if ready == 0 { - poll_oneoff_handle_timeout_event(output_slice, nevents, timeout) - } else { - let events = fd_events.iter().zip(poll_fds.iter()).take(ready); - poll_oneoff_handle_fd_event(output_slice, nevents, events) - }; - if let Err(e) = enc_pointee(memory, nevents, events_count) { - return enc_errno(e); - } - wasm32::__WASI_ESUCCESS -} - #[wasi_common_cbindgen] pub fn fd_prestat_get( wasi_ctx: &WasiCtx, @@ -1665,486 +1353,3 @@ pub fn fd_prestat_dir_name( Err(e) => enc_errno(e), } } - -#[wasi_common_cbindgen] -pub fn proc_exit(rval: wasm32::__wasi_exitcode_t) -> () { - // TODO: Rather than call std::process::exit here, we should trigger a - // stack unwind similar to a trap. - std::process::exit(dec_exitcode(rval) as i32); -} - -#[wasi_common_cbindgen] -pub fn proc_raise( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - sig: wasm32::__wasi_signal_t, -) -> wasm32::__wasi_errno_t { - unimplemented!("proc_raise") -} - -#[wasi_common_cbindgen] -pub fn random_get( - memory: &mut [u8], - buf_ptr: wasm32::uintptr_t, - buf_len: wasm32::size_t, -) -> wasm32::__wasi_errno_t { - use rand::{thread_rng, RngCore}; - - let buf = match dec_slice_of_mut::(memory, buf_ptr, buf_len) { - Ok(buf) => buf, - Err(e) => return enc_errno(e), - }; - - thread_rng().fill_bytes(buf); - - return wasm32::__WASI_ESUCCESS; -} - -#[wasi_common_cbindgen] -pub fn sched_yield() -> wasm32::__wasi_errno_t { - unsafe { libc::sched_yield() }; - wasm32::__WASI_ESUCCESS -} - -#[wasi_common_cbindgen] -pub fn sock_recv( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - sock: wasm32::__wasi_fd_t, - ri_data: wasm32::uintptr_t, - ri_data_len: wasm32::size_t, - ri_flags: wasm32::__wasi_riflags_t, - ro_datalen: wasm32::uintptr_t, - ro_flags: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - unimplemented!("sock_recv") -} - -#[wasi_common_cbindgen] -pub fn sock_send( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - sock: wasm32::__wasi_fd_t, - si_data: wasm32::uintptr_t, - si_data_len: wasm32::size_t, - si_flags: wasm32::__wasi_siflags_t, - so_datalen: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - unimplemented!("sock_send") -} - -#[wasi_common_cbindgen] -pub fn sock_shutdown( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - sock: wasm32::__wasi_fd_t, - how: wasm32::__wasi_sdflags_t, -) -> wasm32::__wasi_errno_t { - unimplemented!("sock_shutdown") -} - -// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` -nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); - -fn wasi_clock_to_relative_ns_delay( - wasi_clock: host::__wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_clock_t, -) -> u128 { - if wasi_clock.flags != wasm32::__WASI_SUBSCRIPTION_CLOCK_ABSTIME { - return wasi_clock.timeout as u128; - } - let now: u128 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Current date is before the epoch") - .as_nanos(); - let deadline = wasi_clock.timeout as u128; - deadline.saturating_sub(now) -} - -#[derive(Debug, Copy, Clone)] -struct ClockEventData { - delay: u128, - userdata: host::__wasi_userdata_t, -} -#[derive(Debug, Copy, Clone)] -struct FdEventData { - fd: c_int, - type_: host::__wasi_eventtype_t, - userdata: host::__wasi_userdata_t, -} - -fn poll_oneoff_handle_timeout_event( - output_slice: &mut [wasm32::__wasi_event_t], - nevents: wasm32::uintptr_t, - timeout: Option, -) -> wasm32::size_t { - if let Some(ClockEventData { userdata, .. }) = timeout { - let output_event = host::__wasi_event_t { - userdata, - type_: wasm32::__WASI_EVENTTYPE_CLOCK, - error: wasm32::__WASI_ESUCCESS, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: 0, - }, - }, - }; - output_slice[0] = enc_event(output_event); - 1 - } else { - // shouldn't happen - 0 - } -} - -fn poll_oneoff_handle_fd_event<'t>( - output_slice: &mut [wasm32::__wasi_event_t], - nevents: wasm32::uintptr_t, - events: impl Iterator, -) -> wasm32::size_t { - let mut output_slice_cur = output_slice.iter_mut(); - let mut revents_count = 0; - for (fd_event, poll_fd) in events { - let revents = match poll_fd.revents() { - Some(revents) => revents, - None => continue, - }; - let mut nbytes = 0; - if fd_event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ { - let _ = unsafe { fionread(fd_event.fd, &mut nbytes) }; - } - let output_event = if revents.contains(nix::poll::EventFlags::POLLNVAL) { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_EBADF, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, - }, - }, - } - } else if revents.contains(nix::poll::EventFlags::POLLERR) { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_EIO, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, - }, - }, - } - } else if revents.contains(nix::poll::EventFlags::POLLHUP) { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_ESUCCESS, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, - }, - }, - } - } else if revents.contains(nix::poll::EventFlags::POLLIN) - | revents.contains(nix::poll::EventFlags::POLLOUT) - { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_ESUCCESS, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: nbytes as host::__wasi_filesize_t, - flags: 0, - }, - }, - } - } else { - continue; - }; - *output_slice_cur.next().unwrap() = enc_event(output_event); - revents_count += 1; - } - revents_count -} - -/// Normalizes a path to ensure that the target path is located under the directory provided. -/// -/// This is a workaround for not having Capsicum support in the OS. -pub fn path_get>( - wasi_ctx: &WasiCtx, - dirfd: host::__wasi_fd_t, - dirflags: host::__wasi_lookupflags_t, - path: P, - needed_base: host::__wasi_rights_t, - needed_inheriting: host::__wasi_rights_t, - needs_final_component: bool, -) -> Result<(RawFd, OsString), host::__wasi_errno_t> { - use nix::errno::Errno; - use nix::fcntl::{openat, readlinkat, OFlag}; - use nix::sys::stat::Mode; - - const MAX_SYMLINK_EXPANSIONS: usize = 128; - - /// close all the intermediate file descriptors, but make sure not to drop either the original - /// dirfd or the one we return (which may be the same dirfd) - fn ret_dir_success(dir_stack: &mut Vec) -> RawFd { - let ret_dir = dir_stack.pop().expect("there is always a dirfd to return"); - if let Some(dirfds) = dir_stack.get(1..) { - for dirfd in dirfds { - nix::unistd::close(*dirfd).unwrap_or_else(|e| { - dbg!(e); - }); - } - } - ret_dir - } - - /// close all file descriptors other than the base directory, and return the errno for - /// convenience with `return` - fn ret_error( - dir_stack: &mut Vec, - errno: host::__wasi_errno_t, - ) -> Result<(RawFd, OsString), host::__wasi_errno_t> { - if let Some(dirfds) = dir_stack.get(1..) { - for dirfd in dirfds { - nix::unistd::close(*dirfd).unwrap_or_else(|e| { - dbg!(e); - }); - } - } - Err(errno) - } - - let dirfe = wasi_ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; - - // Stack of directory file descriptors. Index 0 always corresponds with the directory provided - // to this function. Entering a directory causes a file descriptor to be pushed, while handling - // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply - // escaping the base directory. - let mut dir_stack = vec![dirfe.fd_object.rawfd]; - - // Stack of paths left to process. This is initially the `path` argument to this function, but - // any symlinks we encounter are processed by pushing them on the stack. - let mut path_stack = vec![path.as_ref().to_owned().into_vec()]; - - // Track the number of symlinks we've expanded, so we can return `ELOOP` after too many. - let mut symlink_expansions = 0; - - // Buffer to read links into; defined outside of the loop so we don't reallocate it constantly. - let mut readlink_buf = vec![0u8; libc::PATH_MAX as usize + 1]; - - // TODO: rewrite this using a custom posix path type, with a component iterator that respects - // trailing slashes. This version does way too much allocation, and is way too fiddly. - loop { - let component = if let Some(cur_path) = path_stack.pop() { - // eprintln!( - // "cur_path = {:?}", - // std::str::from_utf8(cur_path.as_slice()).unwrap() - // ); - let mut split = cur_path.splitn(2, |&c| c == '/' as u8); - let head = split.next(); - let tail = split.next(); - match (head, tail) { - (None, _) => { - // split always returns at least a singleton iterator with an empty slice - panic!("unreachable"); - } - // path is empty - (Some([]), None) => { - return ret_error(&mut dir_stack, host::__WASI_ENOENT); - } - // path starts with `/`, is absolute - (Some([]), Some(_)) => { - return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE); - } - // the final component of the path with no trailing slash - (Some(component), None) => component.to_vec(), - (Some(component), Some(rest)) => { - if rest.iter().all(|&c| c == '/' as u8) { - // the final component of the path with trailing slashes; put one trailing - // slash back on - let mut component = component.to_vec(); - component.push('/' as u8); - component - } else { - // non-final component; push the rest back on the stack - path_stack.push(rest.to_vec()); - component.to_vec() - } - } - } - } else { - // if the path stack is ever empty, we return rather than going through the loop again - panic!("unreachable"); - }; - - // eprintln!( - // "component = {:?}", - // std::str::from_utf8(component.as_slice()).unwrap() - // ); - - match component.as_slice() { - b"." => { - // skip component - } - b".." => { - // pop a directory - let dirfd = dir_stack.pop().expect("dir_stack is never empty"); - - // we're not allowed to pop past the original directory - if dir_stack.is_empty() { - return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE); - } else { - nix::unistd::close(dirfd).unwrap_or_else(|e| { - dbg!(e); - }); - } - } - // should the component be a directory? it should if there is more path left to process, or - // if it has a trailing slash and `needs_final_component` is not set - component - if !path_stack.is_empty() - || (component.ends_with(b"/") && !needs_final_component) => - { - match openat( - *dir_stack.first().expect("dir_stack is never empty"), - component, - OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW, - Mode::empty(), - ) { - Ok(new_dir) => { - dir_stack.push(new_dir); - continue; - } - Err(e) - if e.as_errno() == Some(Errno::ELOOP) - || e.as_errno() == Some(Errno::EMLINK) => - { - // attempt symlink expansion - match readlinkat( - *dir_stack.last().expect("dir_stack is never empty"), - component, - readlink_buf.as_mut_slice(), - ) { - Ok(link_path) => { - symlink_expansions += 1; - if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return ret_error(&mut dir_stack, host::__WASI_ELOOP); - } - - let mut link_path = link_path.as_bytes().to_vec(); - - // append a trailing slash if the component leading to it has one, so - // that we preserve any ENOTDIR that might come from trying to open a - // non-directory - if component.ends_with(b"/") { - link_path.push('/' as u8); - } - - path_stack.push(link_path); - continue; - } - Err(e) => { - return ret_error( - &mut dir_stack, - host::errno_from_nix(e.as_errno().unwrap()), - ); - } - } - } - Err(e) => { - return ret_error( - &mut dir_stack, - host::errno_from_nix(e.as_errno().unwrap()), - ); - } - } - } - // the final component - component => { - // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt - // symlink expansion - if component.ends_with(b"/") || (dirflags & host::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0 - { - match readlinkat( - *dir_stack.last().expect("dir_stack is never empty"), - component, - readlink_buf.as_mut_slice(), - ) { - Ok(link_path) => { - symlink_expansions += 1; - if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return ret_error(&mut dir_stack, host::__WASI_ELOOP); - } - - let mut link_path = link_path.as_bytes().to_vec(); - - // append a trailing slash if the component leading to it has one, so - // that we preserve any ENOTDIR that might come from trying to open a - // non-directory - if component.ends_with(b"/") { - link_path.push('/' as u8); - } - - path_stack.push(link_path); - continue; - } - Err(e) => { - let errno = e.as_errno().unwrap(); - if errno != Errno::EINVAL && errno != Errno::ENOENT { - // only return an error if this path is not actually a symlink - return ret_error(&mut dir_stack, host::errno_from_nix(errno)); - } - } - } - } - - // not a symlink, so we're done; - return Ok(( - ret_dir_success(&mut dir_stack), - OsStr::from_bytes(component).to_os_string(), - )); - } - } - - if path_stack.is_empty() { - // no further components to process. means we've hit a case like "." or "a/..", or if the - // input path has trailing slashes and `needs_final_component` is not set - return Ok(( - ret_dir_success(&mut dir_stack), - OsStr::new(".").to_os_string(), - )); - } else { - continue; - } - } -} - -#[cfg(not(target_os = "macos"))] -fn utime_now() -> c_long { - libc::UTIME_NOW -} - -#[cfg(target_os = "macos")] -fn utime_now() -> c_long { - -1 -} - -#[cfg(not(target_os = "macos"))] -fn utime_omit() -> c_long { - libc::UTIME_OMIT -} - -#[cfg(target_os = "macos")] -fn utime_omit() -> c_long { - -2 -} diff --git a/src/hostcalls/fs_helpers.rs b/src/hostcalls/fs_helpers.rs new file mode 100644 index 0000000000..21cb8a35c8 --- /dev/null +++ b/src/hostcalls/fs_helpers.rs @@ -0,0 +1,282 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] + +use crate::ctx::WasiCtx; +use crate::host; + +use nix::libc::{self, c_long}; +use std::ffi::{OsStr, OsString}; +use std::os::unix::prelude::{OsStrExt, OsStringExt, RawFd}; + +/// Normalizes a path to ensure that the target path is located under the directory provided. +/// +/// This is a workaround for not having Capsicum support in the OS. +pub fn path_get>( + wasi_ctx: &WasiCtx, + dirfd: host::__wasi_fd_t, + dirflags: host::__wasi_lookupflags_t, + path: P, + needed_base: host::__wasi_rights_t, + needed_inheriting: host::__wasi_rights_t, + needs_final_component: bool, +) -> Result<(RawFd, OsString), host::__wasi_errno_t> { + use nix::errno::Errno; + use nix::fcntl::{openat, readlinkat, OFlag}; + use nix::sys::stat::Mode; + + const MAX_SYMLINK_EXPANSIONS: usize = 128; + + /// close all the intermediate file descriptors, but make sure not to drop either the original + /// dirfd or the one we return (which may be the same dirfd) + fn ret_dir_success(dir_stack: &mut Vec) -> RawFd { + let ret_dir = dir_stack.pop().expect("there is always a dirfd to return"); + if let Some(dirfds) = dir_stack.get(1..) { + for dirfd in dirfds { + nix::unistd::close(*dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + ret_dir + } + + /// close all file descriptors other than the base directory, and return the errno for + /// convenience with `return` + fn ret_error( + dir_stack: &mut Vec, + errno: host::__wasi_errno_t, + ) -> Result<(RawFd, OsString), host::__wasi_errno_t> { + if let Some(dirfds) = dir_stack.get(1..) { + for dirfd in dirfds { + nix::unistd::close(*dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + Err(errno) + } + + let dirfe = wasi_ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; + + // Stack of directory file descriptors. Index 0 always corresponds with the directory provided + // to this function. Entering a directory causes a file descriptor to be pushed, while handling + // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply + // escaping the base directory. + let mut dir_stack = vec![dirfe.fd_object.rawfd]; + + // Stack of paths left to process. This is initially the `path` argument to this function, but + // any symlinks we encounter are processed by pushing them on the stack. + let mut path_stack = vec![path.as_ref().to_owned().into_vec()]; + + // Track the number of symlinks we've expanded, so we can return `ELOOP` after too many. + let mut symlink_expansions = 0; + + // Buffer to read links into; defined outside of the loop so we don't reallocate it constantly. + let mut readlink_buf = vec![0u8; libc::PATH_MAX as usize + 1]; + + // TODO: rewrite this using a custom posix path type, with a component iterator that respects + // trailing slashes. This version does way too much allocation, and is way too fiddly. + loop { + let component = if let Some(cur_path) = path_stack.pop() { + // eprintln!( + // "cur_path = {:?}", + // std::str::from_utf8(cur_path.as_slice()).unwrap() + // ); + let mut split = cur_path.splitn(2, |&c| c == '/' as u8); + let head = split.next(); + let tail = split.next(); + match (head, tail) { + (None, _) => { + // split always returns at least a singleton iterator with an empty slice + panic!("unreachable"); + } + // path is empty + (Some([]), None) => { + return ret_error(&mut dir_stack, host::__WASI_ENOENT); + } + // path starts with `/`, is absolute + (Some([]), Some(_)) => { + return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE); + } + // the final component of the path with no trailing slash + (Some(component), None) => component.to_vec(), + (Some(component), Some(rest)) => { + if rest.iter().all(|&c| c == '/' as u8) { + // the final component of the path with trailing slashes; put one trailing + // slash back on + let mut component = component.to_vec(); + component.push('/' as u8); + component + } else { + // non-final component; push the rest back on the stack + path_stack.push(rest.to_vec()); + component.to_vec() + } + } + } + } else { + // if the path stack is ever empty, we return rather than going through the loop again + panic!("unreachable"); + }; + + // eprintln!( + // "component = {:?}", + // std::str::from_utf8(component.as_slice()).unwrap() + // ); + + match component.as_slice() { + b"." => { + // skip component + } + b".." => { + // pop a directory + let dirfd = dir_stack.pop().expect("dir_stack is never empty"); + + // we're not allowed to pop past the original directory + if dir_stack.is_empty() { + return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE); + } else { + nix::unistd::close(dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + // should the component be a directory? it should if there is more path left to process, or + // if it has a trailing slash and `needs_final_component` is not set + component + if !path_stack.is_empty() + || (component.ends_with(b"/") && !needs_final_component) => + { + match openat( + *dir_stack.first().expect("dir_stack is never empty"), + component, + OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW, + Mode::empty(), + ) { + Ok(new_dir) => { + dir_stack.push(new_dir); + continue; + } + Err(e) + if e.as_errno() == Some(Errno::ELOOP) + || e.as_errno() == Some(Errno::EMLINK) => + { + // attempt symlink expansion + match readlinkat( + *dir_stack.last().expect("dir_stack is never empty"), + component, + readlink_buf.as_mut_slice(), + ) { + Ok(link_path) => { + symlink_expansions += 1; + if symlink_expansions > MAX_SYMLINK_EXPANSIONS { + return ret_error(&mut dir_stack, host::__WASI_ELOOP); + } + + let mut link_path = link_path.as_bytes().to_vec(); + + // append a trailing slash if the component leading to it has one, so + // that we preserve any ENOTDIR that might come from trying to open a + // non-directory + if component.ends_with(b"/") { + link_path.push('/' as u8); + } + + path_stack.push(link_path); + continue; + } + Err(e) => { + return ret_error( + &mut dir_stack, + host::errno_from_nix(e.as_errno().unwrap()), + ); + } + } + } + Err(e) => { + return ret_error( + &mut dir_stack, + host::errno_from_nix(e.as_errno().unwrap()), + ); + } + } + } + // the final component + component => { + // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt + // symlink expansion + if component.ends_with(b"/") || (dirflags & host::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0 + { + match readlinkat( + *dir_stack.last().expect("dir_stack is never empty"), + component, + readlink_buf.as_mut_slice(), + ) { + Ok(link_path) => { + symlink_expansions += 1; + if symlink_expansions > MAX_SYMLINK_EXPANSIONS { + return ret_error(&mut dir_stack, host::__WASI_ELOOP); + } + + let mut link_path = link_path.as_bytes().to_vec(); + + // append a trailing slash if the component leading to it has one, so + // that we preserve any ENOTDIR that might come from trying to open a + // non-directory + if component.ends_with(b"/") { + link_path.push('/' as u8); + } + + path_stack.push(link_path); + continue; + } + Err(e) => { + let errno = e.as_errno().unwrap(); + if errno != Errno::EINVAL && errno != Errno::ENOENT { + // only return an error if this path is not actually a symlink + return ret_error(&mut dir_stack, host::errno_from_nix(errno)); + } + } + } + } + + // not a symlink, so we're done; + return Ok(( + ret_dir_success(&mut dir_stack), + OsStr::from_bytes(component).to_os_string(), + )); + } + } + + if path_stack.is_empty() { + // no further components to process. means we've hit a case like "." or "a/..", or if the + // input path has trailing slashes and `needs_final_component` is not set + return Ok(( + ret_dir_success(&mut dir_stack), + OsStr::new(".").to_os_string(), + )); + } else { + continue; + } + } +} + +#[cfg(not(target_os = "macos"))] +pub fn utime_now() -> c_long { + libc::UTIME_NOW +} + +#[cfg(target_os = "macos")] +pub fn utime_now() -> c_long { + -1 +} + +#[cfg(not(target_os = "macos"))] +pub fn utime_omit() -> c_long { + libc::UTIME_OMIT +} + +#[cfg(target_os = "macos")] +pub fn utime_omit() -> c_long { + -2 +} diff --git a/src/hostcalls/misc.rs b/src/hostcalls/misc.rs new file mode 100644 index 0000000000..9c5e42d15a --- /dev/null +++ b/src/hostcalls/misc.rs @@ -0,0 +1,470 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] + +use crate::ctx::WasiCtx; +use crate::memory::*; +use crate::{host, wasm32}; + +use cast::From as _0; + +use nix::convert_ioctl_res; +use nix::libc::{self, c_int}; +use std::cmp; +use std::time::SystemTime; +use wasi_common_cbindgen::wasi_common_cbindgen; + +#[wasi_common_cbindgen] +pub fn args_get( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + argv_ptr: wasm32::uintptr_t, + argv_buf: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let mut argv_buf_offset = 0; + let mut argv = vec![]; + + for arg in wasi_ctx.args.iter() { + let arg_bytes = arg.as_bytes_with_nul(); + let arg_ptr = argv_buf + argv_buf_offset; + + if let Err(e) = enc_slice_of(memory, arg_bytes, arg_ptr) { + return enc_errno(e); + } + + argv.push(arg_ptr); + + argv_buf_offset = if let Some(new_offset) = argv_buf_offset.checked_add( + wasm32::uintptr_t::cast(arg_bytes.len()) + .expect("cast overflow would have been caught by `enc_slice_of` above"), + ) { + new_offset + } else { + return wasm32::__WASI_EOVERFLOW; + } + } + + enc_slice_of(memory, argv.as_slice(), argv_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +#[wasi_common_cbindgen] +pub fn args_sizes_get( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + argc_ptr: wasm32::uintptr_t, + argv_buf_size_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let argc = wasi_ctx.args.len(); + let argv_size = wasi_ctx + .args + .iter() + .map(|arg| arg.as_bytes_with_nul().len()) + .sum(); + + if let Err(e) = enc_usize_byref(memory, argc_ptr, argc) { + return enc_errno(e); + } + if let Err(e) = enc_usize_byref(memory, argv_buf_size_ptr, argv_size) { + return enc_errno(e); + } + wasm32::__WASI_ESUCCESS +} + +#[wasi_common_cbindgen] +pub fn clock_res_get( + memory: &mut [u8], + clock_id: wasm32::__wasi_clockid_t, + resolution_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + // convert the supported clocks to the libc types, or return EINVAL + let clock_id = match dec_clockid(clock_id) { + host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, + host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, + host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, + host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, + _ => return wasm32::__WASI_EINVAL, + }; + + // no `nix` wrapper for clock_getres, so we do it ourselves + let mut timespec = unsafe { std::mem::uninitialized::() }; + let res = unsafe { libc::clock_getres(clock_id, &mut timespec as *mut libc::timespec) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + .map_or(wasm32::__WASI_EOVERFLOW, |resolution| { + // a supported clock can never return zero; this case will probably never get hit, but + // make sure we follow the spec + if resolution == 0 { + wasm32::__WASI_EINVAL + } else { + enc_timestamp_byref(memory, resolution_ptr, resolution) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } + }) +} + +#[wasi_common_cbindgen] +pub fn clock_time_get( + memory: &mut [u8], + clock_id: wasm32::__wasi_clockid_t, + // ignored for now, but will be useful once we put optional limits on precision to reduce side + // channels + _precision: wasm32::__wasi_timestamp_t, + time_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + // convert the supported clocks to the libc types, or return EINVAL + let clock_id = match dec_clockid(clock_id) { + host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, + host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, + host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, + host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, + _ => return wasm32::__WASI_EINVAL, + }; + + // no `nix` wrapper for clock_getres, so we do it ourselves + let mut timespec = unsafe { std::mem::uninitialized::() }; + let res = unsafe { libc::clock_gettime(clock_id, &mut timespec as *mut libc::timespec) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + .map_or(wasm32::__WASI_EOVERFLOW, |time| { + enc_timestamp_byref(memory, time_ptr, time) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + }) +} + +#[wasi_common_cbindgen] +pub fn environ_get( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + environ_ptr: wasm32::uintptr_t, + environ_buf: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let mut environ_buf_offset = 0; + let mut environ = vec![]; + + for pair in wasi_ctx.env.iter() { + let env_bytes = pair.as_bytes_with_nul(); + let env_ptr = environ_buf + environ_buf_offset; + + if let Err(e) = enc_slice_of(memory, env_bytes, env_ptr) { + return enc_errno(e); + } + + environ.push(env_ptr); + + environ_buf_offset = if let Some(new_offset) = environ_buf_offset.checked_add( + wasm32::uintptr_t::cast(env_bytes.len()) + .expect("cast overflow would have been caught by `enc_slice_of` above"), + ) { + new_offset + } else { + return wasm32::__WASI_EOVERFLOW; + } + } + + enc_slice_of(memory, environ.as_slice(), environ_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +#[wasi_common_cbindgen] +pub fn environ_sizes_get( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + environ_count_ptr: wasm32::uintptr_t, + environ_size_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let environ_count = wasi_ctx.env.len(); + if let Some(environ_size) = wasi_ctx.env.iter().try_fold(0, |acc: u32, pair| { + acc.checked_add(pair.as_bytes_with_nul().len() as u32) + }) { + if let Err(e) = enc_usize_byref(memory, environ_count_ptr, environ_count) { + return enc_errno(e); + } + if let Err(e) = enc_usize_byref(memory, environ_size_ptr, environ_size as usize) { + return enc_errno(e); + } + wasm32::__WASI_ESUCCESS + } else { + wasm32::__WASI_EOVERFLOW + } +} + +#[wasi_common_cbindgen] +pub fn poll_oneoff( + memory: &mut [u8], + input: wasm32::uintptr_t, + output: wasm32::uintptr_t, + nsubscriptions: wasm32::size_t, + nevents: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + if nsubscriptions as u64 > wasm32::__wasi_filesize_t::max_value() { + return wasm32::__WASI_EINVAL; + } + enc_pointee(memory, nevents, 0).unwrap(); + let input_slice = + dec_slice_of::(memory, input, nsubscriptions).unwrap(); + + let input: Vec<_> = input_slice.iter().map(|x| dec_subscription(x)).collect(); + + let output_slice = + dec_slice_of_mut::(memory, output, nsubscriptions).unwrap(); + + let timeout = input + .iter() + .filter_map(|event| match event { + Ok(event) if event.type_ == wasm32::__WASI_EVENTTYPE_CLOCK => Some(ClockEventData { + delay: wasi_clock_to_relative_ns_delay(unsafe { event.u.clock }) / 1_000_000, + userdata: event.userdata, + }), + _ => None, + }) + .min_by_key(|event| event.delay); + let fd_events: Vec<_> = input + .iter() + .filter_map(|event| match event { + Ok(event) + if event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ + || event.type_ == wasm32::__WASI_EVENTTYPE_FD_WRITE => + { + Some(FdEventData { + fd: unsafe { event.u.fd_readwrite.fd } as c_int, + type_: event.type_, + userdata: event.userdata, + }) + } + _ => None, + }) + .collect(); + if fd_events.is_empty() && timeout.is_none() { + return wasm32::__WASI_ESUCCESS; + } + let mut poll_fds: Vec<_> = fd_events + .iter() + .map(|event| { + let mut flags = nix::poll::EventFlags::empty(); + match event.type_ { + wasm32::__WASI_EVENTTYPE_FD_READ => flags.insert(nix::poll::EventFlags::POLLIN), + wasm32::__WASI_EVENTTYPE_FD_WRITE => flags.insert(nix::poll::EventFlags::POLLOUT), + // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE + // Nothing else has been defined in the specification, and these are also the only two + // events we filtered before. If we get something else here, the code has a serious bug. + _ => unreachable!(), + }; + nix::poll::PollFd::new(event.fd, flags) + }) + .collect(); + let timeout = timeout.map(|ClockEventData { delay, userdata }| ClockEventData { + delay: cmp::min(delay, c_int::max_value() as u128), + userdata, + }); + let poll_timeout = timeout.map_or(-1, |timeout| timeout.delay as c_int); + let ready = loop { + match nix::poll::poll(&mut poll_fds, poll_timeout) { + Err(_) => { + if nix::errno::Errno::last() == nix::errno::Errno::EINTR { + continue; + } + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + Ok(ready) => break ready as usize, + } + }; + let events_count = if ready == 0 { + poll_oneoff_handle_timeout_event(output_slice, timeout) + } else { + let events = fd_events.iter().zip(poll_fds.iter()).take(ready); + poll_oneoff_handle_fd_event(output_slice, events) + }; + if let Err(e) = enc_pointee(memory, nevents, events_count) { + return enc_errno(e); + } + wasm32::__WASI_ESUCCESS +} + +#[wasi_common_cbindgen] +pub fn proc_exit(rval: wasm32::__wasi_exitcode_t) -> () { + // TODO: Rather than call std::process::exit here, we should trigger a + // stack unwind similar to a trap. + std::process::exit(dec_exitcode(rval) as i32); +} + +#[wasi_common_cbindgen] +pub fn proc_raise( + _wasi_ctx: &WasiCtx, + _memory: &mut [u8], + _sig: wasm32::__wasi_signal_t, +) -> wasm32::__wasi_errno_t { + unimplemented!("proc_raise") +} + +#[wasi_common_cbindgen] +pub fn random_get( + memory: &mut [u8], + buf_ptr: wasm32::uintptr_t, + buf_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use rand::{thread_rng, RngCore}; + + let buf = match dec_slice_of_mut::(memory, buf_ptr, buf_len) { + Ok(buf) => buf, + Err(e) => return enc_errno(e), + }; + + thread_rng().fill_bytes(buf); + + return wasm32::__WASI_ESUCCESS; +} + +#[wasi_common_cbindgen] +pub fn sched_yield() -> wasm32::__wasi_errno_t { + unsafe { libc::sched_yield() }; + wasm32::__WASI_ESUCCESS +} + +// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` +nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); + +fn wasi_clock_to_relative_ns_delay( + wasi_clock: host::__wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_clock_t, +) -> u128 { + if wasi_clock.flags != wasm32::__WASI_SUBSCRIPTION_CLOCK_ABSTIME { + return wasi_clock.timeout as u128; + } + let now: u128 = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Current date is before the epoch") + .as_nanos(); + let deadline = wasi_clock.timeout as u128; + deadline.saturating_sub(now) +} + +#[derive(Debug, Copy, Clone)] +struct ClockEventData { + delay: u128, + userdata: host::__wasi_userdata_t, +} +#[derive(Debug, Copy, Clone)] +struct FdEventData { + fd: c_int, + type_: host::__wasi_eventtype_t, + userdata: host::__wasi_userdata_t, +} + +fn poll_oneoff_handle_timeout_event( + output_slice: &mut [wasm32::__wasi_event_t], + timeout: Option, +) -> wasm32::size_t { + if let Some(ClockEventData { userdata, .. }) = timeout { + let output_event = host::__wasi_event_t { + userdata, + type_: wasm32::__WASI_EVENTTYPE_CLOCK, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: 0, + }, + }, + }; + output_slice[0] = enc_event(output_event); + 1 + } else { + // shouldn't happen + 0 + } +} + +fn poll_oneoff_handle_fd_event<'t>( + output_slice: &mut [wasm32::__wasi_event_t], + events: impl Iterator, +) -> wasm32::size_t { + let mut output_slice_cur = output_slice.iter_mut(); + let mut revents_count = 0; + for (fd_event, poll_fd) in events { + let revents = match poll_fd.revents() { + Some(revents) => revents, + None => continue, + }; + let mut nbytes = 0; + if fd_event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ { + let _ = unsafe { fionread(fd_event.fd, &mut nbytes) }; + } + let output_event = if revents.contains(nix::poll::EventFlags::POLLNVAL) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_EBADF, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLERR) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_EIO, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLHUP) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLIN) + | revents.contains(nix::poll::EventFlags::POLLOUT) + { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: nbytes as host::__wasi_filesize_t, + flags: 0, + }, + }, + } + } else { + continue; + }; + *output_slice_cur.next().unwrap() = enc_event(output_event); + revents_count += 1; + } + revents_count +} diff --git a/src/hostcalls/mod.rs b/src/hostcalls/mod.rs new file mode 100644 index 0000000000..a0ce8f8ecc --- /dev/null +++ b/src/hostcalls/mod.rs @@ -0,0 +1,10 @@ +//! Hostcalls that implement +//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). +mod fs; +mod fs_helpers; +mod misc; +mod sock; + +pub use self::fs::*; +pub use self::misc::*; +pub use self::sock::*; diff --git a/src/hostcalls/sock.rs b/src/hostcalls/sock.rs new file mode 100644 index 0000000000..0d38a3f611 --- /dev/null +++ b/src/hostcalls/sock.rs @@ -0,0 +1,44 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] +#![allow(unused)] + +use crate::ctx::WasiCtx; +use crate::wasm32; +use wasi_common_cbindgen::wasi_common_cbindgen; + +#[wasi_common_cbindgen] +pub fn sock_recv( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + sock: wasm32::__wasi_fd_t, + ri_data: wasm32::uintptr_t, + ri_data_len: wasm32::size_t, + ri_flags: wasm32::__wasi_riflags_t, + ro_datalen: wasm32::uintptr_t, + ro_flags: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + unimplemented!("sock_recv") +} + +#[wasi_common_cbindgen] +pub fn sock_send( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + sock: wasm32::__wasi_fd_t, + si_data: wasm32::uintptr_t, + si_data_len: wasm32::size_t, + si_flags: wasm32::__wasi_siflags_t, + so_datalen: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + unimplemented!("sock_send") +} + +#[wasi_common_cbindgen] +pub fn sock_shutdown( + wasi_ctx: &WasiCtx, + memory: &mut [u8], + sock: wasm32::__wasi_fd_t, + how: wasm32::__wasi_sdflags_t, +) -> wasm32::__wasi_errno_t { + unimplemented!("sock_shutdown") +}