use crate::fscore::structs::{Blkptr, DnodePhys};
use crate::storage::dmu::ObjectSet;
use crate::{FsError, FsResult};
use alloc::string::String;
use alloc::vec::Vec;
use core::mem;
pub const ZBT_MICRO: u64 = (1 << 63) + 3; pub const ZBT_HEADER: u64 = (1 << 63) + 1; pub const ZBT_LEAF: u64 = (1 << 63);
pub const MZAP_ENT_LEN: usize = 64;
pub const MZAP_NAME_LEN: usize = 50;
pub const ZAP_LEAF_CHUNKSIZE: usize = 24;
pub const ZAP_LEAF_ARRAY_BYTES: usize = ZAP_LEAF_CHUNKSIZE - 3;
pub const ZAP_LEAF_HASH_NUMENTRIES: usize = 4096 / 2;
pub const ZAP_CHUNK_FREE: u8 = 253;
pub const ZAP_CHUNK_ENTRY: u8 = 252;
pub const ZAP_CHUNK_ARRAY: u8 = 251;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct MzapPhys {
pub block_type: u64,
pub salt: u64,
pub norm_flags: u64,
pub pad: [u64; 5],
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct MzapEntPhys {
pub value: u64,
pub cd: u32,
pub pad: u16,
pub name: [u8; MZAP_NAME_LEN],
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ZapPhys {
pub block_type: u64, pub magic: u64, pub ptrtbl_blk: u64, pub ptrtbl_numblks: u64, pub ptrtbl_shift: u64, pub freeblk: u64, pub num_leafs: u64, pub num_entries: u64, pub salt: u64, pub pad: [u64; 5],
pub leafs: [u64; 8192 / 8 - 14], }
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ZapLeafHeader {
pub block_type: u64, pub next_leaf: u64, pub prefix: u64, pub magic: u32, pub nfree: u16, pub nentries: u16, pub prefix_len: u16, pub freelist: u16, pub pad: [u8; 12],
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ZapLeafEntry {
pub chunk_type: u8, pub int_size: u8, pub next: u16, pub name_chunk: u16, pub name_numints: u16, pub value_chunk: u16, pub value_numints: u16, pub cd: u16, pub pad: u8,
pub hash: u64, }
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ZapLeafArray {
pub chunk_type: u8, pub array: [u8; ZAP_LEAF_ARRAY_BYTES],
pub next: u16, }
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BtreeDirHeader {
pub magic: u64,
pub version: u64,
pub index_root_bp: Blkptr,
pub pad: [u64; 16],
}
pub struct Zap;
impl Zap {
pub fn list_dir(dnode: &DnodePhys) -> FsResult<Vec<(String, u64)>> {
let raw_data = ObjectSet::read_dnode_data(dnode, 0, 4096)?;
if raw_data.len() < 8 {
return Err(FsError::Corruption {
block: 0,
details: "Directory block too small",
});
}
let block_type = u64::from_le_bytes([
raw_data[0],
raw_data[1],
raw_data[2],
raw_data[3],
raw_data[4],
raw_data[5],
raw_data[6],
raw_data[7],
]);
match block_type {
ZBT_MICRO => Self::list_microzap(&raw_data),
ZBT_HEADER => Self::list_fatzap(dnode, &raw_data),
_ => {
if dnode.indirection_levels == 0 && dnode.used_bytes <= 4096 {
Self::list_microzap(&raw_data)
} else {
Err(FsError::ZapError {
reason: "Unknown ZAP block type",
})
}
}
}
}
fn list_microzap(data: &[u8]) -> FsResult<Vec<(String, u64)>> {
if data.len() < mem::size_of::<MzapPhys>() {
return Err(FsError::Corruption {
block: 0,
details: "MicroZAP block too small",
});
}
let mut entries = Vec::new();
let header_size = mem::size_of::<MzapPhys>();
let chunk_size = mem::size_of::<MzapEntPhys>();
let mut offset = header_size;
while offset + chunk_size <= data.len() {
let chunk = unsafe { &*(data.as_ptr().add(offset) as *const MzapEntPhys) };
if chunk.name[0] != 0 {
let name_len = chunk
.name
.iter()
.position(|&c| c == 0)
.unwrap_or(MZAP_NAME_LEN);
if let Ok(name_str) = core::str::from_utf8(&chunk.name[0..name_len]) {
entries.push((String::from(name_str), chunk.value));
}
}
offset += chunk_size;
}
Ok(entries)
}
fn list_fatzap(dnode: &DnodePhys, header_data: &[u8]) -> FsResult<Vec<(String, u64)>> {
if header_data.len() < mem::size_of::<ZapPhys>() {
return Err(FsError::Corruption {
block: 0,
details: "FatZAP header too small",
});
}
let header = unsafe { &*(header_data.as_ptr() as *const ZapPhys) };
let mut entries = Vec::new();
let num_leafs = header.num_leafs as usize;
for leaf_idx in 0..num_leafs.min(256) {
let leaf_blk = if header.ptrtbl_blk == 0 {
if leaf_idx < header.leafs.len() {
header.leafs[leaf_idx]
} else {
continue;
}
} else {
(leaf_idx + 1) as u64
};
if leaf_blk == 0 {
continue;
}
let leaf_offset = leaf_blk * 4096;
match ObjectSet::read_dnode_data(dnode, leaf_offset, 4096) {
Ok(leaf_data) => {
Self::parse_leaf(&leaf_data, &mut entries)?;
}
Err(_) => continue,
}
}
Ok(entries)
}
fn parse_leaf(leaf_data: &[u8], entries: &mut Vec<(String, u64)>) -> FsResult<()> {
if leaf_data.len() < mem::size_of::<ZapLeafHeader>() {
return Ok(());
}
let header = unsafe { &*(leaf_data.as_ptr() as *const ZapLeafHeader) };
if header.block_type != ZBT_LEAF {
return Ok(());
}
let chunks_start = mem::size_of::<ZapLeafHeader>();
let hash_table_size = ZAP_LEAF_HASH_NUMENTRIES * 2; let chunks_offset = chunks_start + hash_table_size;
for hash_idx in 0..ZAP_LEAF_HASH_NUMENTRIES {
let hash_offset = chunks_start + hash_idx * 2;
if hash_offset + 2 > leaf_data.len() {
break;
}
let chunk_idx =
u16::from_le_bytes([leaf_data[hash_offset], leaf_data[hash_offset + 1]]) as usize;
if chunk_idx == 0xFFFF {
continue; }
let mut current_chunk = chunk_idx;
let mut chain_limit = 100;
while current_chunk != 0xFFFF && chain_limit > 0 {
chain_limit -= 1;
let chunk_offset = chunks_offset + current_chunk * ZAP_LEAF_CHUNKSIZE;
if chunk_offset + ZAP_LEAF_CHUNKSIZE > leaf_data.len() {
break;
}
let chunk_type = leaf_data[chunk_offset];
if chunk_type == ZAP_CHUNK_ENTRY {
let entry =
unsafe { &*(leaf_data.as_ptr().add(chunk_offset) as *const ZapLeafEntry) };
if let Some(name) = Self::read_array_chunks(
leaf_data,
chunks_offset,
entry.name_chunk as usize,
entry.name_numints as usize,
) {
let value = if entry.int_size == 8 && entry.value_numints >= 1 {
Self::read_value_u64(
leaf_data,
chunks_offset,
entry.value_chunk as usize,
)
.unwrap_or(0)
} else {
0
};
entries.push((name, value));
}
current_chunk = entry.next as usize;
} else {
break;
}
}
}
Ok(())
}
fn read_array_chunks(
leaf_data: &[u8],
chunks_offset: usize,
start_chunk: usize,
total_bytes: usize,
) -> Option<String> {
let mut result = Vec::with_capacity(total_bytes);
let mut current_chunk = start_chunk;
let mut remaining = total_bytes;
let mut chain_limit = 50;
while remaining > 0 && current_chunk != 0xFFFF && chain_limit > 0 {
chain_limit -= 1;
let chunk_offset = chunks_offset + current_chunk * ZAP_LEAF_CHUNKSIZE;
if chunk_offset + ZAP_LEAF_CHUNKSIZE > leaf_data.len() {
break;
}
let chunk_type = leaf_data[chunk_offset];
if chunk_type != ZAP_CHUNK_ARRAY {
break;
}
let array = unsafe { &*(leaf_data.as_ptr().add(chunk_offset) as *const ZapLeafArray) };
let copy_len = remaining.min(ZAP_LEAF_ARRAY_BYTES);
result.extend_from_slice(&array.array[..copy_len]);
remaining -= copy_len;
current_chunk = array.next as usize;
}
if let Some(null_pos) = result.iter().position(|&c| c == 0) {
result.truncate(null_pos);
}
String::from_utf8(result).ok()
}
fn read_value_u64(leaf_data: &[u8], chunks_offset: usize, value_chunk: usize) -> Option<u64> {
let chunk_offset = chunks_offset + value_chunk * ZAP_LEAF_CHUNKSIZE;
if chunk_offset + ZAP_LEAF_CHUNKSIZE > leaf_data.len() {
return None;
}
let chunk_type = leaf_data[chunk_offset];
if chunk_type != ZAP_CHUNK_ARRAY {
return None;
}
let array = unsafe { &*(leaf_data.as_ptr().add(chunk_offset) as *const ZapLeafArray) };
if ZAP_LEAF_ARRAY_BYTES >= 8 {
Some(u64::from_le_bytes([
array.array[0],
array.array[1],
array.array[2],
array.array[3],
array.array[4],
array.array[5],
array.array[6],
array.array[7],
]))
} else {
None
}
}
pub fn lookup(dnode: &DnodePhys, target_name: &str) -> FsResult<u64> {
let entries = Self::list_dir(dnode)?;
for (name, obj_id) in entries {
if name == target_name {
return Ok(obj_id);
}
}
Err(FsError::NotFound)
}
pub fn lookup_hash(dnode: &DnodePhys, target_name: &str) -> FsResult<u64> {
let raw_data = ObjectSet::read_dnode_data(dnode, 0, 4096)?;
if raw_data.len() < 8 {
return Err(FsError::NotFound);
}
let block_type = u64::from_le_bytes([
raw_data[0],
raw_data[1],
raw_data[2],
raw_data[3],
raw_data[4],
raw_data[5],
raw_data[6],
raw_data[7],
]);
if block_type == ZBT_MICRO || block_type != ZBT_HEADER {
return Self::lookup(dnode, target_name);
}
let header = unsafe { &*(raw_data.as_ptr() as *const ZapPhys) };
let hash = Self::zap_hash(header.salt, target_name);
let num_leafs = header.num_leafs as usize;
if num_leafs == 0 {
return Err(FsError::NotFound);
}
let leaf_idx = (hash >> (64 - header.ptrtbl_shift)) as usize % num_leafs;
let leaf_blk = if header.ptrtbl_blk == 0 && leaf_idx < header.leafs.len() {
header.leafs[leaf_idx]
} else {
(leaf_idx + 1) as u64
};
if leaf_blk == 0 {
return Err(FsError::NotFound);
}
let leaf_offset = leaf_blk * 4096;
let leaf_data = ObjectSet::read_dnode_data(dnode, leaf_offset, 4096)?;
let mut entries = Vec::new();
Self::parse_leaf(&leaf_data, &mut entries)?;
for (name, obj_id) in entries {
if name == target_name {
return Ok(obj_id);
}
}
Err(FsError::NotFound)
}
fn zap_hash(salt: u64, name: &str) -> u64 {
let mut hash = salt;
for byte in name.bytes() {
hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
}
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xff51afd7ed558ccd);
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xc4ceb9fe1a85ec53);
hash ^= hash >> 33;
hash
}
pub fn insert(dnode: &mut DnodePhys, name: &str, object_id: u64) -> FsResult<()> {
let raw_data = ObjectSet::read_dnode_data(dnode, 0, 4096)?;
if raw_data.len() < mem::size_of::<MzapPhys>() {
return Err(FsError::ZapError {
reason: "Cannot insert into invalid ZAP",
});
}
let block_type = u64::from_le_bytes([
raw_data[0],
raw_data[1],
raw_data[2],
raw_data[3],
raw_data[4],
raw_data[5],
raw_data[6],
raw_data[7],
]);
if block_type != ZBT_MICRO && block_type != 0 {
return Err(FsError::ZapError {
reason: "FatZAP insert not implemented",
});
}
let header_size = mem::size_of::<MzapPhys>();
let chunk_size = mem::size_of::<MzapEntPhys>();
let mut offset = header_size;
while offset + chunk_size <= raw_data.len() {
let chunk = unsafe { &*(raw_data.as_ptr().add(offset) as *const MzapEntPhys) };
if chunk.name[0] == 0 {
let mut new_entry = MzapEntPhys {
value: object_id,
cd: 0,
pad: 0,
name: [0u8; MZAP_NAME_LEN],
};
let name_bytes = name.as_bytes();
let copy_len = name_bytes.len().min(MZAP_NAME_LEN - 1);
new_entry.name[..copy_len].copy_from_slice(&name_bytes[..copy_len]);
return Ok(());
}
offset += chunk_size;
}
Err(FsError::ZapError {
reason: "No free slots in MicroZAP",
})
}
}