//! FAT32 support, using the `fatfs` crate (no_std + alloc mode). //! //! Two adapters glue it in: //! //! 1. [`BlockIo`]: `fatfs` wants something that looks like a `std::fs::File` //! (byte-addressed Read + Write + Seek). Our disks only do whole 512-byte //! sectors, so `BlockIo` keeps a byte position and turns byte requests //! into sector requests: whole aligned sectors go straight to the device; //! partial sectors go through a one-sector cache (read-modify-write for //! writes). Writes are write-through, so nothing is ever lost on "power //! off" because of the cache. //! //! 2. [`FatInode`]: our VFS `Inode` for one FAT file/directory. `fatfs`'s //! own `File`/`Dir` objects *borrow* the FileSystem (they carry a lifetime) //! so we can't store them in a long-lived `Arc`. Instead a //! FatInode just remembers its **path** and re-opens it from the root on //! every operation. That's slower (each read walks the directories again) //! but very simple and obviously correct -- fine for a learning kernel. //! A faster design would cache directory entries (like Linux's dcache). //! //! Locking: the whole FileSystem sits behind one Mutex, so FAT operations //! are serialized. The virtio driver underneath polls with interrupts off; //! this lock is only taken from thread context, never from an interrupt. use super::{DirEntry, FileSystem, Inode, InodeKind}; use crate::block::{BlockDevice, SECTOR_SIZE}; use crate::errno::Errno; use alloc::{format, string::String, sync::Arc, vec::Vec}; use fatfs::{IoBase, Read, Seek, SeekFrom, Write}; use spin::Mutex; // --------------------------------------------------------------------------- // 1. Byte-stream view of a block device // --------------------------------------------------------------------------- /// The error type `fatfs` reports back to us: just our errno. #[derive(Debug)] pub struct IoErr(pub Errno); impl fatfs::IoError for IoErr { fn is_interrupted(&self) -> bool { false } fn new_unexpected_eof_error() -> Self { IoErr(Errno::EIO) } fn new_write_zero_error() -> Self { IoErr(Errno::EIO) } } pub struct BlockIo { dev: Arc, pos: u64, size: u64, /// One cached sector: (lba, contents). cache: Option<(u64, [u8; SECTOR_SIZE])>, } impl BlockIo { pub fn new(dev: Arc) -> Self { let size = dev.sector_count() * SECTOR_SIZE as u64; BlockIo { dev, pos: 0, size, cache: None } } /// Get sector `lba` into the cache and return it. fn cached(&mut self, lba: u64) -> Result<&mut [u8; SECTOR_SIZE], IoErr> { if self.cache.map(|(l, _)| l) != Some(lba) { let mut data = [0u8; SECTOR_SIZE]; self.dev.read_sectors(lba, &mut data).map_err(IoErr)?; self.cache = Some((lba, data)); } Ok(&mut self.cache.as_mut().unwrap().1) } } impl IoBase for BlockIo { type Error = IoErr; } impl Read for BlockIo { fn read(&mut self, buf: &mut [u8]) -> Result { let len = buf.len().min(self.size.saturating_sub(self.pos) as usize); if len == 0 { return Ok(0); } let lba = self.pos / SECTOR_SIZE as u64; let offset = (self.pos % SECTOR_SIZE as u64) as usize; let n = if offset == 0 && len >= SECTOR_SIZE { // Fast path: whole sectors straight into the caller's buffer. let n = len / SECTOR_SIZE * SECTOR_SIZE; self.dev.read_sectors(lba, &mut buf[..n]).map_err(IoErr)?; // Writes keep the cache up to date, so it can't be stale here. n } else { // Part of one sector, via the cache. let n = len.min(SECTOR_SIZE - offset); let sector = self.cached(lba)?; buf[..n].copy_from_slice(§or[offset..offset + n]); n }; self.pos += n as u64; Ok(n) } } impl Write for BlockIo { fn write(&mut self, buf: &[u8]) -> Result { let len = buf.len().min(self.size.saturating_sub(self.pos) as usize); if len == 0 { return Ok(0); // fatfs turns this into WriteZero (disk full) } let lba = self.pos / SECTOR_SIZE as u64; let offset = (self.pos % SECTOR_SIZE as u64) as usize; let n = if offset == 0 && len >= SECTOR_SIZE { let n = len / SECTOR_SIZE * SECTOR_SIZE; self.dev.write_sectors(lba, &buf[..n]).map_err(IoErr)?; // Drop the cached sector if we just overwrote it. if let Some((cached, _)) = self.cache { if cached >= lba && cached < lba + (n / SECTOR_SIZE) as u64 { self.cache = None; } } n } else { // Read-modify-write one sector. let n = len.min(SECTOR_SIZE - offset); let sector = self.cached(lba)?; sector[offset..offset + n].copy_from_slice(&buf[..n]); let copy = *sector; self.dev.write_sectors(lba, ©).map_err(IoErr)?; n }; self.pos += n as u64; Ok(n) } fn flush(&mut self) -> Result<(), IoErr> { Ok(()) // write-through: nothing buffered } } impl Seek for BlockIo { fn seek(&mut self, pos: SeekFrom) -> Result { let new = match pos { SeekFrom::Start(n) => Some(n), SeekFrom::End(d) => self.size.checked_add_signed(d), SeekFrom::Current(d) => self.pos.checked_add_signed(d), }; match new { Some(n) if n <= self.size => { self.pos = n; Ok(n) } _ => Err(IoErr(Errno::EINVAL)), } } } /// Translate a fatfs error into an errno. fn errno(e: fatfs::Error) -> Errno { match e { fatfs::Error::Io(IoErr(e)) => e, fatfs::Error::NotFound => Errno::ENOENT, fatfs::Error::AlreadyExists => Errno::EEXIST, fatfs::Error::DirectoryIsNotEmpty => Errno::ENOTEMPTY, fatfs::Error::NotEnoughSpace | fatfs::Error::WriteZero => Errno::ENOSPC, fatfs::Error::InvalidFileNameLength => Errno::ENAMETOOLONG, fatfs::Error::InvalidInput | fatfs::Error::UnsupportedFileNameCharacter => Errno::EINVAL, _ => Errno::EIO, // corrupted file system, unexpected EOF, ... } } // --------------------------------------------------------------------------- // 2. The VFS side // --------------------------------------------------------------------------- type Fs = fatfs::FileSystem; type FatDir<'a> = fatfs::Dir<'a, BlockIo, fatfs::NullTimeProvider, fatfs::LossyOemCpConverter>; /// A mounted FAT volume. The fatfs `FileSystem` sits behind one Mutex /// (shared via Arc with every FatInode), so FAT operations are serialized. pub struct FatFs { fs: Arc>, } impl FatFs { /// Read the boot sector of `dev` and mount it if it's FAT. pub fn mount(dev: Arc) -> Result, Errno> { // No real-time clock driver yet, so new files get the FAT epoch date // (1980-01-01) via NullTimeProvider. let options = fatfs::FsOptions::new().time_provider(fatfs::NullTimeProvider::new()); let fs = fatfs::FileSystem::new(BlockIo::new(dev), options).map_err(errno)?; Ok(Arc::new(FatFs { fs: Arc::new(Mutex::new(fs)) })) } /// The volume label (from `mkfs.vfat -n`). pub fn label(&self) -> String { String::from(self.fs.lock().volume_label().trim_end()) } } impl FileSystem for FatFs { fn name(&self) -> &'static str { "fat32" } fn root(&self) -> Arc { Arc::new(FatInode { fs: self.fs.clone(), path: String::new(), kind: InodeKind::Directory }) } } /// A file or directory on a FAT volume, identified by its path from the /// volume root ("" = the root, "bin/hello" = a file). pub struct FatInode { fs: Arc>, path: String, kind: InodeKind, } impl FatInode { fn child_path(&self, name: &str) -> String { if self.path.is_empty() { String::from(name) } else { format!("{}/{}", self.path, name) } } /// Run `f` on this inode's directory (re-opened from the root). fn with_dir(&self, f: impl FnOnce(&FatDir) -> Result) -> Result { let fs = self.fs.lock(); let root = fs.root_dir(); if self.path.is_empty() { f(&root) } else { f(&root.open_dir(&self.path).map_err(errno)?) } } /// Run `f` on this inode's file (re-opened from the root). fn with_file(&self, f: impl FnOnce(&mut fatfs::File) -> Result) -> Result { if self.kind == InodeKind::Directory { return Err(Errno::EISDIR); } let fs = self.fs.lock(); let mut file = fs.root_dir().open_file(&self.path).map_err(errno)?; f(&mut file) } } impl Inode for FatInode { fn kind(&self) -> InodeKind { self.kind } fn size(&self) -> usize { match self.kind { InodeKind::File => self.with_file(|f| f.seek(SeekFrom::End(0)).map_err(errno)).unwrap_or(0) as usize, _ => self.list().map(|l| l.len()).unwrap_or(0), } } fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { self.with_file(|file| { let size = file.seek(SeekFrom::End(0)).map_err(errno)? as usize; if offset >= size { return Ok(0); } file.seek(SeekFrom::Start(offset as u64)).map_err(errno)?; let len = buf.len().min(size - offset); // fatfs reads at most one cluster per call; loop to fill buf. let mut done = 0; while done < len { match file.read(&mut buf[done..len]).map_err(errno)? { 0 => break, n => done += n, } } Ok(done) }) } fn write_at(&self, offset: usize, buf: &[u8]) -> Result { self.with_file(|file| { let size = file.seek(SeekFrom::End(0)).map_err(errno)? as usize; if offset > size { // Writing past the end: fill the gap with zeros (FAT has no // sparse files). let zeros = [0u8; 512]; let mut gap = offset - size; while gap > 0 { let n = gap.min(zeros.len()); file.write_all(&zeros[..n]).map_err(errno)?; gap -= n; } } else { file.seek(SeekFrom::Start(offset as u64)).map_err(errno)?; } file.write_all(buf).map_err(errno)?; Ok(buf.len()) }) } fn truncate(&self) -> Result<(), Errno> { self.with_file(|file| file.truncate().map_err(errno)) // truncates at the position: 0 } /// truncate/ftruncate (stage 16): fatfs cuts a file at its current /// position, so seek there first. Growing goes through write_at, which /// fills the gap with zeros. fn set_len(&self, len: usize) -> Result<(), Errno> { if self.kind != InodeKind::File { return Err(Errno::EISDIR); } let size = self.size(); if len > size { self.write_at(len - 1, &[0])?; return Ok(()); } self.with_file(|file| { file.seek(SeekFrom::Start(len as u64)).map_err(errno)?; file.truncate().map_err(errno) }) } fn lookup(&self, name: &str) -> Result, Errno> { if self.kind != InodeKind::Directory { return Err(Errno::ENOTDIR); } let kind = self.with_dir(|dir| { for entry in dir.iter() { let entry = entry.map_err(errno)?; // FAT names are case-insensitive (README.TXT == readme.txt). if entry.file_name().eq_ignore_ascii_case(name) { return Ok(if entry.is_dir() { InodeKind::Directory } else { InodeKind::File }); } } Err(Errno::ENOENT) })?; Ok(Arc::new(FatInode { fs: self.fs.clone(), path: self.child_path(name), kind })) } fn create(&self, name: &str, kind: InodeKind) -> Result, Errno> { if self.lookup(name).is_ok() { return Err(Errno::EEXIST); } if kind == InodeKind::Symlink { return Err(Errno::EOPNOTSUPP); // FAT has no symlinks } self.with_dir(|dir| { match kind { InodeKind::Directory => dir.create_dir(name).map(|_| ()), _ => dir.create_file(name).map(|_| ()), } .map_err(errno) })?; Ok(Arc::new(FatInode { fs: self.fs.clone(), path: self.child_path(name), kind })) } fn list(&self) -> Result, Errno> { if self.kind != InodeKind::Directory { return Err(Errno::ENOTDIR); } self.with_dir(|dir| { let mut out = Vec::new(); for entry in dir.iter() { let entry = entry.map_err(errno)?; let name = entry.file_name(); if name == "." || name == ".." { continue; // the VFS doesn't list these (like our ramfs) } let kind = if entry.is_dir() { InodeKind::Directory } else { InodeKind::File }; out.push(DirEntry { name, kind }); } Ok(out) }) } fn remove(&self, name: &str) -> Result<(), Errno> { if self.kind != InodeKind::Directory { return Err(Errno::ENOTDIR); } self.with_dir(|dir| dir.remove(name).map_err(errno)) } }