diff --git a/hermes/bin/src/runtime_extensions/bindings.rs b/hermes/bin/src/runtime_extensions/bindings.rs index ca7841692..5e0002cb2 100644 --- a/hermes/bin/src/runtime_extensions/bindings.rs +++ b/hermes/bin/src/runtime_extensions/bindings.rs @@ -13,9 +13,4 @@ use wasmtime::component::bindgen; bindgen!({ world: "hermes", path: "../../wasm/wasi/wit", - with: { - "wasi:filesystem/types/descriptor": super::wasi::descriptors::Descriptor, - "wasi:io/streams/input-stream": super::wasi::descriptors::Stream, - "wasi:io/streams/output-stream": super::wasi::descriptors::Stream, - } }); diff --git a/hermes/bin/src/runtime_extensions/hermes/crypto/host.rs b/hermes/bin/src/runtime_extensions/hermes/crypto/host.rs index ce7602746..41cf9aed0 100644 --- a/hermes/bin/src/runtime_extensions/hermes/crypto/host.rs +++ b/hermes/bin/src/runtime_extensions/hermes/crypto/host.rs @@ -1,22 +1,17 @@ //! Crypto host implementation for WASM runtime. -use wasmtime::component::Resource; - -use super::bip32_ed25519::check_signature; +use super::{ + bip32_ed25519::{check_signature, derive_new_private_key, get_public_key, sign_data}, + bip39::{generate_new_mnemonic, mnemonic_to_xprv}, + state::get_state, +}; use crate::{ runtime_context::HermesRuntimeContext, - runtime_extensions::{ - bindings::hermes::{ - binary::api::Bstr, - crypto::api::{ - Bip32Ed25519, Bip32Ed25519PublicKey, Bip32Ed25519Signature, Errno, Host, - HostBip32Ed25519, MnemonicPhrase, Passphrase, Path, - }, - }, - hermes::crypto::{ - bip32_ed25519::{derive_new_private_key, get_public_key, sign_data}, - bip39::{generate_new_mnemonic, mnemonic_to_xprv}, - state::{add_resource, delete_resource, get_resource}, + runtime_extensions::bindings::hermes::{ + binary::api::Bstr, + crypto::api::{ + Bip32Ed25519, Bip32Ed25519PublicKey, Bip32Ed25519Signature, Errno, Host, + HostBip32Ed25519, MnemonicPhrase, Passphrase, Path, }, }, }; @@ -32,35 +27,22 @@ impl HostBip32Ed25519 for HermesRuntimeContext { &mut self, mnemonic: MnemonicPhrase, passphrase: Option, ) -> wasmtime::Result> { let passphrase = passphrase.unwrap_or_default(); - let xprv = mnemonic_to_xprv(&mnemonic.join(" "), &passphrase.join(" ")); - match xprv { - Ok(xprv) => { - if let Some(id) = add_resource(self.app_name(), xprv) { - Ok(Resource::new_own(id)) - } else { - // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 - Err(wasmtime::Error::msg("Error creating new resource")) - } - }, - Err(e) => Err(wasmtime::Error::msg(e.to_string())), - } + // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 + let xprv = mnemonic_to_xprv(&mnemonic.join(" "), &passphrase.join(" ")) + .map_err(|e| wasmtime::Error::msg(e.to_string()))?; + + let app_state = get_state().get_app_state(self.app_name())?; + Ok(app_state.create_resource(xprv)) } /// Get the public key for this private key. fn public_key( &mut self, resource: wasmtime::component::Resource, ) -> wasmtime::Result { - let private_key = get_resource(self.app_name(), resource.rep()); - match private_key { - Some(private_key) => { - let public_key = get_public_key(&private_key); - Ok(public_key) - }, - None => { - // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 - Ok((0, 0, 0, 0)) - }, - } + let mut app_state = get_state().get_app_state(self.app_name())?; + let private_key = app_state.get_object(&resource)?; + let public_key = get_public_key(&private_key); + Ok(public_key) } /// Sign data with the Private key, and return it. @@ -71,15 +53,10 @@ impl HostBip32Ed25519 for HermesRuntimeContext { fn sign_data( &mut self, resource: wasmtime::component::Resource, data: Bstr, ) -> wasmtime::Result { - let private_key = get_resource(self.app_name(), resource.rep()); - match private_key { - Some(private_key) => { - let sig = sign_data(&private_key, &data); - Ok(sig) - }, - // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 - None => Ok((0, 0, 0, 0, 0, 0, 0, 0)), - } + let mut app_state = get_state().get_app_state(self.app_name())?; + let private_key = app_state.get_object(&resource)?; + let sig = sign_data(&private_key, &data); + Ok(sig) } /// Check a signature on a set of data. @@ -97,15 +74,10 @@ impl HostBip32Ed25519 for HermesRuntimeContext { &mut self, resource: wasmtime::component::Resource, data: Bstr, sig: Bip32Ed25519Signature, ) -> wasmtime::Result { - let private_key = get_resource(self.app_name(), resource.rep()); - match private_key { - Some(private_key) => { - let check_sig = check_signature(&private_key, &data, sig); - Ok(check_sig) - }, - // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 - None => Ok(false), - } + let mut app_state = get_state().get_app_state(self.app_name())?; + let private_key = app_state.get_object(&resource)?; + let check_sig = check_signature(&private_key, &data, sig); + Ok(check_sig) } /// Derive a new private key from the current private key. @@ -118,19 +90,19 @@ impl HostBip32Ed25519 for HermesRuntimeContext { fn derive( &mut self, resource: wasmtime::component::Resource, path: Path, ) -> wasmtime::Result> { - get_resource(self.app_name(), resource.rep()) - .and_then(|private_key| derive_new_private_key(private_key, &path).ok()) - .and_then(|derived_private_key| add_resource(self.app_name(), derived_private_key)) - .map(Resource::new_own) - // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 - .ok_or_else(|| wasmtime::Error::msg("Error deriving new private key")) + let mut app_state = get_state().get_app_state(self.app_name())?; + + let private_key = app_state.get_object(&resource)?; + // TODO(bkioshn): https://github.com/input-output-hk/hermes/issues/183 + let new_private_key = derive_new_private_key(private_key.clone(), &path) + .map_err(|_| wasmtime::Error::msg("Error deriving new private key"))?; + drop(private_key); + Ok(app_state.create_resource(new_private_key)) } fn drop(&mut self, res: wasmtime::component::Resource) -> wasmtime::Result<()> { - // If the state deletion is successful, drop the resource. - if delete_resource(self.app_name(), res.rep()).is_some() { - let _unused = self.drop(res); - } + let app_state = get_state().get_app_state(self.app_name())?; + app_state.delete_resource(res)?; Ok(()) } } diff --git a/hermes/bin/src/runtime_extensions/hermes/crypto/mod.rs b/hermes/bin/src/runtime_extensions/hermes/crypto/mod.rs index 904ccc3c5..e4fb2a2b3 100644 --- a/hermes/bin/src/runtime_extensions/hermes/crypto/mod.rs +++ b/hermes/bin/src/runtime_extensions/hermes/crypto/mod.rs @@ -1,7 +1,5 @@ //! Crypto runtime extension implementation. -use self::state::{get_state, set_state}; - mod bip32_ed25519; mod bip39; mod host; @@ -9,9 +7,5 @@ mod state; /// Advise Runtime Extensions of a new context pub(crate) fn new_context(ctx: &crate::runtime_context::HermesRuntimeContext) { - // check whether it exist - let state = get_state(); - if !state.contains_key(ctx.app_name()) { - set_state(ctx.app_name().clone()); - } + state::get_state().add_app(ctx.app_name().clone()); } diff --git a/hermes/bin/src/runtime_extensions/hermes/crypto/state.rs b/hermes/bin/src/runtime_extensions/hermes/crypto/state.rs index 4835161b6..43a7d6a15 100644 --- a/hermes/bin/src/runtime_extensions/hermes/crypto/state.rs +++ b/hermes/bin/src/runtime_extensions/hermes/crypto/state.rs @@ -1,258 +1,19 @@ //! Crypto state -use std::{ - cmp::Eq, - hash::{Hash, Hasher}, -}; - -use dashmap::DashMap; use ed25519_bip32::XPrv; use once_cell::sync::Lazy; -use crate::app::ApplicationName; +use crate::runtime_extensions::{ + bindings::hermes::crypto::api::Bip32Ed25519, resource_manager::ApplicationResourceStorage, +}; /// Map of app name to resource holder -type State = DashMap; - -/// Wrapper for `XPrv` to implement Hash used in `DashMap` -#[derive(Eq, Clone, PartialEq)] -struct WrappedXPrv(XPrv); - -/// Implement Hash for `WrappedXPrv` -impl Hash for WrappedXPrv { - /// Hasher for `WrappedXPrv` - fn hash(&self, state: &mut H) { - self.0.as_ref().hash(state); - } -} - -/// Implement From for `XPrv` to `WrappedXPrv` -impl From for WrappedXPrv { - /// Turn `XPrv` into `WrappedXPrv` - fn from(xprv: XPrv) -> Self { - WrappedXPrv(xprv) - } -} - -/// Resource holder to hold the resources for `XPrv`. -#[derive(Clone)] -pub(crate) struct ResourceHolder { - /// Map of resource to id. - id_to_resource_map: DashMap, - /// Map of id to resource. - resource_to_id_map: DashMap, - /// Current Id. - current_id: u32, -} - -impl ResourceHolder { - /// Generate new resource holder. - fn new() -> Self { - Self { - id_to_resource_map: DashMap::new(), - resource_to_id_map: DashMap::new(), - current_id: 0, - } - } - - /// Get the next id and increment the current id. - fn get_and_increment_next_id(&mut self) -> u32 { - self.current_id += 1; - self.current_id - } - - /// Get the resource from id if possible. - fn get_resource_from_id(&self, id: u32) -> Option { - self.id_to_resource_map - .get(&id) - .map(|entry| entry.value().clone()) - } - - /// Get the id from resource if possible. - fn get_id_from_resource(&self, resource: &XPrv) -> Option { - self.resource_to_id_map - .get(&WrappedXPrv::from(resource.clone())) - .map(|entry| *entry.value()) - } - - /// Drop the item from resources using id if possible. - /// Return the id of the resource if successful remove from maps - fn drop(&mut self, id: u32) -> Option { - // Check if the resource exists in id_to_resource_map. - if let Some(resource) = self.get_resource_from_id(id) { - // Check if the id exists in resource_to_id_map. - if let Some(associated_id) = self.get_id_from_resource(&resource) { - // The id should be the same. - if associated_id == id { - // Remove the resource from both maps. - if let Some(r) = self.id_to_resource_map.remove(&id) { - self.resource_to_id_map.remove(&WrappedXPrv(r.1)); - return Some(associated_id); - } - } - } - } - None - } -} +pub(super) type State = ApplicationResourceStorage; /// Global state to hold the resources. -static CRYPTO_INTERNAL_STATE: Lazy = Lazy::new(DashMap::new); +static CRYPTO_STATE: Lazy = Lazy::new(ApplicationResourceStorage::new); -/// Get the state. +/// Get the crypto state. pub(super) fn get_state() -> &'static State { - &CRYPTO_INTERNAL_STATE -} - -/// Set the state according to the app context. -pub(crate) fn set_state(app_name: ApplicationName) { - CRYPTO_INTERNAL_STATE.insert(app_name, ResourceHolder::new()); -} - -/// Get the resource from the state using id if possible. -pub(crate) fn get_resource(app_name: &ApplicationName, id: u32) -> Option { - if let Some(res_holder) = CRYPTO_INTERNAL_STATE.get(app_name) { - return res_holder.get_resource_from_id(id); - } - None -} - -/// Add the resource of `XPrv` to the state if possible. -/// Return the id if successful. -pub(crate) fn add_resource(app_name: &ApplicationName, xprv: XPrv) -> Option { - if let Some(mut res_holder) = CRYPTO_INTERNAL_STATE.get_mut(app_name) { - let wrapped_xprv = WrappedXPrv::from(xprv.clone()); - // Check whether the resource already exists. - if !res_holder.resource_to_id_map.contains_key(&wrapped_xprv) { - // if not get the next id and insert the resource to both maps. - let id = res_holder.get_and_increment_next_id(); - res_holder.id_to_resource_map.insert(id, xprv); - res_holder.resource_to_id_map.insert(wrapped_xprv, id); - return Some(id); - } - } - None -} - -/// Delete the resource from the state using id if possible. -pub(crate) fn delete_resource(app_name: &ApplicationName, id: u32) -> Option { - if let Some(mut res_holder) = CRYPTO_INTERNAL_STATE.get_mut(app_name) { - return res_holder.drop(id); - } - None -} - -#[cfg(test)] -mod tests_crypto_state { - use std::thread; - - use super::*; - const KEY1: [u8; 96] = [ - 0xF8, 0xA2, 0x92, 0x31, 0xEE, 0x38, 0xD6, 0xC5, 0xBF, 0x71, 0x5D, 0x5B, 0xAC, 0x21, 0xC7, - 0x50, 0x57, 0x7A, 0xA3, 0x79, 0x8B, 0x22, 0xD7, 0x9D, 0x65, 0xBF, 0x97, 0xD6, 0xFA, 0xDE, - 0xA1, 0x5A, 0xDC, 0xD1, 0xEE, 0x1A, 0xBD, 0xF7, 0x8B, 0xD4, 0xBE, 0x64, 0x73, 0x1A, 0x12, - 0xDE, 0xB9, 0x4D, 0x36, 0x71, 0x78, 0x41, 0x12, 0xEB, 0x6F, 0x36, 0x4B, 0x87, 0x18, 0x51, - 0xFD, 0x1C, 0x9A, 0x24, 0x73, 0x84, 0xDB, 0x9A, 0xD6, 0x00, 0x3B, 0xBD, 0x08, 0xB3, 0xB1, - 0xDD, 0xC0, 0xD0, 0x7A, 0x59, 0x72, 0x93, 0xFF, 0x85, 0xE9, 0x61, 0xBF, 0x25, 0x2B, 0x33, - 0x12, 0x62, 0xED, 0xDF, 0xAD, 0x0D, - ]; - - const KEY2: [u8; 96] = [ - 0x60, 0xD3, 0x99, 0xDA, 0x83, 0xEF, 0x80, 0xD8, 0xD4, 0xF8, 0xD2, 0x23, 0x23, 0x9E, 0xFD, - 0xC2, 0xB8, 0xFE, 0xF3, 0x87, 0xE1, 0xB5, 0x21, 0x91, 0x37, 0xFF, 0xB4, 0xE8, 0xFB, 0xDE, - 0xA1, 0x5A, 0xDC, 0x93, 0x66, 0xB7, 0xD0, 0x03, 0xAF, 0x37, 0xC1, 0x13, 0x96, 0xDE, 0x9A, - 0x83, 0x73, 0x4E, 0x30, 0xE0, 0x5E, 0x85, 0x1E, 0xFA, 0x32, 0x74, 0x5C, 0x9C, 0xD7, 0xB4, - 0x27, 0x12, 0xC8, 0x90, 0x60, 0x87, 0x63, 0x77, 0x0E, 0xDD, 0xF7, 0x72, 0x48, 0xAB, 0x65, - 0x29, 0x84, 0xB2, 0x1B, 0x84, 0x97, 0x60, 0xD1, 0xDA, 0x74, 0xA6, 0xF5, 0xBD, 0x63, 0x3C, - 0xE4, 0x1A, 0xDC, 0xEE, 0xF0, 0x7A, - ]; - - #[test] - fn test_basic_func_resource() { - let prv = XPrv::from_bytes_verified(KEY1).unwrap(); - let app_name: ApplicationName = ApplicationName("App name".to_string()); - // Set the global state. - set_state(app_name.clone()); - - // Add the resource. - let id1 = add_resource(&app_name, prv.clone()); - // Should return id 1. - assert_eq!(id1, Some(1)); - // Get the resource from id 1. - let resource = get_resource(&app_name, 1); - // The resource should be the same - assert_eq!(resource, Some(prv.clone())); - - // Add another resource, with the same key. - let id2 = add_resource(&app_name, prv.clone()); - // Resource already exist, so it should return None. - assert_eq!(id2, None); - // Get the resource from id. - let k2 = get_resource(&app_name, 2); - // Resource already exist, so it should return None. - assert_eq!(k2, None); - - // Dropping the resource with id 1. - let drop_id_1 = delete_resource(&app_name, 1); - assert_eq!(drop_id_1, Some(1)); - // Dropping the resource with id 2 which doesn't exist. - let drop_id_2 = delete_resource(&app_name, 2); - assert_eq!(drop_id_2, None); - - let res_holder = CRYPTO_INTERNAL_STATE.get(&app_name).unwrap(); - assert_eq!(res_holder.id_to_resource_map.len(), 0); - assert_eq!(res_holder.resource_to_id_map.len(), 0); - } - - #[test] - fn test_thread_safe_insert_resources() { - let app_name: ApplicationName = ApplicationName("App name 2".to_string()); - - // Setup initial state. - set_state(app_name.clone()); - - // Run the test with multiple threads. - let mut handles = vec![]; - - // Spawning 20 threads. - for _ in 0..20 { - let handle = thread::spawn(|| { - let app_name: ApplicationName = ApplicationName("App name 2".to_string()); - let prv1 = XPrv::from_bytes_verified(KEY1).unwrap(); - // Adding resource - add_resource(&app_name, prv1.clone()); - let app_name: ApplicationName = ApplicationName("App name 2".to_string()); - let prv2 = XPrv::from_bytes_verified(KEY2).unwrap(); - // Adding resource. - add_resource(&app_name, prv2.clone()); - }); - handles.push(handle); - } - - // Wait for all threads to finish. - for handle in handles { - handle.join().unwrap(); - } - - // Checking the results. - let prv1 = XPrv::from_bytes_verified(KEY1).unwrap(); - let prv2 = XPrv::from_bytes_verified(KEY2).unwrap(); - - let res_holder = CRYPTO_INTERNAL_STATE.get(&app_name).unwrap(); - - // Maps should contains 2 resources. - assert_eq!(res_holder.id_to_resource_map.len(), 2); - assert_eq!(res_holder.resource_to_id_map.len(), 2); - assert_eq!(res_holder.current_id, 2); - // Maps should contains prv1 and prv2. - assert!(res_holder - .resource_to_id_map - .contains_key(&WrappedXPrv::from(prv1.clone()))); - assert!(res_holder - .resource_to_id_map - .contains_key(&WrappedXPrv::from(prv2.clone()))); - // Map should contains id 1 and 2. - assert!(res_holder.id_to_resource_map.contains_key(&1)); - assert!(res_holder.id_to_resource_map.contains_key(&2)); - } + &CRYPTO_STATE } diff --git a/hermes/bin/src/runtime_extensions/hermes/sqlite/connection/host.rs b/hermes/bin/src/runtime_extensions/hermes/sqlite/connection/host.rs index b64a82d1b..3a179e5cb 100644 --- a/hermes/bin/src/runtime_extensions/hermes/sqlite/connection/host.rs +++ b/hermes/bin/src/runtime_extensions/hermes/sqlite/connection/host.rs @@ -2,11 +2,12 @@ //! `SQLite` connection object host implementation for WASM runtime. -use super::{super::state, core}; +use super::{super::state::get_db_state, core}; use crate::{ runtime_context::HermesRuntimeContext, - runtime_extensions::bindings::hermes::sqlite::api::{ - Errno, ErrorInfo, HostSqlite, Sqlite, Statement, + runtime_extensions::{ + bindings::hermes::sqlite::api::{Errno, ErrorInfo, HostSqlite, Sqlite, Statement}, + hermes::sqlite::state::get_statement_state, }, }; @@ -24,10 +25,8 @@ impl HostSqlite for HermesRuntimeContext { fn close( &mut self, resource: wasmtime::component::Resource, ) -> wasmtime::Result> { - let db_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_db_state() - .delete_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `close`"))?; + let app_state = get_db_state().get_app_state(self.app_name())?; + let db_ptr = app_state.delete_resource(resource)?; Ok(core::close(db_ptr as *mut _)) } @@ -41,12 +40,10 @@ impl HostSqlite for HermesRuntimeContext { fn errcode( &mut self, resource: wasmtime::component::Resource, ) -> wasmtime::Result> { - let db_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_db_state() - .get_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `errcode`"))?; + let mut app_state = get_db_state().get_app_state(self.app_name())?; + let db_ptr = app_state.get_object(&resource)?; - Ok(core::errcode(db_ptr as *mut _)) + Ok(core::errcode(*db_ptr as *mut _)) } /// Compiles SQL text into byte-code that will do the work of querying or updating the @@ -65,27 +62,20 @@ impl HostSqlite for HermesRuntimeContext { fn prepare( &mut self, resource: wasmtime::component::Resource, sql: String, ) -> wasmtime::Result, Errno>> { - let db_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_db_state() - .get_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `prepare`"))?; + let mut db_app_state = get_db_state().get_app_state(self.app_name())?; + let db_ptr = db_app_state.get_object(&resource)?; - let result = core::prepare(db_ptr as *mut _, sql.as_str()); + let result = core::prepare(*db_ptr as *mut _, sql.as_str()); match result { Ok(stmt_ptr) => { if stmt_ptr.is_null() { Ok(Err(Errno::ReturnedNullPointer)) } else { - let stmt_id = - state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_stmt_state() - .add_object(stmt_ptr as _) - .ok_or_else(|| { - wasmtime::Error::msg("Internal state error while calling `prepare`") - })?; + let stm_app_state = get_statement_state().get_app_state(self.app_name())?; + let stmt = stm_app_state.create_resource(stmt_ptr as _); - Ok(Ok(wasmtime::component::Resource::new_own(stmt_id))) + Ok(Ok(stmt)) } }, Err(errno) => Ok(Err(errno)), @@ -101,20 +91,15 @@ impl HostSqlite for HermesRuntimeContext { fn execute( &mut self, resource: wasmtime::component::Resource, sql: String, ) -> wasmtime::Result> { - let db_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_db_state() - .get_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `execute`"))?; + let mut app_state = get_db_state().get_app_state(self.app_name())?; + let db_ptr = app_state.get_object(&resource)?; - Ok(core::execute(db_ptr as *mut _, sql.as_str())) + Ok(core::execute(*db_ptr as *mut _, sql.as_str())) } fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { - let db_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_db_state() - .delete_object_by_id(rep.rep()); - - if let Some(db_ptr) = db_ptr { + let app_state = get_db_state().get_app_state(self.app_name())?; + if let Ok(db_ptr) = app_state.delete_resource(rep) { let _ = core::close(db_ptr as *mut _); } diff --git a/hermes/bin/src/runtime_extensions/hermes/sqlite/host.rs b/hermes/bin/src/runtime_extensions/hermes/sqlite/host.rs index 7e910f77c..a94a139ff 100644 --- a/hermes/bin/src/runtime_extensions/hermes/sqlite/host.rs +++ b/hermes/bin/src/runtime_extensions/hermes/sqlite/host.rs @@ -1,6 +1,6 @@ //! `SQLite` host implementation for WASM runtime. -use super::{core, state}; +use super::{core, state::get_db_state}; use crate::{ runtime_context::HermesRuntimeContext, runtime_extensions::bindings::hermes::sqlite::api::{Errno, Host, Sqlite}, @@ -24,14 +24,10 @@ impl Host for HermesRuntimeContext { ) -> wasmtime::Result, Errno>> { match core::open(readonly, memory, self.app_name().clone()) { Ok(db_ptr) => { - let db_id = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_db_state() - .add_object(db_ptr as _) - .ok_or_else(|| { - wasmtime::Error::msg("Internal state error while calling `open`") - })?; + let app_state = get_db_state().get_app_state(self.app_name())?; + let db_id = app_state.create_resource(db_ptr as _); - Ok(Ok(wasmtime::component::Resource::new_own(db_id))) + Ok(Ok(db_id)) }, Err(err) => Ok(Err(err)), } diff --git a/hermes/bin/src/runtime_extensions/hermes/sqlite/mod.rs b/hermes/bin/src/runtime_extensions/hermes/sqlite/mod.rs index 88bd3f1f8..230fa68df 100644 --- a/hermes/bin/src/runtime_extensions/hermes/sqlite/mod.rs +++ b/hermes/bin/src/runtime_extensions/hermes/sqlite/mod.rs @@ -8,6 +8,9 @@ mod statement; /// Advise Runtime Extensions of a new context pub(crate) fn new_context(ctx: &crate::runtime_context::HermesRuntimeContext) { + state::get_db_state().add_app(ctx.app_name().clone()); + state::get_statement_state().add_app(ctx.app_name().clone()); + connection::new_context(ctx); statement::new_context(ctx); } diff --git a/hermes/bin/src/runtime_extensions/hermes/sqlite/state.rs b/hermes/bin/src/runtime_extensions/hermes/sqlite/state.rs index 5477d4945..5d66d3f6e 100644 --- a/hermes/bin/src/runtime_extensions/hermes/sqlite/state.rs +++ b/hermes/bin/src/runtime_extensions/hermes/sqlite/state.rs @@ -1,109 +1,33 @@ //! Internal state implementation for the `SQLite` module. -use std::collections::HashMap; - -use dashmap::{mapref::one::RefMut, DashMap}; use once_cell::sync::Lazy; -use crate::app::ApplicationName; +use crate::runtime_extensions::{ + bindings::hermes::sqlite::api::{Sqlite, Statement}, + resource_manager::ApplicationResourceStorage, +}; /// The object pointer used specifically with C objects like `sqlite3` or `sqlite3_stmt`. -type ObjectPointer = usize; - -/// Represents an individual state for a particular object. -#[derive(Debug)] -pub(crate) struct ResourceObjectState { - /// A map holding key-value pairs of an object ID and a value. - id_map: HashMap, - /// The current incremental state of ID. - current_id: Option, -} - -/// Represents the state of resources. -pub(crate) struct ResourceState { - /// The state of database object. - db_state: ResourceObjectState, - /// The state of database statement object. - stmt_state: ResourceObjectState, -} - -impl ResourceObjectState { - /// Create a new `ResourceObjectState` with initial state. - fn new() -> Self { - Self { - id_map: HashMap::new(), - current_id: None, - } - } +pub(super) type ObjectPointer = usize; - /// Adds a value into the resource. If it does not exist, assigns one and returns the - /// new created key ID. In case of the key ID is running out of numbers, returns - /// `None`. - pub(super) fn add_object(&mut self, object_ptr: ObjectPointer) -> Option { - if let Some((existing_id, _)) = self.id_map.iter().find(|(_, val)| val == &&object_ptr) { - Some(*existing_id) - } else { - let (new_id, is_overflow) = self - .current_id - .map_or_else(|| (0, false), |id| id.overflowing_add(1)); +/// Map of app name to db resource holder +pub(super) type DbState = ApplicationResourceStorage; - if is_overflow { - None - } else { - self.id_map.insert(new_id, object_ptr); - self.current_id = Some(new_id); - Some(new_id) - } - } - } +/// Map of app name to db statement resource holder +pub(super) type StatementState = ApplicationResourceStorage; - /// Retrieves a value according to its key ID. - pub(super) fn get_object_by_id(&self, id: u32) -> Option { - self.id_map.get(&id).map(ToOwned::to_owned) - } +/// Global state to hold `SQLite` db resources. +static SQLITE_DB_STATE: Lazy = Lazy::new(DbState::new); - /// Deletes a value according to its key ID, and returns the removed value if exists. - pub(super) fn delete_object_by_id(&mut self, id: u32) -> Option { - self.id_map.remove(&id) - } -} - -impl ResourceState { - /// Create a new `ResourceState` with initial state. - pub(super) fn new() -> Self { - Self { - db_state: ResourceObjectState::new(), - stmt_state: ResourceObjectState::new(), - } - } - - /// Gets the state for managing database objects. - pub(super) fn get_db_state(&mut self) -> &mut ResourceObjectState { - &mut self.db_state - } +/// Global state to hold `SQLite` statement resources. +static SQLITE_STATEMENT_STATE: Lazy = Lazy::new(StatementState::new); - /// Gets the state for managing statement objects. - pub(super) fn get_stmt_state(&mut self) -> &mut ResourceObjectState { - &mut self.stmt_state - } +/// Get the global state of `SQLite` db resources. +pub(super) fn get_db_state() -> &'static DbState { + &SQLITE_DB_STATE } -/// Map of app name to resource holder -type State = DashMap; - -/// Global state to hold `SQLite` resources. -static SQLITE_INTERNAL_STATE: Lazy = Lazy::new(State::new); - -/// Represents the internal state object for `SQLite` module. -pub(crate) struct InternalState; - -impl InternalState { - /// Set the state according to the app context. - pub(crate) fn get_or_create_resource<'a>( - app_name: ApplicationName, - ) -> RefMut<'a, ApplicationName, ResourceState> { - SQLITE_INTERNAL_STATE - .entry(app_name) - .or_insert_with(ResourceState::new) - } +/// Get the global state of `SQLite` statement resources. +pub(super) fn get_statement_state() -> &'static StatementState { + &SQLITE_STATEMENT_STATE } diff --git a/hermes/bin/src/runtime_extensions/hermes/sqlite/statement/host.rs b/hermes/bin/src/runtime_extensions/hermes/sqlite/statement/host.rs index 501918578..44f9ec5b4 100644 --- a/hermes/bin/src/runtime_extensions/hermes/sqlite/statement/host.rs +++ b/hermes/bin/src/runtime_extensions/hermes/sqlite/statement/host.rs @@ -1,6 +1,6 @@ //! `SQLite` statement host implementation for WASM runtime. -use super::{super::state, core}; +use super::{super::state::get_statement_state, core}; use crate::{ runtime_context::HermesRuntimeContext, runtime_extensions::bindings::hermes::sqlite::api::{Errno, HostStatement, Statement, Value}, @@ -16,14 +16,10 @@ impl HostStatement for HermesRuntimeContext { fn bind( &mut self, resource: wasmtime::component::Resource, index: u32, value: Value, ) -> wasmtime::Result> { - let stmt_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_stmt_state() - .get_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `bind`"))?; - + let mut app_state = get_statement_state().get_app_state(self.app_name())?; + let stmt_ptr = app_state.get_object(&resource)?; let index = i32::try_from(index).map_err(|_| Errno::ConvertingNumeric)?; - - Ok(core::bind(stmt_ptr as *mut _, index, value)) + Ok(core::bind(*stmt_ptr as *mut _, index, value)) } /// Advances a statement to the next result row or to completion. @@ -33,12 +29,9 @@ impl HostStatement for HermesRuntimeContext { fn step( &mut self, resource: wasmtime::component::Resource, ) -> wasmtime::Result> { - let stmt_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_stmt_state() - .get_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `step`"))?; - - Ok(core::step(stmt_ptr as *mut _)) + let mut app_state = get_statement_state().get_app_state(self.app_name())?; + let stmt_ptr = app_state.get_object(&resource)?; + Ok(core::step(*stmt_ptr as *mut _)) } /// Returns information about a single column of the current result row of a query. @@ -57,14 +50,10 @@ impl HostStatement for HermesRuntimeContext { fn column( &mut self, resource: wasmtime::component::Resource, index: u32, ) -> wasmtime::Result> { - let stmt_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_stmt_state() - .get_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `column`"))?; - + let mut app_state = get_statement_state().get_app_state(self.app_name())?; + let stmt_ptr = app_state.get_object(&resource)?; let index = i32::try_from(index).map_err(|_| Errno::ConvertingNumeric)?; - - Ok(core::column(stmt_ptr as *mut _, index)) + Ok(core::column(*stmt_ptr as *mut _, index)) } /// Destroys a prepared statement object. If the most recent evaluation of the @@ -80,20 +69,15 @@ impl HostStatement for HermesRuntimeContext { fn finalize( &mut self, resource: wasmtime::component::Resource, ) -> wasmtime::Result> { - let stmt_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_stmt_state() - .delete_object_by_id(resource.rep()) - .ok_or_else(|| wasmtime::Error::msg("Internal state error while calling `finalize`"))?; + let app_state = get_statement_state().get_app_state(self.app_name())?; + let stmt_ptr = app_state.delete_resource(resource)?; Ok(core::finalize(stmt_ptr as *mut _)) } fn drop(&mut self, resource: wasmtime::component::Resource) -> wasmtime::Result<()> { - let stmt_ptr = state::InternalState::get_or_create_resource(self.app_name().clone()) - .get_stmt_state() - .delete_object_by_id(resource.rep()); - - if let Some(stmt_ptr) = stmt_ptr { + let app_state = get_statement_state().get_app_state(self.app_name())?; + if let Ok(stmt_ptr) = app_state.delete_resource(resource) { let _ = core::finalize(stmt_ptr as *mut _); } diff --git a/hermes/bin/src/runtime_extensions/mod.rs b/hermes/bin/src/runtime_extensions/mod.rs index 7952ee0aa..3c3e29f6f 100644 --- a/hermes/bin/src/runtime_extensions/mod.rs +++ b/hermes/bin/src/runtime_extensions/mod.rs @@ -4,10 +4,11 @@ use tracing::{span, Level}; -pub(crate) mod app_config; +mod app_config; pub(crate) mod bindings; pub mod hermes; -pub(crate) mod wasi; +mod resource_manager; +mod wasi; /// Advise Runtime Extensions of a new context pub(crate) fn new_context(ctx: &crate::runtime_context::HermesRuntimeContext) { diff --git a/hermes/bin/src/runtime_extensions/resource_manager.rs b/hermes/bin/src/runtime_extensions/resource_manager.rs new file mode 100644 index 000000000..e4d49873e --- /dev/null +++ b/hermes/bin/src/runtime_extensions/resource_manager.rs @@ -0,0 +1,203 @@ +//! Generalized, type safe `wasmtime::component::Resource` manager implementation. + +use std::{ + any::type_name, + ops::DerefMut, + sync::atomic::{AtomicU32, Ordering}, +}; + +use dashmap::DashMap; + +use crate::app::ApplicationName; + +/// `ResourceStorage` struct. +/// - `WitType` represents the type from the wit file definitions and which will appear in +/// the `wasmtime::component::Resource` object. +/// - `RustType` actually the type which is bound to the `WitType` and holds all the data +/// needed for the `WitType`. +pub(crate) struct ResourceStorage { + /// Map of id to resource object. + state: DashMap, + /// Next available address id of the resource. + available_address: AtomicU32, + /// `WitType` type phantom. + _phantom: std::marker::PhantomData, +} + +impl ResourceStorage +where WitType: 'static +{ + /// Creates new `ResourceStorage` instance. + pub(crate) fn new() -> Self { + Self { + state: DashMap::new(), + available_address: AtomicU32::default(), + _phantom: std::marker::PhantomData, + } + } + + /// Creates a new owned resource from the given object. + /// Stores a resources link to the original object in the resource manager. + pub(crate) fn create_resource( + &self, object: RustType, + ) -> wasmtime::component::Resource { + let available_address = self.available_address.load(Ordering::Acquire); + self.state.insert(available_address, object); + + // Increment the value of the available address to 1, + // so that it can be used for the next resource. + // Under the assumption that `ResourceStorage` will not handle too many resources at once, + // and will not hold resources for too long, saturating increment is safe. + self.available_address + .store(available_address.saturating_add(1), Ordering::Release); + + wasmtime::component::Resource::new_own(available_address) + } + + /// Creates a new owned resource from the given object. + /// Stores a resources link to the original object in the resource manager. + /// + /// NOTE: `&mut self` is used to enable the `rustc` borrow checker and prevent + /// potential deadlocking of the `DashMap`. + /// As `ResourceStorage` not supposed to be used in any other cases rather than as a + /// field of `ApplicationResourceStorage` (so no any `static` variable of this + /// type will be created), it is fine to add `&mut self` for this method. + pub(crate) fn get_object<'a>( + &'a mut self, resource: &wasmtime::component::Resource, + ) -> wasmtime::Result + 'a> { + self.state + .get_mut(&resource.rep()) + .ok_or(Self::resource_not_found_err()) + } + + /// Removes the resource from the resource manager. + /// Similar to the `drop` function, resource is releasing and consumed by this + /// function, thats why it is passed by value. + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn delete_resource( + &self, resource: wasmtime::component::Resource, + ) -> anyhow::Result { + self.state + .remove(&resource.rep()) + .map(|(_, v)| v) + .ok_or(Self::resource_not_found_err()) + } + + /// Resource not found error message. + fn resource_not_found_err() -> wasmtime::Error { + let msg = format!( + "Resource <{}, {}> not found, need to add resource first by calling `add_resource`", + type_name::(), + type_name::() + ); + wasmtime::Error::msg(msg) + } +} + +/// `ApplicationResourceStorage` struct. +/// - `WitType` represents the type from the wit file definitions and which will appear in +/// the `wasmtime::component::Resource` object. +/// - `RustType` actually the type which is bound to the `WitType` and holds all the data +/// needed for the `WitType`. +pub(crate) struct ApplicationResourceStorage { + /// Map of app name to resources. + state: DashMap>, +} + +impl ApplicationResourceStorage +where WitType: 'static +{ + /// Creates new `ApplicationResourceStorage` instance. + pub(crate) fn new() -> Self { + Self { + state: DashMap::new(), + } + } + + /// Adds new application to the resource manager. + /// If the application state already exists, do nothing. + pub(crate) fn add_app(&self, app_name: ApplicationName) { + if !self.state.contains_key(&app_name) { + self.state.insert(app_name, ResourceStorage::new()); + } + } + + /// Get application state from the resource manager. + /// To increase performance and reduce locking time, it's better to call + /// `drop(app_state)` immediately when the `app_state` is not needed anymore and don't + /// wait until it will be released by the compiler. + /// + /// **Locking behavior:** May deadlock if called when holding any sort of reference + /// into the map. + pub(crate) fn get_app_state<'a>( + &'a self, app_name: &ApplicationName, + ) -> anyhow::Result> + 'a> { + self.state + .get_mut(app_name) + .ok_or_else(|| anyhow::anyhow!(Self::app_not_found_err())) + } + + /// Removes application and all associated resources from the resource manager. + #[allow(dead_code)] + pub(crate) fn remove_app(&self, app_name: &ApplicationName) { + self.state.remove(app_name); + } + + /// Application not found error message. + fn app_not_found_err() -> wasmtime::Error { + let msg = format!( + "Application not found for resource <{}, {}>, need to add application first by calling `add_app`", + type_name::(), + type_name::() + ); + wasmtime::Error::msg(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct WitType; + + #[test] + fn test_resource_storage() { + let mut resource_manager = ResourceStorage::::new(); + + let object = 100; + let resource = resource_manager.create_resource(object); + let copied_resource = wasmtime::component::Resource::new_borrow(resource.rep()); + + assert_eq!(*resource_manager.get_object(&resource).unwrap(), object); + assert!(resource_manager.delete_resource(resource).is_ok()); + assert!(resource_manager.get_object(&copied_resource).is_err()); + assert!(resource_manager.delete_resource(copied_resource).is_err()); + } + + #[test] + fn test_app_resource_storage() { + let resource_manager = ApplicationResourceStorage::::new(); + let app_name_1 = ApplicationName("app_1".to_string()); + + { + assert!(resource_manager.get_app_state(&app_name_1).is_err()); + resource_manager.add_app(app_name_1.clone()); + assert!(resource_manager.get_app_state(&app_name_1).is_ok()); + resource_manager.remove_app(&app_name_1); + assert!(resource_manager.get_app_state(&app_name_1).is_err()); + } + + { + // Check preserving app state when instantiating the same app twice + let object = 100; + resource_manager.add_app(app_name_1.clone()); + let app_state = resource_manager.get_app_state(&app_name_1).unwrap(); + let res = app_state.create_resource(object); + + drop(app_state); + resource_manager.add_app(app_name_1.clone()); + let mut app_state = resource_manager.get_app_state(&app_name_1).unwrap(); + assert!(app_state.get_object(&res).is_ok()); + } + } +} diff --git a/hermes/bin/src/runtime_extensions/wasi/cli/host.rs b/hermes/bin/src/runtime_extensions/wasi/cli/host.rs index 74f2ef110..f32981fff 100644 --- a/hermes/bin/src/runtime_extensions/wasi/cli/host.rs +++ b/hermes/bin/src/runtime_extensions/wasi/cli/host.rs @@ -9,7 +9,7 @@ use crate::{ cli, io::streams::{InputStream, OutputStream}, }, - wasi::descriptors::{NUL_REP, STDERR_REP, STDOUT_REP}, + wasi::io::streams::{get_input_streams_state, get_output_streams_state}, }, }; @@ -48,20 +48,23 @@ impl cli::exit::Host for HermesRuntimeContext { impl cli::stdin::Host for HermesRuntimeContext { fn get_stdin(&mut self) -> wasmtime::Result> { warn!("Stdin is not supported"); - Ok(wasmtime::component::Resource::new_own(NUL_REP)) + let app_state = get_input_streams_state().get_app_state(self.app_name())?; + Ok(app_state.create_resource(Box::new(std::io::empty()))) } } impl cli::stdout::Host for HermesRuntimeContext { fn get_stdout(&mut self) -> wasmtime::Result> { // TODO: Redirect stdout to Hermes' logging api. - Ok(wasmtime::component::Resource::new_own(STDOUT_REP)) + let app_state = get_output_streams_state().get_app_state(self.app_name())?; + Ok(app_state.create_resource(Box::new(std::io::empty()))) } } impl cli::stderr::Host for HermesRuntimeContext { fn get_stderr(&mut self) -> wasmtime::Result> { // TODO: Redirect stderr to Hermes' logging api. - Ok(wasmtime::component::Resource::new_own(STDERR_REP)) + let app_state = get_output_streams_state().get_app_state(self.app_name())?; + Ok(app_state.create_resource(Box::new(std::io::empty()))) } } diff --git a/hermes/bin/src/runtime_extensions/wasi/context.rs b/hermes/bin/src/runtime_extensions/wasi/context.rs deleted file mode 100644 index ee97da150..000000000 --- a/hermes/bin/src/runtime_extensions/wasi/context.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Hermes WASI runtime context. - -use std::collections::HashMap; - -use super::descriptors::{Descriptor, Stream}; -use crate::hdf5::Dir; - -/// WASI context errors. -pub(crate) enum Error { - /// Represents trying to reference a descriptor or stream identifier that does not - /// exist. - NoEntry, -} - -/// WASI context result type. -pub(crate) type Result = std::result::Result; - -/// Contains all data needed to execute the WASI APIs. -#[derive(Clone, Debug, Default)] -pub(crate) struct WasiContext { - /// Descriptors currently opened in this context. - descriptors: HashMap, - /// Input streams currently opened in this context. - input_streams: HashMap, - /// Output streams currently opened in this context. - output_streams: HashMap, - /// List of preopen directories in this context. - preopens: Vec<(u32, String)>, -} - -impl WasiContext { - /// Stores a new [`Descriptor`] in this WASI context. - /// - /// Returns the identifier used to reference the descriptor. - pub fn put_descriptor(&mut self, desc: Descriptor) -> u32 { - loop { - // We add 2 here in order to reserve rep = 0 to STDOUT and - // rep = 1 to STDERR. - let rep = rand::random::().saturating_add(2); - - if self.descriptors.contains_key(&rep) { - continue; - } - - self.descriptors.insert(rep, desc); - break rep; - } - } - - /// Removes a [`Descriptor`] from this WASI context. - /// - /// This also closes the handle and the streams associated with it. - pub fn remove_descriptor(&mut self, rep: u32) { - self.descriptors.remove(&rep); - self.input_streams.remove(&rep); - self.output_streams.remove(&rep); - } - - /// Gets a reference to the [`Descriptor`] with the given identifier. - /// - /// Returns [`None`] if there's not descriptor with the given id. - pub fn descriptor(&self, rep: u32) -> Option<&Descriptor> { - self.descriptors.get(&rep) - } - - /// Gets a mutable reference to the [`Descriptor`] with the given identifier. - /// - /// Returns [`None`] if there's not descriptor with the given id. - pub fn descriptor_mut(&mut self, rep: u32) -> Option<&mut Descriptor> { - self.descriptors.get_mut(&rep) - } - - /// Stores in this WASI context a new output stream associated to the - /// [`Descriptor`] with the given identifier. - /// - /// Fails if there's no descriptor with the given id. - pub fn put_output_stream(&mut self, desc_rep: u32, offset: u64) -> Result<()> { - if !self.descriptors.contains_key(&desc_rep) { - return Err(Error::NoEntry); - } - - self.output_streams.insert(desc_rep, Stream::new(offset)); - - Ok(()) - } - - /// Removes the output stream associated with the given descriptor identifier. - pub fn remove_output_stream(&mut self, desc_rep: u32) { - self.output_streams.remove(&desc_rep); - } - - /// Gets a mutable reference to the output stream associated with the given - /// descriptor identifier. - /// - /// Returns [`None`] if there's not such output stream. - pub fn output_stream_mut(&mut self, desc_rep: u32) -> Option<&mut Stream> { - self.output_streams.get_mut(&desc_rep) - } - - /// Stores in this WASI context a new input stream associated to the [`Descriptor`] - /// with the given identifier. - /// - /// Fails if there's no descriptor with the given id. - pub fn put_input_stream(&mut self, desc_rep: u32, offset: u64) -> Result<()> { - if !self.descriptors.contains_key(&desc_rep) { - return Err(Error::NoEntry); - } - - self.input_streams.insert(desc_rep, Stream::new(offset)); - - Ok(()) - } - - /// Removes the input stream associated with the given descriptor identifier. - pub fn remove_input_stream(&mut self, desc_rep: u32) { - self.input_streams.remove(&desc_rep); - } - - /// Gets a mutable reference to the input stream associated with the given - /// descriptor identifier. - /// - /// Returns [`None`] if there's not such output stream. - pub fn input_stream_mut(&mut self, desc_rep: u32) -> Option<&mut Stream> { - self.input_streams.get_mut(&desc_rep) - } - - /// Adds a preopen directory to the preopens list. - pub fn put_preopen_dir(&mut self, path: String, dir: Dir) -> u32 { - let rep = self.put_descriptor(Descriptor::Dir(dir)); - self.preopens.push((rep, path)); - - rep - } - - /// Returns the list of descriptor identifiers and paths of the current preopen - /// directories. - pub fn preopen_dirs(&self) -> &Vec<(u32, String)> { - &self.preopens - } -} diff --git a/hermes/bin/src/runtime_extensions/wasi/descriptors.rs b/hermes/bin/src/runtime_extensions/wasi/descriptors.rs deleted file mode 100644 index 55360e1d0..000000000 --- a/hermes/bin/src/runtime_extensions/wasi/descriptors.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Hermes WASI filesystem descriptors. - -/// Identifier for the null file descriptor which discards all input/output. -pub(crate) const NUL_REP: u32 = 0; -/// Identifier for the STDOUT file descriptor. -/// NOTE: This is temporary and should be removed once we redirect STDOUT to the logging -/// API. -pub(crate) const STDOUT_REP: u32 = 1; -/// Identifier for the STDERR file descriptor. -/// NOTE: This is temporary and should be removed once we redirect STDERR to the logging -/// API. -pub(crate) const STDERR_REP: u32 = 2; - -/// Represents an open file or directory. -#[derive(Clone, Debug)] -pub enum Descriptor { - /// File descriptor. - File(crate::hdf5::File), - /// Directory descriptor. - Dir(crate::hdf5::Dir), -} - -/// Represents an open output stream. -#[derive(Clone, Debug, Default)] -pub struct Stream { - /// Stream position in the file. - at: u64, -} - -impl Stream { - /// Creates a new output stream associated with the given file descriptor at - /// the given offset. - pub(crate) fn new(offset: u64) -> Self { - Self { at: offset } - } - - /// Advances the stream offset. - pub(crate) fn advance(&mut self, len: u64) { - self.at += len; - } - - /// Returns the stream offset. - pub(crate) fn at(&self) -> u64 { - self.at - } -} diff --git a/hermes/bin/src/runtime_extensions/wasi/filesystem/host.rs b/hermes/bin/src/runtime_extensions/wasi/filesystem/host.rs index bf6665024..8c352eee5 100644 --- a/hermes/bin/src/runtime_extensions/wasi/filesystem/host.rs +++ b/hermes/bin/src/runtime_extensions/wasi/filesystem/host.rs @@ -1,5 +1,8 @@ //! Filesystem host implementation for WASM runtime. +use std::io::{Seek, SeekFrom}; + +use super::state::{get_state, Descriptor}; use crate::{ hdf5::Path, runtime_context::HermesRuntimeContext, @@ -8,14 +11,14 @@ use crate::{ filesystem::{ self, types::{ - Advice, Descriptor, DescriptorFlags, DescriptorStat, DescriptorType, - DirectoryEntry, DirectoryEntryStream, Error, ErrorCode, Filesize, - MetadataHashValue, NewTimestamp, OpenFlags, PathFlags, + Advice, Descriptor as WasiDescriptor, DescriptorFlags, DescriptorStat, + DescriptorType, DirectoryEntry, DirectoryEntryStream, Error, ErrorCode, + Filesize, MetadataHashValue, NewTimestamp, OpenFlags, PathFlags, }, }, io::streams::{InputStream, OutputStream}, }, - wasi::state::STATE, + wasi::io::streams::{get_input_streams_state, get_output_streams_state}, }, }; @@ -29,21 +32,20 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. fn read_via_stream( - &mut self, descriptor: wasmtime::component::Resource, offset: Filesize, + &mut self, res: wasmtime::component::Resource, offset: Filesize, ) -> wasmtime::Result, ErrorCode>> { - let res = STATE - .get_mut(self.app_name()) - .put_input_stream(descriptor.rep(), offset); - - if let Err(e) = res { - match e { - crate::runtime_extensions::wasi::context::Error::NoEntry => { - return Ok(Err(ErrorCode::NoEntry)) - }, - } - } + let mut fs_app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = fs_app_state.get_object(&res) else { + return Ok(Err(ErrorCode::BadDescriptor)); + }; + let mut file = match &*descriptor { + Descriptor::File(f) => f.clone(), + Descriptor::Dir(_) => return Ok(Err(ErrorCode::IsDirectory)), + }; + file.seek(SeekFrom::Start(offset))?; - Ok(Ok(wasmtime::component::Resource::new_own(descriptor.rep()))) + let input_streams_app_state = get_input_streams_state().get_app_state(self.app_name())?; + Ok(Ok(input_streams_app_state.create_resource(Box::new(file)))) } /// Return a stream for writing to a file, if available. @@ -53,21 +55,20 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// Note: This allows using `write-stream`, which is similar to `write` in /// POSIX. fn write_via_stream( - &mut self, descriptor: wasmtime::component::Resource, offset: Filesize, + &mut self, res: wasmtime::component::Resource, offset: Filesize, ) -> wasmtime::Result, ErrorCode>> { - let res = STATE - .get_mut(self.app_name()) - .put_output_stream(descriptor.rep(), offset); - - if let Err(e) = res { - match e { - crate::runtime_extensions::wasi::context::Error::NoEntry => { - return Ok(Err(ErrorCode::NoEntry)) - }, - } - } + let mut fs_app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = fs_app_state.get_object(&res) else { + return Ok(Err(ErrorCode::BadDescriptor)); + }; + let mut file = match &*descriptor { + Descriptor::File(f) => f.clone(), + Descriptor::Dir(_) => return Ok(Err(ErrorCode::IsDirectory)), + }; + file.seek(SeekFrom::Start(offset))?; - Ok(Ok(wasmtime::component::Resource::new_own(descriptor.rep()))) + let output_streams_app_state = get_output_streams_state().get_app_state(self.app_name())?; + Ok(Ok(output_streams_app_state.create_resource(Box::new(file)))) } /// Return a stream for appending to a file, if available. @@ -77,42 +78,27 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// Note: This allows using `write-stream`, which is similar to `write` with /// `O_APPEND` in in POSIX. fn append_via_stream( - &mut self, descriptor: wasmtime::component::Resource, + &mut self, res: wasmtime::component::Resource, ) -> wasmtime::Result, ErrorCode>> { - let mut app_state = STATE.get_mut(self.app_name()); - - let offset = match app_state.descriptor(descriptor.rep()) { - Some(Descriptor::File(f)) => { - let Ok(offset) = f.size().and_then(|size| { - TryInto::::try_into(size).map_err(|e| anyhow::anyhow!(e)) - }) else { - return Ok(Err(ErrorCode::Io)); - }; - - offset - }, - Some(Descriptor::Dir(_)) => return Ok(Err(ErrorCode::IsDirectory)), - None => return Ok(Err(ErrorCode::BadDescriptor)), + let mut fs_app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = fs_app_state.get_object(&res) else { + return Ok(Err(ErrorCode::BadDescriptor)); }; + let mut file = match &*descriptor { + Descriptor::File(f) => f.clone(), + Descriptor::Dir(_) => return Ok(Err(ErrorCode::IsDirectory)), + }; + file.seek(SeekFrom::End(0))?; - let res = app_state.put_output_stream(descriptor.rep(), offset); - - if let Err(e) = res { - match e { - crate::runtime_extensions::wasi::context::Error::NoEntry => { - return Ok(Err(ErrorCode::NoEntry)) - }, - } - } - - Ok(Ok(wasmtime::component::Resource::new_own(descriptor.rep()))) + let output_streams_app_state = get_output_streams_state().get_app_state(self.app_name())?; + Ok(Ok(output_streams_app_state.create_resource(Box::new(file)))) } /// Provide file advisory information on a descriptor. /// /// This is similar to `posix_fadvise` in POSIX. fn advise( - &mut self, _descriptor: wasmtime::component::Resource, _offset: Filesize, + &mut self, _descriptor: wasmtime::component::Resource, _offset: Filesize, _length: Filesize, _advice: Advice, ) -> wasmtime::Result> { todo!() @@ -125,7 +111,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `fdatasync` in POSIX. fn sync_data( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, ) -> wasmtime::Result> { todo!() } @@ -137,7 +123,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// Note: This returns the value that was the `fs_flags` value returned /// from `fdstat_get` in earlier versions of WASI. fn get_flags( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, ) -> wasmtime::Result> { todo!() } @@ -153,15 +139,16 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// Note: This returns the value that was the `fs_filetype` value returned /// from `fdstat_get` in earlier versions of WASI. fn get_type( - &mut self, fd: wasmtime::component::Resource, + &mut self, res: wasmtime::component::Resource, ) -> wasmtime::Result> { - let app_state = STATE.get(self.app_name()); - let descriptor = app_state.descriptor(fd.rep()); + let mut app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = app_state.get_object(&res) else { + return Ok(Err(ErrorCode::BadDescriptor)); + }; - let dt = match descriptor { - Some(Descriptor::File(_)) => DescriptorType::RegularFile, - Some(Descriptor::Dir(_)) => DescriptorType::Directory, - None => return Ok(Err(ErrorCode::BadDescriptor)), + let dt = match &*descriptor { + Descriptor::File(_) => DescriptorType::RegularFile, + Descriptor::Dir(_) => DescriptorType::Directory, }; Ok(Ok(dt)) @@ -172,7 +159,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. fn set_size( - &mut self, _descriptor: wasmtime::component::Resource, _size: Filesize, + &mut self, _descriptor: wasmtime::component::Resource, _size: Filesize, ) -> wasmtime::Result> { todo!() } @@ -183,7 +170,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. fn set_times( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, _data_access_timestamp: NewTimestamp, _data_modification_timestamp: NewTimestamp, ) -> wasmtime::Result> { todo!() @@ -201,7 +188,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `pread` in POSIX. fn read( - &mut self, _descriptor: wasmtime::component::Resource, _length: Filesize, + &mut self, _descriptor: wasmtime::component::Resource, _length: Filesize, _offset: Filesize, ) -> wasmtime::Result, bool), ErrorCode>> { todo!() @@ -217,7 +204,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `pwrite` in POSIX. fn write( - &mut self, _descriptor: wasmtime::component::Resource, _buffer: Vec, + &mut self, _descriptor: wasmtime::component::Resource, _buffer: Vec, _offset: Filesize, ) -> wasmtime::Result> { todo!() @@ -233,7 +220,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// directory. Multiple streams may be active on the same directory, and they /// do not interfere with each other. fn read_directory( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, ) -> wasmtime::Result, ErrorCode>> { todo!() @@ -246,7 +233,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `fsync` in POSIX. fn sync( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, ) -> wasmtime::Result> { todo!() } @@ -255,7 +242,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `mkdirat` in POSIX. fn create_directory_at( - &mut self, _descriptor: wasmtime::component::Resource, _path: String, + &mut self, _descriptor: wasmtime::component::Resource, _path: String, ) -> wasmtime::Result> { todo!() } @@ -270,15 +257,14 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This was called `fd_filestat_get` in earlier versions of WASI. fn stat( - &mut self, descriptor: wasmtime::component::Resource, + &mut self, res: wasmtime::component::Resource, ) -> wasmtime::Result> { - let mut app_state = STATE.get_mut(self.app_name()); - - let Some(fd) = app_state.descriptor_mut(descriptor.rep()) else { + let mut app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = app_state.get_object(&res) else { return Ok(Err(ErrorCode::BadDescriptor)); }; - let f = match fd { + let f = match &*descriptor { Descriptor::File(f) => f, Descriptor::Dir(_) => todo!(), }; @@ -308,8 +294,8 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This was called `path_filestat_get` in earlier versions of WASI. fn stat_at( - &mut self, _descriptor: wasmtime::component::Resource, _path_flags: PathFlags, - _path: String, + &mut self, _descriptor: wasmtime::component::Resource, + _path_flags: PathFlags, _path: String, ) -> wasmtime::Result> { todo!() } @@ -321,8 +307,8 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// Note: This was called `path_filestat_set_times` in earlier versions of /// WASI. fn set_times_at( - &mut self, _descriptor: wasmtime::component::Resource, _path_flags: PathFlags, - _path: String, _data_access_timestamp: NewTimestamp, + &mut self, _descriptor: wasmtime::component::Resource, + _path_flags: PathFlags, _path: String, _data_access_timestamp: NewTimestamp, _data_modification_timestamp: NewTimestamp, ) -> wasmtime::Result> { todo!() @@ -332,9 +318,9 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `linkat` in POSIX. fn link_at( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, _old_path_flags: PathFlags, _old_path: String, - _new_descriptor: wasmtime::component::Resource, _new_path: String, + _new_descriptor: wasmtime::component::Resource, _new_path: String, ) -> wasmtime::Result> { todo!() } @@ -358,20 +344,20 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `openat` in POSIX. fn open_at( - &mut self, descriptor: wasmtime::component::Resource, _path_flags: PathFlags, + &mut self, res: wasmtime::component::Resource, _path_flags: PathFlags, path: String, open_flags: OpenFlags, _flags: DescriptorFlags, - ) -> wasmtime::Result, ErrorCode>> { - let create = open_flags.contains(OpenFlags::CREATE); - let exclusive = open_flags.contains(OpenFlags::EXCLUSIVE); - - let mut app_state = STATE.get_mut(self.app_name()); - - let dir = match app_state.descriptor(descriptor.rep()) { - Some(Descriptor::Dir(dir)) => dir, - Some(_) => return Ok(Err(ErrorCode::NotDirectory)), - None => return Ok(Err(ErrorCode::BadDescriptor)), + ) -> wasmtime::Result, ErrorCode>> { + let mut app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = app_state.get_object(&res) else { + return Ok(Err(ErrorCode::BadDescriptor)); + }; + let dir = match &*descriptor { + Descriptor::Dir(dir) => dir, + Descriptor::File(_) => return Ok(Err(ErrorCode::NotDirectory)), }; + let create = open_flags.contains(OpenFlags::CREATE); + let exclusive = open_flags.contains(OpenFlags::EXCLUSIVE); let f = match dir.get_file(Path::from_str(&path)) { Ok(f) => { if create && exclusive { @@ -405,10 +391,8 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { } else { f }; - - let rep = app_state.put_descriptor(Descriptor::File(f)); - - Ok(Ok(wasmtime::component::Resource::new_own(rep))) + drop(descriptor); + Ok(Ok(app_state.create_resource(Descriptor::File(f)))) } /// Read the contents of a symbolic link. @@ -418,7 +402,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `readlinkat` in POSIX. fn readlink_at( - &mut self, _descriptor: wasmtime::component::Resource, _path: String, + &mut self, _descriptor: wasmtime::component::Resource, _path: String, ) -> wasmtime::Result> { todo!() } @@ -429,7 +413,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. fn remove_directory_at( - &mut self, _descriptor: wasmtime::component::Resource, _path: String, + &mut self, _descriptor: wasmtime::component::Resource, _path: String, ) -> wasmtime::Result> { todo!() } @@ -438,8 +422,9 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `renameat` in POSIX. fn rename_at( - &mut self, _old_descriptor: wasmtime::component::Resource, _old_path: String, - _new_descriptor: wasmtime::component::Resource, _new_path: String, + &mut self, _old_descriptor: wasmtime::component::Resource, + _old_path: String, _new_descriptor: wasmtime::component::Resource, + _new_path: String, ) -> wasmtime::Result> { todo!() } @@ -451,8 +436,8 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// Note: This is similar to `symlinkat` in POSIX. fn symlink_at( - &mut self, _old_descriptor: wasmtime::component::Resource, _old_path: String, - _new_path: String, + &mut self, _old_descriptor: wasmtime::component::Resource, + _old_path: String, _new_path: String, ) -> wasmtime::Result> { todo!() } @@ -462,10 +447,15 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// Return `error-code::is-directory` if the path refers to a directory. /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. fn unlink_file_at( - &mut self, descriptor: wasmtime::component::Resource, path: String, + &mut self, res: wasmtime::component::Resource, path: String, ) -> wasmtime::Result> { - match STATE.get_mut(self.app_name()).descriptor(descriptor.rep()) { - Some(Descriptor::Dir(dir)) => { + let mut app_state = get_state().get_app_state(self.app_name())?; + let Ok(descriptor) = app_state.get_object(&res) else { + return Ok(Err(ErrorCode::BadDescriptor)); + }; + + match &*descriptor { + Descriptor::Dir(dir) => { let path: Path = path.into(); if dir.get_file(path.clone()).is_err() { @@ -478,8 +468,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { Ok(Ok(())) } }, - Some(Descriptor::File(_)) => Ok(Err(ErrorCode::NotDirectory)), - None => Ok(Err(ErrorCode::BadDescriptor)), + Descriptor::File(_) => Ok(Err(ErrorCode::NotDirectory)), } } @@ -490,8 +479,8 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// wasi-filesystem does not expose device and inode numbers, so this function /// may be used instead. fn is_same_object( - &mut self, _descriptor: wasmtime::component::Resource, - _other: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, + _other: wasmtime::component::Resource, ) -> wasmtime::Result { todo!() } @@ -516,7 +505,7 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// However, none of these is required. fn metadata_hash( - &mut self, _descriptor: wasmtime::component::Resource, + &mut self, _descriptor: wasmtime::component::Resource, ) -> wasmtime::Result> { // TODO: Compute the actual hash Ok(Ok(MetadataHashValue { lower: 0, upper: 0 })) @@ -527,19 +516,15 @@ impl filesystem::types::HostDescriptor for HermesRuntimeContext { /// /// This performs the same hash computation as `metadata-hash`. fn metadata_hash_at( - &mut self, _descriptor: wasmtime::component::Resource, _path_flags: PathFlags, - _path: String, + &mut self, _descriptor: wasmtime::component::Resource, + _path_flags: PathFlags, _path: String, ) -> wasmtime::Result> { todo!() } - fn drop( - &mut self, descriptor: wasmtime::component::Resource, - ) -> wasmtime::Result<()> { - STATE - .get_mut(self.app_name()) - .remove_descriptor(descriptor.rep()); - + fn drop(&mut self, res: wasmtime::component::Resource) -> wasmtime::Result<()> { + let app_state = get_state().get_app_state(self.app_name())?; + app_state.delete_resource(res)?; Ok(()) } } @@ -581,15 +566,10 @@ impl filesystem::preopens::Host for HermesRuntimeContext { /// Return the set of preopened directories, and their path. fn get_directories( &mut self, - ) -> wasmtime::Result, String)>> { - let preopens = STATE - .get(self.app_name()) - .preopen_dirs() - .iter() - .cloned() - .map(|(rep, path)| (wasmtime::component::Resource::new_own(rep), path)) - .collect(); - - Ok(preopens) + ) -> wasmtime::Result, String)>> { + let vfs_root = self.vfs().root().clone(); + let app_state = get_state().get_app_state(self.app_name())?; + let res = app_state.create_resource(Descriptor::Dir(vfs_root)); + Ok(vec![(res, "/".to_string())]) } } diff --git a/hermes/bin/src/runtime_extensions/wasi/filesystem/mod.rs b/hermes/bin/src/runtime_extensions/wasi/filesystem/mod.rs index e61e411a5..b8f33aa54 100644 --- a/hermes/bin/src/runtime_extensions/wasi/filesystem/mod.rs +++ b/hermes/bin/src/runtime_extensions/wasi/filesystem/mod.rs @@ -1,11 +1,9 @@ //! Filesystem runtime extension implementation. -use super::state::STATE; - mod host; +mod state; /// Advise Runtime Extensions of a new context pub(crate) fn new_context(ctx: &crate::runtime_context::HermesRuntimeContext) { - let mut app_state = STATE.get_mut(ctx.app_name()); - app_state.put_preopen_dir("/".to_string(), ctx.vfs().root().clone()); + state::get_state().add_app(ctx.app_name().clone()); } diff --git a/hermes/bin/src/runtime_extensions/wasi/filesystem/state.rs b/hermes/bin/src/runtime_extensions/wasi/filesystem/state.rs new file mode 100644 index 000000000..7983ad7b2 --- /dev/null +++ b/hermes/bin/src/runtime_extensions/wasi/filesystem/state.rs @@ -0,0 +1,26 @@ +//! Filesystem state. + +use once_cell::sync::Lazy; + +use crate::runtime_extensions::{ + bindings::wasi::filesystem, resource_manager::ApplicationResourceStorage, +}; +/// Map of app name to descriptors. +pub(crate) type Descriptors = ApplicationResourceStorage; + +/// Represents an open file or directory. +#[derive(Clone, Debug)] +pub(crate) enum Descriptor { + /// File descriptor. + File(crate::hdf5::File), + /// Directory descriptor. + Dir(crate::hdf5::Dir), +} + +/// Global state to hold the descriptors resources. +static DESCRIPTORS_STATE: Lazy = Lazy::new(Descriptors::new); + +/// Get the filesystem state. +pub(super) fn get_state() -> &'static Descriptors { + &DESCRIPTORS_STATE +} diff --git a/hermes/bin/src/runtime_extensions/wasi/io/streams/host.rs b/hermes/bin/src/runtime_extensions/wasi/io/streams/host.rs index b5190f989..99ef34be6 100644 --- a/hermes/bin/src/runtime_extensions/wasi/io/streams/host.rs +++ b/hermes/bin/src/runtime_extensions/wasi/io/streams/host.rs @@ -1,17 +1,10 @@ //! IO Streams host implementation for WASM runtime. -use std::io::{Read, Seek, Write}; - +use super::{get_input_streams_state, get_output_streams_state}; use crate::{ runtime_context::HermesRuntimeContext, - runtime_extensions::{ - bindings::wasi::io::streams::{ - Host, HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, - }, - wasi::{ - descriptors::{Descriptor, NUL_REP, STDERR_REP, STDOUT_REP}, - state::STATE, - }, + runtime_extensions::bindings::wasi::io::streams::{ + Host, HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, }, }; @@ -48,69 +41,19 @@ impl HostInputStream for HermesRuntimeContext { fn blocking_read( &mut self, resource: wasmtime::component::Resource, len: u64, ) -> wasmtime::Result, StreamError>> { - // NUL_REP always returns 0 bytes. - if resource.rep() == NUL_REP { - return Ok(Ok(Vec::new())); - } - - let seek_start = match STATE - .get_mut(self.app_name()) - .input_stream_mut(resource.rep()) - { - Some(input_stream) => input_stream.at(), - None => return Ok(Err(StreamError::Closed)), + let mut app_state = get_input_streams_state().get_app_state(self.app_name())?; + let Ok(mut stream) = app_state.get_object(&resource) else { + return Ok(Err(StreamError::Closed)); }; - let buf = match STATE - .get_mut(self.app_name()) - .descriptor_mut(resource.rep()) - { - Some(fd) => { - match fd { - Descriptor::File(f) => { - let Ok(f_size) = f.size().and_then(|size| { - TryInto::::try_into(size).map_err(|e| anyhow::anyhow!(e)) - }) else { - // TODO: Probably should return LastOperationFailed here - return Ok(Err(StreamError::Closed)); - }; - - if f_size == 0 || seek_start >= f_size { - return Ok(Ok(Vec::new())); - } - - // At this point seek_start < f_size, so subtracting is safe - let Ok(read_len) = len.min(f_size - seek_start).try_into() else { - return Ok(Err(StreamError::Closed)); - }; - - let mut buf = vec![0u8; read_len]; - - if f.seek(std::io::SeekFrom::Start(seek_start)).is_err() { - return Ok(Err(StreamError::Closed)); - } - - if f.read(&mut buf).is_err() { - return Ok(Err(StreamError::Closed)); - } - - buf - }, - Descriptor::Dir(_) => todo!(), - } - }, - None => { - return Ok(Err(StreamError::Closed)); - }, + let Ok(len) = usize::try_from(len) else { + return Ok(Err(StreamError::Closed)); }; - - match STATE - .get_mut(self.app_name()) - .input_stream_mut(resource.rep()) - { - Some(input_stream) => input_stream.advance(buf.len() as u64), - None => return Ok(Err(StreamError::Closed)), - } + let mut buf = vec![0u8; len]; + let Ok(read_len) = stream.read(&mut buf) else { + return Ok(Err(StreamError::Closed)); + }; + buf.truncate(read_len); Ok(Ok(buf)) } @@ -139,9 +82,8 @@ impl HostInputStream for HermesRuntimeContext { } fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { - STATE - .get_mut(self.app_name()) - .remove_input_stream(rep.rep()); + let app_state = get_input_streams_state().get_app_state(self.app_name())?; + app_state.delete_resource(rep)?; Ok(()) } } @@ -202,75 +144,13 @@ impl HostOutputStream for HermesRuntimeContext { /// let _ = this.check-write(); // eliding error handling /// ``` fn blocking_write_and_flush( - &mut self, rep: wasmtime::component::Resource, contents: Vec, + &mut self, res: wasmtime::component::Resource, contents: Vec, ) -> wasmtime::Result> { - match rep.rep() { - NUL_REP => { - // Discard all bytes. - return Ok(Ok(())); - }, - STDOUT_REP => { - if std::io::stdout().write_all(&contents).is_err() { - return Ok(Err(StreamError::Closed)); - } - - if std::io::stdout().flush().is_err() { - return Ok(Err(StreamError::Closed)); - } - - return Ok(Ok(())); - }, - STDERR_REP => { - if std::io::stderr().write_all(&contents).is_err() { - return Ok(Err(StreamError::Closed)); - } - - if std::io::stderr().flush().is_err() { - return Ok(Err(StreamError::Closed)); - } - - return Ok(Ok(())); - }, - _ => {}, - } - - let seek_start = match STATE.get_mut(self.app_name()).output_stream_mut(rep.rep()) { - Some(output_stream) => output_stream.at(), - None => return Ok(Err(StreamError::Closed)), - }; - - match STATE.get_mut(self.app_name()).descriptor_mut(rep.rep()) { - Some(fd) => { - match fd { - // TODO: I believe it's better to return a LastOperationFailed error if - // any write operations fail. - Descriptor::File(f) => { - if f.seek(std::io::SeekFrom::Start(seek_start)).is_err() { - return Ok(Err(StreamError::Closed)); - } - - if f.write_all(&contents).is_err() { - return Ok(Err(StreamError::Closed)); - } - - if f.flush().is_err() { - return Ok(Err(StreamError::Closed)); - } - }, - Descriptor::Dir(_) => return Ok(Err(StreamError::Closed)), - } - }, - None => return Ok(Err(StreamError::Closed)), - }; + let mut app_state = get_output_streams_state().get_app_state(self.app_name())?; + let mut stream = app_state.get_object(&res)?; - match STATE.get_mut(self.app_name()).output_stream_mut(rep.rep()) { - Some(output_stream) => { - match contents.len().try_into() { - Ok(len) => output_stream.advance(len), - Err(_) => return Ok(Err(StreamError::Closed)), - } - }, - None => return Ok(Err(StreamError::Closed)), + if stream.write_all(&contents).is_err() { + return Ok(Err(StreamError::Closed)); } Ok(Ok(())) @@ -375,9 +255,8 @@ impl HostOutputStream for HermesRuntimeContext { } fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { - STATE - .get_mut(self.app_name()) - .remove_output_stream(rep.rep()); + let app_state = get_output_streams_state().get_app_state(self.app_name())?; + app_state.delete_resource(rep)?; Ok(()) } } diff --git a/hermes/bin/src/runtime_extensions/wasi/io/streams/mod.rs b/hermes/bin/src/runtime_extensions/wasi/io/streams/mod.rs index 22814385c..98441e477 100644 --- a/hermes/bin/src/runtime_extensions/wasi/io/streams/mod.rs +++ b/hermes/bin/src/runtime_extensions/wasi/io/streams/mod.rs @@ -1,6 +1,12 @@ //! IO Streams runtime extension implementation. mod host; +mod state; + +pub(crate) use state::{get_input_streams_state, get_output_streams_state}; /// Advise Runtime Extensions of a new context -pub(crate) fn new_context(_ctx: &crate::runtime_context::HermesRuntimeContext) {} +pub(crate) fn new_context(ctx: &crate::runtime_context::HermesRuntimeContext) { + get_input_streams_state().add_app(ctx.app_name().clone()); + get_output_streams_state().add_app(ctx.app_name().clone()); +} diff --git a/hermes/bin/src/runtime_extensions/wasi/io/streams/state.rs b/hermes/bin/src/runtime_extensions/wasi/io/streams/state.rs new file mode 100644 index 000000000..35eb2ddc4 --- /dev/null +++ b/hermes/bin/src/runtime_extensions/wasi/io/streams/state.rs @@ -0,0 +1,41 @@ +//! Streams state. + +use once_cell::sync::Lazy; + +use crate::runtime_extensions::{ + bindings::wasi::io::streams::{InputStream, OutputStream}, + resource_manager::ApplicationResourceStorage, +}; + +/// Helper super trait for `InputStream` which wraps a `std::io::Read` and +/// `std::io::Seek`. +pub(crate) trait InputStreamTrait: std::io::Read + std::io::Seek + Send + Sync {} +impl InputStreamTrait for T {} + +/// Map of app name to input streams resource holder. +pub(crate) type InputStreams = ApplicationResourceStorage>; + +/// Global state to hold the input streams resources. +static INPUT_STREAMS_STATE: Lazy = Lazy::new(InputStreams::new); + +/// Helper super trait for `OutputStream` which wraps a `std::io::Write` and +/// `std::io::Seek`. +pub(crate) trait OutputStreamTrait: std::io::Write + std::io::Seek + Send + Sync {} +impl OutputStreamTrait for T {} + +/// Map of app name to output streams resource holder. +pub(crate) type OutputStreams = + ApplicationResourceStorage>; + +/// Global state to hold the input streams resources. +static OUTPUT_STREAMS_STATE: Lazy = Lazy::new(OutputStreams::new); + +/// Get the input streams state. +pub(crate) fn get_input_streams_state() -> &'static InputStreams { + &INPUT_STREAMS_STATE +} + +/// Get the output streams state. +pub(crate) fn get_output_streams_state() -> &'static OutputStreams { + &OUTPUT_STREAMS_STATE +} diff --git a/hermes/bin/src/runtime_extensions/wasi/mod.rs b/hermes/bin/src/runtime_extensions/wasi/mod.rs index 232ba7a41..1172b28ba 100644 --- a/hermes/bin/src/runtime_extensions/wasi/mod.rs +++ b/hermes/bin/src/runtime_extensions/wasi/mod.rs @@ -2,13 +2,10 @@ pub(crate) mod cli; pub(crate) mod clocks; -pub(crate) mod context; -pub(crate) mod descriptors; pub(crate) mod filesystem; pub(crate) mod http; pub(crate) mod io; pub(crate) mod random; -mod state; /// Advise Runtime Extensions of a new context pub(crate) fn new_context(ctx: &crate::runtime_context::HermesRuntimeContext) { diff --git a/hermes/bin/src/runtime_extensions/wasi/state.rs b/hermes/bin/src/runtime_extensions/wasi/state.rs deleted file mode 100644 index 7b6d2a553..000000000 --- a/hermes/bin/src/runtime_extensions/wasi/state.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! WASI runtime extension state. - -use dashmap::{ - mapref::one::{Ref, RefMut}, - DashMap, -}; -use once_cell::sync::Lazy; - -use super::context::WasiContext; -use crate::app::ApplicationName; - -/// Holds WASI context for each application. -pub(super) struct State(DashMap); - -impl State { - /// Creates a new state. - pub(super) fn new() -> Self { - Self(DashMap::new()) - } - - /// Returns a mutable reference to the context of the given application. - /// - /// Creates a default one if it doesn't exist. - pub(super) fn get_mut( - &self, app_name: &ApplicationName, - ) -> RefMut { - if let Some(r) = self.0.get_mut(app_name) { - r - } else { - self.0.entry(app_name.clone()).or_default() - } - } - - /// Returns a reference to the context of the given application. - /// - /// Creates a default one if it doesn't exist. - pub(super) fn get(&self, app_name: &ApplicationName) -> Ref { - if let Some(r) = self.0.get(app_name) { - r - } else { - self.0.entry(app_name.clone()).or_default().downgrade() - } - } -} - -/// WASI runtime extension state. -pub(super) static STATE: Lazy = Lazy::new(State::new);