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
use std::io::Write;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use crate::ast::*;
use crate::error::ShellError;
use crate::eval::Shell;
use crate::sys;
/// Saved file descriptor for restoration after redirections.
pub(crate) struct SavedFd {
pub(crate) target_fd: RawFd,
pub(crate) saved_copy: Option<RawFd>,
}
impl Shell {
/// Apply redirections. Returns saved FD state for restoration.
pub(crate) fn setup_redirections(
&mut self,
redirs: &[Redir],
) -> crate::error::Result<Vec<SavedFd>> {
let mut saved = Vec::new();
for redir in redirs {
let target_fd = redir.fd;
// Save the current fd
let saved_copy = {
let copy = sys::fcntl_dupfd_cloexec(target_fd, 10);
if copy >= 0 { Some(copy) } else { None }
};
saved.push(SavedFd {
target_fd,
saved_copy,
});
match &redir.kind {
RedirKind::Input(word) => {
let filename = self.expand_string(word)?;
let filepath = self.resolve_path(&filename);
let file = std::fs::File::open(&filepath).map_err(|e| {
self.err_msg(&format!("{filename}: {e}"));
ShellError::Io(e)
})?;
// SAFETY: file fd is valid from File::open; target_fd is the redirect target.
unsafe {
sys::dup2(file.as_raw_fd(), target_fd);
}
}
RedirKind::Output(word) | RedirKind::Clobber(word) => {
let filename = self.expand_string(word)?;
let filepath = self.resolve_path(&filename);
let file = std::fs::File::create(&filepath).map_err(|e| {
self.err_msg(&format!("{filename}: {e}"));
ShellError::Io(e)
})?;
// SAFETY: file fd is valid from File::create; target_fd is the redirect target.
unsafe {
sys::dup2(file.as_raw_fd(), target_fd);
}
}
RedirKind::Append(word) => {
let filename = self.expand_string(word)?;
let filepath = self.resolve_path(&filename);
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.append(true)
.open(&filepath)
.map_err(|e| {
self.err_msg(&format!("{filename}: {e}"));
ShellError::Io(e)
})?;
// SAFETY: file fd is valid from OpenOptions::open; target_fd is the redirect target.
unsafe {
sys::dup2(file.as_raw_fd(), target_fd);
}
}
RedirKind::ReadWrite(word) => {
let filename = self.expand_string(word)?;
let filepath = self.resolve_path(&filename);
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&filepath)
.map_err(|e| {
self.err_msg(&format!("{filename}: {e}"));
ShellError::Io(e)
})?;
// SAFETY: file fd is valid from OpenOptions::open; target_fd is the redirect target.
unsafe {
sys::dup2(file.as_raw_fd(), target_fd);
}
}
RedirKind::DupInput(word) | RedirKind::DupOutput(word) => {
let fd_str = self.expand_string(word)?;
if fd_str == "-" {
// SAFETY: target_fd is a valid fd number from the redirect syntax.
unsafe {
sys::close(target_fd);
}
} else if let Ok(source_fd) = fd_str.parse::<i32>() {
// SAFETY: source_fd is user-specified (may fail at OS level); target_fd is from redirect syntax.
unsafe {
sys::dup2(source_fd, target_fd);
}
} else {
return Err(ShellError::Runtime {
msg: format!("{fd_str}: bad file descriptor"),
span: redir.span,
});
}
}
RedirKind::HereDoc(body) | RedirKind::HereDocStrip(body) => {
let mut fds = [0i32; 2];
// SAFETY: fds is a valid 2-element array for pipe() to write into.
unsafe {
sys::pipe(fds.as_mut_ptr());
}
// SAFETY: fds[1] is a valid fd just returned by pipe().
let write_end = unsafe { std::fs::File::from_raw_fd(fds[1]) };
let read_fd = fds[0];
let expanded = match body {
HereDocBody::Literal(s) => s.clone(),
HereDocBody::Parsed(parts) => {
let word = Word { parts: parts.clone(), span: redir.span };
self.expand_string(&word)?
}
};
let _ = (&write_end).write_all(&crate::encoding::str_to_bytes(&expanded));
drop(write_end);
// SAFETY: read_fd is valid from pipe(); target_fd is the redirect target.
unsafe {
sys::dup2(read_fd, target_fd);
sys::close(read_fd);
}
}
}
}
Ok(saved)
}
/// Restore file descriptors after redirections.
pub(crate) fn restore_redirections(&self, saved: Vec<SavedFd>) {
for s in saved.into_iter().rev() {
if let Some(copy) = s.saved_copy {
// SAFETY: copy is a valid fd from fcntl_dupfd_cloexec; target_fd is the original fd being restored.
unsafe {
sys::dup2(copy, s.target_fd);
sys::close(copy);
}
}
}
}
}