zellij/zellij-server/src/plugins/plugin_loader.rs
har7an 9f900a7325
Fix compiler warnings (#4026)
* chore(cargo): Update dependencies

for xtask subcommand to remove warnings about unknown compiler
attributes.

* deps(xtask): Remove `lazy_static`

as dependency, since the code we need can now be expressed using rusts
`std` builtin types.

* fix(xtask): Hide deprecated args

so the compiler doesn't complain about dead code any more.

* fix(xtask): Remove needless borrow.

* fix: Remove dead code

across the codebase, as discovered by the rust compiler.

* fix(server): Log previously ignored errors.

* fix(xtask): Remove needless `format!` calls.

* fix(xtask): Remove superfluous return statement.

* fix(client): Remove unused enum variant

from `ClientInstruction` and all code attached to it.

* fix(server): Restore functions needed in tests.

* chore: Apply rustfmt.

* fix(server): Add missing code.

* fix: Remove leftover warnings.

* CHANGELOG: Add MR #4026.
2025-02-28 14:09:35 +00:00

911 lines
33 KiB
Rust

use crate::plugins::plugin_map::{
PluginEnv, PluginMap, RunningPlugin, VecDequeInputStream, WriteOutputStream,
};
use crate::plugins::plugin_worker::{plugin_worker, RunningWorker};
use crate::plugins::zellij_exports::{wasi_write_object, zellij_exports};
use crate::plugins::PluginId;
use highway::{HighwayHash, PortableHash};
use log::info;
use std::{
collections::{HashMap, HashSet, VecDeque},
fs,
path::PathBuf,
sync::{Arc, Mutex},
};
use url::Url;
use wasmtime::{Engine, Instance, Linker, Module, Store};
use wasmtime_wasi::{preview1::WasiP1Ctx, DirPerms, FilePerms, WasiCtxBuilder};
use zellij_utils::consts::ZELLIJ_PLUGIN_ARTIFACT_DIR;
use zellij_utils::prost::Message;
use crate::{
logging_pipe::LoggingPipe, screen::ScreenInstruction, thread_bus::ThreadSenders,
ui::loading_indication::LoadingIndication, ClientId,
};
use zellij_utils::plugin_api::action::ProtobufPluginConfiguration;
use zellij_utils::{
consts::{ZELLIJ_CACHE_DIR, ZELLIJ_SESSION_CACHE_DIR, ZELLIJ_TMP_DIR},
data::{InputMode, PluginCapabilities},
errors::prelude::*,
input::command::TerminalAction,
input::keybinds::Keybinds,
input::layout::Layout,
input::plugins::PluginConfig,
ipc::ClientAttributes,
pane_size::Size,
};
macro_rules! display_loading_stage {
($loading_stage:ident, $loading_indication:expr, $senders:expr, $plugin_id:expr) => {{
$loading_indication.$loading_stage();
drop(
$senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
$plugin_id,
$loading_indication.clone(),
)),
);
}};
}
pub struct PluginLoader<'a> {
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
plugin_path: PathBuf,
loading_indication: &'a mut LoadingIndication,
senders: ThreadSenders,
plugin_id: PluginId,
client_id: ClientId,
engine: Engine,
plugin: PluginConfig,
plugin_dir: &'a PathBuf,
tab_index: Option<usize>,
plugin_own_data_dir: PathBuf,
plugin_own_cache_dir: PathBuf,
size: Size,
wasm_blob_on_hd: Option<(Vec<u8>, PathBuf)>,
path_to_default_shell: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Keybinds,
}
impl<'a> PluginLoader<'a> {
pub fn reload_plugin_from_memory(
plugin_id: PluginId,
plugin_dir: PathBuf,
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
senders: ThreadSenders,
engine: Engine,
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
) -> Result<()> {
let err_context = || format!("failed to reload plugin {plugin_id} from memory");
let mut connected_clients: Vec<ClientId> =
connected_clients.lock().unwrap().iter().copied().collect();
if connected_clients.is_empty() {
return Err(anyhow!("No connected clients, cannot reload plugin"));
}
let first_client_id = connected_clients.remove(0);
let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes(
&plugin_cache,
&plugin_map,
loading_indication,
&senders,
plugin_id,
first_client_id,
engine,
&plugin_dir,
path_to_default_shell,
Some(plugin_cwd),
capabilities,
client_attributes,
default_shell,
default_layout,
layout_dir,
)?;
plugin_loader
.load_module_from_memory()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(store, instance)| {
plugin_loader.load_plugin_instance(store, &instance, &plugin_map)
})
.and_then(|_| {
plugin_loader.clone_instance_for_other_clients(&connected_clients, &plugin_map)
})
.with_context(err_context)?;
display_loading_stage!(end, loading_indication, senders, plugin_id);
Ok(())
}
pub fn start_plugin(
plugin_id: PluginId,
client_id: ClientId,
plugin: &PluginConfig,
tab_index: Option<usize>,
plugin_dir: PathBuf,
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
senders: ThreadSenders,
engine: Engine,
plugin_map: Arc<Mutex<PluginMap>>,
size: Size,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
skip_cache: bool,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Keybinds,
) -> Result<()> {
let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}");
let mut plugin_loader = PluginLoader::new(
&plugin_cache,
loading_indication,
&senders,
plugin_id,
client_id,
engine,
plugin.clone(),
&plugin_dir,
tab_index,
size,
path_to_default_shell,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
layout_dir,
default_mode,
keybinds,
)?;
if skip_cache {
plugin_loader
.compile_module()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(store, instance)| {
plugin_loader.load_plugin_instance(store, &instance, &plugin_map)
})
.and_then(|_| {
plugin_loader.clone_instance_for_other_clients(
&connected_clients.lock().unwrap(),
&plugin_map,
)
})
.with_context(err_context)?;
} else {
plugin_loader
.load_module_from_memory()
.or_else(|_e| plugin_loader.load_module_from_hd_cache())
.or_else(|_e| plugin_loader.compile_module())
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(store, instance)| {
plugin_loader.load_plugin_instance(store, &instance, &plugin_map)
})
.and_then(|_| {
plugin_loader.clone_instance_for_other_clients(
&connected_clients.lock().unwrap(),
&plugin_map,
)
})
.with_context(err_context)?;
};
display_loading_stage!(end, loading_indication, senders, plugin_id);
Ok(())
}
pub fn add_client(
client_id: ClientId,
plugin_dir: PathBuf,
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
senders: ThreadSenders,
engine: Engine,
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Keybinds,
) -> Result<()> {
let mut new_plugins = HashSet::new();
for plugin_id in plugin_map.lock().unwrap().plugin_ids() {
new_plugins.insert((plugin_id, client_id));
}
for (plugin_id, existing_client_id) in new_plugins {
let mut plugin_loader = PluginLoader::new_from_different_client_id(
&plugin_cache,
&plugin_map,
loading_indication,
&senders,
plugin_id,
existing_client_id,
engine.clone(),
&plugin_dir,
path_to_default_shell.clone(),
plugin_cwd.clone(),
capabilities.clone(),
client_attributes.clone(),
default_shell.clone(),
default_layout.clone(),
layout_dir.clone(),
default_mode,
keybinds.clone(),
)?;
plugin_loader
.load_module_from_memory()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(store, instance)| {
plugin_loader.load_plugin_instance(store, &instance, &plugin_map)
})?
}
connected_clients.lock().unwrap().push(client_id);
Ok(())
}
pub fn reload_plugin(
plugin_id: PluginId,
plugin_dir: PathBuf,
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
senders: ThreadSenders,
engine: Engine,
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
) -> Result<()> {
let err_context = || format!("failed to reload plugin id {plugin_id}");
let mut connected_clients: Vec<ClientId> =
connected_clients.lock().unwrap().iter().copied().collect();
if connected_clients.is_empty() {
return Err(anyhow!("No connected clients, cannot reload plugin"));
}
let first_client_id = connected_clients.remove(0);
let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes(
&plugin_cache,
&plugin_map,
loading_indication,
&senders,
plugin_id,
first_client_id,
engine,
&plugin_dir,
path_to_default_shell,
None,
capabilities,
client_attributes,
default_shell,
default_layout,
layout_dir,
)?;
plugin_loader
.compile_module()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(store, instance)| {
plugin_loader.load_plugin_instance(store, &instance, &plugin_map)
})
.and_then(|_| {
plugin_loader.clone_instance_for_other_clients(&connected_clients, &plugin_map)
})
.with_context(err_context)?;
display_loading_stage!(end, loading_indication, senders, plugin_id);
Ok(())
}
pub fn new(
plugin_cache: &Arc<Mutex<HashMap<PathBuf, Module>>>,
loading_indication: &'a mut LoadingIndication,
senders: &ThreadSenders,
plugin_id: PluginId,
client_id: ClientId,
engine: Engine,
plugin: PluginConfig,
plugin_dir: &'a PathBuf,
tab_index: Option<usize>,
size: Size,
path_to_default_shell: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Keybinds,
) -> Result<Self> {
let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
.join(format!("{}-{}", plugin_id, client_id));
let plugin_own_cache_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
.join(format!("plugin_cache"));
create_plugin_fs_entries(&plugin_own_data_dir, &plugin_own_cache_dir)?;
let plugin_path = plugin.path.clone();
Ok(PluginLoader {
plugin_cache: plugin_cache.clone(),
plugin_path,
loading_indication,
senders: senders.clone(),
plugin_id,
client_id,
engine,
plugin,
plugin_dir,
tab_index,
plugin_own_data_dir,
plugin_own_cache_dir,
size,
wasm_blob_on_hd: None,
path_to_default_shell,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
layout_dir,
default_mode,
keybinds,
})
}
pub fn new_from_existing_plugin_attributes(
plugin_cache: &Arc<Mutex<HashMap<PathBuf, Module>>>,
plugin_map: &Arc<Mutex<PluginMap>>,
loading_indication: &'a mut LoadingIndication,
senders: &ThreadSenders,
plugin_id: PluginId,
client_id: ClientId,
engine: Engine,
plugin_dir: &'a PathBuf,
path_to_default_shell: PathBuf,
cwd: Option<PathBuf>,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let (running_plugin, _subscriptions, _workers) = {
let mut plugin_map = plugin_map.lock().unwrap();
plugin_map
.remove_single_plugin(plugin_id, client_id)
.with_context(err_context)?
};
let running_plugin = running_plugin.lock().unwrap();
let tab_index = running_plugin.store.data().tab_index;
let size = Size {
rows: running_plugin.rows,
cols: running_plugin.columns,
};
let keybinds = running_plugin.store.data().keybinds.clone();
let default_mode = running_plugin.store.data().default_mode;
let plugin_config = running_plugin.store.data().plugin.clone();
// prefer the explicitly given cwd, otherwise copy it from the running plugin
// (when reloading a plugin, we want to copy it, when starting a new plugin instance from
// meomory, we want to reset it)
let plugin_cwd = cwd.unwrap_or_else(|| running_plugin.store.data().plugin_cwd.clone());
loading_indication.set_name(running_plugin.store.data().name());
PluginLoader::new(
plugin_cache,
loading_indication,
senders,
plugin_id,
client_id,
engine,
plugin_config,
plugin_dir,
tab_index,
size,
path_to_default_shell,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
layout_dir,
default_mode,
keybinds,
)
}
pub fn new_from_different_client_id(
plugin_cache: &Arc<Mutex<HashMap<PathBuf, Module>>>,
plugin_map: &Arc<Mutex<PluginMap>>,
loading_indication: &'a mut LoadingIndication,
senders: &ThreadSenders,
plugin_id: PluginId,
client_id: ClientId,
engine: Engine,
plugin_dir: &'a PathBuf,
path_to_default_shell: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
keybinds: Keybinds,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let running_plugin = {
let plugin_map = plugin_map.lock().unwrap();
plugin_map
.get_running_plugin(plugin_id, None)
.with_context(err_context)?
.clone()
};
let running_plugin = running_plugin.lock().unwrap();
let tab_index = running_plugin.store.data().tab_index;
let size = Size {
rows: running_plugin.rows,
cols: running_plugin.columns,
};
let plugin_config = running_plugin.store.data().plugin.clone();
loading_indication.set_name(running_plugin.store.data().name());
PluginLoader::new(
plugin_cache,
loading_indication,
senders,
plugin_id,
client_id,
engine,
plugin_config,
plugin_dir,
tab_index,
size,
path_to_default_shell,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
layout_dir,
default_mode,
keybinds,
)
}
pub fn load_module_from_memory(&mut self) -> Result<Module> {
display_loading_stage!(
indicate_loading_plugin_from_memory,
self.loading_indication,
self.senders,
self.plugin_id
);
let module = self
.plugin_cache
.lock()
.unwrap()
.remove(&self.plugin_path)
.ok_or(anyhow!("Plugin is not stored in memory"))?;
display_loading_stage!(
indicate_loading_plugin_from_memory_success,
self.loading_indication,
self.senders,
self.plugin_id
);
Ok(module)
}
pub fn load_module_from_hd_cache(&mut self) -> Result<Module> {
display_loading_stage!(
indicate_loading_plugin_from_memory_notfound,
self.loading_indication,
self.senders,
self.plugin_id
);
display_loading_stage!(
indicate_loading_plugin_from_hd_cache,
self.loading_indication,
self.senders,
self.plugin_id
);
let (_wasm_bytes, cached_path) = self.plugin_bytes_and_cache_path()?;
let timer = std::time::Instant::now();
let module = unsafe { Module::deserialize_file(&self.engine, &cached_path)? };
log::info!(
"Loaded plugin '{}' from cache folder at '{}' in {:?}",
self.plugin_path.display(),
ZELLIJ_CACHE_DIR.display(),
timer.elapsed(),
);
display_loading_stage!(
indicate_loading_plugin_from_hd_cache_success,
self.loading_indication,
self.senders,
self.plugin_id
);
Ok(module)
}
pub fn compile_module(&mut self) -> Result<Module> {
self.loading_indication.override_previous_error();
display_loading_stage!(
indicate_loading_plugin_from_hd_cache_notfound,
self.loading_indication,
self.senders,
self.plugin_id
);
display_loading_stage!(
indicate_compiling_plugin,
self.loading_indication,
self.senders,
self.plugin_id
);
let (wasm_bytes, cached_path) = self.plugin_bytes_and_cache_path()?;
let timer = std::time::Instant::now();
let err_context = || "failed to recover cache dir";
let module = fs::create_dir_all(ZELLIJ_PLUGIN_ARTIFACT_DIR.as_path())
.map_err(anyError::new)
.and_then(|_| {
// compile module
Module::new(&self.engine, &wasm_bytes)
})
.and_then(|m| {
// serialize module to HD cache for faster loading in the future
fs::write(&cached_path, m.serialize()?).map_err(anyError::new)?;
log::info!(
"Compiled plugin '{}' in {:?}",
self.plugin_path.display(),
timer.elapsed()
);
Ok(m)
})
.with_context(err_context)?;
Ok(module)
}
pub fn create_plugin_environment(
&mut self,
module: Module,
) -> Result<(Store<PluginEnv>, Instance)> {
let (store, instance) = self.create_plugin_instance_env(&module)?;
// Only do an insert when everything went well!
let cloned_plugin = self.plugin.clone();
self.plugin_cache
.lock()
.unwrap()
.insert(cloned_plugin.path, module);
Ok((store, instance))
}
pub fn create_plugin_instance_and_wasi_env_for_worker(
&mut self,
) -> Result<(Store<PluginEnv>, Instance)> {
let err_context = || {
format!(
"Failed to create instance and plugin env for worker {}",
self.plugin_id
)
};
let module = self
.plugin_cache
.lock()
.unwrap()
.get(&self.plugin.path)
.with_context(err_context)?
.clone();
let (store, instance) = self.create_plugin_instance_env(&module)?;
Ok((store, instance))
}
pub fn load_plugin_instance(
&mut self,
mut store: Store<PluginEnv>,
instance: &Instance,
plugin_map: &Arc<Mutex<PluginMap>>,
) -> Result<()> {
let err_context = || format!("failed to load plugin from instance {instance:#?}");
let main_user_instance = instance.clone();
display_loading_stage!(
indicate_starting_plugin,
self.loading_indication,
self.senders,
self.plugin_id
);
let start_function = instance
.get_typed_func::<(), ()>(&mut store, "_start")
.with_context(err_context)?;
let load_function = instance
.get_typed_func::<(), ()>(&mut store, "load")
.with_context(err_context)?;
let mut workers = HashMap::new();
for function_name in instance
.exports(&mut store)
.filter_map(|export| export.clone().into_func().map(|_| export.name()))
{
if function_name.ends_with("_worker") {
let (mut store, instance) =
self.create_plugin_instance_and_wasi_env_for_worker()?;
let start_function_for_worker = instance
.get_typed_func::<(), ()>(&mut store, "_start")
.with_context(err_context)?;
start_function_for_worker
.call(&mut store, ())
.with_context(err_context)?;
let worker = RunningWorker::new(store, instance, &function_name);
let worker_sender = plugin_worker(worker);
workers.insert(function_name.into(), worker_sender);
}
}
let subscriptions = store.data().subscriptions.clone();
let plugin = Arc::new(Mutex::new(RunningPlugin::new(
store,
main_user_instance,
self.size.rows,
self.size.cols,
)));
plugin_map.lock().unwrap().insert(
self.plugin_id,
self.client_id,
plugin.clone(),
subscriptions,
workers,
);
start_function
.call(&mut plugin.lock().unwrap().store, ())
.with_context(err_context)?;
let protobuf_plugin_configuration: ProtobufPluginConfiguration = self
.plugin
.userspace_configuration
.clone()
.try_into()
.map_err(|e| anyhow!("Failed to serialize user configuration: {:?}", e))?;
let protobuf_bytes = protobuf_plugin_configuration.encode_to_vec();
wasi_write_object(plugin.lock().unwrap().store.data(), &protobuf_bytes)
.with_context(err_context)?;
load_function
.call(&mut plugin.lock().unwrap().store, ())
.with_context(err_context)?;
display_loading_stage!(
indicate_starting_plugin_success,
self.loading_indication,
self.senders,
self.plugin_id
);
display_loading_stage!(
indicate_writing_plugin_to_cache,
self.loading_indication,
self.senders,
self.plugin_id
);
display_loading_stage!(
indicate_writing_plugin_to_cache_success,
self.loading_indication,
self.senders,
self.plugin_id
);
Ok(())
}
pub fn clone_instance_for_other_clients(
&mut self,
connected_clients: &[ClientId],
plugin_map: &Arc<Mutex<PluginMap>>,
) -> Result<()> {
if !connected_clients.is_empty() {
display_loading_stage!(
indicate_cloning_plugin_for_other_clients,
self.loading_indication,
self.senders,
self.plugin_id
);
for client_id in connected_clients {
if client_id == &self.client_id {
// don't reload the plugin once more for ourselves
continue;
}
let mut loading_indication = LoadingIndication::new("".into());
let mut plugin_loader_for_client = PluginLoader::new_from_different_client_id(
&self.plugin_cache.clone(),
&plugin_map,
&mut loading_indication,
&self.senders.clone(),
self.plugin_id,
*client_id,
self.engine.clone(),
&self.plugin_dir,
self.path_to_default_shell.clone(),
self.plugin_cwd.clone(),
self.capabilities.clone(),
self.client_attributes.clone(),
self.default_shell.clone(),
self.default_layout.clone(),
self.layout_dir.clone(),
self.default_mode,
self.keybinds.clone(),
)?;
plugin_loader_for_client
.load_module_from_memory()
.and_then(|module| plugin_loader_for_client.create_plugin_environment(module))
.and_then(|(store, instance)| {
plugin_loader_for_client.load_plugin_instance(store, &instance, plugin_map)
})?
}
display_loading_stage!(
indicate_cloning_plugin_for_other_clients_success,
self.loading_indication,
self.senders,
self.plugin_id
);
}
Ok(())
}
fn plugin_bytes_and_cache_path(&mut self) -> Result<(Vec<u8>, PathBuf)> {
match self.wasm_blob_on_hd.as_ref() {
Some((wasm_bytes, cached_path)) => Ok((wasm_bytes.clone(), cached_path.clone())),
None => {
if self.plugin._allow_exec_host_cmd {
info!(
"Plugin({:?}) is able to run any host command, this may lead to some security issues!",
self.plugin.path
);
}
// The plugins blob as stored on the filesystem
let wasm_bytes = self.plugin.resolve_wasm_bytes(&self.plugin_dir)?;
let hash: String = PortableHash::default()
.hash256(&wasm_bytes)
.iter()
.map(ToString::to_string)
.collect();
let cached_path = ZELLIJ_PLUGIN_ARTIFACT_DIR.join(&hash);
self.wasm_blob_on_hd = Some((wasm_bytes.clone(), cached_path.clone()));
Ok((wasm_bytes, cached_path))
},
}
}
pub fn create_wasi_ctx(
host_dir: &PathBuf,
data_dir: &PathBuf,
cache_dir: &PathBuf,
tmp_dir: &PathBuf,
plugin_url: &String,
plugin_id: PluginId,
stdin_pipe: Arc<Mutex<VecDeque<u8>>>,
stdout_pipe: Arc<Mutex<VecDeque<u8>>>,
) -> Result<WasiP1Ctx> {
let err_context = || format!("Failed to create wasi_ctx");
let dirs = vec![
("/host".to_owned(), host_dir.clone()),
("/data".to_owned(), data_dir.clone()),
("/cache".to_owned(), cache_dir.clone()),
("/tmp".to_owned(), tmp_dir.clone()),
];
let dirs = dirs.into_iter().filter(|(_dir_name, dir)| {
// note that this does not protect against TOCTOU errors
// eg. if one or more of these folders existed at the time of check but was deleted
// before we mounted in in the wasi environment, we'll crash
// when we move to a new wasi environment, we should address this with locking if
// there's no built-in solution
dir.try_exists().ok().unwrap_or(false)
});
let mut wasi_ctx_builder = WasiCtxBuilder::new();
wasi_ctx_builder.env("CLICOLOR_FORCE", "1");
for (guest_path, host_path) in dirs {
wasi_ctx_builder
.preopened_dir(host_path, guest_path, DirPerms::all(), FilePerms::all())
.with_context(err_context)?;
}
wasi_ctx_builder
.stdin(VecDequeInputStream(stdin_pipe.clone()))
.stdout(WriteOutputStream(stdout_pipe.clone()))
.stderr(WriteOutputStream(Arc::new(Mutex::new(LoggingPipe::new(
plugin_url, plugin_id,
)))));
let wasi_ctx = wasi_ctx_builder.build_p1();
Ok(wasi_ctx)
}
fn create_plugin_instance_env(&self, module: &Module) -> Result<(Store<PluginEnv>, Instance)> {
let err_context = || {
format!(
"Failed to create instance, plugin env and subscriptions for plugin {}",
self.plugin_id
)
};
let stdin_pipe = Arc::new(Mutex::new(VecDeque::new()));
let stdout_pipe = Arc::new(Mutex::new(VecDeque::new()));
let wasi_ctx = PluginLoader::create_wasi_ctx(
&self.plugin_cwd,
&self.plugin_own_data_dir,
&self.plugin_own_cache_dir,
&ZELLIJ_TMP_DIR,
&self.plugin.location.to_string(),
self.plugin_id,
stdin_pipe.clone(),
stdout_pipe.clone(),
)?;
let plugin = self.plugin.clone();
let plugin_env = PluginEnv {
plugin_id: self.plugin_id,
client_id: self.client_id,
plugin,
permissions: Arc::new(Mutex::new(None)),
senders: self.senders.clone(),
wasi_ctx,
plugin_own_data_dir: self.plugin_own_data_dir.clone(),
plugin_own_cache_dir: self.plugin_own_cache_dir.clone(),
tab_index: self.tab_index,
path_to_default_shell: self.path_to_default_shell.clone(),
capabilities: self.capabilities.clone(),
client_attributes: self.client_attributes.clone(),
default_shell: self.default_shell.clone(),
default_layout: self.default_layout.clone(),
plugin_cwd: self.plugin_cwd.clone(),
input_pipes_to_unblock: Arc::new(Mutex::new(HashSet::new())),
input_pipes_to_block: Arc::new(Mutex::new(HashSet::new())),
layout_dir: self.layout_dir.clone(),
default_mode: self.default_mode.clone(),
subscriptions: Arc::new(Mutex::new(HashSet::new())),
keybinds: self.keybinds.clone(),
stdin_pipe,
stdout_pipe,
};
let mut store = Store::new(&self.engine, plugin_env);
let mut linker = Linker::new(&self.engine);
wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |plugin_env: &mut PluginEnv| {
&mut plugin_env.wasi_ctx
})
.unwrap();
zellij_exports(&mut linker);
let instance = linker
.instantiate(&mut store, module)
.with_context(err_context)?;
if let Some(func) = instance.get_func(&mut store, "_initialize") {
func.typed::<(), ()>(&store)?.call(&mut store, ())?;
}
Ok((store, instance))
}
}
fn create_plugin_fs_entries(
plugin_own_data_dir: &PathBuf,
plugin_own_cache_dir: &PathBuf,
) -> Result<()> {
let err_context = || "failed to create plugin fs entries";
// Create filesystem entries mounted into WASM.
// We create them here to get expressive error messages in case they fail.
fs::create_dir_all(&plugin_own_data_dir)
.with_context(|| format!("failed to create datadir in {plugin_own_data_dir:?}"))
.with_context(err_context)?;
fs::create_dir_all(&plugin_own_cache_dir)
.with_context(|| format!("failed to create cache dir in {plugin_own_cache_dir:?}"))
.with_context(err_context)?;
fs::create_dir_all(ZELLIJ_TMP_DIR.as_path())
.with_context(|| format!("failed to create tmpdir at {:?}", &ZELLIJ_TMP_DIR.as_path()))
.with_context(err_context)?;
Ok(())
}