use crate::error::SysError;
use crate::shim::{self, Fork};
use crate::signal;
use crate::status::*;
use crate::term::{self, TtyMode};
use exec::Command;
use rustix::fs::{self, Mode, OFlags};
use rustix::io::{self, Errno, retry_on_intr};
use rustix::process::{self, Pid, Signal, WaitOptions, WaitStatus};
use rustix::pty::{self, OpenptFlags};
use rustix::stdio;
use std::os::fd::{OwnedFd, RawFd};
use std::path::Path;
use std::sync::Mutex;
use sysconf::raw::{SysconfVariable, sysconf};
pub struct PtyProc {
master_fd: OwnedFd,
slave_fd: OwnedFd,
child: Mutex<Child>,
}
struct Child {
pid: Option<Pid>,
last_status: Option<WaitStatus>,
final_status: Option<WaitStatus>,
}
#[derive(PartialEq)]
pub enum PtyWait {
Hang,
NoHang,
}
impl PtyProc {
pub fn open() -> Result<Self, SysError> {
let master_fd = match retry_on_intr(|| pty::openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY))
{
Ok(fd) => fd,
Err(err) => return Err(SysError("openpt()", err)),
};
if let Err(err) = retry_on_intr(|| pty::grantpt(&master_fd)) {
return Err(SysError("grantpt()", err));
}
if let Err(err) = retry_on_intr(|| pty::unlockpt(&master_fd)) {
return Err(SysError("unlockpt()", err));
}
let pts_name = match shim::ptsname(&master_fd) {
Ok(s) => s,
Err(err) => return Err(SysError("ptsname()", err)),
};
let slave_fd = match retry_on_intr(|| {
fs::open(
Path::new(&pts_name),
OFlags::RDWR | OFlags::NOCTTY,
Mode::empty(),
)
}) {
Ok(fd) => fd,
Err(err) => return Err(SysError("open()", err)),
};
Ok(PtyProc {
master_fd,
slave_fd,
child: Mutex::new(Child {
pid: None,
last_status: None,
final_status: None,
}),
})
}
pub fn dup_master(&self) -> Result<OwnedFd, SysError> {
retry_on_intr(|| io::dup(&self.master_fd)).map_err(|err| SysError("dup()", err))
}
pub fn dup_slave(&self) -> Result<OwnedFd, SysError> {
retry_on_intr(|| io::dup(&self.slave_fd)).map_err(|err| SysError("dup()", err))
}
pub fn spawn_child(&self, command: &mut Command) -> Result<(), SysError> {
let mut locked_child = self.child.lock().unwrap();
if locked_child.pid.is_some() {
panic!("attempt to call spawn_child() twice");
}
self.prepare_parent()?;
unsafe {
match shim::fork() {
Ok(Fork::Parent(pid)) => {
locked_child.pid = Some(pid);
}
Ok(Fork::Child) => {
if let Err(_) = self.prepare_child() {
shim::fast_exit(EXIT_FAILURE);
}
_ = command.exec();
shim::fast_exit(EXIT_COMMAND_FAILED);
}
Err(err) => {
return Err(SysError("fork()", err));
}
}
};
Ok(())
}
pub fn resize_child(&self) -> Result<(), SysError> {
let _locked_child = self.child.lock().unwrap();
if term::is_tty(stdio::stdout()) {
term::copy_tty_size(&self.master_fd, stdio::stdout())?;
}
Ok(())
}
pub fn kill_child(&self, sig: Signal) -> Result<(), SysError> {
let locked_child = self.child.lock().unwrap();
if !locked_child.pid.is_some() {
panic!("attempt to call kill_child() before spawn_child()");
}
if locked_child.final_status.is_some() {
panic!("attempt to call kill_child() after wait_child()");
}
if let Err(err) = process::kill_process_group(locked_child.pid.unwrap(), sig) {
return Err(SysError("kill()", err));
}
Ok(())
}
pub fn wait_child(&self, wait_mode: PtyWait) -> Result<Option<WaitStatus>, SysError> {
let mut locked_child = self.child.lock().unwrap();
if !locked_child.pid.is_some() {
panic!("attempt to call wait_child() before spawn_child()");
}
if let Some(final_status) = locked_child.final_status {
return Ok(Some(final_status));
}
let mut wait_opts = WaitOptions::UNTRACED | WaitOptions::CONTINUED;
if wait_mode == PtyWait::NoHang {
wait_opts |= WaitOptions::NOHANG;
}
loop {
let wait_status = match process::waitpid(locked_child.pid, wait_opts) {
Ok(Some((_, status))) => status,
Ok(None) => return Ok(None),
Err(Errno::INTR) => continue,
Err(err) => return Err(SysError("waitpid()", err)),
};
locked_child.last_status = Some(wait_status);
if wait_status.exited() || wait_status.signaled() {
locked_child.final_status = Some(wait_status);
}
return Ok(Some(wait_status));
}
}
pub fn child_status(&self) -> WaitStatus {
let locked_child = self.child.lock().unwrap();
if !locked_child.last_status.is_some() {
panic!("attempt to call child_status() before wait_child()");
}
locked_child.last_status.unwrap()
}
fn prepare_parent(&self) -> Result<(), SysError> {
term::set_tty_mode(&self.master_fd, TtyMode::CanonNoEcho)?;
if term::is_tty(stdio::stdout()) {
term::copy_tty_size(&self.master_fd, stdio::stdout())?;
}
Ok(())
}
fn prepare_child(&self) -> Result<(), SysError> {
signal::init_child_signals()?;
if let Err(err) = retry_on_intr(|| process::setsid()) {
return Err(SysError("setsid()", err));
}
if let Err(err) = retry_on_intr(|| process::ioctl_tiocsctty(&self.slave_fd)) {
return Err(SysError("ioctl(TIOCSCTTY)", err));
}
for dup_fn in &[
stdio::dup2_stdin::<&OwnedFd>,
stdio::dup2_stdout::<&OwnedFd>,
stdio::dup2_stderr::<&OwnedFd>,
] {
if let Err(err) = retry_on_intr(|| dup_fn(&self.slave_fd)) {
return Err(SysError("dup2()", err));
}
}
let max_fd = match sysconf(SysconfVariable::ScOpenMax) {
Ok(n) => n,
Err(_) => return Err(SysError("sysconf(_SC_OPEN_MAX)", Errno::NOTSUP)),
};
unsafe {
for fd in 3..=max_fd {
shim::close_raw(fd as RawFd);
}
};
Ok(())
}
}