use super::{Context, Input, Result};
use crate::io::Fd;
use crate::system::{Concurrent, Fcntl, Read};
use std::rc::Rc;
use std::slice::from_mut;
#[derive(Debug)]
#[must_use = "FdReader2 does nothing unless used by a parser"]
pub struct FdReader2<S> {
fd: Fd,
system: Rc<Concurrent<S>>,
}
impl<S> FdReader2<S> {
pub fn new(fd: Fd, system: Rc<Concurrent<S>>) -> Self {
FdReader2 { fd, system }
}
}
impl<S> Clone for FdReader2<S> {
fn clone(&self) -> Self {
Self {
fd: self.fd,
system: self.system.clone(),
}
}
}
impl<S: Fcntl + Read> Input for FdReader2<S> {
async fn next_line(&mut self, _context: &Context) -> Result {
let mut bytes = Vec::new();
loop {
let mut byte = 0;
match self.system.read(self.fd, from_mut(&mut byte)).await {
Ok(0) => break,
Ok(count) => {
assert_eq!(count, 1);
bytes.push(byte);
if byte == b'\n' {
break;
}
}
Err(errno) => return Err(errno.into()),
}
}
let line = String::from_utf8(bytes)
.unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into());
Ok(line)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::system::Errno;
use crate::system::Mode;
use crate::system::OfdAccess;
use crate::system::Open as _;
use crate::system::OpenFlag;
use crate::system::r#virtual::FileBody;
use crate::system::r#virtual::Inode;
use crate::system::r#virtual::VirtualSystem;
use futures_util::FutureExt as _;
#[test]
fn empty_reader() {
let system = VirtualSystem::new();
let system = Rc::new(Concurrent::new(system));
let mut reader = FdReader2::new(Fd::STDIN, system);
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "");
}
#[test]
fn one_line_reader() {
let system = VirtualSystem::new();
{
let state = system.state.borrow_mut();
let file = state.file_system.get("/dev/stdin").unwrap();
file.borrow_mut().body = FileBody::new(*b"echo ok\n");
}
let system = Rc::new(Concurrent::new(system));
let mut reader = FdReader2::new(Fd::STDIN, system);
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "echo ok\n");
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "");
}
#[test]
fn reader_with_many_lines() {
let system = VirtualSystem::new();
{
let state = system.state.borrow_mut();
let file = state.file_system.get("/dev/stdin").unwrap();
file.borrow_mut().body = FileBody::new(*b"#!/bin/sh\necho ok\nexit");
}
let system = Rc::new(Concurrent::new(system));
let mut reader = FdReader2::new(Fd::STDIN, system);
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "#!/bin/sh\n");
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "echo ok\n");
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "exit");
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "");
}
#[test]
fn reading_from_file() {
let system = VirtualSystem::new();
{
let mut state = system.state.borrow_mut();
let file = Rc::new(Inode::new("echo file\n").into());
state.file_system.save("/foo", file).unwrap();
}
let system = Rc::new(Concurrent::new(system));
let path = c"/foo";
let fd = system
.open(
path,
OfdAccess::ReadOnly,
OpenFlag::CloseOnExec.into(),
Mode::empty(),
)
.now_or_never()
.unwrap()
.unwrap();
let mut reader = FdReader2::new(fd, system);
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "echo file\n");
let line = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(line, "");
}
#[test]
fn reader_error() {
let system = VirtualSystem::new();
system.current_process_mut().close_fd(Fd::STDIN);
let system = Rc::new(Concurrent::new(system));
let mut reader = FdReader2::new(Fd::STDIN, system);
let error = reader
.next_line(&Context::default())
.now_or_never()
.unwrap()
.unwrap_err();
assert_eq!(error.raw_os_error(), Some(Errno::EBADF.0));
}
}