1pub mod history;
27pub mod proc;
28pub mod prompt;
29pub mod unixsignal;
30
31extern crate nix;
32extern crate whoami;
33
34use history::ShellHistory;
35use proc::{ShellError, ShellProc, ShellProcState};
36use prompt::ShellPrompt;
37
38use crate::config::PromptConfig;
39use crate::translator::ioprocessor::IOProcessor;
40
41use std::path::PathBuf;
42use std::time::{Duration};
43
44#[derive(Copy, Clone, PartialEq, std::fmt::Debug)]
49pub enum ShellState {
50 Shell,
51 SubprocessRunning,
52 Terminated,
53 Unknown
54}
55
56pub struct Shell {
60 pub history: ShellHistory,
61 process: ShellProc,
62 prompt: ShellPrompt,
63 props: ShellProps,
64 state: ShellState
65}
66
67pub(crate) struct ShellProps {
71 pub username: String,
72 pub hostname: String,
73 pub elapsed_time: Duration,
74 pub exit_status: u8,
75 pub wrkdir: PathBuf
76}
77
78impl Shell {
79 pub fn start(exec: String, args: Vec<String>, prompt_config: &PromptConfig) -> Result<Shell, ShellError> {
83 let mut argv: Vec<String> = Vec::with_capacity(1 + args.len());
85 let shell_prompt: ShellPrompt = ShellPrompt::new(prompt_config);
86 argv.push(exec.clone());
87 for arg in args.iter() {
88 argv.push(arg.clone());
89 }
90 let shell_process: ShellProc = match ShellProc::start(argv) {
91 Ok(p) => p,
92 Err(err) => return Err(err),
93 };
94 let user: String = whoami::username();
96 let hostname: String = Shell::get_hostname();
98 let wrkdir: PathBuf = shell_process.wrkdir.clone();
99 Ok(Shell {
100 process: shell_process,
101 prompt: shell_prompt,
102 props: ShellProps::new(hostname, user, wrkdir),
103 history: ShellHistory::new(),
104 state: ShellState::Shell
105 })
106 }
107
108 pub fn stop(&mut self) -> Result<u8, ShellError> {
112 while self.get_state() != ShellState::Terminated {
113 let _ = self.process.kill();
114 }
115 self.history.clear();
116 self.process.cleanup()
117 }
118
119 pub fn read(&mut self) -> Result<(Option<String>, Option<String>), ShellError> {
123 self.process.read()
124 }
125
126 pub fn write(&mut self, input: String) -> Result<(), ShellError> {
130 self.process.write(input)
131 }
132
133 #[allow(dead_code)]
137 pub fn raise(&mut self, sig: unixsignal::UnixSignal) -> Result<(), ShellError> {
138 self.process.raise(sig.to_nix_signal())
139 }
140
141 pub fn get_state(&mut self) -> ShellState {
145 let proc_state: ShellProcState = self.process.update_state();
146 match self.state {
147 _ => {
148 self.state = match proc_state {
149 ShellProcState::Idle => ShellState::Shell,
150 ShellProcState::SubprocessRunning => ShellState::SubprocessRunning,
151 _ => ShellState::Terminated
152 };
153 self.state
154 }
155 }
156 }
157
158 pub fn refresh_env(&mut self) {
162 self.props.username = whoami::username();
163 self.props.hostname = Shell::get_hostname();
164 self.props.wrkdir = self.process.wrkdir.clone();
165 self.props.exit_status = self.process.exit_status;
166 self.props.elapsed_time = self.process.exec_time;
167 }
168
169 pub fn get_promptline(&mut self, processor: &IOProcessor) -> String {
173 self.prompt.get_line(&self.props, processor)
174 }
175
176 fn get_hostname() -> String {
180 let full_hostname: String = whoami::hostname();
181 let tokens: Vec<&str> = full_hostname.split(".").collect();
182 String::from(*tokens.get(0).unwrap())
183 }
184
185}
186
187impl ShellProps {
189
190 pub(self) fn new(hostname: String, username: String, wrkdir: PathBuf) -> ShellProps {
194 ShellProps {
195 hostname: hostname,
196 username: username,
197 wrkdir: wrkdir,
198 elapsed_time: Duration::from_secs(0),
199 exit_status: 0
200 }
201 }
202}
203
204#[cfg(test)]
207mod tests {
208
209 use super::*;
210 use std::thread::sleep;
211 use std::time::{Duration, Instant};
212
213 #[test]
214 fn test_shell_props_new() {
215 let shell_props: ShellProps = ShellProps::new(String::from("computer"), String::from("root"), PathBuf::from("/tmp/"));
216 sleep(Duration::from_millis(500)); assert_eq!(shell_props.username, String::from("root"));
218 assert_eq!(shell_props.hostname, String::from("computer"));
219 assert_eq!(shell_props.wrkdir, PathBuf::from("/tmp/"));
220 assert_eq!(shell_props.elapsed_time.as_millis(), 0);
221 assert_eq!(shell_props.exit_status, 0);
222 }
223
224 #[test]
225 fn test_shell_start() {
226 let shell: String = String::from("sh");
228 let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).ok().unwrap();
230 sleep(Duration::from_millis(500)); assert_ne!(shell_env.process.pid, 0);
233 assert_eq!(shell_env.get_state(), ShellState::Shell);
235 assert_eq!(shell_env.history.len(), 0);
237 assert_eq!(shell_env.state, ShellState::Shell);
239 println!("Username: {}", shell_env.props.username);
241 println!("Hostname: {}", shell_env.props.hostname);
242 println!("Working directory: {}", shell_env.props.wrkdir.display());
243 assert!(shell_env.props.username.len() > 0);
244 assert!(shell_env.props.hostname.len() > 0);
245 assert!(format!("{}", shell_env.props.wrkdir.display()).len() > 0);
246 shell_env.refresh_env();
248 assert_eq!(shell_env.stop().unwrap(), 9);
250 sleep(Duration::from_millis(500)); assert_eq!(shell_env.get_state(), ShellState::Terminated);
252 }
253
254 #[test]
255 fn test_shell_start_failed() {
256 let shell: String = String::from("pipponbash");
258 let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).unwrap();
260 sleep(Duration::from_millis(500)); assert_eq!(shell_env.get_state(), ShellState::Terminated);
263 }
264
265 #[test]
266 fn test_shell_exec() {
267 let shell: String = String::from("sh");
269 let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).ok().unwrap();
271 sleep(Duration::from_millis(500)); assert_ne!(shell_env.process.pid, 0);
274 assert_eq!(shell_env.get_state(), ShellState::Shell);
276 let command: String = String::from("head -n 2\n");
278 assert!(shell_env.write(command).is_ok());
279 sleep(Duration::from_millis(500));
280 assert_eq!(shell_env.get_state(), ShellState::SubprocessRunning);
282 let stdin: String = String::from("foobar\n");
283 assert!(shell_env.write(stdin.clone()).is_ok());
284 sleep(Duration::from_millis(500));
286 let t_start: Instant = Instant::now();
288 let mut test_must_pass: bool = false;
289 loop {
290 let (stdout, stderr) = shell_env.read().ok().unwrap();
291 if stdout.is_some() {
292 assert_eq!(stdout.unwrap(), stdin);
293 assert!(stderr.is_none());
294 break;
295 }
296 sleep(Duration::from_millis(50));
297 if t_start.elapsed() > Duration::from_secs(1) {
298 test_must_pass = true;
299 break; }
301 }
302 assert_eq!(shell_env.get_state(), ShellState::SubprocessRunning);
304 if ! test_must_pass { let stdin: String = String::from("foobar\n");
306 assert!(shell_env.write(stdin.clone()).is_ok());
307 sleep(Duration::from_millis(50));
308 assert!(shell_env.read().is_ok());
309 sleep(Duration::from_millis(50));
310 assert_eq!(shell_env.get_state(), ShellState::Shell);
311 }
312 assert!(shell_env.process.kill().is_ok());
315 sleep(Duration::from_millis(500));
317 assert_eq!(shell_env.get_state(), ShellState::Terminated);
318 assert_eq!(shell_env.stop().unwrap(), 9);
319 }
320
321 #[test]
322 fn test_shell_terminate_gracefully() {
323 let shell: String = String::from("sh");
325 let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).ok().unwrap();
327 sleep(Duration::from_millis(500)); assert_ne!(shell_env.process.pid, 0);
330 assert_eq!(shell_env.get_state(), ShellState::Shell);
332 sleep(Duration::from_millis(500));
334 let command: String = String::from("exit 5\n");
335 assert!(shell_env.write(command).is_ok());
336 sleep(Duration::from_millis(1000));
338 assert_eq!(shell_env.get_state(), ShellState::Terminated);
340 assert_eq!(shell_env.stop().unwrap(), 5);
342 }
343
344 #[test]
345 fn test_shell_raise() {
346 let shell: String = String::from("sh");
348 let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).ok().unwrap();
350 sleep(Duration::from_millis(500)); assert!(shell_env.raise(unixsignal::UnixSignal::Sigint).is_ok());
352 sleep(Duration::from_millis(500));
354 assert_eq!(shell_env.get_state(), ShellState::Terminated);
356 assert_eq!(shell_env.stop().unwrap(), 2);
358 }
359
360 #[test]
361 fn test_shell_hostname() {
362 assert_ne!(Shell::get_hostname(), String::from(""));
363 }
364}