mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 05:53:59 +00:00
Port make_autoclose_pipes, fd_event_signaller_t
This allows to get rid of the C++ autoclose_fd_t.
This commit is contained in:
parent
f2cd916f65
commit
f3dd8d306f
19 changed files with 278 additions and 496 deletions
|
@ -112,7 +112,6 @@ set(FISH_SRCS
|
||||||
src/event.cpp
|
src/event.cpp
|
||||||
src/expand.cpp
|
src/expand.cpp
|
||||||
src/fallback.cpp
|
src/fallback.cpp
|
||||||
src/fds.cpp
|
|
||||||
src/fish_version.cpp
|
src/fish_version.cpp
|
||||||
src/flog.cpp
|
src/flog.cpp
|
||||||
src/highlight.cpp
|
src/highlight.cpp
|
||||||
|
|
|
@ -152,8 +152,6 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
|
||||||
check_include_files("sys/types.h;sys/sysctl.h" HAVE_SYS_SYSCTL_H)
|
check_include_files("sys/types.h;sys/sysctl.h" HAVE_SYS_SYSCTL_H)
|
||||||
SET(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}")
|
SET(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}")
|
||||||
|
|
||||||
check_cxx_symbol_exists(eventfd sys/eventfd.h HAVE_EVENTFD)
|
|
||||||
check_cxx_symbol_exists(pipe2 unistd.h HAVE_PIPE2)
|
|
||||||
check_cxx_symbol_exists(wcscasecmp wchar.h HAVE_WCSCASECMP)
|
check_cxx_symbol_exists(wcscasecmp wchar.h HAVE_WCSCASECMP)
|
||||||
check_cxx_symbol_exists(wcsncasecmp wchar.h HAVE_WCSNCASECMP)
|
check_cxx_symbol_exists(wcsncasecmp wchar.h HAVE_WCSNCASECMP)
|
||||||
|
|
||||||
|
@ -293,4 +291,3 @@ IF (APPLE)
|
||||||
SET(HAVE_BROKEN_MBRTOWC_UTF8 1)
|
SET(HAVE_BROKEN_MBRTOWC_UTF8 1)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
|
|
|
@ -43,12 +43,6 @@
|
||||||
/* Define to 1 if you have the <ncurses/term.h> header file. */
|
/* Define to 1 if you have the <ncurses/term.h> header file. */
|
||||||
#cmakedefine HAVE_NCURSES_TERM_H 1
|
#cmakedefine HAVE_NCURSES_TERM_H 1
|
||||||
|
|
||||||
/* Define to 1 if you have the 'eventfd' function. */
|
|
||||||
#cmakedefine HAVE_EVENTFD 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the 'pipe2' function. */
|
|
||||||
#cmakedefine HAVE_PIPE2 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <siginfo.h> header file. */
|
/* Define to 1 if you have the <siginfo.h> header file. */
|
||||||
#cmakedefine HAVE_SIGINFO_H 1
|
#cmakedefine HAVE_SIGINFO_H 1
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,12 @@ fn main() {
|
||||||
if compiles("fish-rust/src/cfg/w_exitcode.cpp") {
|
if compiles("fish-rust/src/cfg/w_exitcode.cpp") {
|
||||||
println!("cargo:rustc-cfg=HAVE_WAITSTATUS_SIGNAL_RET");
|
println!("cargo:rustc-cfg=HAVE_WAITSTATUS_SIGNAL_RET");
|
||||||
}
|
}
|
||||||
|
if compiles("fish-rust/src/cfg/eventfd.c") {
|
||||||
|
println!("cargo:rustc-cfg=HAVE_EVENTFD");
|
||||||
|
}
|
||||||
|
if compiles("fish-rust/src/cfg/pipe2.c") {
|
||||||
|
println!("cargo:rustc-cfg=HAVE_PIPE2");
|
||||||
|
}
|
||||||
if compiles("fish-rust/src/cfg/spawn.c") {
|
if compiles("fish-rust/src/cfg/spawn.c") {
|
||||||
println!("cargo:rustc-cfg=FISH_USE_POSIX_SPAWN");
|
println!("cargo:rustc-cfg=FISH_USE_POSIX_SPAWN");
|
||||||
}
|
}
|
||||||
|
|
1
fish-rust/src/cfg/eventfd.c
Normal file
1
fish-rust/src/cfg/eventfd.c
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#include <sys/eventfd.h>
|
2
fish-rust/src/cfg/pipe2.c
Normal file
2
fish-rust/src/cfg/pipe2.c
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#include <unistd.h>
|
||||||
|
int ok = pipe2(0, 0);
|
|
@ -1,17 +1,23 @@
|
||||||
use std::os::unix::prelude::*;
|
use std::os::unix::prelude::*;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
pub use self::fd_monitor_ffi::ItemWakeReason;
|
pub use self::fd_monitor_ffi::ItemWakeReason;
|
||||||
pub use self::fd_monitor_ffi::{new_fd_event_signaller, FdEventSignaller};
|
use crate::common::exit_without_destructors;
|
||||||
use crate::fd_readable_set::FdReadableSet;
|
use crate::fd_readable_set::FdReadableSet;
|
||||||
use crate::fds::AutoCloseFd;
|
use crate::fds::AutoCloseFd;
|
||||||
use crate::ffi::void_ptr;
|
use crate::ffi::void_ptr;
|
||||||
use crate::flog::FLOG;
|
use crate::flog::FLOG;
|
||||||
use crate::threads::assert_is_background_thread;
|
use crate::threads::assert_is_background_thread;
|
||||||
use crate::wutil::perror;
|
use crate::wutil::perror;
|
||||||
use cxx::SharedPtr;
|
use errno::errno;
|
||||||
|
use libc::{self, c_void, EAGAIN, EINTR, EWOULDBLOCK};
|
||||||
|
|
||||||
|
#[cfg(not(HAVE_EVENTFD))]
|
||||||
|
use crate::fds::{make_autoclose_pipes, make_fd_nonblocking};
|
||||||
|
#[cfg(HAVE_EVENTFD)]
|
||||||
|
use libc::{EFD_CLOEXEC, EFD_NONBLOCK};
|
||||||
|
|
||||||
#[cxx::bridge]
|
#[cxx::bridge]
|
||||||
mod fd_monitor_ffi {
|
mod fd_monitor_ffi {
|
||||||
|
@ -28,22 +34,6 @@ mod fd_monitor_ffi {
|
||||||
Poke,
|
Poke,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C++" {
|
|
||||||
include!("fds.h");
|
|
||||||
|
|
||||||
/// An event signaller implemented using a file descriptor, so it can plug into
|
|
||||||
/// [`select()`](libc::select).
|
|
||||||
///
|
|
||||||
/// This is like a binary semaphore. A call to [`post()`](FdEventSignaller::post) will
|
|
||||||
/// signal an event, making the fd readable. Multiple calls to `post()` may be coalesced.
|
|
||||||
/// On Linux this uses [`eventfd()`](libc::eventfd), on other systems this uses a pipe.
|
|
||||||
/// [`try_consume()`](FdEventSignaller::try_consume) may be used to consume the event.
|
|
||||||
/// Importantly this is async signal safe. Of course it is `CLO_EXEC` as well.
|
|
||||||
#[rust_name = "FdEventSignaller"]
|
|
||||||
type fd_event_signaller_t = crate::ffi::fd_event_signaller_t;
|
|
||||||
#[rust_name = "new_fd_event_signaller"]
|
|
||||||
fn ffi_new_fd_event_signaller_t() -> SharedPtr<FdEventSignaller>;
|
|
||||||
}
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
#[cxx_name = "fd_monitor_item_id_t"]
|
#[cxx_name = "fd_monitor_item_id_t"]
|
||||||
type FdMonitorItemId;
|
type FdMonitorItemId;
|
||||||
|
@ -86,9 +76,151 @@ mod fd_monitor_ffi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove once we're no longer using the FFI variant of FdEventSignaller
|
/// An event signaller implemented using a file descriptor, so it can plug into
|
||||||
unsafe impl Sync for FdEventSignaller {}
|
/// [`select()`](libc::select).
|
||||||
unsafe impl Send for FdEventSignaller {}
|
///
|
||||||
|
/// This is like a binary semaphore. A call to [`post()`](FdEventSignaller::post) will
|
||||||
|
/// signal an event, making the fd readable. Multiple calls to `post()` may be coalesced.
|
||||||
|
/// On Linux this uses [`eventfd()`](libc::eventfd), on other systems this uses a pipe.
|
||||||
|
/// [`try_consume()`](FdEventSignaller::try_consume) may be used to consume the event.
|
||||||
|
/// Importantly this is async signal safe. Of course it is `CLO_EXEC` as well.
|
||||||
|
pub struct FdEventSignaller {
|
||||||
|
// Always the read end of the fd; maybe the write end as well.
|
||||||
|
fd: AutoCloseFd,
|
||||||
|
#[cfg(not(HAVE_EVENTFD))]
|
||||||
|
write: AutoCloseFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FdEventSignaller {
|
||||||
|
/// The default constructor will abort on failure (fd exhaustion).
|
||||||
|
/// This should only be used during startup.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
#[cfg(HAVE_EVENTFD)]
|
||||||
|
{
|
||||||
|
// Note we do not want to use EFD_SEMAPHORE because we are binary (not counting) semaphore.
|
||||||
|
let fd = unsafe { libc::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK) };
|
||||||
|
if fd < 0 {
|
||||||
|
perror("eventfd");
|
||||||
|
exit_without_destructors(1);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
fd: AutoCloseFd::new(fd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(HAVE_EVENTFD))]
|
||||||
|
{
|
||||||
|
// Implementation using pipes.
|
||||||
|
let Some(pipes) = make_autoclose_pipes() else {
|
||||||
|
perror("pipe");
|
||||||
|
exit_without_destructors(1);
|
||||||
|
};
|
||||||
|
make_fd_nonblocking(pipes.read.fd()).unwrap();
|
||||||
|
make_fd_nonblocking(pipes.write.fd()).unwrap();
|
||||||
|
Self {
|
||||||
|
fd: pipes.read,
|
||||||
|
write: pipes.write,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \return the fd to read from, for notification.
|
||||||
|
pub fn read_fd(&self) -> RawFd {
|
||||||
|
self.fd.fd()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If an event is signalled, consume it; otherwise return.
|
||||||
|
/// This does not block.
|
||||||
|
/// This retries on EINTR.
|
||||||
|
pub fn try_consume(&self) -> bool {
|
||||||
|
// If we are using eventfd, we want to read a single uint64.
|
||||||
|
// If we are using pipes, read a lot; note this may leave data on the pipe if post has been
|
||||||
|
// called many more times. In no case do we care about the data which is read.
|
||||||
|
#[cfg(HAVE_EVENTFD)]
|
||||||
|
let mut buff = [0_u64; 1];
|
||||||
|
#[cfg(not(HAVE_EVENTFD))]
|
||||||
|
let mut buff = [0_u8; 1024];
|
||||||
|
let mut ret;
|
||||||
|
loop {
|
||||||
|
ret = unsafe {
|
||||||
|
libc::read(
|
||||||
|
self.read_fd(),
|
||||||
|
&mut buff as *mut _ as *mut c_void,
|
||||||
|
std::mem::size_of_val(&buff),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret >= 0 || errno().0 != EINTR {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ret < 0 && ![EAGAIN, EWOULDBLOCK].contains(&errno().0) {
|
||||||
|
perror("read");
|
||||||
|
}
|
||||||
|
ret > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark that an event has been received. This may be coalesced.
|
||||||
|
/// This retries on EINTR.
|
||||||
|
pub fn post(&self) {
|
||||||
|
// eventfd writes uint64; pipes write 1 byte.
|
||||||
|
#[cfg(HAVE_EVENTFD)]
|
||||||
|
let c = 1_u64;
|
||||||
|
#[cfg(not(HAVE_EVENTFD))]
|
||||||
|
let c = 1_u8;
|
||||||
|
let mut ret;
|
||||||
|
loop {
|
||||||
|
ret = unsafe {
|
||||||
|
libc::write(
|
||||||
|
self.write_fd(),
|
||||||
|
&c as *const _ as *const c_void,
|
||||||
|
std::mem::size_of_val(&c),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret >= 0 || errno().0 != EINTR {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// EAGAIN occurs if either the pipe buffer is full or the eventfd overflows (very unlikely).
|
||||||
|
if ret < 0 && ![EAGAIN, EWOULDBLOCK].contains(&errno().0) {
|
||||||
|
perror("write");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a poll to see if an event is received.
|
||||||
|
/// If \p wait is set, wait until it is readable; this does not consume the event
|
||||||
|
/// but guarantees that the next call to wait() will not block.
|
||||||
|
/// \return true if readable, false if not readable, or not interrupted by a signal.
|
||||||
|
pub fn poll(&self, wait: bool /* = false */) -> bool {
|
||||||
|
let mut timeout = libc::timeval {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_usec: 0,
|
||||||
|
};
|
||||||
|
let mut fds: libc::fd_set = unsafe { std::mem::zeroed() };
|
||||||
|
unsafe { libc::FD_ZERO(&mut fds) };
|
||||||
|
unsafe { libc::FD_SET(self.read_fd(), &mut fds) };
|
||||||
|
let res = unsafe {
|
||||||
|
libc::select(
|
||||||
|
self.read_fd() + 1,
|
||||||
|
&mut fds,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
if wait {
|
||||||
|
std::ptr::null_mut()
|
||||||
|
} else {
|
||||||
|
&mut timeout
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
res > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \return the fd to write to.
|
||||||
|
fn write_fd(&self) -> RawFd {
|
||||||
|
#[cfg(HAVE_EVENTFD)]
|
||||||
|
return self.fd.fd();
|
||||||
|
#[cfg(not(HAVE_EVENTFD))]
|
||||||
|
return self.write.fd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Each item added to fd_monitor_t is assigned a unique ID, which is not recycled. Items may have
|
/// Each item added to fd_monitor_t is assigned a unique ID, which is not recycled. Items may have
|
||||||
/// their callback triggered immediately by passing the ID. Zero is a sentinel.
|
/// their callback triggered immediately by passing the ID. Zero is a sentinel.
|
||||||
|
@ -301,7 +433,7 @@ fn new_fd_monitor_item_ffi(
|
||||||
pub struct FdMonitor {
|
pub struct FdMonitor {
|
||||||
/// Our self-signaller. When this is written to, it means there are new items pending, new items
|
/// Our self-signaller. When this is written to, it means there are new items pending, new items
|
||||||
/// in the poke list, or terminate has been set.
|
/// in the poke list, or terminate has been set.
|
||||||
change_signaller: SharedPtr<FdEventSignaller>,
|
change_signaller: Arc<FdEventSignaller>,
|
||||||
/// The data shared between the background thread and the `FdMonitor` instance.
|
/// The data shared between the background thread and the `FdMonitor` instance.
|
||||||
data: Arc<Mutex<SharedData>>,
|
data: Arc<Mutex<SharedData>>,
|
||||||
/// The last ID assigned or `0` if none.
|
/// The last ID assigned or `0` if none.
|
||||||
|
@ -337,7 +469,7 @@ struct BackgroundFdMonitor {
|
||||||
items: Vec<FdMonitorItem>,
|
items: Vec<FdMonitorItem>,
|
||||||
/// Our self-signaller. When this is written to, it means there are new items pending, new items
|
/// Our self-signaller. When this is written to, it means there are new items pending, new items
|
||||||
/// in the poke list, or terminate has been set.
|
/// in the poke list, or terminate has been set.
|
||||||
change_signaller: SharedPtr<FdEventSignaller>,
|
change_signaller: Weak<FdEventSignaller>,
|
||||||
/// The data shared between the background thread and the `FdMonitor` instance.
|
/// The data shared between the background thread and the `FdMonitor` instance.
|
||||||
data: Arc<Mutex<SharedData>>,
|
data: Arc<Mutex<SharedData>>,
|
||||||
}
|
}
|
||||||
|
@ -377,7 +509,7 @@ impl FdMonitor {
|
||||||
FLOG!(fd_monitor, "Thread starting");
|
FLOG!(fd_monitor, "Thread starting");
|
||||||
let background_monitor = BackgroundFdMonitor {
|
let background_monitor = BackgroundFdMonitor {
|
||||||
data: Arc::clone(&self.data),
|
data: Arc::clone(&self.data),
|
||||||
change_signaller: SharedPtr::clone(&self.change_signaller),
|
change_signaller: Arc::downgrade(&self.change_signaller),
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
};
|
};
|
||||||
crate::threads::spawn(move || {
|
crate::threads::spawn(move || {
|
||||||
|
@ -441,7 +573,7 @@ impl FdMonitor {
|
||||||
running: false,
|
running: false,
|
||||||
terminate: false,
|
terminate: false,
|
||||||
})),
|
})),
|
||||||
change_signaller: new_fd_event_signaller(),
|
change_signaller: Arc::new(FdEventSignaller::new()),
|
||||||
last_id: AtomicU64::new(0),
|
last_id: AtomicU64::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -465,7 +597,7 @@ impl BackgroundFdMonitor {
|
||||||
fds.clear();
|
fds.clear();
|
||||||
|
|
||||||
// Our change_signaller is special-cased
|
// Our change_signaller is special-cased
|
||||||
let change_signal_fd = self.change_signaller.read_fd().into();
|
let change_signal_fd = self.change_signaller.upgrade().unwrap().read_fd();
|
||||||
fds.add(change_signal_fd);
|
fds.add(change_signal_fd);
|
||||||
|
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
|
@ -535,7 +667,7 @@ impl BackgroundFdMonitor {
|
||||||
let change_signalled = fds.test(change_signal_fd);
|
let change_signalled = fds.test(change_signal_fd);
|
||||||
if change_signalled || is_wait_lap {
|
if change_signalled || is_wait_lap {
|
||||||
// Clear the change signaller before processing incoming changes
|
// Clear the change signaller before processing incoming changes
|
||||||
self.change_signaller.try_consume();
|
self.change_signaller.upgrade().unwrap().try_consume();
|
||||||
let mut data = self.data.lock().expect("Mutex poisoned!");
|
let mut data = self.data.lock().expect("Mutex poisoned!");
|
||||||
|
|
||||||
// Move from `pending` to the end of `items`
|
// Move from `pending` to the end of `items`
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use crate::common::wcs2zstring;
|
use crate::common::wcs2zstring;
|
||||||
use crate::ffi;
|
use crate::flog::FLOG;
|
||||||
use crate::wchar::prelude::*;
|
use crate::wchar::prelude::*;
|
||||||
use crate::wutil::perror;
|
use crate::wutil::perror;
|
||||||
use libc::{c_int, EINTR, FD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC, O_NONBLOCK};
|
use libc::{
|
||||||
|
c_int, EINTR, FD_CLOEXEC, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC,
|
||||||
|
O_NONBLOCK,
|
||||||
|
};
|
||||||
use nix::unistd;
|
use nix::unistd;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
@ -59,12 +62,16 @@ mod autoclose_fd_t {
|
||||||
#[cxx_name = "autoclose_fd_t2"]
|
#[cxx_name = "autoclose_fd_t2"]
|
||||||
type AutoCloseFd;
|
type AutoCloseFd;
|
||||||
|
|
||||||
|
fn new_autoclose_fd(fd: i32) -> Box<AutoCloseFd>;
|
||||||
#[cxx_name = "valid"]
|
#[cxx_name = "valid"]
|
||||||
fn is_valid(&self) -> bool;
|
fn is_valid(&self) -> bool;
|
||||||
fn close(&mut self);
|
fn close(&mut self);
|
||||||
fn fd(&self) -> i32;
|
fn fd(&self) -> i32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn new_autoclose_fd(fd: i32) -> Box<AutoCloseFd> {
|
||||||
|
Box::new(AutoCloseFd::new(fd))
|
||||||
|
}
|
||||||
|
|
||||||
impl AutoCloseFd {
|
impl AutoCloseFd {
|
||||||
// Closes the fd if not already closed.
|
// Closes the fd if not already closed.
|
||||||
|
@ -149,18 +156,69 @@ pub struct AutoClosePipes {
|
||||||
/// Construct a pair of connected pipes, set to close-on-exec.
|
/// Construct a pair of connected pipes, set to close-on-exec.
|
||||||
/// \return None on fd exhaustion.
|
/// \return None on fd exhaustion.
|
||||||
pub fn make_autoclose_pipes() -> Option<AutoClosePipes> {
|
pub fn make_autoclose_pipes() -> Option<AutoClosePipes> {
|
||||||
let pipes = ffi::make_pipes_ffi();
|
let mut pipes: [c_int; 2] = [-1, -1];
|
||||||
|
|
||||||
let readp = AutoCloseFd::new(pipes.read);
|
let already_cloexec = false;
|
||||||
let writep = AutoCloseFd::new(pipes.write);
|
#[cfg(HAVE_PIPE2)]
|
||||||
if !readp.is_valid() || !writep.is_valid() {
|
{
|
||||||
None
|
if unsafe { libc::pipe2(&mut pipes[0], O_CLOEXEC) } < 0 {
|
||||||
} else {
|
FLOG!(warning, PIPE_ERROR);
|
||||||
Some(AutoClosePipes {
|
perror("pipe2");
|
||||||
read: readp,
|
return None;
|
||||||
write: writep,
|
}
|
||||||
})
|
already_cloexec = true;
|
||||||
}
|
}
|
||||||
|
#[cfg(not(HAVE_PIPE2))]
|
||||||
|
if unsafe { libc::pipe(&mut pipes[0]) } < 0 {
|
||||||
|
FLOG!(warning, PIPE_ERROR);
|
||||||
|
perror("pipe2");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let readp = AutoCloseFd::new(pipes[0]);
|
||||||
|
let writep = AutoCloseFd::new(pipes[1]);
|
||||||
|
|
||||||
|
// Ensure our fds are out of the user range.
|
||||||
|
let readp = heightenize_fd(readp, already_cloexec);
|
||||||
|
if !readp.is_valid() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let writep = heightenize_fd(writep, already_cloexec);
|
||||||
|
if !writep.is_valid() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(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().
|
||||||
|
/// \p 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.
|
||||||
|
fn heightenize_fd(fd: AutoCloseFd, input_has_cloexec: bool) -> AutoCloseFd {
|
||||||
|
// Check if the fd is invalid or already in our high range.
|
||||||
|
if !fd.is_valid() {
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
if fd.fd() >= FIRST_HIGH_FD {
|
||||||
|
if !input_has_cloexec {
|
||||||
|
set_cloexec(fd.fd(), true);
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
// Here we are asking the kernel to give us a cloexec fd.
|
||||||
|
let newfd = unsafe { libc::fcntl(fd.fd(), F_DUPFD_CLOEXEC, FIRST_HIGH_FD) };
|
||||||
|
if newfd < 0 {
|
||||||
|
perror("fcntl");
|
||||||
|
return AutoCloseFd::default();
|
||||||
|
}
|
||||||
|
return AutoCloseFd::new(newfd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets CLO_EXEC on a given fd according to the value of \p should_set.
|
/// Sets CLO_EXEC on a given fd according to the value of \p should_set.
|
||||||
|
|
|
@ -66,24 +66,18 @@ include_cpp! {
|
||||||
generate!("activate_flog_categories_by_pattern")
|
generate!("activate_flog_categories_by_pattern")
|
||||||
generate!("save_term_foreground_process_group")
|
generate!("save_term_foreground_process_group")
|
||||||
generate!("restore_term_foreground_process_group_for_exit")
|
generate!("restore_term_foreground_process_group_for_exit")
|
||||||
generate!("set_cloexec")
|
|
||||||
|
|
||||||
generate!("builtin_bind")
|
generate!("builtin_bind")
|
||||||
generate!("builtin_commandline")
|
generate!("builtin_commandline")
|
||||||
|
|
||||||
generate!("init_input")
|
generate!("init_input")
|
||||||
|
|
||||||
generate_pod!("pipes_ffi_t")
|
|
||||||
|
|
||||||
generate!("shell_modes_ffi")
|
generate!("shell_modes_ffi")
|
||||||
generate!("make_pipes_ffi")
|
|
||||||
|
|
||||||
generate!("log_extra_to_flog_file")
|
generate!("log_extra_to_flog_file")
|
||||||
|
|
||||||
generate!("wgettext_ptr")
|
generate!("wgettext_ptr")
|
||||||
|
|
||||||
generate!("fd_event_signaller_t")
|
|
||||||
|
|
||||||
generate!("highlight_role_t")
|
generate!("highlight_role_t")
|
||||||
generate!("highlight_spec_t")
|
generate!("highlight_spec_t")
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ use crate::{
|
||||||
ConfigPaths, EnvMode,
|
ConfigPaths, EnvMode,
|
||||||
},
|
},
|
||||||
event::{self, Event},
|
event::{self, Event},
|
||||||
|
fds::set_cloexec,
|
||||||
ffi::{self},
|
ffi::{self},
|
||||||
flog::{self, activate_flog_categories_by_pattern, set_flog_file_fd, FLOG, FLOGF},
|
flog::{self, activate_flog_categories_by_pattern, set_flog_file_fd, FLOG, FLOGF},
|
||||||
function, future_feature_flags as features, history,
|
function, future_feature_flags as features, history,
|
||||||
|
@ -550,7 +551,7 @@ fn main() -> i32 {
|
||||||
std::process::exit(-1);
|
std::process::exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { ffi::set_cloexec(c_int(libc::fileno(debug_file)), true) };
|
set_cloexec(unsafe { libc::fileno(debug_file) }, true);
|
||||||
ffi::flog_setlinebuf_ffi(debug_file as *mut _);
|
ffi::flog_setlinebuf_ffi(debug_file as *mut _);
|
||||||
ffi::set_flog_output_file_ffi(debug_file as *mut _);
|
ffi::set_flog_output_file_ffi(debug_file as *mut _);
|
||||||
set_flog_file_fd(unsafe { libc::fileno(debug_file) });
|
set_flog_file_fd(unsafe { libc::fileno(debug_file) });
|
||||||
|
|
|
@ -4,7 +4,9 @@ use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::fd_monitor::{FdMonitor, FdMonitorItem, FdMonitorItemId, ItemWakeReason};
|
use crate::fd_monitor::{
|
||||||
|
FdEventSignaller, FdMonitor, FdMonitorItem, FdMonitorItemId, ItemWakeReason,
|
||||||
|
};
|
||||||
use crate::fds::{make_autoclose_pipes, AutoCloseFd};
|
use crate::fds::{make_autoclose_pipes, AutoCloseFd};
|
||||||
use crate::ffi_tests::add_test;
|
use crate::ffi_tests::add_test;
|
||||||
|
|
||||||
|
@ -188,3 +190,28 @@ add_test!("fd_monitor_items", || {
|
||||||
assert_eq!(item_pokee.total_calls.load(Ordering::Relaxed), 1);
|
assert_eq!(item_pokee.total_calls.load(Ordering::Relaxed), 1);
|
||||||
assert_eq!(item_pokee.pokes.load(Ordering::Relaxed), 1);
|
assert_eq!(item_pokee.pokes.load(Ordering::Relaxed), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fd_event_signaller() {
|
||||||
|
let sema = FdEventSignaller::new();
|
||||||
|
assert!(!sema.try_consume());
|
||||||
|
assert!(!sema.poll(false));
|
||||||
|
|
||||||
|
// Post once.
|
||||||
|
sema.post();
|
||||||
|
assert!(sema.poll(false));
|
||||||
|
assert!(sema.poll(false));
|
||||||
|
assert!(sema.try_consume());
|
||||||
|
assert!(!sema.poll(false));
|
||||||
|
assert!(!sema.try_consume());
|
||||||
|
|
||||||
|
// Posts are coalesced.
|
||||||
|
sema.post();
|
||||||
|
sema.post();
|
||||||
|
sema.post();
|
||||||
|
assert!(sema.poll(false));
|
||||||
|
assert!(sema.poll(false));
|
||||||
|
assert!(sema.try_consume());
|
||||||
|
assert!(!sema.poll(false));
|
||||||
|
assert!(!sema.try_consume());
|
||||||
|
}
|
||||||
|
|
|
@ -38,16 +38,8 @@ const IO_WAIT_FOR_WORK_DURATION: Duration = Duration::from_millis(500);
|
||||||
static IO_THREAD_POOL: OnceBox<Mutex<ThreadPool>> = OnceBox::new();
|
static IO_THREAD_POOL: OnceBox<Mutex<ThreadPool>> = OnceBox::new();
|
||||||
|
|
||||||
/// The event signaller singleton used for completions and queued main thread requests.
|
/// The event signaller singleton used for completions and queued main thread requests.
|
||||||
static NOTIFY_SIGNALLER: once_cell::sync::Lazy<&'static crate::fd_monitor::FdEventSignaller> =
|
static NOTIFY_SIGNALLER: once_cell::sync::Lazy<crate::fd_monitor::FdEventSignaller> =
|
||||||
once_cell::sync::Lazy::new(|| unsafe {
|
once_cell::sync::Lazy::new(crate::fd_monitor::FdEventSignaller::new);
|
||||||
// This is leaked to avoid C++-side destructors. When ported fully to rust, we won't need to
|
|
||||||
// leak anything.
|
|
||||||
let signaller = crate::fd_monitor::new_fd_event_signaller();
|
|
||||||
let signaller_ref: &crate::fd_monitor::FdEventSignaller = signaller.as_ref().unwrap();
|
|
||||||
let result = std::mem::transmute(signaller_ref);
|
|
||||||
std::mem::forget(signaller);
|
|
||||||
result
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cxx::bridge]
|
#[cxx::bridge]
|
||||||
mod ffi {
|
mod ffi {
|
||||||
|
@ -557,7 +549,7 @@ pub fn iothread_perform_cant_wait(f: impl FnOnce() + 'static + Send) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iothread_port() -> i32 {
|
pub fn iothread_port() -> i32 {
|
||||||
i32::from(NOTIFY_SIGNALLER.read_fd())
|
NOTIFY_SIGNALLER.read_fd()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iothread_service_main_with_timeout(timeout: Duration) {
|
pub fn iothread_service_main_with_timeout(timeout: Duration) {
|
||||||
|
|
249
src/fds.cpp
249
src/fds.cpp
|
@ -1,249 +0,0 @@
|
||||||
/** Facilities for working with file descriptors. */
|
|
||||||
|
|
||||||
#include "config.h" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include "fds.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "flog.h"
|
|
||||||
#include "wutil.h"
|
|
||||||
|
|
||||||
#ifdef HAVE_EVENTFD
|
|
||||||
#include <sys/eventfd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The first fd in the "high range." fds below this are allowed to be used directly by users in
|
|
||||||
// redirections, e.g. >&3
|
|
||||||
const int k_first_high_fd = 10;
|
|
||||||
static constexpr uint64_t kUsecPerMsec = 1000;
|
|
||||||
static constexpr uint64_t kUsecPerSec [[gnu::unused]] = 1000 * kUsecPerMsec;
|
|
||||||
|
|
||||||
void autoclose_fd_t::close() {
|
|
||||||
if (fd_ < 0) return;
|
|
||||||
exec_close(fd_);
|
|
||||||
fd_ = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<fd_event_signaller_t> ffi_new_fd_event_signaller_t() {
|
|
||||||
return std::make_shared<fd_event_signaller_t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_EVENTFD
|
|
||||||
// Note we do not want to use EFD_SEMAPHORE because we are binary (not counting) semaphore.
|
|
||||||
fd_event_signaller_t::fd_event_signaller_t() {
|
|
||||||
int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
|
||||||
if (fd < 0) {
|
|
||||||
wperror(L"eventfd");
|
|
||||||
exit_without_destructors(1);
|
|
||||||
}
|
|
||||||
fd_.reset(fd);
|
|
||||||
};
|
|
||||||
|
|
||||||
int fd_event_signaller_t::write_fd() const { return fd_.fd(); }
|
|
||||||
|
|
||||||
#else
|
|
||||||
// Implementation using pipes.
|
|
||||||
fd_event_signaller_t::fd_event_signaller_t() {
|
|
||||||
auto pipes = make_autoclose_pipes();
|
|
||||||
if (!pipes) {
|
|
||||||
wperror(L"pipe");
|
|
||||||
exit_without_destructors(1);
|
|
||||||
}
|
|
||||||
DIE_ON_FAILURE(make_fd_nonblocking(pipes->read.fd()));
|
|
||||||
DIE_ON_FAILURE(make_fd_nonblocking(pipes->write.fd()));
|
|
||||||
fd_ = std::move(pipes->read);
|
|
||||||
write_ = std::move(pipes->write);
|
|
||||||
}
|
|
||||||
|
|
||||||
int fd_event_signaller_t::write_fd() const { return write_.fd(); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool fd_event_signaller_t::try_consume() const {
|
|
||||||
// If we are using eventfd, we want to read a single uint64.
|
|
||||||
// If we are using pipes, read a lot; note this may leave data on the pipe if post has been
|
|
||||||
// called many more times. In no case do we care about the data which is read.
|
|
||||||
#ifdef HAVE_EVENTFD
|
|
||||||
uint64_t buff[1];
|
|
||||||
#else
|
|
||||||
uint8_t buff[1024];
|
|
||||||
#endif
|
|
||||||
ssize_t ret;
|
|
||||||
do {
|
|
||||||
ret = read(read_fd(), buff, sizeof buff);
|
|
||||||
} while (ret < 0 && errno == EINTR);
|
|
||||||
if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
||||||
wperror(L"read");
|
|
||||||
}
|
|
||||||
return ret > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fd_event_signaller_t::post() const {
|
|
||||||
// eventfd writes uint64; pipes write 1 byte.
|
|
||||||
#ifdef HAVE_EVENTFD
|
|
||||||
const uint64_t c = 1;
|
|
||||||
#else
|
|
||||||
const uint8_t c = 1;
|
|
||||||
#endif
|
|
||||||
ssize_t ret;
|
|
||||||
do {
|
|
||||||
ret = write(write_fd(), &c, sizeof c);
|
|
||||||
} while (ret < 0 && errno == EINTR);
|
|
||||||
// EAGAIN occurs if either the pipe buffer is full or the eventfd overflows (very unlikely).
|
|
||||||
if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
||||||
wperror(L"write");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fd_event_signaller_t::poll(bool wait) const {
|
|
||||||
struct timeval timeout = {0, 0};
|
|
||||||
fd_set fds;
|
|
||||||
FD_ZERO(&fds);
|
|
||||||
FD_SET(read_fd(), &fds);
|
|
||||||
int res = select(read_fd() + 1, &fds, nullptr, nullptr, wait ? nullptr : &timeout);
|
|
||||||
return res > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fd_event_signaller_t::~fd_event_signaller_t() = default;
|
|
||||||
|
|
||||||
/// If the given fd is in the "user range", move it to a new fd in the "high range".
|
|
||||||
/// zsh calls this movefd().
|
|
||||||
/// \p 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.
|
|
||||||
static autoclose_fd_t heightenize_fd(autoclose_fd_t fd, bool input_has_cloexec) {
|
|
||||||
// Check if the fd is invalid or already in our high range.
|
|
||||||
if (!fd.valid()) {
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
if (fd.fd() >= k_first_high_fd) {
|
|
||||||
if (!input_has_cloexec) set_cloexec(fd.fd());
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
#if defined(F_DUPFD_CLOEXEC)
|
|
||||||
// Here we are asking the kernel to give us a
|
|
||||||
int newfd = fcntl(fd.fd(), F_DUPFD_CLOEXEC, k_first_high_fd);
|
|
||||||
if (newfd < 0) {
|
|
||||||
wperror(L"fcntl");
|
|
||||||
return autoclose_fd_t{};
|
|
||||||
}
|
|
||||||
return autoclose_fd_t(newfd);
|
|
||||||
#elif defined(F_DUPFD)
|
|
||||||
int newfd = fcntl(fd.fd(), F_DUPFD, k_first_high_fd);
|
|
||||||
if (newfd < 0) {
|
|
||||||
wperror(L"fcntl");
|
|
||||||
return autoclose_fd_t{};
|
|
||||||
}
|
|
||||||
set_cloexec(newfd);
|
|
||||||
return autoclose_fd_t(newfd);
|
|
||||||
#else
|
|
||||||
// We have fd >= 0, and it's in the user range. dup it and recurse. Note that we recurse before
|
|
||||||
// anything is closed; this forces the kernel to give us a new one (or report fd exhaustion).
|
|
||||||
int tmp_fd;
|
|
||||||
do {
|
|
||||||
tmp_fd = dup(fd.fd());
|
|
||||||
} while (tmp_fd < 0 && errno == EINTR);
|
|
||||||
// Ok, we have a new candidate fd. Recurse.
|
|
||||||
return heightenize_fd(autoclose_fd_t{tmp_fd}, false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
maybe_t<autoclose_pipes_t> make_autoclose_pipes() {
|
|
||||||
int pipes[2] = {-1, -1};
|
|
||||||
|
|
||||||
bool already_cloexec = false;
|
|
||||||
#ifdef HAVE_PIPE2
|
|
||||||
if (pipe2(pipes, O_CLOEXEC) < 0) {
|
|
||||||
FLOGF(warning, PIPE_ERROR);
|
|
||||||
wperror(L"pipe2");
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
already_cloexec = true;
|
|
||||||
#else
|
|
||||||
if (pipe(pipes) < 0) {
|
|
||||||
FLOGF(warning, PIPE_ERROR);
|
|
||||||
wperror(L"pipe");
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
autoclose_fd_t read_end{pipes[0]};
|
|
||||||
autoclose_fd_t write_end{pipes[1]};
|
|
||||||
|
|
||||||
// Ensure our fds are out of the user range.
|
|
||||||
read_end = heightenize_fd(std::move(read_end), already_cloexec);
|
|
||||||
if (!read_end.valid()) return none();
|
|
||||||
|
|
||||||
write_end = heightenize_fd(std::move(write_end), already_cloexec);
|
|
||||||
if (!write_end.valid()) return none();
|
|
||||||
|
|
||||||
return autoclose_pipes_t(std::move(read_end), std::move(write_end));
|
|
||||||
}
|
|
||||||
|
|
||||||
pipes_ffi_t make_pipes_ffi() {
|
|
||||||
pipes_ffi_t res = {-1, -1};
|
|
||||||
if (auto pipes = make_autoclose_pipes()) {
|
|
||||||
res.read = pipes->read.acquire();
|
|
||||||
res.write = pipes->write.acquire();
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int set_cloexec(int fd, bool should_set) {
|
|
||||||
// Note we don't want to overwrite existing flags like O_NONBLOCK which may be set. So fetch the
|
|
||||||
// existing flags and modify them.
|
|
||||||
int flags = fcntl(fd, F_GETFD, 0);
|
|
||||||
if (flags < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int new_flags = flags;
|
|
||||||
if (should_set) {
|
|
||||||
new_flags |= FD_CLOEXEC;
|
|
||||||
} else {
|
|
||||||
new_flags &= ~FD_CLOEXEC;
|
|
||||||
}
|
|
||||||
if (flags == new_flags) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return fcntl(fd, F_SETFD, new_flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int open_cloexec(const std::string &path, int flags, mode_t mode) {
|
|
||||||
return open_cloexec(path.c_str(), flags, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
int open_cloexec(const char *path, int flags, mode_t mode) {
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
// Prefer to use O_CLOEXEC.
|
|
||||||
#ifdef O_CLOEXEC
|
|
||||||
fd = open(path, flags | O_CLOEXEC, mode);
|
|
||||||
#else
|
|
||||||
fd = open(path, flags, mode);
|
|
||||||
if (fd >= 0 && set_cloexec(fd)) {
|
|
||||||
exec_close(fd);
|
|
||||||
fd = -1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode) {
|
|
||||||
return open_cloexec(wcs2zstring(pathname), flags, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void exec_close(int fd) {
|
|
||||||
assert(fd >= 0 && "Invalid fd");
|
|
||||||
while (close(fd) == -1) {
|
|
||||||
if (errno != EINTR) {
|
|
||||||
wperror(L"close");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
136
src/fds.h
136
src/fds.h
|
@ -17,144 +17,14 @@
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "maybe.h"
|
#include "maybe.h"
|
||||||
|
|
||||||
/// Pipe redirection error message.
|
#if INCLUDE_RUST_HEADERS
|
||||||
#define PIPE_ERROR _(L"An error occurred while setting up pipe")
|
#include "fds.rs.h"
|
||||||
|
#endif
|
||||||
/// The first "high fd", which is considered outside the range of valid user-specified redirections
|
|
||||||
/// (like >&5).
|
|
||||||
extern const int k_first_high_fd;
|
|
||||||
|
|
||||||
/// A sentinel value indicating no timeout.
|
/// A sentinel value indicating no timeout.
|
||||||
#define kNoTimeout (std::numeric_limits<uint64_t>::max())
|
#define kNoTimeout (std::numeric_limits<uint64_t>::max())
|
||||||
|
|
||||||
/// A helper class for managing and automatically closing a file descriptor.
|
|
||||||
class autoclose_fd_t : noncopyable_t {
|
|
||||||
int fd_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Closes the fd if not already closed.
|
|
||||||
void close();
|
|
||||||
|
|
||||||
// Returns the fd.
|
|
||||||
int fd() const { return fd_; }
|
|
||||||
|
|
||||||
// Returns the fd, transferring ownership to the caller.
|
|
||||||
int acquire() {
|
|
||||||
int temp = fd_;
|
|
||||||
fd_ = -1;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resets to a new fd, taking ownership.
|
|
||||||
void reset(int fd) {
|
|
||||||
if (fd == fd_) return;
|
|
||||||
close();
|
|
||||||
fd_ = fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// \return if this has a valid fd.
|
|
||||||
bool valid() const { return fd_ >= 0; }
|
|
||||||
|
|
||||||
autoclose_fd_t(autoclose_fd_t &&rhs) : fd_(rhs.fd_) { rhs.fd_ = -1; }
|
|
||||||
|
|
||||||
void operator=(autoclose_fd_t &&rhs) {
|
|
||||||
close();
|
|
||||||
std::swap(this->fd_, rhs.fd_);
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit autoclose_fd_t(int fd = -1) : fd_(fd) {}
|
|
||||||
~autoclose_fd_t() { close(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Helper type returned from making autoclose pipes.
|
|
||||||
struct autoclose_pipes_t {
|
|
||||||
/// Read end of the pipe.
|
|
||||||
autoclose_fd_t read;
|
|
||||||
|
|
||||||
/// Write end of the pipe.
|
|
||||||
autoclose_fd_t write;
|
|
||||||
|
|
||||||
autoclose_pipes_t() = default;
|
|
||||||
autoclose_pipes_t(autoclose_fd_t r, autoclose_fd_t w)
|
|
||||||
: read(std::move(r)), write(std::move(w)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Call pipe(), populating autoclose fds.
|
|
||||||
/// The pipes are marked CLO_EXEC and are placed in the high fd range.
|
|
||||||
/// \return pipes on success, none() on error.
|
|
||||||
maybe_t<autoclose_pipes_t> make_autoclose_pipes();
|
|
||||||
|
|
||||||
/// Create pipes.
|
|
||||||
/// Upon failure both values will be negative.
|
|
||||||
struct pipes_ffi_t {
|
|
||||||
int read;
|
|
||||||
int write;
|
|
||||||
};
|
|
||||||
pipes_ffi_t make_pipes_ffi();
|
|
||||||
|
|
||||||
/// An event signaller implemented using a file descriptor, so it can plug into select().
|
|
||||||
/// This is like a binary semaphore. A call to post() will signal an event, making the fd readable.
|
|
||||||
/// Multiple calls to post() may be coalesced. On Linux this uses eventfd(); on other systems this
|
|
||||||
/// uses a pipe. try_consume() may be used to consume the event. Importantly this is async signal
|
|
||||||
/// safe. Of course it is CLO_EXEC as well.
|
|
||||||
class fd_event_signaller_t {
|
|
||||||
public:
|
|
||||||
/// \return the fd to read from, for notification.
|
|
||||||
int read_fd() const { return fd_.fd(); }
|
|
||||||
|
|
||||||
/// If an event is signalled, consume it; otherwise return.
|
|
||||||
/// This does not block.
|
|
||||||
/// This retries on EINTR.
|
|
||||||
bool try_consume() const;
|
|
||||||
|
|
||||||
/// Mark that an event has been received. This may be coalesced.
|
|
||||||
/// This retries on EINTR.
|
|
||||||
void post() const;
|
|
||||||
|
|
||||||
/// Perform a poll to see if an event is received.
|
|
||||||
/// If \p wait is set, wait until it is readable; this does not consume the event
|
|
||||||
/// but guarantees that the next call to wait() will not block.
|
|
||||||
/// \return true if readable, false if not readable, or not interrupted by a signal.
|
|
||||||
bool poll(bool wait = false) const;
|
|
||||||
|
|
||||||
// The default constructor will abort on failure (fd exhaustion).
|
|
||||||
// This should only be used during startup.
|
|
||||||
fd_event_signaller_t();
|
|
||||||
~fd_event_signaller_t();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// \return the fd to write to.
|
|
||||||
int write_fd() const;
|
|
||||||
|
|
||||||
// Always the read end of the fd; maybe the write end as well.
|
|
||||||
autoclose_fd_t fd_;
|
|
||||||
|
|
||||||
#ifndef HAVE_EVENTFD
|
|
||||||
// If using a pipe, then this is its write end.
|
|
||||||
autoclose_fd_t write_;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<fd_event_signaller_t> ffi_new_fd_event_signaller_t();
|
|
||||||
|
|
||||||
/// Sets CLO_EXEC on a given fd according to the value of \p should_set.
|
|
||||||
int set_cloexec(int fd, bool should_set = true);
|
|
||||||
|
|
||||||
/// Wide character version of open() that also sets the close-on-exec flag (atomically when
|
|
||||||
/// possible).
|
|
||||||
int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode = 0);
|
|
||||||
|
|
||||||
/// Narrow versions of wopen_cloexec.
|
|
||||||
int open_cloexec(const std::string &path, int flags, mode_t mode = 0);
|
|
||||||
int open_cloexec(const char *path, int flags, mode_t mode = 0);
|
|
||||||
|
|
||||||
/// Mark an fd as nonblocking; returns errno or 0 on success.
|
|
||||||
int make_fd_nonblocking(int fd);
|
|
||||||
|
|
||||||
/// Mark an fd as blocking; returns errno or 0 on success.
|
/// Mark an fd as blocking; returns errno or 0 on success.
|
||||||
int make_fd_blocking(int fd);
|
int make_fd_blocking(int fd);
|
||||||
|
|
||||||
/// Close a file descriptor \p fd, retrying on EINTR.
|
|
||||||
void exec_close(int fd);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -21,7 +21,6 @@ void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
|
||||||
get_history_variable_text_ffi({});
|
get_history_variable_text_ffi({});
|
||||||
highlight_spec_t{};
|
highlight_spec_t{};
|
||||||
init_input();
|
init_input();
|
||||||
make_pipes_ffi();
|
|
||||||
reader_change_cursor_selection_mode(cursor_selection_mode_t::exclusive);
|
reader_change_cursor_selection_mode(cursor_selection_mode_t::exclusive);
|
||||||
reader_change_history({});
|
reader_change_history({});
|
||||||
reader_read_ffi({}, {}, {});
|
reader_read_ffi({}, {}, {});
|
||||||
|
|
|
@ -1167,32 +1167,6 @@ void test_dirname_basename() {
|
||||||
do_test(wbasename(longpath) == L"overlong");
|
do_test(wbasename(longpath) == L"overlong");
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo!("port this")
|
|
||||||
static void test_fd_event_signaller() {
|
|
||||||
say(L"Testing fd event signaller");
|
|
||||||
fd_event_signaller_t sema;
|
|
||||||
do_test(!sema.try_consume());
|
|
||||||
do_test(!sema.poll());
|
|
||||||
|
|
||||||
// Post once.
|
|
||||||
sema.post();
|
|
||||||
do_test(sema.poll());
|
|
||||||
do_test(sema.poll());
|
|
||||||
do_test(sema.try_consume());
|
|
||||||
do_test(!sema.poll());
|
|
||||||
do_test(!sema.try_consume());
|
|
||||||
|
|
||||||
// Posts are coalesced.
|
|
||||||
sema.post();
|
|
||||||
sema.post();
|
|
||||||
sema.post();
|
|
||||||
do_test(sema.poll());
|
|
||||||
do_test(sema.poll());
|
|
||||||
do_test(sema.try_consume());
|
|
||||||
do_test(!sema.poll());
|
|
||||||
do_test(!sema.try_consume());
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_rust_smoke() {
|
void test_rust_smoke() {
|
||||||
size_t x = rust::add(37, 5);
|
size_t x = rust::add(37, 5);
|
||||||
do_test(x == 42);
|
do_test(x == 42);
|
||||||
|
@ -1240,7 +1214,6 @@ static const test_t s_tests[]{
|
||||||
{TEST_GROUP("maybe"), test_maybe},
|
{TEST_GROUP("maybe"), test_maybe},
|
||||||
{TEST_GROUP("normalize"), test_normalize_path},
|
{TEST_GROUP("normalize"), test_normalize_path},
|
||||||
{TEST_GROUP("dirname"), test_dirname_basename},
|
{TEST_GROUP("dirname"), test_dirname_basename},
|
||||||
{TEST_GROUP("fd_event"), test_fd_event_signaller},
|
|
||||||
{TEST_GROUP("rust_smoke"), test_rust_smoke},
|
{TEST_GROUP("rust_smoke"), test_rust_smoke},
|
||||||
{TEST_GROUP("rust_ffi"), test_rust_ffi},
|
{TEST_GROUP("rust_ffi"), test_rust_ffi},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2323,14 +2323,14 @@ static bool check_for_orphaned_process(unsigned long loop_count, pid_t shell_pgi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the tty. Presumably this is stdin, but maybe not?
|
// Open the tty. Presumably this is stdin, but maybe not?
|
||||||
autoclose_fd_t tty_fd{open(tty, O_RDONLY | O_NONBLOCK)};
|
rust::Box<autoclose_fd_t2> tty_fd = new_autoclose_fd(open(tty, O_RDONLY | O_NONBLOCK));
|
||||||
if (!tty_fd.valid()) {
|
if (!tty_fd->valid()) {
|
||||||
wperror(L"open");
|
wperror(L"open");
|
||||||
exit_without_destructors(1);
|
exit_without_destructors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
char tmp;
|
char tmp;
|
||||||
if (read(tty_fd.fd(), &tmp, 1) < 0 && errno == EIO) {
|
if (read(tty_fd->fd(), &tmp, 1) < 0 && errno == EIO) {
|
||||||
we_think_we_are_orphaned = true;
|
we_think_we_are_orphaned = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,16 +258,6 @@ void wperror(wcharz_t s) {
|
||||||
std::fwprintf(stderr, L"%s\n", std::strerror(e));
|
std::fwprintf(stderr, L"%s\n", std::strerror(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
int make_fd_nonblocking(int fd) {
|
|
||||||
int flags = fcntl(fd, F_GETFL, 0);
|
|
||||||
int err = 0;
|
|
||||||
bool nonblocking = flags & O_NONBLOCK;
|
|
||||||
if (!nonblocking) {
|
|
||||||
err = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
||||||
}
|
|
||||||
return err == -1 ? errno : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int make_fd_blocking(int fd) {
|
int make_fd_blocking(int fd) {
|
||||||
int flags = fcntl(fd, F_GETFL, 0);
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
@ -663,8 +653,6 @@ file_id_t file_id_for_fd(int fd) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
file_id_t file_id_for_fd(const autoclose_fd_t &fd) { return file_id_for_fd(fd.fd()); }
|
|
||||||
|
|
||||||
bool file_id_t::operator==(const file_id_t &rhs) const { return this->compare_file_id(rhs) == 0; }
|
bool file_id_t::operator==(const file_id_t &rhs) const { return this->compare_file_id(rhs) == 0; }
|
||||||
|
|
||||||
bool file_id_t::operator!=(const file_id_t &rhs) const { return !(*this == rhs); }
|
bool file_id_t::operator!=(const file_id_t &rhs) const { return !(*this == rhs); }
|
||||||
|
|
|
@ -303,7 +303,6 @@ struct hash<file_id_t> {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
file_id_t file_id_for_fd(int fd);
|
file_id_t file_id_for_fd(int fd);
|
||||||
file_id_t file_id_for_fd(const autoclose_fd_t &fd);
|
|
||||||
|
|
||||||
extern const file_id_t kInvalidFileID;
|
extern const file_id_t kInvalidFileID;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue