Skip to main content

yash_env/input/
fd_reader.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17// `FdReader` definition
18
19use super::{Context, Input, Result};
20use crate::io::Fd;
21use crate::option::State;
22#[allow(deprecated)]
23use crate::system::{Fcntl, Read, SharedSystem, Write};
24use std::cell::Cell;
25use std::rc::Rc;
26use std::slice::from_mut;
27
28/// Input function that reads from a file descriptor.
29///
30/// An instance of `FdReader` contains a [`SharedSystem`] to interact with the
31/// file descriptor.
32///
33/// Although `FdReader` implements `Clone`, it does not mean you can create and
34/// keep a copy of a `FdReader` instance to replay the input later. Since both
35/// the original and clone share the same `SharedSystem`, reading a line from
36/// one instance will affect the next read from the other.
37///
38/// Use [`FdReader2`](super::FdReader2) if you want an `Input` that depends on
39/// [`Concurrent`](crate::system::Concurrent) instead of `SharedSystem`.
40#[derive(Debug)]
41#[must_use = "FdReader does nothing unless used by a parser"]
42pub struct FdReader<S> {
43    /// File descriptor to read from
44    fd: Fd,
45    /// System to interact with the FD
46    #[allow(deprecated)]
47    system: SharedSystem<S>,
48    /// Whether lines read are echoed to stderr
49    echo: Option<Rc<Cell<State>>>,
50}
51
52impl<S> FdReader<S> {
53    /// Creates a new `FdReader` instance.
54    ///
55    /// The `fd` argument is the file descriptor to read from. It should be
56    /// readable, have the close-on-exec flag set, and remain open for the
57    /// lifetime of the `FdReader` instance.
58    #[allow(deprecated)]
59    pub fn new(fd: Fd, system: SharedSystem<S>) -> Self {
60        let echo = None;
61        FdReader { fd, system, echo }
62    }
63
64    /// Sets the "echo" flag.
65    ///
66    /// You can use this setter function to set a shared option state that
67    /// controls whether the input function echoes lines it reads to the
68    /// standard error. If `echo` is `None` or some shared cell containing
69    /// `Off`, the function does not echo. If a cell has `On`, the function
70    /// prints every line it reads to the standard error.
71    ///
72    /// This option implements the behavior of the `verbose` shell option. You
73    /// can change the state of the shared cell through the lifetime of the
74    /// input function to reflect the option dynamically changed, which will
75    /// affect the next `next_line` call.
76    ///
77    /// # Deprecation
78    ///
79    /// This function is deprecated in favor of the [`Echo`] struct.
80    ///
81    /// [`Echo`]: super::Echo
82    #[deprecated = "use Echo instead"]
83    pub fn set_echo(&mut self, echo: Option<Rc<Cell<State>>>) {
84        self.echo = echo;
85    }
86}
87
88// Not derived automatically because S may not implement Clone
89impl<S> Clone for FdReader<S> {
90    fn clone(&self) -> Self {
91        Self {
92            fd: self.fd,
93            system: self.system.clone(),
94            echo: self.echo.clone(),
95        }
96    }
97}
98
99impl<S: Fcntl + Read + Write> Input for FdReader<S> {
100    async fn next_line(&mut self, _context: &Context) -> Result {
101        // TODO Read many bytes at once if seekable
102
103        let mut bytes = Vec::new();
104        loop {
105            let mut byte = 0;
106            match self.system.read_async(self.fd, from_mut(&mut byte)).await {
107                // End of input
108                Ok(0) => break,
109
110                Ok(count) => {
111                    assert_eq!(count, 1);
112                    bytes.push(byte);
113                    if byte == b'\n' {
114                        break;
115                    }
116                }
117
118                Err(errno) => return Err(errno.into()),
119            }
120        }
121
122        // TODO Reject invalid UTF-8 sequence if strict POSIX mode is on
123        let line = String::from_utf8(bytes)
124            .unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into());
125
126        if let Some(echo) = &self.echo {
127            if echo.get() == State::On {
128                let _ = self.system.write_all(Fd::STDERR, line.as_bytes()).await;
129            }
130        }
131
132        Ok(line)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::system::Errno;
140    use crate::system::Mode;
141    use crate::system::OfdAccess;
142    use crate::system::Open as _;
143    use crate::system::OpenFlag;
144    use crate::system::r#virtual::FileBody;
145    use crate::system::r#virtual::Inode;
146    use crate::system::r#virtual::VirtualSystem;
147    use assert_matches::assert_matches;
148    use futures_util::FutureExt;
149
150    #[test]
151    fn empty_reader() {
152        let system = VirtualSystem::new();
153        #[allow(deprecated)]
154        let system = SharedSystem::new(system);
155        let mut reader = FdReader::new(Fd::STDIN, system);
156
157        let line = reader
158            .next_line(&Context::default())
159            .now_or_never()
160            .unwrap()
161            .unwrap();
162        assert_eq!(line, "");
163    }
164
165    #[test]
166    fn one_line_reader() {
167        let system = VirtualSystem::new();
168        {
169            let state = system.state.borrow_mut();
170            let file = state.file_system.get("/dev/stdin").unwrap();
171            file.borrow_mut().body = FileBody::new(*b"echo ok\n");
172        }
173        #[allow(deprecated)]
174        let system = SharedSystem::new(system);
175        let mut reader = FdReader::new(Fd::STDIN, system);
176
177        let line = reader
178            .next_line(&Context::default())
179            .now_or_never()
180            .unwrap()
181            .unwrap();
182        assert_eq!(line, "echo ok\n");
183        let line = reader
184            .next_line(&Context::default())
185            .now_or_never()
186            .unwrap()
187            .unwrap();
188        assert_eq!(line, "");
189    }
190
191    #[test]
192    fn reader_with_many_lines() {
193        let system = VirtualSystem::new();
194        {
195            let state = system.state.borrow_mut();
196            let file = state.file_system.get("/dev/stdin").unwrap();
197            file.borrow_mut().body = FileBody::new(*b"#!/bin/sh\necho ok\nexit");
198        }
199        #[allow(deprecated)]
200        let system = SharedSystem::new(system);
201        let mut reader = FdReader::new(Fd::STDIN, system);
202
203        let line = reader
204            .next_line(&Context::default())
205            .now_or_never()
206            .unwrap()
207            .unwrap();
208        assert_eq!(line, "#!/bin/sh\n");
209        let line = reader
210            .next_line(&Context::default())
211            .now_or_never()
212            .unwrap()
213            .unwrap();
214        assert_eq!(line, "echo ok\n");
215        let line = reader
216            .next_line(&Context::default())
217            .now_or_never()
218            .unwrap()
219            .unwrap();
220        assert_eq!(line, "exit");
221        let line = reader
222            .next_line(&Context::default())
223            .now_or_never()
224            .unwrap()
225            .unwrap();
226        assert_eq!(line, "");
227    }
228
229    #[test]
230    fn reading_from_file() {
231        let system = VirtualSystem::new();
232        {
233            let mut state = system.state.borrow_mut();
234            let file = Rc::new(Inode::new("echo file\n").into());
235            state.file_system.save("/foo", file).unwrap();
236        }
237        #[allow(deprecated)]
238        let system = SharedSystem::new(system);
239        let path = c"/foo";
240        let fd = system
241            .open(
242                path,
243                OfdAccess::ReadOnly,
244                OpenFlag::CloseOnExec.into(),
245                Mode::empty(),
246            )
247            .now_or_never()
248            .unwrap()
249            .unwrap();
250        let mut reader = FdReader::new(fd, system);
251
252        let line = reader
253            .next_line(&Context::default())
254            .now_or_never()
255            .unwrap()
256            .unwrap();
257        assert_eq!(line, "echo file\n");
258        let line = reader
259            .next_line(&Context::default())
260            .now_or_never()
261            .unwrap()
262            .unwrap();
263        assert_eq!(line, "");
264    }
265
266    #[test]
267    fn reader_error() {
268        let system = VirtualSystem::new();
269        system.current_process_mut().close_fd(Fd::STDIN);
270        #[allow(deprecated)]
271        let system = SharedSystem::new(system);
272        let mut reader = FdReader::new(Fd::STDIN, system);
273
274        let error = reader
275            .next_line(&Context::default())
276            .now_or_never()
277            .unwrap()
278            .unwrap_err();
279        assert_eq!(error.raw_os_error(), Some(Errno::EBADF.0));
280    }
281
282    #[test]
283    fn echo_off() {
284        let system = VirtualSystem::new();
285        let state = Rc::clone(&system.state);
286        {
287            let state = state.borrow();
288            let file = state.file_system.get("/dev/stdin").unwrap();
289            file.borrow_mut().body = FileBody::new(*b"one\ntwo");
290        }
291        #[allow(deprecated)]
292        let system = SharedSystem::new(system);
293        let mut reader = FdReader::new(Fd::STDIN, system);
294        #[allow(deprecated)]
295        reader.set_echo(Some(Rc::new(Cell::new(State::Off))));
296
297        let _ = reader
298            .next_line(&Context::default())
299            .now_or_never()
300            .unwrap();
301        let state = state.borrow();
302        let file = state.file_system.get("/dev/stderr").unwrap();
303        assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
304            assert_eq!(content, &[]);
305        });
306    }
307
308    #[test]
309    fn echo_on() {
310        let system = VirtualSystem::new();
311        let state = Rc::clone(&system.state);
312        {
313            let state = state.borrow();
314            let file = state.file_system.get("/dev/stdin").unwrap();
315            file.borrow_mut().body = FileBody::new(*b"one\ntwo");
316        }
317        #[allow(deprecated)]
318        let system = SharedSystem::new(system);
319        let mut reader = FdReader::new(Fd::STDIN, system);
320        #[allow(deprecated)]
321        reader.set_echo(Some(Rc::new(Cell::new(State::On))));
322
323        let _ = reader
324            .next_line(&Context::default())
325            .now_or_never()
326            .unwrap();
327        {
328            let state = state.borrow();
329            let file = state.file_system.get("/dev/stderr").unwrap();
330            assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
331                assert_eq!(content, b"one\n");
332            });
333        }
334        let _ = reader
335            .next_line(&Context::default())
336            .now_or_never()
337            .unwrap();
338        {
339            let state = state.borrow();
340            let file = state.file_system.get("/dev/stderr").unwrap();
341            assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
342                assert_eq!(content, b"one\ntwo");
343            });
344        }
345    }
346}