use std::time::Duration;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
use crate::generated::yosh::plugin::commands::ExecOutput;
use crate::generated::yosh::plugin::files::{DirEntry, FileStat};
use crate::generated::yosh::plugin::types::{ErrorCode, HookName, IoStream};
use crate::generated::{PluginWorld, PluginWorldPre};
pub struct MetadataCtx {
table: ResourceTable,
wasi: WasiCtx,
}
impl Default for MetadataCtx {
fn default() -> Self {
let wasi = WasiCtxBuilder::new().build();
MetadataCtx {
table: ResourceTable::new(),
wasi,
}
}
}
impl WasiView for MetadataCtx {
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}
#[derive(Debug, Clone)]
pub struct ExtractedMetadata {
pub name: String,
pub version: String,
pub commands: Vec<String>,
pub required_capabilities: Vec<String>,
pub implemented_hooks: Vec<String>,
}
pub fn extract(engine: &Engine, wasm_bytes: &[u8]) -> Result<ExtractedMetadata, String> {
let component = Component::new(engine, wasm_bytes)
.map_err(|e| format!("metadata: compile component: {}", e))?;
let mut linker = Linker::<MetadataCtx>::new(engine);
register_wasi(&mut linker).map_err(|e| format!("metadata: register WASI: {}", e))?;
register_all_deny_imports(&mut linker)
.map_err(|e| format!("metadata: register deny stubs: {}", e))?;
let pre = PluginWorldPre::new(
linker
.instantiate_pre(&component)
.map_err(|e| format!("metadata: instantiate_pre: {}", e))?,
)
.map_err(|e| format!("metadata: bindings pre-init: {}", e))?;
let mut store = Store::new(engine, MetadataCtx::default());
store.set_epoch_deadline(1);
let watchdog_engine: Engine = engine.clone();
let _watchdog = std::thread::Builder::new()
.name("yosh-plugin-metadata-watchdog".to_string())
.spawn(move || {
std::thread::sleep(Duration::from_secs(5));
watchdog_engine.increment_epoch();
});
let plugin_world: PluginWorld = pre
.instantiate(&mut store)
.map_err(|e| format!("metadata: instantiate: {}", e))?;
let info = plugin_world
.yosh_plugin_plugin()
.call_metadata(&mut store)
.map_err(|e| format!("metadata: call: {}", e))?;
Ok(ExtractedMetadata {
name: info.name,
version: info.version,
commands: info.commands,
required_capabilities: info.required_capabilities,
implemented_hooks: info
.implemented_hooks
.into_iter()
.map(hook_name_to_string)
.collect(),
})
}
fn hook_name_to_string(h: HookName) -> String {
match h {
HookName::PreExec => "pre-exec".into(),
HookName::PostExec => "post-exec".into(),
HookName::OnCd => "on-cd".into(),
HookName::PrePrompt => "pre-prompt".into(),
}
}
fn register_wasi(linker: &mut Linker<MetadataCtx>) -> wasmtime::Result<()> {
wasmtime_wasi::add_to_linker_sync(linker)
}
fn register_all_deny_imports(linker: &mut Linker<MetadataCtx>) -> wasmtime::Result<()> {
let mut vars = linker.instance("yosh:plugin/variables@0.2.1")?;
vars.func_wrap(
"get",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_,): (String,)| {
Ok::<_, wasmtime::Error>((Err::<Option<String>, ErrorCode>(ErrorCode::Denied),))
},
)?;
vars.func_wrap(
"set",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, String)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
vars.func_wrap(
"export-env",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, String)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
let mut fs = linker.instance("yosh:plugin/filesystem@0.2.1")?;
fs.func_wrap(
"cwd",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (): ()| {
Ok::<_, wasmtime::Error>((Err::<String, ErrorCode>(ErrorCode::Denied),))
},
)?;
fs.func_wrap(
"set-cwd",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_,): (String,)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
let mut io = linker.instance("yosh:plugin/io@0.2.1")?;
io.func_wrap(
"write",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (IoStream, Vec<u8>)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
let mut files = linker.instance("yosh:plugin/files@0.2.1")?;
files.func_wrap(
"read-file",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_,): (String,)| {
Ok::<_, wasmtime::Error>((Err::<Vec<u8>, ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"read-dir",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_,): (String,)| {
Ok::<_, wasmtime::Error>((Err::<Vec<DirEntry>, ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"metadata",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_,): (String,)| {
Ok::<_, wasmtime::Error>((Err::<FileStat, ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"write-file",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, Vec<u8>)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"append-file",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, Vec<u8>)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"create-dir",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, bool)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"remove-file",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_,): (String,)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
files.func_wrap(
"remove-dir",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, bool)| {
Ok::<_, wasmtime::Error>((Err::<(), ErrorCode>(ErrorCode::Denied),))
},
)?;
let mut commands = linker.instance("yosh:plugin/commands@0.2.1")?;
commands.func_wrap(
"exec",
|_store: wasmtime::StoreContextMut<'_, MetadataCtx>, (_, _): (String, Vec<String>)| {
Ok::<_, wasmtime::Error>((Err::<ExecOutput, ErrorCode>(ErrorCode::Denied),))
},
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_ctx_default_constructs() {
let _c = MetadataCtx::default();
}
#[test]
fn linker_registration_smoke() {
let engine = crate::precompile::make_engine().unwrap();
let mut linker = Linker::<MetadataCtx>::new(&engine);
register_wasi(&mut linker).expect("wasi");
register_all_deny_imports(&mut linker).expect("deny stubs");
}
}