diff --git a/cdm/core/src/main/java/ucar/nc2/util/xml/RuntimeConfigParser.java b/cdm/core/src/main/java/ucar/nc2/util/xml/RuntimeConfigParser.java index 93bd444698..b3c8add588 100644 --- a/cdm/core/src/main/java/ucar/nc2/util/xml/RuntimeConfigParser.java +++ b/cdm/core/src/main/java/ucar/nc2/util/xml/RuntimeConfigParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ @@ -282,20 +282,27 @@ public static void read(org.jdom2.Element root, StringBuilder errlog) { String name = elem.getChildText("libraryName"); errlog.append( String.format("Netcdf4Clibrary from NJ22CONFIG: libraryPath = '%s', libraryName = '%s' \n", path, name)); + + Element useForReadingElm = elem.getChild("useForReading"); + boolean useForReading = Boolean.parseBoolean(useForReadingElm.getText()); + errlog.append(String.format(" useForReading = '%s' \n", useForReading)); + + boolean strict = Boolean.parseBoolean(useForReadingElm.getAttributeValue("strict")); + errlog.append(String.format(" strictReader = '%s' \n", strict)); + if (path != null && name != null) { // reflection is used to decouple optional jars try { Class netcdfClibraryClass = RuntimeConfigParser.class.getClassLoader().loadClass(netcdfClibraryClassName); - Method method = netcdfClibraryClass.getMethod("setLibraryNameAndPath", String.class, String.class); - method.invoke(null, path, name); // static method has null for object + Method method = + netcdfClibraryClass.getMethod("setLibraryNameAndPath", String.class, String.class, boolean.class); + method.invoke(null, path, name, strict); // static method has null for object } catch (Throwable e) { errlog.append(netcdfClibraryClassName + " is not on classpath\n"); } } - boolean useForReading = Boolean.parseBoolean(elem.getChildText("useForReading")); - errlog.append(String.format(" useForReading = '%s' \n", useForReading)); if (useForReading) { try { // Registers Nc4Iosp in front of all the other IOSPs already registered in NetcdfFile.(). diff --git a/docs/src/site/pages/netcdfJava_tutorial/runtime/runtimeloading.md b/docs/src/site/pages/netcdfJava_tutorial/runtime/runtimeloading.md index 9eea052ce1..02a643c9cc 100644 --- a/docs/src/site/pages/netcdfJava_tutorial/runtime/runtimeloading.md +++ b/docs/src/site/pages/netcdfJava_tutorial/runtime/runtimeloading.md @@ -1,6 +1,6 @@ --- title: Runtime loading -last_updated: 2025-08-15 +last_updated: 2026-06-16 sidebar: netcdfJavaTutorial_sidebar permalink: runtime_loading.html toc: false @@ -122,7 +122,7 @@ The configuration file looks like this: {% raw %}{% annotation 9 %}{% endraw %} /usr/local/lib netcdf - false + false {% endhighlight_with_annotations %} @@ -138,8 +138,10 @@ The configuration file looks like this: * {% annotation 9 %} Configure how the [NetCDF-4 C library](netcdf4_c_library.html) is discovered and used. * `libraryPath`: The directory in which the native library is installed. * `libraryName`: The name of the native library. This will be used to locate the proper `.DLL`, `.SO`, or `.DYLIB` file within the `libraryPath` directory. - * `useForReading`: By default, the native library is only used for writing NetCDF-4 files; a pure-Java layer is responsible for reading them. - However, if this property is set to `true`, then it will be used for reading NetCDF-4 (and HDF5) files as well. + * `useForReading`: By default, the native library is only used for writing NetCDF-4 files; a pure-Java layer is responsible for reading them. + However, if this property is set to `true`, then the native library will be used for reading. + When enabled, all HDF5 files will be read by the native netCDF-C library. + However, if the strict attribute on the `useForReading` element is set to `true`, then the netCDF-C library will only be used to read in files that are highly likely to be netCDF-4 files (vanilla HDF5 files will still be read by the pure-Java layer). There are several ways pass the Runtime Configuration XML to the CDM library. From your application, you can pass a `java.io.InputStream` (or JDOM element) to `ucar.nc2.util.xml.RuntimeConfigParser`, as in the following examples: diff --git a/netcdf4/src/main/java/ucar/nc2/ffi/netcdf/NetcdfClibrary.java b/netcdf4/src/main/java/ucar/nc2/ffi/netcdf/NetcdfClibrary.java index d721c046d3..0af54a0680 100644 --- a/netcdf4/src/main/java/ucar/nc2/ffi/netcdf/NetcdfClibrary.java +++ b/netcdf4/src/main/java/ucar/nc2/ffi/netcdf/NetcdfClibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2021 John Caron and University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ @@ -32,6 +32,8 @@ public class NetcdfClibrary { // Track if already tested for library presence. private static Boolean isClibraryPresent; + private static boolean strictRead = false; + /** * Set the path and name of the netcdf c library. *

@@ -45,7 +47,25 @@ public class NetcdfClibrary { * @param lib_name library name, may be null. If null, will use "netcdf". */ public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable String lib_name) { + setLibraryNameAndPath(jna_path, lib_name, false); + } + /** + * Set the path and name of the netcdf c library. + *

+ * Must be called prior to calling {@link #isLibraryPresent() isLibraryPresent} + * or {@link #getForeignFunctionInterface() getForeignFunctionInterface}, as + * the C library can only be successfully loaded once. + * + * @param jna_path path to shared libraries, may be null. If null, will look for system property + * "jna.library.path", then environment variable "JNA_PATH". If set, will set + * the environment variable "JNA_PATH". + * @param lib_name library name, may be null. If null, will use "netcdf". + * @param strict if true, only attempt to read files through the netCDF-C library that are likely + * netCDF-4 files, not just any HDF5 file. + * + */ + public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable String lib_name, boolean strict) { if (nc4 != null) { log.warn("netCDF-C library already set, ignoring."); return; @@ -72,6 +92,7 @@ public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable St libName = lib_name; jnaPath = jna_path; + strictRead = strict; if ((isClibraryPresent == null || !isClibraryPresent) && jnaPath != null) { // call load to retry loading, but this time with jnaPath set @@ -145,6 +166,10 @@ public static synchronized void shutdown() { } } + public static boolean isStrictRead() { + return strictRead; + } + private static Nc4prototypes load() { if (nc4 == null) { if (jnaPath == null) { diff --git a/netcdf4/src/main/java/ucar/nc2/jni/netcdf/Nc4Iosp.java b/netcdf4/src/main/java/ucar/nc2/jni/netcdf/Nc4Iosp.java index ed85def1ae..ae124d16d9 100755 --- a/netcdf4/src/main/java/ucar/nc2/jni/netcdf/Nc4Iosp.java +++ b/netcdf4/src/main/java/ucar/nc2/jni/netcdf/Nc4Iosp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2026 University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ @@ -132,6 +132,36 @@ public static void useHdfEos(boolean val) { public static void setDebugFlags(DebugFlags flags) {} + private static boolean isLikelyNetcdf4(RandomAccessFile raf) { + boolean likelyNc4 = false; + // Check if likely netCDF-4 file (to prevent reading vanilla HDF5 through the netCDF-C library) + // See https://github.com/Unidata/netcdf-c/discussions/3085 + // Note: netCDF-4 files written outside the netCDF-C library may not pass these checks. + IntByReference test_ncid = new IntByReference(); + int ret = nc4.nc_open(raf.getLocation(), NC_NOWRITE, test_ncid); + SizeTByReference lenp = new SizeTByReference(); + // Does _NCProperties global attribute exist? + ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, "_NCCCCC", lenp); + // ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, CDM.NCPROPERTIES, lenp); + likelyNc4 = ret == NC_NOERR; + if (!likelyNc4) { + // Does _IsNetcdf4 global attribute exist, and is its value not 0? + // note: newer versions of netCDF-C include the _NCProperties check as part of determining + // the value of this attribute, but not all, so we need both checks. + lenp = new SizeTByReference(); + ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, CDM.ISNETCDF4, lenp); + if (ret == NC_NOERR && lenp.getValue().longValue() == 1) { + int[] isnc4 = new int[1]; + ret = nc4.nc_get_att_int(test_ncid.getValue(), NC_GLOBAL, CDM.ISNETCDF4, isnc4); + likelyNc4 = ret == NC_NOERR && isnc4[0] != 0; + } + } + if (!likelyNc4) { + log.debug("May not be a netCDF-4 file: {}; falling back to HDF5 IOSP.", raf.getLocation()); + } + return likelyNc4; + } + ////////////////////////////////////////////////// // Instance Variables @@ -177,24 +207,32 @@ public void setChunker(Nc4Chunking chunker) { @Override public boolean isValidFile(RandomAccessFile raf) throws IOException { int format = NCheader.checkFileType(raf); - boolean valid = false; + boolean validCheck1 = false; switch (format) { case NCheader.NC_FORMAT_NETCDF4: case NCheader.NC_FORMAT_64BIT_DATA: - valid = true; + validCheck1 = true; break; default: break;// everything else is invalid } - if (valid) { - if (isClibraryPresent()) { - return true; + + boolean validCheck2 = false; + if (!validCheck1) { + log.debug("File cannot be opened by Nc4Iosp: {}", raf.getLocation()); + } else if (!isClibraryPresent()) { + log.debug("File appears to be valid but netCDF-C isn't installed: {}", raf.getLocation()); + } else { + // file appears to be valid and netCDF-c is present + if (NetcdfClibrary.isStrictRead()) { + // strictly limit to reading files that are very likely netcdf4 + validCheck2 = isLikelyNetcdf4(raf); } else { - log.debug("File is valid but the NetCDF-4 native library isn't installed: {}", raf.getLocation()); + // try to read all HDF5 files through the netCDF-C library + validCheck2 = true; } } - - return false; + return validCheck2; } // 2016-06-06 note: Once netcdf-c v4.4.1 is released, we should be able to return much better information from