use libc::{grantpt, posix_openpt, unlockpt, O_CLOEXEC, O_NOCTTY, O_RDWR};
use std::ffi::{c_int, CStr, CString, OsStr};
use std::fs::File;
use std::io;
use std::os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, FromRawFd as _, OwnedFd};
use std::os::unix::ffi::OsStrExt;
pub(crate) fn pty_pair() -> io::Result<PtyPair> {
let controlling = openpty()?;
let user = File::open(OsStr::from_bytes(
ptsname_r(controlling.as_fd())?.as_bytes(),
))?
.into();
Ok(PtyPair {
_controlling: controlling,
user,
})
}
#[derive(Debug)]
pub(crate) struct PtyPair {
pub(crate) _controlling: OwnedFd,
pub(crate) user: OwnedFd,
}
fn openpty() -> io::Result<OwnedFd> {
let fd = to_io_result(unsafe { posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC) })?;
to_io_result(unsafe { grantpt(fd) })?;
to_io_result(unsafe { unlockpt(fd) })?;
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
#[cfg(not(target_os = "macos"))]
fn ptsname_r(fd: BorrowedFd) -> io::Result<CString> {
let mut buf = Vec::with_capacity(64);
loop {
let code = unsafe { libc::ptsname_r(fd.as_raw_fd(), buf.as_mut_ptr(), buf.capacity()) };
match code {
0 => return Ok(unsafe { CStr::from_ptr(buf.as_ptr()).to_owned() }),
libc::ERANGE => buf.reserve(64),
code => return Err(io::Error::from_raw_os_error(code)),
}
}
}
#[cfg(target_os = "macos")]
fn ptsname_r(fd: BorrowedFd) -> io::Result<CString> {
use libc::{c_ulong, ioctl, TIOCPTYGNAME};
let buf: [i8; 128] = [0; 128];
unsafe {
match ioctl(fd.as_raw_fd(), TIOCPTYGNAME as c_ulong, &buf) {
0 => {
let res = CStr::from_ptr(buf.as_ptr()).to_owned();
Ok(res)
}
_ => Err(io::Error::last_os_error()),
}
}
}
fn to_io_result(value: c_int) -> io::Result<c_int> {
if value == -1 {
Err(io::Error::last_os_error())
} else {
Ok(value)
}
}