Support removing files and directories.

This commit is contained in:
Rafał Harabień 2017-10-15 22:42:26 +02:00 committed by Rafał
parent b2c3eb1823
commit ba5329edad
3 changed files with 225 additions and 91 deletions

View File

@ -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);
}
};
}
}
}

View File

@ -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)
}