1use super::{Context, Input, Result};
20use crate::Env;
21use crate::io::Fd;
22use crate::option::{IgnoreEof as IgnoreEofOption, Interactive, Off};
23use crate::system::{Fcntl, Isatty, Write};
24use std::cell::RefCell;
25
26#[derive(Debug)]
45pub struct IgnoreEof<'a, 'b, S, T> {
46 inner: T,
48 fd: Fd,
50 env: &'a RefCell<&'b mut Env<S>>,
52 message: String,
54}
55
56impl<'a, 'b, S, T> IgnoreEof<'a, 'b, S, T> {
57 pub fn new(inner: T, fd: Fd, env: &'a RefCell<&'b mut Env<S>>, message: String) -> Self {
72 Self {
73 inner,
74 fd,
75 env,
76 message,
77 }
78 }
79}
80
81impl<S, T: Clone> Clone for IgnoreEof<'_, '_, S, T> {
83 fn clone(&self) -> Self {
84 Self {
85 inner: self.inner.clone(),
86 fd: self.fd,
87 env: self.env,
88 message: self.message.clone(),
89 }
90 }
91}
92
93impl<S: Fcntl + Isatty + Write, T: Input> Input for IgnoreEof<'_, '_, S, T> {
94 #[allow(clippy::await_holding_refcell_ref)]
95 async fn next_line(&mut self, context: &Context) -> Result {
96 let mut remaining_tries = 50;
97
98 loop {
99 let line = self.inner.next_line(context).await?;
100
101 let env = self.env.borrow();
102
103 let should_break = !line.is_empty()
104 || env.options.get(Interactive) == Off
105 || env.options.get(IgnoreEofOption) == Off
106 || remaining_tries == 0
107 || !env.system.isatty(self.fd);
108 if should_break {
109 return Ok(line);
110 }
111
112 env.system.print_error(&self.message).await;
113 remaining_tries -= 1;
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::super::Memory;
121 use super::*;
122 use crate::option::On;
123 use crate::system::Mode;
124 use crate::system::r#virtual::{FdBody, FileBody, Inode, OpenFileDescription, VirtualSystem};
125 use crate::test_helper::assert_stderr;
126 use enumset::EnumSet;
127 use futures_util::FutureExt as _;
128 use std::rc::Rc;
129
130 struct EofStub<T> {
133 inner: T,
134 count: usize,
135 }
136
137 impl<T> Input for EofStub<T>
138 where
139 T: Input,
140 {
141 async fn next_line(&mut self, context: &Context) -> Result {
142 if let Some(remaining) = self.count.checked_sub(1) {
143 self.count = remaining;
144 Ok("".to_string())
145 } else {
146 self.inner.next_line(context).await
147 }
148 }
149 }
150
151 fn set_stdin_to_tty(system: &mut VirtualSystem) {
152 system
153 .current_process_mut()
154 .set_fd(
155 Fd::STDIN,
156 FdBody {
157 open_file_description: Rc::new(RefCell::new(OpenFileDescription::new(
158 Rc::new(RefCell::new(Inode {
159 body: FileBody::Terminal { content: vec![] },
160 permissions: Mode::empty(),
161 })),
162 0,
163 true,
164 true,
165 false,
166 false,
167 ))),
168 flags: EnumSet::empty(),
169 },
170 )
171 .unwrap();
172 }
173
174 fn set_stdin_to_regular_file(system: &mut VirtualSystem) {
175 system
176 .current_process_mut()
177 .set_fd(
178 Fd::STDIN,
179 FdBody {
180 open_file_description: Rc::new(RefCell::new(OpenFileDescription::new(
181 Rc::new(RefCell::new(Inode {
182 body: FileBody::Regular {
183 content: vec![],
184 is_native_executable: false,
185 },
186 permissions: Mode::empty(),
187 })),
188 0,
189 true,
190 true,
191 false,
192 false,
193 ))),
194 flags: EnumSet::empty(),
195 },
196 )
197 .unwrap();
198 }
199
200 #[test]
201 fn decorator_reads_from_inner_input() {
202 let mut system = VirtualSystem::new();
203 set_stdin_to_tty(&mut system);
204 let mut env = Env::with_system(system);
205 env.options.set(Interactive, On);
206 env.options.set(IgnoreEofOption, On);
207 let ref_env = RefCell::new(&mut env);
208 let mut decorator = IgnoreEof::new(
209 Memory::new("echo foo\n"),
210 Fd::STDIN,
211 &ref_env,
212 "unused".to_string(),
213 );
214
215 let result = decorator
216 .next_line(&Context::default())
217 .now_or_never()
218 .unwrap();
219 assert_eq!(result.unwrap(), "echo foo\n");
220 }
221
222 #[test]
223 fn decorator_reads_input_again_on_eof() {
224 let mut system = VirtualSystem::new();
225 set_stdin_to_tty(&mut system);
226 let state = system.state.clone();
227 let mut env = Env::with_system(system);
228 env.options.set(Interactive, On);
229 env.options.set(IgnoreEofOption, On);
230 let ref_env = RefCell::new(&mut env);
231 let mut decorator = IgnoreEof::new(
232 EofStub {
233 inner: Memory::new("echo foo\n"),
234 count: 1,
235 },
236 Fd::STDIN,
237 &ref_env,
238 "EOF ignored\n".to_string(),
239 );
240
241 let result = decorator
242 .next_line(&Context::default())
243 .now_or_never()
244 .unwrap();
245 assert_eq!(result.unwrap(), "echo foo\n");
246 assert_stderr(&state, |stderr| assert_eq!(stderr, "EOF ignored\n"));
247 }
248
249 #[test]
250 fn decorator_reads_input_up_to_50_times() {
251 let mut system = VirtualSystem::new();
252 set_stdin_to_tty(&mut system);
253 let state = system.state.clone();
254 let mut env = Env::with_system(system);
255 env.options.set(Interactive, On);
256 env.options.set(IgnoreEofOption, On);
257 let ref_env = RefCell::new(&mut env);
258 let mut decorator = IgnoreEof::new(
259 EofStub {
260 inner: Memory::new("echo foo\n"),
261 count: 50,
262 },
263 Fd::STDIN,
264 &ref_env,
265 "EOF ignored\n".to_string(),
266 );
267
268 let result = decorator
269 .next_line(&Context::default())
270 .now_or_never()
271 .unwrap();
272 assert_eq!(result.unwrap(), "echo foo\n");
273 assert_stderr(&state, |stderr| {
274 assert_eq!(stderr, "EOF ignored\n".repeat(50))
275 });
276 }
277
278 #[test]
279 fn decorator_returns_empty_line_after_reading_51_times() {
280 let mut system = VirtualSystem::new();
281 set_stdin_to_tty(&mut system);
282 let state = system.state.clone();
283 let mut env = Env::with_system(system);
284 env.options.set(Interactive, On);
285 env.options.set(IgnoreEofOption, On);
286 let ref_env = RefCell::new(&mut env);
287 let mut decorator = IgnoreEof::new(
288 EofStub {
289 inner: Memory::new("echo foo\n"),
290 count: 51,
291 },
292 Fd::STDIN,
293 &ref_env,
294 "EOF ignored\n".to_string(),
295 );
296
297 let result = decorator
298 .next_line(&Context::default())
299 .now_or_never()
300 .unwrap();
301 assert_eq!(result.unwrap(), "");
302 assert_stderr(&state, |stderr| {
303 assert_eq!(stderr, "EOF ignored\n".repeat(50))
304 });
305 }
306
307 #[test]
308 fn decorator_returns_immediately_if_not_interactive() {
309 let mut system = VirtualSystem::new();
310 set_stdin_to_tty(&mut system);
311 let state = system.state.clone();
312 let mut env = Env::with_system(system);
313 env.options.set(Interactive, Off);
314 env.options.set(IgnoreEofOption, On);
315 let ref_env = RefCell::new(&mut env);
316 let mut decorator = IgnoreEof::new(
317 EofStub {
318 inner: Memory::new("echo foo\n"),
319 count: 1,
320 },
321 Fd::STDIN,
322 &ref_env,
323 "EOF ignored\n".to_string(),
324 );
325
326 let result = decorator
327 .next_line(&Context::default())
328 .now_or_never()
329 .unwrap();
330 assert_eq!(result.unwrap(), "");
331 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
332 }
333
334 #[test]
335 fn decorator_returns_immediately_if_not_ignore_eof() {
336 let mut system = VirtualSystem::new();
337 set_stdin_to_tty(&mut system);
338 let state = system.state.clone();
339 let mut env = Env::with_system(system);
340 env.options.set(Interactive, On);
341 env.options.set(IgnoreEofOption, Off);
342 let ref_env = RefCell::new(&mut env);
343 let mut decorator = IgnoreEof::new(
344 EofStub {
345 inner: Memory::new("echo foo\n"),
346 count: 1,
347 },
348 Fd::STDIN,
349 &ref_env,
350 "EOF ignored\n".to_string(),
351 );
352
353 let result = decorator
354 .next_line(&Context::default())
355 .now_or_never()
356 .unwrap();
357 assert_eq!(result.unwrap(), "");
358 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
359 }
360
361 #[test]
362 fn decorator_returns_immediately_if_not_terminal() {
363 let mut system = VirtualSystem::new();
364 set_stdin_to_regular_file(&mut system);
365 let state = system.state.clone();
366 let mut env = Env::with_system(system);
367 env.options.set(Interactive, On);
368 env.options.set(IgnoreEofOption, On);
369 let ref_env = RefCell::new(&mut env);
370 let mut decorator = IgnoreEof::new(
371 EofStub {
372 inner: Memory::new("echo foo\n"),
373 count: 1,
374 },
375 Fd::STDIN,
376 &ref_env,
377 "EOF ignored\n".to_string(),
378 );
379
380 let result = decorator
381 .next_line(&Context::default())
382 .now_or_never()
383 .unwrap();
384 assert_eq!(result.unwrap(), "");
385 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
386 }
387}