#![doc = include_str!("README.md")]
#![no_std]
pub mod exfat;
pub mod fat;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "bytemuck")]
pub use bytemuck;
#[cfg(feature = "zerocopy")]
pub use zerocopy;
use core::{
char::DecodeUtf16Error,
cmp::Ordering,
fmt::{self, Debug, Display, Formatter, Write},
iter::FusedIterator,
};
#[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, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[cfg_attr(
feature = "zerocopy",
derive(AsBytes, FromZeroes, FromBytes, Unaligned)
)]
#[repr(C)]
pub struct ChsAddr([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 & 0xFC00 == 0 && sector & 0xC0 == 0 {
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 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 {
u16::cmp(&self.cylinder(), &other.cylinder())
.then_with(|| Ord::cmp(&self.0[..2], &other.0[..2]))
}
}
#[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
}
}
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromZeroes, FromBytes))]
#[repr(transparent)]
pub struct Wtf16Str([u16]);
impl Wtf16Str {
pub const EMPTY: &'static Self = Self::from_slice_const(&[]);
#[inline]
const fn from_slice_const(slice: &[u16]) -> &Wtf16Str {
unsafe { core::mem::transmute(slice) }
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn code_units(
&self,
) -> impl DoubleEndedIterator<Item = u16> + ExactSizeIterator + FusedIterator + '_ {
self.0.iter().map(|&c| u16::from_le(c))
}
pub fn chars(&self) -> impl Iterator<Item = Result<char, DecodeUtf16Error>> + '_ {
char::decode_utf16(self.0.iter().map(|&c| u16::from_le(c)))
}
#[cfg(feature = "alloc")]
pub fn try_to_string(&self) -> Result<alloc::string::String, DecodeUtf16Error> {
self.chars().collect::<Result<alloc::string::String, _>>()
}
}
impl AsRef<Self> for Wtf16Str {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl AsMut<Self> for Wtf16Str {
#[inline]
fn as_mut(&mut self) -> &mut Self {
self
}
}
impl<const N: usize> AsRef<Wtf16Str> for [u16; N] {
#[inline]
fn as_ref(&self) -> &Wtf16Str {
self[..].as_ref()
}
}
impl AsRef<Wtf16Str> for [u16] {
#[inline]
fn as_ref(&self) -> &Wtf16Str {
Wtf16Str::from_slice_const(self)
}
}
impl AsRef<[u16]> for Wtf16Str {
#[inline]
fn as_ref(&self) -> &[u16] {
&self.0
}
}
impl<const N: usize> AsMut<Wtf16Str> for [u16; N] {
#[inline]
fn as_mut(&mut self) -> &mut Wtf16Str {
self[..].as_mut()
}
}
impl AsMut<Wtf16Str> for [u16] {
#[inline]
fn as_mut(&mut self) -> &mut Wtf16Str {
unsafe { core::mem::transmute(self) }
}
}
impl AsMut<[u16]> for Wtf16Str {
#[inline]
fn as_mut(&mut self) -> &mut [u16] {
&mut self.0
}
}
impl Debug for Wtf16Str {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_char('"')?;
for c in self.chars() {
match c {
Ok('\'') => f.write_char('\'')?,
Ok(c) => Display::fmt(&c.escape_debug(), f)?,
Err(e) => write!(f, "\\u{{{:x}}}", e.unpaired_surrogate())?,
}
}
f.write_char('"')
}
}
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 {
saturating_date: 0x21,
saturating_time: 0,
saturating_time_10ms: 0,
});
}
let year = year - MIN_YEAR;
if year >= 0x80 {
return Err(PackDateError {
saturating_date: 0xFF9F,
saturating_time: 0xBF7D,
saturating_time_10ms: 199,
});
}
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 {
saturating_date: u16,
saturating_time: u16,
saturating_time_10ms: u8,
}
impl PackDateError {
#[inline]
pub const fn overflow(self) -> bool {
self.saturating_time != 0
}
#[inline]
pub const fn saturating_date(self) -> u16 {
self.saturating_date
}
#[inline]
pub const fn saturating_time(self) -> u16 {
self.saturating_time
}
#[inline]
pub const fn saturating_time_10ms(self) -> u8 {
self.saturating_time_10ms
}
}
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,
)
}