feat(storage-server): implement FAT16 filesystem with read, write, create, delete, list, and cluster chain management
This commit is contained in:
396
crates/servers/storage-server/src/fs/fat16.rs
Normal file
396
crates/servers/storage-server/src/fs/fat16.rs
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
use crate::block_device::{BlockDevice, SectorId};
|
||||||
|
use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem};
|
||||||
|
|
||||||
|
const SECTOR_SIZE: usize = 512;
|
||||||
|
const MAX_OPEN_FILES: usize = 8;
|
||||||
|
|
||||||
|
struct Bpb {
|
||||||
|
bytes_per_sector: u16,
|
||||||
|
sectors_per_cluster: u8,
|
||||||
|
reserved_sectors: u16,
|
||||||
|
num_fats: u8,
|
||||||
|
root_entry_count: u16,
|
||||||
|
total_sectors: u16,
|
||||||
|
sectors_per_fat: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bpb {
|
||||||
|
fn parse(sector: &[u8]) -> Option<Self> {
|
||||||
|
if sector.len() < SECTOR_SIZE { return None; }
|
||||||
|
if sector[510] != 0x55 || sector[511] != 0xAA { return None; }
|
||||||
|
Some(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]]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fat_start(&self) -> u32 { self.reserved_sectors as u32 }
|
||||||
|
|
||||||
|
fn root_dir_start(&self) -> u32 {
|
||||||
|
self.fat_start() + (self.num_fats as u32) * (self.sectors_per_fat as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_start(&self) -> u32 {
|
||||||
|
self.root_dir_start() + self.root_dir_sectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cluster_to_sector(&self, cluster: u16) -> u32 {
|
||||||
|
self.data_start() + ((cluster as u32) - 2) * (self.sectors_per_cluster as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cluster_size(&self) -> usize {
|
||||||
|
self.sectors_per_cluster as usize * SECTOR_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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] })
|
||||||
|
}
|
||||||
|
|
||||||
|
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]])
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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.open_files[handle.raw() as usize] = 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.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);
|
||||||
|
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 sector = self.bpb.cluster_to_sector(cluster) + 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(§or_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.open_files[handle.raw() as usize].as_mut().map(|f| f.offset += read);
|
||||||
|
Some(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> {
|
||||||
|
let mut file = *self.open_files[handle.raw() as usize].as_ref()?;
|
||||||
|
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 sector = self.bpb.cluster_to_sector(cluster) + sector_in_cluster as u32;
|
||||||
|
self.device.read_sector(SectorId::new(sector), &mut sector_buf);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
self.device.write_sector(SectorId::new(sector), §or_buf);
|
||||||
|
|
||||||
|
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.open_files[handle.raw() as usize] = Some(file);
|
||||||
|
Some(written)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self, handle: FileHandle) {
|
||||||
|
let file = match self.open_files[handle.raw() as usize].take() {
|
||||||
|
Some(f) => f,
|
||||||
|
None => 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.open_files[handle.raw() as usize] = 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.open_files[handle.raw() as usize].as_ref().map(|f| f.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
@@ -1,3 +1 @@
|
|||||||
// Filesystem implementations go here.
|
pub mod fat16;
|
||||||
// Each implements FileSystem using a BlockDevice.
|
|
||||||
// e.g., fat16.rs: pub struct Fat16<B: BlockDevice> { ... }
|
|
||||||
|
|||||||
112
crates/servers/storage-server/src/tests/fat16.rs
Normal file
112
crates/servers/storage-server/src/tests/fat16.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
use crate::adapters::mock_block::MockBlockDevice;
|
||||||
|
use crate::block_device::{BlockDevice, SectorId};
|
||||||
|
use crate::filesystem::{DirEntry, FileSystem};
|
||||||
|
use crate::fs::fat16::Fat16;
|
||||||
|
|
||||||
|
fn format_fat16_disk(sectors: usize) -> MockBlockDevice {
|
||||||
|
let mut dev = MockBlockDevice::new(sectors);
|
||||||
|
let mut boot = [0u8; 512];
|
||||||
|
|
||||||
|
// BPB
|
||||||
|
boot[11] = 0x00; boot[12] = 0x02; // bytes_per_sector = 512
|
||||||
|
boot[13] = 1; // sectors_per_cluster = 1
|
||||||
|
boot[14] = 1; boot[15] = 0; // reserved_sectors = 1
|
||||||
|
boot[16] = 1; // num_fats = 1
|
||||||
|
boot[17] = 16; boot[18] = 0; // root_entry_count = 16
|
||||||
|
boot[19] = sectors as u8; // total_sectors
|
||||||
|
boot[20] = (sectors >> 8) as u8;
|
||||||
|
boot[22] = 1; boot[23] = 0; // sectors_per_fat = 1
|
||||||
|
|
||||||
|
// boot signature
|
||||||
|
boot[510] = 0x55;
|
||||||
|
boot[511] = 0xAA;
|
||||||
|
|
||||||
|
dev.write_sector(SectorId::new(0), &boot);
|
||||||
|
|
||||||
|
// FAT sector (sector 1) — all zeros = all free
|
||||||
|
let fat = [0u8; 512];
|
||||||
|
dev.write_sector(SectorId::new(1), &fat);
|
||||||
|
|
||||||
|
dev
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_test_disk() -> Fat16<MockBlockDevice> {
|
||||||
|
let dev = format_fat16_disk(32);
|
||||||
|
Fat16::mount(dev).expect("mount failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mount_valid_disk() {
|
||||||
|
let dev = format_fat16_disk(32);
|
||||||
|
assert!(Fat16::mount(dev).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mount_invalid_disk() {
|
||||||
|
let dev = MockBlockDevice::new(32); // no boot signature
|
||||||
|
assert!(Fat16::mount(dev).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_and_open_file() {
|
||||||
|
let mut fs = mount_test_disk();
|
||||||
|
let h = fs.create(b"hello.txt").expect("create");
|
||||||
|
fs.close(h);
|
||||||
|
|
||||||
|
let h2 = fs.open(b"hello.txt").expect("open");
|
||||||
|
assert_eq!(fs.file_size(h2), Some(0));
|
||||||
|
fs.close(h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_and_read_file() {
|
||||||
|
let mut fs = mount_test_disk();
|
||||||
|
let h = fs.create(b"data.bin").expect("create");
|
||||||
|
|
||||||
|
let data = b"Hello, FAT16!";
|
||||||
|
let written = fs.write(h, data).expect("write");
|
||||||
|
assert_eq!(written, data.len());
|
||||||
|
|
||||||
|
fs.close(h);
|
||||||
|
|
||||||
|
let h2 = fs.open(b"data.bin").expect("open");
|
||||||
|
assert_eq!(fs.file_size(h2), Some(data.len()));
|
||||||
|
|
||||||
|
let mut buf = [0u8; 32];
|
||||||
|
let read = fs.read(h2, &mut buf).expect("read");
|
||||||
|
assert_eq!(read, data.len());
|
||||||
|
assert_eq!(&buf[..read], data);
|
||||||
|
fs.close(h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_file() {
|
||||||
|
let mut fs = mount_test_disk();
|
||||||
|
fs.create(b"temp.txt").map(|h| fs.close(h));
|
||||||
|
assert!(fs.delete(b"temp.txt"));
|
||||||
|
assert!(fs.open(b"temp.txt").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_directory() {
|
||||||
|
let mut fs = mount_test_disk();
|
||||||
|
fs.create(b"a.txt").map(|h| fs.close(h));
|
||||||
|
fs.create(b"b.txt").map(|h| fs.close(h));
|
||||||
|
|
||||||
|
let mut entries = [DirEntry::empty(); 16];
|
||||||
|
let count = fs.list_dir(b"/", &mut entries).expect("list");
|
||||||
|
assert_eq!(count, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn open_nonexistent_returns_none() {
|
||||||
|
let mut fs = mount_test_disk();
|
||||||
|
assert!(fs.open(b"nope.txt").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_duplicate_returns_none() {
|
||||||
|
let mut fs = mount_test_disk();
|
||||||
|
fs.create(b"dup.txt").map(|h| fs.close(h));
|
||||||
|
assert!(fs.create(b"dup.txt").is_none());
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
mod fat16;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod server;
|
mod server;
|
||||||
|
|||||||
Reference in New Issue
Block a user