use crate::builder::XCommandBuilder;
use crate::child_handle::XChildHandle;
use crate::env_var::EnvVar;
use async_stream::stream;
use eyre::bail;
use eyre::Result;
use log::debug;
use log::error;
use log::info;
use nix::pty::openpty;
use nix::pty::OpenptyResult;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::dup2;
use nix::unistd::execve;
use nix::unistd::pipe;
use nix::unistd::setsid;
use nix::unistd::ForkResult;
use nix::unistd::{close, fork, Pid};
use std::env;
use std::ffi::CString;
use std::fs::File;
use std::mem;
use std::os::fd::AsRawFd;
use std::os::fd::OwnedFd;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::FromRawFd;
use std::os::unix::prelude::RawFd;
use std::path::Path;
use std::pin::Pin;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tokio_fd::AsyncFd;
use tokio_stream::wrappers::LinesStream;
use tokio_stream::{Stream, StreamExt, StreamMap};
use which::which;
#[derive(Debug)]
pub struct XCommand {
command: CString,
args: Vec<CString>,
env: Vec<EnvVar>,
}
impl XCommand {
pub fn new(command: CString, args: Vec<CString>, env: Vec<EnvVar>) -> Self {
Self { command, args, env }
}
pub fn builder<P: AsRef<Path>>(command: P) -> Result<XCommandBuilder> {
XCommandBuilder::new(command)
}
fn exec(&self) -> Result<()> {
let mut args = self.args.clone();
args.insert(0, self.command.clone());
let env: Vec<CString> = self
.env
.iter()
.map(|var| {
let k = var.key.clone();
let v = var.value.clone();
let key_bytes = k.as_bytes();
let eq_bytes = "=".as_bytes();
let value_bytes = v.as_bytes();
let mut formatted =
Vec::with_capacity(key_bytes.len() + value_bytes.len() + eq_bytes.len());
formatted.extend_from_slice(key_bytes);
formatted.extend_from_slice(eq_bytes);
formatted.extend_from_slice(value_bytes);
CString::new(formatted).unwrap()
})
.collect();
match execve(&self.command, &args, &env) {
Ok(_) => {}
Err(e) => {
bail!(
"Unable to execve command '{:?}' with args {:?} and environment {:?}. Reason: {}",
self.command,
args,
env,
e
);
}
}
Ok(())
}
pub fn spawn(&self) -> Result<XChildHandle> {
debug!(
"Running '{:?}' with args {:?} and env {:?}",
self.command, self.args, self.env
);
let res = openpty(None, None)?;
let master = res.master;
let slave = res.slave;
let stdout_master = master.as_raw_fd();
let stdout_slave = slave.as_raw_fd();
mem::forget(master);
mem::forget(slave);
let res = openpty(None, None)?;
let master = res.master;
let slave = res.slave;
let stderr_master = master.as_raw_fd();
let stderr_slave = slave.as_raw_fd();
mem::forget(master);
mem::forget(slave);
let Ok(res) = (unsafe { fork() }) else {
bail!("fork() failed");
};
match res {
ForkResult::Parent { child } => {
close(stdout_slave).unwrap();
close(stderr_slave).unwrap();
Ok(XChildHandle::new(child, stdout_master, stderr_master).unwrap())
}
ForkResult::Child => {
close(stdout_master).unwrap();
close(stderr_master).unwrap();
dup2(stdout_slave.as_raw_fd(), libc::STDOUT_FILENO).unwrap();
dup2(stderr_slave.as_raw_fd(), libc::STDERR_FILENO).unwrap();
let Err(e) = self.exec() else {
unreachable!();
};
error!("failed to exec: {}", e);
std::process::exit(1);
}
}
}
}