use std::fs::File;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::Command;
const DIAG_FD: RawFd = 3;
pub struct DiagPipe {
pub read: File,
pub write: OwnedFd,
}
pub fn prepare_diag_pipe(cmd: &mut Command) -> std::io::Result<DiagPipe> {
let (read, write) = make_pipe()?;
let write_raw = write.as_raw_fd();
unsafe {
cmd.pre_exec(move || dup_fd(write_raw, DIAG_FD));
}
cmd.env("BALLS_DIAG_FD", DIAG_FD.to_string());
Ok(DiagPipe { read, write })
}
fn make_pipe() -> std::io::Result<(File, OwnedFd)> {
#[cfg(target_os = "linux")]
let (fd_r, fd_w) = pipe2_cloexec(libc::O_CLOEXEC)?;
#[cfg(not(target_os = "linux"))]
let (fd_r, fd_w) = {
let (r, w) = pipe_raw()?;
set_cloexec(r)?;
set_cloexec(w)?;
(r, w)
};
Ok(unsafe { (File::from_raw_fd(fd_r), OwnedFd::from_raw_fd(fd_w)) })
}
#[cfg(target_os = "linux")]
fn pipe2_cloexec(flags: libc::c_int) -> std::io::Result<(RawFd, RawFd)> {
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe2(fds.as_mut_ptr(), flags) } != 0 {
return Err(std::io::Error::last_os_error());
}
Ok((fds[0], fds[1]))
}
#[cfg(not(target_os = "linux"))]
fn pipe_raw() -> std::io::Result<(RawFd, RawFd)> {
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return Err(std::io::Error::last_os_error());
}
Ok((fds[0], fds[1]))
}
#[cfg(not(target_os = "linux"))]
fn set_cloexec(fd: RawFd) -> std::io::Result<()> {
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
fn dup_fd(from: RawFd, to: RawFd) -> std::io::Result<()> {
if unsafe { libc::dup2(from, to) } == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
#[test]
fn make_pipe_success_returns_usable_endpoints() {
let (mut r, w) = make_pipe().expect("real pipe");
let w_raw = w.as_raw_fd();
let mut w_file = unsafe { File::from_raw_fd(w_raw) };
std::mem::forget(w);
w_file.write_all(b"ping").unwrap();
drop(w_file);
let mut buf = Vec::new();
r.read_to_end(&mut buf).unwrap();
assert_eq!(buf, b"ping");
}
#[cfg(target_os = "linux")]
#[test]
fn pipe2_cloexec_error_branch_triggers_on_bad_flags() {
let err = pipe2_cloexec(-1).expect_err("bad flags must error");
assert!(err.raw_os_error().is_some());
}
#[test]
fn dup_fd_success_duplicates_descriptor() {
const TARGET: RawFd = 900;
dup_fd(0, TARGET).expect("dup2 to unused fd");
unsafe {
libc::close(TARGET);
}
}
#[test]
fn dup_fd_error_branch_triggers_on_bad_source() {
let err = dup_fd(-1, 901).expect_err("bad source fd must error");
assert!(err.raw_os_error().is_some());
}
}