use super::constants::{DENT_BLK, DENT_CHR, DENT_DIR, DENT_FIFO, DENT_LNK, DENT_REG, DENT_SOCK};
pub const DIRENT_HEADER_LEN: usize = 8;
#[inline]
pub fn align4(n: usize) -> usize {
(n + 3) & !3
}
pub fn min_rec_len(name_len: usize) -> usize {
align4(DIRENT_HEADER_LEN + name_len)
}
pub fn encode_entry(
out: &mut Vec<u8>,
inode: u32,
name: &[u8],
rec_len: u16,
file_type: u8,
with_filetype: bool,
) {
assert!(
name.len() <= 255,
"ext2 name_len is u8 (or u16 without FILETYPE)"
);
let start = out.len();
out.extend_from_slice(&inode.to_le_bytes());
out.extend_from_slice(&rec_len.to_le_bytes());
if with_filetype {
out.push(name.len() as u8);
out.push(file_type);
} else {
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
}
out.extend_from_slice(name);
let padded_len = rec_len as usize;
let written = out.len() - start;
assert!(
written <= padded_len,
"dir entry overflows declared rec_len: wrote {written}, declared {padded_len}"
);
out.resize(start + padded_len, 0);
}
pub fn decode_entry(b: &[u8], with_filetype: bool) -> Option<DecodedEntry<'_>> {
if b.len() < DIRENT_HEADER_LEN {
return None;
}
let inode = u32::from_le_bytes(b[0..4].try_into().unwrap());
let rec_len = u16::from_le_bytes(b[4..6].try_into().unwrap()) as usize;
let (name_len, file_type) = if with_filetype {
(b[6] as usize, b[7])
} else {
let nl = u16::from_le_bytes(b[6..8].try_into().unwrap()) as usize;
(nl, 0)
};
if rec_len < DIRENT_HEADER_LEN || DIRENT_HEADER_LEN + name_len > rec_len {
return None;
}
if b.len() < rec_len {
return None;
}
Some(DecodedEntry {
inode,
name: &b[DIRENT_HEADER_LEN..DIRENT_HEADER_LEN + name_len],
rec_len,
file_type,
})
}
#[derive(Debug)]
pub struct DecodedEntry<'a> {
pub inode: u32,
pub name: &'a [u8],
pub rec_len: usize,
pub file_type: u8,
}
pub fn file_type_byte(k: crate::fs::EntryKind) -> u8 {
use crate::fs::EntryKind::*;
match k {
Regular => DENT_REG,
Dir => DENT_DIR,
Symlink => DENT_LNK,
Char => DENT_CHR,
Block => DENT_BLK,
Fifo => DENT_FIFO,
Socket => DENT_SOCK,
Unknown => 0,
}
}
pub fn make_empty_dir_block(block_size: u32) -> Vec<u8> {
let mut buf = vec![0u8; block_size as usize];
buf[4..6].copy_from_slice(&(block_size as u16).to_le_bytes());
buf
}
pub fn make_initial_dir_block(
self_inode: u32,
parent_inode: u32,
block_size: u32,
with_filetype: bool,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(block_size as usize);
let dot_rec = min_rec_len(1) as u16;
encode_entry(&mut buf, self_inode, b".", dot_rec, DENT_DIR, with_filetype);
let dotdot_rec = (block_size as usize - buf.len()) as u16;
encode_entry(
&mut buf,
parent_inode,
b"..",
dotdot_rec,
DENT_DIR,
with_filetype,
);
debug_assert_eq!(buf.len(), block_size as usize);
buf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn align4_examples() {
assert_eq!(align4(0), 0);
assert_eq!(align4(1), 4);
assert_eq!(align4(8), 8);
assert_eq!(align4(9), 12);
assert_eq!(align4(11), 12);
assert_eq!(align4(12), 12);
}
#[test]
fn min_rec_len_for_short_name() {
assert_eq!(min_rec_len(1), 12);
assert_eq!(min_rec_len(4), 12);
assert_eq!(min_rec_len(5), 16);
}
#[test]
fn initial_dir_block_round_trips() {
let buf = make_initial_dir_block(2, 2, 1024, false);
assert_eq!(buf.len(), 1024);
let first = decode_entry(&buf, false).unwrap();
assert_eq!(first.inode, 2);
assert_eq!(first.name, b".");
let second = decode_entry(&buf[first.rec_len..], false).unwrap();
assert_eq!(second.inode, 2);
assert_eq!(second.name, b"..");
assert_eq!(first.rec_len + second.rec_len, 1024);
}
#[test]
fn many_entries_round_trip() {
let mut buf = Vec::new();
let names: &[&[u8]] = &[b"first", b"second", b"three", b"four"];
for (inode, name) in (11u32..).zip(names.iter()) {
let rl = min_rec_len(name.len()) as u16;
encode_entry(&mut buf, inode, name, rl, DENT_REG, false);
}
let mut off = 0;
let mut got_names = Vec::new();
while off < buf.len() {
let e = decode_entry(&buf[off..], false).unwrap();
got_names.push(e.name.to_vec());
off += e.rec_len;
}
let got: Vec<&[u8]> = got_names.iter().map(|v| v.as_slice()).collect();
assert_eq!(got, names);
}
}