moved low-level code into fat-bits subcrate
This commit is contained in:
parent
fd24c27338
commit
f3d411f821
17 changed files with 187 additions and 46 deletions
251
fat-bits/src/lib.rs
Normal file
251
fat-bits/src/lib.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
use std::cell::RefCell;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::dir::{DirIter, RegularDirEntry};
|
||||
use crate::fat::{FatError, Fatty};
|
||||
use crate::subslice::{SubSlice, SubSliceMut};
|
||||
|
||||
pub mod bpb;
|
||||
mod datetime;
|
||||
pub mod dir;
|
||||
pub mod fat;
|
||||
pub mod fs_info;
|
||||
mod iter;
|
||||
mod subslice;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FatType {
|
||||
Fat12,
|
||||
Fat16,
|
||||
Fat32,
|
||||
}
|
||||
|
||||
pub trait SliceLike {
|
||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()>;
|
||||
|
||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()>;
|
||||
|
||||
fn read_array_at_offset<const N: usize>(&mut self, offset: u64) -> std::io::Result<[u8; N]> {
|
||||
let mut buf = [0; N];
|
||||
|
||||
self.read_at_offset(offset, &mut buf)?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceLike for &mut [u8] {
|
||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
if offset as usize + buf.len() > self.len() {
|
||||
return Err(std::io::Error::other(anyhow::anyhow!(
|
||||
"reading {} bytes at offset {} is out of bounds for slice of len {}",
|
||||
buf.len(),
|
||||
offset,
|
||||
self.len()
|
||||
)));
|
||||
}
|
||||
|
||||
buf.copy_from_slice(&self[offset as usize..][..buf.len()]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
||||
if offset as usize + bytes.len() > self.len() {
|
||||
return Err(std::io::Error::other(anyhow::anyhow!(
|
||||
"writing {} bytes at offset {} is out of bounds for slice of len {}",
|
||||
bytes.len(),
|
||||
offset,
|
||||
self.len()
|
||||
)));
|
||||
}
|
||||
|
||||
self[offset as usize..][..bytes.len()].copy_from_slice(bytes);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceLike for std::fs::File {
|
||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
self.seek(SeekFrom::Start(offset))?;
|
||||
|
||||
self.read_exact(buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
||||
self.seek(SeekFrom::Start(offset))?;
|
||||
|
||||
self.write_all(bytes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct FatFs<S: SliceLike> {
|
||||
inner: Rc<RefCell<S>>,
|
||||
|
||||
fat_offset: u64,
|
||||
fat_size: usize,
|
||||
|
||||
root_dir_offset: Option<u64>,
|
||||
root_dir_size: usize,
|
||||
|
||||
pub data_offset: u64,
|
||||
data_size: usize,
|
||||
|
||||
bytes_per_cluster: usize,
|
||||
|
||||
bpb: bpb::Bpb,
|
||||
|
||||
fat: fat::Fat,
|
||||
}
|
||||
|
||||
impl<S: SliceLike> FatFs<S> {
|
||||
pub fn load(mut data: S) -> anyhow::Result<FatFs<S>> {
|
||||
let bpb_bytes: [u8; 512] = data.read_array_at_offset(0)?;
|
||||
|
||||
let bpb = bpb::Bpb::load(&bpb_bytes)?;
|
||||
|
||||
let mut fat_buf = vec![0; bpb.fat_len_bytes()];
|
||||
|
||||
data.read_at_offset(bpb.fat_offset(), &mut fat_buf)?;
|
||||
|
||||
let fat = fat::Fat::new(bpb.fat_type(), &fat_buf, bpb.count_of_clusters());
|
||||
|
||||
// {
|
||||
// let eof = fat.get_eof_cluster();
|
||||
|
||||
// if fat.get_entry(0) != (eof & !0xFF) | bpb.media() as u32 {
|
||||
// eprintln!("warning: first sector entry should have media in lowest byte");
|
||||
// }
|
||||
|
||||
// if fat.get_entry(1) != eof {
|
||||
// eprintln!(
|
||||
// "warning: second sector entry should be EOF, not {:#X}",
|
||||
// fat.get_entry(1)
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
let fat_offset = bpb.fat_offset();
|
||||
let fat_size = bpb.fat_len_bytes();
|
||||
|
||||
let root_dir_offset = bpb.root_directory_offset();
|
||||
let root_dir_size = bpb.root_dir_len_bytes();
|
||||
|
||||
let data_offset = bpb.data_offset();
|
||||
let data_size = bpb.data_len_bytes();
|
||||
|
||||
let bytes_per_cluster = bpb.bytes_per_cluster();
|
||||
|
||||
let data = Rc::new(RefCell::new(data));
|
||||
|
||||
Ok(FatFs {
|
||||
inner: data,
|
||||
fat_offset,
|
||||
fat_size,
|
||||
root_dir_offset,
|
||||
root_dir_size,
|
||||
data_offset,
|
||||
data_size,
|
||||
bytes_per_cluster,
|
||||
bpb,
|
||||
fat,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bpb(&self) -> &bpb::Bpb {
|
||||
&self.bpb
|
||||
}
|
||||
|
||||
pub fn fat(&self) -> &fat::Fat {
|
||||
&self.fat
|
||||
}
|
||||
|
||||
/// byte offset of data cluster
|
||||
pub fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
|
||||
// assert!(cluster >= 2);
|
||||
|
||||
assert!(self.fat().get_valid_clusters().contains(&cluster));
|
||||
|
||||
self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64
|
||||
}
|
||||
|
||||
/// next data cluster or None is cluster is EOF
|
||||
///
|
||||
/// giving an invalid cluster (free, reserved, or defective) returns an appropriate error
|
||||
pub fn next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
||||
self.fat().get_next_cluster(cluster)
|
||||
}
|
||||
|
||||
pub fn cluster_as_subslice_mut(&mut self, cluster: u32) -> SubSliceMut<'_, S> {
|
||||
let offset = self.data_cluster_to_offset(cluster);
|
||||
|
||||
SubSliceMut::new(self, offset, self.bytes_per_cluster)
|
||||
}
|
||||
|
||||
pub fn cluster_as_subslice(&self, cluster: u32) -> SubSlice<'_, S> {
|
||||
let offset = self.data_cluster_to_offset(cluster);
|
||||
|
||||
SubSlice::new(self, offset, self.bytes_per_cluster)
|
||||
}
|
||||
|
||||
pub fn root_dir_bytes(&mut self) -> std::io::Result<Vec<u8>> {
|
||||
if let Some(root_dir_offset) = self.root_dir_offset {
|
||||
let mut data = Vec::new();
|
||||
|
||||
let mut subslice = SubSliceMut::new(self, root_dir_offset, self.root_dir_size);
|
||||
|
||||
subslice.read_to_end(&mut data)?;
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
let mut cluster = self.bpb().root_cluster().unwrap();
|
||||
|
||||
let mut data = vec![0; self.bytes_per_cluster];
|
||||
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
inner.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
|
||||
|
||||
while let Ok(Some(next_cluster)) = self.next_cluster(cluster) {
|
||||
cluster = next_cluster;
|
||||
|
||||
inner.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn root_dir_iter(&self) -> Box<dyn Iterator<Item = RegularDirEntry> + '_> {
|
||||
// TODO: maybe wrap this in another RootDirIter enum, so we don't have to Box<dyn>
|
||||
|
||||
if let Some(root_dir_offset) = self.root_dir_offset {
|
||||
// FAT12/FAT16
|
||||
|
||||
let sub_slice = SubSlice::new(self, root_dir_offset, self.root_dir_size);
|
||||
|
||||
return Box::new(DirIter::new(sub_slice));
|
||||
}
|
||||
|
||||
// FAT32
|
||||
|
||||
// can't fail; we're in the FAT32 case
|
||||
let root_cluster = self.bpb().root_cluster().unwrap();
|
||||
|
||||
let cluster_iter = iter::ClusterChainReader::new(self, root_cluster);
|
||||
|
||||
Box::new(DirIter::new(cluster_iter))
|
||||
}
|
||||
|
||||
pub fn chain_reader(&self, first_cluster: u32) -> impl Read {
|
||||
iter::ClusterChainReader::new(self, first_cluster)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue