#[cfg(feature = "exfat")]
use super::exfat::ExfatDirIter;
#[cfg(feature = "fat")]
use super::fat::FatDirIter;
#[cfg(feature = "littlefs")]
use super::littlefs::LittleFsDirIter;
use super::{Entry, ErrorKind, FsType, Metadata, Volume, VolumeDirIter, VolumeError, VolumeFile};
#[cfg(feature = "littlefs")]
use crate::device::SectorFlash;
use crate::device::{SectorDriver, gpt, mbr};
#[cfg(feature = "exfat")]
use crate::fs::exfat;
#[cfg(feature = "fat")]
use crate::fs::fat;
#[cfg(feature = "littlefs")]
use crate::fs::littlefs;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Found {
pub fs: FsType,
pub start_lba: u64,
pub sectors: u64,
pub block_size: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AnyError<E> {
Io(E),
NotRecognised,
ScratchTooSmall {
needed: usize,
got: usize,
},
BlockSize(u32),
WrongVolume,
#[cfg(feature = "fat")]
Fat(fat::Error<E>),
#[cfg(feature = "exfat")]
Exfat(exfat::Error<E>),
#[cfg(feature = "littlefs")]
LittleFs(littlefs::Error<E>),
}
impl<E> VolumeError for AnyError<E> {
fn kind(&self) -> ErrorKind {
match self {
AnyError::Io(_) => ErrorKind::Io,
AnyError::NotRecognised => ErrorKind::NotRecognised,
AnyError::ScratchTooSmall { .. } | AnyError::BlockSize(_) => ErrorKind::Geometry,
AnyError::WrongVolume => ErrorKind::WrongVolume,
#[cfg(feature = "fat")]
AnyError::Fat(e) => e.kind(),
#[cfg(feature = "exfat")]
AnyError::Exfat(e) => e.kind(),
#[cfg(feature = "littlefs")]
AnyError::LittleFs(e) => e.kind(),
}
}
}
impl<E: core::fmt::Display> core::fmt::Display for AnyError<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
AnyError::Io(e) => write!(f, "device error: {e}"),
AnyError::NotRecognised => f.write_str("no filesystem recognised"),
AnyError::ScratchTooSmall { needed, got } => {
write!(f, "scratch buffer is {got} bytes, need {needed}")
}
AnyError::BlockSize(bs) => {
write!(f, "littlefs block size {bs} does not fit the medium")
}
AnyError::WrongVolume => f.write_str("handle belongs to a different volume"),
#[cfg(feature = "fat")]
AnyError::Fat(e) => write!(f, "fat: {e}"),
#[cfg(feature = "exfat")]
AnyError::Exfat(e) => write!(f, "exfat: {e}"),
#[cfg(feature = "littlefs")]
AnyError::LittleFs(e) => write!(f, "littlefs: {e}"),
}
}
}
#[cfg(feature = "std")]
impl<E: core::fmt::Debug + core::fmt::Display> std::error::Error for AnyError<E> {}
pub fn probe<D: SectorDriver, const SECTOR: usize>(
dev: &mut D,
) -> Result<Option<Found>, AnyError<D::Error>> {
let ss = dev.sector_size() as usize;
if SECTOR < ss || !(512..=4096).contains(&ss) || !ss.is_power_of_two() {
return Err(AnyError::ScratchTooSmall {
needed: ss,
got: SECTOR,
});
}
let mut buf = [0u8; SECTOR];
let sector = &mut buf[..ss];
let count = dev.sector_count();
if let Some(found) = detect_at(dev, 0, count, sector)? {
return Ok(Some(found));
}
if let Some(table) = gpt::Table::read(dev, sector).map_err(AnyError::Io)? {
for i in 0..table.entries() {
let Some(part) = table.entry(dev, sector, i).map_err(AnyError::Io)? else {
continue;
};
if let Some(found) = detect_at(dev, part.start_lba, part.sectors(), sector)? {
return Ok(Some(found));
}
}
return Ok(None);
}
dev.read_sectors(0, sector).map_err(AnyError::Io)?;
if let Some(table) = mbr::parse(sector) {
for slot in table.iter().flatten() {
if let Some(found) = detect_at(dev, slot.start_lba, slot.sectors, sector)? {
return Ok(Some(found));
}
}
}
Ok(None)
}
fn detect_at<D: SectorDriver>(
dev: &mut D,
lba: u64,
sectors: u64,
sector: &mut [u8],
) -> Result<Option<Found>, AnyError<D::Error>> {
let count = dev.sector_count();
if lba >= count {
return Ok(None);
}
let sectors = sectors.min(count - lba);
let ss = sector.len() as u32;
dev.read_sectors(lba, sector).map_err(AnyError::Io)?;
let found = |fs, block_size| {
Some(Found {
fs,
start_lba: lba,
sectors,
block_size,
})
};
#[cfg(feature = "exfat")]
if exfat::layout::Boot::decode(sector).is_ok() {
return Ok(found(FsType::Exfat, ss));
}
#[cfg(feature = "fat")]
if fat::Geometry::parse::<()>(sector, lba, count.saturating_mul(ss as u64)).is_ok() {
return Ok(found(FsType::Fat, ss));
}
#[cfg(feature = "littlefs")]
if let Some(block_size) = littlefs_block_size(sector) {
return Ok(found(FsType::LittleFs, block_size));
}
let _ = (found, ss);
Ok(None)
}
#[cfg(feature = "littlefs")]
fn littlefs_block_size(sector: &[u8]) -> Option<u32> {
use crate::fs::littlefs::tag::{self, Tag};
let mut off = 4;
let mut prev = tag::PTAG_INIT;
let mut magic = false;
let mut block_size = None;
while off + 4 <= sector.len() {
let t = Tag(tag::be32(§or[off..off + 4]) ^ prev);
if !t.is_valid() {
break;
}
prev = t.0;
let data = off + 4;
let len = if t.is_delete() { 0 } else { t.size() as usize };
if t.type1() == tag::T1_CRC && t.type3() != tag::TYPE_FCRC {
break;
}
if data + len > sector.len() {
break;
}
match t.type3() {
tag::TYPE_SUPERBLOCK if t.id() == 0 => {
magic = §or[data..data + len] == crate::fs::littlefs::MAGIC;
}
tag::TYPE_INLINESTRUCT if t.id() == 0 && len >= 12 => {
block_size = Some(tag::le32(§or[data + 4..data + 8]));
}
_ => {}
}
off = data + len;
}
block_size.filter(|&bs| magic && bs.is_power_of_two())
}
pub fn mount<D: SectorDriver, const SECTOR: usize, const BLOCK: usize>(
mut dev: D,
) -> Result<AnyVolume<D, SECTOR, BLOCK>, AnyError<D::Error>> {
match probe::<D, SECTOR>(&mut dev)? {
Some(found) => mount_found(dev, found),
None => Err(AnyError::NotRecognised),
}
}
pub fn mount_found<D: SectorDriver, const SECTOR: usize, const BLOCK: usize>(
dev: D,
found: Found,
) -> Result<AnyVolume<D, SECTOR, BLOCK>, AnyError<D::Error>> {
match found.fs {
#[cfg(feature = "fat")]
FsType::Fat => fat::Volume::mount_at(dev, found.start_lba)
.map(AnyVolume::Fat)
.map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
FsType::Exfat => exfat::Volume::mount_at(dev, found.start_lba)
.map(AnyVolume::Exfat)
.map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
FsType::LittleFs => {
if found.block_size as usize > BLOCK {
return Err(AnyError::BlockSize(found.block_size));
}
let flash = SectorFlash::new(dev, found.start_lba, found.sectors, found.block_size)
.map_err(|_| AnyError::BlockSize(found.block_size))?;
littlefs::Volume::mount(flash)
.map(AnyVolume::LittleFs)
.map_err(AnyError::LittleFs)
}
#[allow(unreachable_patterns)] _ => {
let _ = dev;
Err(AnyError::NotRecognised)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormatAs<'a> {
#[cfg(feature = "fat")]
Fat(fat::FormatOpts),
#[cfg(feature = "exfat")]
Exfat(exfat::VolumeFormatOpts<'a>),
#[cfg(feature = "littlefs")]
LittleFs {
block_size: u32,
opts: littlefs::FormatOpts,
},
#[doc(hidden)]
_Lifetime(core::marker::PhantomData<&'a ()>),
}
#[cfg(all(feature = "fat", feature = "exfat"))]
impl FormatAs<'_> {
pub fn sd_card(sectors: u64, sector_size: u32) -> Self {
if sectors.saturating_mul(sector_size as u64) <= 32 << 30 {
FormatAs::Fat(fat::FormatOpts::default())
} else {
FormatAs::Exfat(exfat::VolumeFormatOpts::default())
}
}
}
pub fn format<D: SectorDriver, const SECTOR: usize, const BLOCK: usize>(
dev: D,
start_lba: u64,
sectors: u64,
how: FormatAs<'_>,
) -> Result<AnyVolume<D, SECTOR, BLOCK>, AnyError<D::Error>> {
match how {
#[cfg(feature = "fat")]
FormatAs::Fat(opts) => fat::Volume::format_at(dev, start_lba, sectors, &opts)
.map(AnyVolume::Fat)
.map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
FormatAs::Exfat(opts) => exfat::Volume::format_at(dev, start_lba, sectors, &opts)
.map(AnyVolume::Exfat)
.map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
FormatAs::LittleFs { block_size, opts } => {
if block_size as usize > BLOCK {
return Err(AnyError::BlockSize(block_size));
}
let flash = SectorFlash::new(dev, start_lba, sectors, block_size)
.map_err(|_| AnyError::BlockSize(block_size))?;
littlefs::Volume::format_with(flash, &opts)
.map(AnyVolume::LittleFs)
.map_err(AnyError::LittleFs)
}
FormatAs::_Lifetime(_) => {
let _ = (dev, start_lba, sectors);
Err(AnyError::NotRecognised)
}
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)] #[non_exhaustive]
pub enum AnyVolume<D: SectorDriver, const SECTOR: usize = 512, const BLOCK: usize = 4096> {
#[cfg(feature = "fat")]
Fat(fat::Volume<D, SECTOR>),
#[cfg(feature = "exfat")]
Exfat(exfat::Volume<D, SECTOR>),
#[cfg(feature = "littlefs")]
LittleFs(littlefs::Volume<SectorFlash<D, SECTOR>, BLOCK, SECTOR>),
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum AnyFile {
#[cfg(feature = "fat")]
Fat(fat::File),
#[cfg(feature = "exfat")]
Exfat(exfat::File),
#[cfg(feature = "littlefs")]
LittleFs(littlefs::File),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AnyDir {
#[cfg(feature = "fat")]
Fat(fat::Dir),
#[cfg(feature = "exfat")]
Exfat(exfat::Dir),
#[cfg(feature = "littlefs")]
LittleFs(littlefs::Dir),
}
#[allow(clippy::large_enum_variant)] #[non_exhaustive]
pub enum AnyDirIter<'a, D: SectorDriver, const SECTOR: usize, const BLOCK: usize> {
#[cfg(feature = "fat")]
Fat(FatDirIter<'a, D, SECTOR>),
#[cfg(feature = "exfat")]
Exfat(ExfatDirIter<'a, D, SECTOR>),
#[cfg(feature = "littlefs")]
LittleFs(LittleFsDirIter<'a, SectorFlash<D, SECTOR>, BLOCK, SECTOR>),
Mismatch,
}
macro_rules! each {
($vol:expr, |$v:ident| $body:expr) => {
match $vol {
#[cfg(feature = "fat")]
AnyVolume::Fat($v) => $body.map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
AnyVolume::Exfat($v) => $body.map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
AnyVolume::LittleFs($v) => $body.map_err(AnyError::LittleFs),
}
};
($vol:expr, |$v:ident| $body:expr, $wrap:ident) => {
match $vol {
#[cfg(feature = "fat")]
AnyVolume::Fat($v) => $body.map($wrap::Fat).map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
AnyVolume::Exfat($v) => $body.map($wrap::Exfat).map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
AnyVolume::LittleFs($v) => $body.map($wrap::LittleFs).map_err(AnyError::LittleFs),
}
};
}
macro_rules! paired {
($handle:expr, $vol:expr, $Handle:ident, |$h:ident, $v:ident| $body:expr) => {
#[allow(unreachable_patterns)] match ($handle, $vol) {
#[cfg(feature = "fat")]
($Handle::Fat($h), AnyVolume::Fat($v)) => $body.map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
($Handle::Exfat($h), AnyVolume::Exfat($v)) => $body.map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
($Handle::LittleFs($h), AnyVolume::LittleFs($v)) => $body.map_err(AnyError::LittleFs),
_ => Err(AnyError::WrongVolume),
}
};
}
impl<D: SectorDriver, const SECTOR: usize, const BLOCK: usize> Volume
for AnyVolume<D, SECTOR, BLOCK>
{
type Error = AnyError<D::Error>;
type Device = D;
type File = AnyFile;
type Dir = AnyDir;
type DirIter<'a>
= AnyDirIter<'a, D, SECTOR, BLOCK>
where
Self: 'a;
fn fs_type(&self) -> FsType {
match self {
#[cfg(feature = "fat")]
AnyVolume::Fat(v) => v.fs_type(),
#[cfg(feature = "exfat")]
AnyVolume::Exfat(v) => v.fs_type(),
#[cfg(feature = "littlefs")]
AnyVolume::LittleFs(v) => v.fs_type(),
}
}
fn root(&self) -> AnyDir {
match self {
#[cfg(feature = "fat")]
AnyVolume::Fat(v) => AnyDir::Fat(v.root()),
#[cfg(feature = "exfat")]
AnyVolume::Exfat(v) => AnyDir::Exfat(v.root()),
#[cfg(feature = "littlefs")]
AnyVolume::LittleFs(v) => AnyDir::LittleFs(v.root()),
}
}
fn open_dir(&mut self, path: &str) -> Result<AnyDir, Self::Error> {
each!(self, |v| Volume::open_dir(v, path), AnyDir)
}
fn iter_dir(&mut self, dir: AnyDir) -> Self::DirIter<'_> {
#[allow(unreachable_patterns)] match (dir, self) {
#[cfg(feature = "fat")]
(AnyDir::Fat(d), AnyVolume::Fat(v)) => AnyDirIter::Fat(Volume::iter_dir(v, d)),
#[cfg(feature = "exfat")]
(AnyDir::Exfat(d), AnyVolume::Exfat(v)) => AnyDirIter::Exfat(Volume::iter_dir(v, d)),
#[cfg(feature = "littlefs")]
(AnyDir::LittleFs(d), AnyVolume::LittleFs(v)) => {
AnyDirIter::LittleFs(Volume::iter_dir(v, d))
}
_ => AnyDirIter::Mismatch,
}
}
fn metadata(&mut self, path: &str) -> Result<Metadata, Self::Error> {
each!(self, |v| Volume::metadata(v, path))
}
fn exists(&mut self, path: &str) -> Result<bool, Self::Error> {
each!(self, |v| Volume::exists(v, path))
}
fn create_dir(&mut self, path: &str) -> Result<AnyDir, Self::Error> {
each!(self, |v| Volume::create_dir(v, path), AnyDir)
}
fn remove_dir(&mut self, path: &str) -> Result<(), Self::Error> {
each!(self, |v| Volume::remove_dir(v, path))
}
fn remove_file(&mut self, path: &str) -> Result<(), Self::Error> {
each!(self, |v| Volume::remove_file(v, path))
}
fn open_file(&mut self, path: &str) -> Result<AnyFile, Self::Error> {
each!(self, |v| Volume::open_file(v, path), AnyFile)
}
fn create_file(&mut self, path: &str) -> Result<AnyFile, Self::Error> {
each!(self, |v| Volume::create_file(v, path), AnyFile)
}
fn open_or_create_file(&mut self, path: &str) -> Result<AnyFile, Self::Error> {
each!(self, |v| Volume::open_or_create_file(v, path), AnyFile)
}
fn flush(&mut self) -> Result<(), Self::Error> {
each!(self, |v| Volume::flush(v))
}
fn total_bytes(&self) -> u64 {
match self {
#[cfg(feature = "fat")]
AnyVolume::Fat(v) => Volume::total_bytes(v),
#[cfg(feature = "exfat")]
AnyVolume::Exfat(v) => Volume::total_bytes(v),
#[cfg(feature = "littlefs")]
AnyVolume::LittleFs(v) => Volume::total_bytes(v),
}
}
fn free_bytes(&mut self) -> Result<u64, Self::Error> {
each!(self, |v| Volume::free_bytes(v))
}
fn statfs(&mut self) -> Result<crate::fs::StatFs, Self::Error> {
each!(self, |v| Volume::statfs(v))
}
fn unmount(self) -> Result<D, Self::Error> {
match self {
#[cfg(feature = "fat")]
AnyVolume::Fat(v) => Volume::unmount(v).map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
AnyVolume::Exfat(v) => Volume::unmount(v).map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
AnyVolume::LittleFs(v) => Volume::unmount(v)
.map(SectorFlash::into_inner)
.map_err(AnyError::LittleFs),
}
}
}
impl<D: SectorDriver, const SECTOR: usize, const BLOCK: usize>
VolumeFile<AnyVolume<D, SECTOR, BLOCK>> for AnyFile
{
fn len(&self) -> u64 {
match self {
#[cfg(feature = "fat")]
AnyFile::Fat(f) => f.len() as u64,
#[cfg(feature = "exfat")]
AnyFile::Exfat(f) => f.len(),
#[cfg(feature = "littlefs")]
AnyFile::LittleFs(f) => f.len() as u64,
}
}
fn pos(&self) -> u64 {
match self {
#[cfg(feature = "fat")]
AnyFile::Fat(f) => f.pos() as u64,
#[cfg(feature = "exfat")]
AnyFile::Exfat(f) => f.pos(),
#[cfg(feature = "littlefs")]
AnyFile::LittleFs(f) => f.pos() as u64,
}
}
fn seek(
&mut self,
vol: &mut AnyVolume<D, SECTOR, BLOCK>,
pos: u64,
) -> Result<(), AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::seek(f, v, pos))
}
fn read(
&mut self,
vol: &mut AnyVolume<D, SECTOR, BLOCK>,
buf: &mut [u8],
) -> Result<usize, AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::read(f, v, buf))
}
fn read_exact(
&mut self,
vol: &mut AnyVolume<D, SECTOR, BLOCK>,
buf: &mut [u8],
) -> Result<(), AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::read_exact(f, v, buf))
}
fn write(
&mut self,
vol: &mut AnyVolume<D, SECTOR, BLOCK>,
buf: &[u8],
) -> Result<usize, AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::write(f, v, buf))
}
fn write_all(
&mut self,
vol: &mut AnyVolume<D, SECTOR, BLOCK>,
buf: &[u8],
) -> Result<(), AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::write_all(f, v, buf))
}
fn set_len(
&mut self,
vol: &mut AnyVolume<D, SECTOR, BLOCK>,
len: u64,
) -> Result<(), AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::set_len(f, v, len))
}
fn flush(&mut self, vol: &mut AnyVolume<D, SECTOR, BLOCK>) -> Result<(), AnyError<D::Error>> {
paired!(self, vol, AnyFile, |f, v| VolumeFile::flush(f, v))
}
}
impl<D: SectorDriver, const SECTOR: usize, const BLOCK: usize> VolumeDirIter
for AnyDirIter<'_, D, SECTOR, BLOCK>
{
type Error = AnyError<D::Error>;
fn next(&mut self) -> Result<Option<Entry<'_>>, Self::Error> {
match self {
#[cfg(feature = "fat")]
AnyDirIter::Fat(it) => it.next().map_err(AnyError::Fat),
#[cfg(feature = "exfat")]
AnyDirIter::Exfat(it) => it.next().map_err(AnyError::Exfat),
#[cfg(feature = "littlefs")]
AnyDirIter::LittleFs(it) => it.next().map_err(AnyError::LittleFs),
AnyDirIter::Mismatch => Err(AnyError::WrongVolume),
}
}
}