Create file functionality (#3)
* Support removing files and directories. * Add create_file functionality.
This commit is contained in:
parent
ba5329edad
commit
df98ca8779
147
src/dir.rs
147
src/dir.rs
@ -3,6 +3,7 @@ use std::fmt;
|
|||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Cursor, ErrorKind, SeekFrom};
|
use std::io::{Cursor, ErrorKind, SeekFrom};
|
||||||
|
use std::cmp;
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
@ -78,6 +79,7 @@ bitflags! {
|
|||||||
const LFN_PART_LEN: usize = 13;
|
const LFN_PART_LEN: usize = 13;
|
||||||
const DIR_ENTRY_SIZE: u64 = 32;
|
const DIR_ENTRY_SIZE: u64 = 32;
|
||||||
const DIR_ENTRY_REMOVED_FLAG: u8 = 0xE5;
|
const DIR_ENTRY_REMOVED_FLAG: u8 = 0xE5;
|
||||||
|
const LFN_ENTRY_LAST_FLAG: u8 = 0x40;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@ -193,6 +195,10 @@ impl DirLfnEntryData {
|
|||||||
fn is_removed(&self) -> bool {
|
fn is_removed(&self) -> bool {
|
||||||
self.order == DIR_ENTRY_REMOVED_FLAG
|
self.order == DIR_ENTRY_REMOVED_FLAG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_end(&self) -> bool {
|
||||||
|
self.order == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -244,6 +250,20 @@ impl DirEntryData {
|
|||||||
Ok(DirEntryData::File(data))
|
Ok(DirEntryData::File(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_removed(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&DirEntryData::File(ref file) => file.is_removed(),
|
||||||
|
&DirEntryData::Lfn(ref lfn) => lfn.is_removed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_end(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&DirEntryData::File(ref file) => file.is_end(),
|
||||||
|
&DirEntryData::Lfn(ref lfn) => lfn.is_end(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@ -473,6 +493,20 @@ impl <'a, 'b> Dir<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_file(&mut self, path: &str) -> io::Result<File<'a, 'b>> {
|
||||||
|
let (name, rest_opt) = Self::split_path(path);
|
||||||
|
let r = self.find_entry(name);
|
||||||
|
match rest_opt {
|
||||||
|
Some(rest) => r?.to_dir().create_file(rest),
|
||||||
|
None => {
|
||||||
|
match r {
|
||||||
|
Err(_) => Ok(self.create_file_entry(name)?.to_file()),
|
||||||
|
Ok(e) => Ok(e.to_file())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_empty(&mut self) -> io::Result<bool> {
|
fn is_empty(&mut self) -> io::Result<bool> {
|
||||||
for r in self.iter() {
|
for r in self.iter() {
|
||||||
let e = r?;
|
let e = r?;
|
||||||
@ -516,6 +550,111 @@ impl <'a, 'b> Dir<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_free_entries(&mut self, num_entries: usize) -> io::Result<DirRawStream<'a, 'b>> {
|
||||||
|
let mut stream = self.rdr.clone();
|
||||||
|
let mut first_free = 0;
|
||||||
|
let mut num_free = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let data = DirEntryData::deserialize(&mut stream)?;
|
||||||
|
if data.is_removed() {
|
||||||
|
if num_free == 0 {
|
||||||
|
first_free = i;
|
||||||
|
}
|
||||||
|
num_free += 1;
|
||||||
|
if num_free == num_entries {
|
||||||
|
stream.seek(io::SeekFrom::Start(first_free as u64 * DIR_ENTRY_SIZE))?;
|
||||||
|
return Ok(stream);
|
||||||
|
}
|
||||||
|
} else if data.is_end() {
|
||||||
|
if num_free == 0 {
|
||||||
|
first_free = i;
|
||||||
|
}
|
||||||
|
stream.seek(io::SeekFrom::Start(first_free as u64 * DIR_ENTRY_SIZE))?;
|
||||||
|
// FIXME: make sure there is END?
|
||||||
|
return Ok(stream);
|
||||||
|
} else {
|
||||||
|
num_free = 0;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_short_name(name: &str) -> [u8;11] {
|
||||||
|
// short name is always uppercase
|
||||||
|
let mut name_upper = name.to_uppercase();
|
||||||
|
// padded by ' '
|
||||||
|
let mut short_name = [0x20u8; 11];
|
||||||
|
// find extension after last dot
|
||||||
|
match name_upper.rfind('.') {
|
||||||
|
Some(index) => {
|
||||||
|
// copy first 3 characters of extension
|
||||||
|
let short_ext_len = cmp::min(name_upper.len() - index - 1, 3);
|
||||||
|
short_name[8..8+short_ext_len].copy_from_slice(name_upper[index..index+short_ext_len].as_bytes());
|
||||||
|
// remove extension with dot from name_upper
|
||||||
|
name_upper.truncate(index);
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
// copy first 8 characters of name
|
||||||
|
let short_name_len = cmp::min(name_upper.len(), 8);
|
||||||
|
short_name[..short_name_len].copy_from_slice(name_upper[..short_name_len].as_bytes());
|
||||||
|
// FIXME: make sure short name is unique...
|
||||||
|
short_name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_file_entry(&mut self, name: &str) -> io::Result<DirEntry<'a, 'b>> {
|
||||||
|
if name.len() > 255 {
|
||||||
|
return Err(io::Error::new(ErrorKind::InvalidInput, "filename too long"));
|
||||||
|
}
|
||||||
|
let num_lfn_entries = (name.len() + LFN_PART_LEN - 1) / LFN_PART_LEN;
|
||||||
|
let num_entries = num_lfn_entries + 1; // multiple lfn entries + one file entry
|
||||||
|
let mut stream = self.find_free_entries(num_entries)?;
|
||||||
|
let start_pos = stream.seek(io::SeekFrom::Current(0))?;
|
||||||
|
let short_name = Self::gen_short_name(name);
|
||||||
|
let lfn_chsum = lfn_checksum(&short_name);
|
||||||
|
let lfn_utf8 = name.encode_utf16().collect::<Vec<u16>>();
|
||||||
|
for i in 0..num_lfn_entries {
|
||||||
|
let lfn_index = num_lfn_entries - i;
|
||||||
|
let mut order = lfn_index as u8;
|
||||||
|
if i == 0 {
|
||||||
|
order |= LFN_ENTRY_LAST_FLAG;
|
||||||
|
}
|
||||||
|
debug_assert!(order > 0);
|
||||||
|
let lfn_pos = (lfn_index - 1) * LFN_PART_LEN;
|
||||||
|
let mut lfn_part = [0xFFFFu16; LFN_PART_LEN];
|
||||||
|
let lfn_part_len = cmp::min(name.len() - lfn_pos, LFN_PART_LEN);
|
||||||
|
lfn_part[..lfn_part_len].copy_from_slice(&lfn_utf8[lfn_pos..lfn_pos+lfn_part_len]);
|
||||||
|
if lfn_part_len < LFN_PART_LEN {
|
||||||
|
lfn_part[lfn_part_len] = 0;
|
||||||
|
}
|
||||||
|
let mut lfn_entry = DirLfnEntryData {
|
||||||
|
order,
|
||||||
|
attrs: FileAttributes::LFN,
|
||||||
|
checksum: lfn_chsum,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
lfn_entry.name_0.copy_from_slice(&lfn_part[0..5]);
|
||||||
|
lfn_entry.name_1.copy_from_slice(&lfn_part[5..5+6]);
|
||||||
|
lfn_entry.name_2.copy_from_slice(&lfn_part[11..11+2]);
|
||||||
|
lfn_entry.serialize(&mut stream)?;
|
||||||
|
}
|
||||||
|
let raw_entry = DirFileEntryData {
|
||||||
|
name: short_name,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
raw_entry.serialize(&mut stream)?;
|
||||||
|
let end_pos = stream.seek(io::SeekFrom::Current(0))?;
|
||||||
|
let abs_pos = stream.global_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||||
|
return Ok(DirEntry {
|
||||||
|
data: raw_entry,
|
||||||
|
lfn: Vec::new(),
|
||||||
|
fs: self.fs,
|
||||||
|
entry_pos: abs_pos.unwrap(), // safe
|
||||||
|
offset_range: (start_pos, end_pos),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -649,7 +788,7 @@ impl LongNameBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process(&mut self, data: &DirLfnEntryData) {
|
fn process(&mut self, data: &DirLfnEntryData) {
|
||||||
let is_last = (data.order & 0x40) != 0;
|
let is_last = (data.order & LFN_ENTRY_LAST_FLAG) != 0;
|
||||||
let index = data.order & 0x1F;
|
let index = data.order & 0x1F;
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
// Corrupted entry
|
// Corrupted entry
|
||||||
@ -673,9 +812,9 @@ impl LongNameBuilder {
|
|||||||
}
|
}
|
||||||
let pos = LFN_PART_LEN * (index - 1) as usize;
|
let pos = LFN_PART_LEN * (index - 1) as usize;
|
||||||
// copy name parts into LFN buffer
|
// copy name parts into LFN buffer
|
||||||
self.buf[pos+0..pos+5].clone_from_slice(&data.name_0);
|
self.buf[pos+0..pos+5].copy_from_slice(&data.name_0);
|
||||||
self.buf[pos+5..pos+11].clone_from_slice(&data.name_1);
|
self.buf[pos+5..pos+11].copy_from_slice(&data.name_1);
|
||||||
self.buf[pos+11..pos+13].clone_from_slice(&data.name_2);
|
self.buf[pos+11..pos+13].copy_from_slice(&data.name_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_chksum(&mut self, short_name: &[u8]) {
|
fn validate_chksum(&mut self, short_name: &[u8]) {
|
||||||
|
@ -122,3 +122,37 @@ fn test_remove_fat16() {
|
|||||||
fn test_remove_fat32() {
|
fn test_remove_fat32() {
|
||||||
call_with_fs(&test_remove, FAT32_IMG, 3)
|
call_with_fs(&test_remove, FAT32_IMG, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_create_file(fs: FileSystem) {
|
||||||
|
let mut root_dir = fs.root_dir();
|
||||||
|
let dir = root_dir.open_dir("very/long/path").unwrap();
|
||||||
|
let mut names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
||||||
|
assert_eq!(names, [".", "..", "test.txt"]);
|
||||||
|
{
|
||||||
|
let mut file = root_dir.create_file("very/long/path/new-file-with-long-name.txt").unwrap();
|
||||||
|
file.write_all(&TEST_STR.as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
||||||
|
assert_eq!(names, [".", "..", "test.txt", "new-file-with-long-name.txt"]);
|
||||||
|
{
|
||||||
|
let mut file = root_dir.open_file("very/long/path/new-file-with-long-name.txt").unwrap();
|
||||||
|
let mut content = String::new();
|
||||||
|
file.read_to_string(&mut content).unwrap();
|
||||||
|
assert_eq!(&content, &TEST_STR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_file_fat12() {
|
||||||
|
call_with_fs(&test_create_file, FAT12_IMG, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_file_fat16() {
|
||||||
|
call_with_fs(&test_create_file, FAT16_IMG, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_file_fat32() {
|
||||||
|
call_with_fs(&test_create_file, FAT32_IMG, 4)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user