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