forked from M-Labs/rust-fatfs
Support no_std without alloc
This commit is contained in:
parent
11b2a3b956
commit
35d03daae9
@ -18,15 +18,19 @@ exclude = [
|
||||
travis-ci = { repository = "rafalh/rust-fatfs" }
|
||||
|
||||
[features]
|
||||
# Use Rust std library
|
||||
std = []
|
||||
default = ["chrono", "std"]
|
||||
# Use dynamic allocation - required for LFN support
|
||||
alloc = ["core_io/collections"]
|
||||
# Default features
|
||||
default = ["chrono", "std", "alloc"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1"
|
||||
bitflags = "1.0"
|
||||
log = "0.4"
|
||||
chrono = { version = "0.4", optional = true }
|
||||
core_io = { version = "0.1", features = ["collections"], optional = true }
|
||||
core_io = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
|
@ -47,7 +47,11 @@ Put this in your `Cargo.toml`:
|
||||
[dependencies]
|
||||
fatfs = { version = "0.2", features = ["core_io"], default-features = false }
|
||||
|
||||
Note: alloc crate is required at the moment so when std is disabled, custom allocator has to be provided.
|
||||
Note: LFN support requires `alloc` feature and makes use of `alloc` crate.
|
||||
You may have to provide a memory allocator implementation.
|
||||
|
||||
For building in `no_std` mode nightly Rust version compatible with current `core_io` crate is required.
|
||||
See date string in `core_io` dependency version.
|
||||
|
||||
License
|
||||
-------
|
||||
|
@ -1,2 +1,4 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
cargo build --no-default-features --features core_io
|
||||
cargo build --no-default-features --features core_io,alloc
|
||||
|
58
src/dir.rs
58
src/dir.rs
@ -1,5 +1,5 @@
|
||||
use core::slice;
|
||||
use core::iter;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::{slice, iter};
|
||||
|
||||
use io::prelude::*;
|
||||
use io;
|
||||
@ -7,10 +7,12 @@ use io::{ErrorKind, SeekFrom};
|
||||
|
||||
use fs::{FileSystemRef, DiskSlice};
|
||||
use file::File;
|
||||
use dir_entry::{DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes,
|
||||
DIR_ENTRY_SIZE, LFN_PART_LEN, LFN_ENTRY_LAST_FLAG};
|
||||
use dir_entry::{DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes, ShortName, DIR_ENTRY_SIZE};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
use dir_entry::{LFN_PART_LEN, LFN_ENTRY_LAST_FLAG};
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
||||
use alloc::Vec;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -254,13 +256,11 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_entry(&mut self, name: &str, attrs: FileAttributes, first_cluster: Option<u32>) -> io::Result<DirEntry<'a, 'b>> {
|
||||
trace!("create_entry {}", name);
|
||||
// check if name doesn't contain unsupported characters
|
||||
validate_long_name(name)?;
|
||||
// generate short name and long entries
|
||||
let short_name = generate_short_name(name);
|
||||
#[cfg(feature = "alloc")]
|
||||
fn create_lfn_entries(&mut self, name: &str, short_name: &[u8]) -> io::Result<(DirRawStream<'a, 'b>, u64)> {
|
||||
// get short name checksum
|
||||
let lfn_chsum = lfn_checksum(&short_name);
|
||||
// convert long name to UTF-16
|
||||
let lfn_utf16 = name.encode_utf16().collect::<Vec<u16>>();
|
||||
let lfn_iter = LfnEntriesGenerator::new(&lfn_utf16, lfn_chsum);
|
||||
// find space for new entries
|
||||
@ -271,6 +271,23 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
for lfn_entry in lfn_iter {
|
||||
lfn_entry.serialize(&mut stream)?;
|
||||
}
|
||||
Ok((stream, start_pos))
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
fn create_lfn_entries(&mut self, _name: &str, _short_name: &[u8]) -> io::Result<(DirRawStream<'a, 'b>, u64)> {
|
||||
let mut stream = self.find_free_entries(1)?;
|
||||
let start_pos = stream.seek(io::SeekFrom::Current(0))?;
|
||||
Ok((stream, start_pos))
|
||||
}
|
||||
|
||||
fn create_entry(&mut self, name: &str, attrs: FileAttributes, first_cluster: Option<u32>) -> io::Result<DirEntry<'a, 'b>> {
|
||||
trace!("create_entry {}", name);
|
||||
// check if name doesn't contain unsupported characters
|
||||
validate_long_name(name)?;
|
||||
// generate short name
|
||||
let short_name = generate_short_name(name);
|
||||
// generate long entries
|
||||
let (mut stream, start_pos) = self.create_lfn_entries(&name, &short_name)?;
|
||||
// create and write short name entry
|
||||
let mut raw_entry = DirFileEntryData::new(short_name, attrs);
|
||||
raw_entry.set_first_cluster(first_cluster, self.fs.fat_type());
|
||||
@ -281,8 +298,11 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
let end_pos = stream.seek(io::SeekFrom::Current(0))?;
|
||||
let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||
// return new logical entry descriptor
|
||||
let short_name = ShortName::new(raw_entry.name());
|
||||
return Ok(DirEntry {
|
||||
data: raw_entry,
|
||||
short_name,
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn: Vec::new(),
|
||||
fs: self.fs,
|
||||
entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is absent only for empty file
|
||||
@ -301,6 +321,7 @@ pub struct DirIter<'a, 'b: 'a> {
|
||||
|
||||
impl <'a, 'b> DirIter<'a, 'b> {
|
||||
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'a, 'b>>> {
|
||||
#[cfg(feature = "alloc")]
|
||||
let mut lfn_buf = LongNameBuilder::new();
|
||||
let mut offset = self.stream.seek(SeekFrom::Current(0))?;
|
||||
let mut begin_offset = offset;
|
||||
@ -315,6 +336,7 @@ impl <'a, 'b> DirIter<'a, 'b> {
|
||||
}
|
||||
// Check if this is deleted or volume ID entry
|
||||
if data.is_free() || data.is_volume() {
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.clear();
|
||||
begin_offset = offset;
|
||||
continue;
|
||||
@ -322,9 +344,14 @@ impl <'a, 'b> DirIter<'a, 'b> {
|
||||
// Get entry position on volume
|
||||
let abs_pos = self.stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||
// Check if LFN checksum is valid
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.validate_chksum(data.name());
|
||||
// Return directory entry
|
||||
let short_name = ShortName::new(data.name());
|
||||
return Ok(Some(DirEntry {
|
||||
data,
|
||||
short_name,
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn: lfn_buf.to_vec(),
|
||||
fs: self.fs,
|
||||
entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is empty only for empty file
|
||||
@ -334,11 +361,13 @@ impl <'a, 'b> DirIter<'a, 'b> {
|
||||
DirEntryData::Lfn(data) => {
|
||||
// Check if this is deleted entry
|
||||
if data.is_free() {
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.clear();
|
||||
begin_offset = offset;
|
||||
continue;
|
||||
}
|
||||
// Append to LFN buffer
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.process(&data);
|
||||
}
|
||||
}
|
||||
@ -420,6 +449,7 @@ fn validate_long_name(name: &str) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
fn lfn_checksum(short_name: &[u8]) -> u8 {
|
||||
let mut chksum = 0u8;
|
||||
for i in 0..11 {
|
||||
@ -428,12 +458,14 @@ fn lfn_checksum(short_name: &[u8]) -> u8 {
|
||||
chksum
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
struct LongNameBuilder {
|
||||
buf: Vec<u16>,
|
||||
chksum: u8,
|
||||
index: u8,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl LongNameBuilder {
|
||||
fn new() -> LongNameBuilder {
|
||||
LongNameBuilder {
|
||||
@ -510,6 +542,7 @@ impl LongNameBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
struct LfnEntriesGenerator<'a> {
|
||||
name_parts_iter: iter::Rev<slice::Chunks<'a, u16>>,
|
||||
checksum: u8,
|
||||
@ -518,6 +551,7 @@ struct LfnEntriesGenerator<'a> {
|
||||
ended: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'a> LfnEntriesGenerator<'a> {
|
||||
fn new(name_utf16: &'a [u16], checksum: u8) -> Self {
|
||||
let num_entries = (name_utf16.len() + LFN_PART_LEN - 1) / LFN_PART_LEN;
|
||||
@ -532,6 +566,7 @@ impl<'a> LfnEntriesGenerator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'a> Iterator for LfnEntriesGenerator<'a> {
|
||||
type Item = DirLfnEntryData;
|
||||
|
||||
@ -577,4 +612,5 @@ impl<'a> Iterator for LfnEntriesGenerator<'a> {
|
||||
}
|
||||
|
||||
// name_parts_iter is ExactSizeIterator so size_hint returns one limit
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<'a> ExactSizeIterator for LfnEntriesGenerator<'a> {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use core::fmt;
|
||||
use core::{fmt, str};
|
||||
use io::prelude::*;
|
||||
use io;
|
||||
use io::Cursor;
|
||||
@ -10,7 +10,7 @@ use chrono::{TimeZone, Local, Datelike, Timelike};
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
||||
use alloc::{Vec, String, string::ToString};
|
||||
|
||||
use fs::{FileSystemRef, FatType};
|
||||
@ -37,6 +37,45 @@ pub(crate) const DIR_ENTRY_SIZE: u64 = 32;
|
||||
pub(crate) const DIR_ENTRY_FREE_FLAG: u8 = 0xE5;
|
||||
pub(crate) const LFN_ENTRY_LAST_FLAG: u8 = 0x40;
|
||||
|
||||
/// Decoded file short name
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct ShortName {
|
||||
name: [u8; 12],
|
||||
len: u8,
|
||||
}
|
||||
|
||||
impl ShortName {
|
||||
pub(crate) fn new(raw_name: &[u8; 11]) -> Self {
|
||||
// get name components length by looking for space character
|
||||
const SPACE: u8 = ' ' as u8;
|
||||
let name_len = raw_name[0..8].iter().position(|x| *x == SPACE).unwrap_or(8);
|
||||
let ext_len = raw_name[8..11].iter().position(|x| *x == SPACE).unwrap_or(3);
|
||||
let mut name = [SPACE; 12];
|
||||
name[..name_len].copy_from_slice(&raw_name[..name_len]);
|
||||
let total_len = if ext_len > 0 {
|
||||
name[name_len] = '.' as u8;
|
||||
name[name_len+1..name_len+1+ext_len].copy_from_slice(&raw_name[8..8+ext_len]);
|
||||
// Return total name length
|
||||
name_len+1+ext_len
|
||||
} else {
|
||||
// No extension - return length of name part
|
||||
name_len
|
||||
};
|
||||
// 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.
|
||||
use strip_non_ascii;
|
||||
strip_non_ascii(&mut name);
|
||||
ShortName {
|
||||
name,
|
||||
len: total_len as u8,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_str(&self) -> &str {
|
||||
str::from_utf8(&self.name[..self.len as usize]).unwrap() // SAFE: all characters outside of ASCII table has been removed
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct DirFileEntryData {
|
||||
@ -251,10 +290,12 @@ impl DirLfnEntryData {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) fn order(&self) -> u8 {
|
||||
self.order
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) fn checksum(&self) -> u8 {
|
||||
self.checksum
|
||||
}
|
||||
@ -539,6 +580,8 @@ impl DirEntryEditor {
|
||||
#[derive(Clone)]
|
||||
pub struct DirEntry<'a, 'b: 'a> {
|
||||
pub(crate) data: DirFileEntryData,
|
||||
pub(crate) short_name: ShortName,
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) lfn: Vec<u16>,
|
||||
pub(crate) entry_pos: u64,
|
||||
pub(crate) offset_range: (u64, u64),
|
||||
@ -547,19 +590,17 @@ pub struct DirEntry<'a, 'b: 'a> {
|
||||
|
||||
impl <'a, 'b> DirEntry<'a, 'b> {
|
||||
/// Returns short file name
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn short_file_name(&self) -> String {
|
||||
let name_str = String::from_utf8_lossy(&self.data.name[0..8]);
|
||||
let ext_str = String::from_utf8_lossy(&self.data.name[8..11]);
|
||||
let name_trimmed = name_str.trim_right();
|
||||
let ext_trimmed = ext_str.trim_right();
|
||||
if ext_trimmed.is_empty() {
|
||||
name_trimmed.to_string()
|
||||
} else {
|
||||
format!("{}.{}", name_trimmed, ext_trimmed)
|
||||
}
|
||||
self.short_name.to_str().to_string()
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
pub fn short_file_name(&self) -> &str {
|
||||
self.short_name.to_str()
|
||||
}
|
||||
|
||||
/// Returns long file name or if it doesn't exist fallbacks to short file name.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn file_name(&self) -> String {
|
||||
if self.lfn.len() > 0 {
|
||||
String::from_utf16_lossy(&self.lfn)
|
||||
@ -567,6 +608,10 @@ impl <'a, 'b> DirEntry<'a, 'b> {
|
||||
self.short_file_name()
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
pub fn file_name(&self) -> &str {
|
||||
self.short_file_name()
|
||||
}
|
||||
|
||||
/// Returns file attributes
|
||||
pub fn attributes(&self) -> FileAttributes {
|
||||
|
19
src/fs.rs
19
src/fs.rs
@ -11,8 +11,10 @@ use dir::{DirRawStream, Dir};
|
||||
use dir_entry::DIR_ENTRY_SIZE;
|
||||
use table::{ClusterIterator, alloc_cluster, read_fat_flags};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
||||
use alloc::{String, string::ToString};
|
||||
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
|
||||
use core::str;
|
||||
|
||||
// FAT implementation based on:
|
||||
// http://wiki.osdev.org/FAT
|
||||
@ -46,6 +48,14 @@ impl<T> ReadSeek for T where T: Read + Seek {}
|
||||
pub trait ReadWriteSeek: 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)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct BiosParameterBlock {
|
||||
@ -130,6 +140,8 @@ impl BiosParameterBlock {
|
||||
rdr.read_exact(&mut bpb.volume_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 {
|
||||
// fields after ext_sig are not used - clean them
|
||||
bpb.volume_id = 0;
|
||||
@ -284,9 +296,14 @@ impl <'a> FileSystem<'a> {
|
||||
///
|
||||
/// Note: File with VOLUME_ID attribute in root directory is ignored by this library.
|
||||
/// Only label from BPB is used.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn volume_label(&self) -> String {
|
||||
String::from_utf8_lossy(&self.bpb.volume_label).trim_right().to_string()
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
pub fn volume_label(&self) -> &str {
|
||||
str::from_utf8(&self.bpb.volume_label).unwrap_or("").trim_right()
|
||||
}
|
||||
|
||||
/// Returns root directory object allowing futher penetration of filesystem structure.
|
||||
pub fn root_dir<'b>(&'b self) -> Dir<'b, 'a> {
|
||||
|
@ -4,6 +4,9 @@
|
||||
#![cfg_attr(not(feature="std"), no_std)]
|
||||
#![cfg_attr(not(feature="std"), feature(alloc))]
|
||||
|
||||
// Disable warnings to not clutter code with cfg too much
|
||||
#![cfg_attr(not(feature="alloc"), allow(dead_code, unused_imports))]
|
||||
|
||||
extern crate byteorder;
|
||||
|
||||
#[macro_use]
|
||||
@ -18,7 +21,7 @@ extern crate chrono;
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern crate core_io;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
@ -27,6 +30,8 @@ mod dir;
|
||||
mod dir_entry;
|
||||
mod file;
|
||||
mod table;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod utils;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
@ -48,4 +53,6 @@ pub use fs::*;
|
||||
pub use dir::*;
|
||||
pub use dir_entry::*;
|
||||
pub use file::*;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use utils::*;
|
||||
|
Loading…
Reference in New Issue
Block a user