use std::{collections::HashMap, io::IsTerminal, path::Path, sync::Arc};
use anyhow::Result;
use crate::redact::Redactor;
pub fn run(
shell: &str,
command: &str,
env: HashMap<String, String>,
cwd: &Path,
redactor: Option<Redactor>,
) -> Result<i32> {
let Some(redactor) = redactor else {
return run_plain(shell, command, env, cwd);
};
let redactor = Arc::new(redactor);
if std::io::stdout().is_terminal() {
run_pty(shell, command, env, cwd, redactor)
} else {
run_piped(shell, command, env, cwd, redactor)
}
}
fn run_plain(shell: &str, command: &str, env: HashMap<String, String>, cwd: &Path) -> Result<i32> {
let status = std::process::Command::new(shell)
.args(["-c", command])
.current_dir(cwd)
.envs(std::env::vars())
.envs(env)
.status()?;
Ok(status.code().unwrap_or(1))
}
fn run_pty(
shell: &str,
command: &str,
env: HashMap<String, String>,
cwd: &Path,
redactor: Arc<Redactor>,
) -> Result<i32> {
use portable_pty::{CommandBuilder, PtyPair, native_pty_system};
let PtyPair { master, slave } = native_pty_system().openpty(pty_size())?;
let reader = master.try_clone_reader()?;
let mut writer = master.take_writer()?;
let mut cmd = CommandBuilder::new(shell);
cmd.arg("-c");
cmd.arg(command);
cmd.cwd(cwd);
for (k, v) in std::env::vars() {
cmd.env(k, v);
}
for (k, v) in env {
cmd.env(k, v);
}
let mut child = slave.spawn_command(cmd)?;
drop(slave);
#[cfg(unix)]
setup_sigwinch_resize(&*master);
std::thread::spawn(move || {
std::io::copy(&mut std::io::stdin().lock(), &mut writer).ok();
});
redactor.stream(reader, &mut std::io::stdout().lock())?;
Ok(child.wait()?.exit_code() as i32)
}
fn run_piped(
shell: &str,
command: &str,
env: HashMap<String, String>,
cwd: &Path,
redactor: Arc<Redactor>,
) -> Result<i32> {
use std::process::{Command, Stdio};
let mut child = Command::new(shell)
.args(["-c", command])
.current_dir(cwd)
.envs(std::env::vars())
.envs(env)
.stdin(Stdio::inherit())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let child_stdout = child.stdout.take().unwrap();
let child_stderr = child.stderr.take().unwrap();
let redactor_stderr = Arc::clone(&redactor);
let stdout_thread = std::thread::spawn(move || {
redactor
.stream(child_stdout, &mut std::io::stdout().lock())
.ok();
});
let stderr_thread = std::thread::spawn(move || {
redactor_stderr
.stream(child_stderr, &mut std::io::stderr().lock())
.ok();
});
let status = child.wait()?;
stdout_thread.join().ok();
stderr_thread.join().ok();
Ok(status.code().unwrap_or(1))
}
fn pty_size() -> portable_pty::PtySize {
#[cfg(unix)]
{
let mut ws: nix::libc::winsize = unsafe { std::mem::zeroed() };
if unsafe { nix::libc::ioctl(nix::libc::STDOUT_FILENO, nix::libc::TIOCGWINSZ, &mut ws) }
== 0
&& ws.ws_row > 0
&& ws.ws_col > 0
{
return portable_pty::PtySize {
rows: ws.ws_row,
cols: ws.ws_col,
pixel_width: ws.ws_xpixel,
pixel_height: ws.ws_ypixel,
};
}
}
portable_pty::PtySize::default()
}
#[cfg(unix)]
fn setup_sigwinch_resize(master: &(dyn portable_pty::MasterPty + Send)) {
use nix::sys::signal::{SigHandler, Signal};
use std::sync::atomic::{AtomicBool, Ordering};
static SIGWINCH_PENDING: AtomicBool = AtomicBool::new(false);
extern "C" fn handler(_: nix::libc::c_int) {
SIGWINCH_PENDING.store(true, Ordering::Relaxed);
}
unsafe { nix::sys::signal::signal(Signal::SIGWINCH, SigHandler::Handler(handler)).ok() };
let Some(master_fd) = master.as_raw_fd() else {
return;
};
std::thread::spawn(move || {
loop {
std::thread::sleep(std::time::Duration::from_millis(50));
if SIGWINCH_PENDING.swap(false, Ordering::Relaxed) {
let mut ws: nix::libc::winsize = unsafe { std::mem::zeroed() };
if unsafe {
nix::libc::ioctl(nix::libc::STDOUT_FILENO, nix::libc::TIOCGWINSZ, &mut ws)
} == 0
{
unsafe { nix::libc::ioctl(master_fd, nix::libc::TIOCSWINSZ, &ws) };
}
}
}
});
}