//! 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 ). 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; /// 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; /// 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 { 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 { 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, /// The absolute path it was opened by (see `File::path`). path: String, } impl InodeFile { pub fn open(inode: Arc, path: String) -> Result { Ok(InodeFile { inode, offset: Mutex::new(0), append: false, path }) } pub fn open_append(inode: Arc, path: String) -> Result { Ok(InodeFile { inode, offset: Mutex::new(0), append: true, path }) } } 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 path(&self) -> Option { Some(self.path.clone()) } 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) } }