feat(storage): ext4-only disk via mkfs.ext4 -d; drop FAT16; feature-gated mount (ro on unknown ro_compat); ext4 read spans blocks, delete clears inode, mkdir counts dir; virtio-blk writes; test-qemu e2fsck gate

This commit is contained in:
2026-10-27 17:09:35 +01:00
parent 4030dd4315
commit 0fd568cdad
24 changed files with 390 additions and 809 deletions

View File

@@ -7,6 +7,7 @@ pub struct BlockGroupDescriptor {
pub inode_table: u32,
pub free_blocks_count: u16,
pub free_inodes_count: u16,
pub used_dirs_count: u16,
}
impl BlockGroupDescriptor {
@@ -21,6 +22,7 @@ impl BlockGroupDescriptor {
inode_table: read_u32(&buf, off + 8),
free_blocks_count: read_u16(&buf, off + 12),
free_inodes_count: read_u16(&buf, off + 14),
used_dirs_count: read_u16(&buf, off + 16),
})
}
@@ -34,6 +36,7 @@ impl BlockGroupDescriptor {
write_u32(&mut buf, off + 8, self.inode_table);
write_u16(&mut buf, off + 12, self.free_blocks_count);
write_u16(&mut buf, off + 14, self.free_inodes_count);
write_u16(&mut buf, off + 16, self.used_dirs_count);
write_block(device, gdt_block as u64, spb, &buf)
}
}

View File

@@ -110,6 +110,29 @@ impl<B: BlockDevice> Ext4<B> {
io::write_block(&mut self.device, block_num, self.sb.sectors_per_block(), &block_buf)
}
/// Zeroes `inode_num`'s on-disk record: how ext4 marks an inode unused,
/// so nothing still points at the blocks it had.
pub(super) fn clear_inode(&mut self, inode_num: u32) -> bool {
let Some(bgd) = BlockGroupDescriptor::read(
&mut self.device,
self.gdt_block,
self.sb.inode_block_group(inode_num),
self.sb.sectors_per_block(),
) else {
return false;
};
let size = self.sb.inode_size as usize;
let inode_offset = self.sb.inode_index_in_group(inode_num) as usize * size;
let block_num = (bgd.inode_table + (inode_offset / self.sb.block_size) as u32) as u64;
let at = inode_offset % self.sb.block_size;
let mut block_buf = [0u8; 4096];
if !io::read_block(&mut self.device, block_num, self.sb.sectors_per_block(), &mut block_buf) {
return false;
}
block_buf[at..at + size].fill(0);
io::write_block(&mut self.device, block_num, self.sb.sectors_per_block(), &block_buf)
}
pub fn init_empty_inode(mode: u16) -> Inode {
let mut block_data = [0u8; 60];
io::write_u16(&mut block_data, 0, EXTENT_MAGIC);

View File

@@ -50,9 +50,27 @@ impl<B: BlockDevice> Ext4<B> {
open_files: [const { None }; MAX_OPEN_FILES],
})
}
/// One more directory in `inode`'s block group (e2fsck checks the count).
fn count_dir(&mut self, inode: u32) {
let (bg, spb) = (self.sb.inode_block_group(inode), self.sb.sectors_per_block());
if let Some(mut d) = bgd::BlockGroupDescriptor::read(&mut self.device, self.gdt_block, bg, spb) {
d.used_dirs_count += 1;
d.write_back(&mut self.device, self.gdt_block, bg, spb);
}
}
/// The disk, for a test to look at what was written.
pub fn into_device(self) -> B {
self.device
}
}
impl<B: BlockDevice> FileSystem for Ext4<B> {
fn writable(&self) -> bool {
self.sb.writable && self.device.writable()
}
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
let inode_num = self.resolve_path(path)?;
let inode = self.read_inode(inode_num)?;
@@ -73,13 +91,9 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
let file = *self.open_files[handle.raw() as usize].as_ref()?;
if file.offset >= file.size { return Some(0); }
let remaining = file.size - file.offset;
let to_read = buf.len().min(remaining);
// As much as `buf` holds, across blocks: a short read means the end.
let to_read = buf.len().min(file.size - file.offset);
let bs = self.sb.block_size;
let logical_block = (file.offset / bs) as u32;
let byte_in_block = file.offset % bs;
let inode = Inode {
mode: 0,
size: file.size as u64,
@@ -88,17 +102,22 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
blocks_count: 0,
block_data: file.block_data,
};
let phys_block = self.map_logical_block(&inode, logical_block)?;
let mut done = 0;
let mut block_buf = [0u8; 4096];
read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf);
while done < to_read {
let offset = file.offset + done;
let phys_block = self.map_logical_block(&inode, (offset / bs) as u32)?;
if !read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf) {
return None;
}
let byte_in_block = offset % bs;
let chunk = (to_read - done).min(bs - byte_in_block);
buf[done..done + chunk].copy_from_slice(&block_buf[byte_in_block..byte_in_block + chunk]);
done += chunk;
}
let available = bs - byte_in_block;
let chunk = to_read.min(available);
buf[..chunk].copy_from_slice(&block_buf[byte_in_block..byte_in_block + chunk]);
self.open_files[handle.raw() as usize].as_mut().map(|f| f.offset += chunk);
Some(chunk)
self.open_files[handle.raw() as usize].as_mut().map(|f| f.offset += done);
Some(done)
}
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> {
@@ -262,6 +281,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
if let Some(child_inode) = self.read_inode(child_inode_num) {
self.free_all_blocks(&child_inode);
}
self.clear_inode(child_inode_num);
self.free_inode(child_inode_num);
for slot in self.open_files.iter_mut() {
if let Some(f) = slot {
@@ -331,6 +351,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
pi.links_count += 1;
self.write_inode(parent_inode_num, &pi);
}
self.count_dir(new_inode_num);
true
}

View File

@@ -3,6 +3,22 @@ use super::io::{read_u32, write_u32, read_u16, write_u16, SECTOR_SIZE};
const EXT4_MAGIC: u16 = 0xEF53;
// What this driver understands. A disk with any other incompatible feature
// is not mounted; with any other read-only-compatible one, or a journal or
// hashed directories it would not keep up to date, it is mounted read-only.
const INCOMPAT_FILETYPE: u32 = 0x2;
const INCOMPAT_EXTENTS: u32 = 0x40;
const INCOMPAT_FLEX_BG: u32 = 0x200;
const KNOWN_INCOMPAT: u32 = INCOMPAT_FILETYPE | INCOMPAT_EXTENTS | INCOMPAT_FLEX_BG;
const RO_COMPAT_SPARSE_SUPER: u32 = 0x1;
const RO_COMPAT_LARGE_FILE: u32 = 0x2;
const RO_COMPAT_DIR_NLINK: u32 = 0x20;
const RO_COMPAT_EXTRA_ISIZE: u32 = 0x40;
const KNOWN_RO_COMPAT: u32 =
RO_COMPAT_SPARSE_SUPER | RO_COMPAT_LARGE_FILE | RO_COMPAT_DIR_NLINK | RO_COMPAT_EXTRA_ISIZE;
const COMPAT_HAS_JOURNAL: u32 = 0x4;
const COMPAT_DIR_INDEX: u32 = 0x20;
pub struct Superblock {
pub inodes_count: u32,
pub blocks_count: u32,
@@ -13,6 +29,9 @@ pub struct Superblock {
pub first_data_block: u32,
pub free_blocks_count: u32,
pub free_inodes_count: u32,
/// False when a feature it does not maintain means writing would
/// leave the disk inconsistent.
pub writable: bool,
}
impl Superblock {
@@ -22,7 +41,13 @@ impl Superblock {
if magic != EXT4_MAGIC { return None; }
let log_block_size = read_u32(data, 0x18);
let (compat, incompat, ro_compat) = (read_u32(data, 0x5C), read_u32(data, 0x60), read_u32(data, 0x64));
if incompat & !KNOWN_INCOMPAT != 0 || log_block_size > 2 {
return None;
}
let writable = ro_compat & !KNOWN_RO_COMPAT == 0 && compat & (COMPAT_HAS_JOURNAL | COMPAT_DIR_INDEX) == 0;
Some(Self {
writable,
inodes_count: read_u32(data, 0x00),
blocks_count: read_u32(data, 0x04),
block_size: 1024 << log_block_size,

View File

@@ -1,68 +0,0 @@
//! BIOS parameter block: disk geometry.
use super::SECTOR_SIZE;
pub(super) struct Bpb {
pub(super) bytes_per_sector: u16,
pub(super) sectors_per_cluster: u8,
pub(super) reserved_sectors: u16,
pub(super) num_fats: u8,
pub(super) root_entry_count: u16,
pub(super) total_sectors: u16,
pub(super) sectors_per_fat: u16,
}
impl Bpb {
/// Reads the BPB, refusing geometry the rest of this module cannot
/// work with: the disk is untrusted input like any client request.
pub(super) fn parse(sector: &[u8]) -> Option<Self> {
if sector.len() < SECTOR_SIZE {
return None;
}
if sector[510] != 0x55 || sector[511] != 0xAA {
return None;
}
let bpb = Self {
bytes_per_sector: u16::from_le_bytes([sector[11], sector[12]]),
sectors_per_cluster: sector[13],
reserved_sectors: u16::from_le_bytes([sector[14], sector[15]]),
num_fats: sector[16],
root_entry_count: u16::from_le_bytes([sector[17], sector[18]]),
total_sectors: u16::from_le_bytes([sector[19], sector[20]]),
sectors_per_fat: u16::from_le_bytes([sector[22], sector[23]]),
};
let usable = bpb.bytes_per_sector as usize == SECTOR_SIZE
&& bpb.sectors_per_cluster.is_power_of_two()
&& bpb.num_fats > 0
&& bpb.sectors_per_fat > 0;
usable.then_some(bpb)
}
pub(super) fn fat_start(&self) -> u32 {
self.reserved_sectors as u32
}
pub(super) fn root_dir_start(&self) -> u32 {
self.fat_start() + (self.num_fats as u32) * (self.sectors_per_fat as u32)
}
pub(super) fn root_dir_sectors(&self) -> u32 {
((self.root_entry_count as u32) * 32 + (self.bytes_per_sector as u32) - 1)
/ (self.bytes_per_sector as u32)
}
pub(super) fn data_start(&self) -> u32 {
self.root_dir_start() + self.root_dir_sectors()
}
/// The first sector of data cluster `cluster`. Clusters 0 and 1 are
/// reserved and hold no data.
pub(super) fn cluster_to_sector(&self, cluster: u16) -> Option<u32> {
let index = (cluster as u32).checked_sub(2)?;
Some(self.data_start() + index * (self.sectors_per_cluster as u32))
}
pub(super) fn cluster_size(&self) -> usize {
self.sectors_per_cluster as usize * SECTOR_SIZE
}
}

View File

@@ -1,151 +0,0 @@
//! Root directory entries and 8.3 names.
use super::Fat16;
use super::SECTOR_SIZE;
use crate::filesystem::FileName;
use port_block_device::{BlockDevice, SectorId};
impl<B: BlockDevice> Fat16<B> {
pub(super) fn find_in_root_dir(&mut self, name: &[u8]) -> Option<(u16, usize)> {
let mut buf = [0u8; SECTOR_SIZE];
let fat_name = to_fat_name(name);
for s in 0..self.bpb.root_dir_sectors() {
if !self
.device
.read_sector(SectorId::new(self.bpb.root_dir_start() + s), &mut buf)
{
continue;
}
for i in 0..(SECTOR_SIZE / 32) {
let off = i * 32;
if buf[off] == 0x00 || buf[off] == 0xE5 {
continue;
}
if buf[off..off + 11] == fat_name {
let cluster = u16::from_le_bytes([buf[off + 26], buf[off + 27]]);
let size = u32::from_le_bytes([
buf[off + 28],
buf[off + 29],
buf[off + 30],
buf[off + 31],
]);
return Some((cluster, size as usize));
}
}
}
None
}
pub(super) fn create_dir_entry(&mut self, name: &[u8], cluster: u16) -> bool {
let mut buf = [0u8; SECTOR_SIZE];
let fat_name = to_fat_name(name);
for s in 0..self.bpb.root_dir_sectors() {
let sector = self.bpb.root_dir_start() + s;
if !self.device.read_sector(SectorId::new(sector), &mut buf) {
continue;
}
for i in 0..(SECTOR_SIZE / 32) {
let off = i * 32;
if buf[off] == 0x00 || buf[off] == 0xE5 {
buf[off..off + 11].copy_from_slice(&fat_name);
buf[off + 11] = 0x20;
for b in &mut buf[off + 12..off + 26] {
*b = 0;
}
buf[off + 26] = cluster as u8;
buf[off + 27] = (cluster >> 8) as u8;
for b in &mut buf[off + 28..off + 32] {
*b = 0;
}
self.device.write_sector(SectorId::new(sector), &buf);
return true;
}
}
}
false
}
pub(super) fn delete_dir_entry(&mut self, name: &[u8]) -> Option<u16> {
let mut buf = [0u8; SECTOR_SIZE];
let fat_name = to_fat_name(name);
for s in 0..self.bpb.root_dir_sectors() {
let sector = self.bpb.root_dir_start() + s;
if !self.device.read_sector(SectorId::new(sector), &mut buf) {
continue;
}
for i in 0..(SECTOR_SIZE / 32) {
let off = i * 32;
if buf[off..off + 11] == fat_name {
let cluster = u16::from_le_bytes([buf[off + 26], buf[off + 27]]);
buf[off] = 0xE5;
self.device.write_sector(SectorId::new(sector), &buf);
return Some(cluster);
}
}
}
None
}
pub(super) fn update_dir_size(&mut self, name: &[u8], size: u32, cluster: u16) {
let mut buf = [0u8; SECTOR_SIZE];
let fat_name = to_fat_name(name);
for s in 0..self.bpb.root_dir_sectors() {
let sector = self.bpb.root_dir_start() + s;
if !self.device.read_sector(SectorId::new(sector), &mut buf) {
continue;
}
for i in 0..(SECTOR_SIZE / 32) {
let off = i * 32;
if buf[off..off + 11] == fat_name {
buf[off + 28..off + 32].copy_from_slice(&size.to_le_bytes());
buf[off + 26] = cluster as u8;
buf[off + 27] = (cluster >> 8) as u8;
self.device.write_sector(SectorId::new(sector), &buf);
return;
}
}
}
}
}
pub(super) fn to_fat_name(name: &[u8]) -> [u8; 11] {
let mut fat = [b' '; 11];
let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len());
for i in 0..dot_pos.min(8) {
fat[i] = name[i].to_ascii_uppercase();
}
if dot_pos < name.len() {
for i in 0..(name.len() - dot_pos - 1).min(3) {
fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase();
}
}
fat
}
pub(super) fn from_fat_name(raw: &[u8]) -> FileName {
let mut buf = [0u8; 32];
let mut pos = 0;
let name_end = raw[..8]
.iter()
.rposition(|&b| b != b' ')
.map(|i| i + 1)
.unwrap_or(0);
for i in 0..name_end {
buf[pos] = raw[i].to_ascii_lowercase();
pos += 1;
}
let ext_end = raw[8..11]
.iter()
.rposition(|&b| b != b' ')
.map(|i| i + 1)
.unwrap_or(0);
if ext_end > 0 {
buf[pos] = b'.';
pos += 1;
for i in 0..ext_end {
buf[pos] = raw[8 + i].to_ascii_lowercase();
pos += 1;
}
}
FileName::from_bytes(&buf[..pos])
}

View File

@@ -1,64 +0,0 @@
//! FAT table: cluster allocation and chains.
use super::Fat16;
use super::SECTOR_SIZE;
use port_block_device::{BlockDevice, SectorId};
impl<B: BlockDevice> Fat16<B> {
pub(super) fn read_fat_entry(&mut self, cluster: u16) -> u16 {
let fat_offset = (cluster as u32) * 2;
let sector = self.bpb.fat_start() + fat_offset / (SECTOR_SIZE as u32);
let offset = (fat_offset % (SECTOR_SIZE as u32)) as usize;
let mut buf = [0u8; SECTOR_SIZE];
if !self.device.read_sector(SectorId::new(sector), &mut buf) {
return 0xFFFF;
}
u16::from_le_bytes([buf[offset], buf[offset + 1]])
}
pub(super) fn write_fat_entry(&mut self, cluster: u16, value: u16) {
let fat_offset = (cluster as u32) * 2;
let sector = self.bpb.fat_start() + fat_offset / (SECTOR_SIZE as u32);
let offset = (fat_offset % (SECTOR_SIZE as u32)) as usize;
let mut buf = [0u8; SECTOR_SIZE];
self.device.read_sector(SectorId::new(sector), &mut buf);
buf[offset] = value as u8;
buf[offset + 1] = (value >> 8) as u8;
self.device.write_sector(SectorId::new(sector), &buf);
}
pub(super) fn alloc_cluster(&mut self) -> Option<u16> {
let max = self.bpb.total_sectors as u16 / self.bpb.sectors_per_cluster as u16;
for i in 2..max {
if self.read_fat_entry(i) == 0 {
self.write_fat_entry(i, 0xFFFF);
return Some(i);
}
}
None
}
pub(super) fn free_chain(&mut self, start: u16) {
let mut cluster = start;
loop {
if cluster < 2 || cluster >= 0xFFF8 {
break;
}
let next = self.read_fat_entry(cluster);
self.write_fat_entry(cluster, 0);
cluster = next;
}
}
pub(super) fn follow_chain(&mut self, start: u16, skip_clusters: usize) -> u16 {
let mut cluster = start;
for _ in 0..skip_clusters {
let next = self.read_fat_entry(cluster);
if next >= 0xFFF8 {
return cluster;
}
cluster = next;
}
cluster
}
}

View File

@@ -1,275 +0,0 @@
//! FAT16 filesystem over a block device (root directory only).
mod bpb;
mod dir;
mod fat;
use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem};
use bpb::Bpb;
use dir::from_fat_name;
use port_block_device::{BlockDevice, SectorId};
const SECTOR_SIZE: usize = 512;
const MAX_OPEN_FILES: usize = 8;
#[derive(Clone, Copy)]
struct OpenFile {
first_cluster: u16,
size: usize,
offset: usize,
name: FileName,
}
pub struct Fat16<B: BlockDevice> {
device: B,
bpb: Bpb,
open_files: [Option<OpenFile>; MAX_OPEN_FILES],
}
impl<B: BlockDevice> Fat16<B> {
pub fn mount(mut device: B) -> Option<Self> {
let mut buf = [0u8; SECTOR_SIZE];
if !device.read_sector(SectorId::new(0), &mut buf) {
return None;
}
let bpb = Bpb::parse(&buf)?;
Some(Self {
device,
bpb,
open_files: [const { None }; MAX_OPEN_FILES],
})
}
/// The open-file slot `handle` names; `None` for handles past the
/// table, which come straight from clients.
fn slot(&mut self, handle: FileHandle) -> Option<&mut Option<OpenFile>> {
self.open_files.get_mut(handle.raw() as usize)
}
fn alloc_handle(&self) -> Option<FileHandle> {
for i in 0..MAX_OPEN_FILES {
if self.open_files[i].is_none() {
return Some(FileHandle::new(i as u16));
}
}
None
}
}
impl<B: BlockDevice> FileSystem for Fat16<B> {
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
let (cluster, size) = self.find_in_root_dir(path)?;
let handle = self.alloc_handle()?;
*self.slot(handle)? = Some(OpenFile {
first_cluster: cluster,
size,
offset: 0,
name: FileName::from_bytes(path),
});
Some(handle)
}
fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option<usize> {
let file = (*self.slot(handle)?)?;
if file.offset >= file.size {
return Some(0);
}
let remaining = file.size - file.offset;
let to_read = buf.len().min(remaining);
let cs = self.bpb.cluster_size();
let skip_clusters = file.offset / cs;
let mut cluster = self.follow_chain(file.first_cluster, skip_clusters);
let mut skip = file.offset % cs;
let mut read = 0;
let mut sector_buf = [0u8; SECTOR_SIZE];
while read < to_read {
let sector_in_cluster = skip / SECTOR_SIZE;
let byte_in_sector = skip % SECTOR_SIZE;
let Some(base) = self.bpb.cluster_to_sector(cluster) else {
break;
};
let sector = base + sector_in_cluster as u32;
if !self
.device
.read_sector(SectorId::new(sector), &mut sector_buf)
{
break;
}
let chunk = (SECTOR_SIZE - byte_in_sector).min(to_read - read);
buf[read..read + chunk]
.copy_from_slice(&sector_buf[byte_in_sector..byte_in_sector + chunk]);
read += chunk;
skip += chunk;
if skip >= cs {
let next = self.read_fat_entry(cluster);
if next >= 0xFFF8 {
break;
}
cluster = next;
skip = 0;
}
}
if let Some(Some(f)) = self.slot(handle) {
f.offset += read;
}
Some(read)
}
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> {
let mut file = (*self.slot(handle)?)?;
let cs = self.bpb.cluster_size();
if file.first_cluster < 2 {
let c = self.alloc_cluster()?;
file.first_cluster = c;
}
let skip_clusters = file.offset / cs;
let mut cluster = self.follow_chain(file.first_cluster, skip_clusters);
let mut pos = file.offset % cs;
let mut written = 0;
let mut sector_buf = [0u8; SECTOR_SIZE];
while written < buf.len() {
let sector_in_cluster = pos / SECTOR_SIZE;
let byte_in_sector = pos % SECTOR_SIZE;
let Some(base) = self.bpb.cluster_to_sector(cluster) else {
break;
};
let sector = base + sector_in_cluster as u32;
if !self.device.read_sector(SectorId::new(sector), &mut sector_buf) {
break;
}
let chunk = (SECTOR_SIZE - byte_in_sector).min(buf.len() - written);
sector_buf[byte_in_sector..byte_in_sector + chunk]
.copy_from_slice(&buf[written..written + chunk]);
// Bytes count as written only once they reached the disk.
if !self.device.write_sector(SectorId::new(sector), &sector_buf) {
break;
}
written += chunk;
pos += chunk;
if pos >= cs {
let next = self.read_fat_entry(cluster);
if next >= 0xFFF8 {
if written < buf.len() {
if let Some(new_c) = self.alloc_cluster() {
self.write_fat_entry(cluster, new_c);
cluster = new_c;
} else {
break;
}
}
} else {
cluster = next;
}
pos = 0;
}
}
file.offset += written;
if file.offset > file.size {
file.size = file.offset;
}
*self.slot(handle)? = Some(file);
Some(written)
}
fn close(&mut self, handle: FileHandle) {
let Some(file) = self.slot(handle).and_then(Option::take) else {
return;
};
let mut name_buf = [0u8; 32];
let name_bytes = file.name.as_bytes();
let len = name_bytes.len().min(32);
name_buf[..len].copy_from_slice(&name_bytes[..len]);
self.update_dir_size(&name_buf[..len], file.size as u32, file.first_cluster);
}
fn list_dir(&mut self, _path: &[u8], entries: &mut [DirEntry]) -> Option<usize> {
let mut buf = [0u8; SECTOR_SIZE];
let mut count = 0;
for s in 0..self.bpb.root_dir_sectors() {
if !self
.device
.read_sector(SectorId::new(self.bpb.root_dir_start() + s), &mut buf)
{
continue;
}
for i in 0..(SECTOR_SIZE / 32) {
if count >= entries.len() {
return Some(count);
}
let off = i * 32;
if buf[off] == 0x00 {
return Some(count);
}
if buf[off] == 0xE5 || buf[off + 11] & 0x08 != 0 {
continue;
}
let size = u32::from_le_bytes([
buf[off + 28],
buf[off + 29],
buf[off + 30],
buf[off + 31],
]);
entries[count] = DirEntry {
name: from_fat_name(&buf[off..off + 11]),
size: size as usize,
is_dir: buf[off + 11] & 0x10 != 0,
};
count += 1;
}
}
Some(count)
}
fn create(&mut self, path: &[u8]) -> Option<FileHandle> {
if self.find_in_root_dir(path).is_some() {
return None;
}
let cluster = self.alloc_cluster()?;
if !self.create_dir_entry(path, cluster) {
self.write_fat_entry(cluster, 0);
return None;
}
let handle = self.alloc_handle()?;
*self.slot(handle)? = Some(OpenFile {
first_cluster: cluster,
size: 0,
offset: 0,
name: FileName::from_bytes(path),
});
Some(handle)
}
fn delete(&mut self, path: &[u8]) -> bool {
if let Some(cluster) = self.delete_dir_entry(path) {
self.free_chain(cluster);
true
} else {
false
}
}
fn file_size(&mut self, handle: FileHandle) -> Option<usize> {
self.slot(handle)?.as_ref().map(|f| f.size)
}
fn mkdir(&mut self, _path: &[u8]) -> bool {
false
}
fn writable(&self) -> bool {
self.device.writable()
}
}

View File

@@ -1,2 +1 @@
pub mod ext4;
pub mod fat16;