154 lines
4.3 KiB
Rust
154 lines
4.3 KiB
Rust
use nix::fcntl::{fcntl, FcntlArg, OFlag};
|
|
use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
|
|
use nix::sys::stat::Mode;
|
|
use nix::unistd::{ForkResult, Pid};
|
|
use nix::{ioctl_none_bad, ioctl_write_ptr_bad};
|
|
|
|
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, TIOCSCTTY};
|
|
// ioctl request code to set window size of pty:
|
|
use libc::TIOCSWINSZ;
|
|
use std::ffi::CStr;
|
|
use std::fs::File;
|
|
use std::os::fd::AsRawFd;
|
|
use std::path::Path;
|
|
|
|
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
|
|
|
// nix macro that generates an ioctl call to set window size of pty:
|
|
ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize);
|
|
|
|
// request to "Make the given terminal the controlling terminal of the calling process"
|
|
ioctl_none_bad!(set_controlling_terminal, TIOCSCTTY);
|
|
|
|
pub struct Child {
|
|
pub pty: tokio::fs::File,
|
|
pub proc: Proc,
|
|
}
|
|
|
|
impl Child {
|
|
pub fn set_nodelay(&mut self) -> nix::Result<()> {
|
|
fcntl(self.pty.as_raw_fd(), FcntlArg::F_SETFL(OFlag::O_NDELAY)).map(|_| ())
|
|
}
|
|
}
|
|
|
|
pub struct Proc(pub Pid);
|
|
|
|
impl Proc {
|
|
// copied from
|
|
// https://doc.rust-lang.org/nightly/src/std/sys/unix/process/process_unix.rs.html#744-757
|
|
pub fn try_wait(&self) -> std::io::Result<Option<i32>> {
|
|
let mut status = 0;
|
|
let pid = cvt(unsafe { libc::waitpid(self.0.as_raw(), &mut status, libc::WNOHANG) })?;
|
|
if pid == 0 {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some(status))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for Proc {
|
|
fn drop(&mut self) {
|
|
_ = nix::sys::signal::kill(self.0, nix::sys::signal::Signal::SIGTERM);
|
|
}
|
|
}
|
|
|
|
pub struct PreExec<T>(T);
|
|
|
|
impl<T> PreExec<T> {
|
|
pub unsafe fn new(t: T) -> Self {
|
|
Self(t)
|
|
}
|
|
}
|
|
|
|
pub fn create_pty<S, F>(path: &CStr, argv: &[S], pre_exec: PreExec<F>) -> nix::Result<Child>
|
|
where
|
|
S: AsRef<CStr> + std::fmt::Debug,
|
|
F: FnOnce() -> anyhow::Result<()>,
|
|
{
|
|
/* Create a new master */
|
|
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
|
|
|
/* For some reason, you have to give permission to the master to have a
|
|
* pty. What is it good for otherwise? */
|
|
grantpt(&master_fd)?;
|
|
unlockpt(&master_fd)?;
|
|
|
|
/* Get the path of the slave */
|
|
let slave_name = unsafe { ptsname(&master_fd) }?;
|
|
|
|
/* Try to open the slave */
|
|
let _slave_fd = nix::fcntl::open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
|
|
trace!("master opened the slave_fd!");
|
|
|
|
/* Launch our child process. The main application loop can inspect and then
|
|
pass the stdin data to it. */
|
|
let child_pid = match unsafe { nix::unistd::fork() } {
|
|
Ok(ForkResult::Child) => {
|
|
let t = init_child(path, argv, pre_exec, &slave_name).unwrap();
|
|
#[allow(unreachable_code)]
|
|
match t {}
|
|
},
|
|
Ok(ForkResult::Parent { child }) => child,
|
|
Err(e) => panic!("{}", e),
|
|
};
|
|
|
|
let winsize = Winsize {
|
|
ws_row: 25,
|
|
ws_col: 80,
|
|
ws_xpixel: 0,
|
|
ws_ypixel: 0,
|
|
};
|
|
let master_fd = master_fd.into_raw_fd();
|
|
/* Tell the master the size of the terminal */
|
|
unsafe { set_window_size(master_fd, &winsize)? };
|
|
let master_file = unsafe { File::from_raw_fd(master_fd) };
|
|
Ok(Child {
|
|
pty: master_file.into(),
|
|
proc: Proc(child_pid),
|
|
})
|
|
}
|
|
|
|
fn init_child<S, F>(
|
|
path: &CStr,
|
|
argv: &[S],
|
|
pre_exec: PreExec<F>,
|
|
slave_name: &str,
|
|
) -> anyhow::Result<std::convert::Infallible>
|
|
where
|
|
S: AsRef<CStr> + std::fmt::Debug,
|
|
F: FnOnce() -> anyhow::Result<()>,
|
|
{
|
|
debug!("we are going to execute: {:?} {:?}", path, argv);
|
|
init_child_common(slave_name)?;
|
|
|
|
(pre_exec.0)()?;
|
|
trace!("running exec");
|
|
match nix::unistd::execv(path, argv)? {}
|
|
}
|
|
|
|
fn init_child_common(slave_name: &str) -> anyhow::Result<()> {
|
|
/* Open slave end for pseudoterminal */
|
|
let slave_fd = nix::fcntl::open(Path::new(slave_name), OFlag::O_RDWR, Mode::empty())?;
|
|
trace!("child opened the slave_fd!");
|
|
|
|
// assign stdin, stdout, stderr to the tty
|
|
nix::unistd::dup2(slave_fd, STDIN_FILENO)?;
|
|
nix::unistd::dup2(slave_fd, STDOUT_FILENO)?;
|
|
nix::unistd::dup2(slave_fd, STDERR_FILENO)?;
|
|
|
|
nix::unistd::setsid().unwrap();
|
|
unsafe { set_controlling_terminal(slave_fd) }.unwrap();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// copied from... somewhere in std
|
|
pub fn cvt(t: i32) -> std::io::Result<i32> {
|
|
if t == -1 {
|
|
Err(std::io::Error::last_os_error())
|
|
} else {
|
|
Ok(t)
|
|
}
|
|
}
|