clawhdf5: set_attr adds the Attribute Info message a version-2 header needs
libhdf5 counts a version-2 object header's attributes through its Attribute Info message (0x15) and reports none when the header has none. set_attr gave v110/latest groups, the root group and datasets without attributes an attribute message only, so h5py listed the attribute but len(obj.attrs) and H5Oget_info's num_attrs said 0, and stayed wrong after h5py r+ added more. Like H5O__attr_create, the edit now adds the message when a version-2 header lacks it, in the same planned edit: version 0, the header's creation-order track/index flags, maximum creation index 0, undefined fractal heap and B-tree addresses, message flag DONTSHARE — byte for byte what libhdf5 writes. It goes before the attribute (libhdf5's order) when free space holds both, else after it, so a continuation chunk made for the attribute also takes it. Test: edit_interop attribute_count_in_version_2_headers — v110 and latest files, attributes set on the root group, groups and datasets with and without existing attributes: h5py's len/num_attrs/list/values, h5dump -A and our reader agree, also after h5py r+ adds attributes up to and past the compact limit. Fails on the previous editor (h5py len 0). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -47,6 +47,8 @@ const MSG_EXTERNAL: u16 = 0x07;
|
||||
const MSG_ATTR_INFO: u16 = 0x15;
|
||||
/// Message flag: the message is shared (stored elsewhere).
|
||||
const MSG_FLAG_SHARED: u8 = 0x02;
|
||||
/// Message flag: the message must not be shared (`H5O_MSG_FLAG_DONTSHARE`).
|
||||
const MSG_FLAG_DONTSHARE: u8 = 0x04;
|
||||
|
||||
/// An HDF5 file opened for in-place modification.
|
||||
///
|
||||
@@ -815,7 +817,24 @@ impl FileEditor {
|
||||
if let Some(i) = existing {
|
||||
hdr.delete(img, i)?;
|
||||
}
|
||||
// libhdf5 counts a version-2 header's attributes through its
|
||||
// Attribute Info message and reports none without one; like
|
||||
// H5O__attr_create, add it when missing: before the attribute
|
||||
// when free space holds both (libhdf5's order), else after it,
|
||||
// so that a new continuation chunk made for the attribute has
|
||||
// room for it too.
|
||||
let ainfo = (hdr.version == 2 && hdr.find(MSG_ATTR_INFO).is_none())
|
||||
.then(|| attr_info_message(hdr.flags, img.os));
|
||||
let ainfo_first = ainfo
|
||||
.as_ref()
|
||||
.is_some_and(|a| hdr.has_free(a.len() + hdr.hsize() + body.len()));
|
||||
if let Some(a) = ainfo.as_ref().filter(|_| ainfo_first) {
|
||||
hdr.insert(img, MSG_ATTR_INFO, MSG_FLAG_DONTSHARE, a, None)?;
|
||||
}
|
||||
hdr.insert(img, MSG_ATTRIBUTE, 0, &body, None)?;
|
||||
if let Some(a) = ainfo.as_ref().filter(|_| !ainfo_first) {
|
||||
hdr.insert(img, MSG_ATTR_INFO, MSG_FLAG_DONTSHARE, a, None)?;
|
||||
}
|
||||
hdr.finish(img)
|
||||
})
|
||||
}
|
||||
@@ -900,6 +919,26 @@ fn attr_name(d: &[u8]) -> Result<&[u8], Error> {
|
||||
Ok(name.split(|&b| b == 0).next().unwrap_or(name))
|
||||
}
|
||||
|
||||
/// A new Attribute Info message for a version-2 header with flags
|
||||
/// `hdr_flags`, as `H5O__attr_create` makes it: version 0, creation order
|
||||
/// tracked / indexed as the header's flags say, maximum creation index 0,
|
||||
/// and no dense storage (undefined fractal heap and B-tree addresses).
|
||||
fn attr_info_message(hdr_flags: u8, os: u8) -> Vec<u8> {
|
||||
let track = hdr_flags & 0x04 != 0;
|
||||
let index = hdr_flags & 0x08 != 0;
|
||||
let mut b = vec![0u8, u8::from(track) | (u8::from(index) << 1)];
|
||||
if track {
|
||||
b.extend_from_slice(&0u16.to_le_bytes());
|
||||
}
|
||||
let undef_addr = vec![0xffu8; os as usize];
|
||||
b.extend_from_slice(&undef_addr);
|
||||
b.extend_from_slice(&undef_addr);
|
||||
if index {
|
||||
b.extend_from_slice(&undef_addr);
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
/// A version-2 header's limit on compact attributes: stored when its flags
|
||||
/// say so, else libhdf5's default of 8.
|
||||
fn max_compact_attrs(img: &Image<'_>, hdr: &Header) -> Result<u16, Error> {
|
||||
|
||||
@@ -326,6 +326,11 @@ impl Header {
|
||||
.map(|(i, _)| i)
|
||||
}
|
||||
|
||||
/// Whether free space in the header can take a body of `len` bytes.
|
||||
pub(crate) fn has_free(&self, len: usize) -> bool {
|
||||
self.best_nil(self.padded(len)).is_some()
|
||||
}
|
||||
|
||||
/// Put a message into slot `i` (a NIL message, or a message being
|
||||
/// moved away), splitting off the rest as a NIL message.
|
||||
fn place(
|
||||
|
||||
Reference in New Issue
Block a user