use std::fs as path_std_fs;
use std::process::{Command, Stdio};
use rustix::fd::OwnedFd;
use rustix::termios::{OptionalActions, OutputModes, Winsize, tcgetattr, tcsetattr};
#[cfg(any(target_os = "android", target_os = "linux"))]
use rustix_openpty::rustix;
#[cfg(target_os = "macos")]
use rustix_v1 as rustix;
struct Pty {
controller: OwnedFd,
user: OwnedFd,
}
pub(crate) struct PtyStdio {
pub(crate) stdout_controller: std::fs::File,
pub(crate) stderr_controller: std::fs::File,
pub(crate) output_users: [std::fs::File; 2],
}
impl PtyStdio {
pub(crate) fn attach(command: &mut Command) -> std::io::Result<Self> {
let stdout = open_pty()?;
let stderr = open_pty()?;
let stdout_user_guard = stdout.user.try_clone()?;
let stderr_user_guard = stderr.user.try_clone()?;
command
.stdin(Stdio::null())
.stdout(Stdio::from(stdout.user))
.stderr(Stdio::from(stderr.user));
Ok(Self {
stdout_controller: path_std_fs::File::from(stdout.controller),
stderr_controller: path_std_fs::File::from(stderr.controller),
output_users: [
path_std_fs::File::from(stdout_user_guard),
path_std_fs::File::from(stderr_user_guard),
],
})
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn open_pty() -> std::io::Result<Pty> {
let pty = rustix_openpty::openpty(
None,
Some(&Winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
}),
)?;
configure_user(&pty.user)?;
Ok(Pty {
controller: pty.controller,
user: pty.user,
})
}
#[cfg(target_os = "macos")]
fn open_pty() -> std::io::Result<Pty> {
use rustix::fs::{Mode, OFlags, open};
use rustix::pty::{grantpt, ptsname, unlockpt};
use rustix::termios::tcsetwinsize;
let flags = OFlags::RDWR | OFlags::NOCTTY | OFlags::CLOEXEC;
let controller = open("/dev/ptmx", flags, Mode::empty())?;
grantpt(&controller)?;
unlockpt(&controller)?;
let user_name = ptsname(&controller, Vec::new())?;
let user = open(user_name.as_c_str(), flags, Mode::empty())?;
tcsetwinsize(
&user,
Winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
},
)?;
configure_user(&user)?;
Ok(Pty { controller, user })
}
fn configure_user(user: &OwnedFd) -> std::io::Result<()> {
let mut attributes = tcgetattr(user)?;
attributes.output_modes.remove(OutputModes::OPOST);
tcsetattr(user, OptionalActions::Now, &attributes)?;
Ok(())
}
#[cfg(test)]
mod tests;