mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-11 07:34:32 +00:00
Introduce dup2_list_t
This represents a "resolved" io_chain_t, where all of the different io_data_t types have been reduced to a sequence of dup2() and close(). This will eliminate a lot of the logic duplication around posix_spawn vs fork, and pave the way for in-process redirections.
This commit is contained in:
parent
e3dcb01e67
commit
dbe906b79e
7 changed files with 190 additions and 8 deletions
|
@ -77,7 +77,7 @@ SET(FISH_SRCS
|
|||
src/postfork.cpp src/proc.cpp src/reader.cpp src/sanity.cpp src/screen.cpp
|
||||
src/signal.cpp src/tinyexpr.cpp src/tnode.cpp src/tokenizer.cpp src/utf8.cpp src/util.cpp
|
||||
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp src/wutil.cpp
|
||||
src/future_feature_flags.cpp
|
||||
src/future_feature_flags.cpp src/redirection.cpp
|
||||
)
|
||||
|
||||
# Header files are just globbed.
|
||||
|
|
|
@ -119,7 +119,7 @@ FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bg.o obj/builtin_bind.o ob
|
|||
obj/parser_keywords.o obj/path.o obj/postfork.o obj/proc.o obj/reader.o \
|
||||
obj/sanity.o obj/screen.o obj/signal.o obj/tinyexpr.o obj/tokenizer.o obj/tnode.o obj/utf8.o \
|
||||
obj/util.o obj/wcstringutil.o obj/wgetopt.o obj/wildcard.o obj/wutil.o \
|
||||
obj/future_feature_flags.o
|
||||
obj/future_feature_flags.o obj/redirection.o
|
||||
|
||||
FISH_INDENT_OBJS := obj/fish_indent.o obj/print_help.o $(FISH_OBJS)
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
#include "path.h"
|
||||
#include "proc.h"
|
||||
#include "reader.h"
|
||||
#include "redirection.h"
|
||||
#include "screen.h"
|
||||
#include "signal.h"
|
||||
#include "tnode.h"
|
||||
|
@ -2339,6 +2340,31 @@ static void test_wcstod() {
|
|||
tod_test(L"nope", "nope");
|
||||
}
|
||||
|
||||
static void test_dup2s() {
|
||||
using std::make_shared;
|
||||
io_chain_t chain;
|
||||
chain.push_back(make_shared<io_close_t>(17));
|
||||
chain.push_back(make_shared<io_fd_t>(3, 19, true));
|
||||
auto list = dup2_list_t::resolve_chain(chain);
|
||||
do_test(list.has_value());
|
||||
do_test(list->get_actions().size() == 2);
|
||||
|
||||
auto act1 = list->get_actions().at(0);
|
||||
do_test(act1.src == 17);
|
||||
do_test(act1.target == -1);
|
||||
|
||||
auto act2 = list->get_actions().at(1);
|
||||
do_test(act2.src == 19);
|
||||
do_test(act2.target == 3);
|
||||
|
||||
// Invalid files should fail to open.
|
||||
// Suppress the debug() message.
|
||||
scoped_push<int> saved_debug_level(&debug_level, -1);
|
||||
chain.push_back(make_shared<io_file_t>(2, L"/definitely/not/a/valid/path/for/this/test", 0666));
|
||||
list = dup2_list_t::resolve_chain(chain);
|
||||
do_test(!list.has_value());
|
||||
}
|
||||
|
||||
/// Testing colors.
|
||||
static void test_colors() {
|
||||
say(L"Testing colors");
|
||||
|
@ -5071,6 +5097,7 @@ int main(int argc, char **argv) {
|
|||
if (should_test_function("abbreviations")) test_abbreviations();
|
||||
if (should_test_function("test")) test_test();
|
||||
if (should_test_function("wcstod")) test_wcstod();
|
||||
if (should_test_function("dup2s")) test_dup2s();
|
||||
if (should_test_function("path")) test_path();
|
||||
if (should_test_function("pager_navigation")) test_pager_navigation();
|
||||
if (should_test_function("pager_layout")) test_pager_layout();
|
||||
|
|
|
@ -163,11 +163,8 @@ void io_print(const io_chain_t &chain)
|
|||
}
|
||||
#endif
|
||||
|
||||
/// If the given fd is used by the io chain, duplicates it repeatedly until an fd not used in the io
|
||||
/// chain is found, or we run out. If we return a new fd or an error, closes the old one. Any fd
|
||||
/// created is marked close-on-exec. Returns -1 on failure (in which case the given fd is still
|
||||
/// closed).
|
||||
static int move_fd_to_unused(int fd, const io_chain_t &io_chain) {
|
||||
|
||||
int move_fd_to_unused(int fd, const io_chain_t &io_chain, bool cloexec) {
|
||||
if (fd < 0 || io_chain.get_io_for_fd(fd).get() == NULL) {
|
||||
return fd;
|
||||
}
|
||||
|
@ -188,7 +185,7 @@ static int move_fd_to_unused(int fd, const io_chain_t &io_chain) {
|
|||
// Ok, we have a new candidate fd. Recurse. If we get a valid fd, either it's the same as
|
||||
// what we gave it, or it's a new fd and what we gave it has been closed. If we get a
|
||||
// negative value, the fd also has been closed.
|
||||
set_cloexec(tmp_fd);
|
||||
if (cloexec) set_cloexec(tmp_fd);
|
||||
new_fd = move_fd_to_unused(tmp_fd, io_chain);
|
||||
}
|
||||
|
||||
|
|
6
src/io.h
6
src/io.h
|
@ -295,6 +295,12 @@ shared_ptr<io_data_t> io_chain_get(io_chain_t &src, int fd);
|
|||
/// set to -1).
|
||||
bool pipe_avoid_conflicts_with_io_chain(int fds[2], const io_chain_t &ios);
|
||||
|
||||
/// If the given fd is used by the io chain, duplicates it repeatedly until an fd not used in the io
|
||||
/// chain is found, or we run out. If we return a new fd or an error, closes the old one.
|
||||
/// If \p cloexec is set, any fd created is marked close-on-exec.
|
||||
/// \returns -1 on failure (in which case the given fd is still closed).
|
||||
int move_fd_to_unused(int fd, const io_chain_t &io_chain, bool cloexec = true);
|
||||
|
||||
/// Class representing the output that a builtin can generate.
|
||||
class output_stream_t {
|
||||
private:
|
||||
|
|
90
src/redirection.cpp
Normal file
90
src/redirection.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include "redirection.h"
|
||||
#include "wutil.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
/// File descriptor redirection error message.
|
||||
#define FD_ERROR "An error occurred while redirecting file descriptor %s"
|
||||
|
||||
/// Pipe error message.
|
||||
#define LOCAL_PIPE_ERROR "An error occurred while setting up pipe"
|
||||
|
||||
#define NOCLOB_ERROR _(L"The file '%s' already exists")
|
||||
|
||||
#define FILE_ERROR _(L"An error occurred while redirecting file '%s'")
|
||||
|
||||
/// Base open mode to pass to calls to open.
|
||||
#define OPEN_MASK 0666
|
||||
|
||||
dup2_list_t::~dup2_list_t() = default;
|
||||
|
||||
maybe_t<dup2_list_t> dup2_list_t::resolve_chain(const io_chain_t &io_chain) {
|
||||
ASSERT_IS_NOT_FORKED_CHILD();
|
||||
dup2_list_t result;
|
||||
for (const auto &io_ref : io_chain) {
|
||||
switch (io_ref->io_mode) {
|
||||
case io_mode_t::file: {
|
||||
// Here we definitely do not want to set CLO_EXEC because our child needs access.
|
||||
// Open the file.
|
||||
const io_file_t *io_file = static_cast<const io_file_t *>(io_ref.get());
|
||||
int file_fd = open(io_file->filename_cstr, io_file->flags, OPEN_MASK);
|
||||
if (file_fd < 0) {
|
||||
if ((io_file->flags & O_EXCL) && (errno == EEXIST)) {
|
||||
debug(1, NOCLOB_ERROR, io_file->filename_cstr);
|
||||
} else {
|
||||
debug(1, FILE_ERROR, io_file->filename_cstr);
|
||||
if (should_debug(1)) wperror(L"open");
|
||||
}
|
||||
return none();
|
||||
}
|
||||
|
||||
// If by chance we got the file we want, we're done. Otherwise move the fd to an unused place and dup2 it.
|
||||
// Note move_fd_to_unused() will close the incoming file_fd.
|
||||
if (file_fd != io_file->fd) {
|
||||
file_fd = move_fd_to_unused(file_fd, io_chain, false /* cloexec */);
|
||||
if (file_fd < 0) {
|
||||
debug(1, FILE_ERROR, io_file->filename_cstr);
|
||||
if (should_debug(1)) wperror(L"dup");
|
||||
return none();
|
||||
}
|
||||
}
|
||||
|
||||
// Record that we opened this file, so we will auto-close it.
|
||||
assert(file_fd >= 0 && "Should have a valid file_fd");
|
||||
result.opened_fds_.emplace_back(file_fd);
|
||||
|
||||
// Mark our dup2 and our close actions.
|
||||
result.add_dup2(file_fd, io_file->fd);
|
||||
result.add_close(file_fd);
|
||||
break;
|
||||
}
|
||||
|
||||
case io_mode_t::close: {
|
||||
const io_close_t *io = static_cast<const io_close_t *>(io_ref.get());
|
||||
result.add_close(io->fd);
|
||||
break;
|
||||
}
|
||||
|
||||
case io_mode_t::fd: {
|
||||
const io_fd_t *io = static_cast<const io_fd_t *>(io_ref.get());
|
||||
result.add_dup2(io->old_fd, io->fd);
|
||||
break;
|
||||
}
|
||||
|
||||
case io_mode_t::buffer:
|
||||
case io_mode_t::pipe: {
|
||||
const io_pipe_t *io = static_cast<const io_pipe_t *>(io_ref.get());
|
||||
// If write_pipe_idx is 0, it means we're connecting to the read end (first pipe
|
||||
// fd). If it's 1, we're connecting to the write end (second pipe fd).
|
||||
unsigned int write_pipe_idx = (io->is_input ? 0 : 1);
|
||||
result.add_dup2(io->pipe_fd[write_pipe_idx], io->fd);
|
||||
if (io->pipe_fd[0] >= 0) result.add_close(io->pipe_fd[0]);
|
||||
if (io->pipe_fd[1] >= 0) result.add_close(io->pipe_fd[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {std::move(result)};
|
||||
}
|
62
src/redirection.h
Normal file
62
src/redirection.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef FISH_REDIRECTION_H
|
||||
#define FISH_REDIRECTION_H
|
||||
|
||||
#include "common.h"
|
||||
#include "maybe.h"
|
||||
#include "io.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
/// This file supports "applying" redirections.
|
||||
|
||||
/// A class representing a sequence of basic redirections.
|
||||
class dup2_list_t {
|
||||
/// A type that represents the action dup2(src, target).
|
||||
/// If target is negative, this represents close(src).
|
||||
/// Note none of the fds here are considered 'owned'.
|
||||
struct action_t {
|
||||
int src;
|
||||
int target;
|
||||
};
|
||||
|
||||
/// The list of actions.
|
||||
std::vector<action_t> actions_;
|
||||
|
||||
/// The list of fds that we opened, and are responsible for closing.
|
||||
std::vector<autoclose_fd_t> opened_fds_;
|
||||
|
||||
/// Append a dup2 action.
|
||||
void add_dup2(int src, int target) {
|
||||
assert(src >= 0 && target >= 0 && "Invalid fd in add_dup2");
|
||||
if (src != target) {
|
||||
actions_.push_back(action_t{src, target});
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a close action.
|
||||
void add_close(int fd) {
|
||||
assert(fd >= 0 && "Invalid fd in add_close");
|
||||
actions_.push_back(action_t{fd, -1});
|
||||
}
|
||||
|
||||
dup2_list_t() = default;
|
||||
|
||||
public:
|
||||
~dup2_list_t();
|
||||
|
||||
/// Disable copying because we own our fds.
|
||||
dup2_list_t(const dup2_list_t &) = delete;
|
||||
void operator=(const dup2_list_t &) = delete;
|
||||
|
||||
dup2_list_t(dup2_list_t &&) = default;
|
||||
dup2_list_t &operator=(dup2_list_t &&) = default;
|
||||
|
||||
/// \return the list of dup2 actions.
|
||||
const std::vector<action_t> &get_actions() const { return actions_; }
|
||||
|
||||
/// Produce a dup_fd_list_t from an io_chain. This may not be called before fork().
|
||||
/// The result contains the list of fd actions (dup2 and close), as well as the list of fds opened.
|
||||
static maybe_t<dup2_list_t> resolve_chain(const io_chain_t &);
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue