2025-09-08 20:52:06 -07:00
|
|
|
use crate::fs::{Record, RecordType};
|
2025-09-11 16:00:04 -07:00
|
|
|
use crate::strprint::u32_hex;
|
2025-09-08 20:52:06 -07:00
|
|
|
|
|
|
|
|
#[repr(packed(1), C)]
|
|
|
|
|
pub struct BPB {
|
|
|
|
|
pub bootjmp: [u8; 3],
|
|
|
|
|
pub oem_name: [u8; 8],
|
|
|
|
|
pub bytes_per_sector: u16,
|
|
|
|
|
pub sectors_per_cluster: u8,
|
|
|
|
|
pub reserved_sectors: u16,
|
|
|
|
|
pub num_fats: u8,
|
|
|
|
|
pub root_entry_count: u16,
|
|
|
|
|
pub total_sectors_16: u16,
|
|
|
|
|
pub media_type: u8,
|
|
|
|
|
pub sectors_per_fat16: u16,
|
|
|
|
|
pub sectors_per_track: u16,
|
|
|
|
|
pub head_count: u16,
|
|
|
|
|
pub hidden_sectors: u32,
|
|
|
|
|
pub large_sector_count: u32,
|
|
|
|
|
pub sectors_per_fat: u32,
|
|
|
|
|
pub flags: u16,
|
|
|
|
|
pub version: u16,
|
|
|
|
|
pub root_cluster: u32,
|
|
|
|
|
pub fsinfo_sector: u16,
|
|
|
|
|
pub backup_boot_sector: u16,
|
|
|
|
|
pub reserved0: [u8; 12],
|
|
|
|
|
pub drive_number: u8,
|
|
|
|
|
pub reserved1: u8,
|
|
|
|
|
pub signature: u8,
|
|
|
|
|
pub serial_number: u32,
|
|
|
|
|
pub label: [u8; 11],
|
|
|
|
|
pub identifier: [u8; 8],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
pub struct BPBUseful {
|
|
|
|
|
pub oem_name: [u8; 8],
|
|
|
|
|
pub reserved_sectors: u16,
|
|
|
|
|
pub num_fats: u8,
|
|
|
|
|
pub sectors_per_fat_16: u32,
|
|
|
|
|
pub sectors_per_fat: u32,
|
|
|
|
|
pub sectors_per_cluster: u8,
|
|
|
|
|
pub root_cluster: u32,
|
|
|
|
|
pub padding: [u8; 1],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BPBUseful {
|
|
|
|
|
pub fn empty() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
oem_name: [0; 8],
|
|
|
|
|
reserved_sectors: 0,
|
|
|
|
|
num_fats: 0,
|
|
|
|
|
sectors_per_fat_16: 0,
|
|
|
|
|
sectors_per_fat: 0,
|
|
|
|
|
sectors_per_cluster: 0,
|
|
|
|
|
root_cluster: 0,
|
|
|
|
|
padding: [0; 1],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn fat_size(&self) -> usize {
|
|
|
|
|
self.sectors_per_fat as usize
|
|
|
|
|
}
|
|
|
|
|
pub fn first_data_sector(&self) -> usize {
|
|
|
|
|
self.reserved_sectors as usize + (self.num_fats as usize * self.fat_size())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn first_fat_sector(&self) -> usize {
|
|
|
|
|
self.reserved_sectors as usize
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[repr(packed(1), C)]
|
|
|
|
|
pub struct DirectoryEntry {
|
|
|
|
|
pub name: [u8; 8],
|
|
|
|
|
pub ext: [u8; 3],
|
|
|
|
|
pub attributes: u8,
|
|
|
|
|
pub reserved: u8,
|
|
|
|
|
pub creation_time_tenth: u8,
|
|
|
|
|
pub creation_time: u16,
|
|
|
|
|
pub creation_date: u16,
|
|
|
|
|
pub last_access_date: u16,
|
|
|
|
|
pub first_cluster_high: u16,
|
|
|
|
|
pub last_modified_time: u16,
|
|
|
|
|
pub last_modified_date: u16,
|
|
|
|
|
pub first_cluster_low: u16,
|
|
|
|
|
pub file_size: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
#[repr(packed(1), C)]
|
|
|
|
|
pub struct LongFileNameEntry {
|
|
|
|
|
pub order: u8,
|
|
|
|
|
pub first_five_two_byte_characters: [u16; 5], // [u8; 10]
|
|
|
|
|
pub attributes: u8, // always 0x0F
|
|
|
|
|
pub long_entry_type: u8,
|
|
|
|
|
pub checksum: u8,
|
|
|
|
|
pub next_six_two_byte_characters: [u16; 6], // [u8; 12]
|
|
|
|
|
pub reserved: u16,
|
|
|
|
|
pub final_two_two_byte_characters: [u16; 2], // [u8; 4]
|
|
|
|
|
pub directory_entry: DirectoryEntry,
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
pub struct Fat32DirectoryReader {
|
|
|
|
|
pub underlying: Fat32FileReader,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Fat32DirectoryReader {
|
|
|
|
|
pub fn empty() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
underlying: Fat32FileReader::empty(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
|
|
|
pub struct Fat32FileReader {
|
|
|
|
|
pub cluster: usize,
|
|
|
|
|
pub sector: usize,
|
|
|
|
|
pub cluster_idx: usize,
|
|
|
|
|
pub sector_offset: usize,
|
|
|
|
|
pub eof: u8,
|
|
|
|
|
pub implementation: u32,
|
|
|
|
|
pub padding: [u8; 32 - 24],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Fat32FileReader {
|
|
|
|
|
pub fn empty() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
cluster: 0,
|
|
|
|
|
sector: 0,
|
|
|
|
|
cluster_idx: 0,
|
|
|
|
|
sector_offset: 0,
|
|
|
|
|
eof: 0,
|
|
|
|
|
implementation: 0,
|
|
|
|
|
padding: [0; 32 - 24],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub enum Fat32Error {
|
|
|
|
|
CouldNotReadBPB,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read_bpb() -> Result<BPBUseful, Fat32Error> {
|
|
|
|
|
let bpb_addr = crate::syscalls::alloc_blocks(1);
|
|
|
|
|
if crate::syscalls::read_hbd(0, bpb_addr, 1) != 0 {
|
|
|
|
|
return Err(Fat32Error::CouldNotReadBPB);
|
|
|
|
|
}
|
|
|
|
|
let bpb = unsafe { (bpb_addr as *const BPB).read_volatile() };
|
|
|
|
|
crate::syscalls::free_blocks(bpb_addr, 1);
|
|
|
|
|
Ok(BPBUseful {
|
|
|
|
|
oem_name: bpb.oem_name,
|
|
|
|
|
reserved_sectors: bpb.reserved_sectors,
|
|
|
|
|
num_fats: bpb.num_fats,
|
|
|
|
|
sectors_per_fat_16: bpb.sectors_per_fat16 as u32,
|
|
|
|
|
sectors_per_fat: bpb.sectors_per_fat,
|
|
|
|
|
sectors_per_cluster: bpb.sectors_per_cluster,
|
|
|
|
|
root_cluster: bpb.root_cluster,
|
|
|
|
|
padding: [0; 1],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reader_from_cluster(bpb: &BPBUseful, cluster: usize) -> Fat32FileReader {
|
|
|
|
|
let first_sector_of_cluster =
|
|
|
|
|
((cluster - 2) * bpb.sectors_per_cluster as usize) + bpb.first_data_sector();
|
|
|
|
|
Fat32FileReader {
|
|
|
|
|
cluster,
|
|
|
|
|
sector: first_sector_of_cluster,
|
|
|
|
|
cluster_idx: 0,
|
|
|
|
|
sector_offset: 0,
|
|
|
|
|
eof: 0,
|
|
|
|
|
implementation: 0,
|
|
|
|
|
padding: [0; 32 - 24],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 09:17:42 -07:00
|
|
|
pub fn root_target(bpb: &BPBUseful) -> u32 {
|
|
|
|
|
bpb.root_cluster as u32
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-08 20:52:06 -07:00
|
|
|
pub fn root_reader(bpb: &BPBUseful) -> Fat32DirectoryReader {
|
|
|
|
|
let cluster = bpb.root_cluster as usize;
|
|
|
|
|
Fat32DirectoryReader {
|
|
|
|
|
underlying: reader_from_cluster(bpb, cluster),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// returns true if the record was read successfully, false if the end of the directory was reached
|
|
|
|
|
pub fn read_one_record(
|
|
|
|
|
bpb: &BPBUseful,
|
|
|
|
|
reader: &mut Fat32DirectoryReader,
|
|
|
|
|
record: &mut Record,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let block_addr = crate::syscalls::alloc_blocks(1);
|
|
|
|
|
let buf = unsafe { core::slice::from_raw_parts_mut(block_addr as *mut u8, 512) };
|
|
|
|
|
// read without seeking
|
|
|
|
|
let old_reader = reader.underlying;
|
|
|
|
|
if !read_file(bpb, &mut reader.underlying, buf) {
|
|
|
|
|
crate::syscalls::free_blocks(block_addr, 1);
|
|
|
|
|
crate::syscalls::write_terminal(b"eof on dir");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
reader.underlying = old_reader;
|
|
|
|
|
let mut done = false;
|
|
|
|
|
while !done {
|
|
|
|
|
let entry = unsafe {
|
|
|
|
|
((buf.as_ptr() as usize + reader.underlying.implementation as usize)
|
|
|
|
|
as *const DirectoryEntry)
|
|
|
|
|
.read_volatile()
|
|
|
|
|
};
|
|
|
|
|
if entry.name[0] == 0x00 {
|
|
|
|
|
// end of directory
|
|
|
|
|
crate::syscalls::free_blocks(block_addr, 1);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
#[allow(clippy::collapsible_if)]
|
|
|
|
|
if entry.name[0] != 0xE5 {
|
|
|
|
|
if entry.attributes != 0x0F {
|
|
|
|
|
// for now we do not support long filenames
|
|
|
|
|
let mut namelen = 0;
|
|
|
|
|
let mut last_was_space = false;
|
|
|
|
|
for i in 0..8 {
|
|
|
|
|
if last_was_space {
|
|
|
|
|
if entry.name[i] != b' ' {
|
|
|
|
|
last_was_space = false;
|
|
|
|
|
}
|
|
|
|
|
} else if entry.name[i] == b' ' {
|
|
|
|
|
namelen = i;
|
|
|
|
|
last_was_space = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if namelen == 0 {
|
|
|
|
|
namelen = 8;
|
|
|
|
|
}
|
|
|
|
|
record.name = [0; 12];
|
|
|
|
|
record.name[..namelen].copy_from_slice(&entry.name[..namelen]);
|
|
|
|
|
if entry.attributes & 0x10 != 0 {
|
|
|
|
|
record.record_type = RecordType::Directory as u8;
|
|
|
|
|
} else {
|
|
|
|
|
record.record_type = RecordType::File as u8;
|
|
|
|
|
record.name[namelen] = b'.';
|
|
|
|
|
record.name[namelen + 1] = entry.ext[0];
|
|
|
|
|
record.name[namelen + 2] = entry.ext[1];
|
|
|
|
|
record.name[namelen + 3] = entry.ext[2];
|
|
|
|
|
}
|
|
|
|
|
record.target =
|
|
|
|
|
(entry.first_cluster_high as u32) << 16 | (entry.first_cluster_low as u32);
|
|
|
|
|
record.total_size_bytes = entry.file_size;
|
|
|
|
|
done = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reader.underlying.implementation += 32;
|
|
|
|
|
if reader.underlying.implementation >= 512 {
|
|
|
|
|
reader.underlying.implementation = 0;
|
|
|
|
|
seek_forward_one_sector(bpb, &mut reader.underlying);
|
|
|
|
|
if !read_file(bpb, &mut reader.underlying, buf) {
|
|
|
|
|
crate::syscalls::free_blocks(block_addr, 1);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
crate::syscalls::free_blocks(block_addr, 1);
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn seek_forward_one_sector(bpb: &BPBUseful, reader: &mut Fat32FileReader) -> bool {
|
|
|
|
|
if reader.eof != 0 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
reader.sector += 1;
|
|
|
|
|
reader.sector_offset = 0;
|
|
|
|
|
reader.cluster_idx += 1;
|
|
|
|
|
// if we reached the end of the cluster, let's consult the FAT to see where to go next
|
|
|
|
|
if reader.cluster_idx >= bpb.sectors_per_cluster as usize {
|
|
|
|
|
let fat_addr = crate::syscalls::alloc_blocks(1);
|
|
|
|
|
let fat_ofs = reader.cluster * 4;
|
|
|
|
|
let fat_sector = bpb.first_fat_sector() + (fat_ofs / 512);
|
|
|
|
|
let ent_offset = fat_ofs % 512;
|
|
|
|
|
if crate::syscalls::read_hbd(fat_sector, fat_addr, 1) != 0 {
|
|
|
|
|
crate::syscalls::free_blocks(fat_addr, 1);
|
|
|
|
|
crate::syscalls::write_terminal(b"fatfailure");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
let table_value =
|
|
|
|
|
unsafe { ((fat_addr+(ent_offset)) as *const u32).read_volatile() } & 0x0FFFFFFF;
|
2025-09-11 16:00:04 -07:00
|
|
|
crate::syscalls::free_blocks(fat_addr, 1);
|
2025-09-08 20:52:06 -07:00
|
|
|
if table_value >= 0x0FFFFFF8 {
|
|
|
|
|
// no more clusters, whole file has been read
|
|
|
|
|
reader.eof = 1;
|
|
|
|
|
return false;
|
|
|
|
|
} else if table_value >= 0x00000002 {
|
|
|
|
|
reader.cluster = table_value as usize;
|
|
|
|
|
reader.cluster_idx = 0;
|
|
|
|
|
reader.sector =
|
|
|
|
|
((reader.cluster - 2) * bpb.sectors_per_cluster as usize) + bpb.first_data_sector();
|
|
|
|
|
reader.sector_offset = 0;
|
|
|
|
|
if table_value == 0x00000000 || table_value == 0x00000001 {
|
|
|
|
|
reader.eof = 1;
|
|
|
|
|
return false;
|
|
|
|
|
} else if table_value == 0x0FFFFFF7 {
|
|
|
|
|
// bad cluster, stop reading
|
|
|
|
|
crate::syscalls::write_terminal(b"badcluster");
|
|
|
|
|
reader.eof = 1;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// cluster is free, keep going
|
|
|
|
|
reader.eof = 1;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// continue reading
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read_file(bpb: &BPBUseful, reader: &mut Fat32FileReader, buffer: &mut [u8]) -> bool {
|
|
|
|
|
if reader.eof != 0 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
let mut buffer_idx = 0;
|
|
|
|
|
let sector_addr = crate::syscalls::alloc_blocks(1);
|
|
|
|
|
loop {
|
|
|
|
|
if reader.eof != 0 {
|
|
|
|
|
crate::syscalls::free_blocks(sector_addr, 1);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// read current sector
|
|
|
|
|
if crate::syscalls::read_hbd(reader.sector, sector_addr, 1) != 0 {
|
|
|
|
|
crate::syscalls::free_blocks(sector_addr, 1);
|
|
|
|
|
crate::syscalls::write_terminal(b"badhdb");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
let mut left_in_sector = 512 - reader.sector_offset;
|
|
|
|
|
if left_in_sector == 0 {
|
|
|
|
|
crate::syscalls::write_terminal(b"SECTORNULL?");
|
2025-09-11 16:00:04 -07:00
|
|
|
crate::syscalls::free_blocks(sector_addr, 1);
|
2025-09-08 20:52:06 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
while buffer_idx < buffer.len() && left_in_sector > 0 {
|
|
|
|
|
buffer[buffer_idx] = unsafe {
|
|
|
|
|
(sector_addr as *const u8)
|
|
|
|
|
.add(reader.sector_offset)
|
|
|
|
|
.read_volatile()
|
|
|
|
|
};
|
|
|
|
|
buffer_idx += 1;
|
|
|
|
|
left_in_sector -= 1;
|
|
|
|
|
reader.sector_offset += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if sector is full, make sure next read will start at the correct position
|
|
|
|
|
#[allow(clippy::collapsible_if)]
|
|
|
|
|
if left_in_sector == 0 {
|
|
|
|
|
// we don't care about the return value because if it fails, it'll be handled by the next iteration
|
|
|
|
|
seek_forward_one_sector(bpb, reader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// either buffer got filled, or we need to read the next sector
|
|
|
|
|
if buffer_idx < buffer.len() {
|
|
|
|
|
// we must've reached the end of the sector, let's read the next one
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
// buffer got filled, we're done
|
|
|
|
|
crate::syscalls::free_blocks(sector_addr, 1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
pub fn read_directory(bpb: &BPBUseful, cluster: usize) {
|
|
|
|
|
let mut first_sector_of_cluster =
|
|
|
|
|
((cluster - 2) * bpb.sectors_per_cluster as usize) + bpb.first_data_sector();
|
|
|
|
|
let mut fsoc_addr = crate::syscalls::alloc_blocks(1);
|
|
|
|
|
if crate::syscalls::read_hbd(first_sector_of_cluster, fsoc_addr, 1) != 0 {
|
|
|
|
|
crate::syscalls::free_blocks(fsoc_addr, 1);
|
|
|
|
|
crate::syscalls::write_terminal(b"fsoc");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let mut traveled_in_sector = 0;
|
|
|
|
|
loop {
|
|
|
|
|
let entry = unsafe { (fsoc_addr as *const DirectoryEntry).read_volatile() };
|
|
|
|
|
if entry.name[0] == 0x00 {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if entry.name[0] != 0xE5 {
|
|
|
|
|
if entry.attributes == 0x0F {
|
|
|
|
|
//crate::syscalls::write_terminal(b" unsupported utf16");
|
|
|
|
|
} else {
|
|
|
|
|
crate::syscalls::write_terminal(b" - ");
|
|
|
|
|
let namelen = entry.name.iter().position(|&x| x == 0).unwrap_or(8);
|
|
|
|
|
crate::syscalls::write_terminal(&entry.name[..namelen]);
|
|
|
|
|
if entry.attributes & 0x10 != 0 {
|
|
|
|
|
crate::syscalls::write_terminal(b" (dir)");
|
|
|
|
|
} else {
|
|
|
|
|
let extlen = entry.ext.iter().position(|&x| x == 0).unwrap_or(3);
|
|
|
|
|
crate::syscalls::write_terminal(b".");
|
|
|
|
|
crate::syscalls::write_terminal(&entry.ext[..extlen]);
|
|
|
|
|
}
|
|
|
|
|
crate::syscalls::write_terminal(b"\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fsoc_addr += 32;
|
|
|
|
|
traveled_in_sector += 32;
|
|
|
|
|
if traveled_in_sector >= 512 {
|
|
|
|
|
fsoc_addr -= traveled_in_sector;
|
|
|
|
|
traveled_in_sector = 0;
|
|
|
|
|
first_sector_of_cluster += 1;
|
|
|
|
|
if crate::syscalls::read_hbd(first_sector_of_cluster, fsoc_addr, 1) != 0 {
|
|
|
|
|
crate::syscalls::free_blocks(fsoc_addr, 1);
|
|
|
|
|
crate::syscalls::write_terminal(b"fsoc");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|