/* relish: versatile lisp shell * Copyright (C) 2021 Aidan Hahn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ use crate::segment::{Ctr, Seg}; use crate::sym::{SymTable, ValueType, Symbol, Args}; use crate::eval::eval; use crate::run; use std::collections::VecDeque; use std::cell::RefCell; use std::rc::Rc; use std::process::{Command, Child, Stdio}; use nix::unistd; use ctrlc; pub struct ShellState { pub parent_pid: unistd::Pid, pub parent_pgid: unistd::Pid, pub children: Vec, pub last_exit_code: i32, } pub fn args_from_ast(ast: &Seg, syms: &mut SymTable) -> Vec { let mut args = vec![]; ast.circuit(&mut |arg_ctr: &Ctr| -> bool { match arg_ctr { Ctr::String(s) => args.push(s.clone()) == (), Ctr::Symbol(ref s) => if !syms.contains_key(s) { args.push(s.clone()) == () } else { let eval_args = Seg::new(); let eval_res = syms.call_symbol(s, &eval_args, false); if let Ok(res_ctr) = eval_res { match *res_ctr { Ctr::Lambda(_) => false, Ctr::Seg(_) => false, Ctr::String(s) => args.push(s.clone()) == (), _ => args.push(res_ctr.to_string()) == (), } } else { eprintln!("couldnt eval args!") != () } }, Ctr::Seg(ref form) => { let eval_res = eval(form, syms); if let Ok(res_ctr) = eval_res { match *res_ctr { Ctr::Lambda(_) => false, Ctr::Seg(_) => false, Ctr::String(s) => args.push(s) == (), _ => args.push(res_ctr.to_string()) == (), } } else { eprintln!("couldnt eval args!") != () } }, Ctr::Lambda(_) => eprintln!("lambda passed as shell parameter") != (), _ => args.push(arg_ctr.to_string()) == (), } }); args } fn launch_command( path: String, args: &[String], infd: Stdio, outfd: Stdio, errfd: Stdio, background: bool, state: &mut ShellState ) -> Result<(), String> { let newchld = Command::new(path) .args(args) .stdin(infd) .stdout(outfd) .stderr(errfd).spawn(); if let Ok(child) = newchld { let pid = child.id(); state.children.push(child); if !background { make_foreground(pid, state) } else { Ok(()) } } else { Err(format!("couldnt spawn command: {}", newchld.err().unwrap())) } } // the analogous make_background happens in the app when the user sends it the corresponding signal fn make_foreground(pid: u32, state: &mut ShellState) -> Result<(), String>{ for i in &mut state.children { if i.id() == pid { let exit = i.wait().unwrap(); if let Some(code) = exit.code() { state.last_exit_code = code; } else { state.last_exit_code = -1; } if let Err(e) = unistd::tcsetpgrp(0, state.parent_pgid) { return Err(format!("error setting terminal: {}!", e)); } } } if let Some(pos) = state.children.iter().position(|x| x.id() == pid) { state.children.remove(pos); } Ok(()) } const LOAD_DOCSTRING: &str = "Calls a binary off disk with given arguments. Arguments may be of any type except lambda. If a symbol is not defined it will be passed as a string. first argument (command name) will be found on path (or an error returned). examples: (l ping google.com) (let ((ping-count 4)) (l ping -c ping-count google.com)) (l emacs -nw (concat HOME '/.relishrc')) "; fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result { if ast.is_empty() { Err("need at least one argument".to_string()) } else { let mut args = VecDeque::from(args_from_ast(ast, syms)); if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) { launch_command( filepath, &Vec::from(args.make_contiguous()), Stdio::inherit(), Stdio::inherit(), Stdio::inherit(), false, state, )?; Ok(Ctr::Integer(state.last_exit_code.into())) } else { Err("file not found".to_string()) } } } const Q_DOCSTRING: &str = "returns exit code of last process to be run in posix layer"; fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result { Ok(Ctr::Integer(state.last_exit_code.into())) } const BG_DOCSTRING: &str = ""; fn bg_callback(_ast: &Seg, _syms: &mut SymTable, _state: &mut ShellState) -> Result { unimplemented!() } pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc>) { let pid = unistd::getpid(); let pgid_res = unistd::getpgid(Some(pid)); if !pgid_res.is_ok() { panic!("couldn't get pgid") } let pgid = pgid_res.ok().unwrap(); // one mut borrow { let mut state = shell_state.borrow_mut(); state.parent_pid = pid; state.parent_pgid = pgid; } let term_pgrp_res = unistd::tcgetpgrp(0); if !term_pgrp_res.is_ok() { panic!("couldn't get terminal's pgrp") } let term_owner = term_pgrp_res.ok().unwrap(); if pgid != term_owner { nix::sys::signal::kill( term_owner, nix::sys::signal::Signal::SIGTTIN, ).expect("couldn't take terminal from owning process") } if let Err(e) = unistd::setpgid( unistd::Pid::from_raw(0), unistd::Pid::from_raw(0) ) { panic!("couldn't set PGID: {}", e) }; if let Err(e) = unistd::tcsetpgrp(0, pid) { panic!("couldn't grab terminal: {}", e) } // these clones are needed for callbacks which move references let load_ss = shell_state.clone(); let bg_ss = shell_state.clone(); let q_ss = shell_state.clone(); syms.insert( String::from("l"), Symbol { name: String::from("load"), args: Args::Infinite, conditional_branches: true, docs: String::from(LOAD_DOCSTRING), value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result { load_callback(ast, symtable, &mut shell_state.borrow_mut()) })), ..Default::default() }, ); syms.insert( String::from("load"), Symbol { name: String::from("load"), args: Args::Infinite, conditional_branches: true, docs: String::from(LOAD_DOCSTRING), value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result { load_callback(ast, symtable, &mut load_ss.borrow_mut()) })), ..Default::default() }, ); syms.insert( String::from("bg"), Symbol { name: String::from("background"), args: Args::Infinite, conditional_branches: true, docs: String::from(BG_DOCSTRING), value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result { bg_callback(ast, symtable, &mut bg_ss.clone().borrow_mut()) })), ..Default::default() }, ); syms.insert( String::from("?"), Symbol { name: String::from("?"), args: Args::None, conditional_branches: false, docs: String::from(Q_DOCSTRING), value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result { q_callback(ast, symtable, &mut q_ss.clone().borrow_mut()) })), ..Default::default() }, ); if let Err(e) = ctrlc::set_handler(move || println!("POSIX layer caught SIG-something")) { eprintln!("WARNING: couldn't set sig handler: {}", e); } }