use crate::dirhash::{half_md4, HashSeed, Signedness};
use crate::spec::{dirent, htree, BLOCK_SIZE};
use crate::{csum, Error, Result};
pub(crate) type Entry = (Vec<u8>, u32, u8);
pub(crate) fn needs_htree(entries: &[Entry]) -> bool {
let bytes: usize = entries
.iter()
.map(|(name, _, _)| dirent::rec_len_for(name.len()))
.sum();
bytes >= BLOCK_SIZE - 24
}
pub(crate) struct HtreeDir {
pub(crate) blocks: Vec<Vec<u8>>,
}
pub(crate) fn build(
self_ino: u32,
parent_ino: u32,
entries: &[Entry],
seed: &HashSeed,
fs_seed: u32,
) -> Result<HtreeDir> {
let inode_seed = csum::inode_seed(fs_seed, self_ino, 0);
let mut hashed: Vec<(u32, u32, usize)> = entries
.iter()
.enumerate()
.map(|(i, (name, _, _))| {
let (h, minor) = half_md4(seed, Signedness::Signed, name);
(h, minor, i)
})
.collect();
hashed.sort_unstable();
let usable = BLOCK_SIZE - 12;
let mut leaves: Vec<Vec<usize>> = vec![Vec::new()];
let mut used = 0usize;
for &(_, _, i) in &hashed {
let rl = dirent::rec_len_for(entries[i].0.len());
if used + rl > usable {
leaves.push(Vec::new());
used = 0;
}
leaves.last_mut().unwrap().push(i);
used += rl;
}
let mut leaf_dx: Vec<(u32, u32)> = Vec::with_capacity(leaves.len());
let mut prev_last_hash: Option<u32> = None;
let mut base = 0usize; for (k, leaf) in leaves.iter().enumerate() {
let first_hash = hashed[base].0;
let hash = match prev_last_hash {
Some(p) if k > 0 && p == first_hash => first_hash | 1,
_ => first_hash,
};
leaf_dx.push((hash, (k + 1) as u32));
prev_last_hash = Some(hashed[base + leaf.len() - 1].0);
base += leaf.len();
}
let root_limit = usize::from(htree::root_limit(BLOCK_SIZE));
let node_limit = usize::from(htree::node_limit(BLOCK_SIZE));
let (indirect_levels, node_groups): (u8, Vec<&[(u32, u32)]>) = if leaf_dx.len() <= root_limit {
(0, vec![&leaf_dx[..]])
} else {
let groups: Vec<&[(u32, u32)]> = leaf_dx.chunks(node_limit).collect();
if groups.len() > root_limit {
return Err(Error::Unsupported(
"directory needs more than a 2-level htree (largedir)".into(),
));
}
(1, groups)
};
let mut blocks: Vec<Vec<u8>> = Vec::with_capacity(1 + leaves.len() + node_groups.len());
blocks.push(Vec::new());
let mut cursor = 0usize;
for leaf in &leaves {
let mut buf = vec![0u8; BLOCK_SIZE];
let mut off = 0usize;
for (j, &i) in leaf.iter().enumerate() {
let (name, ino, ft) = &entries[i];
let last = j == leaf.len() - 1;
let rl = dirent::rec_len_for(name.len());
let rec_len = if last { usable - off } else { rl };
crate::le::put_u32(&mut buf, off, *ino);
crate::le::put_u16(&mut buf, off + 4, rec_len as u16);
buf[off + 6] = name.len() as u8;
buf[off + 7] = *ft;
buf[off + 8..off + 8 + name.len()].copy_from_slice(name);
off += rec_len;
}
crate::le::put_u16(&mut buf, BLOCK_SIZE - 12 + 4, 12);
buf[BLOCK_SIZE - 12 + 7] = 0xDE;
let c = csum::dirent_block(inode_seed, &buf);
crate::le::put_u32(&mut buf, BLOCK_SIZE - 4, c);
blocks.push(buf);
cursor += leaf.len();
}
debug_assert_eq!(cursor, hashed.len());
let first_node_logical = (1 + leaves.len()) as u32;
let mut root_dx: Vec<(u32, u32)> = Vec::with_capacity(node_groups.len());
if indirect_levels == 1 {
for (gi, group) in node_groups.iter().enumerate() {
let mut buf = vec![0u8; BLOCK_SIZE];
crate::le::put_u16(&mut buf, 4, BLOCK_SIZE as u16);
write_countlimit_entries(&mut buf, htree::NODE_COUNT_OFFSET, node_limit, group);
let c = csum::dx_block(
inode_seed,
&buf,
htree::NODE_COUNT_OFFSET,
group.len() as u16,
node_limit as u16,
);
let tail = htree::NODE_COUNT_OFFSET + node_limit * 8;
crate::le::put_u32(&mut buf, tail + 4, c);
blocks.push(buf);
root_dx.push((group[0].0, first_node_logical + gi as u32));
}
} else {
root_dx = leaf_dx.clone();
}
let mut root = vec![0u8; BLOCK_SIZE];
crate::le::put_u32(&mut root, 0, self_ino);
crate::le::put_u16(&mut root, 4, 12);
root[6] = 1;
root[7] = crate::spec::file_type::DIR;
root[8] = b'.';
crate::le::put_u32(&mut root, 0x0C, parent_ino);
crate::le::put_u16(&mut root, 0x10, (BLOCK_SIZE - 12) as u16);
root[0x12] = 2;
root[0x13] = crate::spec::file_type::DIR;
root[0x14] = b'.';
root[0x15] = b'.';
root[0x1C] = 1;
root[0x1D] = 8;
root[0x1E] = indirect_levels;
write_countlimit_entries(&mut root, htree::ROOT_COUNT_OFFSET, root_limit, &root_dx);
let c = csum::dx_block(
inode_seed,
&root,
htree::ROOT_COUNT_OFFSET,
root_dx.len() as u16,
root_limit as u16,
);
let tail = htree::ROOT_COUNT_OFFSET + root_limit * 8;
crate::le::put_u32(&mut root, tail + 4, c);
blocks[0] = root;
Ok(HtreeDir { blocks })
}
fn write_countlimit_entries(buf: &mut [u8], count_offset: usize, limit: usize, dx: &[(u32, u32)]) {
crate::le::put_u16(buf, count_offset, limit as u16);
crate::le::put_u16(buf, count_offset + 2, dx.len() as u16);
crate::le::put_u32(buf, count_offset + 4, dx[0].1);
for (i, &(hash, block)) in dx.iter().enumerate().skip(1) {
crate::le::put_u32(buf, count_offset + 8 * i, hash);
crate::le::put_u32(buf, count_offset + 8 * i + 4, block);
}
}