use crate::{flog::flog, prelude::*, signal::signal_check_cancel, wutil::perror_nix};
use cfg_if::cfg_if;
use fish_util::perror;
use fish_widestring::wcs2zstring;
use libc::{c_int, EINTR, FD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL, O_NONBLOCK};
use nix::fcntl::{FcntlArg, OFlag};
use std::{
ffi::CStr,
fs::File,
io,
mem::ManuallyDrop,
ops::{Deref, DerefMut},
os::{
fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
unix::prelude::*,
},
};
localizable_consts!(
pub PIPE_ERROR
"An error occurred while setting up pipe"
);
pub const FIRST_HIGH_FD: RawFd = 10;
pub const NO_TIMEOUT: u64 = u64::MAX;
pub struct AutoClosePipes {
pub read: OwnedFd,
pub write: OwnedFd,
}
pub fn make_autoclose_pipes() -> nix::Result<AutoClosePipes> {
#[allow(unused_mut, unused_assignments)]
let mut already_cloexec = false;
cfg_if!(
if #[cfg(have_pipe2)] {
let pipes = match nix::unistd::pipe2(OFlag::O_CLOEXEC) {
Ok(pipes) => {
already_cloexec = true;
pipes
}
Err(err) => {
flog!(warning, PIPE_ERROR.localize());
perror("pipe2");
return Err(err);
}
};
} else {
let pipes = match nix::unistd::pipe() {
Ok(pipes) => pipes,
Err(err) => {
flog!(warning, PIPE_ERROR.localize());
perror("pipe");
return Err(err);
}
};
}
);
let readp = pipes.0;
let writep = pipes.1;
let readp = heightenize_fd(readp, already_cloexec)?;
let writep = heightenize_fd(writep, already_cloexec)?;
Ok(AutoClosePipes {
read: readp,
write: writep,
})
}
fn heightenize_fd(fd: OwnedFd, input_has_cloexec: bool) -> nix::Result<OwnedFd> {
let raw_fd = fd.as_raw_fd();
if raw_fd >= FIRST_HIGH_FD {
if !input_has_cloexec {
set_cloexec(raw_fd, true);
}
return Ok(fd);
}
let newfd =
nix::fcntl::fcntl(&fd, FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD)).inspect_err(|&err| {
perror_nix("fcntl", err);
})?;
Ok(unsafe { OwnedFd::from_raw_fd(newfd) })
}
pub fn set_cloexec(fd: RawFd, should_set: bool ) -> c_int {
let flags = unsafe { libc::fcntl(fd, F_GETFD, 0) };
if flags < 0 {
return -1;
}
let mut new_flags = flags;
if should_set {
new_flags |= FD_CLOEXEC;
} else {
new_flags &= !FD_CLOEXEC;
}
if flags == new_flags {
0
} else {
unsafe { libc::fcntl(fd, F_SETFD, new_flags) }
}
}
pub fn wopen_cloexec(
pathname: &wstr,
flags: OFlag,
mode: nix::sys::stat::Mode,
) -> nix::Result<File> {
open_cloexec(wcs2zstring(pathname).as_c_str(), flags, mode)
}
pub fn open_cloexec(path: &CStr, flags: OFlag, mode: nix::sys::stat::Mode) -> nix::Result<File> {
loop {
let ret = nix::fcntl::open(path, flags | OFlag::O_CLOEXEC, mode);
let ret = ret.map(File::from);
match ret {
Ok(file) => {
return Ok(file);
}
Err(err) => {
if err != nix::Error::EINTR || signal_check_cancel() != 0 {
return ret;
}
}
}
}
}
pub use o_search::BEST_O_SEARCH;
mod o_search {
use super::OFlag;
#[cfg(apple)]
pub const BEST_O_SEARCH: OFlag = OFlag::from_bits_truncate(libc::O_DIRECTORY | 0x40000000);
#[cfg(target_os = "freebsd")]
pub const BEST_O_SEARCH: OFlag = OFlag::from_bits_truncate(0x00040000);
#[cfg(any(target_os = "linux", target_os = "android"))]
pub const BEST_O_SEARCH: OFlag = OFlag::from_bits_truncate(libc::O_PATH);
#[cfg(not(any(
apple,
target_os = "linux",
target_os = "android",
target_os = "freebsd",
)))]
pub const BEST_O_SEARCH: OFlag = OFlag::O_RDONLY;
}
pub fn wopen_dir(pathname: &wstr, flags: OFlag) -> nix::Result<OwnedFd> {
open_dir(wcs2zstring(pathname).as_c_str(), flags)
}
pub fn open_dir(path: &CStr, flags: OFlag) -> nix::Result<OwnedFd> {
let mode = nix::sys::stat::Mode::empty();
open_cloexec(path, flags | OFlag::O_DIRECTORY, mode).map(OwnedFd::from)
}
pub fn exec_close(fd: RawFd) {
assert!(fd >= 0, "Invalid fd");
while unsafe { libc::close(fd) } == -1 {
if errno::errno().0 != EINTR {
perror("close");
break;
}
}
}
pub fn make_fd_nonblocking(fd: RawFd) -> std::io::Result<()> {
let flags = unsafe { libc::fcntl(fd, F_GETFL, 0) };
let nonblocking = (flags & O_NONBLOCK) == O_NONBLOCK;
if !nonblocking {
match unsafe { libc::fcntl(fd, F_SETFL, flags | O_NONBLOCK) } {
-1 => return Err(io::Error::last_os_error()),
_ => return Ok(()),
};
}
Ok(())
}
pub fn make_fd_blocking(fd: RawFd) -> Result<(), io::Error> {
let flags = unsafe { libc::fcntl(fd, F_GETFL, 0) };
let nonblocking = (flags & O_NONBLOCK) == O_NONBLOCK;
if nonblocking {
match unsafe { libc::fcntl(fd, F_SETFL, flags & !O_NONBLOCK) } {
-1 => return Err(io::Error::last_os_error()),
_ => return Ok(()),
};
}
Ok(())
}
pub struct BorrowedFdFile(ManuallyDrop<File>);
impl Deref for BorrowedFdFile {
type Target = File;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for BorrowedFdFile {
#[inline]
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 {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for BorrowedFdFile {
#[inline]
fn into_raw_fd(self) -> RawFd {
ManuallyDrop::into_inner(self.0).into_raw_fd()
}
}
impl BorrowedFdFile {
pub fn stdin() -> Self {
unsafe { Self::from_raw_fd(libc::STDIN_FILENO) }
}
}
impl Clone for BorrowedFdFile {
fn clone(&self) -> Self {
unsafe { Self::from_raw_fd(self.as_raw_fd()) }
}
}
impl std::io::Read for BorrowedFdFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.deref_mut().read(buf)
}
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.deref_mut().read_vectored(bufs)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
self.deref_mut().read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
self.deref_mut().read_to_string(buf)
}
}
#[cfg(test)]
mod tests {
use super::{make_autoclose_pipes, BorrowedFdFile, FIRST_HIGH_FD};
use crate::tests::prelude::*;
use libc::{FD_CLOEXEC, F_GETFD};
use std::os::fd::{AsRawFd as _, FromRawFd as _};
#[test]
#[serial]
fn test_pipes() {
let _cleanup = test_init();
let mut pipes = vec![];
for _i in 0..10 {
if let Ok(pipe) = make_autoclose_pipes() {
pipes.push(pipe);
}
}
for pipe in pipes {
for fd in [&pipe.read, &pipe.write] {
let fd = fd.as_raw_fd();
assert!(fd >= FIRST_HIGH_FD);
let flags = unsafe { libc::fcntl(fd, F_GETFD, 0) };
assert!(flags >= 0);
assert_ne!(flags & FD_CLOEXEC, 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) };
#[allow(clippy::drop_non_drop)]
drop(borrowed);
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
assert!(flags >= 0);
drop(file);
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
assert!(flags < 0);
}
}