Support reading volume label from root directory

This commit is contained in:
Rafał Harabień 2018-06-24 18:53:50 +02:00
parent 5dea8b5f8a
commit 6f10042784
4 changed files with 76 additions and 25 deletions

View File

@ -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

View File

@ -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,
} }
} }
} }

View File

@ -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 = {

View File

@ -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);
} }