ea_command/commands/
run.rs

1use crate::archive;
2use crate::interface::Style;
3use crate::parsers;
4use atty;
5use pty::fork::Fork;
6use std;
7use std::error;
8use std::fmt;
9use std::fs;
10use std::io::{self, Read, Write};
11use std::process;
12
13#[derive(Debug)]
14enum RunError {
15    CouldNotExecuteCommand(String),
16    CommandEncounteredError(String),
17    CommandWasInterrupted(String),
18}
19
20impl RunError {
21    fn new(code: u8, command: &str) -> Option<Self> {
22        match code {
23            0 => Some(Self::CouldNotExecuteCommand(command.to_string())),
24            1 => Some(Self::CommandEncounteredError(command.to_string())),
25            2 => Some(Self::CommandWasInterrupted(command.to_string())),
26            _ => None,
27        }
28    }
29
30    fn could_not_execute_command() -> u8 { 0 }
31    fn command_encountered_error() -> u8 { 1 }
32    fn command_was_interrupted() -> u8 { 2 }
33}
34
35impl fmt::Display for RunError {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        match self {
38            RunError::CouldNotExecuteCommand(command) => write!(f, "could not execute {}", command),
39            RunError::CommandEncounteredError(command) => write!(f, "{} encountered an error", command),
40            RunError::CommandWasInterrupted(command) => write!(f, "{} was interrupted by signal", command),
41        }
42    }
43}
44
45impl error::Error for RunError {}
46
47// deliberately mis-spelled
48static ERROR_SIGNAL: [u8; 4] = [0xde, 0xad, 0xbe, 0xaf];
49
50fn format_error(is_tty: bool, error: Box<dyn error::Error>) -> String {
51    if is_tty {
52        format!("\x1b[0m\x1b[31m[ea]: {}\x1b[0m\n", error)
53    } else {
54        format!("[ea]: {}\n", error)
55    }
56}
57
58pub fn run(style: &Style, executable: &str, arguments: &[String], debug: Option<String>) {
59    let is_tty = atty::is(atty::Stream::Stdout);
60    let mut output = execute(is_tty, executable, arguments);
61    let output_len = output.len();
62    let mut error_exit_code: Option<i32> = None;
63    if output_len >= 4 && output[(output_len - 4)..] == ERROR_SIGNAL {
64        _ = io::stderr().write(&output);
65        let error = RunError::new(output[output_len - 5], executable).expect("Error synthesized");
66        _ = io::stderr().write(format_error(is_tty, Box::new(error)).as_bytes());
67        error_exit_code = Some(output[output_len - 6] as i32);
68        output = output[0..(output_len - 6)].to_vec();
69    }
70
71    let parsed = match style {
72        Style::Grouped => parsers::grouped::grouped,
73        Style::Linear => parsers::linear::linear,
74        Style::Search => parsers::search::search,
75        Style::Rust => parsers::rust::rust,
76        Style::Py => parsers::python::python,
77    }(&output);
78
79    let (display, locations) = match parsed {
80        Ok(result) => result,
81        Err(error) => {
82            _ = io::stdout().write(&output);
83            _ = io::stderr().write(format_error(is_tty, Box::new(error)).as_bytes());
84            return;
85        }
86    };
87
88    if error_exit_code.is_none() || !&locations.is_empty() {
89        _ = io::stdout().write(&display);
90        _ = archive::write(&locations);
91    } // else we would already let the executable print its errors, and we have nothing to add
92
93    if let Some(debug_path) = debug {
94        _ = fs::write(
95            format!("{}.args", debug_path),
96            format!("{:?}\n{}\n{:?}", style, executable, arguments),
97        );
98        _ = fs::write(format!("{}.in", debug_path), output);
99        _ = fs::write(format!("{}.out", debug_path), &display);
100    }
101
102    if let Some(code) = error_exit_code {
103        process::exit(code)
104    }
105}
106
107fn execute_simple(executable: &str, arguments: &[String], output: &mut Vec<u8>) -> i32 {
108    // We must run with .status() as opposed to .output() because we might be in a pty.
109    // Running .output() would convince the process it's not in a pty!
110    let execution_result = process::Command::new(executable).args(arguments).status();
111
112    let error_code: u8;
113    let exit: u8;
114    match execution_result {
115        Ok(exit_status) => {
116            if let Some(exit_code) = exit_status.code() {
117                exit = exit_code as u8;
118                // if exit is 0, this following error won't really be used later.
119                error_code = RunError::command_encountered_error();
120            } else {
121                exit = 1;
122                error_code = RunError::command_was_interrupted();
123            }
124        }
125        Err(error) => {
126            output.extend_from_slice(error.to_string().as_bytes());
127            exit = error.raw_os_error().unwrap_or(1) as u8;
128            error_code = RunError::could_not_execute_command();
129        }
130    }
131
132    // We are in a child process, whose entire output will be read by the parent.  To signal to the
133    // parent that something went wrong, we print out a special sequence at the end of the output,
134    // proceeded by an error code, proceeded by an exit status.  Any error produced by the external
135    // command is attached after the error signal sequence.
136    if exit != 0 {
137        output.push(exit);
138        output.push(error_code);
139        output.extend_from_slice(&ERROR_SIGNAL);
140    }
141
142    exit as i32
143}
144
145fn execute(is_tty: bool, executable: &str, arguments: &[String]) -> Vec<u8> {
146    let mut output = Vec::new();
147    if is_tty {
148        let fork = Fork::from_ptmx().unwrap();
149        if let Ok(mut parent) = fork.is_parent() {
150            _ = parent.read_to_end(&mut output);
151        } else {
152            let code = execute_simple(executable, arguments, &mut output);
153            if code != 0 {
154                _ = io::stderr().write(&output);
155            }
156            process::exit(code);
157        }
158    } else {
159        _ = execute_simple(executable, arguments, &mut output);
160    }
161    output
162}