forked from M-Labs/rust-fatfs
Support removing files and directories.
This commit is contained in:
parent
b2c3eb1823
commit
ba5329edad
281
src/dir.rs
281
src/dir.rs
@ -75,6 +75,10 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LFN_PART_LEN: usize = 13;
|
||||||
|
const DIR_ENTRY_SIZE: u64 = 32;
|
||||||
|
const DIR_ENTRY_REMOVED_FLAG: u8 = 0xE5;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub(crate) struct DirFileEntryData {
|
pub(crate) struct DirFileEntryData {
|
||||||
@ -144,6 +148,14 @@ impl DirFileEntryData {
|
|||||||
wrt.write_u32::<LittleEndian>(self.size)?;
|
wrt.write_u32::<LittleEndian>(self.size)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_removed(&self) -> bool {
|
||||||
|
self.name[0] == DIR_ENTRY_REMOVED_FLAG
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_end(&self) -> bool {
|
||||||
|
self.name[0] == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -159,12 +171,81 @@ struct DirLfnEntryData {
|
|||||||
name_2: [u16; 2],
|
name_2: [u16; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DirLfnEntryData {
|
||||||
|
fn serialize(&self, wrt: &mut Write) -> io::Result<()> {
|
||||||
|
wrt.write_u8(self.order)?;
|
||||||
|
for ch in self.name_0.iter() {
|
||||||
|
wrt.write_u16::<LittleEndian>(*ch)?;
|
||||||
|
}
|
||||||
|
wrt.write_u8(self.attrs.bits())?;
|
||||||
|
wrt.write_u8(self.entry_type)?;
|
||||||
|
wrt.write_u8(self.checksum)?;
|
||||||
|
for ch in self.name_1.iter() {
|
||||||
|
wrt.write_u16::<LittleEndian>(*ch)?;
|
||||||
|
}
|
||||||
|
wrt.write_u16::<LittleEndian>(self.reserved_0)?;
|
||||||
|
for ch in self.name_2.iter() {
|
||||||
|
wrt.write_u16::<LittleEndian>(*ch)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_removed(&self) -> bool {
|
||||||
|
self.order == DIR_ENTRY_REMOVED_FLAG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum DirEntryData {
|
enum DirEntryData {
|
||||||
File(DirFileEntryData),
|
File(DirFileEntryData),
|
||||||
Lfn(DirLfnEntryData),
|
Lfn(DirLfnEntryData),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DirEntryData {
|
||||||
|
fn serialize(&mut self, wrt: &mut Write) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
&mut DirEntryData::File(ref mut file) => file.serialize(wrt),
|
||||||
|
&mut DirEntryData::Lfn(ref mut lfn) => lfn.serialize(wrt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize(rdr: &mut Read) -> io::Result<DirEntryData> {
|
||||||
|
let mut name = [0; 11];
|
||||||
|
rdr.read_exact(&mut name)?;
|
||||||
|
let attrs = FileAttributes::from_bits_truncate(rdr.read_u8()?);
|
||||||
|
if attrs == FileAttributes::LFN {
|
||||||
|
let mut data = DirLfnEntryData {
|
||||||
|
attrs, ..Default::default()
|
||||||
|
};
|
||||||
|
let mut cur = Cursor::new(&name);
|
||||||
|
data.order = cur.read_u8()?;
|
||||||
|
cur.read_u16_into::<LittleEndian>(&mut data.name_0)?;
|
||||||
|
data.entry_type = rdr.read_u8()?;
|
||||||
|
data.checksum = rdr.read_u8()?;
|
||||||
|
rdr.read_u16_into::<LittleEndian>(&mut data.name_1)?;
|
||||||
|
data.reserved_0 = rdr.read_u16::<LittleEndian>()?;
|
||||||
|
rdr.read_u16_into::<LittleEndian>(&mut data.name_2)?;
|
||||||
|
Ok(DirEntryData::Lfn(data))
|
||||||
|
} else {
|
||||||
|
let data = DirFileEntryData {
|
||||||
|
name,
|
||||||
|
attrs,
|
||||||
|
reserved_0: rdr.read_u8()?,
|
||||||
|
create_time_0: rdr.read_u8()?,
|
||||||
|
create_time_1: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
create_date: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
access_date: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
first_cluster_hi: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
modify_time: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
modify_date: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
first_cluster_lo: rdr.read_u16::<LittleEndian>()?,
|
||||||
|
size: rdr.read_u32::<LittleEndian>()?,
|
||||||
|
};
|
||||||
|
Ok(DirEntryData::File(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Date {
|
pub struct Date {
|
||||||
pub year: u16,
|
pub year: u16,
|
||||||
@ -250,6 +331,7 @@ pub struct DirEntry<'a, 'b: 'a> {
|
|||||||
data: DirFileEntryData,
|
data: DirFileEntryData,
|
||||||
lfn: Vec<u16>,
|
lfn: Vec<u16>,
|
||||||
entry_pos: u64,
|
entry_pos: u64,
|
||||||
|
offset_range: (u64, u64),
|
||||||
fs: FileSystemRef<'a, 'b>,
|
fs: FileSystemRef<'a, 'b>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,6 +472,50 @@ impl <'a, 'b> Dir<'a, 'b> {
|
|||||||
None => Ok(e.to_file())
|
None => Ok(e.to_file())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_empty(&mut self) -> io::Result<bool> {
|
||||||
|
for r in self.iter() {
|
||||||
|
let e = r?;
|
||||||
|
let name = e.file_name();
|
||||||
|
if name != "." && name != ".." {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, path: &str) -> io::Result<()> {
|
||||||
|
let (name, rest_opt) = Self::split_path(path);
|
||||||
|
let e = self.find_entry(name)?;
|
||||||
|
match rest_opt {
|
||||||
|
Some(rest) => e.to_dir().remove(rest),
|
||||||
|
None => {
|
||||||
|
trace!("removing {}", path);
|
||||||
|
if e.is_dir() && !e.to_dir().is_empty()? {
|
||||||
|
return Err(io::Error::new(ErrorKind::NotFound, "removing non-empty directory is denied"));
|
||||||
|
}
|
||||||
|
match e.first_cluster() {
|
||||||
|
Some(n) => self.fs.cluster_iter(n).free()?,
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
let mut stream = self.rdr.clone();
|
||||||
|
stream.seek(SeekFrom::Start(e.offset_range.0 as u64))?;
|
||||||
|
let num = (e.offset_range.1 - e.offset_range.0) as usize / DIR_ENTRY_SIZE as usize;
|
||||||
|
for _ in 0..num {
|
||||||
|
let mut data = DirEntryData::deserialize(&mut stream)?;
|
||||||
|
trace!("removing dir entry {:?}", data);
|
||||||
|
match data {
|
||||||
|
DirEntryData::File(ref mut data) =>
|
||||||
|
data.name[0] = DIR_ENTRY_REMOVED_FLAG,
|
||||||
|
DirEntryData::Lfn(ref mut data) => data.order = DIR_ENTRY_REMOVED_FLAG,
|
||||||
|
};
|
||||||
|
stream.seek(SeekFrom::Current(-(DIR_ENTRY_SIZE as i64)))?;
|
||||||
|
data.serialize(&mut stream)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -400,39 +526,71 @@ pub struct DirIter<'a, 'b: 'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, 'b> DirIter<'a, 'b> {
|
impl <'a, 'b> DirIter<'a, 'b> {
|
||||||
fn read_dir_entry_data(&mut self) -> io::Result<DirEntryData> {
|
fn read_dir_entry_raw_data(&mut self) -> io::Result<DirEntryData> {
|
||||||
let mut name = [0; 11];
|
DirEntryData::deserialize(&mut self.rdr)
|
||||||
self.rdr.read_exact(&mut name)?;
|
}
|
||||||
let attrs = FileAttributes::from_bits_truncate(self.rdr.read_u8()?);
|
|
||||||
if attrs == FileAttributes::LFN {
|
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'a, 'b>>> {
|
||||||
let mut data = DirLfnEntryData {
|
let mut lfn_buf = LongNameBuilder::new();
|
||||||
attrs, ..Default::default()
|
let mut offset = self.rdr.seek(SeekFrom::Current(0))?;
|
||||||
};
|
let mut begin_offset = offset;
|
||||||
let mut cur = Cursor::new(&name);
|
loop {
|
||||||
data.order = cur.read_u8()?;
|
let raw_entry = self.read_dir_entry_raw_data()?;
|
||||||
cur.read_u16_into::<LittleEndian>(&mut data.name_0)?;
|
offset += DIR_ENTRY_SIZE;
|
||||||
data.entry_type = self.rdr.read_u8()?;
|
match raw_entry {
|
||||||
data.checksum = self.rdr.read_u8()?;
|
DirEntryData::File(data) => {
|
||||||
self.rdr.read_u16_into::<LittleEndian>(&mut data.name_1)?;
|
// Check if this is end of dif
|
||||||
data.reserved_0 = self.rdr.read_u16::<LittleEndian>()?;
|
if data.is_end() {
|
||||||
self.rdr.read_u16_into::<LittleEndian>(&mut data.name_2)?;
|
return Ok(None);
|
||||||
Ok(DirEntryData::Lfn(data))
|
}
|
||||||
} else {
|
// Check if this is deleted or volume ID entry
|
||||||
let data = DirFileEntryData {
|
if data.is_removed() || data.attrs.contains(FileAttributes::VOLUME_ID) {
|
||||||
name,
|
lfn_buf.clear();
|
||||||
attrs,
|
begin_offset = offset;
|
||||||
reserved_0: self.rdr.read_u8()?,
|
continue;
|
||||||
create_time_0: self.rdr.read_u8()?,
|
}
|
||||||
create_time_1: self.rdr.read_u16::<LittleEndian>()?,
|
// Get entry position on volume
|
||||||
create_date: self.rdr.read_u16::<LittleEndian>()?,
|
let entry_pos = self.rdr.global_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||||
access_date: self.rdr.read_u16::<LittleEndian>()?,
|
// Check if LFN checksum is valid
|
||||||
first_cluster_hi: self.rdr.read_u16::<LittleEndian>()?,
|
lfn_buf.validate_chksum(&data.name);
|
||||||
modify_time: self.rdr.read_u16::<LittleEndian>()?,
|
return Ok(Some(DirEntry {
|
||||||
modify_date: self.rdr.read_u16::<LittleEndian>()?,
|
data,
|
||||||
first_cluster_lo: self.rdr.read_u16::<LittleEndian>()?,
|
lfn: lfn_buf.to_vec(),
|
||||||
size: self.rdr.read_u32::<LittleEndian>()?,
|
fs: self.fs,
|
||||||
};
|
entry_pos: entry_pos.unwrap(), // safe
|
||||||
Ok(DirEntryData::File(data))
|
offset_range: (begin_offset, offset),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
DirEntryData::Lfn(data) => {
|
||||||
|
// Check if this is deleted entry
|
||||||
|
if data.is_removed() {
|
||||||
|
lfn_buf.clear();
|
||||||
|
begin_offset = offset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Append to LFN buffer
|
||||||
|
lfn_buf.process(&data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a, 'b> Iterator for DirIter<'a, 'b> {
|
||||||
|
type Item = io::Result<DirEntry<'a, 'b>>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.err {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let r = self.read_dir_entry();
|
||||||
|
match r {
|
||||||
|
Ok(Some(e)) => Some(Ok(e)),
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(err) => {
|
||||||
|
self.err = true;
|
||||||
|
Some(Err(err))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,8 +601,6 @@ struct LongNameBuilder {
|
|||||||
index: u8,
|
index: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
const LFN_PART_LEN: usize = 13;
|
|
||||||
|
|
||||||
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 {
|
||||||
@ -530,58 +686,3 @@ impl LongNameBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DIR_ENTRY_SIZE: u64 = 32;
|
|
||||||
|
|
||||||
impl <'a, 'b> Iterator for DirIter<'a, 'b> {
|
|
||||||
type Item = io::Result<DirEntry<'a, 'b>>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.err {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut lfn_buf = LongNameBuilder::new();
|
|
||||||
loop {
|
|
||||||
let res = self.read_dir_entry_data();
|
|
||||||
let data = match res {
|
|
||||||
Ok(data) => data,
|
|
||||||
Err(err) => {
|
|
||||||
self.err = true;
|
|
||||||
return Some(Err(err));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
match data {
|
|
||||||
DirEntryData::File(data) => {
|
|
||||||
// Check if this is end of dif
|
|
||||||
if data.name[0] == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Check if this is deleted or volume ID entry
|
|
||||||
if data.name[0] == 0xE5 || data.attrs.contains(FileAttributes::VOLUME_ID) {
|
|
||||||
lfn_buf.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Get entry position on volume
|
|
||||||
let entry_pos = self.rdr.global_pos().map(|p| p - DIR_ENTRY_SIZE);
|
|
||||||
// Check if LFN checksum is valid
|
|
||||||
lfn_buf.validate_chksum(&data.name);
|
|
||||||
return Some(Ok(DirEntry {
|
|
||||||
data,
|
|
||||||
lfn: lfn_buf.to_vec(),
|
|
||||||
fs: self.fs,
|
|
||||||
entry_pos: entry_pos.unwrap(), // safe
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
DirEntryData::Lfn(data) => {
|
|
||||||
// Check if this is deleted entry
|
|
||||||
if data.order == 0xE5 {
|
|
||||||
lfn_buf.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Append to LFN buffer
|
|
||||||
lfn_buf.process(&data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -89,3 +89,36 @@ fn test_write_long_file_fat16() {
|
|||||||
fn test_write_long_file_fat32() {
|
fn test_write_long_file_fat32() {
|
||||||
call_with_fs(&test_write_long_file, FAT32_IMG, 2)
|
call_with_fs(&test_write_long_file, FAT32_IMG, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_remove(fs: FileSystem) {
|
||||||
|
let mut root_dir = fs.root_dir();
|
||||||
|
assert!(root_dir.remove("very/long/path").is_err());
|
||||||
|
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"]);
|
||||||
|
root_dir.remove("very/long/path/test.txt").unwrap();
|
||||||
|
names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
||||||
|
assert_eq!(names, [".", ".."]);
|
||||||
|
assert!(root_dir.remove("very/long/path").is_ok());
|
||||||
|
|
||||||
|
names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
||||||
|
assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name"]);
|
||||||
|
root_dir.remove("long.txt").unwrap();
|
||||||
|
names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
||||||
|
assert_eq!(names, ["short.txt", "very", "very-long-dir-name"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_fat12() {
|
||||||
|
call_with_fs(&test_remove, FAT12_IMG, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_fat16() {
|
||||||
|
call_with_fs(&test_remove, FAT16_IMG, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_fat32() {
|
||||||
|
call_with_fs(&test_remove, FAT32_IMG, 3)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user