Revises DiskPartition

This commit is contained in:
Putta Khunchalee 2024-09-22 19:37:59 +07:00
parent 5aeb3f7b57
commit ec3906474f
7 changed files with 115 additions and 155 deletions

View File

@ -5,6 +5,7 @@ description = "Pure Rust implementation of exFAT file system"
repository = "https://github.com/obhq/exfat"
license = "MIT"
edition = "2021"
rust-version = "1.81"
[features]
default = ["std"]

View File

@ -143,7 +143,7 @@ impl<P: DiskPartition> Read for ClustersReader<P> {
let amount = min(buf.len(), remaining as usize);
if let Err(e) = self.exfat.partition.read_exact(offset, &mut buf[..amount]) {
return Err(Error::new(ErrorKind::Other, e));
return Err(Error::new(ErrorKind::Other, Box::new(e)));
}
self.offset += amount as u64;

View File

@ -1,67 +1,55 @@
use core::fmt::Display;
use core::error::Error;
/// Encapsulate a disk partition.
pub trait DiskPartition {
#[cfg(not(feature = "std"))]
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<u64, Box<dyn Display + Send + Sync>>;
type Err: PartitionError + 'static;
#[cfg(feature = "std")]
fn read(
&self,
offset: u64,
buf: &mut [u8],
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>>;
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, Self::Err>;
#[cfg(not(feature = "std"))]
fn read_exact(
&self,
mut offset: u64,
mut buf: &mut [u8],
) -> Result<(), Box<dyn Display + Send + Sync>> {
fn read_exact(&self, mut offset: u64, mut buf: &mut [u8]) -> Result<(), Self::Err> {
while !buf.is_empty() {
let n = self.read(offset, buf)?;
if n == 0 {
return Err(Box::new(UnexpectedEop));
return Err(PartitionError::unexpected_eop());
}
offset += n;
buf = &mut buf[n.try_into().unwrap()..];
}
offset = n
.try_into()
.ok()
.and_then(|n| offset.checked_add(n))
.unwrap();
Ok(())
}
#[cfg(feature = "std")]
fn read_exact(
&self,
mut offset: u64,
mut buf: &mut [u8],
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
while !buf.is_empty() {
let n = self.read(offset, buf)?;
if n == 0 {
return Err(Box::new(UnexpectedEop));
}
offset += n;
buf = &mut buf[n.try_into().unwrap()..];
buf = &mut buf[n..];
}
Ok(())
}
}
/// An error for unexpected end of partition.
#[derive(Debug)]
struct UnexpectedEop;
/// Represents an error when an operation on [`DiskPartition`] fails.
pub trait PartitionError: Error + Send + Sync {
fn unexpected_eop() -> Self;
}
impl Display for UnexpectedEop {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("end of partition has been reached")
#[cfg(feature = "std")]
impl DiskPartition for std::fs::File {
type Err = std::io::Error;
#[cfg(unix)]
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, Self::Err> {
std::os::unix::fs::FileExt::read_at(self, buf, offset)
}
#[cfg(windows)]
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, Self::Err> {
std::os::windows::fs::FileExt::seek_read(self, buf, offset)
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnexpectedEop {}
impl PartitionError for std::io::Error {
fn unexpected_eop() -> Self {
std::io::Error::from(std::io::ErrorKind::UnexpectedEof)
}
}

View File

@ -1,7 +1,8 @@
use crate::disk::DiskPartition;
use crate::param::Params;
use byteorder::{ByteOrder, LE};
use core::fmt::Display;
use core::fmt::Debug;
use thiserror::Error;
pub(crate) struct Fat {
entries: Vec<u32>,
@ -12,7 +13,7 @@ impl Fat {
params: &Params,
partition: &P,
index: usize,
) -> Result<Self, LoadError> {
) -> Result<Self, LoadError<P>> {
// Get FAT region offset.
let sector = match params.fat_length.checked_mul(index as u64) {
Some(v) => match params.fat_offset.checked_add(v) {
@ -76,34 +77,26 @@ impl<'fat> Iterator for ClusterChain<'fat> {
}
/// Represents an error for [`Fat::load()`].
#[derive(Debug)]
pub enum LoadError {
#[derive(Error)]
pub enum LoadError<P: DiskPartition> {
#[error("invalid FatLength")]
InvalidFatLength,
#[error("invalid FatOffset")]
InvalidFatOffset,
#[cfg(not(feature = "std"))]
ReadFailed(u64, Box<dyn Display + Send + Sync>),
#[cfg(feature = "std")]
ReadFailed(u64, Box<dyn std::error::Error + Send + Sync>),
#[error("cannot read the data at {0:#x}")]
ReadFailed(u64, #[source] P::Err),
}
impl Display for LoadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
impl<P: DiskPartition> Debug for LoadError<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidFatLength => f.write_str("invalid FatLength"),
Self::InvalidFatOffset => f.write_str("invalid FatOffset"),
Self::ReadFailed(offset, _) => write!(f, "cannot read the data at {offset:#018x}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for LoadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ReadFailed(_, e) => Some(e.as_ref()),
_ => None,
Self::InvalidFatLength => write!(f, "InvalidFatLength"),
Self::InvalidFatOffset => write!(f, "InvalidFatOffset"),
Self::ReadFailed(arg0, arg1) => {
f.debug_tuple("ReadFailed").field(arg0).field(arg1).finish()
}
}
}
}

View File

@ -1,74 +0,0 @@
use crate::disk::DiskPartition;
use std::error::Error;
use std::io::{Read, Seek, SeekFrom};
use std::sync::Mutex;
use thiserror::Error;
/// An implementation of [`DiskPartition`] backed by an exFAT image.
pub struct Image<F: Read + Seek> {
file: Mutex<(F, u64)>,
}
impl<F: Read + Seek> Image<F> {
pub fn open(mut file: F) -> Result<Self, OpenError> {
let offset = match file.stream_position() {
Ok(v) => v,
Err(e) => return Err(OpenError::GetStreamPositionFailed(e)),
};
Ok(Self {
file: Mutex::new((file, offset)),
})
}
}
impl<F: Read + Seek> DiskPartition for Image<F> {
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<u64, Box<dyn Error + Send + Sync>> {
let mut file = self
.file
.lock()
.expect("the mutex that protect the inner file is poisoned");
// Seek the file.
if offset != file.1 {
match file.0.seek(SeekFrom::Start(offset)) {
Ok(v) => {
// The specified offset is out of range.
if v != offset {
return Ok(0);
}
}
Err(e) => return Err(ReadError::SeekFailed(e).into()),
}
file.1 = offset;
}
// Read the file.
let read = match file.0.read(buf) {
Ok(v) => v.try_into().unwrap(),
Err(e) => return Err(ReadError::ReadFailed(e).into()),
};
file.1 += read;
Ok(read)
}
}
/// Represents an error for [`Image::open()`].
#[derive(Debug, Error)]
pub enum OpenError {
#[error("cannot get the current seek position of the file")]
GetStreamPositionFailed(#[source] std::io::Error),
}
/// Represents an error for [`Image::read()`].
#[derive(Debug, Error)]
enum ReadError {
#[error("cannot seek the image to the target offset")]
SeekFailed(#[source] std::io::Error),
#[error("cannot read the image")]
ReadFailed(#[source] std::io::Error),
}

View File

@ -1,22 +1,22 @@
use self::cluster::ClustersReader;
use self::directory::{Directory, Item};
use self::disk::DiskPartition;
use self::entries::{ClusterAllocation, EntriesReader, EntryType, FileEntry};
use self::fat::Fat;
use self::file::File;
use self::param::Params;
use byteorder::{ByteOrder, LE};
use std::error::Error;
use core::fmt::Debug;
use std::sync::Arc;
use thiserror::Error;
pub use self::disk::*;
pub mod cluster;
pub mod directory;
pub mod disk;
mod disk;
pub mod entries;
pub mod fat;
pub mod file;
pub mod image;
pub mod param;
pub mod timestamp;
@ -30,7 +30,7 @@ pub struct Root<P: DiskPartition> {
}
impl<P: DiskPartition> Root<P> {
pub fn open(partition: P) -> Result<Self, OpenError> {
pub fn open(partition: P) -> Result<Self, OpenError<P>> {
// Read boot sector.
let mut boot = [0u8; 512];
@ -300,11 +300,11 @@ pub(crate) struct ExFat<P: DiskPartition> {
fat: Fat,
}
/// Represents an error for [`Root::open()`].
#[derive(Debug, Error)]
pub enum OpenError {
/// Represents an error when [`Root::open()`] fails.
#[derive(Error)]
pub enum OpenError<P: DiskPartition> {
#[error("cannot read main boot region")]
ReadMainBootFailed(#[source] Box<dyn Error + Send + Sync>),
ReadMainBootFailed(#[source] P::Err),
#[error("image is not exFAT")]
NotExFat,
@ -319,7 +319,7 @@ pub enum OpenError {
InvalidNumberOfFats,
#[error("cannot read FAT region")]
ReadFatRegionFailed(#[source] fat::LoadError),
ReadFatRegionFailed(#[source] self::fat::LoadError<P>),
#[error("cannot create a clusters reader")]
CreateClustersReaderFailed(#[source] cluster::NewError),
@ -363,3 +363,57 @@ pub enum OpenError {
#[error("no Up-case Table available")]
NoUpcaseTable,
}
impl<P: DiskPartition> Debug for OpenError<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ReadMainBootFailed(arg0) => {
f.debug_tuple("ReadMainBootFailed").field(arg0).finish()
}
Self::NotExFat => write!(f, "NotExFat"),
Self::InvalidBytesPerSectorShift => write!(f, "InvalidBytesPerSectorShift"),
Self::InvalidSectorsPerClusterShift => write!(f, "InvalidSectorsPerClusterShift"),
Self::InvalidNumberOfFats => write!(f, "InvalidNumberOfFats"),
Self::ReadFatRegionFailed(arg0) => {
f.debug_tuple("ReadFatRegionFailed").field(arg0).finish()
}
Self::CreateClustersReaderFailed(arg0) => f
.debug_tuple("CreateClustersReaderFailed")
.field(arg0)
.finish(),
Self::ReadEntryFailed(arg0) => f.debug_tuple("ReadEntryFailed").field(arg0).finish(),
Self::NotPrimaryEntry(arg0, arg1) => f
.debug_tuple("NotPrimaryEntry")
.field(arg0)
.field(arg1)
.finish(),
Self::TooManyAllocationBitmap => write!(f, "TooManyAllocationBitmap"),
Self::WrongAllocationBitmap => write!(f, "WrongAllocationBitmap"),
Self::MultipleUpcaseTable => write!(f, "MultipleUpcaseTable"),
Self::MultipleVolumeLabel => write!(f, "MultipleVolumeLabel"),
Self::InvalidVolumeLabel => write!(f, "InvalidVolumeLabel"),
Self::LoadFileEntryFailed(arg0) => {
f.debug_tuple("LoadFileEntryFailed").field(arg0).finish()
}
Self::CreateFileObjectFailed(arg0, arg1, arg2) => f
.debug_tuple("CreateFileObjectFailed")
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::ReadClusterAllocationFailed(arg0, arg1, arg2) => f
.debug_tuple("ReadClusterAllocationFailed")
.field(arg0)
.field(arg1)
.field(arg2)
.finish(),
Self::UnknownEntry(arg0, arg1) => f
.debug_tuple("UnknownEntry")
.field(arg0)
.field(arg1)
.finish(),
Self::NoAllocationBitmap => write!(f, "NoAllocationBitmap"),
Self::NoUpcaseTable => write!(f, "NoUpcaseTable"),
}
}
}

View File

@ -1,5 +1,4 @@
use exfat::directory::Item;
use exfat::image::Image;
use exfat::timestamp::Timestamp;
use exfat::Root;
use std::fs::File;
@ -30,7 +29,6 @@ fn read_image() {
// Open the image.
let image: PathBuf = ["tests", "exfat.img"].iter().collect();
let image = File::open(image).expect("cannot open exfat.img");
let image = Image::open(image).expect("cannot open exFAT image from exfat.img");
// Open root directory.
let root = Root::open(image).expect("cannot open the root directory");