Skip to main content

yash_env/input/
fd_reader_2.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// `FdReader2` definition
18
19use super::{Context, Input, Result};
20use crate::io::Fd;
21use crate::system::{Concurrent, Fcntl, Read};
22use std::rc::Rc;
23use std::slice::from_mut;
24
25/// [Input function](Input) that reads from a file descriptor
26///
27/// An instance of `FdReader2<S>` contains a `Rc<Concurrent<S>>` to read input
28/// from a file descriptor.
29///
30/// Although `FdReader2` implements `Clone`, it does not mean you can create and
31/// keep a copy of a `FdReader2` instance to replay the input later. Since both
32/// the original and clone share the same `Concurrent<S>`, reading a line from
33/// one instance will affect the next read from the other.
34///
35/// `FdReader2` is a variant of [`FdReader`](super::FdReader) that depends on
36/// `Concurrent<S>` instead of [`SharedSystem`](crate::system::SharedSystem).
37#[derive(Debug)]
38#[must_use = "FdReader2 does nothing unless used by a parser"]
39pub struct FdReader2<S> {
40    /// File descriptor to read from
41    fd: Fd,
42    /// System to interact with the FD
43    system: Rc<Concurrent<S>>,
44}
45
46impl<S> FdReader2<S> {
47    /// Creates a new `FdReader2` instance.
48    ///
49    /// The `fd` argument is the file descriptor to read from. It should be
50    /// readable, have the close-on-exec flag set, and remain open for the
51    /// lifetime of the `FdReader2` instance.
52    pub fn new(fd: Fd, system: Rc<Concurrent<S>>) -> Self {
53        FdReader2 { fd, system }
54    }
55}
56
57// Not derived automatically because S may not implement Clone
58impl<S> Clone for FdReader2<S> {
59    fn clone(&self) -> Self {
60        Self {
61            fd: self.fd,
62            system: self.system.clone(),
63        }
64    }
65}
66
67impl<S: Fcntl + Read> Input for FdReader2<S> {
68    async fn next_line(&mut self, _context: &Context) -> Result {
69        // TODO Read many bytes at once if seekable
70
71        let mut bytes = Vec::new();
72        loop {
73            let mut byte = 0;
74            match self.system.read(self.fd, from_mut(&mut byte)).await {
75                // End of input
76                Ok(0) => break,
77
78                Ok(count) => {
79                    assert_eq!(count, 1);
80                    bytes.push(byte);
81                    if byte == b'\n' {
82                        break;
83                    }
84                }
85
86                Err(errno) => return Err(errno.into()),
87            }
88        }
89
90        // TODO Reject invalid UTF-8 sequence if strict POSIX mode is on
91        let line = String::from_utf8(bytes)
92            .unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into());
93
94        Ok(line)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::system::Errno;
102    use crate::system::Mode;
103    use crate::system::OfdAccess;
104    use crate::system::Open as _;
105    use crate::system::OpenFlag;
106    use crate::system::r#virtual::FileBody;
107    use crate::system::r#virtual::Inode;
108    use crate::system::r#virtual::VirtualSystem;
109    use futures_util::FutureExt as _;
110
111    #[test]
112    fn empty_reader() {
113        let system = VirtualSystem::new();
114        let system = Rc::new(Concurrent::new(system));
115        let mut reader = FdReader2::new(Fd::STDIN, system);
116
117        let line = reader
118            .next_line(&Context::default())
119            .now_or_never()
120            .unwrap()
121            .unwrap();
122        assert_eq!(line, "");
123    }
124
125    #[test]
126    fn one_line_reader() {
127        let system = VirtualSystem::new();
128        {
129            let state = system.state.borrow_mut();
130            let file = state.file_system.get("/dev/stdin").unwrap();
131            file.borrow_mut().body = FileBody::new(*b"echo ok\n");
132        }
133        let system = Rc::new(Concurrent::new(system));
134        let mut reader = FdReader2::new(Fd::STDIN, system);
135
136        let line = reader
137            .next_line(&Context::default())
138            .now_or_never()
139            .unwrap()
140            .unwrap();
141        assert_eq!(line, "echo ok\n");
142        let line = reader
143            .next_line(&Context::default())
144            .now_or_never()
145            .unwrap()
146            .unwrap();
147        assert_eq!(line, "");
148    }
149
150    #[test]
151    fn reader_with_many_lines() {
152        let system = VirtualSystem::new();
153        {
154            let state = system.state.borrow_mut();
155            let file = state.file_system.get("/dev/stdin").unwrap();
156            file.borrow_mut().body = FileBody::new(*b"#!/bin/sh\necho ok\nexit");
157        }
158        let system = Rc::new(Concurrent::new(system));
159        let mut reader = FdReader2::new(Fd::STDIN, system);
160
161        let line = reader
162            .next_line(&Context::default())
163            .now_or_never()
164            .unwrap()
165            .unwrap();
166        assert_eq!(line, "#!/bin/sh\n");
167        let line = reader
168            .next_line(&Context::default())
169            .now_or_never()
170            .unwrap()
171            .unwrap();
172        assert_eq!(line, "echo ok\n");
173        let line = reader
174            .next_line(&Context::default())
175            .now_or_never()
176            .unwrap()
177            .unwrap();
178        assert_eq!(line, "exit");
179        let line = reader
180            .next_line(&Context::default())
181            .now_or_never()
182            .unwrap()
183            .unwrap();
184        assert_eq!(line, "");
185    }
186
187    #[test]
188    fn reading_from_file() {
189        let system = VirtualSystem::new();
190        {
191            let mut state = system.state.borrow_mut();
192            let file = Rc::new(Inode::new("echo file\n").into());
193            state.file_system.save("/foo", file).unwrap();
194        }
195        let system = Rc::new(Concurrent::new(system));
196        let path = c"/foo";
197        let fd = system
198            .open(
199                path,
200                OfdAccess::ReadOnly,
201                OpenFlag::CloseOnExec.into(),
202                Mode::empty(),
203            )
204            .now_or_never()
205            .unwrap()
206            .unwrap();
207        let mut reader = FdReader2::new(fd, system);
208
209        let line = reader
210            .next_line(&Context::default())
211            .now_or_never()
212            .unwrap()
213            .unwrap();
214        assert_eq!(line, "echo file\n");
215        let line = reader
216            .next_line(&Context::default())
217            .now_or_never()
218            .unwrap()
219            .unwrap();
220        assert_eq!(line, "");
221    }
222
223    #[test]
224    fn reader_error() {
225        let system = VirtualSystem::new();
226        system.current_process_mut().close_fd(Fd::STDIN);
227        let system = Rc::new(Concurrent::new(system));
228        let mut reader = FdReader2::new(Fd::STDIN, system);
229
230        let error = reader
231            .next_line(&Context::default())
232            .now_or_never()
233            .unwrap()
234            .unwrap_err();
235        assert_eq!(error.raw_os_error(), Some(Errno::EBADF.0));
236    }
237}