feat(ext4): seek, rmdir (e2fsck-clean)
This commit is contained in:
@@ -130,4 +130,13 @@ impl FileSystem for MockFileSystem {
|
||||
}
|
||||
|
||||
fn mkdir(&mut self, _path: &[u8]) -> bool { true }
|
||||
|
||||
/// Files are read from the start and written at the end only.
|
||||
fn seek(&mut self, _handle: FileHandle, _offset: usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn rmdir(&mut self, _path: &[u8]) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ pub trait FileSystem {
|
||||
fn delete(&mut self, path: &[u8]) -> bool;
|
||||
fn file_size(&mut self, handle: FileHandle) -> Option<usize>;
|
||||
fn mkdir(&mut self, path: &[u8]) -> bool;
|
||||
/// Moves `handle`'s position to `offset` (at most the file's size).
|
||||
fn seek(&mut self, handle: FileHandle, offset: usize) -> bool;
|
||||
/// Removes the directory at `path`, which must be empty.
|
||||
fn rmdir(&mut self, path: &[u8]) -> bool;
|
||||
/// Whether anything can be written at all; the server refuses
|
||||
/// writing requests up front when not.
|
||||
fn writable(&self) -> bool {
|
||||
|
||||
@@ -60,6 +60,15 @@ impl<B: BlockDevice> Ext4<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// One directory fewer in `inode`'s block group.
|
||||
fn uncount_dir(&mut self, inode: u32) {
|
||||
let (bg, spb) = (self.sb.inode_block_group(inode), self.sb.sectors_per_block());
|
||||
if let Some(mut d) = bgd::BlockGroupDescriptor::read(&mut self.device, self.gdt_block, bg, spb) {
|
||||
d.used_dirs_count = d.used_dirs_count.saturating_sub(1);
|
||||
d.write_back(&mut self.device, self.gdt_block, bg, spb);
|
||||
}
|
||||
}
|
||||
|
||||
/// The disk, for a test to look at what was written.
|
||||
pub fn into_device(self) -> B {
|
||||
self.device
|
||||
@@ -361,4 +370,40 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn seek(&mut self, handle: FileHandle, offset: usize) -> bool {
|
||||
match self.open_files.get_mut(handle.raw() as usize).and_then(Option::as_mut) {
|
||||
Some(f) if offset <= f.size => {
|
||||
f.offset = offset;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn rmdir(&mut self, path: &[u8]) -> bool {
|
||||
let Some((parent_num, name)) = self.resolve_parent(path) else { return false };
|
||||
let Some(parent) = self.read_inode(parent_num) else { return false };
|
||||
let Some(child_num) = dir::find_entry(&mut self.device, &self.sb, &parent, name) else { return false };
|
||||
let Some(child) = self.read_inode(child_num).filter(Inode::is_dir) else { return false };
|
||||
// Empty: nothing but `.` and `..`.
|
||||
let mut entries = [DirEntry::empty(); 3];
|
||||
let Some(n) = dir::list_entries(&mut self.device, &self.sb, &child, &mut entries) else { return false };
|
||||
if entries[..n].iter().any(|e| e.name.as_bytes() != b"." && e.name.as_bytes() != b"..") {
|
||||
return false;
|
||||
}
|
||||
if dir::remove_entry(&mut self.device, &self.sb, &parent, name).is_none() {
|
||||
return false;
|
||||
}
|
||||
self.free_all_blocks(&child);
|
||||
self.clear_inode(child_num);
|
||||
self.free_inode(child_num);
|
||||
self.uncount_dir(child_num);
|
||||
// Its `..` linked the parent.
|
||||
if let Some(mut p) = self.read_inode(parent_num) {
|
||||
p.links_count = p.links_count.saturating_sub(1);
|
||||
self.write_inode(parent_num, &p);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,3 +183,38 @@ fn holes_read_as_zeros() {
|
||||
let mut fs = Ext4::mount(FileDisk(img)).expect("mount");
|
||||
assert_eq!(read_all(&mut fs, b"sparse.bin"), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_directory_goes_and_e2fsck_agrees() {
|
||||
let Some((dir, img)) = setup("rmdir") else { return };
|
||||
let mut fs = Ext4::mount(FileDisk(img)).expect("mount");
|
||||
assert!(fs.mkdir(b"/gone"));
|
||||
assert!(fs.mkdir(b"/gone/deeper"));
|
||||
let h = fs.create(b"/gone/deeper/f").expect("create");
|
||||
fs.close(h);
|
||||
assert!(!fs.rmdir(b"/gone"), "not empty");
|
||||
assert!(fs.delete(b"/gone/deeper/f"));
|
||||
assert!(fs.rmdir(b"/gone/deeper"));
|
||||
assert!(fs.rmdir(b"/gone"));
|
||||
assert!(!names(&mut fs, b"/").iter().any(|n| n == "gone"));
|
||||
assert!(!fs.rmdir(b"/hello.txt"), "a file is not a directory");
|
||||
let (clean, report) = fsck_clean(&dir, &fs.into_device().0);
|
||||
assert!(clean, "e2fsck: {report}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writes_land_where_seek_says() {
|
||||
let Some((dir, img)) = setup("seek") else { return };
|
||||
let mut fs = Ext4::mount(FileDisk(img)).expect("mount");
|
||||
let h = fs.create(b"/f").expect("create");
|
||||
assert_eq!(fs.write(h, &[b'a'; 5000]), Some(5000));
|
||||
assert!(fs.seek(h, 4090));
|
||||
assert_eq!(fs.write(h, b"XYZXYZXYZXYZ"), Some(12));
|
||||
assert!(!fs.seek(h, 6000), "not past the end");
|
||||
fs.close(h);
|
||||
let back = read_all(&mut fs, b"/f");
|
||||
assert_eq!(back.len(), 5000);
|
||||
assert_eq!(&back[4088..4104], b"aaXYZXYZXYZXYZaa");
|
||||
let (clean, report) = fsck_clean(&dir, &fs.into_device().0);
|
||||
assert!(clean, "e2fsck: {report}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user