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 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}