ahrightos-fs 0.1.0

Portable filesystem, block-device, and IPC primitives for the AhRightOS teaching kernel.
Documentation
use crate::file::{File, FileError};
use alloc::sync::Arc;
use spin::Mutex;

pub const PIPE_BUFFER_SIZE: usize = 4096;

pub(crate) struct PipeInner {
    buf: [u8; PIPE_BUFFER_SIZE],
    read_pos: usize,
    write_pos: usize,
    count: usize,
    write_closed: bool,
    read_closed: bool,
}

impl PipeInner {
    fn new() -> Self {
        Self {
            buf: [0u8; PIPE_BUFFER_SIZE],
            read_pos: 0,
            write_pos: 0,
            count: 0,
            write_closed: false,
            read_closed: false,
        }
    }
}

pub(crate) type PipeHandle = Arc<Mutex<PipeInner>>;

pub fn make_pipe() -> (PipeReadEnd, PipeWriteEnd) {
    let inner = Arc::new(Mutex::new(PipeInner::new()));
    (
        PipeReadEnd {
            inner: inner.clone(),
        },
        PipeWriteEnd { inner },
    )
}

pub struct PipeReadEnd {
    inner: PipeHandle,
}

impl File for PipeReadEnd {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, FileError> {
        let mut p = self.inner.lock();
        if p.count == 0 {
            return if p.write_closed {
                Ok(0)
            } else {
                Err(FileError::WouldBlock)
            };
        }
        let n = core::cmp::min(buf.len(), p.count);
        for slot in buf.iter_mut().take(n) {
            *slot = p.buf[p.read_pos];
            p.read_pos = (p.read_pos + 1) % PIPE_BUFFER_SIZE;
        }
        p.count -= n;
        Ok(n)
    }

    fn write(&mut self, _buf: &[u8]) -> Result<usize, FileError> {
        Err(FileError::InvalidOperation)
    }
}

impl Drop for PipeReadEnd {
    fn drop(&mut self) {
        self.inner.lock().read_closed = true;
    }
}

pub struct PipeWriteEnd {
    inner: PipeHandle,
}

impl File for PipeWriteEnd {
    fn read(&mut self, _buf: &mut [u8]) -> Result<usize, FileError> {
        Err(FileError::InvalidOperation)
    }

    fn write(&mut self, buf: &[u8]) -> Result<usize, FileError> {
        let mut p = self.inner.lock();
        if p.read_closed {
            return Err(FileError::IoError);
        }
        let free = PIPE_BUFFER_SIZE - p.count;
        let n = core::cmp::min(buf.len(), free);
        if n == 0 && !buf.is_empty() {
            return Err(FileError::WouldBlock);
        }
        for &byte in buf.iter().take(n) {
            let pos = p.write_pos;
            p.buf[pos] = byte;
            p.write_pos = (pos + 1) % PIPE_BUFFER_SIZE;
        }
        p.count += n;
        Ok(n)
    }
}

impl Drop for PipeWriteEnd {
    fn drop(&mut self) {
        self.inner.lock().write_closed = true;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pipe_round_trips_bytes() {
        let (mut read_end, mut write_end) = make_pipe();
        assert_eq!(write_end.write(b"abc").unwrap(), 3);
        let mut buf = [0; 4];
        assert_eq!(read_end.read(&mut buf).unwrap(), 3);
        assert_eq!(&buf[..3], b"abc");
    }
}