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