use crate::le::{u16 as le16, u32 as le32};
use crate::{corrupt, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DirEntryRef<'a> {
pub inode: u32,
pub file_type: u8,
pub name: &'a [u8],
pub offset: usize,
pub rec_len: u16,
}
pub struct DirentIter<'a> {
block: &'a [u8],
offset: usize,
failed: bool,
}
impl<'a> DirentIter<'a> {
pub fn new(block: &'a [u8]) -> Self {
DirentIter {
block,
offset: 0,
failed: false,
}
}
}
impl<'a> Iterator for DirentIter<'a> {
type Item = Result<DirEntryRef<'a>>;
fn next(&mut self) -> Option<Self::Item> {
while !self.failed && self.offset < self.block.len() {
let b = self.block;
let off = self.offset;
if b.len() - off < 8 {
self.failed = true;
return Some(Err(corrupt("dirent", "trailing bytes shorter than header")));
}
let inode = le32(b, off);
let rec_len = le16(b, off + 4);
let name_len = b[off + 6] as usize;
let file_type = b[off + 7];
if rec_len < 8 || rec_len % 4 != 0 || off + rec_len as usize > b.len() {
self.failed = true;
return Some(Err(corrupt(
"dirent",
format!("bad rec_len {rec_len} at offset {off}"),
)));
}
self.offset = off + rec_len as usize;
if inode == 0 {
continue; }
if 8 + name_len > rec_len as usize {
self.failed = true;
return Some(Err(corrupt(
"dirent",
format!("name_len {name_len} overflows rec_len {rec_len}"),
)));
}
return Some(Ok(DirEntryRef {
inode,
file_type,
name: &b[off + 8..off + 8 + name_len],
offset: off,
rec_len,
}));
}
None
}
}
pub fn tail_checksum(block: &[u8]) -> Option<u32> {
let t = block.len().checked_sub(12)?;
(le32(block, t) == 0 && le16(block, t + 4) == 12 && block[t + 6] == 0 && block[t + 7] == 0xDE)
.then(|| le32(block, t + 8))
}
pub fn rec_len_for(name_len: usize) -> usize {
(8 + name_len + 3) & !3
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spec::file_type;
fn vector(name: &str) -> Vec<u8> {
std::fs::read(format!(
"{}/testdata/vectors/{name}",
env!("CARGO_MANIFEST_DIR")
))
.unwrap()
}
#[test]
fn root_block_entries() {
let blk = vector("dirblock_root.bin");
let entries: Vec<_> = DirentIter::new(&blk).map(|e| e.unwrap()).collect();
assert_eq!(entries[0].name, b".");
assert_eq!(entries[0].inode, 2);
assert_eq!(entries[0].file_type, file_type::DIR);
assert_eq!(entries[1].name, b"..");
assert_eq!(entries[1].inode, 2);
let names: Vec<_> = entries.iter().map(|e| e.name).collect();
assert!(names.contains(&&b"lost+found"[..]));
assert!(names.contains(&&b"small.txt"[..]));
assert!(names.contains(&&b"sym_59"[..]));
let by_name = |n: &[u8]| entries.iter().find(|e| e.name == n).unwrap();
assert_eq!(
by_name(b"small.txt").inode,
by_name(b"hardlink_to_small").inode
);
assert_eq!(by_name(b"sym_59").file_type, file_type::SYMLINK);
assert_eq!(by_name(b"fifo").file_type, file_type::FIFO);
assert_eq!(by_name(b"sock").file_type, file_type::SOCK);
assert_eq!(by_name(b"dev_c_old").file_type, file_type::CHR);
assert_eq!(by_name(b"dev_b_old").file_type, file_type::BLK);
}
#[test]
fn tail_detected() {
let blk = vector("dirblock_root.bin");
assert!(tail_checksum(&blk).is_some());
let empty = vector("dirblock_empty.bin");
assert!(tail_checksum(&empty).is_some());
assert_eq!(DirentIter::new(&empty).count(), 0, "no used entries");
}
#[test]
fn rec_len_alignment() {
assert_eq!(rec_len_for(1), 12);
assert_eq!(rec_len_for(4), 12);
assert_eq!(rec_len_for(5), 16);
assert_eq!(rec_len_for(255), 264);
}
#[test]
fn corrupt_rec_len_errors() {
let mut blk = vector("dirblock_root.bin");
blk[4] = 3; blk[5] = 0;
let r: crate::Result<Vec<_>> = DirentIter::new(&blk).collect();
assert!(r.is_err());
}
}