zellij/zellij-server/src/plugins/plugin_loader.rs
Jae-Heon Ji c8ddb23297
feat: add plugin permission system (#2624)
* WIP: add exaple of permission ui

* feat: add request permission ui

* feat: add caching permission in memory

* feat: add permission check

* feat: add file caching

* fix: changes request

* feat(ui): new status bar mode (#2619)

* supermode prototype

* fix integration tests

* fix tests

* style(fmt): rustfmt

* docs(changelog): status-bar supermode

* fix(rendering): occasional glitches while resizing (#2621)

* docs(changelog): resize glitches fix

* chore(version): bump development version

* Fix colored pane frames in mirrored sessions (#2625)

* server/panes/tiled: Fix colored frames

in mirrored sessions. Colored frames were previously ignored because
they were treated like floating panes when rendering tiled panes.

* CHANGELOG: Add PR #2625

* server/tab/unit: Fix unit tests for server.

* fix(sessions): use custom lists of adjectives and nouns for generating session names (#2122)

* Create custom lists of adjectives and nouns for generating session names

* move word lists to const slices

* add logic to retry name generation

* refactor

 - reuse the name generator
 - iterator instead of for loop

---------

Co-authored-by: Thomas Linford <linford.t@gmail.com>

* docs(changelog): generate session names with custom words list

* feat(plugins): make plugins configurable (#2646)

* work

* make every plugin entry point configurable

* make integration tests pass

* make e2e tests pass

* add test for plugin configuration

* add test snapshot

* add plugin config parsing test

* cleanups

* style(fmt): rustfmt

* style(comment): remove commented code

* docs(changelog): configurable plugins

* style(fmt): rustfmt

* touch up ui

* fix: don't save permission data in memory

* feat: load cached permission

* test: add example test (WIP)

* fix: issue event are always denied

* test: update snapshot

* apply formatting

* refactor: update default cache function

* test: add more new test

* apply formatting

* Revert "apply formatting"

This reverts commit a4e93703fbfdb6865131daa1c8b90fc5c36ab25e.

* apply format

* fix: update cache path

* apply format

* fix: cache path

* fix: update log level

* test for github workflow

* Revert "test for github workflow"

This reverts commit 01eff3bc5d1627a4e60bc6dac8ebe5500bc5b56e.

* refactor: permission cache

* fix(test): permission grant/deny race condition

* style(fmt): rustfmt

* style(fmt): rustfmt

* configure permissions

* permission denied test

* snapshot

* add ui for small plugins

* style(fmt): rustfmt

* some cleanups

---------

Co-authored-by: Aram Drevekenin <aram@poor.dev>
Co-authored-by: har7an <99636919+har7an@users.noreply.github.com>
Co-authored-by: Kyle Sutherland-Cash <kyle.sutherlandcash@gmail.com>
Co-authored-by: Thomas Linford <linford.t@gmail.com>
Co-authored-by: Thomas Linford <tlinford@users.noreply.github.com>
2023-08-12 15:35:42 +02:00

806 lines
29 KiB
Rust

use crate::plugins::plugin_map::{PluginEnv, PluginMap, RunningPlugin, Subscriptions};
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},
fs,
path::PathBuf,
sync::{Arc, Mutex},
};
use url::Url;
use wasmer::{ChainableNamedResolver, Instance, Module, Store};
use wasmer_wasi::{Pipe, WasiState};
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::PluginCapabilities,
errors::prelude::*,
input::command::TerminalAction,
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,
store: Store,
plugin: PluginConfig,
plugin_dir: &'a PathBuf,
tab_index: usize,
plugin_own_data_dir: PathBuf,
size: Size,
wasm_blob_on_hd: Option<(Vec<u8>, PathBuf)>,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
}
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,
store: Store,
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> 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,
&store,
&plugin_dir,
path_to_default_shell,
zellij_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
)?;
plugin_loader
.load_module_from_memory()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(instance, plugin_env, subscriptions)| {
plugin_loader.load_plugin_instance(
&instance,
&plugin_env,
&plugin_map,
&subscriptions,
)
})
.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: usize,
plugin_dir: PathBuf,
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
senders: ThreadSenders,
store: Store,
plugin_map: Arc<Mutex<PluginMap>>,
size: Size,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> 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,
&store,
plugin.clone(),
&plugin_dir,
tab_index,
size,
path_to_default_shell,
zellij_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
)?;
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(|(instance, plugin_env, subscriptions)| {
plugin_loader.load_plugin_instance(
&instance,
&plugin_env,
&plugin_map,
&subscriptions,
)
})
.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,
store: Store,
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> 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,
&store,
&plugin_dir,
path_to_default_shell.clone(),
zellij_cwd.clone(),
capabilities.clone(),
client_attributes.clone(),
default_shell.clone(),
default_layout.clone(),
)?;
plugin_loader
.load_module_from_memory()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(instance, plugin_env, subscriptions)| {
plugin_loader.load_plugin_instance(
&instance,
&plugin_env,
&plugin_map,
&subscriptions,
)
})?
}
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,
store: Store,
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> 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,
&store,
&plugin_dir,
path_to_default_shell,
zellij_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
)?;
plugin_loader
.compile_module()
.and_then(|module| plugin_loader.create_plugin_environment(module))
.and_then(|(instance, plugin_env, subscriptions)| {
plugin_loader.load_plugin_instance(
&instance,
&plugin_env,
&plugin_map,
&subscriptions,
)
})
.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,
store: &Store,
plugin: PluginConfig,
plugin_dir: &'a PathBuf,
tab_index: usize,
size: Size,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> Result<Self> {
let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
.join(format!("{}-{}", plugin_id, client_id));
create_plugin_fs_entries(&plugin_own_data_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,
store: store.clone(),
plugin,
plugin_dir,
tab_index,
plugin_own_data_dir,
size,
wasm_blob_on_hd: None,
path_to_default_shell,
zellij_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
})
}
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,
store: &Store,
plugin_dir: &'a PathBuf,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> 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.plugin_env.tab_index;
let size = Size {
rows: running_plugin.rows,
cols: running_plugin.columns,
};
let plugin_config = running_plugin.plugin_env.plugin.clone();
loading_indication.set_name(running_plugin.plugin_env.name());
PluginLoader::new(
plugin_cache,
loading_indication,
senders,
plugin_id,
client_id,
store,
plugin_config,
plugin_dir,
tab_index,
size,
path_to_default_shell,
zellij_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
)
}
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,
store: &Store,
plugin_dir: &'a PathBuf,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
) -> 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.plugin_env.tab_index;
let size = Size {
rows: running_plugin.rows,
cols: running_plugin.columns,
};
let plugin_config = running_plugin.plugin_env.plugin.clone();
loading_indication.set_name(running_plugin.plugin_env.name());
PluginLoader::new(
plugin_cache,
loading_indication,
senders,
plugin_id,
client_id,
store,
plugin_config,
plugin_dir,
tab_index,
size,
path_to_default_shell,
zellij_cwd,
capabilities,
client_attributes,
default_shell,
default_layout,
)
}
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_from_file(&self.store, &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> {
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_CACHE_DIR.to_owned())
.map_err(anyError::new)
.and_then(|_| {
// compile module
Module::new(&self.store, &wasm_bytes).map_err(anyError::new)
})
.and_then(|m| {
// serialize module to HD cache for faster loading in the future
m.serialize_to_file(&cached_path).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<(Instance, PluginEnv, Arc<Mutex<Subscriptions>>)> {
let (instance, plugin_env, subscriptions) =
self.create_plugin_instance_env_and_subscriptions(&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((instance, plugin_env, subscriptions))
}
pub fn create_plugin_instance_and_wasi_env_for_worker(
&mut self,
) -> Result<(Instance, PluginEnv)> {
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 (instance, plugin_env, _subscriptions) =
self.create_plugin_instance_env_and_subscriptions(&module)?;
Ok((instance, plugin_env))
}
pub fn load_plugin_instance(
&mut self,
instance: &Instance,
plugin_env: &PluginEnv,
plugin_map: &Arc<Mutex<PluginMap>>,
subscriptions: &Arc<Mutex<Subscriptions>>,
) -> Result<()> {
let err_context = || format!("failed to load plugin from instance {instance:#?}");
let main_user_instance = instance.clone();
let main_user_env = plugin_env.clone();
display_loading_stage!(
indicate_starting_plugin,
self.loading_indication,
self.senders,
self.plugin_id
);
let start_function = instance
.exports
.get_function("_start")
.with_context(err_context)?;
let load_function = instance
.exports
.get_function("load")
.with_context(err_context)?;
let mut workers = HashMap::new();
for (function_name, _exported_function) in instance.exports.iter().functions() {
if function_name.ends_with("_worker") {
let plugin_config = self.plugin.clone();
let (instance, plugin_env) =
self.create_plugin_instance_and_wasi_env_for_worker()?;
let start_function_for_worker = instance
.exports
.get_function("_start")
.with_context(err_context)?;
start_function_for_worker
.call(&[])
.with_context(err_context)?;
let worker =
RunningWorker::new(instance, &function_name, plugin_config, plugin_env);
let worker_sender = plugin_worker(worker);
workers.insert(function_name.into(), worker_sender);
}
}
start_function.call(&[]).with_context(err_context)?;
plugin_map.lock().unwrap().insert(
self.plugin_id,
self.client_id,
Arc::new(Mutex::new(RunningPlugin::new(
main_user_instance,
main_user_env,
self.size.rows,
self.size.cols,
))),
subscriptions.clone(),
workers,
);
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_env.wasi_env,
&protobuf_bytes,
// &self.plugin.userspace_configuration.inner(),
)
.with_context(err_context)?;
load_function.call(&[]).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.store,
&self.plugin_dir,
self.path_to_default_shell.clone(),
self.zellij_cwd.clone(),
self.capabilities.clone(),
self.client_attributes.clone(),
self.default_shell.clone(),
self.default_layout.clone(),
)?;
plugin_loader_for_client
.load_module_from_memory()
.and_then(|module| plugin_loader_for_client.create_plugin_environment(module))
.and_then(|(instance, plugin_env, subscriptions)| {
plugin_loader_for_client.load_plugin_instance(
&instance,
&plugin_env,
plugin_map,
&subscriptions,
)
})?
}
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_CACHE_DIR.join(&hash);
self.wasm_blob_on_hd = Some((wasm_bytes.clone(), cached_path.clone()));
Ok((wasm_bytes, cached_path))
},
}
}
fn create_plugin_instance_env_and_subscriptions(
&self,
module: &Module,
) -> Result<(Instance, PluginEnv, Arc<Mutex<Subscriptions>>)> {
let err_context = || {
format!(
"Failed to create instance, plugin env and subscriptions for plugin {}",
self.plugin_id
)
};
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
.map_dir("/host", self.zellij_cwd.clone())
.and_then(|wasi| wasi.map_dir("/data", &self.plugin_own_data_dir))
.and_then(|wasi| wasi.map_dir("/tmp", ZELLIJ_TMP_DIR.as_path()))
.and_then(|wasi| {
wasi.stdin(Box::new(Pipe::new()))
.stdout(Box::new(Pipe::new()))
.stderr(Box::new(LoggingPipe::new(
&self.plugin.location.to_string(),
self.plugin_id,
)))
.finalize()
})
.with_context(err_context)?;
let wasi = wasi_env.import_object(&module).with_context(err_context)?;
let mut mut_plugin = self.plugin.clone();
mut_plugin.set_tab_index(self.tab_index);
let plugin_env = PluginEnv {
plugin_id: self.plugin_id,
client_id: self.client_id,
plugin: mut_plugin,
permissions: Arc::new(Mutex::new(None)),
senders: self.senders.clone(),
wasi_env,
plugin_own_data_dir: self.plugin_own_data_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(),
};
let subscriptions = Arc::new(Mutex::new(HashSet::new()));
let zellij = zellij_exports(&self.store, &plugin_env, &subscriptions);
let instance =
Instance::new(&module, &zellij.chain_back(wasi)).with_context(err_context)?;
Ok((instance, plugin_env, subscriptions))
}
}
fn create_plugin_fs_entries(plugin_own_data_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(ZELLIJ_TMP_DIR.as_path())
.with_context(|| format!("failed to create tmpdir at {:?}", &ZELLIJ_TMP_DIR.as_path()))
.with_context(err_context)?;
Ok(())
}