fish-shell/src/fds.h
Mahmoud Al-Qudsi ce559bc20e 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-19 15:42:03 -06:00

161 lines
4.7 KiB
C++

/** Facilities for working with file descriptors. */
#ifndef FISH_FDS_H
#define FISH_FDS_H
#include "config.h" // IWYU pragma: keep
#include <poll.h> // IWYU pragma: keep
#include <sys/select.h> // IWYU pragma: keep
#include <sys/types.h>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include "common.h"
#include "maybe.h"
/// Pipe redirection error message.
#define PIPE_ERROR _(L"An error occurred while setting up pipe")
/// 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.
#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.
int make_fd_blocking(int fd);
/// Close a file descriptor \p fd, retrying on EINTR.
void exec_close(int fd);
#endif