From 91250e99f113fa754f40d1d26afd774bbb25a438 Mon Sep 17 00:00:00 2001 From: Jonas Maier Date: Mon, 9 Mar 2026 18:41:11 +0100 Subject: correct symlinking --- src/export_fun.rs | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 ++-- src/run/mod.rs | 17 +++++- src/unix.rs | 143 -------------------------------------------- 4 files changed, 196 insertions(+), 149 deletions(-) create mode 100644 src/export_fun.rs delete mode 100644 src/unix.rs diff --git a/src/export_fun.rs b/src/export_fun.rs new file mode 100644 index 0000000..934b81c --- /dev/null +++ b/src/export_fun.rs @@ -0,0 +1,175 @@ +//! allow sub-programs to invoke arbitrary user-defined functions via a unix socket + +use crate::Session; +use std::env::current_exe; +use std::ffi::OsStr; +use std::fs; +use std::io; +use std::io::Write; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::symlink; +use std::os::unix::net::SocketAncillary; +use std::os::unix::net::UnixListener; +use std::os::unix::net::UnixStream; +use std::path::Path; +use std::path::PathBuf; +use std::process::exit; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; + +fn handle_server(session: Arc>, stream: UnixStream) -> io::Result<()> { + todo!() +} + +fn handle_client(stream: UnixStream) -> io::Result<()> { + println!("uhh hi"); + todo!() +} + +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") { + if let Ok(stream) = UnixStream::connect(socket) { + let _ = handle_client(stream); + } + 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() +} + +pub struct SocketRunning { + bin_dir: PathBuf, + path: PathBuf, +} + +impl SocketRunning { + pub fn socket_path(&self) -> &Path { + &self.path + } + pub fn path(&self) -> &Path { + &self.bin_dir + } +} + +#[must_use] +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 :))"); + }; + } +} + +#[must_use] +pub fn listen(session: Arc>) -> impl Drop { + let session_id = unique_string(); + let session_dir = crate::basedir::data_dir().join("session").join(session_id); + let bin_dir = session_dir.join("bin"); + std::fs::create_dir_all(&bin_dir).unwrap(); + let socket_path = session_dir.join("cmd.sock"); + + { + let mut se = session.lock().unwrap(); + assert!(se.socket_running.is_none()); + se.socket_running = Some(SocketRunning { + bin_dir, + path: socket_path.clone(), + }); + } + + let se = session.clone(); + thread::spawn(move || { + struct SessionRemover(PathBuf); + impl Drop for SessionRemover { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + let _session_remover = SessionRemover(session_dir); + let listener = UnixListener::bind(socket_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 { + let se = se.clone(); + thread::spawn(move || handle_server(se, stream)); + } + } + }); + + SocketDropper { session } +} + +fn create_function_hook_res( + session: Arc>, + fun_name: &[u8], +) -> Result<(), Box> { + let session = session.lock().map_err(|e| format!("{e:?}"))?; + let sock_run = session.socket_running.as_ref().ok_or("no socket running")?; + let exe_path = current_exe()?; + let symlink_path = sock_run.bin_dir.join(OsStr::from_bytes(fun_name)); + symlink(exe_path, symlink_path)?; + Ok(()) +} + +pub fn create_function_hook(session: Arc>, fun_name: &[u8]) { + if let Err(e) = create_function_hook_res(session, fun_name) { + println!("failed to create function hook: {e:?}"); + } +} diff --git a/src/main.rs b/src/main.rs index 5d2643f..7196f58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use std::io::{self, IsTerminal, Read, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; use std::path::Path; -use std::process::{Command, Stdio, exit}; +use std::process::{Command, Stdio}; use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::time::Duration; @@ -16,6 +16,7 @@ pub mod basedir; pub mod completion; pub mod cursor; pub mod date; +pub mod export_fun; pub mod history; pub mod linebuf; pub mod panic; @@ -23,7 +24,6 @@ pub mod parse; pub mod raw; pub mod reload; pub mod run; -pub mod unix; use linebuf::LineBuf; use raw::*; @@ -70,7 +70,7 @@ pub struct Session { builtins: HashMap, vars: HashMap, funs: HashMap>, - socket_running: Option, + socket_running: Option, /// n before end of history.len() /// 0 == not checking history @@ -272,7 +272,7 @@ fn event_loop() { print!("{}", se.prompt()); let session = Arc::new(Mutex::new(se)); - let _sock_dropper = unix::listen(session.clone()); + let _sock_dropper = export_fun::listen(session.clone()); loop { let mut buf = [0u8; 1]; @@ -489,7 +489,7 @@ fn event_loop() { } fn main() { - unix::maybe_run_defined_function(); + export_fun::maybe_run_defined_function(); if !io::stdin().is_terminal() { println!("need to run in a tty"); return; diff --git a/src/run/mod.rs b/src/run/mod.rs index 8c36f91..da62778 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -62,6 +62,15 @@ impl Executor { command.stdout(Stdio::from(stdout)); } + if let Some(sr) = self.se.lock().unwrap().socket_running.as_ref() { + let my_path = std::env::var_os("PATH").expect("no PATH - seriously?"); + let mut new_path = sr.path().as_os_str().as_bytes().to_vec(); + new_path.push(b':'); + new_path.extend_from_slice(my_path.as_bytes()); + command.env("PATH", OsStr::from_bytes(&new_path)); + command.env("PISH_SOCKET", sr.socket_path().as_os_str()); + } + let child = match command.spawn() { Ok(c) => c, Err(e) => { @@ -185,7 +194,13 @@ impl Executor { } fn execute_fun_decl(&mut self, fd: parse::FunDecl) -> Result<(), ExecError> { - self.se.lock().unwrap().funs.insert(fd.name, *fd.body.body); + self.se + .lock() + .unwrap() + .funs + .insert(fd.name.clone(), *fd.body.body); + crate::export_fun::create_function_hook(self.se.clone(), &fd.name); + // TODO: very ugly to ad-hoc keep export stuff & session data in sync here Ok(()) } diff --git a/src/unix.rs b/src/unix.rs deleted file mode 100644 index 967c4fe..0000000 --- a/src/unix.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! 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 crate::Session; - -fn handle_server(session: Arc>, stream: UnixStream) -> io::Result<()> { - todo!() -} - -fn handle_client(stream: UnixStream) -> io::Result<()> { - todo!() -} - -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") { - if let Ok(stream) = UnixStream::connect(socket) { - let _ = handle_client(stream); - } - 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 { - let se = se.clone(); - thread::spawn(move || handle_server(se, stream)); - } - } - }); - - SocketDropper { session } -} -- cgit v1.2.3