moros 0.12.0

MOROS: Obscure Rust Operating System
Documentation
use super::bitmap_block::BitmapBlock;
use super::block::LinkedBlock;
use super::dir::Dir;
use super::dir_entry::DirEntry;
use super::{dirname, filename, realpath, FileIO, IO};

use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec;

pub enum SeekFrom {
    Start(u32),
    Current(i32),
    End(i32),
}

#[derive(Debug, Clone)]
pub struct File {
    parent: Option<Box<Dir>>,
    name: String,
    addr: u32,
    size: u32,
    offset: u32,
}

impl From<DirEntry> for File {
    fn from(entry: DirEntry) -> Self {
        Self {
            parent: Some(Box::new(entry.dir())),
            name: entry.name(),
            addr: entry.addr(),
            size: entry.size(),
            offset: 0,
        }
    }
}

impl File {
    pub fn new() -> Self {
        Self {
            parent: None,
            name: String::new(),
            addr: 0,
            size: 0,
            offset: 0,
        }
    }

    pub fn create(pathname: &str) -> Option<Self> {
        let pathname = realpath(pathname);
        let dirname = dirname(&pathname);
        let filename = filename(&pathname);
        if let Some(mut dir) = Dir::open(dirname) {
            if let Some(dir_entry) = dir.create_file(filename) {
                return Some(dir_entry.into());
            }
        }
        None
    }

    pub fn open(pathname: &str) -> Option<Self> {
        let pathname = realpath(pathname);
        let dirname = dirname(&pathname);
        let filename = filename(&pathname);
        if let Some(dir) = Dir::open(dirname) {
            if let Some(dir_entry) = dir.find(filename) {
                if dir_entry.is_file() {
                    return Some(dir_entry.into());
                }
            }
        }
        None
    }

    pub fn name(&self) -> String {
        self.name.clone()
    }

    pub fn size(&self) -> usize {
        self.size as usize
    }

    pub fn seek(&mut self, pos: SeekFrom) -> Result<u32, ()> {
        let offset = match pos {
            SeekFrom::Start(i)   => i as i32,
            SeekFrom::Current(i) => i + self.offset as i32,
            SeekFrom::End(i)     => i + self.size as i32,
        };
        if offset < 0 || offset > self.size as i32 { // TODO: offset > size?
            return Err(());
        }
        self.offset = offset as u32;

        Ok(self.offset)
    }
    // TODO: Add `read_to_end(&self, buf: &mut Vec<u8>) -> Result<u32>`

    // TODO: `return Result<String>`
    pub fn read_to_string(&mut self) -> String {
        let mut buf = vec![0; self.size()];
        if let Ok(bytes) = self.read(&mut buf) {
            buf.resize(bytes, 0);
        }
        String::from_utf8_lossy(&buf).to_string()
    }

    pub fn addr(&self) -> u32 {
        self.addr
    }

    pub fn delete(pathname: &str) -> Result<(), ()> {
        let pathname = realpath(pathname);
        let dirname = dirname(&pathname);
        let filename = filename(&pathname);
        if let Some(mut dir) = Dir::open(dirname) {
            dir.delete_entry(filename)
        } else {
            Err(())
        }
    }
}

impl FileIO for File {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
        let buf_len = buf.len();
        let mut addr = self.addr;
        let mut bytes = 0; // Number of bytes read
        let mut pos = 0; // Position in the file
        loop {
            let block = LinkedBlock::read(addr);
            let data = block.data();
            let data_len = data.len();
            for i in 0..data_len {
                if pos == self.offset {
                    if bytes == buf_len || pos as usize == self.size() {
                        return Ok(bytes);
                    }
                    buf[bytes] = data[i];
                    bytes += 1;
                    self.offset += 1;
                }
                pos += 1;
            }
            match block.next() {
                Some(next_block) => addr = next_block.addr(),
                None => return Ok(bytes),
            }
        }
    }

    fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
        let buf_len = buf.len();
        let mut addr = self.addr;
        let mut bytes = 0; // Number of bytes written
        let mut pos = 0; // Position in the file

        // Optimization: when appending, skip to the last block
        if self.offset == self.size && self.size > 0 {
            let mut block = LinkedBlock::read(addr);
            while let Some(next_block) = block.next() {
                addr = next_block.addr();
                block = LinkedBlock::read(addr);
            }
            // If last block is full, allocate a new one
            let block_data_len = block.len() as u32;
            if self.size % block_data_len == 0 {
                match LinkedBlock::alloc() {
                    Some(new_block) => {
                        let mut last_block = LinkedBlock::read(addr);
                        last_block.set_next_addr(new_block.addr());
                        last_block.write();
                        addr = new_block.addr();
                        pos = self.size;
                    }
                    None => return Err(()),
                }
            } else {
                pos = self.size - (self.size % block_data_len);
            }
        }

        while bytes < buf_len {
            let mut block = LinkedBlock::read(addr);
            let data = block.data_mut();
            let data_len = data.len();
            for i in 0..data_len {
                if pos == self.offset {
                    if bytes == buf_len {
                        break;
                    }
                    data[i] = buf[bytes];
                    bytes += 1;
                    self.offset += 1;
                }
                pos += 1;
            }

            addr = match block.next() {
                Some(next_block) => {
                    if bytes < buf_len {
                        next_block.addr()
                    } else {
                        // Free next block(s)
                        let mut free_block = next_block;
                        loop {
                            BitmapBlock::free(free_block.addr());
                            match free_block.next() { // FIXME: read after free?
                                Some(next_block) => free_block = next_block,
                                None => break,
                            }
                        }
                        0
                    }
                }
                None => {
                    if bytes < buf_len {
                        match LinkedBlock::alloc() {
                            Some(next_block) => next_block.addr(),
                            None => return Err(()),
                        }
                    } else {
                        0
                    }
                }
            };

            block.set_next_addr(addr);
            block.write();
        }
        self.size = self.offset;
        if let Some(dir) = self.parent.clone() {
            dir.update_entry(&self.name, self.size);
        }
        Ok(bytes)
    }

    fn close(&mut self) {}

    fn poll(&mut self, event: IO) -> bool {
        match event {
            IO::Read => self.offset < self.size,
            IO::Write => true,
        }
    }
}

#[test_case]
fn test_file_create() {
    super::mount_mem();
    super::format_mem();
    assert!(File::create("/test").is_some());
    assert_eq!(File::create("/hello").unwrap().name(), "hello");
    super::dismount();
}

#[test_case]
fn test_file_write() {
    super::mount_mem();
    super::format_mem();
    let mut file = File::create("/test").unwrap();
    let buf = "Hello, World!".as_bytes();
    assert_eq!(file.write(&buf), Ok(buf.len()));
    super::dismount();
}

#[test_case]
fn test_file_open() {
    super::mount_mem();
    super::format_mem();
    assert!(File::open("/test").is_none());
    let mut file = File::create("/test").unwrap();
    let buf = "Hello, World!".as_bytes();
    file.write(&buf).unwrap();
    assert!(File::open("/test").is_some());
    super::dismount();
}

#[test_case]
fn test_file_read() {
    super::mount_mem();
    super::format_mem();
    let mut file = File::create("/test").unwrap();
    let input = "Hello, World!".as_bytes();
    file.write(&input).unwrap();

    let mut file = File::open("/test").unwrap();
    let mut output = [0u8; 13];
    assert_eq!(file.read(&mut output), Ok(input.len()));
    assert_eq!(input, output);
    super::dismount();
}

#[test_case]
fn test_file_delete() {
    super::mount_mem();
    super::format_mem();
    assert!(File::open("/test").is_none());
    assert!(File::create("/test").is_some());
    assert!(File::open("/test").is_some());
    assert!(File::delete("/test").is_ok());
    assert!(File::open("/test").is_none());
    super::dismount();
}