RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / fs / ramfs.rs
4047 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! ramfs: a file system that lives entirely on the heap (like Linux's ramfs /
//! tmpfs without the size limit). The simplest possible `FileSystem`: a tree
//! of nodes; files are a `Vec<u8>`, directories a sorted map name -> node.

use super::{DirEntry, FileSystem, Inode, InodeKind};
use crate::errno::Errno;
use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
use spin::Mutex;

pub struct RamFs {
    root: Arc<RamInode>,
}

impl RamFs {
    pub fn new() -> Arc<Self> {
        Arc::new(RamFs { root: Arc::new(RamInode::Dir(Mutex::new(BTreeMap::new()))) })
    }
}

impl FileSystem for RamFs {
    fn name(&self) -> &'static str {
        "ramfs"
    }
    fn root(&self) -> Arc<dyn Inode> {
        self.root.clone()
    }
}

pub enum RamInode {
    File(Mutex<Vec<u8>>),
    Dir(Mutex<BTreeMap<String, Arc<RamInode>>>),
}

impl Inode for RamInode {
    fn kind(&self) -> InodeKind {
        match self {
            RamInode::File(_) => InodeKind::File,
            RamInode::Dir(_) => InodeKind::Directory,
        }
    }

    fn size(&self) -> usize {
        match self {
            RamInode::File(data) => data.lock().len(),
            RamInode::Dir(entries) => entries.lock().len(),
        }
    }

    fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize, Errno> {
        let RamInode::File(data) = self else { return Err(Errno::EISDIR) };
        let data = data.lock();
        if offset >= data.len() {
            return Ok(0); // EOF
        }
        let n = buf.len().min(data.len() - offset);
        buf[..n].copy_from_slice(&data[offset..offset + n]);
        Ok(n)
    }

    fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize, Errno> {
        let RamInode::File(data) = self else { return Err(Errno::EISDIR) };
        let mut data = data.lock();
        let end = offset.checked_add(buf.len()).ok_or(Errno::EINVAL)?;
        if data.len() < end {
            data.resize(end, 0); // writing past the end grows the file (gap = zeros)
        }
        data[offset..end].copy_from_slice(buf);
        Ok(buf.len())
    }

    fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>, Errno> {
        let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) };
        let node = entries.lock().get(name).cloned().ok_or(Errno::ENOENT)?;
        Ok(node)
    }

    fn create(&self, name: &str, kind: InodeKind) -> Result<Arc<dyn Inode>, Errno> {
        let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) };
        if name.is_empty() || name.contains('/') || name.len() > 255 {
            return Err(Errno::EINVAL);
        }
        let mut entries = entries.lock();
        if entries.contains_key(name) {
            return Err(Errno::EEXIST);
        }
        let node = Arc::new(match kind {
            InodeKind::File => RamInode::File(Mutex::new(Vec::new())),
            InodeKind::Directory => RamInode::Dir(Mutex::new(BTreeMap::new())),
            InodeKind::Symlink => return Err(Errno::EOPNOTSUPP), // ramfs has no symlinks
        });
        entries.insert(String::from(name), node.clone());
        Ok(node)
    }

    fn list(&self) -> Result<Vec<DirEntry>, Errno> {
        let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) };
        let entries = entries.lock();
        Ok(entries.iter().map(|(name, node)| DirEntry { name: name.clone(), kind: node.kind() }).collect())
    }

    fn truncate(&self) -> Result<(), Errno> {
        let RamInode::File(data) = self else { return Err(Errno::EISDIR) };
        data.lock().clear();
        Ok(())
    }

    fn remove(&self, name: &str) -> Result<(), Errno> {
        let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) };
        let mut entries = entries.lock();
        let node = entries.get(name).ok_or(Errno::ENOENT)?;
        if let RamInode::Dir(children) = &**node {
            if !children.lock().is_empty() {
                return Err(Errno::ENOTEMPTY);
            }
        }
        entries.remove(name);
        Ok(())
    }
}