feat(plugin): add exec_cmd helper for executing command in host

Signed-off-by: Tw <wei.tan@intel.com>
Signed-off-by: Tw <tw19881113@gmail.com>
This commit is contained in:
Tw 2021-09-09 18:45:03 +08:00 committed by GitHub
parent 6d0c5a56f5
commit 19b3f8366f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 99 additions and 23 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -325,10 +325,15 @@ impl Tab {
if let Some(Run::Plugin(Some(plugin))) = &layout.run { if let Some(Run::Plugin(Some(plugin))) = &layout.run {
let (pid_tx, pid_rx) = channel(); let (pid_tx, pid_rx) = channel();
self.senders self.senders
.send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone(), tab_index)) .send_to_plugin(PluginInstruction::Load(
pid_tx,
plugin.path.clone(),
tab_index,
plugin._allow_exec_host_cmd,
))
.unwrap(); .unwrap();
let pid = pid_rx.recv().unwrap(); let pid = pid_rx.recv().unwrap();
let title = String::from(plugin.as_path().as_os_str().to_string_lossy()); let title = String::from(plugin.path.as_path().as_os_str().to_string_lossy());
let mut new_plugin = PluginPane::new( let mut new_plugin = PluginPane::new(
pid, pid,
*position_and_size, *position_and_size,

View file

@ -1,4 +1,4 @@
use log::info; use log::{info, warn};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@ -28,7 +28,7 @@ use zellij_utils::{input::command::TerminalAction, serde, zellij_tile};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) enum PluginInstruction { pub(crate) enum PluginInstruction {
Load(Sender<u32>, PathBuf, usize), // tx_pid, path_of_plugin , tab_index 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 Update(Option<u32>, Event), // Focused plugin / broadcast, event data
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
Unload(u32), Unload(u32),
@ -54,6 +54,8 @@ pub(crate) struct PluginEnv {
pub senders: ThreadSenders, pub senders: ThreadSenders,
pub wasi_env: WasiEnv, pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>, pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
// FIXME: Once permission system is ready, this could be removed
pub _allow_exec_host_cmd: bool,
} }
// Thread main -------------------------------------------------------------------------------------------------------- // Thread main --------------------------------------------------------------------------------------------------------
@ -65,7 +67,7 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into())); err_ctx.add_call(ContextType::Plugin((&event).into()));
match event { match event {
PluginInstruction::Load(pid_tx, path, tab_index) => { PluginInstruction::Load(pid_tx, path, tab_index, _allow_exec_host_cmd) => {
let plugin_dir = data_dir.join("plugins/"); let plugin_dir = data_dir.join("plugins/");
let wasm_bytes = fs::read(&path) let wasm_bytes = fs::read(&path)
.or_else(|_| fs::read(&path.with_extension("wasm"))) .or_else(|_| fs::read(&path.with_extension("wasm")))
@ -99,12 +101,17 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
let wasi = wasi_env.import_object(&module).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 plugin_env = PluginEnv {
plugin_id, plugin_id,
tab_index, tab_index,
senders: bus.senders.clone(), senders: bus.senders.clone(),
wasi_env, wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())), subscriptions: Arc::new(Mutex::new(HashSet::new())),
_allow_exec_host_cmd,
}; };
let zellij = zellij_exports(&store, &plugin_env); let zellij = zellij_exports(&store, &plugin_env);
@ -174,6 +181,7 @@ pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObj
host_get_plugin_ids, host_get_plugin_ids,
host_open_file, host_open_file,
host_set_timeout, host_set_timeout,
host_exec_cmd,
} }
} }
@ -248,6 +256,24 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
}); });
} }
fn host_exec_cmd(plugin_env: &PluginEnv) {
let mut cmdline: Vec<String> = wasi_read_object(&plugin_env.wasi_env);
let command = cmdline.remove(0);
// Bail out if we're forbidden to run command
if !plugin_env._allow_exec_host_cmd {
warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.",
cmd = command, args = cmdline.join(" "));
return;
}
// Here, we don't wait the command to finish
process::Command::new(command)
.args(cmdline)
.spawn()
.unwrap();
}
// Helper Functions --------------------------------------------------------------------------------------------------- // Helper Functions ---------------------------------------------------------------------------------------------------
pub fn wasi_read_string(wasi_env: &WasiEnv) -> String { pub fn wasi_read_string(wasi_env: &WasiEnv) -> String {

View file

@ -37,6 +37,10 @@ pub fn open_file(path: &Path) {
pub fn set_timeout(secs: f64) { pub fn set_timeout(secs: f64) {
unsafe { host_set_timeout(secs) }; unsafe { host_set_timeout(secs) };
} }
pub fn exec_cmd(cmd: &[&str]) {
object_to_stdout(&cmd);
unsafe { host_exec_cmd() };
}
// Internal Functions // Internal Functions
@ -60,4 +64,5 @@ extern "C" {
fn host_get_plugin_ids(); fn host_get_plugin_ids();
fn host_open_file(); fn host_open_file();
fn host_set_timeout(secs: f64); fn host_set_timeout(secs: f64);
fn host_exec_cmd();
} }

View file

@ -7,7 +7,8 @@ template:
split_size: split_size:
Fixed: 1 Fixed: 1
run: run:
plugin: tab-bar plugin:
path: tab-bar
- direction: Vertical - direction: Vertical
body: true body: true
- direction: Vertical - direction: Vertical
@ -15,6 +16,7 @@ template:
split_size: split_size:
Fixed: 2 Fixed: 2
run: run:
plugin: status-bar plugin:
path: status-bar
tabs: tabs:
- direction: Vertical - direction: Vertical

View file

@ -7,6 +7,7 @@ template:
split_size: split_size:
Fixed: 1 Fixed: 1
run: run:
plugin: tab-bar plugin:
path: tab-bar
- direction: Vertical - direction: Vertical
body: true body: true

View file

@ -7,7 +7,8 @@ template:
split_size: split_size:
Fixed: 1 Fixed: 1
run: run:
plugin: tab-bar plugin:
path: tab-bar
- direction: Vertical - direction: Vertical
body: true body: true
- direction: Vertical - direction: Vertical
@ -15,7 +16,8 @@ template:
split_size: split_size:
Fixed: 2 Fixed: 2
run: run:
plugin: status-bar plugin:
path: status-bar
tabs: tabs:
- direction: Vertical - direction: Vertical
parts: parts:
@ -23,5 +25,6 @@ tabs:
split_size: split_size:
Percent: 20 Percent: 20
run: run:
plugin: strider plugin:
path: strider
- direction: Horizontal - direction: Horizontal

View file

@ -53,11 +53,19 @@ pub enum SplitSize {
#[serde(crate = "self::serde")] #[serde(crate = "self::serde")]
pub enum Run { pub enum Run {
#[serde(rename = "plugin")] #[serde(rename = "plugin")]
Plugin(Option<PathBuf>), Plugin(Option<RunPlugin>),
#[serde(rename = "command")] #[serde(rename = "command")]
Command(RunCommand), Command(RunCommand),
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(crate = "self::serde")]
pub struct RunPlugin {
pub path: PathBuf,
#[serde(default)]
pub _allow_exec_host_cmd: bool,
}
// The layout struct ultimately used to build the layouts. // The layout struct ultimately used to build the layouts.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")] #[serde(crate = "self::serde")]

View file

@ -6,14 +6,16 @@ template:
split_size: split_size:
Fixed: 1 Fixed: 1
run: run:
plugin: tab-bar plugin:
path: tab-bar
- direction: Horizontal - direction: Horizontal
body: true body: true
- direction: Vertical - direction: Vertical
split_size: split_size:
Fixed: 2 Fixed: 2
run: run:
plugin: status-bar plugin:
path: status-bar
tabs: tabs:
- direction: Vertical - direction: Vertical

View file

@ -45,7 +45,10 @@ fn default_layout_merged_correctly() {
borderless: true, borderless: true,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "tab-bar".into(),
..Default::default()
}))),
}, },
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
@ -59,7 +62,10 @@ fn default_layout_merged_correctly() {
borderless: true, borderless: true,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "status-bar".into(),
..Default::default()
}))),
}, },
], ],
split_size: None, split_size: None,
@ -83,7 +89,10 @@ fn default_layout_new_tab_correct() {
borderless: true, borderless: true,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "tab-bar".into(),
..Default::default()
}))),
}, },
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
@ -97,7 +106,10 @@ fn default_layout_new_tab_correct() {
borderless: true, borderless: true,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "status-bar".into(),
..Default::default()
}))),
}, },
], ],
split_size: None, split_size: None,
@ -253,7 +265,10 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
borderless: false, borderless: false,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "tab-bar".into(),
..Default::default()
}))),
}, },
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
@ -297,7 +312,10 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
borderless: false, borderless: false,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "status-bar".into(),
..Default::default()
}))),
}, },
], ],
split_size: None, split_size: None,
@ -321,7 +339,10 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
borderless: false, borderless: false,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "tab-bar".into(),
..Default::default()
}))),
}, },
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
@ -335,7 +356,10 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
borderless: false, borderless: false,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))), run: Some(Run::Plugin(Some(RunPlugin {
path: "status-bar".into(),
..Default::default()
}))),
}, },
], ],
split_size: None, split_size: None,