Skip to content

Commit

Permalink
🐞 fix: Fix shell
Browse files Browse the repository at this point in the history
Fix shell: use ptyprocess to make it interactive
  • Loading branch information
wychlw committed Sep 17, 2024
1 parent 20452d4 commit 1b05c95
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 296 deletions.
287 changes: 71 additions & 216 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.125"
serialport = "4.5.0"
ssh2 = "0.9.4"
subprocess = "0.2.9"
tokio = { version = "1", features = ["full"] }
toml = "0.8.19"
colored = "2.1.0"
nix = { version = "0.29.0", features = ["fs", "process", "signal", "term"] }
ptyprocess = "0.4.1"

[toolchain]
channel = "nightly"
6 changes: 4 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
- [ ] **修 shell……**
- [ ] **想办法把那个 PyTty 缩小掉……**
- [ ] **可维护性:想办法把那个 PyTty 缩小掉……**
- [ ] Shell 控制命令的 filter

- [ ] 更多的连接方式
- [x] 更完善的 SSH
Expand All @@ -13,10 +13,12 @@
- [ ] 设备抽象 : mod device

- [x] 更加的多态支持 : where T: Tty -> Box<dyn Tty>
- [ ] Trait Cast

- [ ] 导出的 API
- [x] 实现 cli-like 面向外界的哪一个巨型 wrapper
- [?] 从 dyn Tty 中区分出这个巨型 wrapper,并分开实现(可以在每次开头前都试一试?)
- [ ]
- [x] 执行器

- [ ] 与下一步测试软件的进一步集成
Expand Down
4 changes: 2 additions & 2 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub const DURATION: u64 = 25;
pub const SHELL_DURATION: u64 = 1;
pub const DURATION: u64 = 100;
pub const SHELL_DURATION: u64 = 50;

pub const SHELL_PROMPT: &str = ""; // I don't know why it doesn't echo back the prompt... Add this as a workaround
2 changes: 1 addition & 1 deletion src/exec/cli_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
};

use crate::{
consts::DURATION, err, info, log, term::tty::{DynTty, InnerTty, Tty, WrapperTty}, util::{anybase::AnyBase, util::rand_string}
consts::DURATION, err, info, term::tty::{DynTty, InnerTty, Tty, WrapperTty}, util::{anybase::AnyBase, util::rand_string}
};

use super::cli_api::CliTestApi;
Expand Down
2 changes: 1 addition & 1 deletion src/term/asciicast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use asciicast::{Entry, EventType, Header};
use serde_json::to_string;

use crate::{
consts::{DURATION, SHELL_PROMPT},
consts::DURATION,
info,
util::anybase::AnyBase,
};
Expand Down
124 changes: 53 additions & 71 deletions src/term/shell.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
use ptyprocess::{stream::Stream, PtyProcess};
use std::io::{BufRead, BufReader, Read};
use std::ops::DerefMut;
use std::os::fd::AsRawFd;
use std::{
any::Any,
collections::HashMap,
env,
error::Error,
io::{ErrorKind, Read, Write},
process::{ChildStdin, Command, Stdio},
io::Write,
process::Command,
sync::{Arc, Mutex},
thread::{sleep, spawn, JoinHandle},
time::Duration,
};

use crate::util::util::try_read;
use crate::{consts::SHELL_DURATION, err, info, log, util::anybase::AnyBase};

use super::tty::Tty;

pub struct Shell {
stdin: ChildStdin,
inner: Arc<Mutex<Stream>>,
buff: Arc<Mutex<Vec<u8>>>,
proc: PtyProcess,
handle: Option<JoinHandle<()>>,
stop: Arc<Mutex<bool>>,
}
Expand All @@ -34,78 +38,59 @@ impl Shell {

info!("Spawn shell process: {}", shell);

let filtered_env: HashMap<String, String> = env::vars()
.filter(|&(ref k, _)| k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH")
.collect();

let inner = Command::new(shell)
.envs(&filtered_env)
.envs(Into::<HashMap<_, _>>::into([("PS1", r"[\u@\h \W]\$")]))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn();
let mut inner = Command::new(shell);
inner.args(&["-i"]);
let inner = PtyProcess::spawn(inner);
if let Err(e) = inner {
err!("Failed to spawn shell process. Reason: {}", e);
return Err(Box::new(e));
}
let mut inner = inner.unwrap();
let proc = inner.unwrap();
let inner = proc.get_pty_stream()?;

let stdin = inner.stdin.take();
if let None = stdin {
err!("Failed to get stdin of shell process.");
return Err(Box::<dyn Error>::from(""));
}
let mut stdin = stdin.unwrap();
stdin
.write_all(b"export PS1=\"[\\u@\\h \\W]\\$\"\n")
.unwrap();

let stdout = inner.stdout.take();
if let None = stdout {
err!("Failed to get stdout of shell process.");
return Err(Box::<dyn Error>::from(""));
}
let stdout = stdout.unwrap();
info!(
"Shell process spawned, got streamed... FD: {:?}",
inner.as_raw_fd()
);

let stderr = inner.stderr.take();
if let None = stderr {
err!("Failed to get stderr of shell process.");
return Err(Box::<dyn Error>::from(""));
}
let stderr = stderr.unwrap();
let mut stdout = stdout.chain(stderr);
let inner = Arc::new(Mutex::new(inner));

let mut res = Shell {
stdin,
inner,
buff: Arc::new(Mutex::new(Vec::new())),
proc,
handle: None,
stop: Arc::new(Mutex::new(false)),
};

let buff_clone = res.buff.clone();
let stop_clone = res.stop.clone();
let buff = res.buff.clone();
let stop = res.stop.clone();
let stream = res.inner.clone();
let handle = spawn(move || loop {
sleep(Duration::from_millis(SHELL_DURATION));
{
let stop = stop_clone.lock().unwrap();
let stop: std::sync::MutexGuard<'_, bool> = stop.lock().unwrap();
if *stop {
log!("Stop shell process");
return;
}
}
let mut buf = [0u8];
let sz = stdout.read(&mut buf);
if let Err(e) = sz {
err!("Read from shell process failed. Reason: {}", e);
break;
}
if buf[0] == 0x0 {
continue;
let mut buf = Vec::new();
{
let mut stream = stream.lock().unwrap();
let mut reader = BufReader::new(stream.deref_mut());
let sz = try_read(&mut reader, &mut buf);
if let Err(e) = sz {
err!("Failed to read from shell process. Reason: {}", e);
return;
}
let sz = sz.unwrap();
if sz == 0 {
continue;
}
}
let mut buff = buff_clone.lock().unwrap();
if buf[0] != 0x0 {
buff.extend_from_slice(&buf);
{
let mut buff = buff.lock().unwrap();
buff.extend(buf.iter());
}
});

Expand All @@ -126,6 +111,7 @@ impl Shell {
}
*stop = true;
log!("Try to stop shell process");
self.proc.exit(false).unwrap();
// if let Some(handle) = self.handle.take() {
// handle.join().unwrap();
// self.inner.wait().unwrap();
Expand Down Expand Up @@ -184,23 +170,19 @@ impl Tty for Shell {
return Ok(res);
}
fn write(&mut self, data: &[u8]) -> Result<(), Box<dyn Error>> {
loop {
sleep(Duration::from_millis(SHELL_DURATION));
match self.stdin.write_all(data) {
Ok(_) => break,
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => {
err!("Write to shell process failed. Reason: {}", e);
return Err(Box::new(e));
}
let mut stream = self.inner.lock().unwrap();
info!("Shell locked...");
match stream.write(data) {
Ok(_) => {
stream.flush().unwrap();
info!("Shell write: {:?}", String::from_utf8_lossy(data));
return Ok(());
}
Err(e) => {
err!("Write to shell process failed. Reason: {}", e);
return Err(Box::new(e));
}
}
let res = self.stdin.flush();
if let Err(e) = res {
err!("Flush to shell process failed. Reason: {}", e);
return Err(Box::<dyn Error>::from(e));
}
return Ok(());
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/term/tee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Tty for Tee {
}
fn write(&mut self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
self.inner.write(data)?;
self.file.write_all(data)?; // tee should not write to file, but for log purpose...
// self.file.write_all(data)?; // tee should not write to file, but for log purpose...
Ok(())
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/util/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::{
error::Error,
io::{BufRead, ErrorKind},
};

use rand::{distributions::Alphanumeric, thread_rng, Rng};

Expand All @@ -24,3 +28,21 @@ pub fn rand_string(len: usize) -> Vec<u8> {
rnd
}

pub fn try_read<R: BufRead + ?Sized>(
r: &mut R,
buf: &mut Vec<u8>,
) -> Result<usize, Box<dyn Error>> {
loop {
let used = {
let available = match r.fill_buf() {
Ok(n) => n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(Box::new(e)),
};
buf.extend_from_slice(available);
available.len()
};
r.consume(used);
return Ok(used);
}
}

0 comments on commit 1b05c95

Please sign in to comment.