From 0cde30197d959c1b16b17e0f0367e14805aeaef9 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 6 Dec 2019 15:48:46 -0800 Subject: [PATCH 1/7] fuzzing: Add initial API call fuzzer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We only generate *valid* sequences of API calls. To do this, we keep track of what objects we've already created in earlier API calls via the `Scope` struct. To generate even-more-pathological sequences of API calls, we use [swarm testing]: > In swarm testing, the usual practice of potentially including all features > in every test case is abandoned. Rather, a large “swarm” of randomly > generated configurations, each of which omits some features, is used, with > configurations receiving equal resources. [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf There are more public APIs and instance introspection APIs that we have than this fuzzer exercises right now. We will need a better generator of valid Wasm than `wasm-opt -ttf` to really get the most out of those currently-unexercised APIs, since the Wasm modules generated by `wasm-opt -ttf` don't import and export a huge variety of things. --- crates/fuzzing/src/generators.rs | 15 ++- crates/fuzzing/src/generators/api.rs | 182 +++++++++++++++++++++++++++ crates/fuzzing/src/lib.rs | 6 +- crates/fuzzing/src/oracles.rs | 128 ++++++++++++++++++- fuzz/Cargo.toml | 6 + fuzz/fuzz_targets/api_calls.rs | 27 ++++ 6 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 crates/fuzzing/src/generators/api.rs create mode 100755 fuzz/fuzz_targets/api_calls.rs diff --git a/crates/fuzzing/src/generators.rs b/crates/fuzzing/src/generators.rs index 043ce47baa..1fb6a29c0f 100644 --- a/crates/fuzzing/src/generators.rs +++ b/crates/fuzzing/src/generators.rs @@ -8,15 +8,28 @@ //! wrapper over an external tool, such that the wrapper implements the //! `Arbitrary` trait for the wrapped external tool. +pub mod api; + use arbitrary::{Arbitrary, Unstructured}; +use std::fmt; /// A Wasm test case generator that is powered by Binaryen's `wasm-opt -ttf`. -#[derive(Debug)] +#[derive(Clone)] pub struct WasmOptTtf { /// The raw, encoded Wasm bytes. pub wasm: Vec, } +impl fmt::Debug for WasmOptTtf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "WasmOptTtf {{ wasm: wat::parse_str(r###\"\n{}\n\"###).unwrap() }}", + wasmprinter::print_bytes(&self.wasm).expect("valid wasm should always disassemble") + ) + } +} + impl Arbitrary for WasmOptTtf { fn arbitrary(input: &mut U) -> Result where diff --git a/crates/fuzzing/src/generators/api.rs b/crates/fuzzing/src/generators/api.rs new file mode 100644 index 0000000000..debae64f05 --- /dev/null +++ b/crates/fuzzing/src/generators/api.rs @@ -0,0 +1,182 @@ +//! Generating sequences of Wasmtime API calls. +//! +//! We only generate *valid* sequences of API calls. To do this, we keep track +//! of what objects we've already created in earlier API calls via the `Scope` +//! struct. +//! +//! To generate even-more-pathological sequences of API calls, we use [swarm +//! testing]: +//! +//! > In swarm testing, the usual practice of potentially including all features +//! > in every test case is abandoned. Rather, a large “swarm” of randomly +//! > generated configurations, each of which omits some features, is used, with +//! > configurations receiving equal resources. +//! +//! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf + +use arbitrary::{Arbitrary, Unstructured}; +use std::collections::HashSet; + +struct Swarm { + config_debug_info: bool, + module_new: bool, + module_drop: bool, + instance_new: bool, + instance_drop: bool, + call_exported_func: bool, +} + +impl Arbitrary for Swarm { + fn arbitrary(input: &mut U) -> Result + where + U: Unstructured + ?Sized, + { + Ok(Swarm { + config_debug_info: bool::arbitrary(input)?, + module_new: bool::arbitrary(input)?, + module_drop: bool::arbitrary(input)?, + instance_new: bool::arbitrary(input)?, + instance_drop: bool::arbitrary(input)?, + call_exported_func: bool::arbitrary(input)?, + }) + } +} + +/// A call to one of Wasmtime's public APIs. +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub enum ApiCall { + ConfigNew, + ConfigDebugInfo(bool), + EngineNew, + StoreNew, + ModuleNew { id: usize, wasm: super::WasmOptTtf }, + ModuleDrop { id: usize }, + InstanceNew { id: usize, module: usize }, + InstanceDrop { id: usize }, + CallExportedFunc { instance: usize, nth: usize }, +} +use ApiCall::*; + +#[derive(Default)] +struct Scope { + id_counter: usize, + modules: HashSet, + instances: HashSet, +} + +impl Scope { + fn next_id(&mut self) -> usize { + let id = self.id_counter; + self.id_counter = id + 1; + id + } +} + +/// A sequence of API calls. +#[derive(Debug)] +pub struct ApiCalls { + /// The API calls. + pub calls: Vec, +} + +impl Arbitrary for ApiCalls { + fn arbitrary(input: &mut U) -> Result + where + U: Unstructured + ?Sized, + { + let swarm = Swarm::arbitrary(input)?; + let mut calls = vec![]; + + arbitrary_config(input, &swarm, &mut calls)?; + calls.push(EngineNew); + calls.push(StoreNew); + + let mut scope = Scope::default(); + + for _ in 0..input.container_size()? { + let mut choices: Vec Result> = vec![]; + + if swarm.module_new { + choices.push(|input, scope| { + let id = scope.next_id(); + scope.modules.insert(id); + let wasm = super::WasmOptTtf::arbitrary(input)?; + Ok(ModuleNew { id, wasm }) + }); + } + if swarm.module_drop && !scope.modules.is_empty() { + choices.push(|input, scope| { + let modules: Vec<_> = scope.modules.iter().cloned().collect(); + let id = arbitrary_choice(input, &modules)?.cloned().unwrap(); + scope.modules.remove(&id); + Ok(ModuleDrop { id }) + }); + } + if swarm.instance_new && !scope.modules.is_empty() { + choices.push(|input, scope| { + let modules: Vec<_> = scope.modules.iter().cloned().collect(); + let module = arbitrary_choice(input, &modules)?.cloned().unwrap(); + let id = scope.next_id(); + scope.instances.insert(id); + Ok(InstanceNew { id, module }) + }); + } + if swarm.instance_drop && !scope.instances.is_empty() { + choices.push(|input, scope| { + let instances: Vec<_> = scope.instances.iter().cloned().collect(); + let id = arbitrary_choice(input, &instances)?.cloned().unwrap(); + scope.instances.remove(&id); + Ok(InstanceDrop { id }) + }); + } + if swarm.call_exported_func && !scope.instances.is_empty() { + choices.push(|input, scope| { + let instances: Vec<_> = scope.instances.iter().cloned().collect(); + let instance = arbitrary_choice(input, &instances)?.cloned().unwrap(); + let nth = usize::arbitrary(input)?; + Ok(CallExportedFunc { instance, nth }) + }); + } + + if let Some(c) = arbitrary_choice(input, &choices)? { + calls.push(c(input, &mut scope)?); + } else { + break; + } + } + + Ok(ApiCalls { calls }) + } +} + +fn arbitrary_choice<'a, T, U>(input: &mut U, choices: &'a [T]) -> Result, U::Error> +where + U: Unstructured + ?Sized, +{ + if choices.is_empty() { + Ok(None) + } else { + let i = usize::arbitrary(input)? % choices.len(); + Ok(Some(&choices[i])) + } +} + +fn arbitrary_config( + input: &mut U, + swarm: &Swarm, + calls: &mut Vec, +) -> Result<(), U::Error> +where + U: Unstructured + ?Sized, +{ + calls.push(ConfigNew); + + if swarm.config_debug_info && bool::arbitrary(input)? { + calls.push(ConfigDebugInfo(bool::arbitrary(input)?)); + } + + // TODO: flags, features, and compilation strategy. + + Ok(()) +} diff --git a/crates/fuzzing/src/lib.rs b/crates/fuzzing/src/lib.rs index df232f8072..394038d0e4 100644 --- a/crates/fuzzing/src/lib.rs +++ b/crates/fuzzing/src/lib.rs @@ -107,8 +107,12 @@ fn my_fuzzing_regression_test() {{ } } -fn scratch_dir() -> PathBuf { +pub(crate) fn scratch_dir() -> PathBuf { let dir = Path::new(env!("CARGO_MANIFEST_DIR")) + // Pop "fuzzing". + .join("..") + // Pop "crates". + .join("..") .join("target") .join("scratch"); diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index b5de268f5d..1068cb8521 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -12,11 +12,11 @@ pub mod dummy; -use dummy::dummy_imports; +use dummy::{dummy_imports, dummy_value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use wasmtime::{Config, Engine, HostRef, Instance, Module, Store}; +use wasmtime::*; use wasmtime_environ::{isa, settings}; use wasmtime_jit::{native, CompilationStrategy, CompiledModule, Compiler, NullResolver}; @@ -83,3 +83,127 @@ pub fn compile(wasm: &[u8], compilation_strategy: CompilationStrategy) { let global_exports = Rc::new(RefCell::new(HashMap::new())); let _ = CompiledModule::new(&mut compiler, wasm, &mut resolver, global_exports, false); } + +/// Invoke the given API calls. +pub fn make_api_calls(api: crate::generators::api::ApiCalls) { + use crate::generators::api::ApiCall; + + let mut config: Option = None; + let mut engine: Option> = None; + let mut store: Option> = None; + let mut modules: HashMap> = Default::default(); + let mut instances: HashMap> = Default::default(); + + for call in api.calls { + match call { + ApiCall::ConfigNew => { + assert!(config.is_none()); + config = Some(Config::new()); + } + + ApiCall::ConfigDebugInfo(b) => { + config.as_mut().unwrap().debug_info(b); + } + + ApiCall::EngineNew => { + assert!(engine.is_none()); + engine = Some(HostRef::new(Engine::new(config.as_ref().unwrap()))); + } + + ApiCall::StoreNew => { + assert!(store.is_none()); + store = Some(HostRef::new(Store::new(engine.as_ref().unwrap()))); + } + + ApiCall::ModuleNew { id, wasm } => { + let module = HostRef::new(match Module::new(store.as_ref().unwrap(), &wasm.wasm) { + Ok(m) => m, + Err(_) => continue, + }); + let old = modules.insert(id, module); + assert!(old.is_none()); + } + + ApiCall::ModuleDrop { id } => { + drop(modules.remove(&id)); + } + + ApiCall::InstanceNew { id, module } => { + let module = match modules.get(&module) { + Some(m) => m, + None => continue, + }; + + let imports = { + let module = module.borrow(); + match dummy_imports(store.as_ref().unwrap(), module.imports()) { + Ok(imps) => imps, + Err(_) => { + // There are some value types that we can't synthesize a + // dummy value for (e.g. anyrefs) and for modules that + // import things of these types we skip instantiation. + continue; + } + } + }; + + // Don't unwrap this: there can be instantiation-/link-time errors that + // aren't caught during validation or compilation. For example, an imported + // table might not have room for an element segment that we want to + // initialize into it. + if let Ok(instance) = Instance::new(store.as_ref().unwrap(), &module, &imports) { + instances.insert(id, HostRef::new(instance)); + } + } + + ApiCall::InstanceDrop { id } => { + drop(instances.remove(&id)); + } + + ApiCall::CallExportedFunc { instance, nth } => { + let instance = match instances.get(&instance) { + Some(i) => i, + None => { + // Note that we aren't guaranteed to instantiate valid + // modules, see comments in `InstanceNew` for details on + // that. But the API call generator can't know if + // instantiation failed, so we might not actually have + // this instance. When that's the case, just skip the + // API call and keep going. + continue; + } + }; + + let funcs = { + let instance = instance.borrow(); + instance + .exports() + .iter() + .filter_map(|e| match e { + Extern::Func(f) => Some(f.clone()), + _ => None, + }) + .collect::>() + }; + + if funcs.is_empty() { + continue; + } + + let nth = nth % funcs.len(); + let f = funcs[nth].borrow(); + let ty = f.r#type(); + let params = match ty + .params() + .iter() + .map(|valty| dummy_value(valty)) + .collect::, _>>() + { + Ok(p) => p, + Err(_) => continue, + }; + let _ = f.call(¶ms); + } + } + } +} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 3fb5420242..637c730c5f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,6 +10,8 @@ cargo-fuzz = true [dependencies] arbitrary = "0.2.0" +env_logger = "0.7.1" +log = "0.4.8" wasmtime-fuzzing = { path = "../crates/fuzzing", features = ["env_logger"] } wasmtime-jit = { path = "../crates/jit" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } @@ -29,3 +31,7 @@ path = "fuzz_targets/instantiate.rs" [[bin]] name = "instantiate_translated" path = "fuzz_targets/instantiate_translated.rs" + +[[bin]] +name = "api_calls" +path = "fuzz_targets/api_calls.rs" diff --git a/fuzz/fuzz_targets/api_calls.rs b/fuzz/fuzz_targets/api_calls.rs new file mode 100755 index 0000000000..9cdc2685f8 --- /dev/null +++ b/fuzz/fuzz_targets/api_calls.rs @@ -0,0 +1,27 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::sync::Once; +use wasmtime_fuzzing::{generators::api::ApiCalls, oracles}; + +fuzz_target!(|api: ApiCalls| { + static INIT_LOGGING: Once = Once::new(); + INIT_LOGGING.call_once(|| env_logger::init()); + + log::debug!( + "If this fuzz test fails, here is a regression tests: +``` +#[test] +fn my_regression_test() {{ + use wasmtime_fuzzing::generators::{{ + api::{{ApiCall::*, ApiCalls}}, + WasmOptTtf, + }}; + wasmtime_fuzzing::oracles::make_api_calls({:#?}); +}} +```", + api + ); + + oracles::make_api_calls(api); +}); From 6750605a615bf70d79db5cb8df0793dfdd860b2b Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 10 Dec 2019 23:03:36 -0800 Subject: [PATCH 2/7] Fix AppVerifier check regarding invalid call to VirtualFree. (#697) Calls to `VirtualFree` that pass `MEM_RELEASE` must specify a size of 0 as the OS will be freeing the original range for the given base address. The calls to free `MMap` memory on Windows were silently failing because of an incorrect assertion (on Windows, `VirtualFree` returns non-zero for success). This was caught via AppVerifier while investigating a heap overrun issue on a different PR. --- crates/runtime/src/mmap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/runtime/src/mmap.rs b/crates/runtime/src/mmap.rs index 32acabbd86..a5387f8705 100644 --- a/crates/runtime/src/mmap.rs +++ b/crates/runtime/src/mmap.rs @@ -255,8 +255,8 @@ impl Drop for Mmap { use winapi::ctypes::c_void; use winapi::um::memoryapi::VirtualFree; use winapi::um::winnt::MEM_RELEASE; - let r = unsafe { VirtualFree(self.ptr as *mut c_void, self.len, MEM_RELEASE) }; - assert_eq!(r, 0); + let r = unsafe { VirtualFree(self.ptr as *mut c_void, 0, MEM_RELEASE) }; + assert_ne!(r, 0); } } } From 0cc8c6c8677982d232d71d36a89e97ff21afde93 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 11 Dec 2019 10:25:53 -0800 Subject: [PATCH 3/7] ci: run the `api_calls` fuzz target in CI and with opts and assertions (#695) We were previously not running the fuzz targets in release mode, but now we are. --- .github/workflows/main.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d00d9f7507..4dc4359ce6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,9 +82,10 @@ jobs: working-directory: ./fuzz # NB: the `-runs=0` means that libFuzzer won't generate new inputs, only run # the seeds from the corpus. - - run: cargo fuzz run compile -- -runs=0 - - run: cargo fuzz run instantiate -- -runs=0 - - run: cargo fuzz run instantiate_translated -- -runs=0 + - run: cargo fuzz run compile --release --debug-assertions -- -runs=0 + - run: cargo fuzz run instantiate --release --debug-assertions -- -runs=0 + - run: cargo fuzz run instantiate_translated --release --debug-assertions -- -runs=0 + - run: cargo fuzz run api_calls --release --debug-assertions -- -runs=0 # Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly # channels of Rust as well as macOS/Linux/Windows. From ddd23000106853a62a0159b00a8f7fbb509c0917 Mon Sep 17 00:00:00 2001 From: XAMPPRocky <4464295+XAMPPRocky@users.noreply.github.com> Date: Wed, 11 Dec 2019 19:46:45 +0100 Subject: [PATCH 4/7] Document Callable, Trap, HostRef, and ValType (#693) --- crates/api/src/callable.rs | 77 ++++++++++++++++++++++++++++++++++++++ crates/api/src/ref.rs | 31 +++++++++++++++ crates/api/src/trap.rs | 19 ++++++++-- crates/api/src/types.rs | 19 ++++++++-- crates/api/tests/traps.rs | 2 +- 5 files changed, 140 insertions(+), 8 deletions(-) diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 84026ca3d5..c06b76d62b 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -9,7 +9,84 @@ use wasmtime_environ::ir; use wasmtime_jit::InstanceHandle; use wasmtime_runtime::Export; +/// A trait representing a function that can be imported and called from inside +/// WebAssembly. +/// # Example +/// ``` +/// use wasmtime::{HostRef, Val}; +/// +/// struct TimesTwo; +/// +/// impl wasmtime::Callable for TimesTwo { +/// fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef> { +/// let mut value = params[0].unwrap_i32(); +/// value *= 2; +/// results[0] = value.into(); +/// +/// Ok(()) +/// } +/// } +/// +/// # fn main () -> Result<(), Box> { +/// // Simple module that imports our host function ("times_two") and re-exports +/// // it as "run". +/// let binary = wat::parse_str(r#" +/// (module +/// (func $times_two (import "" "times_two") (param i32) (result i32)) +/// (func +/// (export "run") +/// (param i32) +/// (result i32) +/// (local.get 0) +/// (call $times_two)) +/// ) +/// "#)?; +/// +/// // Initialise environment and our module. +/// let engine = HostRef::new(wasmtime::Engine::default()); +/// let store = HostRef::new(wasmtime::Store::new(&engine)); +/// let module = HostRef::new(wasmtime::Module::new(&store, &binary)?); +/// +/// // Define the type of the function we're going to call. +/// let times_two_type = wasmtime::FuncType::new( +/// // Parameters +/// Box::new([wasmtime::ValType::I32]), +/// // Results +/// Box::new([wasmtime::ValType::I32]) +/// ); +/// +/// // Build a reference to the "times_two" function that can be used. +/// let times_two_function = HostRef::new( +/// wasmtime::Func::new(&store, times_two_type, std::rc::Rc::new(TimesTwo)) +/// ); +/// +/// // Create module instance that imports our function +/// let instance = wasmtime::Instance::new( +/// &store, +/// &module, +/// &[times_two_function.into()] +/// )?; +/// +/// // Get "run" function from the exports. +/// let run_function = instance.exports()[0].func().unwrap(); +/// +/// // Borrow and call "run". Returning any error message from Wasm as a string. +/// let original = 5i32; +/// let results = run_function +/// .borrow() +/// .call(&[original.into()]) +/// .map_err(|trap| trap.borrow().to_string())?; +/// +/// // Compare that the results returned matches what we expect. +/// assert_eq!(original * 2, results[0].unwrap_i32()); +/// # Ok(()) +/// # } +/// ``` pub trait Callable { + /// What is called when the function is invoked in WebAssembly. + /// `params` is an immutable list of parameters provided to the function. + /// `results` is mutable list of results to be potentially set by your + /// function. Produces a `Trap` if the function encounters any errors. fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef>; } diff --git a/crates/api/src/ref.rs b/crates/api/src/ref.rs index c6010c0624..52f14e6a83 100644 --- a/crates/api/src/ref.rs +++ b/crates/api/src/ref.rs @@ -46,14 +46,19 @@ impl Drop for AnyAndHostInfo { #[derive(Clone)] pub struct OtherRef(Rc>); +/// Represents an opaque reference to any data within WebAssembly. #[derive(Clone)] pub enum AnyRef { + /// A reference to no data. Null, + /// A reference to data stored internally in `wasmtime`. Ref(InternalRef), + /// A reference to data located outside of `wasmtime`. Other(OtherRef), } impl AnyRef { + /// Creates a new instance of `AnyRef` from `Box`. pub fn new(data: Box) -> Self { let info = AnyAndHostInfo { any: data, @@ -62,10 +67,14 @@ impl AnyRef { AnyRef::Other(OtherRef(Rc::new(RefCell::new(info)))) } + /// Creates a `Null` reference. pub fn null() -> Self { AnyRef::Null } + /// Returns the data stored in the reference if available. + /// # Panics + /// Panics if the variant isn't `AnyRef::Other`. pub fn data(&self) -> cell::Ref> { match self { AnyRef::Other(OtherRef(r)) => cell::Ref::map(r.borrow(), |r| &r.any), @@ -73,6 +82,8 @@ impl AnyRef { } } + /// Returns true if the two `AnyRef`'s point to the same value (not just + /// values that compare as equal). pub fn ptr_eq(&self, other: &AnyRef) -> bool { match (self, other) { (AnyRef::Null, AnyRef::Null) => true, @@ -84,6 +95,9 @@ impl AnyRef { } } + /// Returns a mutable reference to the host information if available. + /// # Panics + /// Panics if `AnyRef` is already borrowed or `AnyRef` is `Null`. pub fn host_info(&self) -> Option>> { match self { AnyRef::Null => panic!("null"), @@ -98,6 +112,9 @@ impl AnyRef { } } + /// Sets the host information for an `AnyRef`. + /// # Panics + /// Panics if `AnyRef` is already borrowed or `AnyRef` is `Null`. pub fn set_host_info(&self, info: Option>) { match self { AnyRef::Null => panic!("null"), @@ -133,9 +150,11 @@ impl Drop for ContentBox { } } +/// Represents a piece of data located in the host environment. pub struct HostRef(Rc>>); impl HostRef { + /// Creates a new `HostRef` from `T`. pub fn new(item: T) -> HostRef { let anyref_data: Weak> = Weak::new(); let content = ContentBox { @@ -146,18 +165,30 @@ impl HostRef { HostRef(Rc::new(RefCell::new(content))) } + /// Immutably borrows the wrapped data. + /// # Panics + /// Panics if the value is currently mutably borrowed. pub fn borrow(&self) -> cell::Ref { cell::Ref::map(self.0.borrow(), |b| &b.content) } + /// Mutably borrows the wrapped data. + /// # Panics + /// Panics if the `HostRef` is already borrowed. pub fn borrow_mut(&self) -> cell::RefMut { cell::RefMut::map(self.0.borrow_mut(), |b| &mut b.content) } + /// Returns true if the two `HostRef`'s point to the same value (not just + /// values that compare as equal). pub fn ptr_eq(&self, other: &HostRef) -> bool { Rc::ptr_eq(&self.0, &other.0) } + /// Returns an opaque reference to the wrapped data in the form of + /// an `AnyRef`. + /// # Panics + /// Panics if `HostRef` is already mutably borrowed. pub fn anyref(&self) -> AnyRef { let r = self.0.borrow_mut().anyref_data.upgrade(); if let Some(r) = r { diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index 77628138bd..dff98354d6 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -1,5 +1,7 @@ use thiserror::Error; +/// A struct representing an aborted instruction execution, with a message +/// indicating the cause. #[derive(Error, Debug)] #[error("Wasm trap: {message}")] pub struct Trap { @@ -7,14 +9,25 @@ pub struct Trap { } impl Trap { - pub fn new(message: String) -> Trap { - Trap { message } + /// Creates a new `Trap` with `message`. + /// # Example + /// ``` + /// let trap = wasmtime::Trap::new("unexpected error"); + /// assert_eq!("unexpected error", trap.message()); + /// ``` + pub fn new>(message: I) -> Trap { + Self { + message: message.into(), + } } + /// Create a `Trap` without defining a message for the trap. Mostly useful + /// for prototypes and tests. pub fn fake() -> Trap { - Trap::new("TODO trap".to_string()) + Self::new("TODO trap") } + /// Returns a reference the `message` stored in `Trap`. pub fn message(&self) -> &str { &self.message } diff --git a/crates/api/src/types.rs b/crates/api/src/types.rs index 2084985f87..36fde8ff20 100644 --- a/crates/api/src/types.rs +++ b/crates/api/src/types.rs @@ -39,7 +39,7 @@ impl Limits { self.min } - /// Returs the maximum amount for these limits, if specified. + /// Returns the maximum amount for these limits, if specified. pub fn max(&self) -> Option { self.max } @@ -47,18 +47,28 @@ impl Limits { // Value Types +/// A list of all possible value types in WebAssembly. #[derive(Debug, Clone, Eq, PartialEq)] pub enum ValType { + /// Signed 32 bit integer. I32, + /// Signed 64 bit integer. I64, + /// Floating point 32 bit integer. F32, + /// Floating point 64 bit integer. F64, + /// A 128 bit number. V128, + /// A reference to opaque data in the Wasm instance. AnyRef, /* = 128 */ + /// A reference to a Wasm function. FuncRef, } impl ValType { + /// Returns true if `ValType` matches any of the numeric types. (e.g. `I32`, + /// `I64`, `F32`, `F64`). pub fn is_num(&self) -> bool { match self { ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => true, @@ -66,6 +76,7 @@ impl ValType { } } + /// Returns true if `ValType` matches either of the reference types. pub fn is_ref(&self) -> bool { match self { ValType::AnyRef | ValType::FuncRef => true, @@ -113,8 +124,8 @@ pub enum ExternType { macro_rules! accessors { ($(($variant:ident($ty:ty) $get:ident $unwrap:ident))*) => ($( - /// Attempt to return the underlying type of this external type, - /// returning `None` if it is a different type. + /// Attempt to return the underlying type of this external type, + /// returning `None` if it is a different type. pub fn $get(&self) -> Option<&$ty> { if let ExternType::$variant(e) = self { Some(e) @@ -123,7 +134,7 @@ macro_rules! accessors { } } - /// Returns the underlying descriptor of this [`ExternType`], panicking + /// Returns the underlying descriptor of this [`ExternType`], panicking /// if it is a different type. /// /// # Panics diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index 2e53d1b898..7ead7b322c 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -8,7 +8,7 @@ fn test_trap_return() -> Result<(), String> { impl Callable for HelloCallback { fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), HostRef> { - Err(HostRef::new(Trap::new("test 123".into()))) + Err(HostRef::new(Trap::new("test 123"))) } } From 4e67ccfbc36e7e9e1b008d74c3356160c759368f Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 11 Dec 2019 16:09:30 -0800 Subject: [PATCH 5/7] Fix WASI test program running. PR #585 moved the execution of a `_start` function to the CLI rather than have it automatically invoked by module instantiation. Unfortunately, this broke the WASI test programs that were relying on this behavior from instantiation. This fixes it by adding an invocation of the `_start` function in the test runner. Fixes #698. --- .../test-programs/tests/wasm_tests/runtime.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/test-programs/tests/wasm_tests/runtime.rs b/crates/test-programs/tests/wasm_tests/runtime.rs index ea631eaab1..5de74c0f3a 100644 --- a/crates/test-programs/tests/wasm_tests/runtime.rs +++ b/crates/test-programs/tests/wasm_tests/runtime.rs @@ -100,11 +100,27 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any } }) .collect::, _>>()?; - let _ = HostRef::new(Instance::new(&store, &module, &imports).context(format!( + + let instance = HostRef::new(Instance::new(&store, &module, &imports).context(format!( "error while instantiating Wasm module '{}'", bin_name, ))?); + let export = instance + .borrow() + .find_export_by_name("_start") + .context("expected a _start export")? + .clone(); + + if let Err(trap) = export + .func() + .context("expected export to be a func")? + .borrow() + .call(&[]) + { + bail!("trapped: {:?}", trap.borrow()); + } + Ok(()) } From 95c2addf15153c720da1402eec5e570e8f8ad646 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 12 Dec 2019 01:25:13 +0100 Subject: [PATCH 6/7] Compile wasi-common to Emscripten (#688) * Compile wasi-common to Emscripten This commit enables cross-compiling of `wasi-common` to Emscripten. To achieve this, this commit does quite a bit reshuffling in the existing codebase. Namely, * rename `linux` modules in `wasi-common` and `yanix` to `linux_like` -- this is needed so that we can separate out logic specific to Linux and Emscripten out * tweak `dir` module in `yanix` to support Emscripten -- in particular, the main change involves `SeekLoc::from_raw` which has to be now host-specific, and is now fallible * tweak `filetime` so that in Emscripten we never check for existence of `utimensat` at runtime since we are guaranteed for it to exist by design * since `utimes` and `futimes` are not present in Emscripten, move them into a separate module, `utimesat`, and tag it cfg-non-emscripten only * finally, `to_timespec` is now fallible since on Emscripten we have to cast number of seconds, `FileTime::seconds` from `i64` to `libc::c_long` which resolves to `i32` unlike on other nixes * Fix macos build * Verify wasi-common compiles to Emscripten This commit adds `emscripten` job to Github Actions which installs `wasm32-unknown-emscripten` target, and builds `wasi-common` crate. * Use #[path] to cherry-pick mods for Emscripten This commit effectively reverses the reorg introduced in 145f4a5 in that it ditches `linux_like` mod for separate mods `linux` and `emscripten` which are now on the same crate level, and instead, pulls in common bits from `linux` using the `#[path = ..]` proc macro. --- .github/workflows/main.yml | 13 ++ crates/wasi-common/src/error.rs | 6 + .../wasi-common/src/old/snapshot_0/error.rs | 6 + .../old/snapshot_0/sys/unix/bsd/filetime.rs | 12 +- .../old/snapshot_0/sys/unix/bsd/host_impl.rs | 16 +++ .../src/old/snapshot_0/sys/unix/bsd/mod.rs | 18 +-- .../sys/unix/emscripten/filetime.rs | 36 +++++ .../sys/unix/emscripten/host_impl.rs | 15 ++ .../old/snapshot_0/sys/unix/emscripten/mod.rs | 6 + .../old/snapshot_0/sys/unix/fdentry_impl.rs | 17 +-- .../src/old/snapshot_0/sys/unix/filetime.rs | 136 ++++-------------- .../src/old/snapshot_0/sys/unix/host_impl.rs | 19 +-- .../snapshot_0/sys/unix/hostcalls_impl/fs.rs | 21 +-- .../old/snapshot_0/sys/unix/linux/filetime.rs | 10 +- .../snapshot_0/sys/unix/linux/host_impl.rs | 16 +++ .../src/old/snapshot_0/sys/unix/linux/mod.rs | 16 +-- .../old/snapshot_0/sys/unix/linux/utimesat.rs | 88 ++++++++++++ .../src/old/snapshot_0/sys/unix/mod.rs | 28 ++-- .../wasi-common/src/sys/unix/bsd/filetime.rs | 12 +- .../wasi-common/src/sys/unix/bsd/host_impl.rs | 12 ++ crates/wasi-common/src/sys/unix/bsd/mod.rs | 18 +-- .../src/sys/unix/emscripten/filetime.rs | 36 +++++ .../src/sys/unix/emscripten/mod.rs | 7 + .../wasi-common/src/sys/unix/fdentry_impl.rs | 17 +-- crates/wasi-common/src/sys/unix/filetime.rs | 136 ++++-------------- crates/wasi-common/src/sys/unix/host_impl.rs | 17 +-- .../src/sys/unix/hostcalls_impl/fs.rs | 21 +-- .../src/sys/unix/linux/filetime.rs | 10 +- .../src/sys/unix/linux/host_impl.rs | 11 ++ crates/wasi-common/src/sys/unix/linux/mod.rs | 16 +-- .../src/sys/unix/linux/utimesat.rs | 88 ++++++++++++ crates/wasi-common/src/sys/unix/mod.rs | 28 ++-- crates/wasi-common/yanix/src/dir.rs | 6 +- crates/wasi-common/yanix/src/sys/bsd/dir.rs | 11 +- .../yanix/src/sys/emscripten/mod.rs | 16 +++ crates/wasi-common/yanix/src/sys/linux/dir.rs | 2 +- crates/wasi-common/yanix/src/sys/linux/mod.rs | 9 ++ crates/wasi-common/yanix/src/sys/mod.rs | 23 +-- 38 files changed, 541 insertions(+), 434 deletions(-) create mode 100644 crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/host_impl.rs create mode 100644 crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/filetime.rs create mode 100644 crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/host_impl.rs create mode 100644 crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/mod.rs create mode 100644 crates/wasi-common/src/old/snapshot_0/sys/unix/linux/host_impl.rs create mode 100644 crates/wasi-common/src/old/snapshot_0/sys/unix/linux/utimesat.rs create mode 100644 crates/wasi-common/src/sys/unix/bsd/host_impl.rs create mode 100644 crates/wasi-common/src/sys/unix/emscripten/filetime.rs create mode 100644 crates/wasi-common/src/sys/unix/emscripten/mod.rs create mode 100644 crates/wasi-common/src/sys/unix/linux/host_impl.rs create mode 100644 crates/wasi-common/src/sys/unix/linux/utimesat.rs create mode 100644 crates/wasi-common/yanix/src/sys/emscripten/mod.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4dc4359ce6..7d07592d9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,6 +87,19 @@ jobs: - run: cargo fuzz run instantiate_translated --release --debug-assertions -- -runs=0 - run: cargo fuzz run api_calls --release --debug-assertions -- -runs=0 + # Install wasm32-unknown-emscripten target, and ensure `crates/wasi-common` + # compiles to Emscripten. + emscripten: + name: Emscripten + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: true + - uses: ./.github/actions/install-rust + - run: rustup target add wasm32-unknown-emscripten + - run: cargo build --target wasm32-unknown-emscripten -p wasi-common + # Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly # channels of Rust as well as macOS/Linux/Windows. test: diff --git a/crates/wasi-common/src/error.rs b/crates/wasi-common/src/error.rs index f6fa7be1da..1707f180eb 100644 --- a/crates/wasi-common/src/error.rs +++ b/crates/wasi-common/src/error.rs @@ -149,6 +149,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: ffi::NulError) -> Self { + Self::Wasi(WasiError::EILSEQ) + } +} + impl From<&ffi::NulError> for Error { fn from(_: &ffi::NulError) -> Self { Self::Wasi(WasiError::EILSEQ) diff --git a/crates/wasi-common/src/old/snapshot_0/error.rs b/crates/wasi-common/src/old/snapshot_0/error.rs index 33dce550f5..54185df5d8 100644 --- a/crates/wasi-common/src/old/snapshot_0/error.rs +++ b/crates/wasi-common/src/old/snapshot_0/error.rs @@ -149,6 +149,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: ffi::NulError) -> Self { + Self::Wasi(WasiError::EILSEQ) + } +} + impl From<&ffi::NulError> for Error { fn from(_: &ffi::NulError) -> Self { Self::Wasi(WasiError::EILSEQ) diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/filetime.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/filetime.rs index 8825e97a66..943aba9f7b 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/filetime.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/filetime.rs @@ -1,6 +1,6 @@ //! This internal module consists of helper types and functions for dealing //! with setting the file times specific to BSD-style *nixes. -use super::super::filetime::FileTime; +use crate::old::snapshot_0::{sys::unix::filetime::FileTime, Result}; use cfg_if::cfg_if; use std::ffi::CStr; use std::fs::File; @@ -41,8 +41,8 @@ pub(crate) fn utimensat( atime: FileTime, mtime: FileTime, symlink_nofollow: bool, -) -> io::Result<()> { - use super::super::filetime::{to_timespec, utimesat}; +) -> Result<()> { + use crate::old::snapshot_0::sys::unix::filetime::to_timespec; use std::ffi::CString; use std::os::unix::prelude::*; @@ -56,16 +56,16 @@ pub(crate) fn utimensat( }; let p = CString::new(path.as_bytes())?; - let times = [to_timespec(&atime), to_timespec(&mtime)]; + let times = [to_timespec(&atime)?, to_timespec(&mtime)?]; let rc = unsafe { func(dirfd.as_raw_fd(), p.as_ptr(), times.as_ptr(), flags) }; if rc == 0 { return Ok(()); } else { - return Err(io::Error::last_os_error()); + return Err(io::Error::last_os_error().into()); } } - utimesat(dirfd, path, atime, mtime, symlink_nofollow) + super::utimesat::utimesat(dirfd, path, atime, mtime, symlink_nofollow) } /// Wraps `fetch` specifically targetting `utimensat` symbol. If the symbol exists diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/host_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/host_impl.rs new file mode 100644 index 0000000000..b151e703e1 --- /dev/null +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/host_impl.rs @@ -0,0 +1,16 @@ +use crate::old::snapshot_0::{wasi, Result}; +use std::convert::TryFrom; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; + +pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { + wasi::__wasi_device_t::try_from(dev).map_err(Into::into) +} + +pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { + wasi::__wasi_device_t::try_from(ino).map_err(Into::into) +} + +pub(crate) fn stnlink_from_nix(nlink: libc::nlink_t) -> Result { + Ok(nlink.into()) +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs index 7b14ae69ad..39a4046a74 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs @@ -1,18 +1,6 @@ pub(crate) mod filetime; +pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; - -pub(crate) mod host_impl { - use crate::old::snapshot_0::{wasi, Result}; - use std::convert::TryFrom; - - pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; - - pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { - wasi::__wasi_device_t::try_from(dev).map_err(Into::into) - } - - pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { - wasi::__wasi_device_t::try_from(ino).map_err(Into::into) - } -} +#[path = "../linux/utimesat.rs"] +pub(crate) mod utimesat; diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/filetime.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/filetime.rs new file mode 100644 index 0000000000..adc48a3493 --- /dev/null +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/filetime.rs @@ -0,0 +1,36 @@ +//! This internal module consists of helper types and functions for dealing +//! with setting the file times specific to Emscripten. +use crate::old::snapshot_0::{sys::unix::filetime::FileTime, Result}; +use std::fs::File; +use std::io; + +pub(crate) const UTIME_NOW: i32 = 1_073_741_823; +pub(crate) const UTIME_OMIT: i32 = 1_073_741_822; + +/// Wrapper for `utimensat` syscall. In Emscripten, there is no point in dynamically resolving +/// if `utimensat` is available as it always was and will be. +pub(crate) fn utimensat( + dirfd: &File, + path: &str, + atime: FileTime, + mtime: FileTime, + symlink_nofollow: bool, +) -> Result<()> { + use crate::old::snapshot_0::sys::unix::filetime::to_timespec; + use std::ffi::CString; + use std::os::unix::prelude::*; + + let flags = if symlink_nofollow { + libc::AT_SYMLINK_NOFOLLOW + } else { + 0 + }; + let p = CString::new(path.as_bytes())?; + let times = [to_timespec(&atime)?, to_timespec(&mtime)?]; + let rc = unsafe { libc::utimensat(dirfd.as_raw_fd(), p.as_ptr(), times.as_ptr(), flags) }; + if rc == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/host_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/host_impl.rs new file mode 100644 index 0000000000..412280a519 --- /dev/null +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/host_impl.rs @@ -0,0 +1,15 @@ +use crate::old::snapshot_0::{wasi, Result}; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; + +pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { + Ok(wasi::__wasi_device_t::from(dev)) +} + +pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { + Ok(wasi::__wasi_device_t::from(ino)) +} + +pub(crate) fn stnlink_from_nix(nlink: libc::nlink_t) -> Result { + Ok(nlink) +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/mod.rs new file mode 100644 index 0000000000..c9c3841b9a --- /dev/null +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/emscripten/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod filetime; +pub(crate) mod host_impl; +#[path = "../linux/hostcalls_impl.rs"] +pub(crate) mod hostcalls_impl; +#[path = "../linux/oshandle.rs"] +pub(crate) mod oshandle; diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs index 5adaf22f30..a46177c3c5 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs @@ -1,24 +1,11 @@ use crate::old::snapshot_0::fdentry::{Descriptor, OsHandleRef}; -use crate::old::snapshot_0::{wasi, Error, Result}; +use crate::old::snapshot_0::{sys::unix::sys_impl, wasi, Error, Result}; use std::fs::File; use std::io; use std::mem::ManuallyDrop; use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd}; -cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::linux::oshandle::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::bsd::oshandle::*; - } -} +pub(crate) use sys_impl::oshandle::*; impl AsRawFd for Descriptor { fn as_raw_fd(&self) -> RawFd { diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/filetime.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/filetime.rs index 061537b3b8..15530e1f0c 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/filetime.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/filetime.rs @@ -6,21 +6,30 @@ //! Kudos @alexcrichton! //! //! [filetime]: https://github.com/alexcrichton/filetime -use std::fs::{self, File}; -use std::io; +use crate::old::snapshot_0::Result; +use std::convert::TryInto; + +pub(crate) use super::sys_impl::filetime::*; cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::linux::filetime::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::bsd::filetime::*; + if #[cfg(not(target_os = "emscripten"))] { + fn filetime_to_timespec(ft: &filetime::FileTime) -> Result { + Ok( + libc::timespec { + tv_sec: ft.seconds(), + tv_nsec: ft.nanoseconds().try_into()?, + } + ) + } + } else { + fn filetime_to_timespec(ft: &filetime::FileTime) -> Result { + Ok( + libc::timespec { + tv_sec: ft.seconds().try_into()?, + tv_nsec: ft.nanoseconds().try_into()?, + } + ) + } } } @@ -35,91 +44,6 @@ pub(crate) enum FileTime { FileTime(filetime::FileTime), } -/// For a provided pair of access and modified `FileTime`s, converts the input to -/// `filetime::FileTime` used later in `utimensat` function. For variants `FileTime::Now` -/// and `FileTime::Omit`, this function will make two syscalls: either accessing current -/// system time, or accessing the file's metadata. -/// -/// The original implementation can be found here: [filetime::unix::get_times]. -/// -/// [filetime::unix::get_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L42 -fn get_times( - atime: FileTime, - mtime: FileTime, - current: impl Fn() -> io::Result, -) -> io::Result<(filetime::FileTime, filetime::FileTime)> { - use std::time::SystemTime; - - let atime = match atime { - FileTime::Now => { - let time = SystemTime::now(); - filetime::FileTime::from_system_time(time) - } - FileTime::Omit => { - let meta = current()?; - filetime::FileTime::from_last_access_time(&meta) - } - FileTime::FileTime(ft) => ft, - }; - - let mtime = match mtime { - FileTime::Now => { - let time = SystemTime::now(); - filetime::FileTime::from_system_time(time) - } - FileTime::Omit => { - let meta = current()?; - filetime::FileTime::from_last_modification_time(&meta) - } - FileTime::FileTime(ft) => ft, - }; - - Ok((atime, mtime)) -} - -/// Combines `openat` with `utimes` to emulate `utimensat` on platforms where it is -/// not available. The logic for setting file times is based on [filetime::unix::set_file_handles_times]. -/// -/// [filetime::unix::set_file_handles_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L24 -pub(crate) fn utimesat( - dirfd: &File, - path: &str, - atime: FileTime, - mtime: FileTime, - symlink_nofollow: bool, -) -> io::Result<()> { - use std::ffi::CString; - use std::os::unix::prelude::*; - // emulate *at syscall by reading the path from a combination of - // (fd, path) - let p = CString::new(path.as_bytes())?; - let mut flags = libc::O_RDWR; - if symlink_nofollow { - flags |= libc::O_NOFOLLOW; - } - let fd = unsafe { libc::openat(dirfd.as_raw_fd(), p.as_ptr(), flags) }; - let f = unsafe { File::from_raw_fd(fd) }; - let (atime, mtime) = get_times(atime, mtime, || f.metadata())?; - let times = [to_timeval(atime), to_timeval(mtime)]; - let rc = unsafe { libc::futimes(f.as_raw_fd(), times.as_ptr()) }; - return if rc == 0 { - Ok(()) - } else { - Err(io::Error::last_os_error()) - }; -} - -/// Converts `filetime::FileTime` to `libc::timeval`. This function was taken directly from -/// [filetime] crate. -/// -/// [filetime]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L93 -fn to_timeval(ft: filetime::FileTime) -> libc::timeval { - libc::timeval { - tv_sec: ft.seconds(), - tv_usec: (ft.nanoseconds() / 1000) as libc::suseconds_t, - } -} - /// Converts `FileTime` to `libc::timespec`. If `FileTime::Now` variant is specified, this /// resolves to `UTIME_NOW` special const, `FileTime::Omit` variant resolves to `UTIME_OMIT`, and /// `FileTime::FileTime(ft)` where `ft := filetime::FileTime` uses [filetime] crate's original @@ -127,8 +51,8 @@ fn to_timeval(ft: filetime::FileTime) -> libc::timeval { /// /// [filetime]: https://github.com/alexcrichton/filetime /// [filetime::unix::to_timespec]: https://github.com/alexcrichton/filetime/blob/master/src/unix/mod.rs#L30 -pub(crate) fn to_timespec(ft: &FileTime) -> libc::timespec { - match ft { +pub(crate) fn to_timespec(ft: &FileTime) -> Result { + let ts = match ft { FileTime::Now => libc::timespec { tv_sec: 0, tv_nsec: UTIME_NOW, @@ -137,13 +61,7 @@ pub(crate) fn to_timespec(ft: &FileTime) -> libc::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT, }, - // `filetime::FileTime`'s fields are normalised by definition. `ft.seconds()` return the number - // of whole seconds, while `ft.nanoseconds()` returns only fractional part expressed in - // nanoseconds, as underneath it uses `std::time::Duration::subsec_nanos` to populate the - // `filetime::FileTime::nanoseconds` field. It is, therefore, OK to do an `as` cast here. - FileTime::FileTime(ft) => libc::timespec { - tv_sec: ft.seconds(), - tv_nsec: ft.nanoseconds() as _, - }, - } + FileTime::FileTime(ft) => filetime_to_timespec(ft)?, + }; + Ok(ts) } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs index cd15382587..eb0bf33689 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs @@ -3,7 +3,7 @@ #![allow(non_snake_case)] #![allow(dead_code)] use crate::old::snapshot_0::host::FileType; -use crate::old::snapshot_0::{helpers, wasi, Error, Result}; +use crate::old::snapshot_0::{helpers, sys::unix::sys_impl, wasi, Error, Result}; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use yanix::{ @@ -11,20 +11,7 @@ use yanix::{ Errno, }; -cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::linux::host_impl::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::bsd::host_impl::*; - } -} +pub(crate) use sys_impl::host_impl::*; pub(crate) fn errno_from_nix(errno: Errno) -> Error { match errno { @@ -197,7 +184,7 @@ pub(crate) fn filestat_from_nix(filestat: libc::stat) -> Result( dir.rewind(); } else { log::trace!(" | fd_readdir: doing seekdir to {}", cookie); - let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; + let loc = unsafe { SeekLoc::from_raw(cookie as i64)? }; dir.seek(loc); } @@ -356,7 +343,7 @@ pub(crate) fn fd_readdir<'a>( .to_owned(), ino: entry.ino(), ftype: entry.file_type().into(), - cookie: entry.seek_loc().to_raw().try_into()?, + cookie: entry.seek_loc()?.to_raw().try_into()?, }) })) } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs index 9fb0516b52..a286874413 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs @@ -1,6 +1,6 @@ //! This internal module consists of helper types and functions for dealing //! with setting the file times specific to Linux. -use super::super::filetime::FileTime; +use crate::old::snapshot_0::{sys::unix::filetime::FileTime, Result}; use std::fs::File; use std::io; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; @@ -20,8 +20,8 @@ pub(crate) fn utimensat( atime: FileTime, mtime: FileTime, symlink_nofollow: bool, -) -> io::Result<()> { - use super::super::filetime::{to_timespec, utimesat}; +) -> Result<()> { + use crate::old::snapshot_0::sys::unix::filetime::to_timespec; use std::ffi::CString; use std::os::unix::prelude::*; @@ -53,9 +53,9 @@ pub(crate) fn utimensat( if err.raw_os_error() == Some(libc::ENOSYS) { INVALID.store(true, Relaxed); } else { - return Err(err); + return Err(err.into()); } } - utimesat(dirfd, path, atime, mtime, symlink_nofollow) + super::utimesat::utimesat(dirfd, path, atime, mtime, symlink_nofollow) } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/host_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/host_impl.rs new file mode 100644 index 0000000000..d867e51b6f --- /dev/null +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/host_impl.rs @@ -0,0 +1,16 @@ +use crate::old::snapshot_0::{wasi, Result}; +use std::convert::TryInto; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; + +pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { + Ok(wasi::__wasi_device_t::from(dev)) +} + +pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { + Ok(wasi::__wasi_device_t::from(ino)) +} + +pub(crate) fn stnlink_from_nix(nlink: libc::nlink_t) -> Result { + nlink.try_into().map_err(Into::into) +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs index c177e9d6e4..0b4b8fd0b9 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs @@ -1,17 +1,5 @@ pub(crate) mod filetime; +pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; - -pub(crate) mod host_impl { - use crate::old::snapshot_0::{wasi, Result}; - - pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; - - pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { - Ok(wasi::__wasi_device_t::from(dev)) - } - - pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { - Ok(wasi::__wasi_device_t::from(ino)) - } -} +pub(crate) mod utimesat; diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/utimesat.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/utimesat.rs new file mode 100644 index 0000000000..155d994578 --- /dev/null +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/utimesat.rs @@ -0,0 +1,88 @@ +use crate::old::snapshot_0::sys::unix::filetime::FileTime; +use crate::old::snapshot_0::Result; +use std::{fs, io}; + +/// Combines `openat` with `utimes` to emulate `utimensat` on platforms where it is +/// not available. The logic for setting file times is based on [filetime::unix::set_file_handles_times]. +/// +/// [filetime::unix::set_file_handles_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L24 +pub(crate) fn utimesat( + dirfd: &fs::File, + path: &str, + atime: FileTime, + mtime: FileTime, + symlink_nofollow: bool, +) -> Result<()> { + use std::ffi::CString; + use std::os::unix::prelude::*; + // emulate *at syscall by reading the path from a combination of + // (fd, path) + let p = CString::new(path.as_bytes())?; + let mut flags = libc::O_RDWR; + if symlink_nofollow { + flags |= libc::O_NOFOLLOW; + } + let fd = unsafe { libc::openat(dirfd.as_raw_fd(), p.as_ptr(), flags) }; + let f = unsafe { fs::File::from_raw_fd(fd) }; + let (atime, mtime) = get_times(atime, mtime, || f.metadata().map_err(Into::into))?; + let times = [to_timeval(atime), to_timeval(mtime)]; + let rc = unsafe { libc::futimes(f.as_raw_fd(), times.as_ptr()) }; + if rc == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } +} + +/// Converts `filetime::FileTime` to `libc::timeval`. This function was taken directly from +/// [filetime] crate. +/// +/// [filetime]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L93 +fn to_timeval(ft: filetime::FileTime) -> libc::timeval { + libc::timeval { + tv_sec: ft.seconds(), + tv_usec: (ft.nanoseconds() / 1000) as libc::suseconds_t, + } +} + +/// For a provided pair of access and modified `FileTime`s, converts the input to +/// `filetime::FileTime` used later in `utimensat` function. For variants `FileTime::Now` +/// and `FileTime::Omit`, this function will make two syscalls: either accessing current +/// system time, or accessing the file's metadata. +/// +/// The original implementation can be found here: [filetime::unix::get_times]. +/// +/// [filetime::unix::get_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L42 +fn get_times( + atime: FileTime, + mtime: FileTime, + current: impl Fn() -> Result, +) -> Result<(filetime::FileTime, filetime::FileTime)> { + use std::time::SystemTime; + + let atime = match atime { + FileTime::Now => { + let time = SystemTime::now(); + filetime::FileTime::from_system_time(time) + } + FileTime::Omit => { + let meta = current()?; + filetime::FileTime::from_last_access_time(&meta) + } + FileTime::FileTime(ft) => ft, + }; + + let mtime = match mtime { + FileTime::Now => { + let time = SystemTime::now(); + filetime::FileTime::from_system_time(time) + } + FileTime::Omit => { + let meta = current()?; + filetime::FileTime::from_last_modification_time(&meta) + } + FileTime::FileTime(ft) => ft, + }; + + Ok((atime, mtime)) +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs index 3a4db31aea..723c878950 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs @@ -4,17 +4,23 @@ pub(crate) mod hostcalls_impl; mod filetime; -#[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" -))] -mod bsd; -#[cfg(target_os = "linux")] -mod linux; +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + use self::linux as sys_impl; + } else if #[cfg(target_os = "emscripten")] { + mod emscripten; + use self::emscripten as sys_impl; + } else if #[cfg(any(target_os = "macos", + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "ios", + target_os = "dragonfly"))] { + mod bsd; + use self::bsd as sys_impl; + } +} use crate::old::snapshot_0::Result; use std::fs::{File, OpenOptions}; diff --git a/crates/wasi-common/src/sys/unix/bsd/filetime.rs b/crates/wasi-common/src/sys/unix/bsd/filetime.rs index 8825e97a66..4fb40c926b 100644 --- a/crates/wasi-common/src/sys/unix/bsd/filetime.rs +++ b/crates/wasi-common/src/sys/unix/bsd/filetime.rs @@ -1,6 +1,6 @@ //! This internal module consists of helper types and functions for dealing //! with setting the file times specific to BSD-style *nixes. -use super::super::filetime::FileTime; +use crate::{sys::unix::filetime::FileTime, Result}; use cfg_if::cfg_if; use std::ffi::CStr; use std::fs::File; @@ -41,8 +41,8 @@ pub(crate) fn utimensat( atime: FileTime, mtime: FileTime, symlink_nofollow: bool, -) -> io::Result<()> { - use super::super::filetime::{to_timespec, utimesat}; +) -> Result<()> { + use crate::sys::unix::filetime::to_timespec; use std::ffi::CString; use std::os::unix::prelude::*; @@ -56,16 +56,16 @@ pub(crate) fn utimensat( }; let p = CString::new(path.as_bytes())?; - let times = [to_timespec(&atime), to_timespec(&mtime)]; + let times = [to_timespec(&atime)?, to_timespec(&mtime)?]; let rc = unsafe { func(dirfd.as_raw_fd(), p.as_ptr(), times.as_ptr(), flags) }; if rc == 0 { return Ok(()); } else { - return Err(io::Error::last_os_error()); + return Err(io::Error::last_os_error().into()); } } - utimesat(dirfd, path, atime, mtime, symlink_nofollow) + super::utimesat::utimesat(dirfd, path, atime, mtime, symlink_nofollow) } /// Wraps `fetch` specifically targetting `utimensat` symbol. If the symbol exists diff --git a/crates/wasi-common/src/sys/unix/bsd/host_impl.rs b/crates/wasi-common/src/sys/unix/bsd/host_impl.rs new file mode 100644 index 0000000000..53f0782deb --- /dev/null +++ b/crates/wasi-common/src/sys/unix/bsd/host_impl.rs @@ -0,0 +1,12 @@ +use crate::{wasi, Result}; +use std::convert::TryFrom; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; + +pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { + wasi::__wasi_device_t::try_from(dev).map_err(Into::into) +} + +pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { + wasi::__wasi_device_t::try_from(ino).map_err(Into::into) +} diff --git a/crates/wasi-common/src/sys/unix/bsd/mod.rs b/crates/wasi-common/src/sys/unix/bsd/mod.rs index bdb8dc6f68..39a4046a74 100644 --- a/crates/wasi-common/src/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/sys/unix/bsd/mod.rs @@ -1,18 +1,6 @@ pub(crate) mod filetime; +pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; - -pub(crate) mod host_impl { - use crate::{wasi, Result}; - use std::convert::TryFrom; - - pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; - - pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { - wasi::__wasi_device_t::try_from(dev).map_err(Into::into) - } - - pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { - wasi::__wasi_device_t::try_from(ino).map_err(Into::into) - } -} +#[path = "../linux/utimesat.rs"] +pub(crate) mod utimesat; diff --git a/crates/wasi-common/src/sys/unix/emscripten/filetime.rs b/crates/wasi-common/src/sys/unix/emscripten/filetime.rs new file mode 100644 index 0000000000..08f7c24a79 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/emscripten/filetime.rs @@ -0,0 +1,36 @@ +//! This internal module consists of helper types and functions for dealing +//! with setting the file times specific to Emscripten. +use crate::{sys::unix::filetime::FileTime, Result}; +use std::fs::File; +use std::io; + +pub(crate) const UTIME_NOW: i32 = 1_073_741_823; +pub(crate) const UTIME_OMIT: i32 = 1_073_741_822; + +/// Wrapper for `utimensat` syscall. In Emscripten, there is no point in dynamically resolving +/// if `utimensat` is available as it always was and will be. +pub(crate) fn utimensat( + dirfd: &File, + path: &str, + atime: FileTime, + mtime: FileTime, + symlink_nofollow: bool, +) -> Result<()> { + use crate::sys::unix::filetime::to_timespec; + use std::ffi::CString; + use std::os::unix::prelude::*; + + let flags = if symlink_nofollow { + libc::AT_SYMLINK_NOFOLLOW + } else { + 0 + }; + let p = CString::new(path.as_bytes())?; + let times = [to_timespec(&atime)?, to_timespec(&mtime)?]; + let rc = unsafe { libc::utimensat(dirfd.as_raw_fd(), p.as_ptr(), times.as_ptr(), flags) }; + if rc == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } +} diff --git a/crates/wasi-common/src/sys/unix/emscripten/mod.rs b/crates/wasi-common/src/sys/unix/emscripten/mod.rs new file mode 100644 index 0000000000..82a7ed93a8 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/emscripten/mod.rs @@ -0,0 +1,7 @@ +pub(crate) mod filetime; +#[path = "../linux/host_impl.rs"] +pub(crate) mod host_impl; +#[path = "../linux/hostcalls_impl.rs"] +pub(crate) mod hostcalls_impl; +#[path = "../linux/oshandle.rs"] +pub(crate) mod oshandle; diff --git a/crates/wasi-common/src/sys/unix/fdentry_impl.rs b/crates/wasi-common/src/sys/unix/fdentry_impl.rs index b178fcb38e..e44f54c076 100644 --- a/crates/wasi-common/src/sys/unix/fdentry_impl.rs +++ b/crates/wasi-common/src/sys/unix/fdentry_impl.rs @@ -1,24 +1,11 @@ use crate::fdentry::{Descriptor, OsHandleRef}; -use crate::{wasi, Error, Result}; +use crate::{sys::unix::sys_impl, wasi, Error, Result}; use std::fs::File; use std::io; use std::mem::ManuallyDrop; use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd}; -cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::linux::oshandle::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::bsd::oshandle::*; - } -} +pub(crate) use sys_impl::oshandle::*; impl AsRawFd for Descriptor { fn as_raw_fd(&self) -> RawFd { diff --git a/crates/wasi-common/src/sys/unix/filetime.rs b/crates/wasi-common/src/sys/unix/filetime.rs index 061537b3b8..9a70740a36 100644 --- a/crates/wasi-common/src/sys/unix/filetime.rs +++ b/crates/wasi-common/src/sys/unix/filetime.rs @@ -6,21 +6,30 @@ //! Kudos @alexcrichton! //! //! [filetime]: https://github.com/alexcrichton/filetime -use std::fs::{self, File}; -use std::io; +use crate::Result; +use std::convert::TryInto; + +pub(crate) use super::sys_impl::filetime::*; cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::linux::filetime::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::bsd::filetime::*; + if #[cfg(not(target_os = "emscripten"))] { + fn filetime_to_timespec(ft: &filetime::FileTime) -> Result { + Ok( + libc::timespec { + tv_sec: ft.seconds(), + tv_nsec: ft.nanoseconds().try_into()?, + } + ) + } + } else { + fn filetime_to_timespec(ft: &filetime::FileTime) -> Result { + Ok( + libc::timespec { + tv_sec: ft.seconds().try_into()?, + tv_nsec: ft.nanoseconds().try_into()?, + } + ) + } } } @@ -35,91 +44,6 @@ pub(crate) enum FileTime { FileTime(filetime::FileTime), } -/// For a provided pair of access and modified `FileTime`s, converts the input to -/// `filetime::FileTime` used later in `utimensat` function. For variants `FileTime::Now` -/// and `FileTime::Omit`, this function will make two syscalls: either accessing current -/// system time, or accessing the file's metadata. -/// -/// The original implementation can be found here: [filetime::unix::get_times]. -/// -/// [filetime::unix::get_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L42 -fn get_times( - atime: FileTime, - mtime: FileTime, - current: impl Fn() -> io::Result, -) -> io::Result<(filetime::FileTime, filetime::FileTime)> { - use std::time::SystemTime; - - let atime = match atime { - FileTime::Now => { - let time = SystemTime::now(); - filetime::FileTime::from_system_time(time) - } - FileTime::Omit => { - let meta = current()?; - filetime::FileTime::from_last_access_time(&meta) - } - FileTime::FileTime(ft) => ft, - }; - - let mtime = match mtime { - FileTime::Now => { - let time = SystemTime::now(); - filetime::FileTime::from_system_time(time) - } - FileTime::Omit => { - let meta = current()?; - filetime::FileTime::from_last_modification_time(&meta) - } - FileTime::FileTime(ft) => ft, - }; - - Ok((atime, mtime)) -} - -/// Combines `openat` with `utimes` to emulate `utimensat` on platforms where it is -/// not available. The logic for setting file times is based on [filetime::unix::set_file_handles_times]. -/// -/// [filetime::unix::set_file_handles_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L24 -pub(crate) fn utimesat( - dirfd: &File, - path: &str, - atime: FileTime, - mtime: FileTime, - symlink_nofollow: bool, -) -> io::Result<()> { - use std::ffi::CString; - use std::os::unix::prelude::*; - // emulate *at syscall by reading the path from a combination of - // (fd, path) - let p = CString::new(path.as_bytes())?; - let mut flags = libc::O_RDWR; - if symlink_nofollow { - flags |= libc::O_NOFOLLOW; - } - let fd = unsafe { libc::openat(dirfd.as_raw_fd(), p.as_ptr(), flags) }; - let f = unsafe { File::from_raw_fd(fd) }; - let (atime, mtime) = get_times(atime, mtime, || f.metadata())?; - let times = [to_timeval(atime), to_timeval(mtime)]; - let rc = unsafe { libc::futimes(f.as_raw_fd(), times.as_ptr()) }; - return if rc == 0 { - Ok(()) - } else { - Err(io::Error::last_os_error()) - }; -} - -/// Converts `filetime::FileTime` to `libc::timeval`. This function was taken directly from -/// [filetime] crate. -/// -/// [filetime]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L93 -fn to_timeval(ft: filetime::FileTime) -> libc::timeval { - libc::timeval { - tv_sec: ft.seconds(), - tv_usec: (ft.nanoseconds() / 1000) as libc::suseconds_t, - } -} - /// Converts `FileTime` to `libc::timespec`. If `FileTime::Now` variant is specified, this /// resolves to `UTIME_NOW` special const, `FileTime::Omit` variant resolves to `UTIME_OMIT`, and /// `FileTime::FileTime(ft)` where `ft := filetime::FileTime` uses [filetime] crate's original @@ -127,8 +51,8 @@ fn to_timeval(ft: filetime::FileTime) -> libc::timeval { /// /// [filetime]: https://github.com/alexcrichton/filetime /// [filetime::unix::to_timespec]: https://github.com/alexcrichton/filetime/blob/master/src/unix/mod.rs#L30 -pub(crate) fn to_timespec(ft: &FileTime) -> libc::timespec { - match ft { +pub(crate) fn to_timespec(ft: &FileTime) -> Result { + let ts = match ft { FileTime::Now => libc::timespec { tv_sec: 0, tv_nsec: UTIME_NOW, @@ -137,13 +61,7 @@ pub(crate) fn to_timespec(ft: &FileTime) -> libc::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT, }, - // `filetime::FileTime`'s fields are normalised by definition. `ft.seconds()` return the number - // of whole seconds, while `ft.nanoseconds()` returns only fractional part expressed in - // nanoseconds, as underneath it uses `std::time::Duration::subsec_nanos` to populate the - // `filetime::FileTime::nanoseconds` field. It is, therefore, OK to do an `as` cast here. - FileTime::FileTime(ft) => libc::timespec { - tv_sec: ft.seconds(), - tv_nsec: ft.nanoseconds() as _, - }, - } + FileTime::FileTime(ft) => filetime_to_timespec(ft)?, + }; + Ok(ts) } diff --git a/crates/wasi-common/src/sys/unix/host_impl.rs b/crates/wasi-common/src/sys/unix/host_impl.rs index 41306837b0..34ca49a1f4 100644 --- a/crates/wasi-common/src/sys/unix/host_impl.rs +++ b/crates/wasi-common/src/sys/unix/host_impl.rs @@ -3,7 +3,7 @@ #![allow(non_snake_case)] #![allow(dead_code)] use crate::host::FileType; -use crate::{helpers, wasi, Error, Result}; +use crate::{helpers, sys::unix::sys_impl, wasi, Error, Result}; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use yanix::{ @@ -11,20 +11,7 @@ use yanix::{ Errno, }; -cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::linux::host_impl::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::bsd::host_impl::*; - } -} +pub(crate) use sys_impl::host_impl::*; pub(crate) fn errno_from_nix(errno: Errno) -> Error { match errno { diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs index 8e6baa7bf5..e4bf973502 100644 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs @@ -3,27 +3,14 @@ use crate::helpers::systemtime_to_timestamp; use crate::host::{Dirent, FileType}; use crate::hostcalls_impl::PathGet; -use crate::sys::{fdentry_impl::OsHandle, host_impl}; +use crate::sys::{fdentry_impl::OsHandle, host_impl, unix::sys_impl}; use crate::{wasi, Error, Result}; use std::convert::TryInto; use std::fs::{File, Metadata}; use std::os::unix::fs::FileExt; use std::os::unix::prelude::{AsRawFd, FromRawFd}; -cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - pub(crate) use super::super::linux::hostcalls_impl::*; - } else if #[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" - ))] { - pub(crate) use super::super::bsd::hostcalls_impl::*; - } -} +pub(crate) use sys_impl::hostcalls_impl::*; pub(crate) fn fd_pread( file: &File, @@ -342,7 +329,7 @@ pub(crate) fn fd_readdir<'a>( dir.rewind(); } else { log::trace!(" | fd_readdir: doing seekdir to {}", cookie); - let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; + let loc = unsafe { SeekLoc::from_raw(cookie as i64)? }; dir.seek(loc); } @@ -356,7 +343,7 @@ pub(crate) fn fd_readdir<'a>( .to_owned(), ino: entry.ino(), ftype: entry.file_type().into(), - cookie: entry.seek_loc().to_raw().try_into()?, + cookie: entry.seek_loc()?.to_raw().try_into()?, }) })) } diff --git a/crates/wasi-common/src/sys/unix/linux/filetime.rs b/crates/wasi-common/src/sys/unix/linux/filetime.rs index 9fb0516b52..48413a5514 100644 --- a/crates/wasi-common/src/sys/unix/linux/filetime.rs +++ b/crates/wasi-common/src/sys/unix/linux/filetime.rs @@ -1,6 +1,6 @@ //! This internal module consists of helper types and functions for dealing //! with setting the file times specific to Linux. -use super::super::filetime::FileTime; +use crate::{sys::unix::filetime::FileTime, Result}; use std::fs::File; use std::io; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; @@ -20,8 +20,8 @@ pub(crate) fn utimensat( atime: FileTime, mtime: FileTime, symlink_nofollow: bool, -) -> io::Result<()> { - use super::super::filetime::{to_timespec, utimesat}; +) -> Result<()> { + use crate::sys::unix::filetime::to_timespec; use std::ffi::CString; use std::os::unix::prelude::*; @@ -53,9 +53,9 @@ pub(crate) fn utimensat( if err.raw_os_error() == Some(libc::ENOSYS) { INVALID.store(true, Relaxed); } else { - return Err(err); + return Err(err.into()); } } - utimesat(dirfd, path, atime, mtime, symlink_nofollow) + super::utimesat::utimesat(dirfd, path, atime, mtime, symlink_nofollow) } diff --git a/crates/wasi-common/src/sys/unix/linux/host_impl.rs b/crates/wasi-common/src/sys/unix/linux/host_impl.rs new file mode 100644 index 0000000000..d7a3e46fdb --- /dev/null +++ b/crates/wasi-common/src/sys/unix/linux/host_impl.rs @@ -0,0 +1,11 @@ +use crate::{wasi, Result}; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; + +pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { + Ok(wasi::__wasi_device_t::from(dev)) +} + +pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { + Ok(wasi::__wasi_device_t::from(ino)) +} diff --git a/crates/wasi-common/src/sys/unix/linux/mod.rs b/crates/wasi-common/src/sys/unix/linux/mod.rs index a9fd0557d5..0b4b8fd0b9 100644 --- a/crates/wasi-common/src/sys/unix/linux/mod.rs +++ b/crates/wasi-common/src/sys/unix/linux/mod.rs @@ -1,17 +1,5 @@ pub(crate) mod filetime; +pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; - -pub(crate) mod host_impl { - use crate::{wasi, Result}; - - pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; - - pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { - Ok(wasi::__wasi_device_t::from(dev)) - } - - pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { - Ok(wasi::__wasi_device_t::from(ino)) - } -} +pub(crate) mod utimesat; diff --git a/crates/wasi-common/src/sys/unix/linux/utimesat.rs b/crates/wasi-common/src/sys/unix/linux/utimesat.rs new file mode 100644 index 0000000000..c99e6e0610 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/linux/utimesat.rs @@ -0,0 +1,88 @@ +use crate::sys::unix::filetime::FileTime; +use crate::Result; +use std::{fs, io}; + +/// Combines `openat` with `utimes` to emulate `utimensat` on platforms where it is +/// not available. The logic for setting file times is based on [filetime::unix::set_file_handles_times]. +/// +/// [filetime::unix::set_file_handles_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L24 +pub(crate) fn utimesat( + dirfd: &fs::File, + path: &str, + atime: FileTime, + mtime: FileTime, + symlink_nofollow: bool, +) -> Result<()> { + use std::ffi::CString; + use std::os::unix::prelude::*; + // emulate *at syscall by reading the path from a combination of + // (fd, path) + let p = CString::new(path.as_bytes())?; + let mut flags = libc::O_RDWR; + if symlink_nofollow { + flags |= libc::O_NOFOLLOW; + } + let fd = unsafe { libc::openat(dirfd.as_raw_fd(), p.as_ptr(), flags) }; + let f = unsafe { fs::File::from_raw_fd(fd) }; + let (atime, mtime) = get_times(atime, mtime, || f.metadata().map_err(Into::into))?; + let times = [to_timeval(atime), to_timeval(mtime)]; + let rc = unsafe { libc::futimes(f.as_raw_fd(), times.as_ptr()) }; + if rc == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error().into()) + } +} + +/// Converts `filetime::FileTime` to `libc::timeval`. This function was taken directly from +/// [filetime] crate. +/// +/// [filetime]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L93 +fn to_timeval(ft: filetime::FileTime) -> libc::timeval { + libc::timeval { + tv_sec: ft.seconds(), + tv_usec: (ft.nanoseconds() / 1000) as libc::suseconds_t, + } +} + +/// For a provided pair of access and modified `FileTime`s, converts the input to +/// `filetime::FileTime` used later in `utimensat` function. For variants `FileTime::Now` +/// and `FileTime::Omit`, this function will make two syscalls: either accessing current +/// system time, or accessing the file's metadata. +/// +/// The original implementation can be found here: [filetime::unix::get_times]. +/// +/// [filetime::unix::get_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L42 +fn get_times( + atime: FileTime, + mtime: FileTime, + current: impl Fn() -> Result, +) -> Result<(filetime::FileTime, filetime::FileTime)> { + use std::time::SystemTime; + + let atime = match atime { + FileTime::Now => { + let time = SystemTime::now(); + filetime::FileTime::from_system_time(time) + } + FileTime::Omit => { + let meta = current()?; + filetime::FileTime::from_last_access_time(&meta) + } + FileTime::FileTime(ft) => ft, + }; + + let mtime = match mtime { + FileTime::Now => { + let time = SystemTime::now(); + filetime::FileTime::from_system_time(time) + } + FileTime::Omit => { + let meta = current()?; + filetime::FileTime::from_last_modification_time(&meta) + } + FileTime::FileTime(ft) => ft, + }; + + Ok((atime, mtime)) +} diff --git a/crates/wasi-common/src/sys/unix/mod.rs b/crates/wasi-common/src/sys/unix/mod.rs index 7aa05706ff..60b08ab355 100644 --- a/crates/wasi-common/src/sys/unix/mod.rs +++ b/crates/wasi-common/src/sys/unix/mod.rs @@ -4,17 +4,23 @@ pub(crate) mod hostcalls_impl; mod filetime; -#[cfg(any( - target_os = "macos", - target_os = "netbsd", - target_os = "freebsd", - target_os = "openbsd", - target_os = "ios", - target_os = "dragonfly" -))] -mod bsd; -#[cfg(target_os = "linux")] -mod linux; +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + use self::linux as sys_impl; + } else if #[cfg(target_os = "emscripten")] { + mod emscripten; + use self::emscripten as sys_impl; + } else if #[cfg(any(target_os = "macos", + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "ios", + target_os = "dragonfly"))] { + mod bsd; + use self::bsd as sys_impl; + } +} use crate::Result; use std::fs::{File, OpenOptions}; diff --git a/crates/wasi-common/yanix/src/dir.rs b/crates/wasi-common/yanix/src/dir.rs index e395491f4e..a62a451d2a 100644 --- a/crates/wasi-common/yanix/src/dir.rs +++ b/crates/wasi-common/yanix/src/dir.rs @@ -84,14 +84,10 @@ impl Entry { #[cfg(not(target_os = "android"))] #[derive(Clone, Copy, Debug)] -pub struct SeekLoc(libc::c_long); +pub struct SeekLoc(pub(crate) libc::c_long); #[cfg(not(target_os = "android"))] impl SeekLoc { - pub unsafe fn from_raw(loc: i64) -> Self { - Self(loc.into()) - } - pub fn to_raw(&self) -> i64 { self.0.into() } diff --git a/crates/wasi-common/yanix/src/sys/bsd/dir.rs b/crates/wasi-common/yanix/src/sys/bsd/dir.rs index fee320e3e5..f9255eac4a 100644 --- a/crates/wasi-common/yanix/src/sys/bsd/dir.rs +++ b/crates/wasi-common/yanix/src/sys/bsd/dir.rs @@ -46,7 +46,14 @@ impl EntryExt for Entry { self.0.d_ino.into() } - fn seek_loc(&self) -> SeekLoc { - self.0.loc + fn seek_loc(&self) -> Result { + Ok(self.0.loc) + } +} + +impl SeekLoc { + pub unsafe fn from_raw(loc: i64) -> Result { + let loc = loc.into(); + Ok(Self(loc)) } } diff --git a/crates/wasi-common/yanix/src/sys/emscripten/mod.rs b/crates/wasi-common/yanix/src/sys/emscripten/mod.rs new file mode 100644 index 0000000000..b9edc97e32 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/emscripten/mod.rs @@ -0,0 +1,16 @@ +#[path = "../linux/dir.rs"] +pub(crate) mod dir; +#[path = "../linux/fadvise.rs"] +pub(crate) mod fadvise; +#[path = "../linux/file.rs"] +pub(crate) mod file; + +use crate::{dir::SeekLoc, Result}; +use std::convert::TryInto; + +impl SeekLoc { + pub unsafe fn from_raw(loc: i64) -> Result { + let loc = loc.try_into()?; + Ok(Self(loc)) + } +} diff --git a/crates/wasi-common/yanix/src/sys/linux/dir.rs b/crates/wasi-common/yanix/src/sys/linux/dir.rs index 72349e3cf4..90fb45a3b6 100644 --- a/crates/wasi-common/yanix/src/sys/linux/dir.rs +++ b/crates/wasi-common/yanix/src/sys/linux/dir.rs @@ -20,7 +20,7 @@ impl EntryExt for Entry { self.0.d_ino.into() } - fn seek_loc(&self) -> SeekLoc { + fn seek_loc(&self) -> Result { unsafe { SeekLoc::from_raw(self.0.d_off) } } } diff --git a/crates/wasi-common/yanix/src/sys/linux/mod.rs b/crates/wasi-common/yanix/src/sys/linux/mod.rs index 3e6611f7b9..d7beaa7cbe 100644 --- a/crates/wasi-common/yanix/src/sys/linux/mod.rs +++ b/crates/wasi-common/yanix/src/sys/linux/mod.rs @@ -1,3 +1,12 @@ pub(crate) mod dir; pub(crate) mod fadvise; pub(crate) mod file; + +use crate::{dir::SeekLoc, Result}; + +impl SeekLoc { + pub unsafe fn from_raw(loc: i64) -> Result { + let loc = loc.into(); + Ok(Self(loc)) + } +} diff --git a/crates/wasi-common/yanix/src/sys/mod.rs b/crates/wasi-common/yanix/src/sys/mod.rs index 523e9bf754..72cdcb44fa 100644 --- a/crates/wasi-common/yanix/src/sys/mod.rs +++ b/crates/wasi-common/yanix/src/sys/mod.rs @@ -1,19 +1,20 @@ -use crate::dir::SeekLoc; +use crate::{dir::SeekLoc, Result}; use cfg_if::cfg_if; cfg_if! { if #[cfg(any(target_os = "linux", - target_os = "android", - target_os = "emscripten"))] { + target_os = "android"))] { mod linux; pub(crate) use self::linux::*; - } - else if #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly"))] { + } else if #[cfg(target_os = "emscripten")] { + mod emscripten; + pub(crate) use self::emscripten::*; + } else if #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly"))] { mod bsd; pub(crate) use self::bsd::*; } else { @@ -23,5 +24,5 @@ cfg_if! { pub trait EntryExt { fn ino(&self) -> u64; - fn seek_loc(&self) -> SeekLoc; + fn seek_loc(&self) -> Result; } From 054b79427e160b9842983e6c657605b697ec1682 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Dec 2019 15:19:58 -0800 Subject: [PATCH 7/7] Fix the path_filestat test on Linux (#706) Only very recently in #700 did we actually start running wasi tests again (they weren't running by accident). Just before that landed we also landed #688 which had some refactorings. Unfortunately #688 had a minor issue in it which wasn't caught because tests weren't run. This means that the bug in #688 slipped in and is now being caught by #700 now that both are landed on master. This commit fixes the small issue introduced and should get our CI green again! --- .../wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs | 2 +- crates/wasi-common/src/sys/unix/linux/filetime.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs index a286874413..8162b391fa 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/filetime.rs @@ -36,7 +36,7 @@ pub(crate) fn utimensat( static INVALID: AtomicBool = AtomicBool::new(false); if !INVALID.load(Relaxed) { let p = CString::new(path.as_bytes())?; - let times = [to_timespec(&atime), to_timespec(&mtime)]; + let times = [to_timespec(&atime)?, to_timespec(&mtime)?]; let rc = unsafe { libc::syscall( libc::SYS_utimensat, diff --git a/crates/wasi-common/src/sys/unix/linux/filetime.rs b/crates/wasi-common/src/sys/unix/linux/filetime.rs index 48413a5514..6ffd6b8ddf 100644 --- a/crates/wasi-common/src/sys/unix/linux/filetime.rs +++ b/crates/wasi-common/src/sys/unix/linux/filetime.rs @@ -36,7 +36,7 @@ pub(crate) fn utimensat( static INVALID: AtomicBool = AtomicBool::new(false); if !INVALID.load(Relaxed) { let p = CString::new(path.as_bytes())?; - let times = [to_timespec(&atime), to_timespec(&mtime)]; + let times = [to_timespec(&atime)?, to_timespec(&mtime)?]; let rc = unsafe { libc::syscall( libc::SYS_utimensat,