use std::collections::HashMap; use std::fs; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; use std::thread::Thread; use crate::parse::Ast; use crate::*; mod builtin; pub fn run(se: &mut Session, cmd: Vec) { let parsed = parse::do_parse(&cmd); let parsed = match parsed { Ok(p) => p, Err(err) => { println!("{cmd:?}"); print!("{err:?}\r\n{PROMPT}"); return; } }; let Ast::Pipes(pipes) = parsed else { todo!("can only handle pipes"); }; let mut children = Vec::new(); let mut threads = Vec::new(); let mut prev_reader = None; let mut spawn_error = false; se.raw.disable(); for (i, cmd) in pipes.cmds.iter().enumerate() { let last = i == pipes.cmds.len() - 1; let (reader, writer) = if !last { let (r, w) = io::pipe().unwrap(); (Some(r), Some(w)) } else { (None, None) }; let Some(dc) = se.dispatch.get(&cmd.cmd[..]) else { println!( "unknown command {}", String::from_utf8_lossy(cmd.cmd.as_slice()) ); spawn_error = true; break; }; 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)); } if let Some(w) = writer { command.stdout(Stdio::from(w)); } let Ok(child) = command.spawn() else { println!("failed to spawn {path:?}"); spawn_error = true; break; }; children.push(child); } CommandKind::Builtin(builtin) => { builtin.mod_session(se, &cmd.args); let mut input: Box = match prev_reader.take() { Some(r) => Box::new(r), None => Box::new(io::stdin()), }; let mut output: Box = match writer { Some(w) => Box::new(w), None => Box::new(io::stdout()), }; // SAFETY: safe as long as we join all threads below again. // panics were not considered so probably needs to be fixed let args = &cmd.args; let args: &'static Vec = unsafe { std::mem::transmute(args) }; let handle = std::thread::spawn(move || builtin.io(args, &mut input, &mut output)); threads.push(handle); } } prev_reader = reader; } let status_string; if spawn_error { for child in children.iter_mut() { if let Err(e) = child.kill() { println!("failed to kill child - {e:?}"); } } status_string = "ERR".into(); } else { let mut code = 0; for jh in threads { // TODO do not ignore panics let _ = jh.join(); } for mut child in children { 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 { status_string = String::new(); } else { status_string = format!("{code}"); } } se.raw.enable(); print!("\r{status_string}{PROMPT}"); let _ = std::io::stdout().lock().flush(); } pub trait Builtin: Send + Sync { fn name(&self) -> &str; /// quick synchronous call, `cd` for example fn mod_session(&self, session: &mut Session, args: &[BString]) {} /// potentially long, pipelineable thread, builtin `cat` for example fn io( &self, args: &[BString], stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> std::io::Result<()> { Ok(()) } } 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), ]; pub struct CommandDispatch { map: HashMap, } impl CommandDispatch { pub fn new() -> Self { let mut map = HashMap::new(); // all the commands from PATH let path = std::env::var_os("PATH").unwrap(); for p in path.as_bytes().split(|x| *x == b':').rev() { let p = PathBuf::from(OsStr::from_bytes(p)); let Ok(entries) = fs::read_dir(p) else { continue; }; for entry in entries { let Ok(entry) = entry else { continue }; let Ok(meta) = entry.metadata() else { continue; }; if !meta.is_file() { continue; } let perms = meta.permissions(); let mode = perms.mode(); // Check if any execute bit is set (owner/group/other) if mode & 0o111 == 0 { continue; } // insert into our command map, mind the .rev() on the iterator above s.t. correct precedence is had map.insert( entry.file_name().as_bytes().to_vec(), CommandKind::Path(entry.path()), ); } } // builtins for &b in BUILTINS { map.insert(b.name().as_bytes().to_vec(), CommandKind::Builtin(b)); } Self { map } } fn get(&self, cmd: &bstr) -> Option { if cmd.starts_with(b"/") || cmd.starts_with(b"./") { Some(CommandKind::Path(PathBuf::from(OsStr::from_bytes(cmd)))) } else { self.map.get(cmd).cloned() } } } #[derive(Clone)] pub enum CommandKind { Builtin(&'static dyn Builtin), Path(PathBuf), }