use std::collections::HashMap; use std::fs::File; use std::io::{PipeReader, PipeWriter}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crate::parse::{self, Ast, PostExpansion, PreExpansion}; use crate::*; mod builtin; #[derive(Debug)] pub enum ExecError { UnknownVariable(BString), ExecError(i32), } #[derive(Clone)] pub struct Executor { se: Arc>, args: Option>, } pub enum Input { Stdin, Pipe(PipeReader), File(File), } pub enum Output { Stdout, Pipe(PipeWriter), File(File), } impl Into for Input { fn into(self) -> Stdio { match self { Input::Stdin => Stdio::inherit(), Input::Pipe(reader) => reader.into(), Input::File(file) => file.into(), } } } impl Into for Output { fn into(self) -> Stdio { match self { Output::Stdout => Stdio::inherit(), Output::Pipe(writer) => writer.into(), Output::File(file) => file.into(), } } } impl Read for Input { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { Input::Stdin => io::stdin().read(buf), Input::Pipe(reader) => reader.read(buf), Input::File(file) => file.read(buf), } } } impl Write for Output { fn write(&mut self, buf: &[u8]) -> io::Result { match self { Output::Stdout => io::stdout().write(buf), Output::Pipe(writer) => writer.write(buf), Output::File(file) => file.write(buf), } } fn flush(&mut self) -> io::Result<()> { match self { Output::Stdout => io::stdout().flush(), Output::Pipe(writer) => writer.flush(), Output::File(file) => file.flush(), } } } impl Executor { fn execute_pipeline( &mut self, pipes: parse::Pipes, stdin: Input, stdout: Output, ) -> Result<(), ExecError> { let mut children = Vec::new(); let mut threads = Vec::new(); let mut prev_reader = None; let mut spawn_error = false; let mut stdin = Some(stdin); let mut stdout = Some(stdout); let pipelen = pipes.cmds.len(); for (i, cmd) in pipes.cmds.into_iter().enumerate() { let (reader, writer) = if i < pipelen - 1 { let (r, w) = io::pipe().unwrap(); (Some(r), Some(w)) } else { (None, None) }; let dc = get_command_kind(&self.se.lock().unwrap(), &cmd.cmd[..]); match dc { CommandKind::Path(path) => { let mut command = Command::new(&path); for arg in cmd.args.iter() { command.arg(OsStr::from_bytes(arg)); } if let Some(r) = prev_reader.take() { command.stdin(Stdio::from(r)); } else { command.stdin(stdin.take().unwrap()); } if let Some(w) = writer { command.stdout(Stdio::from(w)); } else { command.stdout(stdout.take().unwrap()); } 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) => { let cmd = path.to_string_lossy(); let msg = match e.kind() { io::ErrorKind::NotFound => format!("{cmd} not found"), io::ErrorKind::PermissionDenied => { format!("{cmd} is not executable") } io::ErrorKind::FileTooLarge => format!("{cmd} is too massive"), io::ErrorKind::ResourceBusy | io::ErrorKind::ExecutableFileBusy => { format!("{cmd} is busy") } io::ErrorKind::TooManyLinks => { format!("{cmd} could not be resolved") } io::ErrorKind::InvalidFilename => { format!("{cmd} is not a valid file name") } io::ErrorKind::ArgumentListTooLong => format!("too many arguments"), io::ErrorKind::Interrupted => format!("got interrupted"), io::ErrorKind::Unsupported => format!("{cmd} is not supported"), e => format!("I am surprised you can get this error here: {e:?}"), }; println!("pish: {msg}"); spawn_error = true; break; } }; children.push(child); } CommandKind::Builtin(builtin) => { let mut input: Box = if let Some(r) = prev_reader.take() { Box::new(r) } else { Box::new(stdin.take().unwrap()) }; let mut output: Box = if let Some(w) = writer { Box::new(w) } else { Box::new(stdout.take().unwrap()) }; // currently only required for `re`, cannot happen in background thread builtin.special(self.se.clone(), &cmd.args); let cloned_session = self.se.clone(); let handle = std::thread::spawn(move || { builtin.io(cloned_session, &cmd.args, &mut input, &mut output) }); threads.push(handle); } CommandKind::Fun(ast) => { let mut this = self.clone(); let mut args = Vec::with_capacity(cmd.args.len() + 1); args.push(cmd.cmd.clone()); args.extend_from_slice(&cmd.args); this.args = Some(args); let ast = ast.expand(&mut this)?; this.execute( ast, prev_reader.map(|r| Input::Pipe(r)).unwrap_or(Input::Stdin), writer.map(|w| Output::Pipe(w)).unwrap_or(Output::Stdout), ) .unwrap(); } } prev_reader = reader; } if spawn_error { for child in children.iter_mut() { if let Err(e) = child.kill() { println!("failed to kill child - {e:?}"); } } return Err(ExecError::ExecError(127)); } let mut code = 0; for jh in threads { match jh.join() { Ok(Ok(())) => (), Ok(Err(e)) => match e { BuiltinError::IO(_) => code = -1, BuiltinError::Exit(c) => code = c, }, Err(_) => code = 127, } } for child in children.iter_mut() { match child.wait() { Ok(ec) => { if let Some(c) = ec.code() { code = c; } } Err(e) => { println!("failed to wait for child - {e:?}") } } } if code == 0 { Ok(()) } else { Err(ExecError::ExecError(code)) } } fn execute_var_assign(&mut self, va: parse::VarAssign) -> Result<(), ExecError> { self.se.lock().unwrap().vars.insert(va.var, va.val); Ok(()) } fn execute_fun_decl(&mut self, fd: parse::FunDecl) -> Result<(), ExecError> { 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(()) } fn execute( &mut self, ast: Ast, stdin: Input, stdout: Output, ) -> Result<(), ExecError> { match ast { Ast::FunDecl(fd) => self.execute_fun_decl(fd), Ast::VarAssign(va) => self.execute_var_assign(va), Ast::Pipes(pipes) => self.execute_pipeline(pipes, stdin, stdout), } } pub fn execute_fun( session: Arc>, invoke: Vec, stdin: Input, stdout: Output, ) -> Result<(), ExecError> { let mut this = Self { se: session, args: None, }; let cmd = parse::Command { cmd: invoke[0].clone(), args: invoke[1..].to_vec(), }; this.execute_pipeline(parse::Pipes { cmds: vec![cmd] }, stdin, stdout) } } impl parse::Expander for Executor { type Error = ExecError; fn expand_var(&mut self, var: BString) -> Result { if var.is_empty() { return Err(ExecError::UnknownVariable(var)); } if var[0].is_ascii_digit() { if let Some(x) = String::from_utf8(var.clone()) .ok() .and_then(|x| x.parse::().ok()) { if let Some(args) = &self.args { if x < args.len() { return Ok(args[x].clone()); } } else if let Some(arg) = std::env::args_os().skip(x).next() { return Ok(arg.into_encoded_bytes()); } } } if let Some(val) = self.se.lock().unwrap().vars.get(&var) { return Ok(val.clone()); } match std::env::var_os(OsStr::from_bytes(&var)) { Some(val) => Ok(val.as_bytes().to_vec()), None => Err(ExecError::UnknownVariable(var)), } } fn expand_cmd(&mut self, ast: Ast) -> Result { let (stdin, _) = io::pipe().unwrap(); let (mut expansion, stdout) = io::pipe().unwrap(); let mut this = self.clone(); let t = std::thread::spawn(move || this.execute(ast, Input::Pipe(stdin), Output::Pipe(stdout))); let mut buf = Vec::new(); expansion.read_to_end(&mut buf).unwrap(); t.join().unwrap()?; if buf.last() == Some(&b'\n') { buf.pop(); } Ok(buf) } } fn exec(se: Arc>, ast: Ast) -> Result<(), ExecError> { let mut exec = Executor { se, args: None }; let ast = ast.expand(&mut exec)?; exec.execute(ast, Input::Stdin, Output::Stdout) } pub fn run(se: Arc>, cmd: Vec) { let parsed = parse::do_parse(&cmd); let parsed = match parsed { Ok(p) => p, Err(err) => { se.lock().unwrap().raw.disable(); println!("{:?}: {}", err.0, String::from_utf8_lossy(&err.1)); print!("{}", se.lock().unwrap().prompt()); std::io::stdout().lock().flush().unwrap(); se.lock().unwrap().raw.enable(); return; } }; se.lock().unwrap().raw.disable(); let result = exec(se.clone(), parsed); se.lock().unwrap().raw.enable(); let status_string = match result { Ok(_) => String::new(), Err(ExecError::UnknownVariable(var)) => { format!("unbound variable: {}", String::from_utf8_lossy(&var)) } Err(ExecError::ExecError(i)) => i.to_string(), }; print!("\r{status_string}\r\n{}", se.lock().unwrap().prompt()); let _ = std::io::stdout().lock().flush(); } #[derive(Debug)] pub enum BuiltinError { IO(std::io::Error), Exit(i32), } impl From for BuiltinError { fn from(value: std::io::Error) -> Self { Self::IO(value) } } type BuiltinResult = Result<(), BuiltinError>; #[allow(unused_variables)] pub trait Builtin: Send + Sync { fn name(&self) -> &str; fn special(&self, session: Arc>, args: &[BString]) {} fn io( &self, session: Arc>, args: &[BString], stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> BuiltinResult; } const BUILTINS: &[&'static dyn Builtin] = &[ &builtin::cd, &builtin::clear, #[cfg(debug_assertions)] &builtin::re, &builtin::sink("to", false), &builtin::sink("into", false), &builtin::sink("append", true), &builtin::from, &builtin::builtins, &builtin::_type, &builtin::history, &builtin::escape, &builtin::parse, &builtin::null, ]; pub fn builtin_map() -> HashMap { let mut map = HashMap::new(); for &b in BUILTINS { map.insert(b.name().as_bytes().to_vec(), b); } map } #[derive(Clone)] pub enum CommandKind { Builtin(&'static dyn Builtin), Fun(Ast), Path(PathBuf), } pub fn get_command_kind(se: &Session, cmd: &bstr) -> CommandKind { let path_cmd = CommandKind::Path(PathBuf::from(OsStr::from_bytes(cmd))); if cmd.contains(&b'/') { path_cmd } else if let Some(fun) = se.funs.get(cmd) { CommandKind::Fun(fun.clone()) } else if let Some(builtin) = se.builtins.get(cmd) { CommandKind::Builtin(*builtin) } else { path_cmd } }