use std::collections::HashMap;
use std::env;
use std::ffi::CString;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::io::FromRawFd;
use std::os::unix::io::RawFd;
use std::process;
use libc;
use regex::Regex;
use nix::errno::Errno;
use nix::unistd::execve;
use nix::unistd::pipe;
use nix::unistd::{fork, ForkResult};
use nix::Error;
use nom::IResult;
use crate::builtins;
use crate::jobc;
use crate::libs;
use crate::parsers;
use crate::shell;
use crate::tools::{self, clog};
use crate::types;
use crate::types::CommandOptions;
use crate::types::CommandResult;
use crate::types::Tokens;
pub fn handle_non_tty(sh: &mut shell::Shell) {
let mut buffer = String::new();
let stdin = io::stdin();
let mut handle = stdin.lock();
match handle.read_to_string(&mut buffer) {
Ok(_) => {
log!("run non tty command: {}", &buffer);
run_procs(sh, &buffer, false);
}
Err(e) => {
println!("cicada: io stdin read_to_string failed: {:?}", e);
}
}
}
fn tokens_to_cmd_tokens(tokens: &Tokens) -> Vec<Tokens> {
let mut cmd = Vec::new();
let mut cmds = Vec::new();
for token in tokens {
let sep = &token.0;
let value = &token.1;
if sep.is_empty() && value == "|" {
if cmd.is_empty() {
return Vec::new();
}
cmds.push(cmd.clone());
cmd = Vec::new();
} else {
cmd.push(token.clone());
}
}
if cmd.is_empty() {
return Vec::new();
}
cmds.push(cmd.clone());
cmds
}
pub fn run_procs(sh: &mut shell::Shell, line: &str, tty: bool) -> i32 {
if tools::is_arithmetic(line) {
if line.contains('.') {
match run_calc_float(line) {
Ok(x) => {
println!("{}", x);
return 0;
}
Err(e) => {
println!("Error: {}", e);
return 1;
}
}
} else {
match run_calc_int(line) {
Ok(x) => {
println!("{}", x);
return 0;
}
Err(e) => {
println!("Error: {}", e);
return 1;
}
}
}
}
let mut status = 0;
let mut sep = String::new();
for token in parsers::parser_line::line_to_cmds(&line) {
if token == ";" || token == "&&" || token == "||" {
sep = token.clone();
continue;
}
if sep == "&&" && status != 0 {
break;
}
if sep == "||" && status == 0 {
break;
}
let cmd = token.clone();
status = run_proc(sh, &cmd, tty);
sh.previous_status = status;
}
status
}
fn drain_env_tokens(tokens: &mut Tokens) -> HashMap<String, String> {
let mut envs: HashMap<String, String> = HashMap::new();
let mut n = 0;
for (sep, text) in tokens.iter() {
if !sep.is_empty() || !tools::re_contains(text, r"^([a-zA-Z0-9_]+)=(.*)$") {
break;
}
let re;
match Regex::new(r"^([a-zA-Z0-9_]+)=(.*)$") {
Ok(x) => {
re = x;
}
Err(e) => {
println_stderr!("Regex new: {:?}", e);
return envs;
}
}
for cap in re.captures_iter(text) {
let name = cap[1].to_string();
let value = parsers::parser_line::unquote(&cap[2]);
envs.insert(name, value);
}
n += 1;
}
if n > 0 {
tokens.drain(0..n);
}
envs
}
fn line_to_tokens(sh: &mut shell::Shell, line: &str) -> (Tokens, HashMap<String, String>) {
let mut tokens = parsers::parser_line::cmd_to_tokens(line);
shell::do_expansion(sh, &mut tokens);
let envs = drain_env_tokens(&mut tokens);
if tokens.is_empty() {
for (name, value) in envs.iter() {
sh.set_env(name, value);
}
return (Vec::new(), HashMap::new());
}
return (tokens, envs);
}
pub fn run_proc(sh: &mut shell::Shell, line: &str, tty: bool) -> i32 {
let (mut tokens, envs) = line_to_tokens(sh, line);
if tokens.is_empty() {
return 0;
}
let cmd = tokens[0].1.clone();
if cmd == "bg" {
return builtins::bg::run(sh, &tokens);
}
if cmd == "cd" {
return builtins::cd::run(sh, &tokens);
}
if cmd == "export" {
return builtins::export::run(sh, &tokens);
}
if cmd == "exec" {
return builtins::exec::run(&tokens);
}
if cmd == "exit" {
return builtins::exit::run(sh, &tokens);
}
if cmd == "fg" {
return builtins::fg::run(sh, &tokens);
}
if cmd == "vox" && tokens.len() > 1 && (tokens[1].1 == "enter" || tokens[1].1 == "exit") {
return builtins::vox::run(sh, &tokens);
}
let mut background = false;
let mut len = tokens.len();
if len > 1 && tokens[len - 1].1 == "&" {
background = true;
tokens.pop();
len -= 1;
}
let mut redirect_from = String::new();
let has_redirect_from = tokens.iter().any(|x| x.1 == "<");
if has_redirect_from {
if let Some(idx) = tokens.iter().position(|x| x.1 == "<") {
tokens.remove(idx);
len -= 1;
if len > idx {
redirect_from = tokens.remove(idx).1;
len -= 1;
} else {
println!("cicada: invalid command: cannot get redirect from");
return 1;
}
}
}
if len == 0 {
return 0;
}
let log_cmd = !sh.cmd.starts_with(' ');
let (term_given, cr) = run_pipeline(
sh,
&tokens,
&redirect_from,
background,
tty,
false,
log_cmd,
Some(envs),
);
if term_given {
unsafe {
let gid = libc::getpgid(0);
shell::give_terminal_to(gid);
}
}
cr.status
}
fn run_calc_float(line: &str) -> Result<f64, String> {
match parsers::parser_float::expr_float(line.as_bytes()) {
IResult::Done(_, x) => Ok(x),
IResult::Error(e) => Err(e.description().to_owned()),
IResult::Incomplete(_) => Err(String::from("Incomplete arithmetic")),
}
}
fn run_calc_int(line: &str) -> Result<i64, String> {
match parsers::parser_int::expr_int(line.as_bytes()) {
IResult::Done(_, x) => Ok(x),
IResult::Error(e) => Err(e.description().to_owned()),
IResult::Incomplete(_) => Err(String::from("Incomplete arithmetic")),
}
}
fn log_cmd_info(cmds: &Vec<Tokens>) {
let length = cmds.len();
let mut info = String::new();
for (i, cmd) in cmds.iter().enumerate() {
for item in cmd {
let sep = &item.0;
let token = &item.1;
info.push_str(sep);
info.push_str(token);
info.push_str(sep);
info.push(' ');
}
if length > 1 && i < length - 1 {
info.push_str("| ")
}
}
log!("run: {}", info.trim());
}
fn run_command(
sh: &mut shell::Shell,
cmd: &types::Command,
idx_cmd: usize,
options: &CommandOptions,
pgid: &mut i32,
term_given: &mut bool,
cmd_result: &mut CommandResult,
pipes: &Vec<(RawFd, RawFd)>,
) -> i32 {
let fds_capture_stdout: (RawFd, RawFd);
let fds_capture_stderr: (RawFd, RawFd);
match pipe() {
Ok(x) => {
fds_capture_stdout = x;
}
Err(e) => {
println_stderr!("cicada: pipe error: {:?}", e);
*cmd_result = CommandResult::error();
return 0;
}
}
match pipe() {
Ok(x) => {
fds_capture_stderr = x;
}
Err(e) => {
println_stderr!("cicada: pipe error: {:?}", e);
*cmd_result = CommandResult::error();
return 0;
}
}
let pipes_count = pipes.len();
match fork() {
Ok(ForkResult::Child) => {
if idx_cmd == 0 {
unsafe {
let pid = libc::getpid();
libc::setpgid(0, pid);
}
} else {
unsafe {
libc::setpgid(0, *pgid);
}
}
if idx_cmd > 0 {
let fds_prev = pipes[idx_cmd - 1];
unsafe {
libc::dup2(fds_prev.0, 0);
libc::close(fds_prev.0);
}
}
if idx_cmd < pipes_count {
let fds = pipes[idx_cmd];
unsafe {
libc::dup2(fds.1, 1);
}
}
if idx_cmd == 0 && !options.redirect_from.is_empty() {
let fd = tools::get_fd_from_file(&options.redirect_from);
unsafe {
libc::dup2(fd, 0);
libc::close(fd);
}
}
let mut stdout_redirected = false;
let mut stderr_redirected = false;
for item in &cmd.redirects {
let from_ = &item.0;
let op_ = &item.1;
let to_ = &item.2;
if to_ == "&1" && from_ == "2" {
unsafe {
if idx_cmd < pipes_count {
let fds = pipes[idx_cmd];
libc::dup2(fds.1, 2);
} else if !options.capture_output {
let fd = libc::dup(1);
libc::dup2(fd, 2);
} else {
}
}
} else if to_ == "&2" && from_ == "1" {
unsafe {
if idx_cmd < pipes_count || !options.capture_output {
let fd = libc::dup(2);
libc::dup2(fd, 1);
} else {
}
}
} else {
let append = op_ == ">>";
match tools::create_raw_fd_from_file(to_, append) {
Ok(fd) => {
if from_ == "1" {
unsafe {
libc::dup2(fd, 1);
}
stdout_redirected = true;
} else {
unsafe {
libc::dup2(fd, 2);
}
stderr_redirected = true;
}
}
Err(e) => {
println_stderr!("cicada: {}", e);
*cmd_result = CommandResult::error();
return 0;
}
}
}
}
if idx_cmd == pipes_count && options.capture_output {
unsafe {
if !stdout_redirected {
libc::close(fds_capture_stdout.0);
libc::dup2(fds_capture_stdout.1, 1);
libc::close(fds_capture_stdout.1);
}
if !stderr_redirected {
libc::close(fds_capture_stderr.0);
libc::dup2(fds_capture_stderr.1, 2);
libc::close(fds_capture_stderr.1);
}
}
}
let program = &cmd.tokens[0].1;
if program == "history" {
let status = builtins::history::run(&cmd);
process::exit(status);
} else if program == "vox" {
let status = builtins::vox::run(sh, &cmd.tokens);
process::exit(status);
} else if program == "cinfo" {
let status = builtins::cinfo::run();
process::exit(status);
} else if program == "jobs" {
let status = builtins::jobs::run(sh);
process::exit(status);
}
let mut c_envs: Vec<_> = env::vars()
.map(|(k, v)| CString::new(format!("{}={}", k, v).as_str()).expect("CString error"))
.collect();
for (key, value) in options.envs.iter() {
c_envs.push(
CString::new(format!("{}={}", key, value).as_str()).expect("CString error"),
);
}
let path = if program.contains('/') {
program.clone()
} else {
libs::path::find_first_exec(&program)
};
if path.is_empty() {
println_stderr!("cicada: {}: command not found", program);
process::exit(127);
}
let c_program = CString::new(path.as_str()).expect("CString::new failed");
let c_args: Vec<_> = cmd
.tokens
.iter()
.map(|x| CString::new(x.1.as_str()).expect("CString error"))
.collect();
match execve(&c_program, &c_args, &c_envs) {
Ok(_) => {}
Err(e) => match e {
Error::Sys(Errno::ENOEXEC) => {
println_stderr!("cicada: {}: exec format error (ENOEXEC)", program);
}
Error::Sys(Errno::ENOENT) => {
println_stderr!("cicada: {}: file does not exist", program);
}
Error::Sys(Errno::EACCES) => {
println_stderr!("cicada: {}: Permission denied", program);
}
_ => {
println_stderr!("cicada: {}: {:?}", program, e);
}
},
}
process::exit(1);
}
Ok(ForkResult::Parent { child, .. }) => {
let pid: i32 = child.into();
if options.isatty && !options.capture_output && idx_cmd == 0 {
*pgid = pid;
unsafe {
loop {
let _pgid = libc::getpgid(pid);
if _pgid == pid {
break;
}
}
if !options.background {
*term_given = shell::give_terminal_to(pid);
}
}
}
if options.isatty && !options.capture_output {
let _cmd = parsers::parser_line::tokens_to_line(&cmd.tokens);
sh.insert_job(*pgid, pid, &_cmd, "Running", options.background);
}
if idx_cmd < pipes_count {
let fds = pipes[idx_cmd];
unsafe {
libc::close(fds.1);
}
}
if idx_cmd == pipes_count && options.capture_output {
unsafe {
libc::close(fds_capture_stdout.1);
libc::close(fds_capture_stderr.1);
}
let mut f_out = unsafe { File::from_raw_fd(fds_capture_stdout.0) };
let mut s_out = String::new();
f_out.read_to_string(&mut s_out).expect("fds stdout");
let mut f_err = unsafe { File::from_raw_fd(fds_capture_stderr.0) };
let mut s_err = String::new();
f_err.read_to_string(&mut s_err).expect("fds stderr");
*cmd_result = CommandResult {
gid: *pgid,
status: 0,
stdout: s_out.clone(),
stderr: s_err.clone(),
}
}
return pid;
}
Err(_) => {
println_stderr!("Fork failed");
*cmd_result = CommandResult::error();
}
}
0
}
pub fn run_pipeline(
sh: &mut shell::Shell,
tokens: &Tokens,
redirect_from: &str,
background: bool,
tty: bool,
capture_output: bool,
log_cmd: bool,
envs: Option<HashMap<String, String>>,
) -> (bool, CommandResult) {
if background && capture_output {
println_stderr!("cicada: cannot capture output of background cmd");
return (false, CommandResult::error());
}
let mut term_given = false;
let mut cmd_result = CommandResult::new();
let cmds = tokens_to_cmd_tokens(&tokens);
if log_cmd {
log_cmd_info(&cmds);
}
let length = cmds.len();
if length == 0 {
println!("cicada: invalid command: cmds with empty length");
return (false, CommandResult::error());
}
let mut pipes = Vec::new();
for _ in 0..length - 1 {
let fds;
match pipe() {
Ok(x) => fds = x,
Err(e) => {
println!("pipe error: {:?}", e);
return (false, CommandResult::error());
}
}
pipes.push(fds);
}
if pipes.len() + 1 != length {
println!("cicada: invalid command: unmatched pipes count");
return (false, CommandResult::error());
}
let isatty = if tty {
unsafe { libc::isatty(0) == 1 }
} else {
false
};
let mut i = 0;
let mut pgid: i32 = 0;
let mut children: Vec<i32> = Vec::new();
let mut _envs: HashMap<String, String> = HashMap::new();
if let Some(x) = envs {
_envs = x;
}
for cmd in &cmds {
let cmd_new;
match parsers::parser_line::cmd_to_with_redirects(&cmd) {
Ok(x) => {
cmd_new = x;
}
Err(e) => {
println!("cicada: cmd_to_with_redirects failed: {:?}", e);
return (false, CommandResult::error());
}
}
let cmd_ = parsers::parser_line::tokens_to_args(&cmd_new.tokens);
if cmd_.is_empty() {
println!("cicada: cmd_ is empty");
return (false, CommandResult::error());
}
let options = CommandOptions {
redirect_from: redirect_from.to_string(),
isatty: isatty,
capture_output: capture_output,
background: background,
envs: _envs.clone(),
};
let child_id: i32 = run_command(
sh,
&cmd_new,
i,
&options,
&mut pgid,
&mut term_given,
&mut cmd_result,
&pipes,
);
if child_id > 0 && !background {
children.push(child_id);
}
i += 1;
}
if background {
if let Some(job) = sh.get_job_by_gid(pgid) {
println_stderr!("[{}] {}", job.id, job.gid);
}
}
for pid in &children {
let status = jobc::wait_process(sh, pgid, *pid, true);
if capture_output {
cmd_result.status = status;
} else {
cmd_result = CommandResult::from_status(pgid, status);
}
}
if cmd_result.status == types::STOPPED {
jobc::mark_job_as_stopped(sh, pgid);
}
(term_given, cmd_result)
}
fn run_with_shell<'a, 'b>(sh: &'a mut shell::Shell, line: &'b str) -> CommandResult {
let (mut tokens, envs) = line_to_tokens(sh, &line);
if tokens.is_empty() {
return CommandResult::new();
}
let mut len = tokens.len();
if len > 1 && tokens[len - 1].1 == "&" {
tokens.pop();
len -= 1;
}
let mut redirect_from = String::new();
let has_redirect_from = tokens.iter().any(|x| x.1 == "<");
if has_redirect_from {
if let Some(idx) = tokens.iter().position(|x| x.1 == "<") {
tokens.remove(idx);
len -= 1;
if len > idx {
redirect_from = tokens.remove(idx).1;
len -= 1;
} else {
println_stderr!("cicada: invalid command: cannot get redirect from");
return CommandResult::error();
}
}
}
if len == 0 {
return CommandResult::new();
}
let (_, cmd_result) = run_pipeline(
sh,
&tokens,
redirect_from.as_str(),
false,
false,
true,
false,
Some(envs),
);
cmd_result
}
pub fn run(line: &str) -> CommandResult {
let mut sh = shell::Shell::new();
return run_with_shell(&mut sh, line);
}
#[cfg(test)]
mod tests {
use super::run_calc_float;
use super::run_calc_int;
use super::run_with_shell;
use super::shell;
use super::tools;
#[test]
fn test_run_calc_float() {
assert_eq!(
run_calc_float("(1 + 2 * 3.0 - 1.54) / 0.2"),
Ok(27.299999999999997)
);
}
#[test]
fn test_run_calc_int() {
assert_eq!(run_calc_int("(5 + 2 * 3 - 4) / 3"), Ok(2));
}
#[test]
fn test_run_itself() {
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
let f = File::open("./tests/run_procs.txt").expect("file not found");
let file = BufReader::new(&f);
let mut input = String::new();
let mut expected_stdout = String::new();
let mut sh = shell::Shell::new();
for (num, l) in file.lines().enumerate() {
let line = l.unwrap();
match num % 3 {
0 => {
input = line.clone();
}
1 => {
expected_stdout = line.clone();
}
2 => match run_with_shell(&mut sh, &input) {
cr => {
let ptn = if expected_stdout.is_empty() {
r"^$"
} else {
expected_stdout.as_str()
};
let matched = tools::re_contains(&cr.stdout.trim(), &ptn);
if !matched {
println!("\nSTDOUT Check Failed:");
println!("input: {}", &input);
println!("stdout: {:?}", &cr.stdout.trim());
println!("expected: {:?}", &expected_stdout);
println!("line number: {}\n", num);
}
assert!(matched);
let ptn = if line.is_empty() {
r"^$"
} else {
line.as_str()
};
let matched = tools::re_contains(&cr.stderr.trim(), &ptn);
if !matched {
println!("\nSTDERR Check Failed:");
println!("input: {}", &input);
println!("stderr: {:?}", &cr.stderr);
println!("expected: {}", &ptn);
println!("line number: {}\n", num + 1);
}
assert!(matched);
}
},
_ => {
assert!(false);
}
}
}
}
}