Skip to content

Commit

Permalink
feat(plugins): allow changing the plugin's /host folder (under a ne…
Browse files Browse the repository at this point in the history
…w permission) (#3827)

* working without notifying plugins

* permissions and events

* cleanups and formatting

* style(fmt): rustfmt
  • Loading branch information
imsnif authored Dec 1, 2024
1 parent 90ecd8f commit 0c21eae
Show file tree
Hide file tree
Showing 16 changed files with 301 additions and 37 deletions.
7 changes: 7 additions & 0 deletions zellij-server/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ pub enum PluginInstruction {
},
WatchFilesystem,
ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId),
ChangePluginHostDir(PathBuf, PluginId, ClientId),
Exit,
}

Expand Down Expand Up @@ -204,6 +205,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginContext::FailedToWriteConfigToDisk
},
PluginInstruction::ListClientsToPlugin(..) => PluginContext::ListClientsToPlugin,
PluginInstruction::ChangePluginHostDir(..) => PluginContext::ChangePluginHostDir,
}
}
}
Expand Down Expand Up @@ -886,6 +888,11 @@ pub(crate) fn plugin_thread_main(
PluginInstruction::WatchFilesystem => {
wasm_bridge.start_fs_watcher_if_not_started();
},
PluginInstruction::ChangePluginHostDir(new_host_folder, plugin_id, client_id) => {
wasm_bridge
.change_plugin_host_dir(new_host_folder, plugin_id, client_id)
.non_fatal();
},
PluginInstruction::Exit => {
break;
},
Expand Down
85 changes: 54 additions & 31 deletions zellij-server/src/plugins/plugin_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::{
};
use url::Url;
use wasmtime::{Engine, Instance, Linker, Module, Store};
use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder};
use wasmtime_wasi::{preview1::WasiP1Ctx, DirPerms, FilePerms, WasiCtxBuilder};
use zellij_utils::consts::ZELLIJ_PLUGIN_ARTIFACT_DIR;
use zellij_utils::prost::Message;

Expand Down Expand Up @@ -64,7 +64,7 @@ pub struct PluginLoader<'a> {
size: Size,
wasm_blob_on_hd: Option<(Vec<u8>, PathBuf)>,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
Expand All @@ -85,7 +85,7 @@ impl<'a> PluginLoader<'a> {
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
Expand All @@ -110,7 +110,7 @@ impl<'a> PluginLoader<'a> {
engine,
&plugin_dir,
path_to_default_shell,
Some(zellij_cwd),
Some(plugin_cwd),
capabilities,
client_attributes,
default_shell,
Expand Down Expand Up @@ -145,7 +145,7 @@ impl<'a> PluginLoader<'a> {
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
Expand All @@ -168,7 +168,7 @@ impl<'a> PluginLoader<'a> {
tab_index,
size,
path_to_default_shell,
zellij_cwd,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
Expand Down Expand Up @@ -222,7 +222,7 @@ impl<'a> PluginLoader<'a> {
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
Expand All @@ -246,7 +246,7 @@ impl<'a> PluginLoader<'a> {
engine.clone(),
&plugin_dir,
path_to_default_shell.clone(),
zellij_cwd.clone(),
plugin_cwd.clone(),
capabilities.clone(),
client_attributes.clone(),
default_shell.clone(),
Expand Down Expand Up @@ -333,7 +333,7 @@ impl<'a> PluginLoader<'a> {
tab_index: Option<usize>,
size: Size,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
Expand Down Expand Up @@ -366,7 +366,7 @@ impl<'a> PluginLoader<'a> {
size,
wasm_blob_on_hd: None,
path_to_default_shell,
zellij_cwd,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
Expand Down Expand Up @@ -412,7 +412,7 @@ impl<'a> PluginLoader<'a> {
// 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 zellij_cwd = cwd.unwrap_or_else(|| running_plugin.store.data().plugin_cwd.clone());
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,
Expand All @@ -426,7 +426,7 @@ impl<'a> PluginLoader<'a> {
tab_index,
size,
path_to_default_shell,
zellij_cwd,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
Expand All @@ -446,7 +446,7 @@ impl<'a> PluginLoader<'a> {
engine: Engine,
plugin_dir: &'a PathBuf,
path_to_default_shell: PathBuf,
zellij_cwd: PathBuf,
plugin_cwd: PathBuf,
capabilities: PluginCapabilities,
client_attributes: ClientAttributes,
default_shell: Option<TerminalAction>,
Expand Down Expand Up @@ -483,7 +483,7 @@ impl<'a> PluginLoader<'a> {
tab_index,
size,
path_to_default_shell,
zellij_cwd,
plugin_cwd,
capabilities,
client_attributes,
default_shell,
Expand Down Expand Up @@ -736,7 +736,7 @@ impl<'a> PluginLoader<'a> {
self.engine.clone(),
&self.plugin_dir,
self.path_to_default_shell.clone(),
self.zellij_cwd.clone(),
self.plugin_cwd.clone(),
self.capabilities.clone(),
self.client_attributes.clone(),
self.default_shell.clone(),
Expand Down Expand Up @@ -784,18 +784,22 @@ impl<'a> PluginLoader<'a> {
},
}
}
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
)
};
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(), self.zellij_cwd.clone()),
("/data".to_owned(), self.plugin_own_data_dir.clone()),
("/cache".to_owned(), self.plugin_own_cache_dir.clone()),
("/tmp".to_owned(), ZELLIJ_TMP_DIR.clone()),
("/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
Expand All @@ -812,16 +816,35 @@ impl<'a> PluginLoader<'a> {
.preopened_dir(host_path, guest_path, DirPerms::all(), FilePerms::all())
.with_context(err_context)?;
}
let stdin_pipe = Arc::new(Mutex::new(VecDeque::new()));
let stdout_pipe = Arc::new(Mutex::new(VecDeque::new()));
wasi_ctx_builder
.stdin(VecDequeInputStream(stdin_pipe.clone()))
.stdout(WriteOutputStream(stdout_pipe.clone()))
.stderr(WriteOutputStream(Arc::new(Mutex::new(LoggingPipe::new(
&self.plugin.location.to_string(),
self.plugin_id,
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,
Expand All @@ -838,7 +861,7 @@ impl<'a> PluginLoader<'a> {
client_attributes: self.client_attributes.clone(),
default_shell: self.default_shell.clone(),
default_layout: self.default_layout.clone(),
plugin_cwd: self.zellij_cwd.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(),
Expand Down
104 changes: 103 additions & 1 deletion zellij-server/src/plugins/wasm_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::plugins::pipes::{
};
use crate::plugins::plugin_loader::PluginLoader;
use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions};

use crate::plugins::plugin_worker::MessageToWorker;
use crate::plugins::watch_filesystem::watch_filesystem;
use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object};
Expand All @@ -18,7 +19,7 @@ use std::{
use wasmtime::{Engine, Module};
use zellij_utils::async_channel::Sender;
use zellij_utils::async_std::task::{self, JoinHandle};
use zellij_utils::consts::ZELLIJ_CACHE_DIR;
use zellij_utils::consts::{ZELLIJ_CACHE_DIR, ZELLIJ_TMP_DIR};
use zellij_utils::data::{InputMode, PermissionStatus, PermissionType, PipeMessage, PipeSource};
use zellij_utils::downloader::Downloader;
use zellij_utils::input::keybinds::Keybinds;
Expand Down Expand Up @@ -736,6 +737,107 @@ impl WasmBridge {
}
Ok(())
}
pub fn change_plugin_host_dir(
&mut self,
new_host_dir: PathBuf,
plugin_id_to_update: PluginId,
client_id_to_update: ClientId,
) -> Result<()> {
let plugins_to_change: Vec<(
PluginId,
ClientId,
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
)> = self
.plugin_map
.lock()
.unwrap()
.running_plugins_and_subscriptions()
.iter()
.cloned()
.filter(|(plugin_id, _client_id, _running_plugin, _subscriptions)| {
// TODO: cache this somehow in this case...
!&self
.cached_events_for_pending_plugins
.contains_key(&plugin_id)
})
.collect();
task::spawn({
let senders = self.senders.clone();
async move {
match new_host_dir.try_exists() {
Ok(false) => {
log::error!(
"Failed to change folder to {},: folder does not exist",
new_host_dir.display()
);
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
Some(plugin_id_to_update),
Some(client_id_to_update),
Event::FailedToChangeHostFolder(Some(format!(
"Folder {} does not exist",
new_host_dir.display()
))),
)]));
return;
},
Err(e) => {
log::error!(
"Failed to change folder to {},: {}",
new_host_dir.display(),
e
);
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
Some(plugin_id_to_update),
Some(client_id_to_update),
Event::FailedToChangeHostFolder(Some(e.to_string())),
)]));
return;
},
_ => {},
}
for (plugin_id, client_id, running_plugin, _subscriptions) in &plugins_to_change {
if plugin_id == &plugin_id_to_update && client_id == &client_id_to_update {
let mut running_plugin = running_plugin.lock().unwrap();
let plugin_env = running_plugin.store.data_mut();
let stdin_pipe = plugin_env.stdin_pipe.clone();
let stdout_pipe = plugin_env.stdout_pipe.clone();
let wasi_ctx = PluginLoader::create_wasi_ctx(
&new_host_dir,
&plugin_env.plugin_own_data_dir,
&plugin_env.plugin_own_cache_dir,
&ZELLIJ_TMP_DIR,
&plugin_env.plugin.location.to_string(),
plugin_env.plugin_id,
stdin_pipe.clone(),
stdout_pipe.clone(),
);
match wasi_ctx {
Ok(wasi_ctx) => {
drop(std::mem::replace(&mut plugin_env.wasi_ctx, wasi_ctx));
plugin_env.plugin_cwd = new_host_dir.clone();

let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
Some(*plugin_id),
Some(*client_id),
Event::HostFolderChanged(new_host_dir.clone()),
)]));
},
Err(e) => {
let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
Some(*plugin_id),
Some(*client_id),
Event::FailedToChangeHostFolder(Some(e.to_string())),
)]));
log::error!("Failed to create wasi ctx: {}", e);
},
}
}
}
}
});
Ok(())
}
pub fn pipe_messages(
&mut self,
mut messages: Vec<(Option<PluginId>, Option<ClientId>, PipeMessage)>,
Expand Down
Loading

0 comments on commit 0c21eae

Please sign in to comment.