forked from M-Labs/rust-fatfs
Support reading volume label from root directory
This commit is contained in:
parent
5dea8b5f8a
commit
6f10042784
1
TODO.md
1
TODO.md
@ -1,6 +1,5 @@
|
|||||||
TODO
|
TODO
|
||||||
====
|
====
|
||||||
* support for a volume label file in the root directory
|
|
||||||
* format volume API
|
* format volume API
|
||||||
* add method for getting `DirEntry` from a path (possible names: metadata, lookup)
|
* add method for getting `DirEntry` from a path (possible names: metadata, lookup)
|
||||||
* do not create LFN entries if the name fits in a SFN entry
|
* do not create LFN entries if the name fits in a SFN entry
|
||||||
|
64
src/dir.rs
64
src/dir.rs
@ -106,11 +106,7 @@ impl<'a, T: ReadWriteSeek + 'a> Dir<'a, T> {
|
|||||||
|
|
||||||
/// Creates directory entries iterator.
|
/// Creates directory entries iterator.
|
||||||
pub fn iter(&self) -> DirIter<'a, T> {
|
pub fn iter(&self) -> DirIter<'a, T> {
|
||||||
DirIter {
|
DirIter::new(self.stream.clone(), self.fs, true)
|
||||||
stream: self.stream.clone(),
|
|
||||||
fs: self.fs,
|
|
||||||
err: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_entry(
|
fn find_entry(
|
||||||
@ -138,6 +134,16 @@ impl<'a, T: ReadWriteSeek + 'a> Dir<'a, T> {
|
|||||||
Err(io::Error::new(ErrorKind::NotFound, "No such file or directory"))
|
Err(io::Error::new(ErrorKind::NotFound, "No such file or directory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_volume_entry(&self) -> io::Result<Option<DirEntry<'a, T>>> {
|
||||||
|
for r in DirIter::new(self.stream.clone(), self.fs, false) {
|
||||||
|
let e = r?;
|
||||||
|
if e.data.is_volume() {
|
||||||
|
return Ok(Some(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
fn check_for_existence(&self, name: &str, is_dir: Option<bool>) -> io::Result<DirEntryOrShortName<'a, T>> {
|
fn check_for_existence(&self, name: &str, is_dir: Option<bool>) -> io::Result<DirEntryOrShortName<'a, T>> {
|
||||||
let mut short_name_gen = ShortNameGenerator::new(name);
|
let mut short_name_gen = ShortNameGenerator::new(name);
|
||||||
loop {
|
loop {
|
||||||
@ -453,10 +459,28 @@ impl<'a, T: ReadWriteSeek> Clone for Dir<'a, T> {
|
|||||||
pub struct DirIter<'a, T: ReadWriteSeek + 'a> {
|
pub struct DirIter<'a, T: ReadWriteSeek + 'a> {
|
||||||
stream: DirRawStream<'a, T>,
|
stream: DirRawStream<'a, T>,
|
||||||
fs: &'a FileSystem<T>,
|
fs: &'a FileSystem<T>,
|
||||||
|
skip_volume: bool,
|
||||||
err: bool,
|
err: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
||||||
|
fn new(stream: DirRawStream<'a, T>, fs: &'a FileSystem<T>, skip_volume: bool) -> Self {
|
||||||
|
DirIter {
|
||||||
|
stream, fs, skip_volume,
|
||||||
|
err: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_ship_entry(&self, raw_entry: &DirEntryData) -> bool {
|
||||||
|
if raw_entry.is_deleted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match raw_entry {
|
||||||
|
DirEntryData::File(sfn_entry) => self.skip_volume && sfn_entry.is_volume(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'a, T>>> {
|
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'a, T>>> {
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
let mut lfn_buf = LongNameBuilder::new();
|
let mut lfn_buf = LongNameBuilder::new();
|
||||||
@ -465,19 +489,19 @@ impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
|||||||
loop {
|
loop {
|
||||||
let raw_entry = DirEntryData::deserialize(&mut self.stream)?;
|
let raw_entry = DirEntryData::deserialize(&mut self.stream)?;
|
||||||
offset += DIR_ENTRY_SIZE;
|
offset += DIR_ENTRY_SIZE;
|
||||||
|
// Check if this is end of dir
|
||||||
|
if raw_entry.is_end() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
// Check if this is deleted or volume ID entry
|
||||||
|
if self.should_ship_entry(&raw_entry) {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
lfn_buf.clear();
|
||||||
|
begin_offset = offset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
match raw_entry {
|
match raw_entry {
|
||||||
DirEntryData::File(data) => {
|
DirEntryData::File(data) => {
|
||||||
// Check if this is end of dif
|
|
||||||
if data.is_end() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
// Check if this is deleted or volume ID entry
|
|
||||||
if data.is_deleted() || data.is_volume() {
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
lfn_buf.clear();
|
|
||||||
begin_offset = offset;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Get entry position on volume
|
// Get entry position on volume
|
||||||
let abs_pos = self.stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
let abs_pos = self.stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||||
// Check if LFN checksum is valid
|
// Check if LFN checksum is valid
|
||||||
@ -496,13 +520,6 @@ impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
DirEntryData::Lfn(data) => {
|
DirEntryData::Lfn(data) => {
|
||||||
// Check if this is deleted entry
|
|
||||||
if data.is_deleted() {
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
lfn_buf.clear();
|
|
||||||
begin_offset = offset;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Append to LFN buffer
|
// Append to LFN buffer
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
lfn_buf.process(&data);
|
lfn_buf.process(&data);
|
||||||
@ -519,6 +536,7 @@ impl<'a, T: ReadWriteSeek> Clone for DirIter<'a, T> {
|
|||||||
stream: self.stream.clone(),
|
stream: self.stream.clone(),
|
||||||
fs: self.fs,
|
fs: self.fs,
|
||||||
err: self.err,
|
err: self.err,
|
||||||
|
skip_volume: self.skip_volume,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
src/fs.rs
35
src/fs.rs
@ -494,11 +494,44 @@ impl<T: ReadWriteSeek> FileSystem<T> {
|
|||||||
/// Note: File with `VOLUME_ID` attribute in root directory is ignored by this library.
|
/// Note: File with `VOLUME_ID` attribute in root directory is ignored by this library.
|
||||||
/// Only label from BPB is used.
|
/// Only label from BPB is used.
|
||||||
pub fn volume_label_as_bytes(&self) -> &[u8] {
|
pub fn volume_label_as_bytes(&self) -> &[u8] {
|
||||||
|
const PADDING: u8 = 0x20;
|
||||||
let full_label_slice = &self.bpb.volume_label;
|
let full_label_slice = &self.bpb.volume_label;
|
||||||
let len = full_label_slice.iter().rposition(|b| *b != 0x20).map(|p| p + 1).unwrap_or(0);
|
let len = full_label_slice.iter().rposition(|b| *b != PADDING).map(|p| p + 1).unwrap_or(0);
|
||||||
&full_label_slice[..len]
|
&full_label_slice[..len]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a volume label from root directory as `String`.
|
||||||
|
///
|
||||||
|
/// It finds file with `VOLUME_ID` attribute and returns its short name.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub fn read_volume_label_from_root_dir(&self) -> io::Result<Option<String>> {
|
||||||
|
// Note: DirEntry::file_short_name() cannot be used because it interprets name as 8.3
|
||||||
|
// (adds dot before an extension)
|
||||||
|
let volume_label_opt = self.read_volume_label_from_root_dir_as_bytes()?;
|
||||||
|
if let Some(volume_label) = volume_label_opt {
|
||||||
|
const PADDING: u8 = 0x20;
|
||||||
|
// Strip label padding
|
||||||
|
let len = volume_label.iter().rposition(|b| *b != PADDING).map(|p| p + 1).unwrap_or(0);
|
||||||
|
let label_slice = &volume_label[..len];
|
||||||
|
// Decode volume label from OEM codepage
|
||||||
|
let volume_label_iter = label_slice.iter().cloned();
|
||||||
|
let char_iter = volume_label_iter.map(|c| self.options.oem_cp_converter.decode(c));
|
||||||
|
// Build string from character iterator
|
||||||
|
Ok(Some(String::from_iter(char_iter)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a volume label from root directory as byte array.
|
||||||
|
///
|
||||||
|
/// Label is encoded in the OEM codepage.
|
||||||
|
/// It finds file with `VOLUME_ID` attribute and returns its short name.
|
||||||
|
pub fn read_volume_label_from_root_dir_as_bytes(&self) -> io::Result<Option<[u8; 11]>> {
|
||||||
|
let entry_opt = self.root_dir().find_volume_entry()?;
|
||||||
|
Ok(entry_opt.map(|e| *e.raw_short_name()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a root directory object allowing for futher penetration of a filesystem structure.
|
/// Returns a root directory object allowing for futher penetration of a filesystem structure.
|
||||||
pub fn root_dir<'b>(&'b self) -> Dir<'b, T> {
|
pub fn root_dir<'b>(&'b self) -> Dir<'b, T> {
|
||||||
let root_rdr = {
|
let root_rdr = {
|
||||||
|
@ -187,6 +187,7 @@ fn test_get_file_by_path_fat32() {
|
|||||||
fn test_volume_metadata(fs: FileSystem, fat_type: FatType) {
|
fn test_volume_metadata(fs: FileSystem, fat_type: FatType) {
|
||||||
assert_eq!(fs.volume_id(), 0x12345678);
|
assert_eq!(fs.volume_id(), 0x12345678);
|
||||||
assert_eq!(fs.volume_label(), "Test!");
|
assert_eq!(fs.volume_label(), "Test!");
|
||||||
|
assert_eq!(&fs.read_volume_label_from_root_dir().unwrap().unwrap(), "Test!");
|
||||||
assert_eq!(fs.fat_type(), fat_type);
|
assert_eq!(fs.fat_type(), fat_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user