Skip to main content

remowt_plugin/
host.rs

1use std::ffi::OsStr;
2use std::process::Stdio;
3use std::sync::Mutex;
4
5use bifrostlink::{Rpc, Rtt, WeakRpc};
6use tokio::process::{Child, Command};
7
8use remowt_link_shared::plugin::{Error, PluginEndpoints, PluginHost};
9use remowt_link_shared::port::child_port;
10use remowt_link_shared::{Address, BifConfig};
11
12pub fn serve(rpc: &mut Rpc<BifConfig>) {
13	let host = Host {
14		me: rpc.me(),
15		rpc: rpc.clone().downgrade(),
16		children: Mutex::new(Vec::new()),
17	};
18	PluginEndpoints(host).register_endpoints(rpc);
19}
20
21struct Host {
22	me: Address,
23	rpc: WeakRpc<BifConfig>,
24	children: Mutex<Vec<Child>>,
25}
26
27impl Host {
28	fn spawn(&self, id: u16, path: impl AsRef<OsStr>) -> Result<(), Error> {
29		let rpc = self.rpc.clone().upgrade().ok_or(Error::Gone)?;
30
31		let mut child = Command::new(path)
32			.arg(id.to_string())
33			.arg(serde_json::to_string(&self.me).expect("address serializes"))
34			.stdin(Stdio::piped())
35			.stdout(Stdio::piped())
36			.kill_on_drop(true)
37			.spawn()
38			.map_err(|e| Error::Spawn(e.to_string()))?;
39		let stdin = child.stdin.take().expect("stdin piped");
40		let stdout = child.stdout.take().expect("stdout piped");
41
42		rpc.add_direct(Address::Plugin(id), child_port(stdout, stdin), Rtt(0));
43		self.children.lock().expect("not poisoned").push(child);
44		Ok(())
45	}
46}
47
48impl PluginHost for Host {
49	async fn load_plugin(&self, id: u16, name: String) -> Result<(), Error> {
50		// TODO: Right now loads plugin next to the binary...
51		// But with our CA addressed schema, the plugins should be located in content-addressed subdir...
52		// Maybe it should just be scrapped in favor of load_plugin_path.
53		if name.is_empty() || name == "." || name == ".." || name.contains(['/', '\0']) {
54			return Err(Error::BadName);
55		}
56		let exe = std::env::current_exe().map_err(|e| Error::Spawn(e.to_string()))?;
57		let dir = exe
58			.parent()
59			.ok_or_else(|| Error::Spawn("primary agent has no parent directory".to_owned()))?;
60		self.spawn(id, dir.join(&name))
61	}
62
63	async fn load_plugin_path(&self, id: u16, path: String) -> Result<(), Error> {
64		if path.is_empty() || path.contains('\0') {
65			return Err(Error::BadName);
66		}
67		self.spawn(id, path)
68	}
69}