use std::io;
use std::os::fd::RawFd;
pub const UNATTACHED: RawFd = -1;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Stdio {
pub terminal: RawFd,
pub stdin: RawFd,
pub stdout: RawFd,
pub stderr: RawFd,
}
impl Stdio {
pub fn terminal(terminal: RawFd, stdout: RawFd) -> Self {
Self {
terminal,
stdin: UNATTACHED,
stdout,
stderr: UNATTACHED,
}
}
pub fn inherit(interactive: bool) -> Self {
Self {
terminal: UNATTACHED,
stdin: if interactive { libc::STDIN_FILENO } else { UNATTACHED },
stdout: libc::STDOUT_FILENO,
stderr: libc::STDERR_FILENO,
}
}
pub fn nothing() -> Self {
Self {
terminal: UNATTACHED,
stdin: UNATTACHED,
stdout: UNATTACHED,
stderr: UNATTACHED,
}
}
pub fn try_clone(&self) -> io::Result<Self> {
Ok(Self {
terminal: lend_attached(self.terminal)?,
stdin: lend_attached(self.stdin)?,
stdout: lend_attached(self.stdout)?,
stderr: lend_attached(self.stderr)?,
})
}
pub fn has_terminal(&self) -> bool {
self.terminal != UNATTACHED
}
}
fn lend_attached(descriptor: RawFd) -> io::Result<RawFd> {
if descriptor == UNATTACHED {
return Ok(UNATTACHED);
}
lend(descriptor)
}
pub fn lend(descriptor: RawFd) -> io::Result<RawFd> {
let lent = unsafe { libc::dup(descriptor) };
if lent < 0 {
return Err(io::Error::last_os_error());
}
Ok(lent)
}
pub fn is_tty(descriptor: RawFd) -> bool {
unsafe { libc::isatty(descriptor) == 1 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attaches_a_terminal_and_leaves_the_plain_streams_alone() {
let stdio = Stdio::terminal(7, 9);
assert!(stdio.has_terminal());
assert_eq!(stdio.stdout, 9);
assert_eq!(stdio.stdin, UNATTACHED);
assert_eq!(stdio.stderr, UNATTACHED);
}
#[test]
fn leaves_out_stdin_for_a_process_that_reads_none() {
assert_eq!(Stdio::inherit(false).stdin, UNATTACHED);
assert_eq!(Stdio::inherit(true).stdin, libc::STDIN_FILENO);
}
#[test]
fn duplicates_only_the_streams_the_caller_attached() {
let cloned = Stdio::inherit(false)
.try_clone()
.expect("the process's own streams should duplicate");
assert_eq!(cloned.terminal, UNATTACHED);
assert_eq!(cloned.stdin, UNATTACHED);
assert_ne!(cloned.stdout, libc::STDOUT_FILENO, "stdout should be a duplicate");
for descriptor in [cloned.stdout, cloned.stderr] {
unsafe { libc::close(descriptor) };
}
}
#[test]
fn nothing_is_attached_to_nothing() {
assert_eq!(
Stdio::nothing(),
Stdio {
terminal: UNATTACHED,
stdin: UNATTACHED,
stdout: UNATTACHED,
stderr: UNATTACHED,
}
);
}
}