Read HDF5 1.6-era files, user blocks, VDS, dense attributes and large groups #13

Merged
osobh merged 28 commits from fix/p1-read-gaps into main 2026-09-26 09:42:10 +00:00
Showing only changes of commit 10d1029ead - Show all commits
+63 -19
View File
@@ -286,6 +286,29 @@ impl<'a> Ctx<'a> {
Ok(()) Ok(())
} }
/// VDS source files resolve next to the virtual file; like the library,
/// refuse absolute paths and `..`.
fn vds_resolver(
&self,
) -> impl Fn(&str) -> Result<Option<Vec<u8>>, clawhdf5_format::error::FormatError> + use<> {
let base = self.base_dir.clone();
move |name: &str| {
use clawhdf5_format::error::FormatError;
let p = std::path::Path::new(name);
if p.is_absolute()
|| p.components()
.any(|c| matches!(c, std::path::Component::ParentDir))
{
return Err(FormatError::ChunkedReadError(format!("refused {name}")));
}
match std::fs::read(base.join(p)) {
Ok(b) => Ok(Some(b)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(FormatError::ChunkedReadError(err.to_string())),
}
}
}
fn read_dataset(&self, h: &ObjectHeader, rec: &mut Map<String, Value>) -> Result<(), String> { fn read_dataset(&self, h: &ObjectHeader, rec: &mut Map<String, Value>) -> Result<(), String> {
let dtb = self let dtb = self
.payload(h, MessageType::Datatype)? .payload(h, MessageType::Datatype)?
@@ -295,7 +318,27 @@ impl<'a> Ctx<'a> {
let dsb = self let dsb = self
.payload(h, MessageType::Dataspace)? .payload(h, MessageType::Dataspace)?
.ok_or("MissingMessage(Dataspace)")?; .ok_or("MissingMessage(Dataspace)")?;
let ds = Dataspace::parse(&dsb, self.ls).map_err(e)?; let mut ds = Dataspace::parse(&dsb, self.ls).map_err(e)?;
// A virtual dataset's extent can come from its sources (unlimited /
// printf mappings), as h5py reports it, rather than the stored one.
if let Some(lm) = h
.messages
.iter()
.find(|m| m.msg_type == MessageType::DataLayout)
&& let Ok(dl @ DataLayout::Virtual { .. }) =
DataLayout::parse(&lm.data, self.os, self.ls)
{
let resolver = self.vds_resolver();
ds.dimensions = clawhdf5_format::vds::virtual_dataset_extent(
self.data,
&dl,
&ds,
self.os,
self.ls,
Some(&resolver),
)
.map_err(e)?;
}
let (shape, n) = Self::shape(&ds); let (shape, n) = Self::shape(&ds);
rec.insert("shape".into(), shape); rec.insert("shape".into(), shape);
if n.saturating_mul(dt.type_size() as u64) > MAX_BYTES { if n.saturating_mul(dt.type_size() as u64) > MAX_BYTES {
@@ -331,28 +374,26 @@ impl<'a> Ctx<'a> {
); );
} }
let raw = if matches!(dl, DataLayout::Virtual { .. }) { let raw = if matches!(dl, DataLayout::Virtual { .. }) {
let base = self.base_dir.clone(); let resolver = self.vds_resolver();
let resolver = move |name: &str| -> Option<Vec<u8>> { let fill = clawhdf5_format::fill_value::dataset_fill_value_in(
let p = std::path::Path::new(name); self.data,
if p.is_absolute() &h.messages,
|| p.components() self.os,
.any(|c| matches!(c, std::path::Component::ParentDir)) self.ls,
{ )
return None; .map_err(e)?;
} clawhdf5_format::vds::read_virtual_dataset(
std::fs::read(base.join(p)).ok()
};
data_read::read_raw_data_full_with_resolver(
self.data, self.data,
&dl, &dl,
&ds, &ds,
&dt, &dt,
pipeline.as_ref(), fill.as_deref(),
self.os, self.os,
self.ls, self.ls,
Some(&resolver), Some(&resolver),
) )
.map_err(e)? .map_err(e)?
.data
} else { } else {
let cache = clawhdf5_format::chunk_cache::ChunkCache::new(); let cache = clawhdf5_format::chunk_cache::ChunkCache::new();
clawhdf5_format::fill_value::read_full_with_fill::<clawhdf5_format::error::FormatError>( clawhdf5_format::fill_value::read_full_with_fill::<clawhdf5_format::error::FormatError>(
@@ -660,10 +701,13 @@ fn main() {
return; return;
} }
}; };
let sb = guarded(|| { // Every address is relative to the superblock: look at the file from
let off = signature::find_signature(&data).map_err(e)?; // there on (past any user block), as libhdf5 does.
Superblock::parse(&data, off).map_err(e) let hdf5: &[u8] = match signature::find_signature(&data) {
}); Ok(off) => &data[off..],
Err(_) => &data,
};
let sb = guarded(|| Superblock::parse(hdf5, 0).map_err(e));
let sb = match sb { let sb = match sb {
Ok(sb) => sb, Ok(sb) => sb,
Err(msg) => { Err(msg) => {
@@ -674,7 +718,7 @@ fn main() {
}; };
top.insert("superblock_version".into(), json!(sb.version)); top.insert("superblock_version".into(), json!(sb.version));
let ctx = Ctx { let ctx = Ctx {
data: &data, data: hdf5,
os: sb.offset_size, os: sb.offset_size,
ls: sb.length_size, ls: sb.length_size,
base_dir: std::path::Path::new(&path) base_dir: std::path::Path::new(&path)