summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorspacemaison <tuchsen@protonmail.com>2021-09-22 10:13:21 -0700
committerGitHub <noreply@github.com>2021-09-22 18:13:21 +0100
commitc9372212f68fed52d45590b2dac271ab6270d943 (patch)
tree031f384ab012817656876b5db020b9e9c6a41a1f
parentc39f02181052f1c0948001d4ae419009fc0df677 (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>
-rw-r--r--Cargo.lock79
-rw-r--r--zellij-client/src/lib.rs1
-rw-r--r--zellij-server/Cargo.toml1
-rw-r--r--zellij-server/src/lib.rs49
-rw-r--r--zellij-server/src/pty.rs7
-rw-r--r--zellij-server/src/tab.rs13
-rw-r--r--zellij-server/src/unit/screen_tests.rs3
-rw-r--r--zellij-server/src/unit/tab_tests.rs7
-rw-r--r--zellij-server/src/wasm_vm.rs203
-rw-r--r--zellij-tile/src/data.rs23
-rw-r--r--zellij-tile/src/lib.rs4
-rw-r--r--zellij-tile/src/shim.rs6
-rw-r--r--zellij-utils/Cargo.toml2
-rw-r--r--zellij-utils/assets/config/default.yaml7
-rw-r--r--zellij-utils/assets/layouts/default.yaml4
-rw-r--r--zellij-utils/assets/layouts/disable-status-bar.yaml2
-rw-r--r--zellij-utils/assets/layouts/strider.yaml6
-rw-r--r--zellij-utils/src/input/config.rs29
-rw-r--r--zellij-utils/src/input/layout.rs150
-rw-r--r--zellij-utils/src/input/mod.rs1
-rw-r--r--zellij-utils/src/input/plugins.rs315
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml5
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs89
-rw-r--r--zellij-utils/src/ipc.rs10
-rw-r--r--zellij-utils/src/setup.rs1
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,
- };
-