2023-02-04 18:21:42 +08:00
|
|
|
//! This file supports specifying and applying redirections.
|
|
|
|
|
2023-03-05 16:24:44 +08:00
|
|
|
use crate::ffi::wcharz_t;
|
|
|
|
use crate::wchar::{WString, L};
|
|
|
|
use crate::wchar_ffi::WCharToFFI;
|
2023-02-04 18:21:42 +08:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|