diff options
author | spacemaison <tuchsen@protonmail.com> | 2021-09-22 10:13:21 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-22 18:13:21 +0100 |
commit | c9372212f68fed52d45590b2dac271ab6270d943 (patch) | |
tree | 031f384ab012817656876b5db020b9e9c6a41a1f | |
parent | c39f02181052f1c0948001d4ae419009fc0df677 (diff) |
feat(plugin): add manifest to allow for plugin configuration (#660)
* feat(plugins-manifest): Add a plugins manifest to allow for more configuration of plugins
* refactor(plugins-manifest): Better storage of plugin metadata in wasm_vm
* fix(plugins-manifest): Inherit permissions from run configuration
* refactor(plugins-manifest): Rename things for more clarity
- The Plugins/Plugin structs had "Config" appended to them to clarify
that they're metadata about plugins, and not the plugins themselves.
- The PluginType::OncePerPane variant was renamed to be just
PluginType::Pane, and the documentation clarified to explain what it
is.
- The "service" nomenclature was completely removed in favor of
"headless".
* refactor(plugins-manifest): Move security warning into start plugin
* refactor(plugins-manifest): Remove hack in favor of standard method
* refactor(plugins-manifest): Change display of plugin location
The only time that a plugin location is displayed in Zellij is the
border of the pane. Having `zellij:strider` display instead of just
`strider` was a little annoying, so we're stripping out the scheme
information from a locations display.
* refactor(plugins-manifest): Add a little more documentation
* fix(plugins-manifest): Formatting
Co-authored-by: Jesse Tuchsen <not@disclosing>
25 files changed, 832 insertions, 185 deletions
diff --git a/Cargo.lock b/Cargo.lock index 66a0b3cbc..bf4ae0fd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,6 +749,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -971,6 +981,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] name = "indexmap" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1207,6 +1228,12 @@ dependencies = [ ] [[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1433,6 +1460,12 @@ dependencies = [ ] [[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] name = "pin-project-lite" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2108,6 +2141,21 @@ dependencies = [ ] [[package]] +name = "tinyvec" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] name = "tracing" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2180,6 +2228,21 @@ dependencies = [ ] [[package]] +name = "unicode-bidi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] name = "unicode-segmentation" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2207,6 +2270,19 @@ dependencies = [ ] [[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] name = "utf8parse" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2680,6 +2756,7 @@ dependencies = [ "serde_json", "typetag", "unicode-width", + "url", "wasmer", "wasmer-wasi", "zellij-utils", @@ -2720,6 +2797,7 @@ dependencies = [ "nix", "once_cell", "serde", + "serde_json", "serde_yaml", "signal-hook 0.3.9", "strip-ansi-escapes", @@ -2728,6 +2806,7 @@ dependencies = [ "tempfile", "termion", "unicode-width", + "url", "vte 0.10.1", "zellij-tile", ] diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index bd2c63768..b4d63d274 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -138,6 +138,7 @@ pub fn start_client( Box::new(opts), Box::new(config_options.clone()), layout.unwrap(), + Some(config.plugins.clone()), ) } }; diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index bc0abe310..c9f98ecf1 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -16,6 +16,7 @@ byteorder = "1.4.3" daemonize = "0.4.1" serde_json = "1.0" unicode-width = "0.1.8" +url = "2.2.2" wasmer = "1.0.0" wasmer-wasi = "1.0.0" cassowary = "0.3.0" diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 419aae4f6..36569522e 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -38,6 +38,7 @@ use zellij_utils::{ get_mode_info, layout::LayoutFromYaml, options::Options, + plugins::PluginsConfig, }, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, setup::get_default_data_dir, @@ -46,7 +47,13 @@ use zellij_utils::{ /// Instructions related to server-side application #[derive(Debug, Clone)] pub(crate) enum ServerInstruction { - NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, LayoutFromYaml), + NewClient( + ClientAttributes, + Box<CliArgs>, + Box<Options>, + LayoutFromYaml, + Option<PluginsConfig>, + ), Render(Option<String>), UnblockInputThread, ClientExit, @@ -58,8 +65,8 @@ pub(crate) enum ServerInstruction { impl From<ClientToServerMsg> for ServerInstruction { fn from(instruction: ClientToServerMsg) -> Self { match instruction { - ClientToServerMsg::NewClient(attrs, opts, options, layout) => { - ServerInstruction::NewClient(attrs, opts, options, layout) + ClientToServerMsg::NewClient(attrs, opts, options, layout, plugins) => { + ServerInstruction::NewClient(attrs, opts, options, layout, plugins) } ClientToServerMsg::AttachClient(attrs, force, options) => { ServerInstruction::AttachClient(attrs, force, options) @@ -193,15 +200,24 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { let (instruction, mut err_ctx) = server_receiver.recv().unwrap(); err_ctx.add_call(ContextType::IPCServer((&instruction).into())); match instruction { - ServerInstruction::NewClient(client_attributes, opts, config_options, layout) => { + ServerInstruction::NewClient( + client_attributes, + opts, + config_options, + layout, + plugins, + ) => { let session = init_session( os_input.clone(), - opts, - config_options.clone(), to_server.clone(), client_attributes, session_state.clone(), - layout.clone(), + SessionOptions { + opts, + layout: layout.clone(), + plugins, + config_options: config_options.clone(), + }, ); *session_data.write().unwrap() = Some(session); *session_state.write().unwrap() = SessionState::Attached; @@ -302,15 +318,26 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { drop(std::fs::remove_file(&socket_path)); } +pub struct SessionOptions { + pub opts: Box<CliArgs>, + pub config_options: Box<Options>, + pub layout: LayoutFromYaml, + pub plugins: Option<PluginsConfig>, +} + fn init_session( os_input: Box<dyn ServerOsApi>, - opts: Box<CliArgs>, - config_options: Box<Options>, to_server: SenderWithContext<ServerInstruction>, client_attributes: ClientAttributes, session_state: Arc<RwLock<SessionState>>, - layout: LayoutFromYaml, + options: SessionOptions, ) -> SessionMetaData { + let SessionOptions { + opts, + config_options, + layout, + plugins, + } = options; let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded(); let to_screen = SenderWithContext::new(to_screen); @@ -394,7 +421,7 @@ fn init_session( ); let store = Store::default(); - move || wasm_thread_main(plugin_bus, store, data_dir) + move || wasm_thread_main(plugin_bus, store, data_dir, plugins.unwrap_or_default()) }) .unwrap(); SessionMetaData { diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index e37e60621..761b1702f 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -65,6 +65,8 @@ pub(crate) struct Pty { task_handles: HashMap<RawFd, JoinHandle<()>>, } +use std::convert::TryFrom; + pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { loop { let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); @@ -104,7 +106,10 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { }); let merged_layout = layout.template.clone().insert_tab_layout(tab_layout); - pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone()); + let layout: Layout = + Layout::try_from(merged_layout).unwrap_or_else(|err| panic!("{}", err)); + + pty.spawn_terminals_for_layout(layout, terminal_action.clone()); if let Some(tab_name) = tab_name { // clear current name at first diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 62e0a2cb5..fa62cfce9 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -322,23 +322,18 @@ impl Tab { for (layout, position_and_size) in positions_and_size { // A plugin pane - if let Some(Run::Plugin(Some(plugin))) = &layout.run { + if let Some(Run::Plugin(run)) = layout.run.clone() { let (pid_tx, pid_rx) = channel(); + let pane_title = run.location.to_string(); self.senders - .send_to_plugin(PluginInstruction::Load( - pid_tx, - plugin.path.clone(), - tab_index, - plugin._allow_exec_host_cmd, - )) + .send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index)) .unwrap(); let pid = pid_rx.recv().unwrap(); - let title = String::from(plugin.path.as_path().as_os_str().to_string_lossy()); let mut new_plugin = PluginPane::new( pid, *position_and_size, self.senders.to_plugin.as_ref().unwrap().clone(), - title, + pane_title, ); new_plugin.set_borderless(layout.borderless); self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index aeea671ba..e1df2c64c 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -5,6 +5,7 @@ use crate::{ thread_bus::Bus, SessionState, }; +use std::convert::TryInto; use std::path::PathBuf; use std::sync::{Arc, RwLock}; use zellij_utils::input::command::TerminalAction; @@ -101,7 +102,7 @@ fn create_new_screen(size: Size) -> Screen { } fn new_tab(screen: &mut Screen, pid: i32) { - screen.apply_layout(LayoutTemplate::default().into(), vec![pid]); + screen.apply_layout(LayoutTemplate::default().try_into().unwrap(), vec![pid]); } #[test] diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index de5c10670..7f0fa71f8 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -6,6 +6,7 @@ use crate::{ thread_bus::ThreadSenders, SessionState, }; +use std::convert::TryInto; use std::path::PathBuf; use std::sync::{Arc, RwLock}; use zellij_utils::input::layout::LayoutTemplate; @@ -101,7 +102,11 @@ fn create_new_tab(size: Size) -> Tab { session_state, true, // draw pane frames ); - tab.apply_layout(LayoutTemplate::default().into(), vec![1], index); + tab.apply_layout( + LayoutTemplate::default().try_into().unwrap(), + vec![1], + index, + ); tab } diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index 40967c5a3..1d67e8477 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -1,7 +1,7 @@ -use log::{info, warn}; +use log::{debug, info, warn}; use std::collections::{HashMap, HashSet}; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process; use std::str::FromStr; use std::sync::{mpsc::Sender, Arc, Mutex}; @@ -9,6 +9,7 @@ use std::thread; use std::time::{Duration, Instant}; use serde::{de::DeserializeOwned, Serialize}; +use url::Url; use wasmer::{ imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value, WasmerEnv, @@ -24,12 +25,17 @@ use crate::{ thread_bus::{Bus, ThreadSenders}, }; use zellij_utils::errors::{ContextType, PluginContext}; -use zellij_utils::{input::command::TerminalAction, serde, zellij_tile}; +use zellij_utils::{ + input::command::TerminalAction, + input::layout::RunPlugin, + input::plugins::{PluginConfig, PluginType, PluginsConfig}, + serde, zellij_tile, +}; #[derive(Clone, Debug)] pub(crate) enum PluginInstruction { - Load(Sender<u32>, PathBuf, usize, bool), // tx_pid, path_of_plugin , tab_index, allow_exec_host_cmd - Update(Option<u32>, Event), // Focused plugin / broadcast, event data + Load(Sender<u32>, RunPlugin, usize), // tx_pid, plugin metadata, tab_index + Update(Option<u32>, Event), // Focused plugin / broadcast, event data Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols Unload(u32), Exit, @@ -50,83 +56,62 @@ impl From<&PluginInstruction> for PluginContext { #[derive(WasmerEnv, Clone)] pub(crate) struct PluginEnv { pub plugin_id: u32, - pub tab_index: usize, + pub plugin: PluginConfig, pub senders: ThreadSenders, pub wasi_env: WasiEnv, pub subscriptions: Arc<Mutex<HashSet<EventType>>>, - // FIXME: Once permission system is ready, this could be removed - pub _allow_exec_host_cmd: bool, plugin_own_data_dir: PathBuf, } // Thread main -------------------------------------------------------------------------------------------------------- -pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf) { +pub(crate) fn wasm_thread_main( + bus: Bus<PluginInstruction>, + store: Store, + data_dir: PathBuf, + plugins: PluginsConfig, +) { info!("Wasm main thread starts"); + let mut plugin_id = 0; let mut plugin_map = HashMap::new(); let plugin_dir = data_dir.join("plugins/"); let plugin_global_data_dir = plugin_dir.join("data"); fs::create_dir_all(plugin_global_data_dir.as_path()).unwrap(); + for plugin in plugins.iter() { + if let PluginType::Headless = plugin.run { + let (instance, plugin_env) = start_plugin( + plugin_id, + plugin, + 0, + &bus, + &store, + &data_dir, + &plugin_global_data_dir, + ); + plugin_map.insert(plugin_id, (instance, plugin_env)); + plugin_id += 1; + } + } + loop { let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Plugin((&event).into())); match event { - PluginInstruction::Load(pid_tx, path, tab_index, _allow_exec_host_cmd) => { - let wasm_bytes = fs::read(&path) - .or_else(|_| fs::read(&path.with_extension("wasm"))) - .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) - .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); - - // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that - let module = Module::new(&store, &wasm_bytes).unwrap(); - - let output = Pipe::new(); - let input = Pipe::new(); - let stderr = LoggingPipe::new( - path.as_path().file_name().unwrap().to_str().unwrap(), - plugin_id, - ); + PluginInstruction::Load(pid_tx, run, tab_index) => { + let plugin = plugins + .get(&run) + .unwrap_or_else(|| panic!("Plugin {:?} could not be resolved", run)); - let plugin_name = path.as_path().file_stem().unwrap(); - let plugin_own_data_dir = plugin_global_data_dir.join(plugin_name); - fs::create_dir_all(&plugin_own_data_dir).unwrap(); - - let mut wasi_env = WasiState::new("Zellij") - .env("CLICOLOR_FORCE", "1") - .map_dir("/host", ".") - .unwrap() - .map_dir("/data", plugin_own_data_dir.as_path()) - .unwrap() - .stdin(Box::new(input)) - .stdout(Box::new(output)) - .stderr(Box::new(stderr)) - .finalize() - .unwrap(); - - let wasi = wasi_env.import_object(&module).unwrap(); - - if _allow_exec_host_cmd { - info!("Plugin({:?}) is able to run any host command, this may lead to some security issues!", path); - } - - let plugin_env = PluginEnv { + let (instance, plugin_env) = start_plugin( plugin_id, + &plugin, tab_index, - senders: bus.senders.clone(), - wasi_env, - subscriptions: Arc::new(Mutex::new(HashSet::new())), - _allow_exec_host_cmd, - plugin_own_data_dir, - }; - |