pub mod ansi;
pub mod entropy;
pub mod redact;
#[cfg(unix)]
pub mod resizer;
#[cfg(unix)]
pub mod sigwinch;
pub use ansi::AnsiRedactor;
pub use entropy::{calculate_entropy, is_entropy_masked, mask_high_entropy_tokens};
pub use redact::{RedactionStyle, StreamingRedactor};
#[cfg(all(unix, target_os = "macos"))]
use std::ffi::CStr;
#[cfg(unix)]
use std::ffi::CString;
#[cfg(unix)]
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
#[cfg(unix)]
use crate::error::{VettoError, VettoResult};
#[cfg(unix)]
pub struct Pty {
pub master: OwnedFd,
pub slave: OwnedFd,
}
#[cfg(unix)]
impl Pty {
pub fn open(rows: u16, cols: u16) -> VettoResult<Self> {
let master = unsafe { libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY | libc::O_CLOEXEC) };
if master < 0 {
return Err(VettoError::Pty(format!(
"posix_openpt: {}",
std::io::Error::last_os_error()
)));
}
let fail = |stage: &str, fd: RawFd| {
unsafe { libc::close(fd) };
VettoError::Pty(format!("{stage}: {}", std::io::Error::last_os_error()))
};
if unsafe { libc::grantpt(master) } != 0 {
return Err(fail("grantpt", master));
}
if unsafe { libc::unlockpt(master) } != 0 {
return Err(fail("unlockpt", master));
}
let name: CString = {
#[cfg(target_os = "linux")]
{
let mut name_buf = [0u8; 128];
if unsafe { libc::ptsname_r(master, name_buf.as_mut_ptr().cast(), name_buf.len()) }
!= 0
{
return Err(fail("ptsname_r", master));
}
let Some(len) = name_buf.iter().position(|&b| b == 0) else {
return Err(fail("ptsname_r: unterminated result", master));
};
let Ok(name) = CString::new(&name_buf[..len]) else {
return Err(fail("ptsname_r: invalid result", master));
};
name
}
#[cfg(target_os = "macos")]
{
let name = unsafe { libc::ptsname(master) };
if name.is_null() {
return Err(fail("ptsname", master));
}
let name = unsafe { CStr::from_ptr(name) };
let Ok(name) = CString::new(name.to_bytes()) else {
return Err(fail("ptsname: invalid result", master));
};
name
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
return Err(fail(
"pty slave lookup unsupported on this platform",
master,
));
}
};
let slave = unsafe {
libc::open(
name.as_ptr(),
libc::O_RDWR | libc::O_NOCTTY | libc::O_CLOEXEC,
)
};
if slave < 0 {
return Err(fail("open slave", master));
}
set_winsize(master, rows, cols);
Ok(Pty {
master: unsafe { OwnedFd::from_raw_fd(master) },
slave: unsafe { OwnedFd::from_raw_fd(slave) },
})
}
}
#[cfg(unix)]
pub fn set_winsize(fd: RawFd, rows: u16, cols: u16) {
let ws = libc::winsize {
ws_row: rows,
ws_col: cols,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { libc::ioctl(fd, libc::TIOCSWINSZ, &ws) };
}
#[cfg(unix)]
pub fn set_nonblocking(fd: RawFd, on: bool) -> std::io::Result<()> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if flags < 0 {
return Err(std::io::Error::last_os_error());
}
let new_flags = if on {
flags | libc::O_NONBLOCK
} else {
flags & !libc::O_NONBLOCK
};
if unsafe { libc::fcntl(fd, libc::F_SETFL, new_flags) } < 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[cfg(unix)]
pub fn read_ready(fd: RawFd, buf: &mut [u8]) -> usize {
let n = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) };
if n > 0 {
n as usize
} else {
0
}
}
#[cfg(unix)]
pub fn passthrough_once(input_fd: RawFd, output_fd: RawFd, buf: &mut [u8]) -> usize {
let n = read_ready(input_fd, buf);
if n > 0 {
write_all_fd(output_fd, &buf[..n]);
}
n
}
#[cfg(unix)]
pub fn passthrough_redacted(
input_fd: RawFd,
output_fd: RawFd,
buf: &mut [u8],
redactor: &mut AnsiRedactor,
) -> usize {
let n = read_ready(input_fd, buf);
if n > 0 {
let redacted = redactor.redact_chunk(&buf[..n]);
write_all_fd(output_fd, &redacted);
}
n
}
#[cfg(unix)]
pub fn write_all_fd(fd: RawFd, mut buf: &[u8]) {
while !buf.is_empty() {
let n = unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) };
if n > 0 {
buf = &buf[n as usize..];
} else if n < 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
continue;
} else {
return;
}
}
}