//! 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; 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, }; 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; /// Write bytes; returns how many were written. fn write(&self, buf: &[u8]) -> Result; /// Move the offset; returns the new offset. Consoles and pipes can't. fn seek(&self, _offset: i64, _whence: u64) -> Result { Err(Errno::ESPIPE) } fn stat(&self) -> Result; /// 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 { 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, offset: Mutex, /// O_APPEND: every write goes to the current end of the file (`>>`). append: bool, } impl InodeFile { pub fn open(inode: Arc) -> Result { Ok(InodeFile { inode, offset: Mutex::new(0), append: false }) } pub fn open_append(inode: Arc) -> Result { Ok(InodeFile { inode, offset: Mutex::new(0), append: true }) } } impl File for InodeFile { fn read(&self, buf: &mut [u8]) -> Result { let mut offset = self.offset.lock(); let n = self.inode.read_at(*offset, buf)?; *offset += n; Ok(n) } fn write(&self, buf: &[u8]) -> Result { 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 { 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 { Ok(Stat::of(&*self.inode)) } fn getdents(&self, buf: &mut [u8]) -> Result { // 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 }; rec[19..19 + entry.name.len()].copy_from_slice(entry.name.as_bytes()); used += reclen; *index += 1; } Ok(used) } }