From 68b5959ca6a395fe522d062b84b9c740174dcbf0 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 14 Feb 2026 20:51:29 +0900 Subject: [PATCH 1/6] fix: try multiple library names when loading HDF5 on Linux On Debian/Ubuntu, the HDF5 shared library is named `libhdf5_serial.so` rather than `libhdf5.so`. The previous code only tried `libhdf5.so`, causing dlopen to fail on these distributions. Now tries multiple candidate names in order: - Linux: `libhdf5.so`, `libhdf5_serial.so` - macOS: `/opt/homebrew/lib/libhdf5.dylib`, `/usr/local/lib/libhdf5.dylib`, `libhdf5.dylib` Co-Authored-By: Claude Opus 4.6 --- hdf5/src/sys/runtime.rs | 67 ++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/hdf5/src/sys/runtime.rs b/hdf5/src/sys/runtime.rs index 8a7a4bd..4c93ed6 100644 --- a/hdf5/src/sys/runtime.rs +++ b/hdf5/src/sys/runtime.rs @@ -844,33 +844,58 @@ fn get_library() -> &'static Library { *LIBRARY.get().expect("HDF5 library not initialized. Call hdf5::sys::init() first.") } +/// Returns the list of default library names to try when loading HDF5. +fn default_library_candidates() -> Vec<&'static str> { + #[cfg(target_os = "macos")] + { + vec!["/opt/homebrew/lib/libhdf5.dylib", "/usr/local/lib/libhdf5.dylib", "libhdf5.dylib"] + } + #[cfg(target_os = "linux")] + { + vec!["libhdf5.so", "libhdf5_serial.so"] + } + #[cfg(target_os = "windows")] + { + vec!["hdf5.dll"] + } + #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] + { + vec!["libhdf5.so"] + } +} + /// Initialize the HDF5 library by loading it from the specified path. pub fn init(path: Option<&str>) -> Result<(), String> { if LIBRARY.get().is_some() { return Ok(()); } - let lib_path = path.map(|s| s.to_string()).unwrap_or_else(|| { - #[cfg(target_os = "macos")] - { - "/opt/homebrew/lib/libhdf5.dylib".to_string() - } - #[cfg(target_os = "linux")] - { - "libhdf5.so".to_string() - } - #[cfg(target_os = "windows")] - { - "hdf5.dll".to_string() - } - #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] - { - "libhdf5.so".to_string() + let (library, resolved_path) = if let Some(p) = path { + let lib = unsafe { Library::new(p) } + .map_err(|e| format!("Failed to load HDF5 library from {}: {}", p, e))?; + (lib, p.to_string()) + } else { + let candidates = default_library_candidates(); + let mut last_err = String::new(); + let mut loaded = None; + for name in &candidates { + match unsafe { Library::new(*name) } { + Ok(lib) => { + loaded = Some((lib, name.to_string())); + break; + } + Err(e) => { + last_err = format!("{}", e); + } + } } - }); - - let library = unsafe { Library::new(&lib_path) } - .map_err(|e| format!("Failed to load HDF5 library from {}: {}", lib_path, e))?; + loaded.ok_or_else(|| { + format!( + "Failed to load HDF5 library. Tried: {:?}. Last error: {}", + candidates, last_err + ) + })? + }; // Leak the library handle to prevent dlclose() on exit. // HDF5 has problematic cleanup routines that can cause "infinite loop closing library" @@ -880,7 +905,7 @@ pub fn init(path: Option<&str>) -> Result<(), String> { let library = Box::leak(Box::new(library)); LIBRARY.set(library).map_err(|_| "Library already initialized".to_string())?; - LIBRARY_PATH.set(lib_path).map_err(|_| "Library path already set".to_string())?; + LIBRARY_PATH.set(resolved_path).map_err(|_| "Library path already set".to_string())?; // Initialize HDF5 unsafe { From 7b526c744ad665bb0f7c273a79fa3392a886d333 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 14 Feb 2026 21:16:52 +0900 Subject: [PATCH 2/6] feat: convert H5Dget_num_chunks and H5Dget_chunk_info to optional loading These two functions are only available in HDF5 1.10.5+. By using optional symbol loading (returning Option instead of panicking), HDF5 1.10.4 can be supported with graceful degradation for chunk queries. Updated the call sites in chunks.rs to handle the Option return type using and_then instead of directly passing to h5check. Co-Authored-By: Claude Opus 4.6 --- hdf5/src/hl/chunks.rs | 9 +++---- hdf5/src/sys/runtime.rs | 56 +++++++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/hdf5/src/hl/chunks.rs b/hdf5/src/hl/chunks.rs index 61d2970..35920dc 100644 --- a/hdf5/src/hl/chunks.rs +++ b/hdf5/src/hl/chunks.rs @@ -38,7 +38,7 @@ pub(crate) fn chunk_info(ds: &Dataset, index: usize) -> Option { } h5lock!(ds.space().map_or(None, |s| { let mut chunk_info = ChunkInfo::new(ds.ndim()); - h5check(H5Dget_chunk_info( + H5Dget_chunk_info( ds.id(), s.id(), index as _, @@ -46,9 +46,8 @@ pub(crate) fn chunk_info(ds: &Dataset, index: usize) -> Option { &mut chunk_info.filter_mask, &mut chunk_info.addr, &mut chunk_info.size, - )) - .map(|_| chunk_info) - .ok() + ) + .and_then(|ret| h5check(ret).map(|_| chunk_info).ok()) })) } @@ -58,7 +57,7 @@ pub(crate) fn get_num_chunks(ds: &Dataset) -> Option { } h5lock!(ds.space().map_or(None, |s| { let mut n: hsize_t = 0; - h5check(H5Dget_num_chunks(ds.id(), s.id(), &mut n)).map(|_| n as _).ok() + H5Dget_num_chunks(ds.id(), s.id(), &mut n).and_then(|ret| h5check(ret).map(|_| n as _).ok()) })) } diff --git a/hdf5/src/sys/runtime.rs b/hdf5/src/sys/runtime.rs index 4c93ed6..0ff6ed9 100644 --- a/hdf5/src/sys/runtime.rs +++ b/hdf5/src/sys/runtime.rs @@ -1128,22 +1128,46 @@ hdf5_function!( hdf5_function!(H5Dset_extent, fn(dset_id: hid_t, size: *const hsize_t) -> herr_t); hdf5_function!(H5Dflush, fn(dset_id: hid_t) -> herr_t); hdf5_function!(H5Drefresh, fn(dset_id: hid_t) -> herr_t); -hdf5_function!( - H5Dget_num_chunks, - fn(dset_id: hid_t, fspace_id: hid_t, nchunks: *mut hsize_t) -> herr_t -); -hdf5_function!( - H5Dget_chunk_info, - fn( - dset_id: hid_t, - fspace_id: hid_t, - chk_idx: hsize_t, - offset: *mut hsize_t, - filter_mask: *mut c_uint, - addr: *mut haddr_t, - size: *mut hsize_t, - ) -> herr_t -); +/// H5Dget_num_chunks - Available in HDF5 1.10.5+ +/// Returns None if the function is not available (HDF5 < 1.10.5) +pub unsafe fn H5Dget_num_chunks( + dset_id: hid_t, + fspace_id: hid_t, + nchunks: *mut hsize_t, +) -> Option { + let lib = get_library(); + let func: Option herr_t>> = + lib.get(b"H5Dget_num_chunks").ok(); + func.map(|f| f(dset_id, fspace_id, nchunks)) +} + +/// H5Dget_chunk_info - Available in HDF5 1.10.5+ +/// Returns None if the function is not available (HDF5 < 1.10.5) +pub unsafe fn H5Dget_chunk_info( + dset_id: hid_t, + fspace_id: hid_t, + chk_idx: hsize_t, + offset: *mut hsize_t, + filter_mask: *mut c_uint, + addr: *mut haddr_t, + size: *mut hsize_t, +) -> Option { + let lib = get_library(); + let func: Option< + Symbol< + unsafe extern "C" fn( + hid_t, + hid_t, + hsize_t, + *mut hsize_t, + *mut c_uint, + *mut haddr_t, + *mut hsize_t, + ) -> herr_t, + >, + > = lib.get(b"H5Dget_chunk_info").ok(); + func.map(|f| f(dset_id, fspace_id, chk_idx, offset, filter_mask, addr, size)) +} hdf5_function!( H5Dcreate_anon, fn(loc_id: hid_t, type_id: hid_t, space_id: hid_t, dcpl_id: hid_t, dapl_id: hid_t) -> hid_t From 046c711ac3480d3d86f00c74c3f1886904afe94e Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 14 Feb 2026 21:22:35 +0900 Subject: [PATCH 3/6] fix: lower minimum HDF5 version to 1.10.4 (#21) Co-Authored-By: Claude Opus 4.6 --- hdf5/src/sys/runtime.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hdf5/src/sys/runtime.rs b/hdf5/src/sys/runtime.rs index 0ff6ed9..c95ada6 100644 --- a/hdf5/src/sys/runtime.rs +++ b/hdf5/src/sys/runtime.rs @@ -918,7 +918,7 @@ pub fn init(path: Option<&str>) -> Result<(), String> { Ok(()) } -/// Check that the HDF5 library version is at least 1.10.5 and store the version. +/// Check that the HDF5 library version is at least 1.10.4 and store the version. /// Returns an error if the version is too old. fn check_hdf5_version() -> Result<(), String> { let mut major: c_uint = 0; @@ -932,10 +932,10 @@ fn check_hdf5_version() -> Result<(), String> { let version = Version { major: major as u8, minor: minor as u8, micro: release as u8 }; let _ = HDF5_RUNTIME_VERSION.set(version); - // Check minimum version: 1.10.5 - if major < 1 || (major == 1 && minor < 10) || (major == 1 && minor == 10 && release < 5) { + // Check minimum version: 1.10.4 + if major < 1 || (major == 1 && minor < 10) || (major == 1 && minor == 10 && release < 4) { return Err(format!( - "HDF5 {}.{}.{} is not supported. Minimum required version is 1.10.5", + "HDF5 {}.{}.{} is not supported. Minimum required version is 1.10.4", major, minor, release )); } From 16a4c9e48a7d1e7e3fee9559b68eccf92515fda3 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 14 Feb 2026 21:22:40 +0900 Subject: [PATCH 4/6] docs: update minimum HDF5 version to 1.10.4 in README Co-Authored-By: Claude Opus 4.6 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4193039..84aeb9b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Forked from [hdf5-metno](https://github.com/metno/hdf5-rust). ## Features - **Runtime loading** - No compile-time HDF5 dependency -- **HDF5 1.10.5+ support** - Compatible with Ubuntu 22.04, HDF5.jl, h5py +- **HDF5 1.10.4+ support** - Compatible with Ubuntu 20.04+, HDF5.jl, h5py - **Thread-safe** - Safe concurrent access to HDF5 Optional features: @@ -44,7 +44,7 @@ fn main() -> hdf5_rt::Result<()> { ## Requirements -- **HDF5 1.10.5 or later** installed on your system +- **HDF5 1.10.4 or later** installed on your system - Rust 1.80.0+ ```bash From 18b72a231c37294ff080a0affe69625e76a439b5 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 14 Feb 2026 21:23:06 +0900 Subject: [PATCH 5/6] ci: add HDF5 1.10.4 to test matrix Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d8e98d..478683a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,11 @@ jobs: fail-fast: false matrix: include: - # Ubuntu with system HDF5 1.10.x (minimum supported version) + # Ubuntu with conda HDF5 1.10.4 (minimum supported version) + - os: ubuntu-22.04 + hdf5: "1.10.4" + hdf5_source: "conda" + # Ubuntu with system HDF5 1.10.x (apt, typically 1.10.7) - os: ubuntu-22.04 hdf5: "1.10" hdf5_source: "apt" From 55fb1f50c4c9f6bc48cf49864c46d27c44541c0a Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 14 Feb 2026 21:24:17 +0900 Subject: [PATCH 6/6] fix: update remaining 1.10.5 references to 1.10.4 in tests and comments Co-Authored-By: Claude Opus 4.6 --- hdf5/src/lib.rs | 2 +- hdf5/src/sys/runtime.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hdf5/src/lib.rs b/hdf5/src/lib.rs index d04a5b8..6f891f9 100644 --- a/hdf5/src/lib.rs +++ b/hdf5/src/lib.rs @@ -219,6 +219,6 @@ pub mod tests { #[test] pub fn test_minimum_library_version() { - assert!(library_version() >= (1, 10, 5)); + assert!(library_version() >= (1, 10, 4)); } } diff --git a/hdf5/src/sys/runtime.rs b/hdf5/src/sys/runtime.rs index c95ada6..5bc762f 100644 --- a/hdf5/src/sys/runtime.rs +++ b/hdf5/src/sys/runtime.rs @@ -912,7 +912,7 @@ pub fn init(path: Option<&str>) -> Result<(), String> { H5open(); } - // Check HDF5 version (require 1.10.5 or later) + // Check HDF5 version (require 1.10.4 or later) check_hdf5_version()?; Ok(()) @@ -2318,10 +2318,10 @@ mod tests { // Version should be accessible after init let version = hdf5_version().expect("Version should be stored after init"); - // Version should be at least 1.10.5 (our minimum) + // Version should be at least 1.10.4 (our minimum) assert!( - hdf5_version_at_least(1, 10, 5), - "Version {}.{}.{} should be at least 1.10.5", + hdf5_version_at_least(1, 10, 4), + "Version {}.{}.{} should be at least 1.10.4", version.major, version.minor, version.micro