mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Port redirection.cpp to Rust
This commit is contained in:
parent
9ca160eac2
commit
25816627de
15 changed files with 331 additions and 210 deletions
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
239
fish-rust/src/redirection.rs
Normal file
239
fish-rust/src/redirection.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
11
src/exec.cpp
11
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<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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
39
src/io.cpp
39
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<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)) {
|
||||
|
|
2
src/io.h
2
src/io.h
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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_{};
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue