fix(io): refuse truncated files in the VOL, async and MPI readers

The truncated-file check and the end-of-file clamp reached File,
LazyFile and MmapFile but not clawhdf5-io's readers, which still opened
truncated files and read past the recorded end of file. NativeVol
(open, and read_dataset for from_bytes), AsyncHDF5File::from_bytes and
MpiVol's collective read now view the file through the new
vol::hdf5_view: from the superblock to Superblock::data_end, refusing a
file shorter than that.

MpiVol's read is compiled only with the mpi-io feature, which needs an
MPI installation; it was not built here. The edit there only swaps its
two-line superblock setup for hdf5_view.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 01:30:56 -05:00
co-authored by Claude Opus 5.5
parent dd40bea467
commit 3938f7f8a2
4 changed files with 88 additions and 12 deletions
+27
View File
@@ -281,8 +281,13 @@ impl AsyncHDF5File {
// HDF5 addresses are relative to the superblock: drop any user block
// so they index `data` directly.
let user_block = find_signature(&data)?;
let whole_len = data.len() as u64;
data.drain(..user_block);
let superblock = Superblock::parse(&data, 0)?;
// Refuse a truncated file, and keep nothing past the end of file the
// superblock records, as libhdf5 does.
let end = superblock.data_end(user_block as u64, whole_len)?;
data.truncate(end as usize);
Ok(Self { data, superblock })
}
@@ -605,6 +610,28 @@ mod tests {
tokio::fs::remove_file(&path).await.ok();
}
/// As libhdf5 does: a truncated file is refused, and bytes past the end
/// of file the superblock records are dropped.
#[tokio::test]
async fn async_refuses_truncated_files_and_drops_trailing_bytes() {
let bytes = make_test_hdf5_f64("v", &[1.0, 2.0]);
let truncated = bytes[..bytes.len() - 8].to_vec();
let err = AsyncHDF5File::from_bytes(truncated).err().unwrap();
assert!(
matches!(
err,
AsyncHDF5Error::Format(FormatError::TruncatedFile { .. })
),
"{err}"
);
let mut appended = bytes.clone();
appended.extend_from_slice(&[0xAB; 64]);
let file = AsyncHDF5File::from_bytes(appended).unwrap();
assert_eq!(file.as_bytes().len(), bytes.len());
assert_eq!(file.read_f64("v").await.unwrap(), vec![1.0, 2.0]);
}
#[tokio::test]
async fn async_error_display() {
let io_err = AsyncHDF5Error::Io(io::Error::new(io::ErrorKind::NotFound, "gone"));