2023-03-24 18:14:33 -07:00
|
|
|
/* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-04-17 22:11:49 -07:00
|
|
|
use {
|
|
|
|
|
crate::{
|
2023-05-18 06:53:23 +00:00
|
|
|
segment::{
|
|
|
|
|
Ctr, Seg, Type
|
|
|
|
|
},
|
2023-04-17 22:11:49 -07:00
|
|
|
sym::{
|
|
|
|
|
SymTable, ValueType,
|
|
|
|
|
Symbol, Args,
|
|
|
|
|
},
|
2023-05-23 22:06:11 +00:00
|
|
|
error::{
|
|
|
|
|
Traceback, start_trace,
|
|
|
|
|
},
|
2023-04-17 22:11:49 -07:00
|
|
|
eval::eval,
|
|
|
|
|
run,
|
|
|
|
|
},
|
2023-05-18 06:53:23 +00:00
|
|
|
libc::{
|
2023-05-25 23:08:44 +00:00
|
|
|
sigaddset, sigemptyset, sigprocmask, exit,
|
2023-05-18 06:53:23 +00:00
|
|
|
SIGINT, SIGCHLD, SIGTTOU, SIGTTIN, SIGQUIT, SIGTSTP,
|
|
|
|
|
SIG_BLOCK, SIG_UNBLOCK
|
|
|
|
|
},
|
2023-04-17 22:11:49 -07:00
|
|
|
std::{
|
|
|
|
|
collections::VecDeque,
|
2023-05-28 23:22:49 +00:00
|
|
|
cell::RefCell,
|
2023-05-18 06:53:23 +00:00
|
|
|
io::Result as IOResult,
|
2023-04-17 22:11:49 -07:00
|
|
|
rc::Rc,
|
2023-05-18 06:53:23 +00:00
|
|
|
fs::{
|
|
|
|
|
File, OpenOptions
|
|
|
|
|
},
|
2023-04-17 22:11:49 -07:00
|
|
|
path::Path,
|
|
|
|
|
process::{
|
2023-05-18 06:53:23 +00:00
|
|
|
Command, Stdio, Child
|
2023-04-17 22:11:49 -07:00
|
|
|
},
|
|
|
|
|
env::set_current_dir,
|
2023-05-18 06:53:23 +00:00
|
|
|
os::unix::process::CommandExt,
|
|
|
|
|
mem,
|
|
|
|
|
},
|
|
|
|
|
nix::{
|
|
|
|
|
unistd, unistd::Pid,
|
|
|
|
|
sys::termios::{
|
|
|
|
|
Termios, tcgetattr,
|
|
|
|
|
tcsetattr, SetArg,
|
|
|
|
|
},
|
2023-04-17 22:11:49 -07:00
|
|
|
},
|
|
|
|
|
};
|
2023-03-24 18:14:33 -07:00
|
|
|
|
|
|
|
|
pub struct ShellState {
|
2023-05-18 06:53:23 +00:00
|
|
|
pub parent_pid: Pid,
|
|
|
|
|
pub parent_sid: Pid,
|
2023-03-24 18:14:33 -07:00
|
|
|
pub children: Vec<Child>,
|
|
|
|
|
pub last_exit_code: i32,
|
2023-05-18 06:53:23 +00:00
|
|
|
pub attr: Option<Termios>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ign_sigs() {
|
|
|
|
|
unsafe {
|
|
|
|
|
let mut mask = mem::zeroed();
|
|
|
|
|
sigemptyset(&mut mask);
|
|
|
|
|
sigaddset(&mut mask, SIGINT);
|
|
|
|
|
sigaddset(&mut mask, SIGQUIT);
|
|
|
|
|
sigaddset(&mut mask, SIGCHLD);
|
|
|
|
|
sigaddset(&mut mask, SIGTSTP);
|
|
|
|
|
sigaddset(&mut mask, SIGTTOU);
|
|
|
|
|
sigaddset(&mut mask, SIGTTIN);
|
|
|
|
|
sigprocmask(
|
|
|
|
|
SIG_BLOCK,
|
|
|
|
|
&mask as *const libc::sigset_t,
|
|
|
|
|
std::ptr::null_mut()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn unign_sigs() {
|
|
|
|
|
unsafe {
|
|
|
|
|
let mut mask = mem::zeroed();
|
|
|
|
|
sigemptyset(&mut mask);
|
|
|
|
|
sigaddset(&mut mask, SIGINT);
|
|
|
|
|
sigaddset(&mut mask, SIGQUIT);
|
|
|
|
|
sigaddset(&mut mask, SIGCHLD);
|
|
|
|
|
sigaddset(&mut mask, SIGTSTP);
|
|
|
|
|
sigaddset(&mut mask, SIGTTOU);
|
|
|
|
|
sigaddset(&mut mask, SIGTTIN);
|
|
|
|
|
sigprocmask(
|
|
|
|
|
SIG_UNBLOCK,
|
|
|
|
|
&mask as *const libc::sigset_t,
|
|
|
|
|
std::ptr::null_mut()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: trigger this on SIGCHLD instead of traditional shell once per input
|
|
|
|
|
pub fn check_jobs(state: &mut ShellState) {
|
2023-05-28 23:22:49 +00:00
|
|
|
let mut idx = 0usize;
|
2023-05-18 06:53:23 +00:00
|
|
|
while idx < state.children.len() {
|
2023-05-28 23:22:49 +00:00
|
|
|
if state.children[idx].try_wait().is_ok() {
|
2023-05-18 06:53:23 +00:00
|
|
|
state.children.remove(idx);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
idx += 1;
|
|
|
|
|
}
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn args_from_ast(ast: &Seg, syms: &mut SymTable) -> Vec<String> {
|
|
|
|
|
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,
|
2023-05-28 23:22:49 +00:00
|
|
|
Ctr::String(s) => args.push(s) == (),
|
2023-03-24 18:14:33 -07:00
|
|
|
_ => args.push(res_ctr.to_string()) == (),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-04-17 22:11:49 -07:00
|
|
|
eprintln!("couldnt eval args: {}",
|
|
|
|
|
eval_res.err().unwrap()) != ()
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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 {
|
2023-04-17 22:11:49 -07:00
|
|
|
eprintln!("couldnt eval args: {}",
|
|
|
|
|
eval_res.err().unwrap()) != ()
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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> {
|
2023-05-18 06:53:23 +00:00
|
|
|
let newchld: Result<std::process::Child, std::io::Error>;
|
|
|
|
|
unsafe {
|
|
|
|
|
newchld = Command::new(path)
|
|
|
|
|
.pre_exec(move || -> IOResult<()> {
|
|
|
|
|
let pid = unistd::getpid();
|
2023-05-28 23:22:49 +00:00
|
|
|
if unistd::setpgid(pid, pid).is_err() {
|
2023-05-18 06:53:23 +00:00
|
|
|
// crying would be nice... if you had eyes
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-28 23:22:49 +00:00
|
|
|
if !background &&unistd::tcsetpgrp(0, pid).is_err() {
|
|
|
|
|
// you wish to scream, but fork() has taken from you your throat
|
2023-05-18 06:53:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unign_sigs();
|
|
|
|
|
Ok(())
|
|
|
|
|
})
|
|
|
|
|
.args(args)
|
|
|
|
|
.stdin(infd)
|
|
|
|
|
.stdout(outfd)
|
|
|
|
|
.stderr(errfd)
|
|
|
|
|
.process_group(0)
|
|
|
|
|
.spawn();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-24 18:14:33 -07:00
|
|
|
|
2023-05-18 06:53:23 +00:00
|
|
|
// glibc says to do this in both parent and child
|
2023-03-24 18:14:33 -07:00
|
|
|
if let Ok(child) = newchld {
|
|
|
|
|
let pid = child.id();
|
2023-06-08 12:48:34 -07:00
|
|
|
_ = unistd::setpgid(
|
2023-05-18 06:53:23 +00:00
|
|
|
Pid::from_raw(pid as i32),
|
|
|
|
|
Pid::from_raw(pid as i32),
|
2023-06-08 12:48:34 -07:00
|
|
|
); // ignore result
|
2023-03-24 18:14:33 -07:00
|
|
|
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 {
|
2023-05-18 06:53:23 +00:00
|
|
|
let pid_fancy = Pid::from_raw(pid as i32);
|
|
|
|
|
if let Err(e) = unistd::tcsetpgrp(0, pid_fancy) {
|
|
|
|
|
eprintln!("couldn't give child the terminal: {}!", e);
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
2023-05-18 06:53:23 +00:00
|
|
|
nix::sys::signal::kill(
|
|
|
|
|
pid_fancy,
|
|
|
|
|
nix::sys::signal::Signal::SIGCONT,
|
|
|
|
|
).expect(
|
|
|
|
|
"couldnt sigcont... better get another tty."
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
match i.try_wait() {
|
|
|
|
|
Ok(maybe) => {
|
|
|
|
|
if let Some(exit) = maybe {
|
|
|
|
|
if let Err(e) = unistd::tcsetpgrp(0, state.parent_pid) {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"error re-acquiring terminal: {}!", e
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: this could be more elegant
|
|
|
|
|
if let Some(ref attr) = state.attr {
|
|
|
|
|
tcsetattr(0, SetArg::TCSADRAIN, attr).ok();
|
|
|
|
|
} else {
|
|
|
|
|
panic!("somehow no attrs")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.last_exit_code = exit
|
|
|
|
|
.code()
|
2023-05-28 23:22:49 +00:00
|
|
|
.unwrap_or(-1);
|
2023-05-18 06:53:23 +00:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
return Err(format!("error waiting on child: {}", e))
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(pos) = state.children.iter().position(|x| x.id() == pid) {
|
|
|
|
|
state.children.remove(pos);
|
2023-05-18 06:53:23 +00:00
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(format!("pid not found in active children"))
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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'))
|
|
|
|
|
";
|
2023-05-23 22:06:11 +00:00
|
|
|
fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
2023-03-24 18:14:33 -07:00
|
|
|
if ast.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load", "expected at least one input")
|
|
|
|
|
.into()))
|
2023-03-24 18:14:33 -07:00
|
|
|
} else {
|
|
|
|
|
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
2023-03-28 20:47:55 -07:00
|
|
|
if args.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load", "empty command")
|
|
|
|
|
.into()))
|
2023-05-28 23:22:49 +00:00
|
|
|
} else if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
|
|
|
|
if let Err(e) = launch_command(
|
|
|
|
|
filepath,
|
|
|
|
|
&Vec::from(args.make_contiguous()),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
false,
|
|
|
|
|
state,
|
|
|
|
|
) {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
2023-05-28 23:22:49 +00:00
|
|
|
("load", e)
|
2023-05-23 22:06:11 +00:00
|
|
|
.into()))
|
2023-05-28 23:22:49 +00:00
|
|
|
} else {
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
}
|
2023-05-28 23:22:49 +00:00
|
|
|
} else {
|
|
|
|
|
Err(start_trace(
|
|
|
|
|
("load", "binary not found on PATH")
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-05 21:18:40 -07:00
|
|
|
const PIPE_DOCSTRING: &str = "Calls a sequence of shell commands.
|
|
|
|
|
Each one has their output redirected to the input of the next one.
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
(pipe
|
|
|
|
|
(ls -la)
|
2023-04-07 12:21:22 -07:00
|
|
|
(grep '.rs')
|
2023-04-05 21:18:40 -07:00
|
|
|
(tr -d '.rs'))";
|
2023-05-23 22:06:11 +00:00
|
|
|
fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
2023-04-05 21:18:40 -07:00
|
|
|
if ast.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
return Err(start_trace(
|
|
|
|
|
("pipe", "expected at least one input")
|
|
|
|
|
.into()))
|
2023-04-05 21:18:40 -07:00
|
|
|
}
|
|
|
|
|
|
2023-04-07 12:21:22 -07:00
|
|
|
let mut err: String = String::new();
|
|
|
|
|
// posix layer will never control pid 0
|
|
|
|
|
let mut lastpid = 0;
|
2023-04-05 21:18:40 -07:00
|
|
|
if !ast.circuit(&mut |arg: &Ctr| -> bool {
|
|
|
|
|
if let Ctr::Seg(command_form) = arg {
|
2023-04-07 12:21:22 -07:00
|
|
|
let mut args = VecDeque::from(args_from_ast(command_form, syms));
|
2023-04-05 21:18:40 -07:00
|
|
|
if args.is_empty() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
2023-04-07 12:21:22 -07:00
|
|
|
let mut newchld = Command::new(filepath);
|
|
|
|
|
newchld.args(Vec::from(args.make_contiguous()))
|
2023-05-18 06:53:23 +00:00
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.process_group(0);
|
2023-04-07 12:21:22 -07:00
|
|
|
|
|
|
|
|
if let Some(pos) = state.children
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|x| x.id() == lastpid)
|
|
|
|
|
{
|
2023-05-18 06:53:23 +00:00
|
|
|
newchld.stdin(state.children.remove(pos).stdout.take().unwrap());
|
2023-04-07 12:21:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let chld_spwn = newchld.spawn();
|
|
|
|
|
if let Ok(child) = chld_spwn {
|
|
|
|
|
let pid = child.id();
|
|
|
|
|
lastpid = pid;
|
|
|
|
|
state.children.push(child);
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
err = format!("failed to spawn: {}", chld_spwn.err().unwrap());
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-05 21:18:40 -07:00
|
|
|
} else {
|
2023-04-07 12:21:22 -07:00
|
|
|
err = "file not found".to_string();
|
2023-04-05 21:18:40 -07:00
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-04-07 12:21:22 -07:00
|
|
|
err = format!("{} not a shell command", arg);
|
2023-04-05 21:18:40 -07:00
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}) {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("pipe", err)
|
|
|
|
|
.into()))
|
2023-05-28 23:22:49 +00:00
|
|
|
} else if lastpid > 0 {
|
|
|
|
|
if let Some(pos) = state.children
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|x| x.id() == lastpid)
|
|
|
|
|
{
|
|
|
|
|
let chld = state.children.remove(pos);
|
|
|
|
|
let exit = chld.wait_with_output()
|
|
|
|
|
.expect("failed to wait on last child");
|
|
|
|
|
state.last_exit_code = exit.status
|
|
|
|
|
.code()
|
|
|
|
|
.or(Some(-1))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
println!("{}", String::from_utf8_lossy(&exit.stdout));
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
2023-04-07 12:21:22 -07:00
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
2023-05-28 23:22:49 +00:00
|
|
|
("pipe", format!("lost last child (pid {})", lastpid))
|
2023-05-23 22:06:11 +00:00
|
|
|
.into()))
|
2023-04-07 12:21:22 -07:00
|
|
|
}
|
2023-05-28 23:22:49 +00:00
|
|
|
} else {
|
|
|
|
|
Err(start_trace(
|
|
|
|
|
("pipe", "impossible error state".to_string())
|
|
|
|
|
.into()))
|
2023-04-05 21:18:40 -07:00
|
|
|
}
|
|
|
|
|
}
|
2023-03-30 20:47:22 -07:00
|
|
|
|
|
|
|
|
const LOAD_TO_STRING_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'))
|
|
|
|
|
|
|
|
|
|
Unlike with the normal load function, the load to string function collects stdout output and returns it as a string.
|
|
|
|
|
";
|
2023-05-23 22:06:11 +00:00
|
|
|
fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
2023-03-30 20:47:22 -07:00
|
|
|
if ast.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-to-string", "expected at least one input")
|
|
|
|
|
.into()))
|
2023-03-30 20:47:22 -07:00
|
|
|
} else {
|
|
|
|
|
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
|
|
|
|
if args.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-to-string", "command is empty")
|
|
|
|
|
.into()))
|
2023-05-28 23:22:49 +00:00
|
|
|
} else if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
|
|
|
|
let res = Command::new(filepath).args(args).output();
|
|
|
|
|
if let Ok(output) = res {
|
|
|
|
|
if let Some(code) = output.status.code() {
|
|
|
|
|
state.last_exit_code = code;
|
|
|
|
|
}
|
|
|
|
|
if let Ok(string) = String::from_utf8(output.stdout) {
|
|
|
|
|
Ok(Ctr::String(string.trim_end().to_string()))
|
2023-03-30 20:47:22 -07:00
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
2023-05-28 23:22:49 +00:00
|
|
|
("load-to-string", "couldn't marshall utf-8 command output into a string")
|
2023-05-23 22:06:11 +00:00
|
|
|
.into()))
|
2023-03-30 20:47:22 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
2023-05-28 23:22:49 +00:00
|
|
|
("load-to-string", format!("{}", res.err().unwrap()))
|
2023-05-23 22:06:11 +00:00
|
|
|
.into()))
|
2023-03-30 20:47:22 -07:00
|
|
|
}
|
2023-05-28 23:22:49 +00:00
|
|
|
} else {
|
|
|
|
|
Err(start_trace(
|
|
|
|
|
("load-to-string", "binary not found on PATH")
|
|
|
|
|
.into()))
|
2023-03-30 20:47:22 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 20:47:55 -07:00
|
|
|
// TODO: rework this callback to reduce spaghett
|
2023-04-05 21:18:40 -07:00
|
|
|
const LOAD_WITH_DOCSTRING: &str = "Takes two arguments.
|
|
|
|
|
1. a list of up to three redirects
|
|
|
|
|
- A redirect looks like a variable declaration in a let form
|
|
|
|
|
Ex: ('stdout' '/dev/null')
|
|
|
|
|
- The first value is one of 'stdout', 'stdin', or 'stderr'
|
|
|
|
|
- The second value is a filename.
|
|
|
|
|
The file must exist for stdin redirects.
|
|
|
|
|
For other redirects the file will be created if not existing.
|
|
|
|
|
|
|
|
|
|
Example: (('stdin' '/path/to/myinput.txt')
|
|
|
|
|
('stdout' '/dev/null'))
|
|
|
|
|
|
|
|
|
|
2. a shell command
|
|
|
|
|
- exactly as would be passed to load
|
|
|
|
|
Example: (ping -c 4 google.com)
|
|
|
|
|
|
|
|
|
|
Example invocation:
|
|
|
|
|
(load-with (('stdout' '/dev/null'))
|
|
|
|
|
(ping -c 4 google.com))";
|
2023-05-23 22:06:11 +00:00
|
|
|
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
2023-03-28 20:47:55 -07:00
|
|
|
if ast.len() != 2 {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-with", "expected two inputs")
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
} else {
|
|
|
|
|
if let Ctr::Seg(ref fd_redirect_forms) = *ast.car {
|
|
|
|
|
let mut stdout: Option<Stdio> = None;
|
|
|
|
|
let mut stdin: Option<Stdio> = None;
|
|
|
|
|
let mut stderr: Option<Stdio> = None;
|
|
|
|
|
let mut e: Option<String> = None;
|
|
|
|
|
if !fd_redirect_forms.circuit(&mut |arg: &Ctr| -> bool {
|
|
|
|
|
// of the form Seg(Str(fd), Seg(Str(filepath)))
|
|
|
|
|
if let Ctr::Seg(ref io_redir) = arg {
|
|
|
|
|
if let Ctr::String(ref fd) = *io_redir.car {
|
|
|
|
|
if let Ctr::Seg(ref io_file) = *io_redir.cdr {
|
|
|
|
|
if let Ctr::String(ref filename) = *io_file.car {
|
|
|
|
|
match fd.as_str() {
|
|
|
|
|
"stdin" => {
|
|
|
|
|
if !stdin.is_none() {
|
|
|
|
|
e = Some(format!("stdin already defined"));
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
let input = File::open(filename);
|
|
|
|
|
if !input.is_ok() {
|
|
|
|
|
e = Some(format!("input file does not exist"));
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
stdin = Some(Stdio::from(input.ok().unwrap()));
|
2023-05-28 23:22:49 +00:00
|
|
|
true
|
2023-03-28 20:47:55 -07:00
|
|
|
},
|
|
|
|
|
"stdout" => {
|
|
|
|
|
if !stdout.is_none() {
|
|
|
|
|
e = Some(format!("stdout already defined"));
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 20:02:45 -07:00
|
|
|
let out = OpenOptions::new()
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.open(filename);
|
2023-03-28 20:47:55 -07:00
|
|
|
if !out.is_ok() {
|
|
|
|
|
e = Some(out.unwrap_err().to_string());
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
stdout = Some(Stdio::from(out.ok().unwrap()));
|
2023-05-28 23:22:49 +00:00
|
|
|
true
|
2023-03-28 20:47:55 -07:00
|
|
|
},
|
|
|
|
|
"stderr" => {
|
|
|
|
|
if !stderr.is_none() {
|
|
|
|
|
e = Some(format!("stderr already defined"));
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 20:02:45 -07:00
|
|
|
let err = OpenOptions::new()
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.open(filename);
|
2023-03-28 20:47:55 -07:00
|
|
|
if !err.is_ok() {
|
|
|
|
|
e = Some(err.unwrap_err().to_string());
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
stderr = Some(Stdio::from(err.ok().unwrap()));
|
2023-05-28 23:22:49 +00:00
|
|
|
true
|
2023-03-28 20:47:55 -07:00
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
e = Some(format!("can only override stdin, stdout, or stderr"));
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
e = Some(format!("{} is not a string", *io_file.car));
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
e = Some(format!("fd override must have both an fd and a filename"));
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
e = Some(format!("{} is not a string", *io_redir.car));
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
e = Some(format!("not a list: {}", arg));
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}) {
|
2023-05-23 22:06:11 +00:00
|
|
|
return Err(start_trace(
|
|
|
|
|
("load-with", e.unwrap())
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now deal with the actual command
|
|
|
|
|
if let Ctr::Seg(ref command_forms) = *ast.cdr {
|
|
|
|
|
let mut args: VecDeque<String>;
|
|
|
|
|
if let Ctr::Seg(ref command) = *command_forms.car {
|
|
|
|
|
args = VecDeque::from(args_from_ast(command, syms));
|
|
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
return Err(start_trace(
|
|
|
|
|
("load-with", "expected command input to be a list")
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if args.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-with", "empty command")
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
} else {
|
|
|
|
|
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
2023-05-23 22:06:11 +00:00
|
|
|
if let Err(e) = launch_command(
|
2023-03-28 20:47:55 -07:00
|
|
|
filepath,
|
|
|
|
|
&Vec::from(args.make_contiguous()),
|
|
|
|
|
stdin.or(Some(Stdio::inherit())).unwrap(),
|
|
|
|
|
stdout.or(Some(Stdio::inherit())).unwrap(),
|
|
|
|
|
stderr.or(Some(Stdio::inherit())).unwrap(),
|
|
|
|
|
false,
|
|
|
|
|
state,
|
2023-05-23 22:06:11 +00:00
|
|
|
) {
|
|
|
|
|
Err(start_trace(
|
|
|
|
|
("load-with", e)
|
|
|
|
|
.into()))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
|
|
|
|
}
|
2023-03-28 20:47:55 -07:00
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-with", "binary not found on PATH")
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-with", "expected second input to be list of command elements")
|
|
|
|
|
.into()))
|
2023-03-28 20:47:55 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("load-with", "expected first input to be a list of lists")
|
|
|
|
|
.into()))
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Q_DOCSTRING: &str = "returns exit code of last process to be run in posix layer";
|
2023-05-23 22:06:11 +00:00
|
|
|
fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
2023-03-24 18:14:33 -07:00
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 23:08:44 +00:00
|
|
|
const EXIT_DOCSTRING: &str = "Takes on integer input. calls libc exit() with given argument.";
|
|
|
|
|
fn exit_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
|
|
|
|
|
let mut ret = -1;
|
|
|
|
|
if let Ctr::Integer(i) = *ast.car {
|
|
|
|
|
ret = i;
|
|
|
|
|
} else {
|
|
|
|
|
eprintln!("WARNING: input was not an integer. Exiting -1.")
|
|
|
|
|
}
|
|
|
|
|
unsafe {
|
|
|
|
|
exit(ret as i32);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 06:53:23 +00:00
|
|
|
const BG_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).
|
|
|
|
|
|
|
|
|
|
The command executed in the background while your shell remains active.
|
|
|
|
|
Output from your command may appear in the terminal, but it will not be able to read from the terminal.
|
|
|
|
|
|
|
|
|
|
examples:
|
|
|
|
|
(bg ping -c4 google.com)
|
|
|
|
|
(bg vim) ;; vim waits patiently for you to foreground the process with 'fg'
|
|
|
|
|
";
|
2023-05-23 22:06:11 +00:00
|
|
|
fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, Traceback> {
|
2023-05-18 06:53:23 +00:00
|
|
|
if ast.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("bg", "expected at least one input")
|
|
|
|
|
.into()))
|
2023-05-18 06:53:23 +00:00
|
|
|
} else {
|
|
|
|
|
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
|
|
|
|
if args.is_empty() {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("bg", "empty command")
|
|
|
|
|
.into()))
|
2023-05-28 23:22:49 +00:00
|
|
|
} else if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
|
|
|
|
if let Err(e) = launch_command(
|
|
|
|
|
filepath,
|
|
|
|
|
&Vec::from(args.make_contiguous()),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
true,
|
|
|
|
|
state,
|
|
|
|
|
) {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
2023-05-28 23:22:49 +00:00
|
|
|
("bg", e)
|
2023-05-23 22:06:11 +00:00
|
|
|
.into()))
|
2023-05-28 23:22:49 +00:00
|
|
|
} else {
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
2023-05-18 06:53:23 +00:00
|
|
|
}
|
2023-05-28 23:22:49 +00:00
|
|
|
} else {
|
|
|
|
|
Err(start_trace(
|
|
|
|
|
("bg", "binary not found on PATH")
|
|
|
|
|
.into()))
|
2023-05-18 06:53:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 23:08:44 +00:00
|
|
|
const FG_DOCSTRING: &str = "takes one argument (an integer).
|
2023-05-18 06:53:23 +00:00
|
|
|
Integer is presumed to be a PID of a running background job.
|
|
|
|
|
If PID corresponds with a background job, fg will try to foreground it.
|
|
|
|
|
If PID is not a current running background job, fg will return an error.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
(bg vim) (fg 13871)
|
|
|
|
|
";
|
2023-05-25 23:08:44 +00:00
|
|
|
fn fg_callback(
|
2023-05-18 06:53:23 +00:00
|
|
|
ast: &Seg,
|
|
|
|
|
_syms: &mut SymTable,
|
|
|
|
|
shell_state: &mut ShellState
|
2023-05-23 22:06:11 +00:00
|
|
|
) -> Result <Ctr, Traceback> {
|
2023-05-18 06:53:23 +00:00
|
|
|
if let Ctr::Integer(i) = *ast.car {
|
2023-05-23 22:06:11 +00:00
|
|
|
if let Err(e) = make_foreground(i as u32, shell_state) {
|
|
|
|
|
Err(start_trace(
|
|
|
|
|
("fg", e)
|
|
|
|
|
.into()))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Ctr::None)
|
|
|
|
|
}
|
2023-05-18 06:53:23 +00:00
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("fg", "expected input to be an integer")
|
|
|
|
|
.into()))
|
2023-05-18 06:53:23 +00:00
|
|
|
}
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
|
2023-05-25 23:08:44 +00:00
|
|
|
const CD_DOCSTRING: &str =
|
2023-04-17 22:11:49 -07:00
|
|
|
"Expects 1 argument (a string). Changes to a new directory";
|
2023-05-25 23:08:44 +00:00
|
|
|
fn cd_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
|
2023-04-17 22:11:49 -07:00
|
|
|
if let Ctr::String(ref dir) = *ast.car {
|
|
|
|
|
let dirp = Path::new(dir);
|
2023-05-28 23:22:49 +00:00
|
|
|
if let Err(s) = set_current_dir(dirp) {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("cd", s.to_string())
|
|
|
|
|
.into()))
|
2023-04-17 22:11:49 -07:00
|
|
|
} else {
|
|
|
|
|
Ok(Ctr::None)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-05-23 22:06:11 +00:00
|
|
|
Err(start_trace(
|
|
|
|
|
("cd", "expected input to be a string")
|
|
|
|
|
.into()))
|
2023-04-17 22:11:49 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-24 18:14:33 -07:00
|
|
|
pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>>) {
|
|
|
|
|
let pid = unistd::getpid();
|
|
|
|
|
let pgid_res = unistd::getpgid(Some(pid));
|
2023-05-18 06:53:23 +00:00
|
|
|
let sid_res = unistd::getsid(Some(pid));
|
|
|
|
|
|
2023-05-28 23:22:49 +00:00
|
|
|
if pgid_res.is_err() || sid_res.is_err() {
|
2023-03-24 18:14:33 -07:00
|
|
|
panic!("couldn't get pgid")
|
|
|
|
|
}
|
|
|
|
|
let pgid = pgid_res.ok().unwrap();
|
2023-05-18 06:53:23 +00:00
|
|
|
let sid = sid_res.ok().unwrap();
|
|
|
|
|
let shattr: Termios;
|
2023-03-24 18:14:33 -07:00
|
|
|
|
|
|
|
|
// one mut borrow
|
|
|
|
|
{
|
|
|
|
|
let mut state = shell_state.borrow_mut();
|
|
|
|
|
state.parent_pid = pid;
|
2023-05-18 06:53:23 +00:00
|
|
|
state.parent_sid = sid;
|
|
|
|
|
if let Ok(attr) = tcgetattr(0) {
|
|
|
|
|
state.attr = Some(attr.clone());
|
2023-05-28 23:22:49 +00:00
|
|
|
shattr = attr;
|
2023-05-18 06:53:23 +00:00
|
|
|
} else {
|
|
|
|
|
panic!("couldn't get term attrs");
|
|
|
|
|
}
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let term_pgrp_res = unistd::tcgetpgrp(0);
|
2023-05-28 23:22:49 +00:00
|
|
|
if term_pgrp_res.is_err() {
|
2023-05-18 06:53:23 +00:00
|
|
|
panic!("couldn't get terminal's pgrp: {:?}", term_pgrp_res)
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 20:17:42 -07:00
|
|
|
if pid != pgid {
|
|
|
|
|
if let Err(e) = unistd::setpgid(pid, pid) {
|
|
|
|
|
panic!("couldn't set PGID: {}", e)
|
2023-05-18 06:53:23 +00:00
|
|
|
}
|
2023-04-17 20:17:42 -07:00
|
|
|
}
|
2023-03-24 18:14:33 -07:00
|
|
|
|
|
|
|
|
if let Err(e) = unistd::tcsetpgrp(0, pid) {
|
|
|
|
|
panic!("couldn't grab terminal: {}", e)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-27 14:34:07 -07:00
|
|
|
// these clones are needed for callbacks which move references
|
|
|
|
|
let load_ss = shell_state.clone();
|
2023-03-24 18:14:33 -07:00
|
|
|
let bg_ss = shell_state.clone();
|
|
|
|
|
let q_ss = shell_state.clone();
|
2023-03-28 20:47:55 -07:00
|
|
|
let lw_ss = shell_state.clone();
|
2023-03-30 20:47:22 -07:00
|
|
|
let lts_ss = shell_state.clone();
|
2023-04-07 12:21:22 -07:00
|
|
|
let p_ss = shell_state.clone();
|
2023-05-18 06:53:23 +00:00
|
|
|
let fg_ss = shell_state.clone();
|
|
|
|
|
|
|
|
|
|
ign_sigs();
|
2023-03-24 18:14:33 -07:00
|
|
|
|
|
|
|
|
syms.insert(
|
|
|
|
|
String::from("l"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("load"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(LOAD_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-03-24 18:14:33 -07:00
|
|
|
load_callback(ast, symtable, &mut shell_state.borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-27 14:34:07 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("load"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("load"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(LOAD_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-03-27 14:34:07 -07:00
|
|
|
load_callback(ast, symtable, &mut load_ss.borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-24 18:14:33 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("bg"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("background"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(BG_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-04-19 18:24:15 -07:00
|
|
|
bg_callback(ast, symtable, &mut bg_ss.borrow_mut())
|
2023-03-24 18:14:33 -07:00
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-05-18 06:53:23 +00:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("fg"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("foreground"),
|
|
|
|
|
args: Args::Strict(vec![Type::Integer]),
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(FG_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-05-18 06:53:23 +00:00
|
|
|
fg_callback(ast, symtable, &mut fg_ss.borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-24 18:14:33 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("?"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("?"),
|
|
|
|
|
args: Args::None,
|
|
|
|
|
conditional_branches: false,
|
|
|
|
|
docs: String::from(Q_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-04-19 18:24:15 -07:00
|
|
|
q_callback(ast, symtable, &mut q_ss.borrow_mut())
|
2023-03-24 18:14:33 -07:00
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-28 20:47:55 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("load-with"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("load-with"),
|
|
|
|
|
args: Args::Lazy(2),
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(LOAD_WITH_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-04-19 18:24:15 -07:00
|
|
|
load_with_callback(ast, symtable, &mut lw_ss.borrow_mut())
|
2023-03-28 20:47:55 -07:00
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-30 20:47:22 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("load-to-string"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("load-to-string"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(LOAD_TO_STRING_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-04-19 18:24:15 -07:00
|
|
|
// extra nonsense needed to allow nested calls
|
|
|
|
|
load_to_string_callback(
|
|
|
|
|
ast,
|
|
|
|
|
symtable,
|
|
|
|
|
&mut lts_ss
|
|
|
|
|
.try_borrow_mut()
|
2023-05-28 23:22:49 +00:00
|
|
|
.unwrap_or(RefCell::from(ShellState{
|
2023-04-19 18:24:15 -07:00
|
|
|
parent_pid: pid,
|
2023-05-18 06:53:23 +00:00
|
|
|
parent_sid: pgid,
|
2023-04-19 18:24:15 -07:00
|
|
|
children: vec![],
|
|
|
|
|
last_exit_code: 0,
|
2023-05-18 06:53:23 +00:00
|
|
|
attr: Some(shattr.clone()),
|
|
|
|
|
}).borrow_mut()))
|
2023-03-30 20:47:22 -07:00
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-03-28 20:47:55 -07:00
|
|
|
|
2023-04-07 12:21:22 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("pipe"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("pipe"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(PIPE_DOCSTRING),
|
2023-05-23 22:06:11 +00:00
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, Traceback> {
|
2023-04-19 18:24:15 -07:00
|
|
|
pipe_callback(ast, symtable, &mut p_ss.borrow_mut())
|
2023-04-07 12:21:22 -07:00
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-05-25 23:08:44 +00:00
|
|
|
|
|
|
|
|
syms.insert(
|
|
|
|
|
String::from("exit"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("exit"),
|
|
|
|
|
args: Args::Strict(vec![Type::Integer]),
|
|
|
|
|
conditional_branches: false,
|
|
|
|
|
docs: String::from(EXIT_DOCSTRING),
|
|
|
|
|
value: ValueType::Internal(Rc::new(exit_callback)),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
syms.insert(
|
|
|
|
|
"cd".to_string(),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("cd"),
|
|
|
|
|
args: Args::Strict(vec![Type::String]),
|
|
|
|
|
conditional_branches: false,
|
|
|
|
|
docs: CD_DOCSTRING.to_string(),
|
|
|
|
|
value: ValueType::Internal(Rc::new(cd_callback)),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|