Add FAT12/16/32 scheme daemon, management tools, and build integration
5-crate Rust workspace implementing full VFAT support: fatd scheme daemon (FSScheme with open/read/write/mkdir/unlink/rename/fstat), fat-mkfs (create FAT12/16/32 with labels and cluster size), fat-label (read/write BPB + root-dir volume labels), fat-check (verify + repair dirty flags, FSInfo, lost clusters, orphaned LFN). 60 unit tests, 0 unwrap in production code. Included in all 5 redbear configs via redbear-device-services.toml. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "fat-blockdev"
|
||||
description = "Block device adapter for fatfs crate on Redox OS"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fatfs.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
[dependencies.redox_syscall]
|
||||
workspace = true
|
||||
optional = true
|
||||
|
||||
[dependencies.libredox]
|
||||
workspace = true
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["redox"]
|
||||
redox = ["dep:redox_syscall", "dep:libredox"]
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
||||
/// Block device adapter backed by a host file (Linux/macOS).
|
||||
///
|
||||
/// Implements `Read + Write + Seek` for use with the `fatfs` crate.
|
||||
/// Wraps `std::fs::File` and reports total size from filesystem metadata.
|
||||
pub struct FileDisk {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FileDisk {
|
||||
/// Open an existing file for read/write.
|
||||
pub fn new(file: File) -> Self {
|
||||
let size = file.metadata()
|
||||
.map(|m| m.len())
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("file_disk: metadata read failed, assuming zero size: {e}");
|
||||
0
|
||||
});
|
||||
Self { file, size }
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)?;
|
||||
let size = file.metadata()?.len();
|
||||
Ok(Self { file, size })
|
||||
}
|
||||
|
||||
/// Create a new file of the given size, zero-filled.
|
||||
pub fn create<P: AsRef<Path>>(path: P, size: u64) -> io::Result<Self> {
|
||||
let file = File::create(&path)?;
|
||||
file.set_len(size)?;
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)?;
|
||||
Ok(Self { file, size })
|
||||
}
|
||||
|
||||
/// Total size of the backing file in bytes.
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for FileDisk {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.file.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for FileDisk {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.file.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.file.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for FileDisk {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
self.file.seek(pos)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
mod file_disk;
|
||||
|
||||
pub use file_disk::FileDisk;
|
||||
|
||||
#[cfg(feature = "redox")]
|
||||
mod redox_disk;
|
||||
|
||||
#[cfg(feature = "redox")]
|
||||
pub use redox_disk::RedoxDisk;
|
||||
@@ -0,0 +1,55 @@
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
|
||||
pub struct RedoxDisk {
|
||||
fd: usize,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl RedoxDisk {
|
||||
pub fn open(disk_path: &str) -> syscall::error::Result<Self> {
|
||||
let fd = libredox::call::open(disk_path, libredox::flag::O_RDWR, 0)?;
|
||||
let mut stat = syscall::data::Stat::default();
|
||||
syscall::call::fstat(fd, &mut stat)?;
|
||||
Ok(Self {
|
||||
fd,
|
||||
size: stat.st_size,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for RedoxDisk {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
syscall::call::read(self.fd, buf)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("redox read: {e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for RedoxDisk {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
syscall::call::write(self.fd, buf)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("redox write: {e:?}")))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
syscall::call::fsync(self.fd)
|
||||
.map(|_| ())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("redox flush: {e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for RedoxDisk {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let offset = match pos {
|
||||
SeekFrom::Start(off) => off as isize,
|
||||
SeekFrom::Current(off) => off as isize,
|
||||
SeekFrom::End(off) => (self.size as isize) + (off as isize),
|
||||
};
|
||||
let result = syscall::call::lseek(self.fd, offset, syscall::flag::SEEK_SET)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("redox seek: {e:?}")))?;
|
||||
Ok(result as u64)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user