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;
22use crate::system::{Fcntl, Read, SharedSystem, Write};
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(Debug)]
37#[must_use = "FdReader does nothing unless used by a parser"]
38pub struct FdReader<S> {
39    /// File descriptor to read from
40    fd: Fd,
41    /// System to interact with the FD
42    system: SharedSystem<S>,
43    /// Whether lines read are echoed to stderr
44    echo: Option<Rc<Cell<State>>>,
45}
46
47impl<S> FdReader<S> {
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<S>) -> 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
82// Not derived automatically because S may not implement Clone
83impl<S> Clone for FdReader<S> {
84    fn clone(&self) -> Self {
85        Self {
86            fd: self.fd,
87            system: self.system.clone(),
88            echo: self.echo.clone(),
89        }
90    }
91}
92
93impl<S: Fcntl + Read + Write> Input for FdReader<S> {
94    async fn next_line(&mut self, _context: &Context) -> Result {
95        // TODO Read many bytes at once if seekable
96
97        let mut bytes = Vec::new();
98        loop {
99            let mut byte = 0;
100            match self.system.read_async(self.fd, from_mut(&mut byte)).await {
101                // End of input
102                Ok(0) => break,
103
104                Ok(count) => {
105                    assert_eq!(count, 1);
106                    bytes.push(byte);
107                    if byte == b'\n' {
108                        break;
109                    }
110                }
111
112                Err(errno) => return Err(errno.into()),
113            }
114        }
115
116        // TODO Reject invalid UTF-8 sequence if strict POSIX mode is on
117        let line = String::from_utf8(bytes)
118            .unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into());
119
120        if let Some(echo) = &self.echo {
121            if echo.get() == State::On {
122                let _ = self.system.write_all(Fd::STDERR, line.as_bytes()).await;
123            }
124        }
125
126        Ok(line)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::system::Errno;
134    use crate::system::Mode;
135    use crate::system::OfdAccess;
136    use crate::system::Open as _;
137    use crate::system::OpenFlag;
138    use crate::system::r#virtual::FileBody;
139    use crate::system::r#virtual::Inode;
140    use crate::system::r#virtual::VirtualSystem;
141    use assert_matches::assert_matches;
142    use futures_util::FutureExt;
143
144    #[test]
145    fn empty_reader() {
146        let system = VirtualSystem::new();
147        let system = SharedSystem::new(system);
148        let mut reader = FdReader::new(Fd::STDIN, system);
149
150        let line = reader
151            .next_line(&Context::default())
152            .now_or_never()
153            .unwrap()
154            .unwrap();
155        assert_eq!(line, "");
156    }
157
158    #[test]
159    fn one_line_reader() {
160        let system = VirtualSystem::new();
161        {
162            let state = system.state.borrow_mut();
163            let file = state.file_system.get("/dev/stdin").unwrap();
164            file.borrow_mut().body = FileBody::new(*b"echo ok\n");
165        }
166        let system = SharedSystem::new(system);
167        let mut reader = FdReader::new(Fd::STDIN, system);
168
169        let line = reader
170            .next_line(&Context::default())
171            .now_or_never()
172            .unwrap()
173            .unwrap();
174        assert_eq!(line, "echo ok\n");
175        let line = reader
176            .next_line(&Context::default())
177            .now_or_never()
178            .unwrap()
179            .unwrap();
180        assert_eq!(line, "");
181    }
182
183    #[test]
184    fn reader_with_many_lines() {
185        let system = VirtualSystem::new();
186        {
187            let state = system.state.borrow_mut();
188            let file = state.file_system.get("/dev/stdin").unwrap();
189            file.borrow_mut().body = FileBody::new(*b"#!/bin/sh\necho ok\nexit");
190        }
191        let system = SharedSystem::new(system);
192        let mut reader = FdReader::new(Fd::STDIN, system);
193
194        let line = reader
195            .next_line(&Context::default())
196            .now_or_never()
197            .unwrap()
198            .unwrap();
199        assert_eq!(line, "#!/bin/sh\n");
200        let line = reader
201            .next_line(&Context::default())
202            .now_or_never()
203            .unwrap()
204            .unwrap();
205        assert_eq!(line, "echo ok\n");
206        let line = reader
207            .next_line(&Context::default())
208            .now_or_never()
209            .unwrap()
210            .unwrap();
211        assert_eq!(line, "exit");
212        let line = reader
213            .next_line(&Context::default())
214            .now_or_never()
215            .unwrap()
216            .unwrap();
217        assert_eq!(line, "");
218    }
219
220    #[test]
221    fn reading_from_file() {
222        let system = VirtualSystem::new();
223        {
224            let mut state = system.state.borrow_mut();
225            let file = Rc::new(Inode::new("echo file\n").into());
226            state.file_system.save("/foo", file).unwrap();
227        }
228        let system = SharedSystem::new(system);
229        let path = c"/foo";
230        let fd = system
231            .open(
232                path,
233                OfdAccess::ReadOnly,
234                OpenFlag::CloseOnExec.into(),
235                Mode::empty(),
236            )
237            .unwrap();
238        let mut reader = FdReader::new(fd, system);
239
240        let line = reader
241            .next_line(&Context::default())
242            .now_or_never()
243            .unwrap()
244            .unwrap();
245        assert_eq!(line, "echo file\n");
246        let line = reader
247            .next_line(&Context::default())
248            .now_or_never()
249            .unwrap()
250            .unwrap();
251        assert_eq!(line, "");
252    }
253
254    #[test]
255    fn reader_error() {
256        let system = VirtualSystem::new();
257        system.current_process_mut().close_fd(Fd::STDIN);
258        let system = SharedSystem::new(system);
259        let mut reader = FdReader::new(Fd::STDIN, system);
260
261        let error = reader
262            .next_line(&Context::default())
263            .now_or_never()
264            .unwrap()
265            .unwrap_err();
266        assert_eq!(error.raw_os_error(), Some(Errno::EBADF.0));
267    }
268
269    #[test]
270    fn echo_off() {
271        let system = VirtualSystem::new();
272        let state = Rc::clone(&system.state);
273        {
274            let state = state.borrow();
275            let file = state.file_system.get("/dev/stdin").unwrap();
276            file.borrow_mut().body = FileBody::new(*b"one\ntwo");
277        }
278        let system = SharedSystem::new(system);
279        let mut reader = FdReader::new(Fd::STDIN, system);
280        #[allow(deprecated)]
281        reader.set_echo(Some(Rc::new(Cell::new(State::Off))));
282
283        let _ = reader
284            .next_line(&Context::default())
285            .now_or_never()
286            .unwrap();
287        let state = state.borrow();
288        let file = state.file_system.get("/dev/stderr").unwrap();
289        assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
290            assert_eq!(content, &[]);
291        });
292    }
293
294    #[test]
295    fn echo_on() {
296        let system = VirtualSystem::new();
297        let state = Rc::clone(&system.state);
298        {
299            let state = state.borrow();
300            let file = state.file_system.get("/dev/stdin").unwrap();
301            file.borrow_mut().body = FileBody::new(*b"one\ntwo");
302        }
303        let system = SharedSystem::new(system);
304        let mut reader = FdReader::new(Fd::STDIN, system);
305        #[allow(deprecated)]
306        reader.set_echo(Some(Rc::new(Cell::new(State::On))));
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\n");
317            });
318        }
319        let _ = reader
320            .next_line(&Context::default())
321            .now_or_never()
322            .unwrap();
323        {
324            let state = state.borrow();
325            let file = state.file_system.get("/dev/stderr").unwrap();
326            assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
327                assert_eq!(content, b"one\ntwo");
328            });
329        }
330    }
331}