use super::constants::{
F2FS_FT_BLKDEV, F2FS_FT_CHRDEV, F2FS_FT_DIR, F2FS_FT_FIFO, F2FS_FT_REG_FILE, F2FS_FT_SOCK,
F2FS_FT_SYMLINK, F2FS_SLOT_LEN, NR_DENTRY_IN_BLOCK, SIZE_OF_DENTRY_BITMAP, SIZE_OF_DIR_ENTRY,
SIZE_OF_RESERVED,
};
use super::inode::F2fsInode;
use crate::fs::EntryKind;
#[derive(Debug, Clone)]
pub struct RawDentry {
pub hash: u32,
pub ino: u32,
pub file_type: u8,
pub name: Vec<u8>,
}
impl RawDentry {
pub fn into_dir_entry(self) -> crate::fs::DirEntry {
crate::fs::DirEntry {
name: String::from_utf8_lossy(&self.name).into_owned(),
inode: self.ino,
kind: type_to_kind(self.file_type),
}
}
}
pub fn type_to_kind(file_type: u8) -> EntryKind {
match file_type {
F2FS_FT_REG_FILE => EntryKind::Regular,
F2FS_FT_DIR => EntryKind::Dir,
F2FS_FT_SYMLINK => EntryKind::Symlink,
F2FS_FT_CHRDEV => EntryKind::Char,
F2FS_FT_BLKDEV => EntryKind::Block,
F2FS_FT_FIFO => EntryKind::Fifo,
F2FS_FT_SOCK => EntryKind::Socket,
_ => EntryKind::Unknown,
}
}
pub fn decode_dentry_block(buf: &[u8]) -> crate::Result<Vec<RawDentry>> {
decode_dentry_region(
buf,
NR_DENTRY_IN_BLOCK,
0,
SIZE_OF_DENTRY_BITMAP + SIZE_OF_RESERVED,
SIZE_OF_DENTRY_BITMAP + SIZE_OF_RESERVED + NR_DENTRY_IN_BLOCK * SIZE_OF_DIR_ENTRY,
)
}
pub fn decode_inline_dentries(
inode: &F2fsInode,
inode_block: &[u8],
) -> crate::Result<Vec<RawDentry>> {
if !inode.is_inline_dentry() {
return Ok(Vec::new());
}
let payload = inode.inline_payload(inode_block);
let nr = INLINE_DENTRY_NR;
let bitmap_bytes = nr.div_ceil(8);
let reserved = 1usize; let dentries_off = bitmap_bytes + reserved;
let names_off = dentries_off + nr * SIZE_OF_DIR_ENTRY;
decode_dentry_region(payload, nr, 0, dentries_off, names_off)
}
pub const INLINE_DENTRY_NR: usize = 182;
fn decode_dentry_region(
region: &[u8],
nr: usize,
bitmap_off: usize,
dentries_off: usize,
names_off: usize,
) -> crate::Result<Vec<RawDentry>> {
let bitmap_bytes = nr.div_ceil(8);
let end = names_off + nr * F2FS_SLOT_LEN;
if region.len() < end {
return Err(crate::Error::InvalidImage(
"f2fs: dentry region truncated".into(),
));
}
let bitmap = ®ion[bitmap_off..bitmap_off + bitmap_bytes];
let mut out = Vec::new();
let mut slot = 0;
while slot < nr {
if !get_bit(bitmap, slot) {
slot += 1;
continue;
}
let de_off = dentries_off + slot * SIZE_OF_DIR_ENTRY;
let hash = u32::from_le_bytes(region[de_off..de_off + 4].try_into().unwrap());
let ino = u32::from_le_bytes(region[de_off + 4..de_off + 8].try_into().unwrap());
let name_len =
u16::from_le_bytes(region[de_off + 8..de_off + 10].try_into().unwrap()) as usize;
let file_type = region[de_off + 10];
let need_slots = name_len.div_ceil(F2FS_SLOT_LEN).max(1);
if slot + need_slots > nr {
return Err(crate::Error::InvalidImage(format!(
"f2fs: dentry@slot {slot} runs past end (name_len={name_len})"
)));
}
let name_start = names_off + slot * F2FS_SLOT_LEN;
let name_end = (name_start + name_len).min(region.len());
let name = region[name_start..name_end].to_vec();
out.push(RawDentry {
hash,
ino,
file_type,
name,
});
slot += need_slots;
}
Ok(out)
}
#[inline]
fn get_bit(bitmap: &[u8], i: usize) -> bool {
let byte = i / 8;
let bit = i % 8;
byte < bitmap.len() && (bitmap[byte] & (1 << bit)) != 0
}
#[cfg(test)]
#[inline]
fn set_bit(bitmap: &mut [u8], i: usize) {
let byte = i / 8;
let bit = i % 8;
if byte < bitmap.len() {
bitmap[byte] |= 1 << bit;
}
}
pub fn file_type_from_mode(mode: u16) -> u8 {
use super::constants::{
S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
};
match mode & S_IFMT {
S_IFREG => F2FS_FT_REG_FILE,
S_IFDIR => F2FS_FT_DIR,
S_IFLNK => F2FS_FT_SYMLINK,
S_IFCHR => F2FS_FT_CHRDEV,
S_IFBLK => F2FS_FT_BLKDEV,
S_IFIFO => F2FS_FT_FIFO,
S_IFSOCK => F2FS_FT_SOCK,
_ => 0,
}
}
#[cfg(test)]
pub(crate) fn encode_dentry_block(entries: &[RawDentry]) -> Vec<u8> {
encode_dentry_region(
entries,
NR_DENTRY_IN_BLOCK,
SIZE_OF_DENTRY_BITMAP,
SIZE_OF_RESERVED,
super::constants::F2FS_BLKSIZE,
)
}
#[cfg(test)]
pub(crate) fn encode_inline_dentries_payload(entries: &[RawDentry]) -> Vec<u8> {
encode_dentry_region(
entries,
INLINE_DENTRY_NR,
INLINE_DENTRY_NR.div_ceil(8),
1,
INLINE_DENTRY_NR.div_ceil(8) + 1 + INLINE_DENTRY_NR * (SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN),
)
}
#[cfg(test)]
fn encode_dentry_region(
entries: &[RawDentry],
nr: usize,
bitmap_bytes: usize,
reserved: usize,
region_size: usize,
) -> Vec<u8> {
let dentries_off = bitmap_bytes + reserved;
let names_off = dentries_off + nr * SIZE_OF_DIR_ENTRY;
let mut buf = vec![0u8; region_size];
let mut slot = 0;
for e in entries {
let name_slots = e.name.len().div_ceil(F2FS_SLOT_LEN).max(1);
if slot + name_slots > nr {
break;
}
set_bit(&mut buf[0..bitmap_bytes], slot);
let de_off = dentries_off + slot * SIZE_OF_DIR_ENTRY;
buf[de_off..de_off + 4].copy_from_slice(&e.hash.to_le_bytes());
buf[de_off + 4..de_off + 8].copy_from_slice(&e.ino.to_le_bytes());
buf[de_off + 8..de_off + 10].copy_from_slice(&(e.name.len() as u16).to_le_bytes());
buf[de_off + 10] = e.file_type;
let name_start = names_off + slot * F2FS_SLOT_LEN;
let name_end = name_start + e.name.len();
buf[name_start..name_end].copy_from_slice(&e.name);
slot += name_slots;
}
buf
}