RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / fs / file.rs
8442 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! 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 mode: u64, // permission bits like 0o644 (0 = the file system has none)
}

pub const KIND_FILE: u64 = 1;
pub const KIND_DIR: u64 = 2;
pub const KIND_CONSOLE: u64 = 3;
pub const KIND_PIPE: u64 = 4;
pub const KIND_SYMLINK: u64 = 5;
pub const KIND_SOCKET: u64 = 6;
pub const KIND_DEVICE: u64 = 7; // /dev/null, /dev/fb0, ...

impl Stat {
    pub fn of(inode: &dyn Inode) -> Stat {
        let kind = match inode.kind() {
            InodeKind::File => KIND_FILE,
            InodeKind::Directory => KIND_DIR,
            InodeKind::Symlink => KIND_SYMLINK,
            InodeKind::Device => KIND_DEVICE,
        };
        Stat { kind, size: inode.size() as u64, mode: inode.mode() 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.
    /// Socket syscalls (connect, accept, ...) need the socket behind an fd;
    /// everything that isn't a socket says "no" (ENOTSOCK).
    fn as_socket(&self) -> Option<&crate::net::socket::Socket> {
        None
    }
    fn getdents(&self, _buf: &mut [u8]) -> Result<usize, Errno> {
        Err(Errno::ENOTDIR)
    }
    /// The framebuffer ioctls need /dev/fb0 behind the fd.
    fn as_framebuffer(&self) -> Option<&crate::dev::Framebuffer> {
        None
    }
    /// mmap of a file needs the regular file's inode behind the fd.
    fn inode(&self) -> Option<Arc<dyn Inode>> {
        None
    }

    // --- stage 15: what event loops and a C library need -----------------

    /// Readiness right now, as poll(2) bits (crate::poll::POLLIN...): can a
    /// read / a write go ahead without blocking? Files that never block
    /// (regular files, /dev/null) are always ready.
    fn poll(&self) -> u16 {
        crate::poll::POLLIN | crate::poll::POLLOUT
    }
    /// O_NONBLOCK (fcntl F_SETFL, ioctl FIONBIO): instead of blocking,
    /// fail with EAGAIN. It belongs to the open file, so dup'ed and
    /// inherited fds share it. Only files that can block keep it.
    fn set_nonblocking(&self, _on: bool) {}
    fn nonblocking(&self) -> bool {
        false
    }
    /// Device-specific ioctls (ttys: termios, window size...).
    fn ioctl(&self, _cmd: u64, _arg: u64) -> Result<u64, Errno> {
        Err(Errno::ENOTTY)
    }
    /// pread/pwrite: at an explicit offset, leaving the file offset alone.
    fn read_at(&self, _buf: &mut [u8], _offset: u64) -> Result<usize, Errno> {
        Err(Errno::ESPIPE)
    }
    fn write_at(&self, _buf: &[u8], _offset: u64) -> Result<usize, Errno> {
        Err(Errno::ESPIPE)
    }
    /// epoll_ctl / epoll_wait need the epoll instance behind the fd.
    fn as_epoll(&self) -> Option<&crate::poll::Epoll> {
        None
    }
    /// The terminal behind the fd, if it is one (tty.rs).
    fn as_tty(&self) -> Option<&crate::tty::TtyFile> {
        None
    }
}

/// 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 read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize, Errno> {
        match self.inode.kind() {
            InodeKind::Directory => Err(Errno::EISDIR),
            _ => self.inode.read_at(offset as usize, buf),
        }
    }

    fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize, Errno> {
        match self.inode.kind() {
            InodeKind::Directory => Err(Errno::EISDIR),
            _ => self.inode.write_at(offset as usize, buf),
        }
    }

    fn inode(&self) -> Option<Arc<dyn Inode>> {
        (self.inode.kind() == InodeKind::File).then(|| self.inode.clone())
    }

    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
                InodeKind::Symlink => 10,  // DT_LNK
                InodeKind::Device => 2,    // DT_CHR (a "character device")
            };
            rec[19..19 + entry.name.len()].copy_from_slice(entry.name.as_bytes());
            used += reclen;
            *index += 1;
        }
        Ok(used)
    }
}