Skip to content

Commit d9eb2c8

Browse files
committed
initial commit to implement FAT table write/read and setting the volume dirty status
Description: The dirty flag will be set when opening the volume to indicate that the FAT table might be out of date. In case of an unexpected close without unmount in the next mount it is visible that the FAT table is outdated and can be reread by the operating system (https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system). Without this, the file must always closed before removing the sd card because otherwise the FAT table is outdated and so the files are not visible when mounting the sd card on a computer. For embedded systems it is quite likely that unexpect end can happen and always closing the file after a single write operation is a performance issue. For readonly this dirty flag is not required and therefore it will not set and reset when opening/closing a volume. https://unix.stackexchange.com/questions/230181/why-does-linux-mark-fat-as-dirty-simply-due-to-mounting-it Linux kernel is doing it during mount, so there is no need to ckeck it every time during a new write
1 parent 44b63de commit d9eb2c8

File tree

5 files changed

+146
-5
lines changed

5 files changed

+146
-5
lines changed

src/fat/fat_table.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/// FAT table definition
2+
///
3+
///
4+
use crate::fat::FatType;
5+
use byteorder::{ByteOrder, LittleEndian};
6+
7+
/// Represents a single FAT table. It contains all information about which cluster is occupied
8+
/// from a file
9+
pub struct FatTable<'a> {
10+
fat_type: FatType,
11+
data: &'a mut [u8],
12+
}
13+
14+
impl<'a> FatTable<'a> {
15+
/// Attempt to parse a FAT table from a multiple sectors.
16+
pub fn create_from_bytes(data: &'a mut [u8], fat_type: FatType) -> Result<Self, &'static str> {
17+
Ok(Self { data, fat_type })
18+
}
19+
20+
// FAT16 only
21+
//define_field!(fat_id16, u16, 0);
22+
23+
// FAT32 only
24+
//define_field!(fat_id32, u32, 0);
25+
26+
const FAT16_DIRTY_BIT: u16 = 15;
27+
const FAT32_DIRTY_BIT: u32 = 27;
28+
29+
pub(crate) fn dirty(&self) -> bool {
30+
match self.fat_type {
31+
FatType::Fat16 => {
32+
(LittleEndian::read_u16(&self.data[2..2 + 2]) & (1 << Self::FAT16_DIRTY_BIT)) == 0
33+
}
34+
FatType::Fat32 => {
35+
(LittleEndian::read_u32(&self.data[4..4 + 4]) & (1 << Self::FAT32_DIRTY_BIT)) == 0
36+
}
37+
}
38+
}
39+
40+
pub(crate) fn set_dirty(&mut self, dirty: bool) {
41+
match self.fat_type {
42+
FatType::Fat16 => {
43+
let mut v = LittleEndian::read_u16(&self.data[2..2 + 2]);
44+
if dirty {
45+
v &= !(1 << Self::FAT16_DIRTY_BIT);
46+
} else {
47+
v |= 1 << Self::FAT16_DIRTY_BIT
48+
}
49+
LittleEndian::write_u16(&mut self.data[2..2 + 2], v);
50+
}
51+
FatType::Fat32 => {
52+
let mut v = LittleEndian::read_u32(&self.data[4..4 + 4]);
53+
if dirty {
54+
v &= !(1 << Self::FAT32_DIRTY_BIT);
55+
} else {
56+
v |= 1 << Self::FAT32_DIRTY_BIT
57+
}
58+
LittleEndian::write_u32(&mut self.data[4..4 + 4], v);
59+
}
60+
}
61+
}
62+
}

src/fat/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ impl BlockCache {
4545
}
4646

4747
mod bpb;
48+
mod fat_table;
4849
mod info;
4950
mod ondiskdirentry;
5051
mod volume;
5152

5253
pub use bpb::Bpb;
54+
pub use fat_table::FatTable;
5355
pub use info::{Fat16Info, Fat32Info, FatSpecificInfo, InfoSector};
5456
pub use ondiskdirentry::OnDiskDirEntry;
5557
pub use volume::{parse_volume, FatVolume, VolumeName};

src/fat/volume.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ pub struct FatVolume {
5555
/// The block the FAT starts in. Relative to start of partition (so add
5656
/// `self.lba_offset` before passing to volume manager)
5757
pub(crate) fat_start: BlockCount,
58+
/// Size of the FAT table in blocks
59+
pub(crate) fat_size: BlockCount,
60+
/// Number of FAT tables (Normaly there are 2 which are always synchronized (backup))
61+
pub(crate) fat_nums: u8,
5862
/// Expected number of free clusters
5963
pub(crate) free_clusters_count: Option<u32>,
6064
/// Number of the next expected free cluster
@@ -1098,6 +1102,8 @@ where
10981102
blocks_per_cluster: bpb.blocks_per_cluster(),
10991103
first_data_block: (first_data_block),
11001104
fat_start: BlockCount(u32::from(bpb.reserved_block_count())),
1105+
fat_size: BlockCount(bpb.fat_size()),
1106+
fat_nums: bpb.num_fats(),
11011107
free_clusters_count: None,
11021108
next_free_cluster: None,
11031109
cluster_count: bpb.total_clusters(),
@@ -1135,6 +1141,8 @@ where
11351141
blocks_per_cluster: bpb.blocks_per_cluster(),
11361142
first_data_block: BlockCount(first_data_block),
11371143
fat_start: BlockCount(u32::from(bpb.reserved_block_count())),
1144+
fat_size: BlockCount(bpb.fat_size()),
1145+
fat_nums: bpb.num_fats(),
11381146
free_clusters_count: info_sector.free_clusters_count(),
11391147
next_free_cluster: info_sector.next_free_cluster(),
11401148
cluster_count: bpb.total_clusters(),

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ where
175175
VolumeStillInUse,
176176
/// You can't open a volume twice
177177
VolumeAlreadyOpen,
178+
/// Volume is opened in read only mode
179+
VolumeReadOnly,
180+
/// Fat table is longer than supported
181+
FatTableTooLarge,
178182
/// We can't do that yet
179183
Unsupported,
180184
/// Tried to read beyond end of file
@@ -344,6 +348,8 @@ pub(crate) struct VolumeInfo {
344348
idx: VolumeIdx,
345349
/// What kind of volume this is
346350
volume_type: VolumeType,
351+
/// Flag to indicate if the volume was opened as read only. If read only, files cannot be opened in write mode!
352+
read_only: bool,
347353
}
348354

349355
/// This enum holds the data for the various different types of filesystems we

src/volume_mgr.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use crate::filesystem::{
1212
SearchIdGenerator, TimeSource, ToShortFileName, MAX_FILE_SIZE,
1313
};
1414
use crate::{
15-
debug, Block, BlockCount, BlockDevice, BlockIdx, Error, RawVolume, ShortFileName, Volume,
16-
VolumeIdx, VolumeInfo, VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA,
15+
debug, Block, BlockCount, BlockDevice, BlockIdx, Error, FatVolume, RawVolume, ShortFileName,
16+
Volume, VolumeIdx, VolumeInfo, VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA,
1717
PARTITION_ID_FAT32_CHS_LBA, PARTITION_ID_FAT32_LBA,
1818
};
1919
use heapless::Vec;
@@ -102,8 +102,26 @@ where
102102
pub fn open_volume(
103103
&mut self,
104104
volume_idx: VolumeIdx,
105+
read_only: bool,
105106
) -> Result<Volume<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>, Error<D::Error>> {
106-
let v = self.open_raw_volume(volume_idx)?;
107+
return self._open_volume(volume_idx, false);
108+
}
109+
110+
/// Get a volume (or partition) based on entries in the Master Boot Record.
111+
///
112+
/// We do not support GUID Partition Table disks. Nor do we support any
113+
/// concept of drive letters - that is for a higher layer to handle.
114+
fn _open_volume(
115+
&mut self,
116+
volume_idx: VolumeIdx,
117+
read_only: bool,
118+
) -> Result<Volume<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>, Error<D::Error>> {
119+
let v = self.open_raw_volume(volume_idx, read_only)?;
120+
if !read_only {
121+
let idx = self.get_volume_by_id(v)?;
122+
let VolumeType::Fat(volume_type) = &self.open_volumes[idx].volume_type;
123+
self.set_volume_status_dirty(volume_type, true)?;
124+
}
107125
Ok(v.to_volume(self))
108126
}
109127

@@ -114,7 +132,11 @@ where
114132
///
115133
/// This function gives you a `RawVolume` and you must close the volume by
116134
/// calling `VolumeManager::close_volume`.
117-
pub fn open_raw_volume(&mut self, volume_idx: VolumeIdx) -> Result<RawVolume, Error<D::Error>> {
135+
pub fn open_raw_volume(
136+
&mut self,
137+
volume_idx: VolumeIdx,
138+
read_only: bool,
139+
) -> Result<RawVolume, Error<D::Error>> {
118140
const PARTITION1_START: usize = 446;
119141
const PARTITION2_START: usize = PARTITION1_START + PARTITION_INFO_LENGTH;
120142
const PARTITION3_START: usize = PARTITION2_START + PARTITION_INFO_LENGTH;
@@ -192,6 +214,7 @@ where
192214
volume_id: id,
193215
idx: volume_idx,
194216
volume_type: volume,
217+
read_only: read_only,
195218
};
196219
// We already checked for space
197220
self.open_volumes.push(info).unwrap();
@@ -319,13 +342,49 @@ where
319342
return Err(Error::VolumeStillInUse);
320343
}
321344
}
322-
323345
let volume_idx = self.get_volume_by_id(volume)?;
346+
if !self.open_volumes[volume_idx].read_only {
347+
let VolumeType::Fat(volume_type) = &self.open_volumes[volume_idx].volume_type;
348+
self.set_volume_status_dirty(volume_type, false)?;
349+
}
350+
324351
self.open_volumes.swap_remove(volume_idx);
325352

326353
Ok(())
327354
}
328355

356+
/// Sets the volume status dirty to dirty if true, to not dirty if false
357+
fn set_volume_status_dirty(
358+
&self,
359+
volume: &FatVolume,
360+
dirty: bool,
361+
) -> Result<(), Error<D::Error>> {
362+
if volume.fat_size > BlockCount(512) {
363+
return Err(Error::FatTableTooLarge);
364+
}
365+
let mut blocks = [Block::new()];
366+
self.block_device.read(
367+
&mut blocks,
368+
volume.lba_start + volume.fat_start,
369+
"reading fat table",
370+
)?;
371+
let block = &mut blocks[0];
372+
let mut fat_table =
373+
fat::FatTable::create_from_bytes(&mut block.contents, volume.get_fat_type())
374+
.map_err(Error::FormatError)?;
375+
fat_table.set_dirty(dirty);
376+
let fat_table_start = volume.lba_start + volume.fat_start;
377+
if volume.fat_nums == 1 || volume.fat_nums == 2 {
378+
self.block_device.write(&blocks, fat_table_start)?;
379+
// Synchronize also backup fat table
380+
if volume.fat_nums == 2 {
381+
self.block_device
382+
.write(&blocks, fat_table_start + volume.fat_size)?
383+
}
384+
}
385+
Ok(())
386+
}
387+
329388
/// Look in a directory for a named file.
330389
pub fn find_directory_entry<N>(
331390
&mut self,
@@ -479,6 +538,10 @@ where
479538
let volume_info = &self.open_volumes[volume_idx];
480539
let sfn = name.to_short_filename().map_err(Error::FilenameError)?;
481540

541+
if volume_info.read_only && mode != Mode::ReadOnly {
542+
return Err(Error::VolumeReadOnly);
543+
}
544+
482545
let dir_entry = match &volume_info.volume_type {
483546
VolumeType::Fat(fat) => {
484547
fat.find_directory_entry(&self.block_device, directory_info, &sfn)

0 commit comments

Comments
 (0)