forked from M-Labs/rust-fatfs
No longer return &str for short names in no_std mode
Instead methods returning &[u8] were added. It is API preparation for proper OEM codepage decoding. Previous behavior would require to store duplicated short names (one lossy and one real).
This commit is contained in:
parent
d99ba96f72
commit
7a53215a57
@ -118,7 +118,7 @@ impl <'a, T: ReadWriteSeek + 'a> Dir<'a, T> {
|
|||||||
for r in self.iter() {
|
for r in self.iter() {
|
||||||
let e = r?;
|
let e = r?;
|
||||||
// compare name ignoring case
|
// compare name ignoring case
|
||||||
if e.file_name().eq_ignore_ascii_case(name) || e.short_file_name().eq_ignore_ascii_case(name) {
|
if e.eq_name(name) {
|
||||||
// check if file or directory is expected
|
// check if file or directory is expected
|
||||||
if is_dir.is_some() && Some(e.is_dir()) != is_dir {
|
if is_dir.is_some() && Some(e.is_dir()) != is_dir {
|
||||||
let error_msg = if e.is_dir() { "Is a directory" } else { "Not a directory" };
|
let error_msg = if e.is_dir() { "Is a directory" } else { "Not a directory" };
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use core::{fmt, str};
|
use core::{fmt, str};
|
||||||
|
use core::iter::FromIterator;
|
||||||
|
use core::char;
|
||||||
use io::prelude::*;
|
use io::prelude::*;
|
||||||
use io;
|
use io;
|
||||||
use io::Cursor;
|
use io::Cursor;
|
||||||
@ -13,7 +15,7 @@ use chrono;
|
|||||||
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
||||||
use alloc::{Vec, String, string::ToString};
|
use alloc::{Vec, String, string::ToString};
|
||||||
|
|
||||||
use fs::{FileSystem, FatType, ReadWriteSeek};
|
use fs::{FileSystem, FatType, ReadWriteSeek, decode_oem_char_lossy};
|
||||||
use file::File;
|
use file::File;
|
||||||
use dir::{Dir, DirRawStream};
|
use dir::{Dir, DirRawStream};
|
||||||
|
|
||||||
@ -63,16 +65,29 @@ impl ShortName {
|
|||||||
};
|
};
|
||||||
// Short names in FAT filesystem are encoded in OEM code-page. Rust operates on UTF-8 strings
|
// Short names in FAT filesystem are encoded in OEM code-page. Rust operates on UTF-8 strings
|
||||||
// and there is no built-in conversion so strip non-ascii characters in the name.
|
// and there is no built-in conversion so strip non-ascii characters in the name.
|
||||||
use strip_non_ascii;
|
|
||||||
strip_non_ascii(&mut name);
|
|
||||||
ShortName {
|
ShortName {
|
||||||
name,
|
name,
|
||||||
len: total_len as u8,
|
len: total_len as u8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_str(&self) -> &str {
|
fn bytes(&self) -> &[u8] {
|
||||||
str::from_utf8(&self.name[..self.len as usize]).unwrap() // SAFE: all characters outside of ASCII table has been removed
|
&self.name[..self.len as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
// Strip non-ascii characters from short name
|
||||||
|
let char_iter = self.bytes().iter().cloned().map(decode_oem_char_lossy);
|
||||||
|
// Build string from character iterator
|
||||||
|
String::from_iter(char_iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq_ignore_ascii_case(&self, name: &str) -> bool {
|
||||||
|
// Strip non-ascii characters from short name
|
||||||
|
let char_iter = self.bytes().iter().cloned().map(decode_oem_char_lossy).map(|c| c.to_ascii_uppercase());
|
||||||
|
// Build string from character iterator
|
||||||
|
char_iter.eq(name.chars().map(|c| c.to_ascii_uppercase()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +139,7 @@ impl DirFileEntryData {
|
|||||||
*c = (*c as char).to_ascii_lowercase() as u8;
|
*c = (*c as char).to_ascii_lowercase() as u8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ShortName::new(&name_copy).to_str().to_string()
|
ShortName::new(&name_copy).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn first_cluster(&self, fat_type: FatType) -> Option<u32> {
|
pub(crate) fn first_cluster(&self, fat_type: FatType) -> Option<u32> {
|
||||||
@ -620,13 +635,18 @@ pub struct DirEntry<'a, T: ReadWriteSeek + 'a> {
|
|||||||
|
|
||||||
impl <'a, T: ReadWriteSeek> DirEntry<'a, T> {
|
impl <'a, T: ReadWriteSeek> DirEntry<'a, T> {
|
||||||
/// Returns short file name.
|
/// Returns short file name.
|
||||||
|
///
|
||||||
|
/// Non-ASCII characters are replaced by the replacement character (U+FFFD).
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn short_file_name(&self) -> String {
|
pub fn short_file_name(&self) -> String {
|
||||||
self.short_name.to_str().to_string()
|
self.short_name.to_string()
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "alloc"))]
|
|
||||||
pub fn short_file_name(&self) -> &str {
|
/// Returns short file name as byte array slice.
|
||||||
self.short_name.to_str()
|
///
|
||||||
|
/// Characters are encoded in the OEM codepage.
|
||||||
|
pub fn short_file_name_bytes(&self) -> &[u8] {
|
||||||
|
self.short_name.bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns long file name or if it doesn't exist fallbacks to short file name.
|
/// Returns long file name or if it doesn't exist fallbacks to short file name.
|
||||||
@ -638,10 +658,6 @@ impl <'a, T: ReadWriteSeek> DirEntry<'a, T> {
|
|||||||
self.data.lowercase_name()
|
self.data.lowercase_name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "alloc"))]
|
|
||||||
pub fn file_name(&self) -> &str {
|
|
||||||
self.short_file_name()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns file attributes.
|
/// Returns file attributes.
|
||||||
pub fn attributes(&self) -> FileAttributes {
|
pub fn attributes(&self) -> FileAttributes {
|
||||||
@ -715,6 +731,15 @@ impl <'a, T: ReadWriteSeek> DirEntry<'a, T> {
|
|||||||
pub(crate) fn raw_short_name(&self) -> &[u8; 11] {
|
pub(crate) fn raw_short_name(&self) -> &[u8; 11] {
|
||||||
&self.data.name
|
&self.data.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub(crate) fn eq_name(&self, name: &str) -> bool {
|
||||||
|
self.file_name().eq_ignore_ascii_case(name) || self.short_name.eq_ignore_ascii_case(name)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
pub(crate) fn eq_name(&self, name: &str) -> bool {
|
||||||
|
self.short_name.eq_ignore_ascii_case(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, T: ReadWriteSeek> fmt::Debug for DirEntry<'a, T> {
|
impl <'a, T: ReadWriteSeek> fmt::Debug for DirEntry<'a, T> {
|
||||||
@ -731,14 +756,14 @@ mod tests {
|
|||||||
fn short_name_with_ext() {
|
fn short_name_with_ext() {
|
||||||
let mut raw_short_name = [0u8;11];
|
let mut raw_short_name = [0u8;11];
|
||||||
raw_short_name.copy_from_slice("FOO BAR".as_bytes());
|
raw_short_name.copy_from_slice("FOO BAR".as_bytes());
|
||||||
assert_eq!(ShortName::new(&raw_short_name).to_str(), "FOO.BAR");
|
assert_eq!(ShortName::new(&raw_short_name).to_string(), "FOO.BAR");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn short_name_without_ext() {
|
fn short_name_without_ext() {
|
||||||
let mut raw_short_name = [0u8;11];
|
let mut raw_short_name = [0u8;11];
|
||||||
raw_short_name.copy_from_slice("FOO ".as_bytes());
|
raw_short_name.copy_from_slice("FOO ".as_bytes());
|
||||||
assert_eq!(ShortName::new(&raw_short_name).to_str(), "FOO");
|
assert_eq!(ShortName::new(&raw_short_name).to_string(), "FOO");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
37
src/fs.rs
37
src/fs.rs
@ -1,5 +1,6 @@
|
|||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
|
use core::char;
|
||||||
use io::prelude::*;
|
use io::prelude::*;
|
||||||
use io::{Error, ErrorKind, SeekFrom};
|
use io::{Error, ErrorKind, SeekFrom};
|
||||||
use io;
|
use io;
|
||||||
@ -15,6 +16,7 @@ use table::{ClusterIterator, alloc_cluster, read_fat_flags, count_free_clusters}
|
|||||||
use alloc::{String, string::ToString};
|
use alloc::{String, string::ToString};
|
||||||
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
|
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
|
||||||
use core::str;
|
use core::str;
|
||||||
|
use core::iter::FromIterator;
|
||||||
|
|
||||||
// FAT implementation based on:
|
// FAT implementation based on:
|
||||||
// http://wiki.osdev.org/FAT
|
// http://wiki.osdev.org/FAT
|
||||||
@ -69,14 +71,6 @@ impl<T> ReadSeek for T where T: Read + Seek {}
|
|||||||
pub trait ReadWriteSeek: Read + Write + Seek {}
|
pub trait ReadWriteSeek: Read + Write + Seek {}
|
||||||
impl<T> ReadWriteSeek for T where T: Read + Write + Seek {}
|
impl<T> ReadWriteSeek for T where T: Read + Write + Seek {}
|
||||||
|
|
||||||
pub(crate) fn strip_non_ascii(slice: &mut [u8]) {
|
|
||||||
for c in slice {
|
|
||||||
if *c < 0x20 || *c >= 0x80 {
|
|
||||||
*c = '_' as u8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
struct BiosParameterBlock {
|
struct BiosParameterBlock {
|
||||||
@ -161,8 +155,6 @@ impl BiosParameterBlock {
|
|||||||
rdr.read_exact(&mut bpb.volume_label)?;
|
rdr.read_exact(&mut bpb.volume_label)?;
|
||||||
rdr.read_exact(&mut bpb.fs_type_label)?;
|
rdr.read_exact(&mut bpb.fs_type_label)?;
|
||||||
}
|
}
|
||||||
// Strip non-ascii characters from volume label
|
|
||||||
strip_non_ascii(&mut bpb.volume_label);
|
|
||||||
if bpb.ext_sig != 0x29 {
|
if bpb.ext_sig != 0x29 {
|
||||||
// fields after ext_sig are not used - clean them
|
// fields after ext_sig are not used - clean them
|
||||||
bpb.volume_id = 0;
|
bpb.volume_id = 0;
|
||||||
@ -444,17 +436,28 @@ impl <T: ReadWriteSeek> FileSystem<T> {
|
|||||||
self.bpb.volume_id
|
self.bpb.volume_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a volume label from BPB in the Boot Sector.
|
/// Returns a volume label from BPB in the Boot Sector as `String`.
|
||||||
///
|
///
|
||||||
|
/// Non-ASCII characters are replaced by the replacement character (U+FFFD).
|
||||||
/// 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.
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn volume_label(&self) -> String {
|
pub fn volume_label(&self) -> String {
|
||||||
String::from_utf8_lossy(&self.bpb.volume_label).trim_right().to_string()
|
// Strip non-ascii characters from volume label
|
||||||
|
let char_iter = self.volume_label_bytes().iter().cloned().map(decode_oem_char_lossy);
|
||||||
|
// Build string from character iterator
|
||||||
|
String::from_iter(char_iter)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "alloc"))]
|
|
||||||
pub fn volume_label(&self) -> &str {
|
/// Returns a volume label from BPB in the Boot Sector as byte array slice.
|
||||||
str::from_utf8(&self.bpb.volume_label).unwrap_or("").trim_right()
|
///
|
||||||
|
/// Label is encoded in the OEM codepage.
|
||||||
|
/// Note: File with `VOLUME_ID` attribute in root directory is ignored by this library.
|
||||||
|
/// Only label from BPB is used.
|
||||||
|
pub fn volume_label_bytes(&self) -> &[u8] {
|
||||||
|
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);
|
||||||
|
&full_label_slice[..len]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -683,3 +686,7 @@ impl <'a, T: ReadWriteSeek> Seek for DiskSlice<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decode_oem_char_lossy(oem_char: u8) -> char {
|
||||||
|
if oem_char < 0x80 { oem_char as char } else { char::REPLACEMENT_CHARACTER }
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user