use super::*;
use crate::{error::*, path::*, utils};
use core::{
cell::{Ref, RefCell},
cmp, iter, num, ops,
};
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::ToString, vec, vec::Vec};
use ::time;
use embedded_io::*;
use time::PrimitiveDateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FATType {
FAT12,
FAT16,
FAT32,
ExFAT,
}
impl FATType {
#[inline]
fn bits_per_entry(&self) -> u8 {
match self {
FATType::FAT12 => 12,
FATType::FAT16 => 16,
FATType::FAT32 => 32,
FATType::ExFAT => 32,
}
}
#[inline]
fn entry_size(&self) -> u8 {
self.bits_per_entry().next_power_of_two() / 8
}
}
const RESERVED_FAT_ENTRIES: FATEntryCount = 2;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum FATEntry {
Free,
Allocated(ClusterIndex),
Reserved,
Bad,
Eof,
}
impl From<FATEntry> for FATEntryValue {
fn from(value: FATEntry) -> Self {
Self::from(&value)
}
}
impl From<&FATEntry> for FATEntryValue {
fn from(value: &FATEntry) -> Self {
match value {
FATEntry::Free => FATEntryValue::MIN,
FATEntry::Allocated(cluster) => *cluster,
FATEntry::Reserved => 0xFFFFFF6,
FATEntry::Bad => 0xFFFFFF7,
FATEntry::Eof => FATEntryValue::MAX,
}
}
}
struct FATEntryProps {
fat_sector: SectorIndex,
sector_offset: usize,
}
impl FATEntryProps {
pub fn new<S>(n: FATEntryIndex, fs: &FileSystem<S>) -> Self
where
S: Read + Seek,
{
let fat_byte_offset: u64 = u64::from(n) * u64::from(fs.fat_type.bits_per_entry()) / 8;
let fat_sector = SectorIndex::try_from(
u64::from(fs.props.first_fat_sector)
+ fat_byte_offset / u64::from(fs.props.sector_size),
)
.expect("this should fit into a u32");
let sector_offset: usize =
usize::try_from(fat_byte_offset % u64::from(fs.props.sector_size))
.expect("this should fit into a usize");
FATEntryProps {
fat_sector,
sector_offset,
}
}
}
pub(crate) type FATOffset = u8;
struct FATSectorProps {
#[allow(unused)]
fat_offset: FATOffset,
sector_offset: SectorIndex,
}
impl FATSectorProps {
pub fn new<S>(sector: SectorIndex, fs: &FileSystem<S>) -> Option<Self>
where
S: Read + Seek,
{
if !fs.sector_belongs_to_FAT(sector) {
return None;
}
let sector_offset_from_first_fat = sector - SectorIndex::from(fs.props.first_fat_sector);
let fat_offset =
FATOffset::try_from(sector_offset_from_first_fat / fs.props.fat_sector_size)
.expect("this should fit in a u89");
let sector_offset = sector_offset_from_first_fat % fs.props.fat_sector_size;
Some(FATSectorProps {
fat_offset,
sector_offset,
})
}
#[allow(non_snake_case)]
pub fn get_corresponding_FAT_sectors<S>(&self, fs: &FileSystem<S>) -> Box<[SectorIndex]>
where
S: Read + Seek,
{
let mut vec = Vec::with_capacity(fs.props.fat_table_count.into());
for i in 0..fs.props.fat_table_count {
vec.push(
SectorIndex::from(fs.props.first_fat_sector)
+ SectorCount::from(i) * fs.props.fat_sector_size
+ self.sector_offset,
)
}
vec.into_boxed_slice()
}
}
#[derive(Debug, Clone)]
pub(crate) struct DirInfo {
pub(crate) path: PathBuf,
pub(crate) chain_start: EntryLocationUnit,
pub(crate) chain_end: Option<EntryLocation>,
}
impl DirInfo {
pub(crate) fn at_root_dir(boot_record: &BootRecord) -> Self {
DirInfo {
path: PathBuf::from(path_consts::SEPARATOR_STR),
chain_start: match boot_record {
BootRecord::Fat(boot_record_fat) => match &boot_record_fat.ebr {
Ebr::FAT12_16(_ebr_fat12_16) => EntryLocationUnit::RootDirSector(0),
Ebr::FAT32(ebr_fat32, _) => {
EntryLocationUnit::DataCluster(ebr_fat32.root_cluster)
}
},
BootRecord::ExFAT(_boot_record_exfat) => todo!(),
},
chain_end: None,
}
}
}
impl<S> iter::FusedIterator for ReadDir<'_, S> where S: Read + Seek {}
pub(crate) trait OffsetConversions {
fn sector_size(&self) -> u16;
fn cluster_size(&self) -> u32;
fn first_data_sector(&self) -> SectorIndex;
#[inline]
fn cluster_to_sector(&self, cluster: ClusterIndex) -> SectorIndex {
cluster * ClusterIndex::from(self.sectors_per_cluster())
}
#[inline]
fn sectors_per_cluster(&self) -> u8 {
(self.cluster_size() / u32::from(self.sector_size()))
.try_into()
.expect("the SecPerClus field is 1 byte long (u8)")
}
#[inline]
fn sector_to_partition_offset(&self, sector: SectorIndex) -> u64 {
u64::from(sector) * u64::from(self.sector_size())
}
#[inline]
fn data_cluster_to_partition_sector(&self, cluster: ClusterIndex) -> SectorIndex {
self.cluster_to_sector(cluster - RESERVED_FAT_ENTRIES) + self.first_data_sector()
}
#[inline]
fn partition_sector_to_data_cluster(&self, sector: SectorIndex) -> ClusterIndex {
(sector - self.first_data_sector()) / ClusterIndex::from(self.sectors_per_cluster())
+ RESERVED_FAT_ENTRIES
}
}
impl<S> OffsetConversions for FileSystem<S>
where
S: Read + Seek,
{
#[inline]
fn sector_size(&self) -> u16 {
self.props.sector_size
}
#[inline]
fn cluster_size(&self) -> u32 {
self.props.cluster_size
}
#[inline]
fn first_data_sector(&self) -> SectorIndex {
self.props.first_data_sector
}
#[inline]
fn sectors_per_cluster(&self) -> u8 {
self.props.sec_per_clus
}
}
#[derive(Debug)]
pub(crate) struct FSProperties {
pub(crate) sector_size: u16,
pub(crate) cluster_size: u32,
pub(crate) sec_per_clus: u8,
pub(crate) total_sectors: SectorCount,
pub(crate) total_clusters: ClusterCount,
pub(crate) fat_table_count: u8,
pub(crate) fat_sector_size: u32,
pub(crate) first_fat_sector: u16,
pub(crate) first_root_dir_sector: SectorIndex,
pub(crate) first_data_sector: SectorIndex,
}
impl From<&BootRecord> for FSProperties {
fn from(value: &BootRecord) -> Self {
let sector_size = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.bpb.bytes_per_sector,
BootRecord::ExFAT(boot_record_exfat) => 1 << boot_record_exfat.sector_shift,
};
let cluster_size = match value {
BootRecord::Fat(boot_record_fat) => {
u32::from(boot_record_fat.bpb.sectors_per_cluster) * u32::from(sector_size)
}
BootRecord::ExFAT(boot_record_exfat) => {
1 << (boot_record_exfat.sector_shift + boot_record_exfat.cluster_shift)
}
};
let sec_per_clus = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.bpb.sectors_per_cluster,
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT is not yet implemented"),
};
let total_sectors = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.total_sectors(),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT is not yet implemented"),
};
let total_clusters = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.total_clusters(),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT is not yet implemented"),
};
let fat_table_count = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.bpb.table_count,
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT is not yet implemented"),
};
let fat_sector_size = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.fat_sector_size(),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT not yet implemented"),
};
let first_fat_sector = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.first_fat_sector(),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT not yet implemented"),
};
let first_root_dir_sector = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.first_root_dir_sector(),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT is not yet implemented"),
};
let first_data_sector = match value {
BootRecord::Fat(boot_record_fat) => boot_record_fat.first_data_sector(),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT is not yet implemented"),
};
FSProperties {
sector_size,
cluster_size,
sec_per_clus,
fat_table_count,
fat_sector_size,
first_fat_sector,
total_sectors,
total_clusters,
first_root_dir_sector,
first_data_sector,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct FileFilter {
show_hidden: bool,
show_systen: bool,
}
impl FileFilter {
pub(crate) fn filter(&self, item: &RawProperties) -> bool {
let is_hidden = item.attributes.contains(RawAttributes::HIDDEN);
let is_system = item.attributes.contains(RawAttributes::SYSTEM);
let should_filter = !self.show_hidden && is_hidden || !self.show_systen && is_system;
!should_filter
}
}
#[allow(clippy::derivable_impls)]
impl Default for FileFilter {
fn default() -> Self {
FileFilter {
show_hidden: false,
show_systen: false,
}
}
}
type SyncSectorBufferFn<S> = fn(&FileSystem<S>) -> Result<(), <S as ErrorType>::Error>;
type UnmountFn<S> = fn(&FileSystem<S>) -> FSResult<(), <S as ErrorType>::Error>;
#[derive(Debug)]
pub struct FileSystem<S>
where
S: Read + Seek,
{
storage: RefCell<S>,
pub(crate) sector_buffer: RefCell<SectorBuffer>,
fsinfo_modified: RefCell<bool>,
pub(crate) dir_info: RefCell<DirInfo>,
sync_f: RefCell<Option<SyncSectorBufferFn<S>>>,
unmount_f: RefCell<Option<UnmountFn<S>>>,
pub(crate) options: FSOptions,
pub(crate) boot_record: RefCell<BootRecord>,
fat_type: FATType,
pub(crate) props: FSProperties,
first_free_cluster: RefCell<ClusterIndex>,
pub(crate) filter: RefCell<FileFilter>,
}
impl<S> FileSystem<S>
where
S: Read + Seek,
{
pub fn fat_type(&self) -> FATType {
self.fat_type
}
}
impl<S> FileSystem<S>
where
S: Read + Seek,
{
#[inline]
pub fn show_hidden(&self, show: bool) {
self.filter.borrow_mut().show_hidden = show;
}
#[inline]
pub fn show_system(&self, show: bool) {
self.filter.borrow_mut().show_systen = show;
}
}
impl<S> FileSystem<S>
where
S: Read + Seek,
{
pub fn new(mut storage: S, options: FSOptions) -> FSResult<Self, S::Error> {
use utils::bincode::BINCODE_CONFIG;
let mut buffer = [0u8; MAX_SECTOR_SIZE];
let bytes_read = storage.read(&mut buffer)?;
let mut stored_sector = 0;
if bytes_read < MIN_SECTOR_SIZE {
return Err(FSError::InternalFSError(InternalFSError::StorageTooSmall));
}
let bpb: BpbFat = bincode::decode_from_slice(&buffer[..BPBFAT_SIZE], BINCODE_CONFIG)
.map(|(v, _)| v)
.map_err(utils::bincode::map_err_dec)?;
let ebr = if bpb.table_size_16 == 0 {
let ebr_fat32: EBRFAT32 = bincode::decode_from_slice(
&buffer[BPBFAT_SIZE..BPBFAT_SIZE + EBR_SIZE],
BINCODE_CONFIG,
)
.map(|(v, _)| v)
.map_err(utils::bincode::map_err_dec)?;
storage.seek(SeekFrom::Start(
u64::from(ebr_fat32.fat_info) * u64::from(bpb.bytes_per_sector),
))?;
stored_sector = ebr_fat32.fat_info.into();
storage.read_exact(&mut buffer[..usize::from(bpb.bytes_per_sector)])?;
let fsinfo: FSInfoFAT32 = bincode::decode_from_slice(
&buffer[..usize::from(bpb.bytes_per_sector)],
BINCODE_CONFIG,
)
.map(|(v, _)| v)
.map_err(utils::bincode::map_err_dec)?;
if !fsinfo.verify_signature() {
log::error!("FAT32 FSInfo has invalid signature(s)");
return Err(FSError::InternalFSError(InternalFSError::InvalidFSInfoSig));
}
Ebr::FAT32(ebr_fat32, fsinfo)
} else {
Ebr::FAT12_16(
bincode::decode_from_slice(
&buffer[BPBFAT_SIZE..BPBFAT_SIZE + EBR_SIZE],
BINCODE_CONFIG,
)
.map(|(v, _)| v)
.map_err(utils::bincode::map_err_dec)?,
)
};
let boot_record = BootRecord::Fat(BootRecordFAT { bpb, ebr });
let fat_type = boot_record.fat_type();
if fat_type == FATType::ExFAT {
log::error!("Filesystem is ExFAT, which is currently unsupported");
return Err(FSError::UnsupportedFS);
}
log::info!("The FAT type of the filesystem is {fat_type:?}");
match &boot_record {
BootRecord::Fat(boot_record_fat) => {
if boot_record_fat.verify_signature() {
log::error!("FAT boot record has invalid signature(s)");
return Err(FSError::InternalFSError(InternalFSError::InvalidBPBSig));
}
}
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT not yet implemented"),
};
let props = FSProperties::from(&boot_record);
let fs = Self {
storage: storage.into(),
sector_buffer: SectorBuffer::new(
Box::from(&buffer[..usize::from(props.sector_size)]),
stored_sector,
)
.into(),
fsinfo_modified: false.into(),
options,
dir_info: DirInfo::at_root_dir(&boot_record).into(),
sync_f: None.into(),
unmount_f: None.into(),
boot_record: boot_record.into(),
fat_type,
props,
first_free_cluster: RESERVED_FAT_ENTRIES.into(),
filter: FileFilter::default().into(),
};
if !fs.FAT_tables_are_identical()? {
return Err(FSError::InternalFSError(
InternalFSError::MismatchingFATTables,
));
}
Ok(fs)
}
}
impl<S> FileSystem<S>
where
S: Read + Seek,
{
pub(crate) fn process_current_dir<'a>(&'a self) -> ReadDirInt<'a, S> {
ReadDirInt::new(self, &self.dir_info.borrow().chain_start)
}
fn _go_to_parent_dir(&self) -> FSResult<(), S::Error> {
if let Some(parent_path) = self.dir_info.borrow().path.parent() {
let parent_pathbuf = parent_path.to_path_buf();
let mut entries = self.process_current_dir();
let parent_entry = entries
.nth(NONROOT_MIN_DIRENTRIES - 1)
.transpose()?
.filter(|entry| entry.is_dir && entry.name == path_consts::PARENT_DIR_STR)
.ok_or(FSError::InternalFSError(
InternalFSError::MalformedEntryChain,
))?;
self.dir_info.borrow_mut().path = parent_pathbuf;
self.dir_info.borrow_mut().chain_start =
EntryLocationUnit::DataCluster(parent_entry.data_cluster);
self.dir_info.borrow_mut().chain_end = None;
} else {
self._go_to_root_directory();
}
Ok(())
}
fn _go_to_child_dir(&self, name: &str) -> FSResult<(), S::Error> {
let mut entries = self.process_current_dir();
let child_entry = loop {
let entry = entries.next().ok_or(FSError::NotFound)??;
if entry.name == name {
break entry;
}
};
if !child_entry.is_dir {
return Err(FSError::NotADirectory);
}
self.dir_info.borrow_mut().path.push(&child_entry.name);
self.dir_info.borrow_mut().chain_start =
EntryLocationUnit::DataCluster(child_entry.data_cluster);
self.dir_info.borrow_mut().chain_end = None;
Ok(())
}
fn _go_to_root_directory(&self) {
*self.dir_info.borrow_mut() = DirInfo::at_root_dir(&self.boot_record.borrow());
}
fn _go_up_till_target<P>(&self, target: P) -> FSResult<(), S::Error>
where
P: AsRef<Path>,
{
let target = target.as_ref();
while self.dir_info.borrow().path != target {
self._go_to_parent_dir()?;
}
Ok(())
}
fn _go_down_till_target<P>(&self, target: P) -> FSResult<(), S::Error>
where
P: AsRef<Path>,
{
let target = target.as_ref();
let common_path_prefix = find_common_path_prefix(&self.dir_info.borrow().path, target);
let common_components = common_path_prefix
.normalize()
.components()
.filter(keep_path_normals)
.count();
for dir_name in target
.components()
.filter(keep_path_normals)
.skip(common_components)
{
self._go_to_child_dir(dir_name.as_str())?;
}
Ok(())
}
fn _go_to_cached_dir(&self) -> FSResult<(), S::Error> {
let dir_chain = self.dir_info.borrow().chain_start;
let target_sector = dir_chain.get_entry_sector(self);
if target_sector != self.sector_buffer.borrow().stored_sector {
self.load_nth_sector(target_sector)?;
}
Ok(())
}
pub(crate) fn go_to_dir<P>(&self, target: P) -> FSResult<(), S::Error>
where
P: AsRef<Path>,
{
let target = target.as_ref();
if !target.is_valid() {
return Err(FSError::MalformedPath);
}
if self.dir_info.borrow().path == target {
self._go_to_cached_dir()?;
return Ok(());
}
let common_path_prefix = find_common_path_prefix(&self.dir_info.borrow().path, target);
let distance_from_root = common_path_prefix.ancestors().count() - 1;
let distance_from_current_path =
(self.dir_info.borrow().path.ancestors().count() - 1) - distance_from_root;
if distance_from_root <= distance_from_current_path {
self._go_to_root_directory();
self._go_down_till_target(target)?;
} else {
self._go_up_till_target(common_path_prefix)?;
self._go_down_till_target(target)?;
}
Ok(())
}
pub(crate) fn next_free_cluster(&self) -> Result<Option<ClusterIndex>, S::Error> {
let start_cluster = match *self.boot_record.borrow() {
BootRecord::Fat(ref boot_record_fat) => {
let mut first_free_cluster = *self.first_free_cluster.borrow();
if let Ebr::FAT32(_, fsinfo) = &boot_record_fat.ebr {
if fsinfo.first_free_cluster != ClusterIndex::MAX
&& fsinfo.first_free_cluster <= self.props.total_sectors
{
first_free_cluster =
cmp::min(first_free_cluster, fsinfo.first_free_cluster);
}
}
first_free_cluster
}
BootRecord::ExFAT(_) => todo!("ExFAT not yet implemented"),
};
let mut current_cluster = start_cluster;
while current_cluster < self.props.total_clusters {
if self.read_nth_FAT_entry(current_cluster)? == FATEntry::Free {
*self.first_free_cluster.borrow_mut() = current_cluster;
match *self.boot_record.borrow_mut() {
BootRecord::Fat(ref mut boot_record_fat) => {
if let Ebr::FAT32(_, fsinfo) = &mut boot_record_fat.ebr {
fsinfo.first_free_cluster = current_cluster;
*self.fsinfo_modified.borrow_mut() = true;
}
}
BootRecord::ExFAT(_) => todo!("ExFAT not yet implemented"),
}
return Ok(Some(current_cluster));
}
current_cluster += 1;
}
*self.first_free_cluster.borrow_mut() = self.props.total_clusters - 1;
Ok(None)
}
pub(crate) fn get_next_cluster(
&self,
cluster: ClusterIndex,
) -> Result<Option<ClusterIndex>, S::Error> {
Ok(match self.read_nth_FAT_entry(cluster)? {
FATEntry::Allocated(next_cluster) => Some(next_cluster),
_ => None,
})
}
#[allow(non_snake_case)]
pub(crate) fn FAT_tables_are_identical(&self) -> Result<bool, S::Error> {
assert_ne!(
self.fat_type,
FATType::ExFAT,
"this function doesn't work with ExFAT"
);
const MAX_PROBE_SIZE: u32 = 1 << 20;
let fat_byte_size = match &*self.boot_record.borrow() {
BootRecord::Fat(boot_record_fat) => boot_record_fat.fat_sector_size(),
BootRecord::ExFAT(_) => unreachable!(),
};
for nth_iteration in 0..fat_byte_size.div_ceil(MAX_PROBE_SIZE) {
let mut tables: Vec<Vec<u8>> = Vec::new();
for i in 0..self.props.fat_table_count {
let fat_start = u32::try_from(
self.sector_to_partition_offset(self.boot_record.borrow().nth_FAT_table_sector(i)),
)
.expect("there's no way the FAT is more that 4GBs away from the start of the storage medium");
let current_offset = fat_start + nth_iteration * MAX_PROBE_SIZE;
let bytes_left = fat_byte_size - nth_iteration * MAX_PROBE_SIZE;
self.storage
.borrow_mut()
.seek(SeekFrom::Start(current_offset.into()))?;
let mut buf = vec![
0_u8;
usize::try_from(cmp::min(MAX_PROBE_SIZE, bytes_left))
.unwrap_or(usize::MAX)
];
self.storage
.borrow_mut()
.read_exact(buf.as_mut_slice())
.map_err(|e| match e {
ReadExactError::UnexpectedEof => {
panic!("Unexpected EOF while reading FAT table")
}
ReadExactError::Other(e) => e,
})?;
tables.push(buf);
}
if !tables.iter().skip(1).all(|buf| buf == &tables[0]) {
return Ok(false);
}
}
Ok(true)
}
#[allow(non_snake_case)]
pub(crate) fn sector_belongs_to_FAT(&self, sector: SectorIndex) -> bool {
match &*self.boot_record.borrow() {
BootRecord::Fat(boot_record_fat) => (boot_record_fat.first_fat_sector().into()
..boot_record_fat.first_root_dir_sector())
.contains(§or),
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT not yet implemented"),
}
}
pub(crate) fn load_nth_sector(&self, n: SectorIndex) -> Result<Ref<'_, [u8]>, S::Error> {
if n >= self.props.total_sectors {
panic!(concat!(
"seeked past end of device medium. ",
"This is most likely an internal error, please report it: ",
"https://github.com/Oakchris1955/simple-fatfs/issues"
));
}
let stored_sector = self.sector_buffer.borrow().stored_sector;
if n != stored_sector {
let sync_sector_option = *self.sync_f.borrow();
if let Some(sync_sector_buffer) = sync_sector_option {
log::debug!("Syncing sector {stored_sector}");
sync_sector_buffer(self)?;
*self.sync_f.borrow_mut() = None;
}
self.storage
.borrow_mut()
.seek(SeekFrom::Start(self.sector_to_partition_offset(n)))?;
self.storage
.borrow_mut()
.read_exact(&mut self.sector_buffer.borrow_mut())
.map_err(|e| match e {
ReadExactError::UnexpectedEof => {
panic!("Unexpected EOF while reading sector {n}\n\
This is most likely an interal error, please report it: https://github.com/Oakchris1955/simple-fatfs/issues")
}
ReadExactError::Other(e) => e,
})?;
self.storage
.borrow_mut()
.seek(SeekFrom::Current(-i64::from(self.props.sector_size)))?;
self.sector_buffer.borrow_mut().stored_sector = n;
}
Ok(Ref::map(self.sector_buffer.borrow(), |s| &**s))
}
#[allow(non_snake_case)]
pub(crate) fn read_nth_FAT_entry(&self, n: FATEntryIndex) -> Result<FATEntry, S::Error> {
let entry_size = self.fat_type.entry_size();
let entry_props = FATEntryProps::new(n, self);
self.load_nth_sector(entry_props.fat_sector)?;
let mut value_bytes = [0_u8; 4];
let bytes_to_read: usize = cmp::min(
entry_props.sector_offset + usize::from(entry_size),
usize::from(self.sector_size()),
) - entry_props.sector_offset;
value_bytes[..bytes_to_read].copy_from_slice(
&self.sector_buffer.borrow_mut()
[entry_props.sector_offset..entry_props.sector_offset + bytes_to_read],
);
if self.fat_type == FATType::FAT12 && bytes_to_read < usize::from(entry_size) {
self.load_nth_sector(entry_props.fat_sector + 1)?;
value_bytes[bytes_to_read..usize::from(entry_size)].copy_from_slice(
&self.sector_buffer.borrow_mut()[..(usize::from(entry_size) - bytes_to_read)],
);
};
let mut value = FATEntryValue::from_le_bytes(value_bytes);
match self.fat_type {
FATType::FAT12 => {
if n & 1 != 0 {
value >>= 4
} else {
value &= 0xFFF
}
}
FATType::FAT32 => value &= 0x0FFFFFFF,
_ => (),
}
Ok(match self.fat_type {
FATType::FAT12 => match value {
0x000 => FATEntry::Free,
0xFF7 => FATEntry::Bad,
#[allow(clippy::manual_range_patterns)]
0xFF8..=0xFFE | 0xFFF => FATEntry::Eof,
_ => {
if (0x002..(self.props.total_clusters + 1)).contains(&value) {
FATEntry::Allocated(value)
} else {
FATEntry::Reserved
}
}
},
FATType::FAT16 => match value {
0x0000 => FATEntry::Free,
0xFFF7 => FATEntry::Bad,
#[allow(clippy::manual_range_patterns)]
0xFFF8..=0xFFFE | 0xFFFF => FATEntry::Eof,
_ => {
if (0x0002..(self.props.total_clusters + 1)).contains(&value) {
FATEntry::Allocated(value)
} else {
FATEntry::Reserved
}
}
},
FATType::FAT32 => match value {
0x00000000 => FATEntry::Free,
0x0FFFFFF7 => FATEntry::Bad,
#[allow(clippy::manual_range_patterns)]
0x0FFFFFF8..=0xFFFFFFE | 0x0FFFFFFF => FATEntry::Eof,
_ => {
if (0x00000002..(self.props.total_clusters + 1)).contains(&value) {
FATEntry::Allocated(value)
} else {
FATEntry::Reserved
}
}
},
FATType::ExFAT => todo!("ExFAT not yet implemented"),
})
}
}
impl<S> FileSystem<S>
where
S: Read + Write + Seek,
{
#[allow(non_snake_case)]
pub(crate) fn write_nth_FAT_entry(
&self,
n: FATEntryIndex,
entry: FATEntry,
) -> Result<(), S::Error> {
let entry_size = self.fat_type.entry_size();
let entry_props = FATEntryProps::new(n, self);
let mask = utils::bits::setbits_u32_lo(self.fat_type.bits_per_entry());
let mut value: FATEntryValue = FATEntryValue::from(entry.clone()) & mask;
if self.fat_type == FATType::FAT32 {
value &= 0x0FFFFFFF;
}
match self.fat_type {
FATType::FAT12 => {
let should_shift = n & 1 != 0;
if should_shift {
value <<= 4;
}
self.load_nth_sector(entry_props.fat_sector)?;
let value_bytes = value.to_le_bytes();
let mut first_byte = value_bytes[0];
if should_shift {
let mut old_byte = self.sector_buffer.borrow()[entry_props.sector_offset];
old_byte &= 0x0F;
first_byte |= old_byte;
}
self.sector_buffer.borrow_mut()[entry_props.sector_offset] = first_byte; self.set_modified();
let bytes_left_on_sector: usize = cmp::min(
usize::from(entry_size),
usize::from(self.sector_size()) - entry_props.sector_offset,
);
if bytes_left_on_sector < entry_size.into() {
self.load_nth_sector(entry_props.fat_sector + 1)?;
}
let mut second_byte = value_bytes[1];
let second_byte_index =
(entry_props.sector_offset + 1) % usize::from(self.sector_size());
if !should_shift {
let mut old_byte = self.sector_buffer.borrow()[second_byte_index];
old_byte &= 0xF0;
second_byte |= old_byte;
}
self.sector_buffer.borrow_mut()[second_byte_index] = second_byte; self.set_modified();
}
FATType::FAT16 | FATType::FAT32 => {
self.load_nth_sector(entry_props.fat_sector)?;
let value_bytes = value.to_le_bytes();
self.sector_buffer.borrow_mut()[entry_props.sector_offset
..entry_props.sector_offset + usize::from(entry_size)]
.copy_from_slice(&value_bytes[..usize::from(entry_size)]); self.set_modified();
}
FATType::ExFAT => todo!("ExFAT not yet implemented"),
};
if entry == FATEntry::Free && n < *self.first_free_cluster.borrow() {
*self.first_free_cluster.borrow_mut() = n;
}
if let BootRecord::Fat(boot_record_fat) = &mut *self.boot_record.borrow_mut() {
if let Ebr::FAT32(_, fsinfo) = &mut boot_record_fat.ebr {
match entry {
FATEntry::Free => {
fsinfo.free_cluster_count += 1;
if n < fsinfo.first_free_cluster {
fsinfo.first_free_cluster = n;
}
}
_ => fsinfo.free_cluster_count -= 1,
};
*self.fsinfo_modified.borrow_mut() = true;
}
}
Ok(())
}
pub(crate) fn allocate_nth_entries(
&self,
n: num::NonZero<EntryCount>,
) -> FSResult<EntryLocation, S::Error> {
self._go_to_cached_dir()?;
let mut first_entry = self.dir_info.borrow().chain_end.unwrap_or_else(|| {
let stored_sector = self.sector_buffer.borrow().stored_sector;
EntryLocation::from_partition_sector(stored_sector, self)
});
let mut last_entry = first_entry;
let mut chain_len = 0;
let mut entry_count: EntryCount = 0;
loop {
let entry_status = last_entry.entry_status(self)?;
entry_count += 1;
match entry_status {
EntryStatus::Unused | EntryStatus::LastUnused => chain_len += 1,
EntryStatus::Used => chain_len = 0,
}
if chain_len >= n.get() {
return Ok(first_entry);
}
if entry_status == EntryStatus::LastUnused {
break;
}
if last_entry.unit.get_next_unit(self)?.is_none()
&& last_entry.index + 1 >= last_entry.unit.get_max_offset(self)
{
break;
}
#[allow(clippy::absurd_extreme_comparisons)]
if entry_count + n.get() >= DIRENTRY_LIMIT {
let new_entry_count = self.defragment_entry_chain()?;
if new_entry_count + n.get() >= DIRENTRY_LIMIT {
return Err(FSError::DirEntryLimitReached);
}
return self.allocate_nth_entries(n);
}
last_entry = last_entry
.next_entry(self)?
.ok_or(FSError::InternalFSError(
InternalFSError::MalformedEntryChain,
))?;
if entry_status == EntryStatus::Used {
first_entry = last_entry;
}
}
self.dir_info.borrow_mut().chain_end = Some(last_entry);
match last_entry.unit {
EntryLocationUnit::RootDirSector(_) => {
let remaining_sectors: SectorCount = match &*self.boot_record.borrow() {
BootRecord::Fat(boot_record_fat) => {
boot_record_fat.first_root_dir_sector()
+ SectorCount::from(boot_record_fat.root_dir_sectors())
- last_entry.unit.get_entry_sector(self)
}
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT not yet implemented"),
};
let entries_per_sector = self.props.sector_size
/ u16::try_from(DIRENTRY_SIZE).expect("32 can fit in a u16");
let remaining_entries = EntryCount::try_from(
remaining_sectors * SectorCount::from(entries_per_sector)
+ (SectorCount::from(entries_per_sector)
- SectorCount::from(last_entry.index)
+ 1),
)
.unwrap();
if remaining_entries < n.get() - chain_len {
Err(FSError::RootDirectoryFull)
} else {
Ok(first_entry)
}
}
EntryLocationUnit::DataCluster(cluster) => {
let entries_per_cluster = u16::try_from(
self.props.cluster_size
/ u32::try_from(DIRENTRY_SIZE).expect("32 can fit into u32"),
)
.expect("a cluster can have a max of ~16k entries");
let entries_left = n.get() - chain_len;
let free_entries_on_current_cluster = entries_per_cluster - (last_entry.index + 1);
if free_entries_on_current_cluster < entries_left {
let clusters_to_allocate = (entries_left - free_entries_on_current_cluster)
.div_ceil(entries_per_cluster);
let first_cluster = self.allocate_clusters(
num::NonZero::new(clusters_to_allocate.into())
.expect("this should be at least 1"),
Some(cluster),
)?;
if chain_len == 0 {
first_entry.unit = EntryLocationUnit::DataCluster(first_cluster);
first_entry.index = 0;
}
for cluster in
first_cluster..(first_cluster + ClusterCount::from(clusters_to_allocate))
{
let first_sector = self.data_cluster_to_partition_sector(cluster);
for sector in first_sector
..(first_sector + SectorCount::from(self.sectors_per_cluster()))
{
self.load_nth_sector(sector)?;
self.sector_buffer.borrow_mut().fill(0);
self.set_modified();
}
}
}
Ok(first_entry)
}
}
}
pub(crate) fn create_entry_chain(
&self,
parent: EntryLocationUnit,
datetime: PrimitiveDateTime,
) -> FSResult<u32, S::Error> {
let dir_cluster = self.allocate_clusters(num::NonZero::new(1).unwrap(), None)?;
let entries = Box::from([
MinProperties {
name: Box::from(typed_path::constants::windows::CURRENT_DIR_STR),
sfn: CURRENT_DIR_SFN,
attributes: RawAttributes::empty() | RawAttributes::DIRECTORY,
created: Some(datetime),
modified: datetime,
accessed: Some(datetime.date()),
file_size: 0,
data_cluster: dir_cluster,
},
MinProperties {
name: Box::from(typed_path::constants::windows::PARENT_DIR_STR),
sfn: PARENT_DIR_SFN,
attributes: RawAttributes::empty() | RawAttributes::DIRECTORY,
created: Some(datetime),
modified: datetime,
accessed: Some(datetime.date()),
file_size: 0,
data_cluster: match parent {
EntryLocationUnit::DataCluster(cluster) => cluster,
EntryLocationUnit::RootDirSector(_) => 0,
},
},
]);
let entries_iter = EntryComposer::new(entries, &self.options.codepage);
self.load_nth_sector(self.data_cluster_to_partition_sector(dir_cluster))?;
self.sector_buffer.borrow_mut().fill(0);
let mut entry_location = EntryLocation {
unit: EntryLocationUnit::DataCluster(dir_cluster),
index: 0,
};
for (i, bytes) in entries_iter.enumerate() {
entry_location.set_bytes(self, bytes)?;
if i < NONROOT_MIN_DIRENTRIES {
entry_location = entry_location
.next_entry(self)?
.expect("this will only be called once");
}
}
self.set_modified();
let stored_sector = self.sector_buffer.borrow().stored_sector;
for sector in
(stored_sector + 1)..(stored_sector + SectorCount::from(self.sectors_per_cluster()))
{
self.load_nth_sector(sector)?;
self.sector_buffer.borrow_mut().fill(0);
self.set_modified();
}
Ok(dir_cluster)
}
pub(crate) fn insert_to_entry_chain(
&self,
entries: Box<[MinProperties]>,
) -> FSResult<DirEntryChain, S::Error> {
let mut entries_needed = 0;
self._go_to_cached_dir()?;
for entry in &entries {
entries_needed += calc_entries_needed(&*entry.name, &self.options.codepage).get();
}
let first_entry = self.allocate_nth_entries(
num::NonZero::new(entries_needed).expect("The entries array shouldn't be empty"),
)?;
let mut entries_iter = EntryComposer::new(entries, &self.options.codepage);
let mut current_entry = first_entry;
let mut entry_bytes = entries_iter
.next()
.expect("this iterator is guaranteed to return at least once");
loop {
current_entry.set_bytes(self, entry_bytes)?;
match entries_iter.next() {
Some(bytes) => entry_bytes = bytes,
None => break,
};
current_entry = current_entry
.next_entry(self)?
.expect("This entry chain should be valid, we just generated it");
}
Ok(DirEntryChain {
len: entries_needed,
location: first_entry,
})
}
pub(crate) fn defragment_entry_chain(&self) -> FSResult<EntryCount, S::Error> {
let mut current_entry_loc = EntryLocation {
unit: self.dir_info.borrow().chain_start,
index: 0,
};
let mut new_chain_end = current_entry_loc;
let mut entry_count: EntryCount = 0;
loop {
match current_entry_loc.entry_status(self)? {
EntryStatus::Used => {
if current_entry_loc != new_chain_end {
let bytes = current_entry_loc.get_bytes(self)?;
new_chain_end.set_bytes(self, bytes)?;
#[allow(clippy::absurd_extreme_comparisons)]
if entry_count >= DIRENTRY_LIMIT {
break;
}
current_entry_loc.free_entry(self, false)?;
}
entry_count += 1;
new_chain_end = new_chain_end
.next_entry(self)?
.expect("we just pushed an entry to this chain")
}
EntryStatus::LastUnused => break,
_ => (),
}
current_entry_loc = match current_entry_loc.next_entry(self)? {
Some(entry) => entry,
None => break,
}
}
new_chain_end.free_entry(self, true)?;
self.dir_info.borrow_mut().chain_end = Some(new_chain_end);
Ok(entry_count)
}
pub(crate) fn remove_entry_chain(&self, chain: &DirEntryChain) -> Result<(), S::Error> {
let mut entries_freed = 0;
let mut current_entry = chain.location;
loop {
current_entry.free_entry(self, false)?;
entries_freed += 1;
if entries_freed >= chain.len {
break;
}
current_entry = match current_entry.next_entry(self)? {
Some(current_entry) => current_entry,
None => unreachable!(
concat!("It is guaranteed that at least as many entries ",
"as there are in chain exist, since we counted them when initializing the struct")
),
};
}
Ok(())
}
pub(crate) fn free_cluster_chain(&self, first_cluster: u32) -> Result<(), S::Error> {
let mut current_cluster = first_cluster;
loop {
let next_cluster_option = self.get_next_cluster(current_cluster)?;
self.write_nth_FAT_entry(current_cluster, FATEntry::Free)?;
match next_cluster_option {
Some(next_cluster) => current_cluster = next_cluster,
None => break,
}
}
Ok(())
}
pub(crate) fn allocate_clusters(
&self,
n: num::NonZero<ClusterCount>,
first_cluster: Option<ClusterIndex>,
) -> FSResult<ClusterIndex, S::Error> {
let mut last_cluster_in_chain = first_cluster;
let mut first_allocated_cluster = None;
for i in 0..n.into() {
match self.next_free_cluster()? {
Some(next_free_cluster) => {
if i == 0 {
first_allocated_cluster = Some(next_free_cluster);
}
if let Some(last_cluster_in_chain) = last_cluster_in_chain {
self.write_nth_FAT_entry(
last_cluster_in_chain,
FATEntry::Allocated(next_free_cluster),
)?;
}
self.write_nth_FAT_entry(next_free_cluster, FATEntry::Eof)?;
if let Some(last_cluster_in_chain) = last_cluster_in_chain {
log::trace!(
"cluster {last_cluster_in_chain} now points to {next_free_cluster}"
);
}
last_cluster_in_chain = Some(next_free_cluster);
}
None => {
log::error!("storage medium full while attempting to allocate more clusters");
return Err(FSError::StorageFull);
}
}
}
Ok(first_allocated_cluster.expect("This should have Some value by now"))
}
fn _sync_current_sector(&self) -> Result<(), S::Error> {
self.storage
.borrow_mut()
.write_all(&self.sector_buffer.borrow())?;
self.storage
.borrow_mut()
.seek(SeekFrom::Current(-i64::from(self.props.sector_size)))?;
Ok(())
}
#[allow(non_snake_case)]
fn _sync_FAT_sector(&self, fat_sector_props: &FATSectorProps) -> Result<(), S::Error> {
let current_offset = self.storage.borrow_mut().stream_position()?;
for sector in fat_sector_props.get_corresponding_FAT_sectors(self) {
self.storage.borrow_mut().seek(SeekFrom::Start(u64::from(
sector * u32::from(self.props.sector_size),
)))?;
self.storage
.borrow_mut()
.write_all(&self.sector_buffer.borrow())?;
}
self.storage
.borrow_mut()
.seek(SeekFrom::Start(current_offset))?;
Ok(())
}
pub(crate) fn set_modified(&self) {
*self.sync_f.borrow_mut() = Some(Self::sync_sector_buffer);
*self.unmount_f.borrow_mut() = Some(Self::unmount);
}
pub(crate) fn sync_sector_buffer(&self) -> Result<(), S::Error> {
let stored_sector = self.sector_buffer.borrow().stored_sector;
if let Some(fat_sector_props) = FATSectorProps::new(stored_sector, self) {
log::trace!("syncing FAT sector {}", fat_sector_props.sector_offset,);
match &*self.boot_record.borrow() {
BootRecord::Fat(boot_record_fat) => match &boot_record_fat.ebr {
Ebr::FAT12_16(_) => {
self._sync_FAT_sector(&fat_sector_props)?;
}
Ebr::FAT32(ebr_fat32, _) => {
if ebr_fat32.extended_flags.mirroring_disabled() {
self._sync_current_sector()?;
} else {
self._sync_FAT_sector(&fat_sector_props)?;
}
}
},
BootRecord::ExFAT(_boot_record_exfat) => todo!("ExFAT not yet implemented"),
}
} else {
log::trace!(
"syncing sector {}",
self.sector_buffer.borrow().stored_sector
);
self._sync_current_sector()?;
}
*self.sync_f.borrow_mut() = None;
Ok(())
}
pub(crate) fn sync_fsinfo(&self) -> FSResult<(), S::Error> {
use utils::bincode::BINCODE_CONFIG;
if *self.fsinfo_modified.borrow() {
if let BootRecord::Fat(boot_record_fat) = &*self.boot_record.borrow() {
if let Ebr::FAT32(ebr_fat32, fsinfo) = &boot_record_fat.ebr {
self.load_nth_sector(ebr_fat32.fat_info.into())?;
bincode::encode_into_slice(
fsinfo,
&mut self.sector_buffer.borrow_mut()[..FSINFO_SIZE],
BINCODE_CONFIG,
)
.map_err(utils::bincode::map_err_enc)?;
}
}
*self.fsinfo_modified.borrow_mut() = false;
}
Ok(())
}
fn get_rw_file_unchecked<P: AsRef<Path>>(&self, path: P) -> FSResult<RWFile<'_, S>, S::Error> {
let ro_file = self.get_ro_file(path)?;
Ok(ro_file.into())
}
}
impl<S> FileSystem<S>
where
S: Read + Seek,
{
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> FSResult<ReadDir<'_, S>, S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
let path = path.normalize();
self.go_to_dir(&path)?;
Ok(ReadDir::new(
self,
&self.dir_info.borrow().chain_start,
&self.dir_info.borrow().path,
))
}
pub fn get_ro_file<P: AsRef<Path>>(&self, path: P) -> FSResult<ROFile<'_, S>, S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
if let Some(file_name) = path.file_name() {
let parent_dir = self.read_dir(
path.parent()
.expect("we aren't in the root directory, this shouldn't panic"),
)?;
let mut entry = None;
for dir_entry in parent_dir {
let dir_entry = dir_entry?;
if dir_entry
.path()
.file_name()
.is_some_and(|entry_name| entry_name == file_name)
{
entry = Some(dir_entry.entry);
break;
}
}
match entry {
Some(entry) => {
let mut file = ROFile::from_props(
FileProps {
offset: 0,
current_cluster: entry.data_cluster,
entry,
},
self,
);
if file.cluster_chain_is_healthy()? {
Ok(file)
} else {
log::error!("The cluster chain of a file is malformed");
Err(FSError::InternalFSError(
InternalFSError::MalformedClusterChain,
))
}
}
None => {
log::error!("ROFile {path} not found");
Err(FSError::NotFound)
}
}
} else {
log::error!("Is a directory (not a file)");
Err(FSError::IsADirectory)
}
}
}
impl<S> FileSystem<S>
where
S: Read + Write + Seek,
{
#[inline]
pub fn create_file<P: AsRef<Path>>(&self, path: P) -> FSResult<RWFile<'_, S>, S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
let target = path.normalize();
let parent_dir = match target.parent() {
Some(parent) => parent,
None => return Err(FSError::IsADirectory),
};
let file_name = target
.file_name()
.expect("the path is normalized and it isn't the root directory either");
self.go_to_dir(parent_dir)?;
for entry in self.process_current_dir() {
let entry = entry?;
if entry.name == file_name {
return Err(FSError::AlreadyExists);
}
}
let file_cluster = self.allocate_clusters(num::NonZero::new(1).expect("1 != 0"), None)?;
let sfn = utils::string::gen_sfn(file_name, self, parent_dir)?;
let now = self.options.clock.now();
let raw_properties = MinProperties {
name: file_name.into(),
sfn,
attributes: RawAttributes::empty() | RawAttributes::ARCHIVE,
created: Some(now),
modified: now,
accessed: Some(now.date()),
file_size: 0,
data_cluster: file_cluster,
};
let entries = [raw_properties.clone()];
let chain = self.insert_to_entry_chain(Box::new(entries))?;
Ok(RWFile::from_props(
FileProps {
current_cluster: raw_properties.data_cluster,
entry: Properties::from_raw(
RawProperties::from_chain(raw_properties, chain),
path.into(),
self.options.codepage,
),
offset: 0,
},
self,
))
}
#[inline]
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> FSResult<(), S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
let target = path.normalize();
let parent_dir = match target.parent() {
Some(parent) => parent,
None => return Err(FSError::AlreadyExists),
};
let file_name = target
.file_name()
.expect("the path is normalized and it isn't the root directory either");
for entry in self.process_current_dir() {
let entry = entry?;
if entry.name == file_name {
return Err(FSError::AlreadyExists);
}
}
let now = self.options.clock.now();
let dir_cluster = self.create_entry_chain(self.dir_info.borrow().chain_start, now)?;
let sfn = utils::string::gen_sfn(file_name, self, parent_dir)?;
let raw_properties = MinProperties {
name: file_name.into(),
sfn,
attributes: RawAttributes::empty() | RawAttributes::DIRECTORY,
created: Some(now),
modified: now,
accessed: Some(now.date()),
file_size: 0,
data_cluster: dir_cluster,
};
let entries = [raw_properties];
self.go_to_dir(parent_dir)?;
self.insert_to_entry_chain(Box::new(entries))?;
Ok(())
}
pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to: Q) -> FSResult<(), S::Error> {
let from = from.as_ref();
let to = to.as_ref();
if !from.is_valid() || !to.is_valid() {
return Err(FSError::MalformedPath);
}
let from = from.normalize();
let to = to.normalize();
let parent_from = match from.parent() {
Some(parent) => parent,
None => return Err(FSError::PermissionDenied),
};
let parent_to = match to.parent() {
Some(parent) => parent,
None => return Err(FSError::PermissionDenied),
};
let entry_from = {
let mut entry_from = None;
for entry in self.read_dir(parent_from)? {
let entry = entry?;
if *entry.path() == from {
entry_from = Some(entry);
break;
}
}
match entry_from {
Some(entry) => entry,
None => return Err(FSError::NotFound),
}
};
for entry in self.read_dir(parent_to)? {
let entry = entry?;
if *entry.path() == to {
return Err(FSError::AlreadyExists);
}
}
self.go_to_dir(parent_to)?;
let now = self.options.clock.now();
if entry_from.is_dir() {
let parent_entry = MinProperties {
name: Box::from(typed_path::constants::windows::PARENT_DIR_STR),
sfn: PARENT_DIR_SFN,
attributes: RawAttributes::empty() | RawAttributes::DIRECTORY,
created: Some(now),
modified: now,
accessed: Some(now.date()),
file_size: 0,
data_cluster: match self.dir_info.borrow().chain_start {
EntryLocationUnit::DataCluster(cluster) => cluster,
EntryLocationUnit::RootDirSector(_) => 0,
},
};
let entry_location = EntryLocation {
unit: EntryLocationUnit::DataCluster(entry_from.data_cluster),
index: 1,
};
use utils::bincode::BINCODE_CONFIG;
self._go_to_cached_dir()?;
let mut bytes: [u8; DIRENTRY_SIZE] = [0; DIRENTRY_SIZE];
bincode::encode_into_slice(FATDirEntry::from(parent_entry), &mut bytes, BINCODE_CONFIG)
.map_err(utils::bincode::map_err_enc)?;
entry_location.set_bytes(self, bytes)?;
}
let old_chain = entry_from.chain;
let old_props: MinProperties = entry_from.into();
let to_filename = to.file_name().expect("this path is normalized");
let sfn = utils::string::gen_sfn(to_filename, self, parent_to)?;
let props = MinProperties {
name: Box::from(to_filename),
sfn,
attributes: old_props.attributes,
created: Some(now),
modified: now,
accessed: Some(now.date()),
file_size: old_props.file_size,
data_cluster: old_props.data_cluster,
};
self.insert_to_entry_chain(Box::from([props]))?;
self.remove_entry_chain(&old_chain)?;
Ok(())
}
#[inline]
pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> FSResult<(), S::Error> {
self.get_rw_file(path)?.remove()?;
Ok(())
}
#[inline]
pub fn remove_file_unchecked<P: AsRef<Path>>(&self, path: P) -> FSResult<(), S::Error> {
self.get_rw_file_unchecked(path)?.remove()?;
Ok(())
}
pub fn remove_empty_dir<P: AsRef<Path>>(&self, path: P) -> FSResult<(), S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
if path
.components()
.next_back()
.expect("this iterator will always yield at least the root directory")
== WindowsComponent::root()
{
return Err(FSError::InvalidInput);
}
if self.read_dir(path)?.next().is_some() {
return Err(FSError::DirectoryNotEmpty);
}
let parent_path = path
.parent()
.expect("we aren't in the root directory, this shouldn't panic");
let parent_dir_entries = self.read_dir(parent_path)?;
let entry = {
let mut entry = None;
for ent in parent_dir_entries {
let ent = ent?;
if ent.path() == path {
entry = Some(ent);
break;
}
}
entry.ok_or(FSError::NotFound)?
};
self.remove_entry_chain(&entry.chain)?;
self.free_cluster_chain(entry.data_cluster)?;
Ok(())
}
pub fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> FSResult<(), S::Error> {
if self.check_for_readonly_files(&path)? {
log::error!(concat!(
"A read-only file has been found ",
"in a directory pending deletion."
));
return Err(FSError::ReadOnlyFile);
}
self.remove_dir_all_unchecked(&path)?;
Ok(())
}
pub fn remove_dir_all_unchecked<P: AsRef<Path>>(&self, path: P) -> FSResult<(), S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
let mut read_dir = self.read_dir(path)?;
loop {
let entry = match read_dir.next() {
Some(entry) => entry?,
None => break,
};
if entry.is_dir() {
self.remove_dir_all_unchecked(&entry.path)?;
} else if entry.is_file() {
self.remove_file_unchecked(&entry.path)?;
} else {
unreachable!()
}
}
self.remove_empty_dir(path)?;
Ok(())
}
pub fn check_for_readonly_files<P: AsRef<Path>>(&self, path: P) -> FSResult<bool, S::Error> {
let path = path.as_ref();
if !path.is_valid() {
return Err(FSError::MalformedPath);
}
let mut read_dir = self.read_dir(path)?;
loop {
let entry = match read_dir.next() {
Some(entry) => entry?,
None => break,
};
let read_only_found = if entry.is_dir() {
self.check_for_readonly_files(&entry.path)?
} else if entry.is_file() {
entry.attributes.read_only
} else {
unreachable!()
};
if read_only_found {
return Ok(true);
}
}
Ok(false)
}
pub fn get_rw_file<P: AsRef<Path>>(&self, path: P) -> FSResult<RWFile<'_, S>, S::Error> {
let rw_file = self.get_rw_file_unchecked(path)?;
if rw_file.attributes.read_only {
return Err(FSError::ReadOnlyFile);
}
Ok(rw_file)
}
pub fn unmount(&self) -> FSResult<(), S::Error> {
self.sync_fsinfo()?;
let should_sync_buffer = self.sync_f.borrow().is_some();
if should_sync_buffer {
self.sync_sector_buffer()?;
}
self.storage.borrow_mut().flush()?;
Ok(())
}
}
impl<S> ops::Drop for FileSystem<S>
where
S: Read + Seek,
{
fn drop(&mut self) {
if let Some(unmount) = *self.unmount_f.borrow() {
let _ = unmount(self);
}
}
}