diff --git a/CMakeLists.txt b/CMakeLists.txt index b99e9bd20..61b23e689 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/fish-rust/build.rs b/fish-rust/build.rs index 9da547e52..7338b357b 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -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", diff --git a/fish-rust/src/lib.rs b/fish-rust/src/lib.rs index 59005e146..61457184c 100644 --- a/fish-rust/src/lib.rs +++ b/fish-rust/src/lib.rs @@ -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; diff --git a/fish-rust/src/redirection.rs b/fish-rust/src/redirection.rs new file mode 100644 index 000000000..06644868b --- /dev/null +++ b/fish-rust/src/redirection.rs @@ -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; + fn oflags(self: &RedirectionSpec) -> i32; + + fn fd(self: &RedirectionSpec) -> i32; + fn mode(self: &RedirectionSpec) -> RedirectionMode; + fn target(self: &RedirectionSpec) -> UniquePtr; + fn new_redirection_spec( + fd: i32, + mode: RedirectionMode, + target: wcharz_t, + ) -> Box; + + type RedirectionSpecList; + fn new_redirection_spec_list() -> Box; + fn size(self: &RedirectionSpecList) -> usize; + fn at(self: &RedirectionSpecList, offset: usize) -> *const RedirectionSpec; + fn push_back(self: &mut RedirectionSpecList, spec: Box); + fn clone(self: &RedirectionSpecList) -> Box; + } + + /// 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, + } + + extern "Rust" { + fn get_actions(self: &Dup2List) -> &Vec; + #[cxx_name = "dup2_list_resolve_chain"] + fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector) -> 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 { + 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 { + fish_wcstoi(self.target.as_char_slice().iter().copied()).ok() + } + fn get_target_as_fd_ffi(&self) -> SharedPtr { + 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 { + self.target.to_ffi() + } +} + +fn new_redirection_spec(fd: i32, mode: RedirectionMode, target: wcharz_t) -> Box { + Box::new(RedirectionSpec { + fd, + mode, + target: target.into(), + }) +} + +/// TODO This should be type alias once we drop the FFI. +pub struct RedirectionSpecList(Vec); + +fn new_redirection_spec_list() -> Box { + 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) { + self.0.push(*spec) + } + fn clone(self: &RedirectionSpecList) -> Box { + 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) -> 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) -> Dup2List { + dup2_list_resolve_chain(&io_chain.iter().cloned().collect()) +} + +impl Dup2List { + /// \return the list of dup2 actions. + fn get_actions(&self) -> &Vec { + &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, + }) + } +} diff --git a/src/exec.cpp b/src/exec.cpp index 7fae21c3d..a8b0ffd61 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -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 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; } diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 2fa7a1ea0..c69b095c9 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -3065,7 +3065,7 @@ static void test_dup2s() { io_chain_t chain; chain.push_back(make_shared(17)); chain.push_back(make_shared(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(5, 8)); chain.push_back(make_shared(1, 4)); chain.push_back(make_shared(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); diff --git a/src/io.cpp b/src/io.cpp index 9cc881ca7..f8cb64b17 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -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(spec.fd)); + if (spec->is_close()) { + this->push_back(make_unique(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(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(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(spec.fd)); + this->push_back(make_unique(spec->fd())); have_error = true; break; } - this->push_back(std::make_shared(spec.fd, std::move(file))); + this->push_back(std::make_shared(spec->fd(), std::move(file))); break; } } @@ -309,6 +309,15 @@ shared_ptr 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 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)) { diff --git a/src/io.h b/src/io.h index 205b91b56..6908e598f 100644 --- a/src/io.h +++ b/src/io.h @@ -346,6 +346,8 @@ class io_chain_t : public std::vector { 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 { diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 635d52c73..f89058cee 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -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 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)); } diff --git a/src/postfork.h b/src/postfork.h index 5c09065c1..3d952fa2d 100644 --- a/src/postfork.h +++ b/src/postfork.h @@ -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; diff --git a/src/proc.cpp b/src/proc.cpp index eb6310b10..c9d9dd318 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -251,7 +251,7 @@ static void handle_child_status(const shared_ptr &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(); diff --git a/src/proc.h b/src/proc.h index cc41b620d..1846d9ebd 100644 --- a/src/proc.h +++ b/src/proc.h @@ -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 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 proc_redirection_specs_; // The wait handle. This is constructed lazily, and cached. wait_handle_ref_t wait_handle_{}; diff --git a/src/redirection.cpp b/src/redirection.cpp deleted file mode 100644 index 1e884809d..000000000 --- a/src/redirection.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "config.h" // IWYU pragma: keep - -#include "redirection.h" - -#include -#include - -#include - -#include "io.h" -#include "wutil.h" - -dup2_list_t::~dup2_list_t() = default; - -maybe_t 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; -} diff --git a/src/redirection.h b/src/redirection.h index c00082960..3e4c7d703 100644 --- a/src/redirection.h +++ b/src/redirection.h @@ -1,101 +1,32 @@ #ifndef FISH_REDIRECTION_H #define FISH_REDIRECTION_H -#include -#include -#include +#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 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; - -/// 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 &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 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 diff --git a/src/wutil.h b/src/wutil.h index 20625b7fe..a90504bb7 100644 --- a/src/wutil.h +++ b/src/wutil.h @@ -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;