RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / fs / file.rs
5456 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Open files: what a file descriptor refers to.
//!
//! `File` is the interface the read/write/lseek/fstat/getdents syscalls use.
//! It's separate from `Inode` because the same inode can be open several
//! times with different offsets, and because some files aren't in any file
//! system at all (the console, pipes). Linux calls this `struct file` +
//! `file_operations`.

use super::{Inode, InodeKind};
use crate::errno::Errno;
use alloc::sync::Arc;
use spin::Mutex;

/// What `stat`/`fstat` return. Our own small layout (Linux's `struct stat`
/// has ~15 fields we have no use for yet). `#[repr(C)]` because it's copied
/// to user space as is; user/ulib.rs has the same struct.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Stat {
    pub kind: u64, // one of the KIND_* constants
    pub size: u64, // bytes (files), entries (directories)
}

pub const KIND_FILE: u64 = 1;
pub const KIND_DIR: u64 = 2;
pub const KIND_CONSOLE: u64 = 3;
pub const KIND_PIPE: u64 = 4;

impl Stat {
    pub fn of(inode: &dyn Inode) -> Stat {
        let kind = match inode.kind() {
            InodeKind::File => KIND_FILE,
            InodeKind::Directory => KIND_DIR,
        };
        Stat { kind, size: inode.size() as u64 }
    }
}

/// `whence` values for `seek`, as in Linux's lseek.
pub const SEEK_SET: u64 = 0;
pub const SEEK_CUR: u64 = 1;
pub const SEEK_END: u64 = 2;

pub trait File: Send + Sync {
    /// Read up to `buf.len()` bytes; 0 means end of file.
    fn read(&self, buf: &mut [u8]) -> Result<usize, Errno>;
    /// Write bytes; returns how many were written.
    fn write(&self, buf: &[u8]) -> Result<usize, Errno>;
    /// Move the offset; returns the new offset. Consoles and pipes can't.
    fn seek(&self, _offset: i64, _whence: u64) -> Result<u64, Errno> {
        Err(Errno::ESPIPE)
    }
    fn stat(&self) -> Result<Stat, Errno>;
    /// Fill `buf` with Linux `dirent64` records for the next directory
    /// entries; returns bytes used, 0 at the end.
    fn getdents(&self, _buf: &mut [u8]) -> Result<usize, Errno> {
        Err(Errno::ENOTDIR)
    }
}

/// A file or directory opened through the VFS: inode + current position.
/// For directories the "position" is the index of the next entry to list.
pub struct InodeFile {
    inode: Arc<dyn Inode>,
    offset: Mutex<usize>,
    /// O_APPEND: every write goes to the current end of the file (`>>`).
    append: bool,
}

impl InodeFile {
    pub fn open(inode: Arc<dyn Inode>) -> Result<Self, Errno> {
        Ok(InodeFile { inode, offset: Mutex::new(0), append: false })
    }

    pub fn open_append(inode: Arc<dyn Inode>) -> Result<Self, Errno> {
        Ok(InodeFile { inode, offset: Mutex::new(0), append: true })
    }
}

impl File for InodeFile {
    fn read(&self, buf: &mut [u8]) -> Result<usize, Errno> {
        let mut offset = self.offset.lock();
        let n = self.inode.read_at(*offset, buf)?;
        *offset += n;
        Ok(n)
    }

    fn write(&self, buf: &[u8]) -> Result<usize, Errno> {
        let mut offset = self.offset.lock();
        if self.append {
            *offset = self.inode.size();
        }
        let n = self.inode.write_at(*offset, buf)?;
        *offset += n;
        Ok(n)
    }

    fn seek(&self, offset: i64, whence: u64) -> Result<u64, Errno> {
        if self.inode.kind() == InodeKind::Directory {
            return if offset == 0 && whence == SEEK_SET { *self.offset.lock() = 0; Ok(0) } else { Err(Errno::EINVAL) };
        }
        let mut current = self.offset.lock();
        let base = match whence {
            SEEK_SET => 0,
            SEEK_CUR => *current as i64,
            SEEK_END => self.inode.size() as i64,
            _ => return Err(Errno::EINVAL),
        };
        let new = base.checked_add(offset).filter(|&n| n >= 0).ok_or(Errno::EINVAL)?;
        *current = new as usize;
        Ok(new as u64)
    }

    fn stat(&self) -> Result<Stat, Errno> {
        Ok(Stat::of(&*self.inode))
    }

    fn getdents(&self, buf: &mut [u8]) -> Result<usize, Errno> {
        // Re-list the directory each call and skip what we've returned
        // already (simple; a real fs would keep a cursor into the directory).
        let entries = self.inode.list()?;
        let mut index = self.offset.lock();
        let mut used = 0;
        for entry in entries.iter().skip(*index) {
            // struct linux_dirent64 { u64 ino; i64 off; u16 reclen; u8 type; char name[]; }
            // padded to a multiple of 8 bytes.
            let reclen = (19 + entry.name.len() + 1).next_multiple_of(8);
            if used + reclen > buf.len() {
                if used == 0 {
                    return Err(Errno::EINVAL); // buffer too small for even one entry
                }
                break;
            }
            let rec = &mut buf[used..used + reclen];
            rec.fill(0);
            rec[0..8].copy_from_slice(&(*index as u64 + 1).to_le_bytes()); // fake inode number
            rec[8..16].copy_from_slice(&(*index as i64 + 1).to_le_bytes()); // offset of next
            rec[16..18].copy_from_slice(&(reclen as u16).to_le_bytes());
            rec[18] = match entry.kind {
                InodeKind::Directory => 4, // DT_DIR
                InodeKind::File => 8,      // DT_REG
            };
            rec[19..19 + entry.name.len()].copy_from_slice(entry.name.as_bytes());
            used += reclen;
            *index += 1;
        }
        Ok(used)
    }
}