use wasmtime::Engine;
use wasmtime::component::Linker;
use yosh_plugin_api::{
CAP_COMMANDS_EXEC, CAP_FILES_READ, CAP_FILES_WRITE, CAP_FILESYSTEM, CAP_IO, CAP_VARIABLES_READ,
CAP_VARIABLES_WRITE,
};
use super::host::{
HostContext, deny_commands_exec, deny_files_append_file, deny_files_create_dir,
deny_files_metadata, deny_files_read_dir, deny_files_read_file, deny_files_remove_dir,
deny_files_remove_file, deny_files_write_file, deny_filesystem_cwd, deny_filesystem_set_cwd,
deny_io_write, deny_variables_export_env, deny_variables_get, deny_variables_set,
host_commands_exec, host_files_append_file, host_files_create_dir, host_files_metadata,
host_files_read_dir, host_files_read_file, host_files_remove_dir, host_files_remove_file,
host_files_write_file, host_filesystem_cwd, host_filesystem_set_cwd, host_io_write,
host_variables_export_env, host_variables_get, host_variables_set,
};
#[inline]
fn has(allowed: u32, cap: u32) -> bool {
allowed & cap != 0
}
pub fn build_linker(engine: &Engine, allowed: u32) -> Result<Linker<HostContext>, wasmtime::Error> {
let mut linker = Linker::<HostContext>::new(engine);
wasmtime_wasi::add_to_linker_sync(&mut linker)?;
let mut vars = linker.instance("yosh:plugin/variables@0.2.1")?;
if has(allowed, CAP_VARIABLES_READ) {
vars.func_wrap("get", |store, (name,): (wasmtime::component::WasmStr,)| {
let name_str = name.to_str(&store)?;
Ok((host_variables_get(store.data(), &name_str),))
})?;
} else {
vars.func_wrap("get", |store, (name,): (wasmtime::component::WasmStr,)| {
let name_str = name.to_str(&store)?;
Ok((deny_variables_get(store.data(), &name_str),))
})?;
}
if has(allowed, CAP_VARIABLES_WRITE) {
vars.func_wrap(
"set",
|store, (name, value): (wasmtime::component::WasmStr, wasmtime::component::WasmStr)| {
let name_str = name.to_str(&store)?;
let value_str = value.to_str(&store)?;
Ok((host_variables_set(store.data(), &name_str, &value_str),))
},
)?;
vars.func_wrap(
"export-env",
|store, (name, value): (wasmtime::component::WasmStr, wasmtime::component::WasmStr)| {
let name_str = name.to_str(&store)?;
let value_str = value.to_str(&store)?;
Ok((host_variables_export_env(
store.data(),
&name_str,
&value_str,
),))
},
)?;
} else {
vars.func_wrap(
"set",
|store, (name, value): (wasmtime::component::WasmStr, wasmtime::component::WasmStr)| {
let name_str = name.to_str(&store)?;
let value_str = value.to_str(&store)?;
Ok((deny_variables_set(store.data(), &name_str, &value_str),))
},
)?;
vars.func_wrap(
"export-env",
|store, (name, value): (wasmtime::component::WasmStr, wasmtime::component::WasmStr)| {
let name_str = name.to_str(&store)?;
let value_str = value.to_str(&store)?;
Ok((deny_variables_export_env(
store.data(),
&name_str,
&value_str,
),))
},
)?;
}
let mut fs = linker.instance("yosh:plugin/filesystem@0.2.1")?;
if has(allowed, CAP_FILESYSTEM) {
fs.func_wrap("cwd", |mut store, (): ()| {
Ok((host_filesystem_cwd(store.data_mut()),))
})?;
fs.func_wrap(
"set-cwd",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((host_filesystem_set_cwd(store.data(), &path_str),))
},
)?;
} else {
fs.func_wrap("cwd", |mut store, (): ()| {
Ok((deny_filesystem_cwd(store.data_mut()),))
})?;
fs.func_wrap(
"set-cwd",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((deny_filesystem_set_cwd(store.data(), &path_str),))
},
)?;
}
use super::generated::yosh::plugin::types::IoStream;
let mut io = linker.instance("yosh:plugin/io@0.2.1")?;
if has(allowed, CAP_IO) {
io.func_wrap(
"write",
|store, (target, data): (IoStream, wasmtime::component::WasmList<u8>)| {
let bytes = data.as_le_slice(&store);
Ok((host_io_write(store.data(), target, bytes),))
},
)?;
} else {
io.func_wrap(
"write",
|store, (target, data): (IoStream, wasmtime::component::WasmList<u8>)| {
let bytes = data.as_le_slice(&store);
Ok((deny_io_write(store.data(), target, bytes),))
},
)?;
}
let mut files = linker.instance("yosh:plugin/files@0.2.1")?;
if has(allowed, CAP_FILES_READ) {
files.func_wrap(
"read-file",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((host_files_read_file(store.data(), &path_str),))
},
)?;
files.func_wrap(
"read-dir",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((host_files_read_dir(store.data(), &path_str),))
},
)?;
files.func_wrap(
"metadata",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((host_files_metadata(store.data(), &path_str),))
},
)?;
} else {
files.func_wrap(
"read-file",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((deny_files_read_file(store.data(), &path_str),))
},
)?;
files.func_wrap(
"read-dir",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((deny_files_read_dir(store.data(), &path_str),))
},
)?;
files.func_wrap(
"metadata",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((deny_files_metadata(store.data(), &path_str),))
},
)?;
}
if has(allowed, CAP_FILES_WRITE) {
files.func_wrap(
"write-file",
|store,
(path, data): (
wasmtime::component::WasmStr,
wasmtime::component::WasmList<u8>,
)| {
let path_str = path.to_str(&store)?;
let bytes = data.as_le_slice(&store);
Ok((host_files_write_file(store.data(), &path_str, bytes),))
},
)?;
files.func_wrap(
"append-file",
|store,
(path, data): (
wasmtime::component::WasmStr,
wasmtime::component::WasmList<u8>,
)| {
let path_str = path.to_str(&store)?;
let bytes = data.as_le_slice(&store);
Ok((host_files_append_file(store.data(), &path_str, bytes),))
},
)?;
files.func_wrap(
"create-dir",
|store, (path, recursive): (wasmtime::component::WasmStr, bool)| {
let path_str = path.to_str(&store)?;
Ok((host_files_create_dir(store.data(), &path_str, recursive),))
},
)?;
files.func_wrap(
"remove-file",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((host_files_remove_file(store.data(), &path_str),))
},
)?;
files.func_wrap(
"remove-dir",
|store, (path, recursive): (wasmtime::component::WasmStr, bool)| {
let path_str = path.to_str(&store)?;
Ok((host_files_remove_dir(store.data(), &path_str, recursive),))
},
)?;
} else {
files.func_wrap(
"write-file",
|store,
(path, data): (
wasmtime::component::WasmStr,
wasmtime::component::WasmList<u8>,
)| {
let path_str = path.to_str(&store)?;
let bytes = data.as_le_slice(&store);
Ok((deny_files_write_file(store.data(), &path_str, bytes),))
},
)?;
files.func_wrap(
"append-file",
|store,
(path, data): (
wasmtime::component::WasmStr,
wasmtime::component::WasmList<u8>,
)| {
let path_str = path.to_str(&store)?;
let bytes = data.as_le_slice(&store);
Ok((deny_files_append_file(store.data(), &path_str, bytes),))
},
)?;
files.func_wrap(
"create-dir",
|store, (path, recursive): (wasmtime::component::WasmStr, bool)| {
let path_str = path.to_str(&store)?;
Ok((deny_files_create_dir(store.data(), &path_str, recursive),))
},
)?;
files.func_wrap(
"remove-file",
|store, (path,): (wasmtime::component::WasmStr,)| {
let path_str = path.to_str(&store)?;
Ok((deny_files_remove_file(store.data(), &path_str),))
},
)?;
files.func_wrap(
"remove-dir",
|store, (path, recursive): (wasmtime::component::WasmStr, bool)| {
let path_str = path.to_str(&store)?;
Ok((deny_files_remove_dir(store.data(), &path_str, recursive),))
},
)?;
}
let mut commands = linker.instance("yosh:plugin/commands@0.2.1")?;
if has(allowed, CAP_COMMANDS_EXEC) {
commands.func_wrap(
"exec",
|mut store,
(program, args): (
wasmtime::component::WasmStr,
wasmtime::component::WasmList<wasmtime::component::WasmStr>,
)| {
let arg_wstrs: Vec<wasmtime::component::WasmStr> =
args.iter(&mut store).collect::<wasmtime::Result<_>>()?;
let program_str = program.to_str(&store)?;
let args_strs: Vec<std::borrow::Cow<'_, str>> = arg_wstrs
.iter()
.map(|w| w.to_str(&store))
.collect::<wasmtime::Result<_>>()?;
Ok((host_commands_exec(store.data(), &program_str, &args_strs),))
},
)?;
} else {
commands.func_wrap(
"exec",
|_store,
(_program, _args): (
wasmtime::component::WasmStr,
wasmtime::component::WasmList<wasmtime::component::WasmStr>,
)| { Ok((deny_commands_exec(),)) },
)?;
}
Ok(linker)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linker_construction_smoke() {
let mut config = wasmtime::Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config).expect("engine");
let _linker = build_linker(&engine, 0).expect("linker construction");
let _linker = build_linker(&engine, yosh_plugin_api::CAP_ALL).expect("linker w/ caps");
}
}