Skip to content

Commit a7a2ec2

Browse files
authored
Merge pull request #1562 from lesserwhirls/gh-1556
Add strict mode for netCDF4 IOSP
2 parents 6b36b52 + b926295 commit a7a2ec2

4 files changed

Lines changed: 91 additions & 19 deletions

File tree

cdm/core/src/main/java/ucar/nc2/util/xml/RuntimeConfigParser.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata
2+
* Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata
33
* See LICENSE for license information.
44
*/
55

@@ -282,20 +282,27 @@ public static void read(org.jdom2.Element root, StringBuilder errlog) {
282282
String name = elem.getChildText("libraryName");
283283
errlog.append(
284284
String.format("Netcdf4Clibrary from NJ22CONFIG: libraryPath = '%s', libraryName = '%s' \n", path, name));
285+
286+
Element useForReadingElm = elem.getChild("useForReading");
287+
boolean useForReading = Boolean.parseBoolean(useForReadingElm.getText());
288+
errlog.append(String.format(" useForReading = '%s' \n", useForReading));
289+
290+
boolean strict = Boolean.parseBoolean(useForReadingElm.getAttributeValue("strict"));
291+
errlog.append(String.format(" strictReader = '%s' \n", strict));
292+
285293
if (path != null && name != null) {
286294
// reflection is used to decouple optional jars
287295
try {
288296
Class<?> netcdfClibraryClass =
289297
RuntimeConfigParser.class.getClassLoader().loadClass(netcdfClibraryClassName);
290-
Method method = netcdfClibraryClass.getMethod("setLibraryNameAndPath", String.class, String.class);
291-
method.invoke(null, path, name); // static method has null for object
298+
Method method =
299+
netcdfClibraryClass.getMethod("setLibraryNameAndPath", String.class, String.class, boolean.class);
300+
method.invoke(null, path, name, strict); // static method has null for object
292301
} catch (Throwable e) {
293302
errlog.append(netcdfClibraryClassName + " is not on classpath\n");
294303
}
295304
}
296305

297-
boolean useForReading = Boolean.parseBoolean(elem.getChildText("useForReading"));
298-
errlog.append(String.format(" useForReading = '%s' \n", useForReading));
299306
if (useForReading) {
300307
try {
301308
// Registers Nc4Iosp in front of all the other IOSPs already registered in NetcdfFile.<clinit>().

docs/src/site/pages/netcdfJava_tutorial/runtime/runtimeloading.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Runtime loading
3-
last_updated: 2025-08-15
3+
last_updated: 2026-06-16
44
sidebar: netcdfJavaTutorial_sidebar
55
permalink: runtime_loading.html
66
toc: false
@@ -122,7 +122,7 @@ The configuration file looks like this:
122122
<Netcdf4Clibrary>{% raw %}{% annotation 9 %}{% endraw %}
123123
<libraryPath>/usr/local/lib</libraryPath>
124124
<libraryName>netcdf</libraryName>
125-
<useForReading>false</useForReading>
125+
<useForReading strict="false">false</useForReading>
126126
</Netcdf4Clibrary>
127127
</runtimeConfig>
128128
{% endhighlight_with_annotations %}
@@ -138,8 +138,10 @@ The configuration file looks like this:
138138
* {% annotation 9 %} Configure how the [NetCDF-4 C library](netcdf4_c_library.html) is discovered and used.
139139
* `libraryPath`: The directory in which the native library is installed.
140140
* `libraryName`: The name of the native library. This will be used to locate the proper `.DLL`, `.SO`, or `.DYLIB` file within the `libraryPath` directory.
141-
* `useForReading`: By default, the native library is only used for writing NetCDF-4 files; a pure-Java layer is responsible for reading them.
142-
However, if this property is set to `true`, then it will be used for reading NetCDF-4 (and HDF5) files as well.
141+
* `useForReading`: By default, the native library is only used for writing NetCDF-4 files; a pure-Java layer is responsible for reading them.
142+
However, if this property is set to `true`, then the native library will be used for reading.
143+
When enabled, all HDF5 files will be read by the native netCDF-C library.
144+
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).
143145

144146
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
145147
`ucar.nc2.util.xml.RuntimeConfigParser`, as in the following examples:

netcdf4/src/main/java/ucar/nc2/ffi/netcdf/NetcdfClibrary.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998-2021 John Caron and University Corporation for Atmospheric Research/Unidata
2+
* Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata
33
* See LICENSE for license information.
44
*/
55

@@ -32,6 +32,8 @@ public class NetcdfClibrary {
3232
// Track if already tested for library presence.
3333
private static Boolean isClibraryPresent;
3434

35+
private static boolean strictRead = false;
36+
3537
/**
3638
* Set the path and name of the netcdf c library.
3739
* <p>
@@ -45,7 +47,25 @@ public class NetcdfClibrary {
4547
* @param lib_name library name, may be null. If null, will use "netcdf".
4648
*/
4749
public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable String lib_name) {
50+
setLibraryNameAndPath(jna_path, lib_name, false);
51+
}
4852

53+
/**
54+
* Set the path and name of the netcdf c library.
55+
* <p>
56+
* Must be called prior to calling {@link #isLibraryPresent() isLibraryPresent}
57+
* or {@link #getForeignFunctionInterface() getForeignFunctionInterface}, as
58+
* the C library can only be successfully loaded once.
59+
*
60+
* @param jna_path path to shared libraries, may be null. If null, will look for system property
61+
* "jna.library.path", then environment variable "JNA_PATH". If set, will set
62+
* the environment variable "JNA_PATH".
63+
* @param lib_name library name, may be null. If null, will use "netcdf".
64+
* @param strict if true, only attempt to read files through the netCDF-C library that are likely
65+
* netCDF-4 files, not just any HDF5 file.
66+
*
67+
*/
68+
public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable String lib_name, boolean strict) {
4969
if (nc4 != null) {
5070
log.warn("netCDF-C library already set, ignoring.");
5171
return;
@@ -72,6 +92,7 @@ public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable St
7292

7393
libName = lib_name;
7494
jnaPath = jna_path;
95+
strictRead = strict;
7596

7697
if ((isClibraryPresent == null || !isClibraryPresent) && jnaPath != null) {
7798
// call load to retry loading, but this time with jnaPath set
@@ -145,6 +166,10 @@ public static synchronized void shutdown() {
145166
}
146167
}
147168

169+
public static boolean isStrictRead() {
170+
return strictRead;
171+
}
172+
148173
private static Nc4prototypes load() {
149174
if (nc4 == null) {
150175
if (jnaPath == null) {

netcdf4/src/main/java/ucar/nc2/jni/netcdf/Nc4Iosp.java

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998-2025 University Corporation for Atmospheric Research/Unidata
2+
* Copyright (c) 1998-2026 University Corporation for Atmospheric Research/Unidata
33
* See LICENSE for license information.
44
*/
55

@@ -132,6 +132,36 @@ public static void useHdfEos(boolean val) {
132132

133133
public static void setDebugFlags(DebugFlags flags) {}
134134

135+
private static boolean isLikelyNetcdf4(RandomAccessFile raf) {
136+
boolean likelyNc4 = false;
137+
// Check if likely netCDF-4 file (to prevent reading vanilla HDF5 through the netCDF-C library)
138+
// See https://github.com/Unidata/netcdf-c/discussions/3085
139+
// Note: netCDF-4 files written outside the netCDF-C library may not pass these checks.
140+
IntByReference test_ncid = new IntByReference();
141+
int ret = nc4.nc_open(raf.getLocation(), NC_NOWRITE, test_ncid);
142+
SizeTByReference lenp = new SizeTByReference();
143+
// Does _NCProperties global attribute exist?
144+
ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, "_NCCCCC", lenp);
145+
// ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, CDM.NCPROPERTIES, lenp);
146+
likelyNc4 = ret == NC_NOERR;
147+
if (!likelyNc4) {
148+
// Does _IsNetcdf4 global attribute exist, and is its value not 0?
149+
// note: newer versions of netCDF-C include the _NCProperties check as part of determining
150+
// the value of this attribute, but not all, so we need both checks.
151+
lenp = new SizeTByReference();
152+
ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, CDM.ISNETCDF4, lenp);
153+
if (ret == NC_NOERR && lenp.getValue().longValue() == 1) {
154+
int[] isnc4 = new int[1];
155+
ret = nc4.nc_get_att_int(test_ncid.getValue(), NC_GLOBAL, CDM.ISNETCDF4, isnc4);
156+
likelyNc4 = ret == NC_NOERR && isnc4[0] != 0;
157+
}
158+
}
159+
if (!likelyNc4) {
160+
log.debug("May not be a netCDF-4 file: {}; falling back to HDF5 IOSP.", raf.getLocation());
161+
}
162+
return likelyNc4;
163+
}
164+
135165
//////////////////////////////////////////////////
136166
// Instance Variables
137167

@@ -177,24 +207,32 @@ public void setChunker(Nc4Chunking chunker) {
177207
@Override
178208
public boolean isValidFile(RandomAccessFile raf) throws IOException {
179209
int format = NCheader.checkFileType(raf);
180-
boolean valid = false;
210+
boolean validCheck1 = false;
181211
switch (format) {
182212
case NCheader.NC_FORMAT_NETCDF4:
183213
case NCheader.NC_FORMAT_64BIT_DATA:
184-
valid = true;
214+
validCheck1 = true;
185215
break;
186216
default:
187217
break;// everything else is invalid
188218
}
189-
if (valid) {
190-
if (isClibraryPresent()) {
191-
return true;
219+
220+
boolean validCheck2 = false;
221+
if (!validCheck1) {
222+
log.debug("File cannot be opened by Nc4Iosp: {}", raf.getLocation());
223+
} else if (!isClibraryPresent()) {
224+
log.debug("File appears to be valid but netCDF-C isn't installed: {}", raf.getLocation());
225+
} else {
226+
// file appears to be valid and netCDF-c is present
227+
if (NetcdfClibrary.isStrictRead()) {
228+
// strictly limit to reading files that are very likely netcdf4
229+
validCheck2 = isLikelyNetcdf4(raf);
192230
} else {
193-
log.debug("File is valid but the NetCDF-4 native library isn't installed: {}", raf.getLocation());
231+
// try to read all HDF5 files through the netCDF-C library
232+
validCheck2 = true;
194233
}
195234
}
196-
197-
return false;
235+
return validCheck2;
198236
}
199237

200238
// 2016-06-06 note: Once netcdf-c v4.4.1 is released, we should be able to return much better information from

0 commit comments

Comments
 (0)