use super::{Context, Input, Result};
use crate::io::Fd;
use crate::option::State;
#[allow(deprecated)]
use crate::system::{Fcntl, Read, SharedSystem, Write};
use std::cell::Cell;
use std::rc::Rc;
use std::slice::from_mut;
#[derive(Debug)]
#[must_use = "FdReader does nothing unless used by a parser"]
pub struct FdReader<S> {
fd: Fd,
#[allow(deprecated)]
system: SharedSystem<S>,
echo: Option<Rc<Cell<State>>>,
}
impl<S> FdReader<S> {
#[allow(deprecated)]
pub fn new(fd: Fd, system: SharedSystem<S>) -> Self {
let echo = None;
FdReader { fd, system, echo }
}
#[deprecated = "use Echo instead"]
pub fn set_echo(&mut self, echo: Option<Rc<Cell<State>>>) {
self.echo = echo;
}
}
impl<S> Clone for FdReader<S> {
fn clone(&self) -> Self {
Self {
fd: self.fd,
system: self.system.clone(),
echo: self.echo.clone(),
}
}
}
impl<S: Fcntl + Read + Write> Input for FdReader<S> {
async fn next_line(&mut self, _context: &Context) -> Result {
let mut bytes = Vec::new();
loop {
let mut byte = 0;
match self.system.read_async(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());
if let Some(echo) = &self.echo {
if echo.get() == State::On {
let _ = self.system.write_all(Fd::STDERR, line.as_bytes()).await;
}
}
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 assert_matches::assert_matches;
use futures_util::FutureExt;
#[test]
fn empty_reader() {
let system = VirtualSystem::new();
#[allow(deprecated)]
let system = SharedSystem::new(system);
let mut reader = FdReader::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");
}
#[allow(deprecated)]
let system = SharedSystem::new(system);
let mut reader = FdReader::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");
}
#[allow(deprecated)]
let system = SharedSystem::new(system);
let mut reader = FdReader::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();
}
#[allow(deprecated)]
let system = SharedSystem::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 = FdReader::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);
#[allow(deprecated)]
let system = SharedSystem::new(system);
let mut reader = FdReader::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));
}
#[test]
fn echo_off() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
{
let state = state.borrow();
let file = state.file_system.get("/dev/stdin").unwrap();
file.borrow_mut().body = FileBody::new(*b"one\ntwo");
}
#[allow(deprecated)]
let system = SharedSystem::new(system);
let mut reader = FdReader::new(Fd::STDIN, system);
#[allow(deprecated)]
reader.set_echo(Some(Rc::new(Cell::new(State::Off))));
let _ = reader
.next_line(&Context::default())
.now_or_never()
.unwrap();
let state = state.borrow();
let file = state.file_system.get("/dev/stderr").unwrap();
assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
assert_eq!(content, &[]);
});
}
#[test]
fn echo_on() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
{
let state = state.borrow();
let file = state.file_system.get("/dev/stdin").unwrap();
file.borrow_mut().body = FileBody::new(*b"one\ntwo");
}
#[allow(deprecated)]
let system = SharedSystem::new(system);
let mut reader = FdReader::new(Fd::STDIN, system);
#[allow(deprecated)]
reader.set_echo(Some(Rc::new(Cell::new(State::On))));
let _ = reader
.next_line(&Context::default())
.now_or_never()
.unwrap();
{
let state = state.borrow();
let file = state.file_system.get("/dev/stderr").unwrap();
assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
assert_eq!(content, b"one\n");
});
}
let _ = reader
.next_line(&Context::default())
.now_or_never()
.unwrap();
{
let state = state.borrow();
let file = state.file_system.get("/dev/stderr").unwrap();
assert_matches!(&file.borrow().body, FileBody::Regular { content, .. } => {
assert_eq!(content, b"one\ntwo");
});
}
}
}