#![doc = include_str!("../README.md")]
#![no_std]
pub mod exfat;
pub mod fat;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "bytemuck")]
pub use bytemuck;
#[cfg(feature = "zerocopy")]
pub use zerocopy;
use core::{
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
};
#[cfg(feature = "bytemuck")]
use bytemuck::{Pod, Zeroable};
#[cfg(feature = "zerocopy")]
use zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned};
pub const MIN_BLK_SIZE: usize = 0x0200;
pub const MAX_BLK_SIZE: usize = 0x1000;
pub const NONEXISTENT_CLUSTERS: ClusterIdx = 2;
pub type ClusterIdx = u32;
pub type Guid = [u8; 0x10];
pub type Lba = u64;
pub type U16Le = [u8; 2];
pub type U32Le = [u8; 4];
pub type U64Le = [u8; 8];
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[cfg_attr(
feature = "zerocopy",
derive(AsBytes, FromZeroes, FromBytes, Unaligned)
)]
#[repr(C)]
pub struct ChsAddr(pub [u8; 3]);
impl ChsAddr {
pub const ZERO: Self = Self([0; 3]);
pub const INVALID: Self = Self([0xFF; 3]);
#[inline]
pub const fn new(cylinder: u16, head: u8, sector: u8) -> Option<Self> {
if cylinder < 0x0400 && sector < 0x40 {
Some(Self([
head,
sector | ((cylinder >> 2) as u8 & 0xC0),
cylinder as u8,
]))
} else {
None
}
}
#[inline]
pub const fn cylinder(self) -> u16 {
((self.0[1] as u16 & 0xC0) << 2) | self.0[2] as u16
}
#[inline]
pub const fn head(self) -> u8 {
self.0[0]
}
#[inline]
pub const fn sector(self) -> u8 {
self.0[1] & 0x3F
}
}
impl Debug for ChsAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("ChsAddr")
.field("cylinder", &self.cylinder())
.field("head", &self.head())
.field("sector", &self.sector())
.finish()
}
}
impl PartialOrd for ChsAddr {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Self::cmp(self, other))
}
}
impl Ord for ChsAddr {
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(
&(self.cylinder(), self.head(), self.0[1]),
&(other.cylinder(), other.head(), other.0[1]),
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DiskGeometry {
pub heads: u16,
pub sectors_per_track: u16,
}
impl DiskGeometry {
pub const fn lba_to_chs(self, lba: Lba) -> Option<ChsAddr> {
let sector = (lba % self.sectors_per_track as u64) as u8 + 1;
let head_and_cylinder = lba / self.sectors_per_track as u64;
let head = (head_and_cylinder % self.heads as u64) as u8;
let cylinder = head_and_cylinder / self.heads as u64;
if cylinder > u16::MAX as u64 {
return None;
}
ChsAddr::new(cylinder as u16, head, sector)
}
pub const fn chs_to_lba(self, chs: ChsAddr) -> Lba {
(chs.cylinder() as u64 * self.heads as u64 + chs.head() as u64)
* self.sectors_per_track as u64
+ chs.sector() as u64
- 1
}
}
pub const MIN_YEAR: i32 = 1980;
pub const MAX_YEAR: i32 = 2107;
pub const fn pack_date((year, month, day): (i32, u8, u8)) -> Result<u16, PackDateError> {
if year < MIN_YEAR {
return Err(PackDateError { overflow: false });
}
let year = year - MIN_YEAR;
if year >= 0x80 {
return Err(PackDateError { overflow: true });
}
Ok((year as u16) << 9 | (month as u16) << 5 | day as u16)
}
pub const fn unpack_date(packed: u16) -> (i32, u8, u8) {
(
MIN_YEAR + (packed >> 9) as i32,
(packed >> 5) as u8 & 0x0F,
packed as u8 & 0x1F,
)
}
#[derive(Clone, Copy, Debug)]
pub struct PackDateError {
overflow: bool,
}
impl PackDateError {
#[inline]
pub const fn overflow(self) -> bool {
self.overflow
}
#[inline]
pub const fn underflow(self) -> bool {
!self.overflow
}
#[inline]
pub const fn saturating_date(self) -> u16 {
if self.overflow {
0xFF9F
} else {
0x21
}
}
#[inline]
pub const fn saturating_time(self) -> u16 {
if self.overflow {
0xBF7D
} else {
0
}
}
#[inline]
pub const fn saturating_time_10ms(self) -> u8 {
if self.overflow {
199
} else {
0
}
}
}
impl Display for PackDateError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("FAT date packing error: ")?;
f.write_str(if self.overflow {
"overflow"
} else {
"underflow"
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for PackDateError {}
pub const fn pack_time((hours, minutes, millis): (u8, u8, u16)) -> (u16, u8) {
(
(hours as u16) << 0xB | (minutes as u16) << 5 | (millis / 2_000),
(millis / 10 % 200) as u8,
)
}
pub const fn unpack_time(packed: u16, packed_10ms: u8) -> (u8, u8, u16) {
(
(packed >> 0xB) as u8,
(packed >> 5) as u8 & 0x3F,
(packed & 0x1F) * 2_000 + packed_10ms as u16 * 10,
)
}