fish-shell/src/fds.rs

353 lines
11 KiB
Rust
Raw Normal View History

2023-04-09 20:03:20 +08:00
use crate::common::wcs2zstring;
use crate::flog::FLOG;
use crate::signal::signal_check_cancel;
#[cfg(test)]
use crate::tests::prelude::*;
2023-08-09 06:16:04 +08:00
use crate::wchar::prelude::*;
2023-04-09 20:03:20 +08:00
use crate::wutil::perror;
use errno::{errno, set_errno};
2024-02-10 11:49:07 +08:00
use libc::{c_int, EINTR, FD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL, O_NONBLOCK};
use nix::fcntl::FcntlArg;
2024-01-21 06:13:13 +08:00
use nix::{fcntl::OFlag, unistd};
2023-04-09 20:03:20 +08:00
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::prelude::*;
2023-01-15 06:56:24 +08:00
pub const PIPE_ERROR: &str = "An error occurred while setting up pipe";
2023-04-09 20:03:20 +08:00
/// The first "high fd", which is considered outside the range of valid user-specified redirections
/// (like >&5).
pub const FIRST_HIGH_FD: RawFd = 10;
/// A sentinel value indicating no timeout.
pub const NO_TIMEOUT: u64 = u64::MAX;
2023-01-15 06:56:24 +08:00
/// A helper type for managing and automatically closing a file descriptor
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
///
/// This was implemented in rust as a port of the existing C++ code but it didn't take its place
/// (yet) and there's still the original cpp implementation in `src/fds.h`, so its name is
/// disambiguated because some code uses a mix of both for interop purposes.
pub struct AutoCloseFd {
2023-01-15 06:56:24 +08:00
fd_: RawFd,
}
impl Read for AutoCloseFd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
nix::unistd::read(self.as_raw_fd(), buf).map_err(std::io::Error::from)
}
}
impl Write for AutoCloseFd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
nix::unistd::write(self.as_raw_fd(), buf).map_err(std::io::Error::from)
}
fn flush(&mut self) -> std::io::Result<()> {
// We don't buffer anything so this is a no-op.
Ok(())
}
}
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
impl AutoCloseFd {
2023-01-15 06:56:24 +08:00
// Closes the fd if not already closed.
pub fn close(&mut self) {
if self.fd_ != -1 {
_ = unistd::close(self.fd_);
self.fd_ = -1;
}
}
// Returns the fd.
pub fn fd(&self) -> RawFd {
self.fd_
}
// Returns the fd, transferring ownership to the caller.
pub fn acquire(&mut self) -> RawFd {
let temp = self.fd_;
self.fd_ = -1;
temp
}
// Resets to a new fd, taking ownership.
pub fn reset(&mut self, fd: RawFd) {
if fd == self.fd_ {
return;
}
self.close();
self.fd_ = fd;
}
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
// Returns if this has a valid fd.
pub fn is_valid(&self) -> bool {
2023-01-15 06:56:24 +08:00
self.fd_ >= 0
}
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
// Create a new AutoCloseFd instance taking ownership of the passed fd
pub fn new(fd: RawFd) -> Self {
AutoCloseFd { fd_: fd }
}
// Create a new AutoCloseFd without an open fd
pub fn empty() -> Self {
AutoCloseFd { fd_: -1 }
}
}
impl FromRawFd for AutoCloseFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
AutoCloseFd { fd_: fd }
}
}
impl AsRawFd for AutoCloseFd {
fn as_raw_fd(&self) -> RawFd {
self.fd()
2023-01-15 06:56:24 +08:00
}
}
impl AsFd for AutoCloseFd {
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.fd()) }
}
}
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
impl Default for AutoCloseFd {
fn default() -> AutoCloseFd {
AutoCloseFd { fd_: -1 }
2023-01-15 06:56:24 +08:00
}
}
Port fd_monitor (and its needed components) I needed to rename some types already ported to rust so they don't clash with their still-extant cpp counterparts. Helper ffi functions added to avoid needing to dynamically allocate an FdMonitorItem for every fd (we use dozens per basic prompt). I ported some functions from cpp to rust that are used only in the backend but without removing their existing cpp counterparts so cpp code can continue to use their version of them (`wperror` and `make_detached_pthread`). I ran into issues porting line-by-line logic because rust inverts the behavior of `std::remove_if(..)` by making it (basically) `Vec::retain_if(..)` so I replaced bools with an explict enum to make everything clearer. I'll port the cpp tests for this separately, for now they're using ffi. Porting closures was ugly. It's nothing hard, but it's very ugly as now each capturing lambda has been changed into an explicit struct that contains its parameters (that needs to be dynamically allocated), a standalone callback (member) function to replace the lambda contents, and a separate trampoline function to call it from rust over the shared C abi (not really relevant to x86_64 w/ its single calling convention but probably needed on other platforms). I don't like that `fd_monitor.rs` has its own `c_void`. I couldn't find a way to move that to `ffi.rs` but still get cxx bridge to consider it a shared POD. Every time I moved it to a different module, it would consider it to be an opaque rust type instead. I worry this means we're going to have multiple `c_void1`, `c_void2`, etc. types as we continue to port code to use function pointers. Also, rust treats raw pointers as foreign so you can't do `impl Send for * const Foo` even if `Foo` is from the same module. That necessitated a wrapper type (`void_ptr`) that implements `Send` and `Sync` so we can move stuff between threads. The code in fd_monitor_t has been split into two objects, one that is used by the caller and a separate one associated with the background thread (this is made nice and clean by rust's ownership model). Objects not needed under the lock (i.e. accessed by the background thread exclusively) were moved to the separate `BackgroundFdMonitor` type.
2023-02-18 09:21:44 +08:00
impl Drop for AutoCloseFd {
2023-01-15 06:56:24 +08:00
fn drop(&mut self) {
self.close()
}
}
/// Helper type returned from make_autoclose_pipes.
pub struct AutoClosePipes {
2023-01-15 06:56:24 +08:00
/// Read end of the pipe.
2024-01-29 02:07:06 +08:00
pub read: OwnedFd,
2023-01-15 06:56:24 +08:00
/// Write end of the pipe.
2024-01-29 02:07:06 +08:00
pub write: OwnedFd,
2023-01-15 06:56:24 +08:00
}
/// Construct a pair of connected pipes, set to close-on-exec.
/// Return None on fd exhaustion.
2024-02-10 11:49:07 +08:00
pub fn make_autoclose_pipes() -> nix::Result<AutoClosePipes> {
#[allow(unused_mut, unused_assignments)]
let mut already_cloexec = false;
#[cfg(HAVE_PIPE2)]
2024-02-10 11:49:07 +08:00
let pipes = match nix::unistd::pipe2(OFlag::O_CLOEXEC) {
Ok(pipes) => {
already_cloexec = true;
pipes
}
Err(err) => {
FLOG!(warning, PIPE_ERROR);
perror("pipe2");
2024-02-10 11:49:07 +08:00
return Err(err);
}
2024-02-10 11:49:07 +08:00
};
#[cfg(not(HAVE_PIPE2))]
2024-02-10 11:49:07 +08:00
let pipes = match nix::unistd::pipe() {
Ok(pipes) => pipes,
Err(err) => {
FLOG!(warning, PIPE_ERROR);
perror("pipe");
2024-02-10 11:49:07 +08:00
return Err(err);
}
};
2023-01-15 06:56:24 +08:00
2024-02-10 11:49:07 +08:00
let readp = unsafe { OwnedFd::from_raw_fd(pipes.0) };
let writep = unsafe { OwnedFd::from_raw_fd(pipes.1) };
// Ensure our fds are out of the user range.
2024-01-29 02:07:06 +08:00
let readp = heightenize_fd(readp, already_cloexec)?;
let writep = heightenize_fd(writep, already_cloexec)?;
2024-02-10 11:49:07 +08:00
Ok(AutoClosePipes {
read: readp,
write: writep,
})
}
/// If the given fd is in the "user range", move it to a new fd in the "high range".
/// zsh calls this movefd().
/// `input_has_cloexec` describes whether the input has CLOEXEC already set, so we can avoid
/// setting it again.
/// Return the fd, which always has CLOEXEC set; or an invalid fd on failure, in
/// which case an error will have been printed, and the input fd closed.
2024-02-10 11:49:07 +08:00
fn heightenize_fd(fd: OwnedFd, input_has_cloexec: bool) -> nix::Result<OwnedFd> {
2024-01-29 02:07:06 +08:00
let raw_fd = fd.as_raw_fd();
if raw_fd >= FIRST_HIGH_FD {
if !input_has_cloexec {
2024-01-29 02:07:06 +08:00
set_cloexec(raw_fd, true);
}
2024-02-10 11:49:07 +08:00
return Ok(fd);
}
2024-02-10 11:49:07 +08:00
// Here we are asking the kernel to give us a cloexec fd.
2024-02-10 11:49:07 +08:00
let newfd = match nix::fcntl::fcntl(raw_fd, FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD)) {
Ok(newfd) => newfd,
Err(err) => {
perror("fcntl");
return Err(err);
}
};
2024-01-29 02:07:06 +08:00
2024-02-10 11:49:07 +08:00
Ok(unsafe { OwnedFd::from_raw_fd(newfd) })
2023-01-15 06:56:24 +08:00
}
2023-04-09 20:03:20 +08:00
/// Sets CLO_EXEC on a given fd according to the value of `should_set`.
2023-12-03 23:54:52 +08:00
pub fn set_cloexec(fd: RawFd, should_set: bool /* = true */) -> c_int {
// Note we don't want to overwrite existing flags like O_NONBLOCK which may be set. So fetch the
// existing flags and modify them.
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) }
}
}
2023-04-09 20:03:20 +08:00
/// Wide character version of open() that also sets the close-on-exec flag (atomically when
/// possible).
2024-01-21 06:57:34 +08:00
pub fn wopen_cloexec(
pathname: &wstr,
2024-01-21 07:21:49 +08:00
flags: OFlag,
2024-01-21 06:57:34 +08:00
mode: nix::sys::stat::Mode,
) -> nix::Result<File> {
2023-04-09 20:03:20 +08:00
open_cloexec(wcs2zstring(pathname).as_c_str(), flags, mode)
}
/// Narrow versions of wopen_cloexec().
pub fn open_cloexec(path: &CStr, flags: OFlag, mode: nix::sys::stat::Mode) -> nix::Result<File> {
// Port note: the C++ version of this function had a fallback for platforms where
// O_CLOEXEC is not supported, using fcntl. In 2023, this is no longer needed.
let saved_errno = errno();
errno::set_errno(errno::Errno(0));
// We retry this in case of signals,
// if we get EINTR and it's not a SIGINIT, we continue.
// If it is that's our cancel signal, so we abort.
loop {
2024-01-21 06:13:13 +08:00
let ret = nix::fcntl::open(path, flags | OFlag::O_CLOEXEC, mode);
let ret = ret.map(|raw_fd| unsafe { File::from_raw_fd(raw_fd) });
2024-01-21 06:13:13 +08:00
match ret {
Ok(file) => {
2024-01-21 06:13:13 +08:00
set_errno(saved_errno);
return Ok(file);
2024-01-21 06:13:13 +08:00
}
Err(err) => {
if err != nix::Error::EINTR || signal_check_cancel() != 0 {
return ret;
}
}
}
}
2023-04-09 20:03:20 +08:00
}
/// POSIX specifies an open option O_SEARCH for opening directories for later
/// `fchdir` or `openat`, not for `readdir`. The read permission is not checked,
/// and the x permission is checked on opening. Not all platforms have this,
/// so we fall back to O_PATH or O_RDONLY according to the platform.
pub use o_search::BEST_O_SEARCH;
mod o_search {
use super::OFlag;
/// On FreeBSD or MacOS we have O_SEARCH.
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
pub const BEST_O_SEARCH: OFlag = unsafe { OFlag::from_bits_unchecked(libc::O_SEARCH) };
/// On Linux we can use O_PATH, it has nearly the same semantics. we can use the fd for openat / fchdir, with only requiring
/// x permission on the directory.
#[cfg(all(not(any(target_os = "macos", target_os = "freebsd")), any(target_os = "linux", target_os = "android")))]
pub const BEST_O_SEARCH: OFlag = unsafe { OFlag::from_bits_unchecked(libc::O_PATH) };
/// Fall back to O_RDONLY, this is what fish did before.
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "macos")))]
pub const BEST_O_SEARCH: OFlag = OFlag::O_RDONLY;
}
/// Wide character version of open_dir() that also sets the close-on-exec flag (atomically when
/// possible).
pub fn wopen_dir(pathname: &wstr, flags: OFlag) -> nix::Result<OwnedFd> {
open_dir(wcs2zstring(pathname).as_c_str(), flags)
}
/// Narrow version of wopen_dir().
pub fn open_dir(path: &CStr, flags: OFlag) -> nix::Result<OwnedFd> {
open_cloexec(path, flags | OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()).map(OwnedFd::from)
}
/// Close a file descriptor `fd`, retrying on EINTR.
2023-04-09 20:03:20 +08:00
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;
}
}
}
/// Mark an fd as nonblocking
pub fn make_fd_nonblocking(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(())
}
/// Mark an fd as blocking
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(())
}
2023-12-10 00:18:51 +08:00
#[test]
#[serial]
fn test_pipes() {
let _cleanup = test_init();
2023-12-10 00:18:51 +08:00
// Here we just test that each pipe has CLOEXEC set and is in the high range.
// Note pipe creation may fail due to fd exhaustion; don't fail in that case.
let mut pipes = vec![];
for _i in 0..10 {
2024-02-10 11:49:07 +08:00
if let Ok(pipe) = make_autoclose_pipes() {
2023-12-10 00:18:51 +08:00
pipes.push(pipe);
}
}
for pipe in pipes {
2024-01-29 02:07:06 +08:00
for fd in [&pipe.read, &pipe.write] {
let fd = fd.as_raw_fd();
2023-12-10 00:18:51 +08:00
assert!(fd >= FIRST_HIGH_FD);
let flags = unsafe { libc::fcntl(fd, F_GETFD, 0) };
assert!(flags >= 0);
assert!(flags & FD_CLOEXEC != 0);
}
}
}