RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / fs / file.rs
11568 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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
//! 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::{string::String, sync::Arc};
use spin::Mutex;

/// What a `File` or inode tells the kernel about itself. Our own small
/// layout; the syscalls turn it into Linux's `struct stat` (below), which
/// is what user programs get.
#[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 }
    }
}

/// Linux's `struct stat` on x86_64 (144 bytes), what stat/fstat/lstat/
/// newfstatat write to user memory since stage 15.1: a C library reads
/// these exact offsets. We fill in what we know and make up the rest the
/// way a simple Linux file system would.
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct LinuxStat {
    pub st_dev: u64,     // which file system (we say 1 for all)
    pub st_ino: u64,     // file number: we hash the path (see below)
    pub st_nlink: u64,   // hard links: 1 (directories 2)
    pub st_mode: u32,    // S_IF* type bits | permission bits
    pub st_uid: u32,     // owner: everything belongs to root (0)
    pub st_gid: u32,
    _pad0: u32,
    pub st_rdev: u64,    // device number for device files (0 here)
    pub st_size: i64,    // bytes
    pub st_blksize: i64, // good I/O size: 4096
    pub st_blocks: i64,  // 512-byte blocks used
    pub st_atime: [i64; 2], // last access (seconds, nanoseconds): we don't keep times yet
    pub st_mtime: [i64; 2], // last change of the contents
    pub st_ctime: [i64; 2], // last change of the inode
    _unused: [i64; 3],
}

/// File type bits of `st_mode` (octal, as in <sys/stat.h>).
pub const S_IFIFO: u32 = 0o010000;
pub const S_IFCHR: u32 = 0o020000;
pub const S_IFDIR: u32 = 0o040000;
pub const S_IFREG: u32 = 0o100000;
pub const S_IFLNK: u32 = 0o120000;
pub const S_IFSOCK: u32 = 0o140000;

impl LinuxStat {
    /// `ino` should tell files apart: callers pass a hash of the path (or of
    /// the open file's address for pipes and sockets). Programs use
    /// (st_dev, st_ino) to spot "same file" (`cp a a`), nothing more.
    pub fn new(stat: &Stat, ino: u64) -> LinuxStat {
        let (ty, default_perm) = match stat.kind {
            KIND_DIR => (S_IFDIR, 0o755),
            KIND_SYMLINK => (S_IFLNK, 0o777),
            KIND_CONSOLE | KIND_DEVICE => (S_IFCHR, 0o666),
            KIND_PIPE => (S_IFIFO, 0o600),
            KIND_SOCKET => (S_IFSOCK, 0o777),
            _ => (S_IFREG, 0o644),
        };
        let perm = if stat.mode == 0 { default_perm } else { stat.mode as u32 & 0o7777 };
        LinuxStat {
            st_dev: 1,
            st_ino: ino.max(1), // 0 means "no inode" to some programs
            st_nlink: if stat.kind == KIND_DIR { 2 } else { 1 },
            st_mode: ty | perm,
            st_size: stat.size as i64,
            st_blksize: 4096,
            st_blocks: stat.size.div_ceil(512) as i64,
            ..Default::default()
        }
    }
}

/// A 64-bit FNV-1a hash: our stand-in for inode numbers (from the path).
pub fn ino_hash(bytes: &[u8]) -> u64 {
    bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, &b| (h ^ b as u64).wrapping_mul(0x100_0000_01b3))
}

/// `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>;
    /// The absolute path this was opened by, if it was (files and
    /// directories): for openat & co. relative to a directory fd, fchdir,
    /// and fstat's inode number.
    fn path(&self) -> Option<String> {
        None
    }
    /// 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,
    /// The absolute path it was opened by (see `File::path`).
    path: String,
}

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

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

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 path(&self) -> Option<String> {
        Some(self.path.clone())
    }

    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)
    }
}