1use std::ffi::OsStr;2use std::process::Stdio;3use std::sync::Mutex;45use bifrostlink::{Rpc, Rtt, WeakRpc};6use tokio::process::{Child, Command};78use remowt_link_shared::plugin::{Error, PluginEndpoints, PluginHost};9use remowt_link_shared::port::child_port;10use remowt_link_shared::{Address, BifConfig};1112pub 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}2021struct Host {22 me: Address,23 rpc: WeakRpc<BifConfig>,24 children: Mutex<Vec<Child>>,25}2627impl Host {28 fn spawn(&self, id: u16, path: impl AsRef<OsStr>) -> Result<(), Error> {29 let rpc = self.rpc.clone().upgrade().ok_or(Error::Gone)?;3031 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");4142 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}4748impl PluginHost for Host {49 async fn load_plugin(&self, id: u16, name: String) -> Result<(), Error> {50 51 52 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 = exe58 .parent()59 .ok_or_else(|| Error::Spawn("primary agent has no parent directory".to_owned()))?;60 self.spawn(id, dir.join(&name))61 }6263 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}