use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
pub const FIRST_HIGH_FD: RawFd = 10;
pub struct AutoClosePipes {
pub read: OwnedFd,
pub write: OwnedFd,
}
pub fn make_autoclose_pipes() -> io::Result<AutoClosePipes> {
let (read_fd, write_fd) =
nix::unistd::pipe().map_err(|e| io::Error::from_raw_os_error(e as i32))?;
let read_fd = heightenize_fd(read_fd)?;
let write_fd = heightenize_fd(write_fd)?;
Ok(AutoClosePipes {
read: read_fd,
write: write_fd,
})
}
fn heightenize_fd(fd: OwnedFd) -> io::Result<OwnedFd> {
let raw_fd = fd.as_raw_fd();
if raw_fd >= FIRST_HIGH_FD {
set_cloexec(raw_fd, true)?;
return Ok(fd);
}
let new_fd = nix::fcntl::fcntl(raw_fd, nix::fcntl::FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD))
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
Ok(unsafe { OwnedFd::from_raw_fd(new_fd) })
}
pub fn set_cloexec(fd: RawFd, should_set: bool) -> io::Result<()> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
let new_flags = if should_set {
flags | libc::FD_CLOEXEC
} else {
flags & !libc::FD_CLOEXEC
};
if flags != new_flags {
let result = unsafe { libc::fcntl(fd, libc::F_SETFD, new_flags) };
if result < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn make_fd_nonblocking(fd: RawFd) -> io::Result<()> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
if (flags & libc::O_NONBLOCK) == 0 {
let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
if result < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn make_fd_blocking(fd: RawFd) -> io::Result<()> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
if (flags & libc::O_NONBLOCK) != 0 {
let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK) };
if result < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn close_fd(fd: RawFd) {
if fd < 0 {
return;
}
loop {
let result = unsafe { libc::close(fd) };
if result == 0 {
break;
}
let err = io::Error::last_os_error();
if err.raw_os_error() != Some(libc::EINTR) {
break;
}
}
}
pub fn dup_fd(fd: RawFd) -> io::Result<RawFd> {
let new_fd = unsafe { libc::dup(fd) };
if new_fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(new_fd)
}
}
pub fn dup2_fd(src: RawFd, dst: RawFd) -> io::Result<()> {
if src == dst {
return Ok(());
}
let result = unsafe { libc::dup2(src, dst) };
if result < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub struct BorrowedFdFile(ManuallyDrop<File>);
impl Deref for BorrowedFdFile {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for BorrowedFdFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl FromRawFd for BorrowedFdFile {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self(ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }))
}
}
impl AsRawFd for BorrowedFdFile {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for BorrowedFdFile {
fn into_raw_fd(self) -> RawFd {
ManuallyDrop::into_inner(self.0).into_raw_fd()
}
}
impl Clone for BorrowedFdFile {
fn clone(&self) -> Self {
unsafe { Self::from_raw_fd(self.as_raw_fd()) }
}
}
impl BorrowedFdFile {
pub fn stdin() -> Self {
unsafe { Self::from_raw_fd(libc::STDIN_FILENO) }
}
pub fn stdout() -> Self {
unsafe { Self::from_raw_fd(libc::STDOUT_FILENO) }
}
pub fn stderr() -> Self {
unsafe { Self::from_raw_fd(libc::STDERR_FILENO) }
}
}
impl io::Read for BorrowedFdFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.deref_mut().read(buf)
}
}
impl io::Write for BorrowedFdFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.deref_mut().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.deref_mut().flush()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FdType {
Unused = 0,
External = 1,
Internal = 2,
Module = 3,
Flock = 4,
FlockExec = 5,
Proc = 6,
}
pub fn movefd(fd: RawFd) -> RawFd {
if fd < 0 || fd >= FIRST_HIGH_FD {
return fd;
}
unsafe {
let new_fd = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, FIRST_HIGH_FD);
if new_fd != -1 {
libc::close(fd);
return new_fd;
}
}
fd
}
pub fn redup(x: RawFd, y: RawFd) -> RawFd {
if x < 0 {
unsafe { libc::close(y) };
return y;
}
if x == y {
return y;
}
let result = unsafe { libc::dup2(x, y) };
if result == -1 {
return -1;
}
unsafe { libc::close(x) };
y
}
pub fn zclose(fd: RawFd) -> i32 {
if fd >= 0 {
unsafe { libc::close(fd) }
} else {
-1
}
}
pub fn zdup(fd: RawFd) -> RawFd {
if fd < 0 {
return -1;
}
unsafe { libc::dup(fd) }
}
pub fn fd_is_open(fd: RawFd) -> bool {
if fd < 0 {
return false;
}
unsafe { libc::fcntl(fd, libc::F_GETFD, 0) >= 0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_make_autoclose_pipes() {
let pipes = make_autoclose_pipes().expect("Failed to create pipes");
assert!(pipes.read.as_raw_fd() >= FIRST_HIGH_FD);
assert!(pipes.write.as_raw_fd() >= FIRST_HIGH_FD);
let read_flags = unsafe { libc::fcntl(pipes.read.as_raw_fd(), libc::F_GETFD, 0) };
let write_flags = unsafe { libc::fcntl(pipes.write.as_raw_fd(), libc::F_GETFD, 0) };
assert!(read_flags >= 0);
assert!(write_flags >= 0);
assert_ne!(read_flags & libc::FD_CLOEXEC, 0);
assert_ne!(write_flags & libc::FD_CLOEXEC, 0);
}
#[test]
fn test_set_cloexec() {
let file = std::fs::File::open("/dev/null").unwrap();
let fd = file.as_raw_fd();
set_cloexec(fd, true).unwrap();
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
assert_ne!(flags & libc::FD_CLOEXEC, 0);
set_cloexec(fd, false).unwrap();
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
assert_eq!(flags & libc::FD_CLOEXEC, 0);
}
#[test]
fn test_nonblocking() {
let file = std::fs::File::open("/dev/null").unwrap();
let fd = file.as_raw_fd();
make_fd_nonblocking(fd).unwrap();
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
assert_ne!(flags & libc::O_NONBLOCK, 0);
make_fd_blocking(fd).unwrap();
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
assert_eq!(flags & libc::O_NONBLOCK, 0);
}
#[test]
fn test_borrowed_fd_file_does_not_close() {
let file = std::fs::File::open("/dev/null").unwrap();
let fd = file.as_raw_fd();
let borrowed = unsafe { BorrowedFdFile::from_raw_fd(fd) };
drop(borrowed);
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
assert!(
flags >= 0,
"fd should still be valid after dropping BorrowedFdFile"
);
drop(file);
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
assert!(
flags < 0,
"fd should be invalid after dropping original File"
);
}
}