Support no_std without alloc

This commit is contained in:
Rafał Harabień 2018-05-10 15:00:59 +02:00
parent 11b2a3b956
commit 35d03daae9
7 changed files with 142 additions and 27 deletions

View File

@ -18,15 +18,19 @@ exclude = [
travis-ci = { repository = "rafalh/rust-fatfs" } travis-ci = { repository = "rafalh/rust-fatfs" }
[features] [features]
# Use Rust std library
std = [] std = []
default = ["chrono", "std"] # Use dynamic allocation - required for LFN support
alloc = ["core_io/collections"]
# Default features
default = ["chrono", "std", "alloc"]
[dependencies] [dependencies]
byteorder = "1" byteorder = "1"
bitflags = "1.0" bitflags = "1.0"
log = "0.4" log = "0.4"
chrono = { version = "0.4", optional = true } chrono = { version = "0.4", optional = true }
core_io = { version = "0.1", features = ["collections"], optional = true } core_io = { version = "0.1", optional = true }
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"

View File

@ -47,7 +47,11 @@ Put this in your `Cargo.toml`:
[dependencies] [dependencies]
fatfs = { version = "0.2", features = ["core_io"], default-features = false } 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 License
------- -------

View File

@ -1,2 +1,4 @@
#!/bin/sh #!/bin/sh
set -e
cargo build --no-default-features --features core_io cargo build --no-default-features --features core_io
cargo build --no-default-features --features core_io,alloc

View File

@ -1,5 +1,5 @@
use core::slice; #[cfg(feature = "alloc")]
use core::iter; use core::{slice, iter};
use io::prelude::*; use io::prelude::*;
use io; use io;
@ -7,10 +7,12 @@ use io::{ErrorKind, SeekFrom};
use fs::{FileSystemRef, DiskSlice}; use fs::{FileSystemRef, DiskSlice};
use file::File; use file::File;
use dir_entry::{DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes, use dir_entry::{DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes, ShortName, DIR_ENTRY_SIZE};
DIR_ENTRY_SIZE, LFN_PART_LEN, LFN_ENTRY_LAST_FLAG};
#[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; use alloc::Vec;
#[derive(Clone)] #[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>> { #[cfg(feature = "alloc")]
trace!("create_entry {}", name); fn create_lfn_entries(&mut self, name: &str, short_name: &[u8]) -> io::Result<(DirRawStream<'a, 'b>, u64)> {
// check if name doesn't contain unsupported characters // get short name checksum
validate_long_name(name)?;
// generate short name and long entries
let short_name = generate_short_name(name);
let lfn_chsum = lfn_checksum(&short_name); let lfn_chsum = lfn_checksum(&short_name);
// convert long name to UTF-16
let lfn_utf16 = name.encode_utf16().collect::<Vec<u16>>(); let lfn_utf16 = name.encode_utf16().collect::<Vec<u16>>();
let lfn_iter = LfnEntriesGenerator::new(&lfn_utf16, lfn_chsum); let lfn_iter = LfnEntriesGenerator::new(&lfn_utf16, lfn_chsum);
// find space for new entries // find space for new entries
@ -271,6 +271,23 @@ impl <'a, 'b> Dir<'a, 'b> {
for lfn_entry in lfn_iter { for lfn_entry in lfn_iter {
lfn_entry.serialize(&mut stream)?; 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 // create and write short name entry
let mut raw_entry = DirFileEntryData::new(short_name, attrs); let mut raw_entry = DirFileEntryData::new(short_name, attrs);
raw_entry.set_first_cluster(first_cluster, self.fs.fat_type()); 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 end_pos = stream.seek(io::SeekFrom::Current(0))?;
let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE); let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
// return new logical entry descriptor // return new logical entry descriptor
let short_name = ShortName::new(raw_entry.name());
return Ok(DirEntry { return Ok(DirEntry {
data: raw_entry, data: raw_entry,
short_name,
#[cfg(feature = "alloc")]
lfn: Vec::new(), lfn: Vec::new(),
fs: self.fs, fs: self.fs,
entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is absent only for empty file 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> { impl <'a, 'b> DirIter<'a, 'b> {
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'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 lfn_buf = LongNameBuilder::new();
let mut offset = self.stream.seek(SeekFrom::Current(0))?; let mut offset = self.stream.seek(SeekFrom::Current(0))?;
let mut begin_offset = offset; let mut begin_offset = offset;
@ -315,6 +336,7 @@ impl <'a, 'b> DirIter<'a, 'b> {
} }
// Check if this is deleted or volume ID entry // Check if this is deleted or volume ID entry
if data.is_free() || data.is_volume() { if data.is_free() || data.is_volume() {
#[cfg(feature = "alloc")]
lfn_buf.clear(); lfn_buf.clear();
begin_offset = offset; begin_offset = offset;
continue; continue;
@ -322,9 +344,14 @@ impl <'a, 'b> DirIter<'a, 'b> {
// 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
#[cfg(feature = "alloc")]
lfn_buf.validate_chksum(data.name()); lfn_buf.validate_chksum(data.name());
// Return directory entry
let short_name = ShortName::new(data.name());
return Ok(Some(DirEntry { return Ok(Some(DirEntry {
data, data,
short_name,
#[cfg(feature = "alloc")]
lfn: lfn_buf.to_vec(), lfn: lfn_buf.to_vec(),
fs: self.fs, fs: self.fs,
entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is empty only for empty file 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) => { DirEntryData::Lfn(data) => {
// Check if this is deleted entry // Check if this is deleted entry
if data.is_free() { if data.is_free() {
#[cfg(feature = "alloc")]
lfn_buf.clear(); lfn_buf.clear();
begin_offset = offset; begin_offset = offset;
continue; continue;
} }
// Append to LFN buffer // Append to LFN buffer
#[cfg(feature = "alloc")]
lfn_buf.process(&data); lfn_buf.process(&data);
} }
} }
@ -420,6 +449,7 @@ fn validate_long_name(name: &str) -> io::Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "alloc")]
fn lfn_checksum(short_name: &[u8]) -> u8 { fn lfn_checksum(short_name: &[u8]) -> u8 {
let mut chksum = 0u8; let mut chksum = 0u8;
for i in 0..11 { for i in 0..11 {
@ -428,12 +458,14 @@ fn lfn_checksum(short_name: &[u8]) -> u8 {
chksum chksum
} }
#[cfg(feature = "alloc")]
struct LongNameBuilder { struct LongNameBuilder {
buf: Vec<u16>, buf: Vec<u16>,
chksum: u8, chksum: u8,
index: u8, index: u8,
} }
#[cfg(feature = "alloc")]
impl LongNameBuilder { impl LongNameBuilder {
fn new() -> LongNameBuilder { fn new() -> LongNameBuilder {
LongNameBuilder { LongNameBuilder {
@ -510,6 +542,7 @@ impl LongNameBuilder {
} }
} }
#[cfg(feature = "alloc")]
struct LfnEntriesGenerator<'a> { struct LfnEntriesGenerator<'a> {
name_parts_iter: iter::Rev<slice::Chunks<'a, u16>>, name_parts_iter: iter::Rev<slice::Chunks<'a, u16>>,
checksum: u8, checksum: u8,
@ -518,6 +551,7 @@ struct LfnEntriesGenerator<'a> {
ended: bool, ended: bool,
} }
#[cfg(feature = "alloc")]
impl<'a> LfnEntriesGenerator<'a> { impl<'a> LfnEntriesGenerator<'a> {
fn new(name_utf16: &'a [u16], checksum: u8) -> Self { fn new(name_utf16: &'a [u16], checksum: u8) -> Self {
let num_entries = (name_utf16.len() + LFN_PART_LEN - 1) / LFN_PART_LEN; 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> { impl<'a> Iterator for LfnEntriesGenerator<'a> {
type Item = DirLfnEntryData; type Item = DirLfnEntryData;
@ -577,4 +612,5 @@ impl<'a> Iterator for LfnEntriesGenerator<'a> {
} }
// name_parts_iter is ExactSizeIterator so size_hint returns one limit // name_parts_iter is ExactSizeIterator so size_hint returns one limit
#[cfg(feature = "alloc")]
impl<'a> ExactSizeIterator for LfnEntriesGenerator<'a> {} impl<'a> ExactSizeIterator for LfnEntriesGenerator<'a> {}

View File

@ -1,4 +1,4 @@
use core::fmt; use core::{fmt, str};
use io::prelude::*; use io::prelude::*;
use io; use io;
use io::Cursor; use io::Cursor;
@ -10,7 +10,7 @@ use chrono::{TimeZone, Local, Datelike, Timelike};
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
use chrono; use chrono;
#[cfg(not(feature = "std"))] #[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{Vec, String, string::ToString}; use alloc::{Vec, String, string::ToString};
use fs::{FileSystemRef, FatType}; 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 DIR_ENTRY_FREE_FLAG: u8 = 0xE5;
pub(crate) const LFN_ENTRY_LAST_FLAG: u8 = 0x40; 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)] #[allow(dead_code)]
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub(crate) struct DirFileEntryData { pub(crate) struct DirFileEntryData {
@ -251,10 +290,12 @@ impl DirLfnEntryData {
Ok(()) Ok(())
} }
#[cfg(feature = "alloc")]
pub(crate) fn order(&self) -> u8 { pub(crate) fn order(&self) -> u8 {
self.order self.order
} }
#[cfg(feature = "alloc")]
pub(crate) fn checksum(&self) -> u8 { pub(crate) fn checksum(&self) -> u8 {
self.checksum self.checksum
} }
@ -539,6 +580,8 @@ impl DirEntryEditor {
#[derive(Clone)] #[derive(Clone)]
pub struct DirEntry<'a, 'b: 'a> { pub struct DirEntry<'a, 'b: 'a> {
pub(crate) data: DirFileEntryData, pub(crate) data: DirFileEntryData,
pub(crate) short_name: ShortName,
#[cfg(feature = "alloc")]
pub(crate) lfn: Vec<u16>, pub(crate) lfn: Vec<u16>,
pub(crate) entry_pos: u64, pub(crate) entry_pos: u64,
pub(crate) offset_range: (u64, u64), pub(crate) offset_range: (u64, u64),
@ -547,19 +590,17 @@ pub struct DirEntry<'a, 'b: 'a> {
impl <'a, 'b> DirEntry<'a, 'b> { impl <'a, 'b> DirEntry<'a, 'b> {
/// Returns short file name /// Returns short file name
#[cfg(feature = "alloc")]
pub fn short_file_name(&self) -> String { pub fn short_file_name(&self) -> String {
let name_str = String::from_utf8_lossy(&self.data.name[0..8]); self.short_name.to_str().to_string()
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)
} }
#[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. /// Returns long file name or if it doesn't exist fallbacks to short file name.
#[cfg(feature = "alloc")]
pub fn file_name(&self) -> String { pub fn file_name(&self) -> String {
if self.lfn.len() > 0 { if self.lfn.len() > 0 {
String::from_utf16_lossy(&self.lfn) String::from_utf16_lossy(&self.lfn)
@ -567,6 +608,10 @@ impl <'a, 'b> DirEntry<'a, 'b> {
self.short_file_name() self.short_file_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 {

View File

@ -11,8 +11,10 @@ use dir::{DirRawStream, Dir};
use dir_entry::DIR_ENTRY_SIZE; use dir_entry::DIR_ENTRY_SIZE;
use table::{ClusterIterator, alloc_cluster, read_fat_flags}; use table::{ClusterIterator, alloc_cluster, read_fat_flags};
#[cfg(not(feature = "std"))] #[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{String, string::ToString}; use alloc::{String, string::ToString};
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
use core::str;
// FAT implementation based on: // FAT implementation based on:
// http://wiki.osdev.org/FAT // http://wiki.osdev.org/FAT
@ -46,6 +48,14 @@ 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 {
@ -130,6 +140,8 @@ 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;
@ -284,9 +296,14 @@ impl <'a> FileSystem<'a> {
/// ///
/// 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")]
pub fn volume_label(&self) -> String { pub fn volume_label(&self) -> String {
String::from_utf8_lossy(&self.bpb.volume_label).trim_right().to_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. /// Returns root directory object allowing futher penetration of filesystem structure.
pub fn root_dir<'b>(&'b self) -> Dir<'b, 'a> { pub fn root_dir<'b>(&'b self) -> Dir<'b, 'a> {

View File

@ -4,6 +4,9 @@
#![cfg_attr(not(feature="std"), no_std)] #![cfg_attr(not(feature="std"), no_std)]
#![cfg_attr(not(feature="std"), feature(alloc))] #![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; extern crate byteorder;
#[macro_use] #[macro_use]
@ -18,7 +21,7 @@ extern crate chrono;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
extern crate core_io; extern crate core_io;
#[cfg(not(feature = "std"))] #[cfg(all(not(feature = "std"), feature = "alloc"))]
#[macro_use] #[macro_use]
extern crate alloc; extern crate alloc;
@ -27,6 +30,8 @@ mod dir;
mod dir_entry; mod dir_entry;
mod file; mod file;
mod table; mod table;
#[cfg(feature = "alloc")]
mod utils; mod utils;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
@ -48,4 +53,6 @@ pub use fs::*;
pub use dir::*; pub use dir::*;
pub use dir_entry::*; pub use dir_entry::*;
pub use file::*; pub use file::*;
#[cfg(feature = "alloc")]
pub use utils::*; pub use utils::*;