1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! This file supports specifying and applying redirections.
use crate::io::IoChain;
use crate::prelude::*;
use crate::wutil::fish_wcstoi;
use nix::fcntl::OFlag;
use std::os::fd::RawFd;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum RedirectionMode {
Overwrite, // normal redirection: > file.txt
Append, // appending redirection: >> file.txt
Input, // input redirection: < file.txt
TryInput, // try-input redirection: <? file.txt
Fd, // fd redirection: 2>&1
NoClob, // noclobber redirection: >? file.txt
}
/// 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)]
pub struct Dup2Action {
pub src: i32,
pub target: i32,
}
/// A class representing a sequence of basic redirections.
#[derive(Default)]
pub struct Dup2List {
/// The list of actions.
pub actions: Vec<Dup2Action>,
}
impl RedirectionMode {
/// The open flags for this redirection mode.
pub fn oflags(self) -> Option<OFlag> {
match self {
RedirectionMode::Append => Some(OFlag::O_CREAT | OFlag::O_APPEND | OFlag::O_WRONLY),
RedirectionMode::Overwrite => Some(OFlag::O_CREAT | OFlag::O_WRONLY | OFlag::O_TRUNC),
RedirectionMode::NoClob => Some(OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY),
RedirectionMode::Input | RedirectionMode::TryInput => Some(OFlag::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.
pub fd: RawFd,
/// The redirection mode.
pub mode: RedirectionMode,
/// The target of the redirection.
/// For example in "3>&1", this will be "1".
/// In "< file.txt" this will be "file.txt".
pub target: WString,
}
impl RedirectionSpec {
pub fn new(fd: RawFd, mode: RedirectionMode, target: WString) -> Self {
Self { fd, mode, target }
}
/// Return if this is a close-type redirection.
pub fn is_close(&self) -> bool {
self.mode == RedirectionMode::Fd && self.target == "-"
}
/// Attempt to parse target as an fd.
pub fn get_target_as_fd(&self) -> Option<RawFd> {
fish_wcstoi(&self.target).ok()
}
/// Return the open flags for this redirection.
pub fn oflags(&self) -> OFlag {
match self.mode.oflags() {
Some(flags) => flags,
None => panic!("Not a file redirection"),
}
}
}
pub type RedirectionSpecList = Vec<RedirectionSpec>;
/// 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.
pub fn dup2_list_resolve_chain(io_chain: &IoChain) -> Dup2List {
let mut result = Dup2List { actions: vec![] };
for io in &io_chain.0 {
if io.source_fd() < 0 {
result.add_close(io.fd());
} else {
result.add_dup2(io.source_fd(), io.fd());
}
}
result
}
impl Dup2List {
pub fn new() -> Self {
Default::default()
}
/// Return the list of dup2 actions.
pub fn get_actions(&self) -> &[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.
pub 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.
pub 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.
pub fn add_close(&mut self, fd: RawFd) {
assert!(fd >= 0, "Invalid fd in add_close");
self.actions.push(Dup2Action {
src: fd,
target: -1,
});
}
}
#[cfg(test)]
mod tests {
use crate::io::{IoChain, IoClose, IoFd};
use crate::redirection::dup2_list_resolve_chain;
use std::sync::Arc;
#[test]
fn test_dup2s() {
let mut chain = IoChain::new();
chain.push(Arc::new(IoClose::new(17)));
chain.push(Arc::new(IoFd::new(3, 19)));
let list = dup2_list_resolve_chain(&chain);
assert_eq!(list.get_actions().len(), 2);
let act1 = list.get_actions()[0];
assert_eq!(act1.src, 17);
assert_eq!(act1.target, -1);
let act2 = list.get_actions()[1];
assert_eq!(act2.src, 19);
assert_eq!(act2.target, 3);
}
#[test]
fn test_dup2s_fd_for_target_fd() {
let mut chain = IoChain::new();
// note io_fd_t params are backwards from dup2.
chain.push(Arc::new(IoClose::new(10)));
chain.push(Arc::new(IoFd::new(9, 10)));
chain.push(Arc::new(IoFd::new(5, 8)));
chain.push(Arc::new(IoFd::new(1, 4)));
chain.push(Arc::new(IoFd::new(3, 5)));
let list = dup2_list_resolve_chain(&chain);
assert_eq!(list.fd_for_target_fd(3), 8);
assert_eq!(list.fd_for_target_fd(5), 8);
assert_eq!(list.fd_for_target_fd(8), 8);
assert_eq!(list.fd_for_target_fd(1), 4);
assert_eq!(list.fd_for_target_fd(4), 4);
assert_eq!(list.fd_for_target_fd(100), 100);
assert_eq!(list.fd_for_target_fd(0), 0);
assert_eq!(list.fd_for_target_fd(-1), -1);
assert_eq!(list.fd_for_target_fd(9), -1);
assert_eq!(list.fd_for_target_fd(10), -1);
}
}