#![cfg_attr(not(test), no_std)]
#![deny(missing_docs)]
#[macro_use]
mod structure;
pub use embedded_sdmmc_types;
pub use embedded_sdmmc_types::blockdevice;
pub mod fat;
pub mod filesystem;
pub mod sdcard;
use core::fmt::Debug;
use embedded_io::ErrorKind;
use filesystem::Handle;
#[doc(inline)]
pub use crate::blockdevice::{Block, BlockCount, BlockDevice, BlockIdx};
#[doc(inline)]
pub use crate::fat::{FatVolume, VolumeName};
#[doc(inline)]
pub use crate::filesystem::{
Attributes, ClusterId, DirEntry, Directory, File, FilenameError, LfnBuffer, MAX_FILE_SIZE,
Mode, RawDirectory, RawFile, ShortFileName, TimeSource, Timestamp,
};
use filesystem::DirectoryInfo;
#[doc(inline)]
pub use crate::sdcard::spi::Error as SdCardError;
#[doc(inline)]
pub use crate::sdcard::spi::SdCard;
mod volume_mgr;
#[doc(inline)]
pub use volume_mgr::VolumeManager;
#[cfg(all(feature = "defmt-log", feature = "log"))]
compile_error!("Cannot enable both log and defmt-log");
#[cfg(feature = "log")]
use log::{debug, trace, warn};
#[cfg(feature = "defmt-log")]
use defmt::{debug, trace, warn};
#[cfg(all(not(feature = "defmt-log"), not(feature = "log")))]
#[macro_export]
macro_rules! debug {
($($arg:tt)+) => {};
}
#[cfg(all(not(feature = "defmt-log"), not(feature = "log")))]
#[macro_export]
macro_rules! trace {
($($arg:tt)+) => {};
}
#[cfg(all(not(feature = "defmt-log"), not(feature = "log")))]
#[macro_export]
macro_rules! warn {
($($arg:tt)+) => {};
}
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Error<E>
where
E: core::error::Error,
{
#[error("error from underlying block device: {0}")]
DeviceError(#[from] E),
#[error("filesystem is badly formatted: {0}")]
FormatError(&'static str),
#[error("no such volume")]
NoSuchVolume,
#[error("bad filename")]
FilenameError(FilenameError),
#[error("too many open volumes")]
TooManyOpenVolumes,
#[error("too many open directories")]
TooManyOpenDirs,
#[error("too many open files")]
TooManyOpenFiles,
#[error("bad handle")]
BadHandle,
#[error("file or directory does not exist")]
NotFound,
#[error("file already open")]
FileAlreadyOpen,
#[error("directory already open")]
DirAlreadyOpen,
#[error("cannot open directory as file")]
OpenedDirAsFile,
#[error("cannot open file as directory")]
OpenedFileAsDir,
#[error("cannot delete a non-empty directory")]
DeleteNonEmptyDir,
#[error("volume is still in use")]
VolumeStillInUse,
#[error("cannot open volume twice")]
VolumeAlreadyOpen,
#[error("unsupported operation")]
Unsupported,
#[error("end of file")]
EndOfFile,
#[error("bad cluster")]
BadCluster,
#[error("type conversion failed")]
ConversionError,
#[error("not enough space on device")]
NotEnoughSpace,
#[error("cluster not properly allocated")]
AllocationError,
#[error("FAT chain unterminated")]
UnterminatedFatChain,
#[error("file is read-only")]
ReadOnly,
#[error("file already exists")]
FileAlreadyExists,
#[error("bad block size: {0} (only 512 byte blocks supported)")]
BadBlockSize(u16),
#[error("invalid seek offset")]
InvalidOffset,
#[error("disk full")]
DiskFull,
#[error("directory already exists")]
DirAlreadyExists,
#[error("already locked")]
LockError,
}
impl<E: core::error::Error + 'static> embedded_io::Error for Error<E> {
fn kind(&self) -> ErrorKind {
match self {
Error::DeviceError(_)
| Error::FormatError(_)
| Error::FileAlreadyOpen
| Error::DirAlreadyOpen
| Error::VolumeStillInUse
| Error::VolumeAlreadyOpen
| Error::EndOfFile
| Error::DiskFull
| Error::NotEnoughSpace
| Error::AllocationError
| Error::LockError => ErrorKind::Other,
Error::NoSuchVolume
| Error::FilenameError(_)
| Error::BadHandle
| Error::InvalidOffset => ErrorKind::InvalidInput,
Error::TooManyOpenVolumes | Error::TooManyOpenDirs | Error::TooManyOpenFiles => {
ErrorKind::OutOfMemory
}
Error::NotFound => ErrorKind::NotFound,
Error::OpenedDirAsFile
| Error::OpenedFileAsDir
| Error::DeleteNonEmptyDir
| Error::BadCluster
| Error::ConversionError
| Error::UnterminatedFatChain => ErrorKind::InvalidData,
Error::Unsupported | Error::BadBlockSize(_) => ErrorKind::Unsupported,
Error::ReadOnly => ErrorKind::PermissionDenied,
Error::FileAlreadyExists | Error::DirAlreadyExists => ErrorKind::AlreadyExists,
}
}
}
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct RawVolume(Handle);
impl RawVolume {
pub fn to_volume<
D,
T,
const MAX_DIRS: usize,
const MAX_FILES: usize,
const MAX_VOLUMES: usize,
>(
self,
volume_mgr: &VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
) -> Volume<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
where
D: crate::BlockDevice,
T: crate::TimeSource,
{
Volume::new(self, volume_mgr)
}
}
#[derive(Debug)]
pub struct BlockCache<D> {
block_device: D,
block: [Block; 1],
block_idx: Option<BlockIdx>,
}
impl<D> BlockCache<D>
where
D: BlockDevice,
{
pub fn new(block_device: D) -> Self {
BlockCache {
block_device,
block: [Block::new()],
block_idx: None,
}
}
pub fn read(&mut self, block_idx: BlockIdx) -> Result<&Block, D::Error> {
if self.block_idx != Some(block_idx) {
self.block_idx = None;
self.block_device.read(&mut self.block, block_idx)?;
self.block_idx = Some(block_idx);
}
Ok(&self.block[0])
}
pub fn read_mut(&mut self, block_idx: BlockIdx) -> Result<&mut Block, D::Error> {
if self.block_idx != Some(block_idx) {
self.block_idx = None;
self.block_device.read(&mut self.block, block_idx)?;
self.block_idx = Some(block_idx);
}
Ok(&mut self.block[0])
}
pub fn write_back(&mut self) -> Result<(), D::Error> {
self.block_device.write(
&self.block,
self.block_idx.expect("write_back with no read"),
)
}
pub fn write_back_with_duplicate(&mut self, duplicate: BlockIdx) -> Result<(), D::Error> {
self.block_device.write(
&self.block,
self.block_idx.expect("write_back with no read"),
)?;
self.block_device.write(&self.block, duplicate)?;
Ok(())
}
pub fn blank_mut(&mut self, block_idx: BlockIdx) -> &mut Block {
self.block_idx = Some(block_idx);
self.block[0].fill(0);
&mut self.block[0]
}
pub fn block_device(&mut self) -> &mut D {
self.block_idx = None;
&mut self.block_device
}
pub fn free(self) -> D {
self.block_device
}
}
pub struct Volume<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
where
D: crate::BlockDevice,
T: crate::TimeSource,
{
raw_volume: RawVolume,
volume_mgr: &'a VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
}
impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
where
D: crate::BlockDevice,
T: crate::TimeSource,
{
pub fn new(
raw_volume: RawVolume,
volume_mgr: &'a VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
) -> Self {
Volume {
raw_volume,
volume_mgr,
}
}
pub fn open_root_dir(
&self,
) -> Result<crate::Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>, Error<D::Error>> {
let d = self.volume_mgr.open_root_dir(self.raw_volume)?;
Ok(d.to_directory(self.volume_mgr))
}
pub fn to_raw_volume(self) -> RawVolume {
let v = self.raw_volume;
core::mem::forget(self);
v
}
pub fn close(self) -> Result<(), Error<D::Error>> {
let result = self.volume_mgr.close_volume(self.raw_volume);
core::mem::forget(self);
result
}
}
impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop
for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
where
D: crate::BlockDevice,
T: crate::TimeSource,
{
fn drop(&mut self) {
_ = self.volume_mgr.close_volume(self.raw_volume)
}
}
impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
core::fmt::Debug for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
where
D: crate::BlockDevice,
T: crate::TimeSource,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Volume({})", self.raw_volume.0.0)
}
}
#[cfg(feature = "defmt-log")]
impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
defmt::Format for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
where
D: crate::BlockDevice,
T: crate::TimeSource,
{
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "Volume({})", self.raw_volume.0.0)
}
}
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct VolumeInfo {
raw_volume: RawVolume,
idx: VolumeIdx,
volume_type: VolumeType,
}
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
#[derive(Debug, PartialEq, Eq)]
pub enum VolumeType {
Fat(FatVolume),
}
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct VolumeIdx(pub usize);
const PARTITION_ID_FAT32_LBA: u8 = 0x0C;
const PARTITION_ID_FAT16_LBA: u8 = 0x0E;
const PARTITION_ID_FAT16: u8 = 0x06;
const PARTITION_ID_FAT16_SMALL: u8 = 0x04;
const PARTITION_ID_FAT32_CHS_LBA: u8 = 0x0B;