Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libdispatch/dinfermodel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,7 @@ NC_interpret_magic_number(char* magic, NCmodel* model)
{
int formatx = (i <= 1) ? (NC_FORMATX_UDF0 + i) : (NC_FORMATX_UDF2 + i - 2);
model->impl = formatx;
model->format = NC_FORMAT_NETCDF4;
status = NC_NOERR;
break;
}
Expand Down
17 changes: 17 additions & 0 deletions ncdump/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ endif()
# Add this test by hand, as it is also called from a script.
# Editing the script would break autotools compatibility.
build_bin_test_no_prefix(tst_special_atts)
if(NETCDF_ENABLE_PLUGINS)
add_library(tst_udf_format_plugin MODULE tst_udf_format_plugin.c)
target_link_libraries(tst_udf_format_plugin netcdf ${ALL_TLL_LIBS})
set_target_properties(tst_udf_format_plugin PROPERTIES PREFIX "")
if(WIN32)
set_target_properties(tst_udf_format_plugin PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}
LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}
RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR})
endif()
endif()
endif(USE_HDF5)

# Base tests
Expand Down Expand Up @@ -262,6 +276,9 @@ endif()

if(USE_HDF5)
add_sh_test(ncdump tst_formatx4)
if(NETCDF_ENABLE_PLUGINS)
add_sh_test(ncdump tst_udf_format)
endif()
add_sh_test(ncdump_sh tst_fillbug)
add_sh_test(ncdump_shell tst_h_scalar)
add_sh_test(ncdump tst_mud)
Expand Down
21 changes: 17 additions & 4 deletions ncdump/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,26 @@ tst_vlen_data tst_comp tst_comp2 tst_nans tst_special_atts \
tst_unicode tst_fillbug tst_compress tst_chunking tst_h_scalar \
tst_enum_undef

if NETCDF_ENABLE_PLUGINS
check_LTLIBRARIES = tst_udf_format_plugin.la
tst_udf_format_plugin_la_SOURCES = tst_udf_format_plugin.c
tst_udf_format_plugin_la_LDFLAGS = -module -avoid-version -shared -no-undefined -rpath /tmp
tst_udf_format_plugin_la_LIBADD = ${top_builddir}/liblib/libnetcdf.la
endif # NETCDF_ENABLE_PLUGINS

check_PROGRAMS += tst_vlen_demo

# Tests for netCDF-4 behavior.
TESTS += tst_fileinfo.sh tst_hdf5_offset.sh tst_inttags4.sh \
tst_netcdf4.sh tst_fillbug.sh \
tst_grp_spec.sh tst_mud.sh tst_h_scalar.sh tst_formatx4.sh \
tst_netcdf4.sh tst_fillbug.sh tst_grp_spec.sh tst_mud.sh \
tst_h_scalar.sh tst_formatx4.sh \
run_utf8_nc4_tests.sh run_ncgen_nc4_tests.sh \
tst_ncgen4.sh test_scope.sh

if NETCDF_ENABLE_PLUGINS
TESTS += tst_udf_format.sh
endif # NETCDF_ENABLE_PLUGINS

# Record interscript dependencies so parallel builds work.
tst_nccopy4.log: run_ncgen_tests.log tst_output.log tst_ncgen4.log \
tst_fillbug.log tst_netcdf4_4.log tst_h_scalar.log tst_netcdf4.log
Expand Down Expand Up @@ -220,7 +231,8 @@ ref_rcapi.txt ref_tst_enum_undef.cdl tst_calendars_nc4.cdl ref_times_nc4.cdl ref
EXTRA_DIST += L512.bin

EXTRA_DIST += tst_ctests.sh ref_ctest_small_3.c ref_ctest_small_4.c \
ref_ctest_special_atts_4.c test_ncdump.sh
ref_ctest_special_atts_4.c test_ncdump.sh tst_udf_format.sh \
tst_udf_format_plugin.c

EXTRA_DIST += testpathcvt.sh ref_pathcvt.txt

Expand Down Expand Up @@ -255,7 +267,8 @@ tst_roman_szip_unlim.cdl tst_perdimpspecs.nc tmppds.* \
keyword1.nc keyword2.nc keyword3.nc keyword4.nc \
tmp_keyword1.cdl tmp_keyword2.cdl tmp_keyword3.cdl tmp_keyword4.cdl \
type_*.nc copy_type_*.cdl \
scope_*.nc copy_scope_*.cdl keyword5.nc tst_enum_undef.cdl tst_times_nc4.cdl
scope_*.nc copy_scope_*.cdl keyword5.nc tst_enum_undef.cdl tst_times_nc4.cdl \
tst_udf_format.ncrc tst_udf_format.udf tst_udf_format.out tst_udf_format.err

# Remove directories
clean-local:
Expand Down
14 changes: 11 additions & 3 deletions ncdump/ncdump.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ kind_string(int kind)
case NC_FORMAT_NETCDF4_CLASSIC:
return "netCDF-4 classic model";
default:
error("unrecognized file format: %d", kind);
if(kind == NC_FORMATX_UDF0 || kind == NC_FORMATX_UDF1
|| (kind >= NC_FORMATX_UDF2 && kind <= NC_FORMATX_UDF9))
return "user-defined format";
error("unrecognized file format: %d", kind);
return "unrecognized";
}
}
Expand Down Expand Up @@ -315,8 +318,13 @@ kind_string_extended(int kind, int mode)
snprintf(text,sizeof(text),"%s mode=%08x", "unknown",mode);
break;
default:
error("unrecognized extended format: %d",kind);
snprintf(text,sizeof(text),"%s mode=%08x", "unrecognized",mode);
if(kind == NC_FORMATX_UDF0 || kind == NC_FORMATX_UDF1
|| (kind >= NC_FORMATX_UDF2 && kind <= NC_FORMATX_UDF9))
snprintf(text,sizeof(text),"%s mode=%08x", "user-defined format",mode);
else {
error("unrecognized extended format: %d",kind);
snprintf(text,sizeof(text),"%s mode=%08x", "unrecognized",mode);
}
break;
}
return text;
Expand Down
75 changes: 75 additions & 0 deletions ncdump/tst_udf_format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/sh
#
# This is part of the netCDF package. Copyright 2026 University
# Corporation for Atmospheric Research/Unidata. See COPYRIGHT file
# for conditions of use.
#
# Test for netCDF-C issue #3411. ncdump could open a UDF file and
# print its metadata, but then rejected the UDF extended format code
# while generating the virtual _Format attribute or processing -K.
#
# The test autoloads tst_udf_format_plugin through an RC file and opens
# a fixture recognized by its UDF magic number. It verifies that -hs
# prints the generic user-defined _Format value and -K prints the
# generic extended format name with its mode.
#
# Author: Edward Hartnett, 2026-07-21

if test "x$srcdir" = x ; then srcdir=`pwd`; fi
. ../test_common.sh

for directory in . .libs; do
for extension in so dylib dll; do
if test -f $directory/tst_udf_format_plugin.$extension; then
plugin=`pwd`/$directory/tst_udf_format_plugin.$extension
break 2
fi
done
done

if test -z "$plugin" || test ! -f "$plugin"; then
echo "*** Fail: UDF format test plugin was not built"
echo "Working directory: `pwd`"
echo "Plugin candidates:"
find . -maxdepth 2 -type f -name 'tst_udf_format_plugin*' -print
echo "Current directory contents:"
ls -la
if test -d .libs; then
echo ".libs contents:"
ls -la .libs
fi
exit 1
fi

cat > tst_udf_format.ncrc <<EOF
NETCDF.UDF0.LIBRARY=$plugin
NETCDF.UDF0.INIT=tst_udf_format_init
NETCDF.UDF0.MAGIC=TUSF
EOF
printf TUSF00000 > tst_udf_format.udf

if ! NCRCENV_RC=`pwd`/tst_udf_format.ncrc ${NCDUMP} -hs tst_udf_format.udf > tst_udf_format.out 2> tst_udf_format.err; then
cat tst_udf_format.err
rm -f tst_udf_format.ncrc tst_udf_format.udf tst_udf_format.out tst_udf_format.err
exit 1
fi

if ! grep ':_Format = "user-defined format" ;' tst_udf_format.out; then
cat tst_udf_format.out
rm -f tst_udf_format.ncrc tst_udf_format.udf tst_udf_format.out tst_udf_format.err
exit 1
fi

if ! NCRCENV_RC=`pwd`/tst_udf_format.ncrc ${NCDUMP} -K tst_udf_format.udf > tst_udf_format.out 2> tst_udf_format.err; then
cat tst_udf_format.err
rm -f tst_udf_format.ncrc tst_udf_format.udf tst_udf_format.out tst_udf_format.err
exit 1
fi

if ! grep 'user-defined format mode=' tst_udf_format.out; then
cat tst_udf_format.out
rm -f tst_udf_format.ncrc tst_udf_format.udf tst_udf_format.out tst_udf_format.err
exit 1
fi

rm -f tst_udf_format.ncrc tst_udf_format.udf tst_udf_format.out tst_udf_format.err
169 changes: 169 additions & 0 deletions ncdump/tst_udf_format_plugin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/* This is part of the netCDF package. Copyright 2026 University
Corporation for Atmospheric Research/Unidata. See COPYRIGHT file
for conditions of use.

Minimal self-registering UDF0 plugin for tst_udf_format.sh. It
provides an empty root dataset and reports NC_FORMATX_UDF0 through
both format inquiry functions.

This plugin reproduces netCDF-C issue #3411 without depending on
an external UDF implementation. Before the fix, ncdump could read
the UDF dataset but rejected extended format code 8 while generating
its virtual _Format attribute or processing -K output.

Author: Edward Hartnett, 2026-07-21
*/

#include "config.h"
#include <string.h>
#include "netcdf.h"
#include "netcdf_dispatch.h"

static NC_Dispatch dispatcher;

static int
plugin_open(const char *path, int mode, int basepe, size_t *chunksizehintp,
void *parameters, const NC_Dispatch *table, int ncid)
{
return NC_NOERR;
}

static int
plugin_close(int ncid, void *dispatchdata)
{
return NC_NOERR;
}

static int
plugin_inq_format(int ncid, int *formatp)
{
if (formatp)
*formatp = NC_FORMATX_UDF0;
return NC_NOERR;
}

static int
plugin_inq_format_extended(int ncid, int *formatp, int *modep)
{
if (formatp)
*formatp = NC_FORMATX_UDF0;
if (modep)
*modep = NC_NOWRITE;
return NC_NOERR;
}

static int
plugin_inq(int ncid, int *ndimsp, int *nvarsp, int *nattsp, int *unlimdimidp)
{
if (ndimsp)
*ndimsp = 0;
if (nvarsp)
*nvarsp = 0;
if (nattsp)
*nattsp = 0;
if (unlimdimidp)
*unlimdimidp = -1;
return NC_NOERR;
}

static int
plugin_inq_att(int ncid, int varid, const char *name, nc_type *xtypep,
size_t *lenp)
{
return NC_ENOTATT;
}

static int
plugin_inq_typeids(int ncid, int *ntypes, int *typeids)
{
if (ntypes)
*ntypes = 0;
return NC_NOERR;
}

static int
plugin_inq_grps(int ncid, int *numgrps, int *ncids)
{
if (numgrps)
*numgrps = 0;
return NC_NOERR;
}

static int
plugin_inq_grp_parent(int ncid, int *parent_ncid)
{
return NC_ENOGRP;
}

static int
plugin_inq_ncid(int ncid, const char *name, int *grp_ncid)
{
if (name != NULL)
return NC_ENOGRP;
if (grp_ncid)
*grp_ncid = ncid;
return NC_NOERR;
}

static int
plugin_inq_dimids(int ncid, int *ndims, int *dimids, int include_parents)
{
if (ndims)
*ndims = 0;
return NC_NOERR;
}

static int
plugin_inq_unlimdims(int ncid, int *nunlimdimsp, int *unlimdimidsp)
{
if (nunlimdimsp)
*nunlimdimsp = 0;
return NC_NOERR;
}

#if defined(_MSC_VER)
#define PLUGIN_EXPORT __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#define PLUGIN_EXPORT __attribute__((visibility("default")))
#else
#define PLUGIN_EXPORT
#endif

PLUGIN_EXPORT NC_Dispatch *
tst_udf_format_init(void)
{
memset(&dispatcher, 0, sizeof(dispatcher));
dispatcher.model = NC_FORMATX_UDF0;
dispatcher.dispatch_version = NC_DISPATCH_VERSION;
dispatcher.create = NC_RO_create;
dispatcher.open = plugin_open;
dispatcher.redef = NC_RO_redef;
dispatcher._enddef = NC_RO__enddef;
dispatcher.sync = NC_RO_sync;
dispatcher.close = plugin_close;
dispatcher.set_fill = NC_RO_set_fill;
dispatcher.inq_format = plugin_inq_format;
dispatcher.inq_format_extended = plugin_inq_format_extended;
dispatcher.inq = plugin_inq;
dispatcher.inq_att = plugin_inq_att;
dispatcher.inq_grps = plugin_inq_grps;
dispatcher.inq_ncid = plugin_inq_ncid;
dispatcher.inq_grp_parent = plugin_inq_grp_parent;
dispatcher.inq_dimids = plugin_inq_dimids;
dispatcher.inq_unlimdims = plugin_inq_unlimdims;
dispatcher.inq_typeids = plugin_inq_typeids;
dispatcher.rename_att = NC_RO_rename_att;
dispatcher.del_att = NC_RO_del_att;
dispatcher.put_att = NC_RO_put_att;
dispatcher.def_dim = NC_RO_def_dim;
dispatcher.rename_dim = NC_RO_rename_dim;
dispatcher.def_var = NC_RO_def_var;
dispatcher.rename_var = NC_RO_rename_var;
dispatcher.put_vara = NC_RO_put_vara;
dispatcher.var_par_access = NC_NOTNC4_var_par_access;
dispatcher.def_var_fill = NC_RO_def_var_fill;
dispatcher.inq_var_filter_ids = NC_NOOP_inq_var_filter_ids;
dispatcher.inq_var_filter_info = NC_NOOP_inq_var_filter_info;
dispatcher.inq_filter_avail = NC_NOOP_inq_filter_avail;
return &dispatcher;
}
1 change: 1 addition & 0 deletions unit_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ ENDIF()

SET(UNIT_TESTS test_ncuri)
add_bin_test(unit_test test_ncuri)
add_bin_test(unit_test tst_udf_infermodel)
SET(UNIT_TESTS test_dauth)
add_bin_test(unit_test test_dauth)

Expand Down
6 changes: 3 additions & 3 deletions unit_test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ noinst_PROGRAMS =
noinst_PROGRAMS += ncpluginpath
ncpluginpath_SOURCES = ncpluginpath.c

check_PROGRAMS += tst_nclist test_ncuri test_pathcvt test_dauth
TESTS += tst_nclist test_ncuri run_pathcvt.sh test_dauth
check_PROGRAMS += tst_nclist test_ncuri test_pathcvt test_dauth tst_udf_infermodel
TESTS += tst_nclist test_ncuri run_pathcvt.sh test_dauth tst_udf_infermodel

# Performance tests
if BUILD_BENCHMARKS
Expand Down Expand Up @@ -75,7 +75,7 @@ EXTRA_DIST += ref_get.txt ref_set.txt
EXTRA_DIST += ref_xget.txt ref_xset.txt
EXTRA_DIST += ref_provparse.txt
EXTRA_DIST += reclaim_tests.baseline
CLEANFILES = reclaim_tests*.txt reclaim_tests.nc reclaim_tests.dmp tmp_*.txt
CLEANFILES = reclaim_tests*.txt reclaim_tests.nc reclaim_tests.dmp tmp_*.txt tst_udf_infermodel.udf

# Remove directories
clean-local:
Expand Down
Loading
Loading