edit: delete a heap's huge-object B-tree with its last huge object

libhdf5 deletes a fractal heap's huge-object B-tree when the heap is
closed with no huge object left (H5HF__huge_term) and starts huge IDs
over. The editor left the empty tree, and a read-only libhdf5 then
failed to list the object's attributes: closing the heap tried to delete
the tree ("no write intent on file"), and h5dump failed the same way.

Replacing an object's last huge attribute (one above the heap's 4 KiB
managed limit) now deletes the tree (header and nodes freed), resets the
next huge ID and the wrapped flag, as libhdf5 does; a later huge
attribute creates a new tree. Bt2::delete frees a whole tree.

Found by the extended random-operation test; regression:
last_huge_attribute_replaced (fails before: h5dump, h5py listing), which
also compares the heap with libhdf5's after the same replacement.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 17:11:36 -05:00
co-authored by Claude Opus 5.5
parent dc9cfba6bb
commit 1ffd013de9
3 changed files with 89 additions and 0 deletions
+26
View File
@@ -384,6 +384,32 @@ impl Bt2 {
}
}
/// Delete the whole tree (`H5B2_delete`): every node and the header go
/// to the image's free list.
pub(crate) fn delete(mut self, img: &mut Image<'_>) -> Result<(), Error> {
if self.root.addr != undef(img.os) && self.root.nrec > 0 {
let mut level = vec![self.root];
let mut depth = self.depth;
loop {
let mut next = Vec::new();
for p in level {
self.load(img, p, depth)?;
if depth > 0 {
next.extend(self.peek(p.addr).ptrs.iter().copied());
}
img.free(p.addr, u64::from(self.node_size));
}
if depth == 0 {
break;
}
depth -= 1;
level = next;
}
}
img.free(self.addr, Self::header_len(img.os, img.ls) as u64);
Ok(())
}
/// Insert `rec`, or replace the record `cmp` matches (`H5B2_update`).
pub(crate) fn update(
&mut self,
+9
View File
@@ -948,6 +948,15 @@ impl Heap {
img.free(get_uint(&rec, os), len);
self.huge_size = self.huge_size.saturating_sub(len);
self.huge_nobjs = self.huge_nobjs.saturating_sub(1);
// H5HF__huge_term: with no huge object left, the huge-object
// B-tree is deleted and IDs start over (libhdf5 does it when
// it closes the heap, and a read-only libhdf5 fails to).
if self.huge_nobjs == 0 {
t.delete(img)?;
self.huge_bt2 = undef(os);
self.next_huge_id = 0;
self.flags &= !0x01;
}
Ok(())
}
_ => Err(unsupported("tiny objects")),