//! allow sub-programs to invoke arbitrary user-defined functions via a unix socket use std::ffi::OsStr; use std::fs; use std::io; use std::io::Write; use std::os::unix::ffi::OsStrExt; use std::os::unix::net::SocketAncillary; use std::os::unix::net::UnixListener; use std::os::unix::net::UnixStream; use std::path::PathBuf; use std::process::exit; use std::sync::Arc; use std::sync::Mutex; use std::thread; use libc::socket; use crate::BString; use crate::Session; fn user_function_res(sock: &OsStr) -> io::Result<()> { let sock = UnixStream::connect(sock)?; let mut ancillary_buffer = [0; 128]; let mut ancillary = SocketAncillary::new(&mut ancillary_buffer[..]); ancillary.add_fds(&[0, 1, 2]); let buf = [0; 8]; let mut bufs = &mut [io::IoSlice::new(&buf[..])][..]; sock.send_vectored_with_ancillary(bufs, &mut ancillary)?; todo!() } pub fn maybe_run_defined_function() { if let Some(program_name) = std::env::args_os().next() { let program_name = program_name.as_bytes(); if !program_name.contains(&b'/') && program_name != b"pish" { if let Some(socket) = std::env::var_os("PISH_SOCKET") { let _ = user_function_res(&socket); exit(-1); } } } } fn unique_string() -> String { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::process; use std::time::{SystemTime, UNIX_EPOCH}; let pid = process::id(); let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); let mut hasher = DefaultHasher::new(); pid.hash(&mut hasher); now.hash(&mut hasher); let hash = hasher.finish(); let hex = format!("{:016x}", hash); hex[..12.min(hex.len())].to_string() } fn unique_socket_path() -> PathBuf { let mut socket_name = b"pish-cmd-".to_vec(); socket_name.extend_from_slice(&unique_string().as_bytes()); socket_name.extend_from_slice(b".sock"); let socket_path = crate::basedir::data_dir().join(OsStr::from_bytes(&socket_name)); socket_path } pub struct SocketRunning { path: PathBuf, } #[must_use] pub struct SocketDropper { session: Arc>, } impl Drop for SocketDropper { fn drop(&mut self) { // mark socket for closing by `take`ing the socket_running value let session = &self.session; let Ok(mut se) = session.lock() else { return }; let Some(sr) = se.socket_running.take() else { return; }; // connect to the socket for a brief moment to make it check that it should terminate if let Ok(mut con) = UnixStream::connect(&sr.path) { let _ = write!(con, "please shut down :))"); }; } } pub fn listen(session: Arc>) -> SocketDropper { let path = unique_socket_path(); { let mut se = session.lock().unwrap(); assert!(se.socket_running.is_none()); se.socket_running = Some(SocketRunning { path: path.clone() }); } let se = session.clone(); thread::spawn(move || { struct SocketRemover(PathBuf); impl Drop for SocketRemover { fn drop(&mut self) { let _ = fs::remove_file(&self.0); } } let _socket_remover = SocketRemover(path.clone()); let listener = UnixListener::bind(path).unwrap(); let mut it = listener.incoming(); while let Some(stream) = it.next() { match se.lock() { Err(_) => break, Ok(se) if se.socket_running.is_none() => break, _ => (), } if let Ok(_stream) = stream { todo!("handle client") } } }); SocketDropper { session } }