Port redirection.cpp to Rust

This commit is contained in:
Johannes Altmanninger 2023-02-04 11:21:42 +01:00
parent 9ca160eac2
commit 25816627de
15 changed files with 331 additions and 210 deletions

View File

@ -124,7 +124,7 @@ set(FISH_SRCS
src/null_terminated_array.cpp src/operation_context.cpp src/output.cpp
src/pager.cpp src/parse_execution.cpp src/parse_tree.cpp src/parse_util.cpp
src/parser.cpp src/parser_keywords.cpp src/path.cpp src/postfork.cpp
src/proc.cpp src/re.cpp src/reader.cpp src/redirection.cpp src/screen.cpp
src/proc.cpp src/re.cpp src/reader.cpp src/screen.cpp
src/signals.cpp src/termsize.cpp src/timer.cpp src/tinyexpr.cpp
src/tokenizer.cpp src/trace.cpp src/utf8.cpp
src/wait_handle.cpp src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp

View File

@ -23,6 +23,7 @@ fn main() -> miette::Result<()> {
"src/ffi_init.rs",
"src/ffi_tests.rs",
"src/future_feature_flags.rs",
"src/redirection.rs",
"src/smoke.rs",
"src/topic_monitor.rs",
"src/util.rs",

View File

@ -15,6 +15,7 @@ mod ffi_init;
mod ffi_tests;
mod flog;
mod future_feature_flags;
mod redirection;
mod signal;
mod smoke;
mod topic_monitor;

View File

@ -0,0 +1,239 @@
//! This file supports specifying and applying redirections.
use crate::wchar::L;
use crate::wchar_ffi::{wcharz_t, WCharToFFI, WString};
use crate::wutil::fish_wcstoi;
use cxx::{CxxVector, CxxWString, SharedPtr, UniquePtr};
use libc::{c_int, O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, O_WRONLY};
use std::os::fd::RawFd;
#[cxx::bridge]
mod redirection_ffi {
extern "C++" {
include!("wutil.h");
type wcharz_t = super::wcharz_t;
}
enum RedirectionMode {
overwrite, // normal redirection: > file.txt
append, // appending redirection: >> file.txt
input, // input redirection: < file.txt
fd, // fd redirection: 2>&1
noclob, // noclobber redirection: >? file.txt
}
extern "Rust" {
type RedirectionSpec;
fn is_close(self: &RedirectionSpec) -> bool;
#[cxx_name = "get_target_as_fd"]
fn get_target_as_fd_ffi(self: &RedirectionSpec) -> SharedPtr<i32>;
fn oflags(self: &RedirectionSpec) -> i32;
fn fd(self: &RedirectionSpec) -> i32;
fn mode(self: &RedirectionSpec) -> RedirectionMode;
fn target(self: &RedirectionSpec) -> UniquePtr<CxxWString>;
fn new_redirection_spec(
fd: i32,
mode: RedirectionMode,
target: wcharz_t,
) -> Box<RedirectionSpec>;
type RedirectionSpecList;
fn new_redirection_spec_list() -> Box<RedirectionSpecList>;
fn size(self: &RedirectionSpecList) -> usize;
fn at(self: &RedirectionSpecList, offset: usize) -> *const RedirectionSpec;
fn push_back(self: &mut RedirectionSpecList, spec: Box<RedirectionSpec>);
fn clone(self: &RedirectionSpecList) -> Box<RedirectionSpecList>;
}
/// 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'.
#[derive(Clone, Copy)]
struct Dup2Action {
src: i32,
target: i32,
}
/// A class representing a sequence of basic redirections.
struct Dup2List {
/// The list of actions.
actions: Vec<Dup2Action>,
}
extern "Rust" {
fn get_actions(self: &Dup2List) -> &Vec<Dup2Action>;
#[cxx_name = "dup2_list_resolve_chain"]
fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector<Dup2Action>) -> Dup2List;
fn fd_for_target_fd(self: &Dup2List, target: i32) -> i32;
}
}
pub use redirection_ffi::{Dup2Action, Dup2List, RedirectionMode};
impl RedirectionMode {
/// The open flags for this redirection mode.
pub fn oflags(self) -> Option<c_int> {
match self {
RedirectionMode::append => Some(O_CREAT | O_APPEND | O_WRONLY),
RedirectionMode::overwrite => Some(O_CREAT | O_WRONLY | O_TRUNC),
RedirectionMode::noclob => Some(O_CREAT | O_EXCL | O_WRONLY),
RedirectionMode::input => Some(O_RDONLY),
_ => None,
}
}
}
/// A struct which represents a redirection specification from the user.
/// Here the file descriptors don't represent open files - it's purely textual.
#[derive(Clone)]
pub struct RedirectionSpec {
/// The redirected fd, or -1 on overflow.
/// In the common case of a pipe, this is 1 (STDOUT_FILENO).
/// For example, in the case of "3>&1" this will be 3.
fd: RawFd,
/// The redirection mode.
mode: RedirectionMode,
/// The target of the redirection.
/// For example in "3>&1", this will be "1".
/// In "< file.txt" this will be "file.txt".
target: WString,
}
impl RedirectionSpec {
/// \return if this is a close-type redirection.
pub fn is_close(&self) -> bool {
self.mode == RedirectionMode::fd && self.target == L!("-")
}
/// Attempt to parse target as an fd.
pub fn get_target_as_fd(&self) -> Option<RawFd> {
fish_wcstoi(self.target.as_char_slice().iter().copied()).ok()
}
fn get_target_as_fd_ffi(&self) -> SharedPtr<i32> {
match self.get_target_as_fd() {
Some(fd) => SharedPtr::new(fd),
None => SharedPtr::null(),
}
}
/// \return the open flags for this redirection.
pub fn oflags(&self) -> c_int {
match self.mode.oflags() {
Some(flags) => flags,
None => panic!("Not a file redirection"),
}
}
fn fd(&self) -> RawFd {
self.fd
}
fn mode(&self) -> RedirectionMode {
self.mode
}
fn target(&self) -> UniquePtr<CxxWString> {
self.target.to_ffi()
}
}
fn new_redirection_spec(fd: i32, mode: RedirectionMode, target: wcharz_t) -> Box<RedirectionSpec> {
Box::new(RedirectionSpec {
fd,
mode,
target: target.into(),
})
}
/// TODO This should be type alias once we drop the FFI.
pub struct RedirectionSpecList(Vec<RedirectionSpec>);
fn new_redirection_spec_list() -> Box<RedirectionSpecList> {
Box::new(RedirectionSpecList(Vec::new()))
}
impl RedirectionSpecList {
fn size(&self) -> usize {
self.0.len()
}
fn at(&self, offset: usize) -> *const RedirectionSpec {
&self.0[offset]
}
#[allow(clippy::boxed_local)]
fn push_back(self: &mut RedirectionSpecList, spec: Box<RedirectionSpec>) {
self.0.push(*spec)
}
fn clone(self: &RedirectionSpecList) -> Box<RedirectionSpecList> {
Box::new(RedirectionSpecList(self.0.clone()))
}
}
/// 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.
fn dup2_list_resolve_chain(io_chain: &Vec<Dup2Action>) -> Dup2List {
let mut result = Dup2List { actions: vec![] };
for io in io_chain {
if io.src < 0 {
result.add_close(io.target)
} else {
result.add_dup2(io.src, io.target)
}
}
result
}
fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector<Dup2Action>) -> Dup2List {
dup2_list_resolve_chain(&io_chain.iter().cloned().collect())
}
impl Dup2List {
/// \return the list of dup2 actions.
fn get_actions(&self) -> &Vec<Dup2Action> {
&self.actions
}
/// \return the fd ultimately dup'd to a target fd, or -1 if the target is closed.
/// For example, if target fd is 1, and we have a dup2 chain 5->3 and 3->1, then we will
/// return 5. If the target is not referenced in the chain, returns target.
fn fd_for_target_fd(&self, target: RawFd) -> RawFd {
// Paranoia.
if target < 0 {
return target;
}
// Note we can simply walk our action list backwards, looking for src -> target dups.
let mut cursor = target;
for action in self.actions.iter().rev() {
if action.target == cursor {
// cursor is replaced by action.src
cursor = action.src;
} else if action.src == cursor && action.target < 0 {
// cursor is closed.
cursor = -1;
break;
}
}
cursor
}
/// Append a dup2 action.
fn add_dup2(&mut self, src: RawFd, target: RawFd) {
assert!(src >= 0 && target >= 0, "Invalid fd in add_dup2");
// Note: record these even if src and target is the same.
// This is a note that we must clear the CLO_EXEC bit.
self.actions.push(Dup2Action { src, target });
}
/// Append a close action.
fn add_close(&mut self, fd: RawFd) {
assert!(fd >= 0, "Invalid fd in add_close");
self.actions.push(Dup2Action {
src: fd,
target: -1,
})
}
}

View File

@ -240,7 +240,7 @@ static void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &block_i
}
// child_setup_process makes sure signals are properly set up.
dup2_list_t redirs = dup2_list_t::resolve_chain(all_ios);
dup2_list_t redirs = dup2_list_resolve_chain_shim(all_ios);
if (child_setup_process(false /* not claim_tty */, *j, false /* not is_forked */, redirs) ==
0) {
// Decrement SHLVL as we're removing ourselves from the shell "stack".
@ -306,7 +306,7 @@ static void run_internal_process(process_t *p, std::string &&outdata, std::strin
// Note it's important we do this even if we have no out or err data, because we may have been
// asked to truncate a file (e.g. `echo -n '' > /tmp/truncateme.txt'). The open() in the dup2
// list resolution will ensure this happens.
f->dup2s = dup2_list_t::resolve_chain(ios);
f->dup2s = dup2_list_resolve_chain_shim(ios);
// Figure out which source fds to write to. If they are closed (unlikely) we just exit
// successfully.
@ -514,7 +514,7 @@ static launch_result_t exec_external_command(parser_t &parser, const std::shared
null_terminated_array_t<char> argv_array(narrow_argv);
// Convert our IO chain to a dup2 sequence.
auto dup2s = dup2_list_t::resolve_chain(proc_io_chain);
auto dup2s = dup2_list_resolve_chain_shim(proc_io_chain);
// Ensure that stdin is blocking before we hand it off (see issue #176).
// Note this will also affect stdout and stderr if they refer to the same tty.
@ -717,8 +717,9 @@ static proc_performer_t get_performer_for_builtin(
} else {
// We are not a pipe. Check if there is a redirection local to the process
// that's not io_mode_t::close.
for (const auto &redir : p->redirection_specs()) {
if (redir.fd == STDIN_FILENO && !redir.is_close()) {
for (size_t i = 0; i < p->redirection_specs().size(); i++) {
const auto *redir = p->redirection_specs().at(i);
if (redir->fd() == STDIN_FILENO && !redir->is_close()) {
stdin_is_directly_redirected = true;
break;
}

View File

@ -3065,7 +3065,7 @@ static void test_dup2s() {
io_chain_t chain;
chain.push_back(make_shared<io_close_t>(17));
chain.push_back(make_shared<io_fd_t>(3, 19));
auto list = dup2_list_t::resolve_chain(chain);
auto list = dup2_list_resolve_chain_shim(chain);
do_test(list.get_actions().size() == 2);
auto act1 = list.get_actions().at(0);
@ -3086,7 +3086,7 @@ static void test_dup2s_fd_for_target_fd() {
chain.push_back(make_shared<io_fd_t>(5, 8));
chain.push_back(make_shared<io_fd_t>(1, 4));
chain.push_back(make_shared<io_fd_t>(3, 5));
auto list = dup2_list_t::resolve_chain(chain);
auto list = dup2_list_resolve_chain_shim(chain);
do_test(list.fd_for_target_fd(3) == 8);
do_test(list.fd_for_target_fd(5) == 8);

View File

@ -214,37 +214,37 @@ bool io_chain_t::append(const io_chain_t &chain) {
bool io_chain_t::append_from_specs(const redirection_spec_list_t &specs, const wcstring &pwd) {
bool have_error = false;
for (const auto &spec : specs) {
switch (spec.mode) {
for (size_t i = 0; i < specs.size(); i++) {
const redirection_spec_t *spec = specs.at(i);
switch (spec->mode()) {
case redirection_mode_t::fd: {
if (spec.is_close()) {
this->push_back(make_unique<io_close_t>(spec.fd));
if (spec->is_close()) {
this->push_back(make_unique<io_close_t>(spec->fd()));
} else {
auto target_fd = spec.get_target_as_fd();
assert(target_fd.has_value() &&
"fd redirection should have been validated already");
this->push_back(make_unique<io_fd_t>(spec.fd, *target_fd));
auto target_fd = spec->get_target_as_fd();
assert(target_fd && "fd redirection should have been validated already");
this->push_back(make_unique<io_fd_t>(spec->fd(), *target_fd));
}
break;
}
default: {
// We have a path-based redireciton. Resolve it to a file.
// Mark it as CLO_EXEC because we don't want it to be open in any child.
wcstring path = path_apply_working_directory(spec.target, pwd);
int oflags = spec.oflags();
wcstring path = path_apply_working_directory(*spec->target(), pwd);
int oflags = spec->oflags();
autoclose_fd_t file{wopen_cloexec(path, oflags, OPEN_MASK)};
if (!file.valid()) {
if ((oflags & O_EXCL) && (errno == EEXIST)) {
FLOGF(warning, NOCLOB_ERROR, spec.target.c_str());
FLOGF(warning, NOCLOB_ERROR, spec->target()->c_str());
} else {
if (should_flog(warning)) {
FLOGF(warning, FILE_ERROR, spec.target.c_str());
FLOGF(warning, FILE_ERROR, spec->target()->c_str());
auto err = errno;
// If the error is that the file doesn't exist
// or there's a non-directory component,
// find the first problematic component for a better message.
if (err == ENOENT || err == ENOTDIR) {
auto dname = spec.target;
auto dname = *spec->target();
struct stat buf;
while (!dname.empty()) {
@ -269,11 +269,11 @@ bool io_chain_t::append_from_specs(const redirection_spec_list_t &specs, const w
// If opening a file fails, insert a closed FD instead of the file redirection
// and return false. This lets execution potentially recover and at least gives
// the shell a chance to gracefully regain control of the shell (see #7038).
this->push_back(make_unique<io_close_t>(spec.fd));
this->push_back(make_unique<io_close_t>(spec->fd()));
have_error = true;
break;
}
this->push_back(std::make_shared<io_file_t>(spec.fd, std::move(file)));
this->push_back(std::make_shared<io_file_t>(spec->fd(), std::move(file)));
break;
}
}
@ -309,6 +309,15 @@ shared_ptr<const io_data_t> io_chain_t::io_for_fd(int fd) const {
return nullptr;
}
dup2_list_t dup2_list_resolve_chain_shim(const io_chain_t &io_chain) {
ASSERT_IS_NOT_FORKED_CHILD();
std::vector<dup2_action_t> chain;
for (const auto &io_data : io_chain) {
chain.push_back(dup2_action_t{io_data->source_fd, io_data->fd});
}
return dup2_list_resolve_chain(chain);
}
bool output_stream_t::append_narrow_buffer(const separated_buffer_t &buffer) {
for (const auto &rhs_elem : buffer.elements()) {
if (!append_with_separation(str2wcstring(rhs_elem.contents), rhs_elem.separation, false)) {

View File

@ -346,6 +346,8 @@ class io_chain_t : public std::vector<io_data_ref_t> {
void print() const;
};
dup2_list_t dup2_list_resolve_chain_shim(const io_chain_t &io_chain);
/// Base class representing the output that a builtin can generate.
/// This has various subclasses depending on the ultimate output destination.
class output_stream_t : noncopyable_t, nonmovable_t {

View File

@ -112,9 +112,9 @@ static wcstring profiling_cmd_name_for_redirectable_block(const ast::node_t &nod
}
/// Get a redirection from stderr to stdout (i.e. 2>&1).
static redirection_spec_t get_stderr_merge() {
static rust::Box<redirection_spec_t> get_stderr_merge() {
const wchar_t *stdout_fileno_str = L"1";
return redirection_spec_t{STDERR_FILENO, redirection_mode_t::fd, stdout_fileno_str};
return new_redirection_spec(STDERR_FILENO, redirection_mode_t::fd, stdout_fileno_str);
}
parse_execution_context_t::parse_execution_context_t(parsed_source_ref_t pstree,
@ -450,7 +450,8 @@ end_execution_reason_t parse_execution_context_t::run_for_statement(
auto var = parser->vars().get(for_var_name, ENV_DEFAULT);
if (env_var_t::flags_for(for_var_name.c_str()) & env_var_t::flag_read_only) {
return report_error(STATUS_INVALID_ARGS, header.var_name,
_(L"%ls: %ls: cannot overwrite read-only variable"), L"for", for_var_name.c_str());
_(L"%ls: %ls: cannot overwrite read-only variable"), L"for",
for_var_name.c_str());
}
auto &vars = parser->vars();
@ -735,19 +736,20 @@ end_execution_reason_t parse_execution_context_t::handle_command_not_found(
// If the original command did not include a "/", assume we found it via $PATH.
auto src = get_source(statement.command);
if (src.find(L"/") == wcstring::npos) {
return this->report_error(
STATUS_NOT_EXECUTABLE, statement.command,
_(L"Unknown command. A component of '%ls' is not a directory. Check your $PATH."), cmd);
return this->report_error(STATUS_NOT_EXECUTABLE, statement.command,
_(L"Unknown command. A component of '%ls' is not a "
L"directory. Check your $PATH."),
cmd);
} else {
return this->report_error(
STATUS_NOT_EXECUTABLE, statement.command,
_(L"Unknown command. A component of '%ls' is not a directory."), cmd);
STATUS_NOT_EXECUTABLE, statement.command,
_(L"Unknown command. A component of '%ls' is not a directory."), cmd);
}
}
return this->report_error(
STATUS_NOT_EXECUTABLE, statement.command,
_(L"Unknown command. '%ls' exists but is not an executable file."), cmd);
STATUS_NOT_EXECUTABLE, statement.command,
_(L"Unknown command. '%ls' exists but is not an executable file."), cmd);
}
// Handle unrecognized commands with standard command not found handler that can make better
@ -770,7 +772,9 @@ end_execution_reason_t parse_execution_context_t::handle_command_not_found(
// Redirect to stderr
auto io = io_chain_t{};
io.append_from_specs({redirection_spec_t{STDOUT_FILENO, redirection_mode_t::fd, L"2"}}, L"");
auto list = new_redirection_spec_list();
list->push_back(new_redirection_spec(STDOUT_FILENO, redirection_mode_t::fd, L"2"));
io.append_from_specs(*list, L"");
if (function_exists(L"fish_command_not_found", *parser)) {
buffer = L"fish_command_not_found";
@ -890,7 +894,7 @@ end_execution_reason_t parse_execution_context_t::populate_plain_process(
// Produce the full argument list and the set of IO redirections.
wcstring_list_t cmd_args;
redirection_spec_list_t redirections;
auto redirections = new_redirection_spec_list();
if (use_implicit_cd) {
// Implicit cd is simple.
cmd_args = {L"cd", cmd};
@ -917,7 +921,7 @@ end_execution_reason_t parse_execution_context_t::populate_plain_process(
}
// The set of IO redirections that we construct for the process.
auto reason = this->determine_redirections(statement.args_or_redirs, &redirections);
auto reason = this->determine_redirections(statement.args_or_redirs, &*redirections);
if (reason != end_execution_reason_t::ok) {
return reason;
}
@ -1018,14 +1022,14 @@ end_execution_reason_t parse_execution_context_t::determine_redirections(
// Make a redirection spec from the redirect token.
assert(oper && oper->is_valid() && "expected to have a valid redirection");
redirection_spec_t spec{oper->fd, oper->mode, std::move(target)};
auto spec = new_redirection_spec(oper->fd, oper->mode, target.c_str());
// Validate this spec.
if (spec.mode == redirection_mode_t::fd && !spec.is_close() &&
!spec.get_target_as_fd().has_value()) {
if (spec->mode() == redirection_mode_t::fd && !spec->is_close() &&
!spec->get_target_as_fd()) {
const wchar_t *fmt =
_(L"Requested redirection to '%ls', which is not a valid file descriptor");
return report_error(STATUS_INVALID_ARGS, redir_node, fmt, spec.target.c_str());
return report_error(STATUS_INVALID_ARGS, redir_node, fmt, spec->target()->c_str());
}
out_redirections->push_back(std::move(spec));
@ -1077,8 +1081,8 @@ end_execution_reason_t parse_execution_context_t::populate_block_process(
}
assert(args_or_redirs && "Should have args_or_redirs");
redirection_spec_list_t redirections;
auto reason = this->determine_redirections(*args_or_redirs, &redirections);
auto redirections = new_redirection_spec_list();
auto reason = this->determine_redirections(*args_or_redirs, &*redirections);
if (reason == end_execution_reason_t::ok) {
proc->type = process_type_t::block_node;
proc->block_node_source = pstree;
@ -1207,8 +1211,8 @@ end_execution_reason_t parse_execution_context_t::populate_job_from_job_node(
if (parsed_pipe->stderr_merge) {
// This was a pipe like &| which redirects both stdout and stderr.
// Also redirect stderr to stdout.
auto specs = processes.back()->redirection_specs();
specs.push_back(get_stderr_merge());
auto specs = processes.back()->redirection_specs().clone();
specs->push_back(get_stderr_merge());
processes.back()->set_redirection_specs(std::move(specs));
}

View File

@ -15,7 +15,8 @@
#include "common.h"
#include "maybe.h"
class dup2_list_t;
class Dup2List;
using dup2_list_t = Dup2List;
class job_t;
class process_t;

View File

@ -251,7 +251,7 @@ static void handle_child_status(const shared_ptr<job_t> &job, process_t *proc,
}
}
process_t::process_t() = default;
process_t::process_t() : proc_redirection_specs_(new_redirection_spec_list()) {}
void process_t::check_generations_before_launch() {
gens_ = topic_monitor_principal().current_generations();

View File

@ -275,9 +275,9 @@ class process_t : noncopyable_t {
const wchar_t *argv0() const { return argv_.empty() ? nullptr : argv_.front().c_str(); }
/// Redirection list getter and setter.
const redirection_spec_list_t &redirection_specs() const { return proc_redirection_specs_; }
const redirection_spec_list_t &redirection_specs() const { return *proc_redirection_specs_; }
void set_redirection_specs(redirection_spec_list_t specs) {
void set_redirection_specs(rust::Box<redirection_spec_list_t> specs) {
this->proc_redirection_specs_ = std::move(specs);
}
@ -340,7 +340,7 @@ class process_t : noncopyable_t {
private:
wcstring_list_t argv_;
redirection_spec_list_t proc_redirection_specs_;
rust::Box<redirection_spec_list_t> proc_redirection_specs_;
// The wait handle. This is constructed lazily, and cached.
wait_handle_ref_t wait_handle_{};

View File

@ -1,69 +0,0 @@
#include "config.h" // IWYU pragma: keep
#include "redirection.h"
#include <errno.h>
#include <fcntl.h>
#include <memory>
#include "io.h"
#include "wutil.h"
dup2_list_t::~dup2_list_t() = default;
maybe_t<int> redirection_spec_t::get_target_as_fd() const {
errno = 0;
int result = fish_wcstoi(target.c_str());
if (errno || result < 0) return none();
return result;
}
int redirection_spec_t::oflags() const {
switch (mode) {
case redirection_mode_t::append:
return O_CREAT | O_APPEND | O_WRONLY;
case redirection_mode_t::overwrite:
return O_CREAT | O_WRONLY | O_TRUNC;
case redirection_mode_t::noclob:
return O_CREAT | O_EXCL | O_WRONLY;
case redirection_mode_t::input:
return O_RDONLY;
case redirection_mode_t::fd:
default:
DIE("Not a file redirection");
}
}
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 : io_chain) {
if (io->source_fd < 0) {
result.add_close(io->fd);
} else {
result.add_dup2(io->source_fd, io->fd);
}
}
return result;
}
int dup2_list_t::fd_for_target_fd(int target) const {
// Paranoia.
if (target < 0) {
return target;
}
// Note we can simply walk our action list backwards, looking for src -> target dups.
int cursor = target;
for (auto iter = actions_.rbegin(); iter != actions_.rend(); ++iter) {
if (iter->target == cursor) {
// cursor is replaced by iter->src
cursor = iter->src;
} else if (iter->src == cursor && iter->target < 0) {
// cursor is closed.
cursor = -1;
break;
}
}
return cursor;
}

View File

@ -1,101 +1,32 @@
#ifndef FISH_REDIRECTION_H
#define FISH_REDIRECTION_H
#include <string>
#include <utility>
#include <vector>
#if INCLUDE_RUST_HEADERS
#include "common.h"
#include "maybe.h"
#include "redirection.rs.h"
/// This file supports specifying and applying redirections.
#else
enum class redirection_mode_t {
overwrite, // normal redirection: > file.txt
append, // appending redirection: >> file.txt
input, // input redirection: < file.txt
fd, // fd redirection: 2>&1
noclob // noclobber redirection: >? file.txt
};
class io_chain_t;
/// A struct which represents a redirection specification from the user.
/// Here the file descriptors don't represent open files - it's purely textual.
struct redirection_spec_t {
/// The redirected fd, or -1 on overflow.
/// In the common case of a pipe, this is 1 (STDOUT_FILENO).
/// For example, in the case of "3>&1" this will be 3.
int fd{-1};
/// The redirection mode.
redirection_mode_t mode{redirection_mode_t::overwrite};
/// The target of the redirection.
/// For example in "3>&1", this will be "1".
/// In "< file.txt" this will be "file.txt".
wcstring target{};
/// \return if this is a close-type redirection.
bool is_close() const { return mode == redirection_mode_t::fd && target == L"-"; }
/// Attempt to parse target as an fd. Return the fd, or none() if none.
maybe_t<int> get_target_as_fd() const;
/// \return the open flags for this redirection.
int oflags() const;
redirection_spec_t(int fd, redirection_mode_t mode, wcstring target)
: fd(fd), mode(mode), target(std::move(target)) {}
};
using redirection_spec_list_t = std::vector<redirection_spec_t>;
/// A class representing a sequence of basic redirections.
class dup2_list_t : noncopyable_t {
public:
/// 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;
};
dup2_list_t() = default;
dup2_list_t(dup2_list_t &&) = default;
dup2_list_t &operator=(dup2_list_t &&) = default;
~dup2_list_t();
/// \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 dup2_list_t resolve_chain(const io_chain_t &);
/// \return the fd ultimately dup'd to a target fd, or -1 if the target is closed.
/// For example, if target fd is 1, and we have a dup2 chain 5->3 and 3->1, then we will
/// return 5. If the target is not referenced in the chain, returns target.
int fd_for_target_fd(int target) const;
private:
/// The list of actions.
std::vector<action_t> actions_;
/// Append a dup2 action.
void add_dup2(int src, int target) {
assert(src >= 0 && target >= 0 && "Invalid fd in add_dup2");
// Note: record these even if src and target is the same.
// This is a note that we must clear the CLO_EXEC bit.
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});
}
// Hacks to allow us to compile without Rust headers.
enum class RedirectionMode {
overwrite,
append,
input,
fd,
noclob,
};
struct Dup2Action;
class Dup2List;
struct RedirectionSpec;
struct RedirectionSpecList;
#endif
using redirection_mode_t = RedirectionMode;
using redirection_spec_t = RedirectionSpec;
using redirection_spec_list_t = RedirectionSpecList;
using dup2_action_t = Dup2Action;
using dup2_list_t = Dup2List;
#endif

View File

@ -188,6 +188,7 @@ class dir_iter_t : noncopyable_t {
private:
/// Whether this dir_iter considers the "." and ".." filesystem entries.
bool withdot_{false};
public:
struct entry_t;