299 lines
9.7 KiB
C++
Raw Normal View History

#ifndef FISH_IO_H
#define FISH_IO_H
#include <stdarg.h>
#include <unistd.h>
#include <cstdint>
#include <cwchar>
#include <future>
#include <memory>
#include <string>
#include <utility>
2019-02-01 01:58:06 -08:00
#include <vector>
2015-07-25 23:14:25 +08:00
#include "common.h"
#include "cxx.h"
#include "fds.h"
#include "global_safety.h"
#include "redirection.h"
2023-01-14 14:56:24 -08:00
#include "signals.h"
#if INCLUDE_RUST_HEADERS
#include "io.rs.h"
#else
struct IoChain;
struct IoStreams;
struct OutputStreamFfi;
#endif
using output_stream_t = OutputStreamFfi;
using io_streams_t = IoStreams;
// null_output_stream_t
2019-02-01 01:58:06 -08:00
using std::shared_ptr;
2015-07-25 23:14:25 +08:00
struct job_group_t;
/// separated_buffer_t represents a buffer of output from commands, prepared to be turned into a
/// variable. For example, command substitutions output into one of these. Most commands just
/// produce a stream of bytes, and those get stored directly. However other commands produce
/// explicitly separated output, in particular `string` like `string collect` and `string split0`.
/// The buffer tracks a sequence of elements. Some elements are explicitly separated and should not
/// be further split; other elements have inferred separation and may be split by IFS (or not,
/// depending on its value).
enum class separation_type_t {
inferred, // this element should be further separated by IFS
explicitly, // this element is explicitly separated and should not be further split
};
/// A separated_buffer_t contains a list of elements, some of which may be separated explicitly and
/// others which must be separated further by the user (e.g. via IFS).
class separated_buffer_t : noncopyable_t {
public:
struct element_t {
std::string contents;
separation_type_t separation;
element_t(std::string contents, separation_type_t sep)
: contents(std::move(contents)), separation(sep) {}
bool is_explicitly_separated() const { return separation == separation_type_t::explicitly; }
};
/// We not be copied but may be moved.
/// Note this leaves the moved-from value in a bogus state until clear() is called on it.
2021-05-10 15:58:05 -07:00
separated_buffer_t(separated_buffer_t &&) = default;
separated_buffer_t &operator=(separated_buffer_t &&) = default;
/// Construct a separated_buffer_t with the given buffer limit \p limit, or 0 for no limit.
separated_buffer_t(size_t limit) : buffer_limit_(limit) {}
/// \return the buffer limit size, or 0 for no limit.
size_t limit() const { return buffer_limit_; }
/// \return the contents size.
size_t size() const { return contents_size_; }
/// \return whether the output has been discarded.
bool discarded() const { return discard_; }
/// Serialize the contents to a single string, where explicitly separated elements have a
/// newline appended.
std::string newline_serialized() const {
std::string result;
result.reserve(size());
for (const auto &elem : elements_) {
result.append(elem.contents);
if (elem.is_explicitly_separated()) {
result.push_back('\n');
}
}
return result;
}
/// \return the list of elements.
const std::vector<element_t> &elements() const { return elements_; }
/// Append a string \p str of a given length \p len, with separation type \p sep.
bool append(const char *str, size_t len, separation_type_t sep = separation_type_t::inferred) {
if (!try_add_size(len)) return false;
// Try merging with the last element.
if (sep == separation_type_t::inferred && last_inferred()) {
elements_.back().contents.append(str, len);
} else {
elements_.emplace_back(std::string(str, len), sep);
}
return true;
}
/// Append a string \p str with separation type \p sep.
bool append(std::string &&str, separation_type_t sep = separation_type_t::inferred) {
if (!try_add_size(str.size())) return false;
// Try merging with the last element.
if (sep == separation_type_t::inferred && last_inferred()) {
elements_.back().contents.append(str);
} else {
elements_.emplace_back(std::move(str), sep);
}
return true;
}
/// Remove all elements and unset the discard flag.
void clear() {
elements_.clear();
contents_size_ = 0;
discard_ = false;
}
private:
/// \return true if our last element has an inferred separation type.
bool last_inferred() const {
return !elements_.empty() && !elements_.back().is_explicitly_separated();
}
/// If our last element has an inferred separation, return a pointer to it; else nullptr.
/// This is useful for appending one inferred separation to another.
element_t *last_if_inferred() {
if (!elements_.empty() && !elements_.back().is_explicitly_separated()) {
return &elements_.back();
}
return nullptr;
}
/// Mark that we are about to add the given size \p delta to the buffer. \return true if we
/// succeed, false if we exceed buffer_limit.
bool try_add_size(size_t delta) {
if (discard_) return false;
size_t proposed_size = contents_size_ + delta;
if ((proposed_size < delta) || (buffer_limit_ > 0 && proposed_size > buffer_limit_)) {
clear();
discard_ = true;
return false;
}
contents_size_ = proposed_size;
return true;
}
/// Limit on how much data we'll buffer. Zero means no limit.
size_t buffer_limit_;
/// Current size of all contents.
size_t contents_size_{0};
/// List of buffer elements.
std::vector<element_t> elements_;
/// True if we're discarding input because our buffer_limit has been exceeded.
bool discard_{false};
};
/// Describes what type of IO operation an io_data_t represents.
enum class io_mode_t { file, pipe, fd, close, bufferfill };
/// Represents an FD redirection.
class io_data_t : noncopyable_t, nonmovable_t {
protected:
io_data_t(io_mode_t m, int fd, int source_fd) : io_mode(m), fd(fd), source_fd(source_fd) {}
public:
/// Type of redirect.
const io_mode_t io_mode;
/// FD to redirect.
const int fd;
/// Source fd. This is dup2'd to fd, or if it is -1, then fd is closed.
/// That is, we call dup2(source_fd, fd).
const int source_fd;
virtual void print() const = 0;
virtual ~io_data_t() = 0;
};
2013-01-09 16:02:04 +08:00
2020-12-19 20:06:36 -08:00
class io_close_t final : public io_data_t {
public:
explicit io_close_t(int f) : io_data_t(io_mode_t::close, f, -1) {}
2013-01-09 16:02:04 +08:00
void print() const override;
~io_close_t() override;
2013-01-09 16:02:04 +08:00
};
2013-01-15 15:37:33 +08:00
2020-12-19 20:06:36 -08:00
class io_fd_t final : public io_data_t {
public:
void print() const override;
2013-01-15 15:37:33 +08:00
~io_fd_t() override;
/// fd to redirect specified fd to. For example, in 2>&1, source_fd is 1, and io_data_t::fd
/// is 2.
io_fd_t(int f, int source_fd) : io_data_t(io_mode_t::fd, f, source_fd) {}
2013-01-15 15:37:33 +08:00
};
2013-01-15 16:18:03 +08:00
/// Represents a redirection to or from an opened file.
2020-12-19 20:06:36 -08:00
class io_file_t final : public io_data_t {
public:
void print() const override;
2013-01-15 16:18:03 +08:00
io_file_t(int fd, autoclose_fd_t file)
: io_data_t(io_mode_t::file, fd, file.fd()), file_fd_(std::move(file)) {
// Invalid file redirections are replaced with a closed fd, so the following
// assertion isn't guaranteed to pass:
// assert(file_fd_.valid() && "File is not valid");
}
~io_file_t() override;
private:
// The fd for the file which we are writing to or reading from.
autoclose_fd_t file_fd_;
2013-01-15 16:18:03 +08:00
};
/// Represents (one end) of a pipe.
2020-12-19 20:06:36 -08:00
class io_pipe_t final : public io_data_t {
// The pipe's fd. Conceptually this is dup2'd to io_data_t::fd.
autoclose_fd_t pipe_fd_;
/// Whether this is an input pipe. This is used only for informational purposes.
const bool is_input_;
public:
void print() const override;
io_pipe_t(int fd, bool is_input, autoclose_fd_t pipe_fd)
: io_data_t(io_mode_t::pipe, fd, pipe_fd.fd()),
pipe_fd_(std::move(pipe_fd)),
is_input_(is_input) {
assert(pipe_fd_.valid() && "Pipe is not valid");
}
~io_pipe_t() override;
};
class io_buffer_t;
/// Represents filling an io_buffer_t. Very similar to io_pipe_t.
2020-12-19 20:06:36 -08:00
class io_bufferfill_t final : public io_data_t {
/// Write end. The other end is connected to an io_buffer_t.
const autoclose_fd_t write_fd_;
/// The receiving buffer.
const std::shared_ptr<io_buffer_t> buffer_;
public:
void print() const override;
2019-02-01 01:58:06 -08:00
// The ctor is public to support make_shared() in the static create function below.
// Do not invoke this directly.
io_bufferfill_t(int target, autoclose_fd_t write_fd, std::shared_ptr<io_buffer_t> buffer)
: io_data_t(io_mode_t::bufferfill, target, write_fd.fd()),
write_fd_(std::move(write_fd)),
buffer_(std::move(buffer)) {
assert(write_fd_.valid() && "fd is not valid");
}
~io_bufferfill_t() override;
std::shared_ptr<io_buffer_t> buffer() const { return buffer_; }
2019-02-01 01:58:06 -08:00
/// Create an io_bufferfill_t which, when written from, fills a buffer with the contents.
/// \returns nullptr on failure, e.g. too many open fds.
///
/// \param target the fd which this will be dup2'd to - typically stdout.
static shared_ptr<io_bufferfill_t> create(size_t buffer_limit = 0, int target = STDOUT_FILENO);
2019-02-01 01:58:06 -08:00
/// Reset the receiver (possibly closing the write end of the pipe), and complete the fillthread
/// of the buffer. \return the buffer.
static separated_buffer_t finish(std::shared_ptr<io_bufferfill_t> &&filler);
};
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-17 19:21:44 -06:00
struct callback_args_t;
struct autoclose_fd_t2;
using io_data_ref_t = std::shared_ptr<const io_data_t>;
using io_chain_t = IoChain;
2023-02-04 11:21:42 +01:00
dup2_list_t dup2_list_resolve_chain_shim(const io_chain_t &io_chain);
#endif