use crate::{
block_read::BlockRead,
btree::{find_exact, find_first_ge},
checksum::crc32c_with_seed,
chunk_tree::ChunkMap,
error::{Error, Result},
format::{
constants::{
DIR_ITEM_KEY, FS_TREE_OBJECTID, NAME_HASH_SEED, ROOT_ITEM_KEY, ROOT_TREE_DIR_OBJECTID,
},
repr::{DirEntry, DiskKey, RootItem},
},
};
pub(crate) fn name_hash(name: &[u8]) -> u64 {
u64::from(crc32c_with_seed(NAME_HASH_SEED, name))
}
pub(crate) fn resolve_default_subvol<R: BlockRead>(
reader: &mut R,
chunk_map: &ChunkMap,
nodesize: u32,
root_tree_root: u64,
superblock_default_objectid: u64,
) -> Result<(u64, u8, u64)> {
let subvol_objectid =
match lookup_default_dir_item(reader, chunk_map, nodesize, root_tree_root)? {
Some(oid) => oid,
None => {
if superblock_default_objectid == 0 || superblock_default_objectid == 6 {
FS_TREE_OBJECTID
} else {
superblock_default_objectid
}
}
};
let root_item = lookup_root_item(reader, chunk_map, nodesize, root_tree_root, subvol_objectid)?
.ok_or(Error::CorruptBtree {
token: "subvol_root_missing",
logical: root_tree_root,
})?;
Ok((root_item.bytenr, root_item.level, subvol_objectid))
}
fn lookup_default_dir_item<R: BlockRead>(
reader: &mut R,
chunk_map: &ChunkMap,
nodesize: u32,
root_tree_root: u64,
) -> Result<Option<u64>> {
let hash = name_hash(b"default");
let target = DiskKey {
objectid: ROOT_TREE_DIR_OBJECTID,
item_type: DIR_ITEM_KEY,
offset: hash,
};
let Some((leaf, idx)) = find_exact(reader, chunk_map, nodesize, root_tree_root, &target)?
else {
return Ok(None);
};
let item = leaf.leaf_item(idx)?;
let data = leaf.leaf_item_data(item)?;
let mut p = 0usize;
while p < data.len() {
let Some((entry, next)) = DirEntry::parse(data, p) else {
return Err(Error::CorruptBtree {
token: "dir_entry_short",
logical: leaf.header.bytenr,
});
};
if entry.name == b"default" {
return Ok(Some(entry.location.objectid));
}
p = next;
}
Ok(None)
}
fn lookup_root_item<R: BlockRead>(
reader: &mut R,
chunk_map: &ChunkMap,
nodesize: u32,
root_tree_root: u64,
subvol_objectid: u64,
) -> Result<Option<RootItem>> {
let target = DiskKey {
objectid: subvol_objectid,
item_type: ROOT_ITEM_KEY,
offset: 0,
};
let Some((leaf, idx)) = find_first_ge(reader, chunk_map, nodesize, root_tree_root, &target)?
else {
return Ok(None);
};
let item = leaf.leaf_item(idx)?;
if item.key.objectid != subvol_objectid || item.key.item_type != ROOT_ITEM_KEY {
return Ok(None);
}
let data = leaf.leaf_item_data(item)?;
let parsed = RootItem::parse(data, 0).ok_or(Error::CorruptBtree {
token: "root_item_short",
logical: leaf.header.bytenr,
})?;
Ok(Some(parsed))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn name_hash_default_is_known() {
let h = name_hash(b"default");
assert_ne!(h, 0);
assert_eq!(name_hash(b"default"), h);
assert_ne!(name_hash(b"default"), name_hash(b"DEFAULT"));
}
}