//! 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; /// 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. /// 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 { 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> { 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 { Err(Errno::ENOTTY) } /// pread/pwrite: at an explicit offset, leaving the file offset alone. fn read_at(&self, _buf: &mut [u8], _offset: u64) -> Result { Err(Errno::ESPIPE) } fn write_at(&self, _buf: &[u8], _offset: u64) -> Result { 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, 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 read_at(&self, buf: &mut [u8], offset: u64) -> Result { 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 { match self.inode.kind() { InodeKind::Directory => Err(Errno::EISDIR), _ => self.inode.write_at(offset as usize, buf), } } fn inode(&self) -> Option> { (self.inode.kind() == InodeKind::File).then(|| self.inode.clone()) } 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 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) } }