use std::env;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::process;
use libc;
use nix::unistd::{ForkResult, execve, pipe};
use crate::builtins;
use crate::calculator;
use crate::jobc;
use crate::libs;
use crate::parsers;
use crate::scripting;
use crate::shell::{self, Shell};
use crate::tools::{self, clog};
use crate::types::{CommandLine, CommandOptions, CommandResult};
fn try_run_builtin_in_subprocess(sh: &mut Shell, cl: &CommandLine,
idx_cmd: usize, capture: bool) -> Option<i32> {
if let Some(cr) = try_run_builtin(sh, cl, idx_cmd, capture) {
return Some(cr.status);
}
None
}
fn try_run_builtin(sh: &mut Shell, cl: &CommandLine,
idx_cmd: usize, capture: bool) -> Option<CommandResult> {
let capture = capture && idx_cmd +1 == cl.commands.len();
if idx_cmd >= cl.commands.len() {
println_stderr!("unexpected error in try_run_builtin");
return None;
}
let cmd = &cl.commands[idx_cmd];
let tokens = cmd.tokens.clone();
let cname = tokens[0].1.clone();
if cname == "alias" {
let cr = builtins::alias::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "bg" {
let cr = builtins::bg::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "cd" {
let cr = builtins::cd::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "cinfo" {
let cr = builtins::cinfo::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "exec" {
let cr = builtins::exec::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "exit" {
let cr = builtins::exit::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "export" {
let cr = builtins::export::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "fg" {
let cr = builtins::fg::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "history" {
let cr = builtins::history::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "jobs" {
let cr = builtins::jobs::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "minfd" {
let cr = builtins::minfd::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "read" {
let cr = builtins::read::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "set" {
let cr = builtins::set::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "source" {
let cr = builtins::source::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "ulimit" {
let cr = builtins::ulimit::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "unalias" {
let cr = builtins::unalias::run(sh, cl, cmd, capture);
return Some(cr);
} else if cname == "vox" {
let cr = builtins::vox::run(sh, cl, cmd, capture);
return Some(cr);
}
None
}
pub fn run_pipeline(sh: &mut shell::Shell, cl: &CommandLine, tty: bool,
capture: bool, log_cmd: bool) -> (bool, CommandResult) {
let mut term_given = false;
if cl.background && capture {
println_stderr!("cicada: cannot capture output of background cmd");
return (term_given, CommandResult::error());
}
if let Some(cr) = try_run_calculator(&cl.line, capture) {
return (term_given, cr);
}
if let Some(cr) = try_run_func(sh, &cl, capture, log_cmd) {
return (term_given, cr);
}
if log_cmd {
log!("run: {}", cl.line);
}
let length = cl.commands.len();
if length == 0 {
println!("cicada: invalid command: cmds with empty length");
return (false, CommandResult::error());
}
let mut pipes = Vec::new();
let mut errored_pipes = false;
for _ in 0..length - 1 {
match pipe() {
Ok(fds) => pipes.push(fds),
Err(e) => {
errored_pipes = true;
println_stderr!("cicada: pipeline1: {}", e);
break;
}
}
}
if errored_pipes {
unsafe {
for fds in pipes {
libc::close(fds.0);
libc::close(fds.1);
}
}
return (false, CommandResult::error());
}
if pipes.len() + 1 != length {
println!("cicada: invalid command: unmatched pipes count");
return (false, CommandResult::error());
}
let mut pgid: i32 = 0;
let mut fg_pids: Vec<i32> = Vec::new();
let isatty = if tty { unsafe { libc::isatty(1) == 1 } } else { false };
let options = CommandOptions {
isatty: isatty,
capture_output: capture,
background: cl.background,
envs: cl.envs.clone(),
};
let mut fds_capture_stdout = None;
let mut fds_capture_stderr = None;
if capture {
match pipe() {
Ok(fds) => fds_capture_stdout = Some(fds),
Err(e) => {
println_stderr!("cicada: pipeline2: {}", e);
return (false, CommandResult::error());
}
}
match pipe() {
Ok(fds) => fds_capture_stderr = Some(fds),
Err(e) => {
if let Some(fds) = fds_capture_stdout {
unsafe {
libc::close(fds.0);
libc::close(fds.1);
}
}
println_stderr!("cicada: pipeline3: {}", e);
return (false, CommandResult::error());
}
}
}
let mut cmd_result = CommandResult::new();
for i in 0..length {
let child_id: i32 = _run_single_command(
sh,
cl,
i,
&options,
&mut pgid,
&mut term_given,
&mut cmd_result,
&pipes,
&fds_capture_stdout,
&fds_capture_stderr,
);
if child_id > 0 && !cl.background {
fg_pids.push(child_id);
}
}
if cl.is_single_and_builtin() {
return (false, cmd_result);
}
if cl.background {
if let Some(job) = sh.get_job_by_gid(pgid) {
println_stderr!("[{}] {}", job.id, job.gid);
}
}
if !fg_pids.is_empty() {
let _cr = jobc::wait_fg_job(sh, pgid, &fg_pids);
if !capture {
cmd_result = _cr;
}
}
(term_given, cmd_result)
}
fn _run_single_command(sh: &mut shell::Shell, cl: &CommandLine, idx_cmd: usize,
options: &CommandOptions, pgid: &mut i32,
term_given: &mut bool, cmd_result: &mut CommandResult,
pipes: &Vec<(RawFd, RawFd)>,
fds_capture_stdout: &Option<(RawFd, RawFd)>,
fds_capture_stderr: &Option<(RawFd, RawFd)>) -> i32 {
let capture = options.capture_output;
if cl.is_single_and_builtin() {
if let Some(cr) = try_run_builtin(sh, cl, idx_cmd, capture) {
*cmd_result = cr;
return unsafe { libc::getpid() };
}
println_stderr!("cicada: error when run singler builtin");
log!("error when run singler builtin: {:?}", cl);
return 1;
}
let pipes_count = pipes.len();
let mut fds_stdin = None;
let cmd = cl.commands.get(idx_cmd).unwrap();
if cmd.has_here_string() {
match pipe() {
Ok(fds) => fds_stdin = Some(fds),
Err(e) => {
println_stderr!("cicada: pipeline4: {}", e);
return 1;
}
}
}
match libs::fork::fork() {
Ok(ForkResult::Child) => {
unsafe {
libc::signal(libc::SIGTSTP, libc::SIG_DFL);
libc::signal(libc::SIGQUIT, libc::SIG_DFL);
}
if idx_cmd > 0 {
for i in 0..idx_cmd-1 {
let fds = pipes[i];
unsafe {
libc::close(fds.0);
libc::close(fds.1);
}
}
}
for i in idx_cmd+1..pipes_count {
let fds = pipes[i];
unsafe {
libc::close(fds.0);
libc::close(fds.1);
}
}
if idx_cmd < pipes_count {
unsafe {
if let Some(fds) = fds_capture_stdout {
libc::close(fds.0);
libc::close(fds.1);
}
if let Some(fds) = fds_capture_stderr {
libc::close(fds.0);
libc::close(fds.1);
}
}
}
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);
libc::close(fds_prev.1);
}
}
if idx_cmd < pipes_count {
let fds = pipes[idx_cmd];
unsafe {
libc::dup2(fds.1, 1);
libc::close(fds.1);
libc::close(fds.0);
}
}
if cmd.has_redirect_from() {
if let Some(redirect_from) = &cmd.redirect_from {
let fd = tools::get_fd_from_file(&redirect_from.clone().1);
if fd == -1 {
process::exit(1);
}
unsafe {
libc::dup2(fd, 0);
libc::close(fd);
}
}
}
if cmd.has_here_string() {
if let Some(fds) = fds_stdin {
unsafe {
libc::close(fds.1);
libc::dup2(fds.0, 0);
libc::close(fds.0);
}
}
}
let mut stdout_redirected = false;
let mut stderr_redirected = false;
for item in &cmd.redirects_to {
let from_ = &item.0;
let op_ = &item.1;
let to_ = &item.2;
if to_ == "&1" && from_ == "2" {
unsafe {
if idx_cmd < pipes_count {
libc::dup2(1, 2);
} else if !options.capture_output {
let fd = libc::dup(1);
if fd == -1 {
println_stderr!("cicada: dup error");
process::exit(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);
if fd == -1 {
println_stderr!("cicada: dup error");
process::exit(1);
}
libc::dup2(fd, 1);
} else {
}
}
} else {
let append = op_ == ">>";
match tools::create_raw_fd_from_file(to_, append) {
Ok(fd) => {
if fd == -1 {
println_stderr!("cicada: fork: fd error");
process::exit(1);
}
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: fork: {}", e);
process::exit(1);
}
}
}
}
if idx_cmd == pipes_count && options.capture_output {
unsafe {
if !stdout_redirected {
if let Some(fds) = fds_capture_stdout {
libc::close(fds.0);
libc::dup2(fds.1, 1);
libc::close(fds.1);
}
}
if !stderr_redirected {
if let Some(fds) = fds_capture_stderr {
libc::close(fds.0);
libc::dup2(fds.1, 2);
libc::close(fds.1);
}
}
}
}
if cmd.is_builtin() {
if let Some(status) = try_run_builtin_in_subprocess(sh, cl, idx_cmd, capture) {
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 cl.envs.iter() {
c_envs.push(
CString::new(format!("{}={}", key, value).as_str()).expect("CString error"),
);
}
let program = &cmd.tokens[0].1;
let path = if program.contains('/') {
program.clone()
} else {
libs::path::find_file_in_path(&program, true)
};
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();
let c_args: Vec<&CStr> = c_args.iter().map(|x| x.as_c_str()).collect();
let c_envs: Vec<&CStr> = c_envs.iter().map(|x| x.as_c_str()).collect();
match execve(&c_program, &c_args, &c_envs) {
Ok(_) => {}
Err(e) => match e {
nix::Error::ENOEXEC => {
println_stderr!("cicada: {}: exec format error (ENOEXEC)", program);
}
nix::Error::ENOENT => {
println_stderr!("cicada: {}: file does not exist", program);
}
nix::Error::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 idx_cmd == 0 {
*pgid = pid;
unsafe {
if cfg!(target_os = "macos") {
loop {
let _pgid = libc::getpgid(pid);
if _pgid == pid {
break;
}
}
}
if options.isatty && !options.capture_output && !cl.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", cl.background);
}
if let Some(redirect_from) = &cmd.redirect_from {
if redirect_from.0 == "<<<" {
if let Some(fds) = fds_stdin {
unsafe {
libc::close(fds.0);
let mut f = File::from_raw_fd(fds.1);
match f.write_all(redirect_from.1.clone().as_bytes()) {
Ok(_) => {},
Err(e) => println_stderr!("cicada: write_all: {}", e),
}
match f.write_all(b"\n") {
Ok(_) => {},
Err(e) => println_stderr!("cicada: write_all: {}", e),
}
libc::close(fds.1);
}
}
}
}
if idx_cmd < pipes_count {
let fds = pipes[idx_cmd];
unsafe {
libc::close(fds.1);
}
}
if idx_cmd > 0 {
unsafe {
let fds = pipes[idx_cmd - 1];
libc::close(fds.0);
}
}
if idx_cmd == pipes_count && options.capture_output {
let mut s_out = String::new();
let mut s_err = String::new();
unsafe {
if let Some(fds) = fds_capture_stdout {
libc::close(fds.1);
let mut f_out = File::from_raw_fd(fds.0);
match f_out.read_to_string(&mut s_out) {
Ok(_) => {},
Err(e) => println_stderr!("cicada: readstr: {}", e),
}
libc::close(fds.0);
}
if let Some(fds) = fds_capture_stderr {
libc::close(fds.1);
let mut f_err = File::from_raw_fd(fds.0);
match f_err.read_to_string(&mut s_err) {
Ok(_) => {},
Err(e) => println_stderr!("cicada: readstr: {}", e),
}
libc::close(fds.0);
}
}
*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
}
}
}
fn try_run_func(sh: &mut Shell, cl: &CommandLine, capture: bool,
log_cmd: bool) -> Option<CommandResult> {
if cl.is_empty() {
return None;
}
let command = &cl.commands[0];
if let Some(func_body) = sh.get_func(&command.tokens[0].1) {
let mut args = vec!["cicada".to_string()];
for token in &command.tokens {
args.push(token.1.to_string());
}
if log_cmd {
log!("run func: {:?}", &args);
}
let cr_list = scripting::run_lines(sh, &func_body, &args, capture);
let mut stdout = String::new();
let mut stderr = String::new();
for cr in cr_list {
stdout.push_str(&cr.stdout.trim());
stdout.push(' ');
stderr.push_str(&cr.stderr.trim());
stderr.push(' ');
}
let mut cr = CommandResult::new();
cr.stdout = stdout;
cr.stderr = stderr;
return Some(cr);
}
None
}
fn try_run_calculator(line: &str, capture: bool) -> Option<CommandResult> {
if tools::is_arithmetic(line) {
match run_calculator(line) {
Ok(result) => {
let mut cr = CommandResult::new();
if capture {
cr.stdout = result.clone();
} else {
println!("{}", result);
}
return Some(cr);
}
Err(e) => {
let mut cr = CommandResult::from_status(0, 1);
if capture {
cr.stderr = e.to_string();
} else {
println_stderr!("cicada: calculator: {}", e);
}
return Some(cr);
}
}
}
None
}
pub fn run_calculator(line: &str) -> Result<String, &str> {
let parse_result = calculator::calculate(line);
match parse_result {
Ok(calc) => {
if line.contains('.') {
Ok(format!("{}", calculator::eval_float(calc)))
} else {
Ok(format!("{}", calculator::eval_int(calc)))
}
}
Err(_) => {
return Err("syntax error");
}
}
}