Repository: Unidata/netcdf-c (c)

Issue:
surprising results writing data to a 2d variable with both dimensions unlimited
reported in https://github.com/Unidata/netcdf4-python/issues/933

Here's a C test program:

```c
#include <stdlib.h>
#include <stdio.h>
#include <netcdf.h>
#define NDIMS 2
#define NY 10
#define NX 5
#define FILE_NAME "test_unlimall.nc"
#define DIM_LEN 2
#define VAR_NAME "var"
#define DIM_NAME1 "y"
#define DIM_NAME2 "x"

void handle_error(int status) {
if (status != NC_NOERR) {
   fprintf(stderr, "%s\n", nc_strerror(status));
   exit(-1);
   }
}

int main()
{
  int dimids[NDIMS];
  int ydata[NY];
  int xdata[NX];
  int data;
  int varid1,varid2,varid,i,ncid,status;
  size_t start[1];
  size_t count[1];
  size_t start2[2];
  size_t count2[2];

  for (int i = 0; i < NX; ++i) {
        xdata[i] = i;
        }
  for (int i = 0; i < NY; ++i) {
        ydata[i] = i;

  /* create variable with two unlimited dimensions */
  status = nc_create(FILE_NAME, NC_NETCDF4, &ncid);
  if(status != NC_NOERR) handle_error(status);
  status = nc_def_dim(ncid, DIM_NAME1, NC_UNLIMITED, &dimids[0]);
  if(status != NC_NOERR) handle_error(status);
  status = nc_def_dim(ncid, DIM_NAME2, NC_UNLIMITED, &dimids[1]);
  if(status != NC_NOERR) handle_error(status);
  status = nc_def_var(ncid, DIM_NAME1, NC_INT, 1, &dimids[0], &varid1);
  if(status != NC_NOERR) handle_error(status);
  status = nc_def_var(ncid, DIM_NAME2, NC_INT, 1, &dimids[1], &varid2);
  if(status != NC_NOERR) handle_error(status);
  status = nc_def_var(ncid, VAR_NAME, NC_INT, NDIMS, dimids, &varid);
  if(status != NC_NOERR) handle_error(status);
  /* write data to coordinate vars to define dimension lengths */
  start[0]=0;
  count[0]=NY;
  status = nc_put_vara_int(ncid, varid1, start, count, ydata);
  if(status != NC_NOERR) handle_error(status);
  count[0]=NX;
  status = nc_put_vara_int(ncid, varid2, start, count, xdata);
  if(status != NC_NOERR) handle_error(status);
  /* write a single data point to the 2d variable */
  start2[0]=2;
  start2[1]=3;
  count2[0]=1;
  count2[1]=1;
  data = 23;
  status = nc_put_vara_int(ncid, varid, start2, count2, &data);
  if(status != NC_NOERR) handle_error(status);
  status = nc_close(ncid);
};
```

The resulting netcdf file looks like this:

```
ncdump test_unlimall.nc
netcdf test_unlimall {
dimensions:
	y = UNLIMITED ; // (10 currently)
	x = UNLIMITED ; // (5 currently)
variables:
	int y(y) ;
	int x(x) ;
	int var(y, x) ;
data:

 y = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ;

 x = 0, 1, 2, 3, 4 ;

 var =
  {_, _, _, _, _},
  {_, _, _, _, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _},
  {_, _, _, 23, _} ;
}
```

Note that the data value is repeated along the 2nd dimension.  The expected result is to just have a single value at the location y=2; x=3


Pull request:
Fix problems with read past end of dataset but within dimension length for vars with multiple unlimited dimensions
Fixes #1413 

This PR also includes PR #1561 which also touches this section of code.



Patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b63da859f..7161e074d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1078,21 +1078,8 @@ IF(ENABLE_ERANGE_FILL) # enable or auto
 ENDIF(ENABLE_ERANGE_FILL)
 # Now ENABLE_ERANGE_FILL is either AUTO, ON, or OFF
 
-# Options to use a more relaxed coordinate argument boundary check
-SET(ENABLE_ZERO_LENGTH_COORD_BOUND AUTO CACHE STRING "AUTO")
-OPTION(ENABLE_ZERO_LENGTH_COORD_BOUND "Enable a more relaxed boundary error check NC_EINVALCOORDS to allow coordinate start argument equal to dimension size when argument count is zero." OFF)
-IF(ENABLE_ZERO_LENGTH_COORD_BOUND) # enable or auto
-   STRING(TOUPPER ${ENABLE_ZERO_LENGTH_COORD_BOUND} ENABLE_ZERO_LENGTH_COORD_BOUND)
-   IF(ENABLE_ZERO_LENGTH_COORD_BOUND AND NOT ENABLE_ZERO_LENGTH_COORD_BOUND STREQUAL "AUTO")
-      # explicitly enabled
-      SET(ENABLE_ZERO_LENGTH_COORD_BOUND ON)
-   ELSE()
-      IF(NOT ENABLE_ZERO_LENGTH_COORD_BOUND STREQUAL "AUTO")
-         SET(ENABLE_ZERO_LENGTH_COORD_BOUND OFF)
-      ENDIF()
-   ENDIF()
-ENDIF(ENABLE_ZERO_LENGTH_COORD_BOUND)
-# Now ENABLE_ZERO_LENGTH_COORD_BOUND is either AUTO, ON, or OFF
+# More relaxed coordinate check is now mandatory for all builds.
+SET(ENABLE_ZERO_LENGTH_COORD_BOUND ON)
 
 # check and conform with PnetCDF settings on ERANGE_FILL and RELAX_COORD_BOUND
 IF(STATUS_PNETCDF)
@@ -1123,17 +1110,9 @@ IF(STATUS_PNETCDF)
   ELSE()
      SET(relax_coord_bound_pnetcdf "OFF")
   ENDIF()
-  IF(ENABLE_ZERO_LENGTH_COORD_BOUND STREQUAL "AUTO") # not set on command line
-     SET(ENABLE_ZERO_LENGTH_COORD_BOUND "${relax_coord_bound_pnetcdf}")
-  ELSE()
-     # user explicitly set this option on command line
-     IF(NOT ENABLE_ZERO_LENGTH_COORD_BOUND STREQUAL "${relax_coord_bound_pnetcdf}")
-        IF(ENABLE_ZERO_LENGTH_COORD_BOUND)
-           MESSAGE(FATAL_ERROR "Enabling relax-coord-bound conflicts with PnetCDF setting")
-        ELSE()
-           MESSAGE(FATAL_ERROR "Disabling relax-coord-bound conflicts with PnetCDF setting")
-        ENDIF()
-     ENDIF()
+  # pnetcdf must have relaxed coord bounds to build with netCDF-4
+  IF(NOT ENABLE_ZERO_LENGTH_COORD_BOUND STREQUAL "${relax_coord_bound_pnetcdf}")
+     MESSAGE(FATAL_ERROR "Pnetcdf must be built with relax-coord-bound enabled")
   ENDIF()
 ENDIF()
 
diff --git a/configure.ac b/configure.ac
index 6fbf6b36e..584b32efd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1193,13 +1193,6 @@ AC_ARG_ENABLE([erange_fill],
    [enable_erange_fill=${enableval}], [enable_erange_fill=auto]
 )
 
-dnl Always build with relaxed coord bound checking, to prevent API
-dnl incompatibilities between different installs of netCDF.
-AC_ARG_ENABLE([zero-length-coord-bound],
-   [AS_HELP_STRING([--disable-zero-length-coord-bound],
-                   [Disable relaxed boundary error check. Deprecated. Using this can cause API incompatibilities. This option will be removed in the next release of netCDF.])])
-test "x$enable_zero_length_coord_bound" = xno || enable_zero_length_coord_bound=yes
-
 # check PnetCDF's settings on enable_erange_fill and relax_coord_bound
 if test "x$enable_pnetcdf" = xyes; then
    UD_CHECK_HEADER_PATH([pnetcdf.h])
@@ -1238,10 +1231,6 @@ else
    fi
 fi
 
-if test "x$enable_zero_length_coord_bound" = xyes; then
-   AC_DEFINE([RELAX_COORD_BOUND], [1], [if true, NC_EINVALCOORDS check is more relaxed])
-fi
-
 if test "x$enable_erange_fill" = xyes ; then
    if test "x$M4FLAGS" = x ; then
       M4FLAGS="-DERANGE_FILL"
@@ -1424,7 +1413,6 @@ AC_SUBST(HAS_PARALLEL4,[$enable_parallel4])
 AC_SUBST(HAS_DISKLESS,[yes])
 AC_SUBST(HAS_MMAP,[$enable_mmap])
 AC_SUBST(HAS_JNA,[$enable_jna])
-AC_SUBST(RELAX_COORD_BOUND,[$enable_zero_length_coord_bound])
 AC_SUBST(HAS_ERANGE_FILL,[$enable_erange_fill])
 AC_SUBST(HAS_BYTERANGE,[$enable_byterange])
 
@@ -1494,7 +1482,6 @@ AX_SET_META([NC_HAS_PARALLEL],[$enable_parallel],[yes])
 AX_SET_META([NC_HAS_PARALLEL4],[$enable_parallel4],[yes])
 AX_SET_META([NC_HAS_CDF5],[$enable_cdf5],[yes])
 AX_SET_META([NC_HAS_ERANGE_FILL], [$enable_erange_fill],[yes])
-AX_SET_META([NC_RELAX_COORD_BOUND], [$enable_zero_length_coord_bound],[yes])
 AX_SET_META([NC_HAS_BYTERANGE],[$enable_byterange],[yes])
 AC_SUBST([NC_DISPATCH_VERSION], [1])
 #####
diff --git a/include/netcdf_meta.h.in b/include/netcdf_meta.h.in
index 50a5d21b9..de44e7286 100644
--- a/include/netcdf_meta.h.in
+++ b/include/netcdf_meta.h.in
@@ -56,7 +56,7 @@
 
 #define NC_HAS_CDF5      @NC_HAS_CDF5@  /*!< CDF5 support. */
 #define NC_HAS_ERANGE_FILL @NC_HAS_ERANGE_FILL@ /*!< ERANGE_FILL Support */
-#define NC_RELAX_COORD_BOUND @NC_RELAX_COORD_BOUND@ /*!< RELAX_COORD_BOUND */
+#define NC_RELAX_COORD_BOUND 1 /*!< RELAX_COORD_BOUND */
 #define NC_DISPATCH_VERSION @NC_DISPATCH_VERSION@ /*!< Dispatch table version */
 
 #endif
diff --git a/libdispatch/dvarget.c b/libdispatch/dvarget.c
index 5b000e041..4eb41aa38 100644
--- a/libdispatch/dvarget.c
+++ b/libdispatch/dvarget.c
@@ -226,11 +226,8 @@ NCDEFAULT_get_vars(int ncid, int varid, const size_t * start,
         /* illegal value checks */
 	dimlen = (i == 0 && isrecvar ? numrecs : varshape[i]);
         /* mystart is unsigned, never < 0 */
-#ifdef RELAX_COORD_BOUND
 	if (mystart[i] > dimlen) return NC_EINVALCOORDS;
-#else
-	if (mystart[i] >= dimlen) return NC_EINVALCOORDS;
-#endif
+
 	if(edges == NULL) {
 	   if(i == 0 && isrecvar)
   	      myedges[i] = numrecs - start[i];
@@ -238,9 +235,9 @@ NCDEFAULT_get_vars(int ncid, int varid, const size_t * start,
 	      myedges[i] = varshape[i] - mystart[i];
 	} else
 	    myedges[i] = edges[i];
-#ifdef RELAX_COORD_BOUND
+
 	if (mystart[i] == dimlen && myedges[i] > 0) return NC_EINVALCOORDS;
-#endif
+
         /* myedges is unsigned, never < 0 */
 	if(mystart[i] + myedges[i] > dimlen)
 	  return NC_EEDGE;
@@ -416,11 +413,7 @@ NCDEFAULT_get_varm(int ncid, int varid, const size_t *start,
 	    ? start[idim]
 	    : 0;
 
-#ifdef RELAX_COORD_BOUND
 	 if (mystart[idim] > dimlen)
-#else
-	 if (mystart[idim] >= dimlen)
-#endif
 	 {
 	    status = NC_EINVALCOORDS;
 	    goto done;
@@ -441,13 +434,11 @@ NCDEFAULT_get_varm(int ncid, int varid, const size_t *start,
 	    myedges[idim] = varshape[idim] - mystart[idim];
 #endif
 
-#ifdef RELAX_COORD_BOUND
 	 if (mystart[idim] == dimlen && myedges[idim] > 0)
 	 {
 	    status = NC_EINVALCOORDS;
 	    goto done;
 	 }
-#endif
 
 	 if (mystart[idim] + myedges[idim] > dimlen)
 	 {
diff --git a/libdispatch/dvarput.c b/libdispatch/dvarput.c
index 7f460b6f9..652e9a11c 100644
--- a/libdispatch/dvarput.c
+++ b/libdispatch/dvarput.c
@@ -221,11 +221,7 @@ NCDEFAULT_put_vars(int ncid, int varid, const size_t * start,
 #endif
         else {
 	  /* mystart is unsigned, will never be < 0 */
-#ifdef RELAX_COORD_BOUND
 	  if (mystart[i] > dimlen) return NC_EINVALCOORDS;
-#else
-          if (mystart[i] >= dimlen) return NC_EINVALCOORDS;
-#endif
        }
 	if(edges == NULL) {
 #if 0
@@ -239,12 +235,12 @@ NCDEFAULT_put_vars(int ncid, int varid, const size_t * start,
 	      myedges[i] = varshape[i] - mystart[i];
 	} else
 	    myedges[i] = edges[i];
-#ifdef RELAX_COORD_BOUND
+
 	if(!is_recdim[i]) {
 	  if (mystart[i] == dimlen && myedges[i] > 0)
               return NC_EINVALCOORDS;
         }
-#endif
+
 	if(!is_recdim[i]) {
           /* myediges is unsigned, will never be < 0 */
 	  if(mystart[i] + myedges[i] > dimlen)
@@ -434,12 +430,8 @@ NCDEFAULT_put_varm(
 
       for (idim = isrecvar; idim <= maxidim; ++idim)
       {
-#ifdef RELAX_COORD_BOUND
 	 if (mystart[idim] > varshape[idim] ||
 	    (mystart[idim] == varshape[idim] && myedges[idim] > 0))
-#else
-         if (mystart[idim] >= varshape[idim])
-#endif
 	 {
 	    status = NC_EINVALCOORDS;
 	    goto done;
diff --git a/libhdf5/hdf5var.c b/libhdf5/hdf5var.c
index 9df9d5079..db9901b7c 100644
--- a/libhdf5/hdf5var.c
+++ b/libhdf5/hdf5var.c
@@ -1586,19 +1586,12 @@ NC4_put_vars(int ncid, int varid, const size_t *startp, const size_t *countp,
             endindex = start[d2]; /* fixup for zero read count */
         if (!dim->unlimited)
         {
-#ifdef RELAX_COORD_BOUND
             /* Allow start to equal dim size if count is zero. */
             if (start[d2] > (hssize_t)fdims[d2] ||
                 (start[d2] == (hssize_t)fdims[d2] && count[d2] > 0))
                 BAIL_QUIET(NC_EINVALCOORDS);
             if (!zero_count && endindex >= fdims[d2])
                 BAIL_QUIET(NC_EEDGE);
-#else
-            if (start[d2] >= (hssize_t)fdims[d2])
-                BAIL_QUIET(NC_EINVALCOORDS);
-            if (endindex >= fdims[d2])
-                BAIL_QUIET(NC_EEDGE);
-#endif
         }
     }
 
@@ -1902,7 +1895,8 @@ NC4_get_vars(int ncid, int varid, const size_t *startp, const size_t *countp,
 #endif
 
     /* Check dimension bounds. Remember that unlimited dimensions can
-     * put data beyond their current length. */
+     * get data beyond the length of the dataset, but within the
+     * lengths of the unlimited dimension(s). */
     for (d2 = 0; d2 < var->ndims; d2++)
     {
         hsize_t endindex = start[d2] + stride[d2] * (count[d2] - 1); /* last index read */
@@ -1920,45 +1914,41 @@ NC4_get_vars(int ncid, int varid, const size_t *startp, const size_t *countp,
                 BAIL(retval);
 
             /* Check for out of bound requests. */
-#ifdef RELAX_COORD_BOUND
             /* Allow start to equal dim size if count is zero. */
             if (start[d2] > (hssize_t)ulen ||
                 (start[d2] == (hssize_t)ulen && count[d2] > 0))
                 BAIL_QUIET(NC_EINVALCOORDS);
-#else
-            if (start[d2] >= (hssize_t)ulen && ulen > 0)
-                BAIL_QUIET(NC_EINVALCOORDS);
-#endif
             if (count[d2] && endindex >= ulen)
                 BAIL_QUIET(NC_EEDGE);
 
-            /* Things get a little tricky here. If we're getting
-               a GET request beyond the end of this var's
-               current length in an unlimited dimension, we'll
-               later need to return the fill value for the
-               variable. */
-            if (start[d2] >= (hssize_t)fdims[d2])
-                fill_value_size[d2] = count[d2];
-            else if (endindex >= fdims[d2])
-                fill_value_size[d2] = count[d2] - ((fdims[d2] - start[d2])/stride[d2]);
+            /* Things get a little tricky here. If we're getting a GET
+               request beyond the end of this var's current length in
+               an unlimited dimension, we'll later need to return the
+               fill value for the variable. */
+            if (!no_read)
+            {
+                if (start[d2] >= (hssize_t)fdims[d2])
+                    fill_value_size[d2] = count[d2];
+                else if (endindex >= fdims[d2])
+                    fill_value_size[d2] = count[d2] - ((fdims[d2] - start[d2])/stride[d2]);
+                else
+                    fill_value_size[d2] = 0;
+                count[d2] -= fill_value_size[d2];
+                if (count[d2] == 0)
+                    no_read++;
+                if (fill_value_size[d2])
+                    provide_fill++;
+            }
             else
-                fill_value_size[d2] = 0;
-            count[d2] -= fill_value_size[d2];
-            if (fill_value_size[d2])
-                provide_fill++;
+                fill_value_size[d2] = count[d2];
         }
         else /* Dim is not unlimited. */
         {
             /* Check for out of bound requests. */
-#ifdef RELAX_COORD_BOUND
             /* Allow start to equal dim size if count is zero. */
             if (start[d2] > (hssize_t)fdims[d2] ||
                 (start[d2] == (hssize_t)fdims[d2] && count[d2] > 0))
                 BAIL_QUIET(NC_EINVALCOORDS);
-#else
-            if (start[d2] >= (hssize_t)fdims[d2])
-                BAIL_QUIET(NC_EINVALCOORDS);
-#endif
             if (count[d2] && endindex >= fdims[d2])
                 BAIL_QUIET(NC_EEDGE);
 
diff --git a/libnetcdf.settings.in b/libnetcdf.settings.in
index fbe5853b2..50070720f 100644
--- a/libnetcdf.settings.in
+++ b/libnetcdf.settings.in
@@ -39,4 +39,4 @@ MMap Support:		@HAS_MMAP@
 JNA Support:		@HAS_JNA@
 CDF5 Support:		@HAS_CDF5@
 ERANGE Fill Support:	@HAS_ERANGE_FILL@
-Relaxed Boundary Check:	@RELAX_COORD_BOUND@
+Relaxed Boundary Check:	yes
diff --git a/libsrc/putget.m4 b/libsrc/putget.m4
index ebd0fc04a..dcbb91981 100644
--- a/libsrc/putget.m4
+++ b/libsrc/putget.m4
@@ -498,11 +498,7 @@ NCcoordck(NC3_INFO* ncp, const NC_var *varp, const size_t *coord)
 	{
 		if(*coord > X_UINT_MAX) /* rkr: bug fix from previous X_INT_MAX */
 			return NC_EINVALCOORDS; /* sanity check */
-#ifdef RELAX_COORD_BOUND
 		if(NC_readonly(ncp) && *coord > NC_get_numrecs(ncp))
-#else
-		if(NC_readonly(ncp) && *coord >= NC_get_numrecs(ncp))
-#endif
 		{
 			if(!NC_doNsync(ncp))
 				return NC_EINVALCOORDS;
@@ -512,11 +508,7 @@ NCcoordck(NC3_INFO* ncp, const NC_var *varp, const size_t *coord)
 				const int status = read_numrecs(ncp);
 				if(status != NC_NOERR)
 					return status;
-#ifdef RELAX_COORD_BOUND
 				if(*coord > NC_get_numrecs(ncp))
-#else
-				if(*coord >= NC_get_numrecs(ncp))
-#endif
 					return NC_EINVALCOORDS;
 			}
 		}
@@ -543,11 +535,7 @@ fprintf(stderr,"	NCcoordck: ip %p, *ip %ld, up %p, *up %lu\n",
 #endif /* CDEBUG */
 
 		/* cast needed for braindead systems with signed size_t */
-#ifdef RELAX_COORD_BOUND
 		if((unsigned long) *ip > (unsigned long) *up )
-#else
-		if((unsigned long) *ip >= (unsigned long) *up )
-#endif
 			return NC_EINVALCOORDS;
 	}
 
@@ -571,11 +559,9 @@ NCedgeck(const NC3_INFO* ncp, const NC_var *varp,
 
 	if(IS_RECVAR(varp))
 	{
-#ifdef RELAX_COORD_BOUND
 		if (NC_readonly(ncp) &&
                     (start[0] == NC_get_numrecs(ncp) && edges[0] > 0))
 			return(NC_EINVALCOORDS);
-#endif
 		start++;
 		edges++;
 		shp++;
@@ -583,10 +569,8 @@ NCedgeck(const NC3_INFO* ncp, const NC_var *varp,
 
 	for(; start < end; start++, edges++, shp++)
 	{
-#ifdef RELAX_COORD_BOUND
 		if ((unsigned long) *start == *shp && *edges > 0)
 			return(NC_EINVALCOORDS);
-#endif
 		/* cast needed for braindead systems with signed size_t */
 		if((unsigned long) *edges > *shp ||
 			(unsigned long) *start + (unsigned long) *edges > *shp)


Test patch:
diff --git a/nc_test/Makefile.am b/nc_test/Makefile.am
index ceaf9ecda..c8fa6f5db 100644
--- a/nc_test/Makefile.am
+++ b/nc_test/Makefile.am
@@ -127,7 +127,7 @@ endif
 
 # Only clean these on maintainer-clean, because they require m4 to
 # regenerate.
-#MAINTAINERCLEANFILES = test_get.c test_put.c
+MAINTAINERCLEANFILES = test_get.c test_put.c
 
 # This rule tells make how to turn our .m4 files into .c files.
 .m4.c:
diff --git a/nc_test/test_get.m4 b/nc_test/test_get.m4
index 1e81e5667..19d9e043c 100644
--- a/nc_test/test_get.m4
+++ b/nc_test/test_get.m4
@@ -487,13 +487,8 @@ ifelse(`$1',`uchar',`ifdef(`PNETCDF',,``#'endif')')
                 start[j] = 0;
                 continue;
             }
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             start[j] = var_shape[i][j]+1; /* should cause NC_EINVALCOORDS */
             err = GetVara($1)(ncid, i, start, edge, value);
             IF (err != NC_EINVALCOORDS)
@@ -756,13 +751,8 @@ ifdef(`PNETCDF',`dnl
                 start[j] = 0;
                 continue;
             }
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             start[j] = var_shape[i][j]+1;  /* should cause NC_EINVALCOORDS */
             err = GetVars($1)(ncid, i, start, edge, stride, value);
             IF (err != NC_EINVALCOORDS)
@@ -1054,13 +1044,8 @@ ifdef(`PNETCDF',`dnl
                 start[j] = 0;
                 continue;
             }
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             start[j] = var_shape[i][j]+1;  /* should cause NC_EINVALCOORDS */
             err = GetVarm($1)(ncid, i, start, edge, stride, imap, value);
             IF (err != NC_EINVALCOORDS)
diff --git a/nc_test/test_put.m4 b/nc_test/test_put.m4
index 0d6705ed0..5f7cb0171 100644
--- a/nc_test/test_put.m4
+++ b/nc_test/test_put.m4
@@ -778,13 +778,8 @@ ifdef(`PNETCDF',`dnl
                 start[j] = 0;
                 continue;
             }
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1; /* out of boundary check */
             err = PutVara($1)(ncid, i, start, edge, value);
@@ -1007,13 +1002,8 @@ ifdef(`PNETCDF',`dnl
                 start[j] = 0;
                 continue;
             }
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1; /* out of boundary check */
             err = PutVars($1)(ncid, i, start, edge, stride, value);
@@ -1259,13 +1249,8 @@ ifdef(`PNETCDF',`dnl
                 start[j] = 0;
                 continue;
             }
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1; /* out of boundary check */
             err = PutVarm($1)(ncid, i, start, edge, stride, imap, value);
diff --git a/nc_test/test_read.m4 b/nc_test/test_read.m4
index 4434d6d31..41bc5b55e 100644
--- a/nc_test/test_read.m4
+++ b/nc_test/test_read.m4
@@ -1207,13 +1207,8 @@ ifdef(`PNETCDF',`dnl
             if (var_dimid[i][j] == RECDIM) continue; /* skip record dim */
             start[j] = var_shape[i][j];
             err = GetVara(ncid, i, start, edge, buf, 0, datatype);
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1;  /* out of boundary check */
             err = GetVara(ncid, i, start, edge, buf, 1, datatype);
@@ -1399,13 +1394,8 @@ ifdef(`PNETCDF',`dnl
             if (var_dimid[i][j] == RECDIM) continue; /* skip record dim */
             start[j] = var_shape[i][j];
             err = GetVars(ncid, i, start, edge, stride, buf, 0, datatype);
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1;  /* out of boundary check */
             err = GetVars(ncid, i, start, edge, stride, buf, 1, datatype);
@@ -1628,13 +1618,8 @@ ifdef(`PNETCDF',`dnl
             if (var_dimid[i][j] == RECDIM) continue; /* skip record dim */
             start[j] = var_shape[i][j];
             err = GetVarm(ncid, i, start, edge, stride, imap, buf, 0, datatype);
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1;  /* out of boundary check */
             err = GetVarm(ncid, i, start, edge, stride, imap, buf, 1, datatype);
diff --git a/nc_test/test_write.m4 b/nc_test/test_write.m4
index 4650e3706..6d011971b 100644
--- a/nc_test/test_write.m4
+++ b/nc_test/test_write.m4
@@ -1043,13 +1043,8 @@ ifdef(`PNETCDF',`dnl
             if (var_dimid[i][j] == RECDIM) continue; /* skip record dim */
             start[j] = var_shape[i][j];
             err = PutVara(ncid, i, start, edge, buf, 0, datatype);
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1; /* out of boundary check */
             err = PutVara(ncid, i, start, edge, buf, 1, datatype);
@@ -1236,13 +1231,8 @@ ifdef(`PNETCDF',`dnl
             if (var_dimid[i][j] == 0) continue; /* skip record dim */
             start[j] = var_shape[i][j];
             err = PutVars(ncid, i, start, edge, stride, buf, 0, datatype);
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1; /* out of boundary check */
             err = PutVars(ncid, i, start, edge, stride, buf, 1, datatype);
@@ -1459,13 +1449,8 @@ ifdef(`PNETCDF',`dnl
             if (var_dimid[i][j] == 0) continue; /* skip record dim */
             start[j] = var_shape[i][j];
             err = PutVarm(ncid, i, start, edge, stride, imap, buf, 0, datatype);
-#ifdef RELAX_COORD_BOUND
             IF (err != NC_NOERR) /* allowed when edge[j]==0 */
                 EXPECT_ERR(NC_NOERR, err)
-#else
-            IF (err != NC_EINVALCOORDS) /* not allowed even when edge[j]==0 */
-                EXPECT_ERR(NC_EINVALCOORDS, err)
-#endif
             ELSE_NOK
             start[j] = var_shape[i][j]+1; /* out of boundary check */
             err = PutVarm(ncid, i, start, edge, stride, imap, buf, 1, datatype);
diff --git a/nc_test4/tst_dims3.c b/nc_test4/tst_dims3.c
index c98ec5e20..cb31cc47a 100644
--- a/nc_test4/tst_dims3.c
+++ b/nc_test4/tst_dims3.c
@@ -12,6 +12,14 @@
 #include <nc_tests.h>
 #include "err_macros.h"
 
+#define NDIM2 2
+#define VAR_NAME "var"
+#define DIM_NAME1 "y"
+#define DIM_NAME2 "x"
+#define NY 3
+#define NX 3
+#define DIM_LEN 2
+
 int
 main(int argc, char **argv)
 {
@@ -235,5 +243,152 @@ main(int argc, char **argv)
        if (nc_close(ncid)) ERR;
    }
    SUMMARIZE_ERR;
+
+   printf("*** testing very simple case writing data with 2 unlim dims...");
+   {
+       /* This test code based on test code from Jeff Whitaker. See
+        * https://github.com/Unidata/netcdf4-python/issues/1413. This
+        * is a simplified version. */
+       int varid1, varid, ncid;
+       int dimids[NDIM2];
+       size_t start[1] = {0};
+       size_t count[1] = {NY};
+       int ydata[NY];
+       int y, i;
+       size_t len_in;
+
+       /* Fill Y data array. */
+       for (i = 0; i < NY; ++i)
+           ydata[i] = i;
+
+       /* Create a file. */
+       if (nc_create(FILE_NAME, NC_NETCDF4, &ncid)) ERR;
+
+       /* Define two unlimited dims. */
+       if (nc_def_dim(ncid, DIM_NAME1, NC_UNLIMITED, &dimids[0])) ERR;
+       if (nc_def_dim(ncid, DIM_NAME2, NC_UNLIMITED, &dimids[1])) ERR;
+
+       /* Define coord var for first dim Y. */
+       if (nc_def_var(ncid, DIM_NAME1, NC_INT, 1, &dimids[0], &varid1)) ERR;
+
+       /* Define a data var with dims Y,X. */
+       if (nc_def_var(ncid, VAR_NAME, NC_INT, NDIM2, dimids, &varid)) ERR;
+
+       /* Write data to coordinate var to extend Y. */
+       if (nc_put_vara_int(ncid, varid1, start, count, ydata)) ERR;
+       if (nc_close(ncid)) ERR;
+
+       /* Reopen the file and check. */
+       if (nc_open(FILE_NAME, NC_NOWRITE, &ncid)) ERR;
+       if (nc_inq_dimlen(ncid, 0, &len_in)) ERR;
+       if (len_in != NY) ERR;
+       if (nc_inq_dimlen(ncid, 1, &len_in)) ERR;
+       if (len_in != 0) ERR;
+
+       /* Read and check each value of the coordinate var. */
+       for (y = 0; y < NY; y++)
+       {
+           size_t index = y;
+           int data_in;
+
+           if (nc_get_var1_int(ncid, varid1, &index, &data_in)) ERR;
+           if (data_in != y) ERR;
+       }
+
+       if (nc_close(ncid)) ERR;
+   }
+   SUMMARIZE_ERR;
+   printf("*** testing simple case writing data with 2 unlim dims...");
+   {
+       /* This test code based on test code from Jeff Whitaker. See
+        * https://github.com/Unidata/netcdf4-python/issues/1413. This
+        * is a simplified version. */
+       int varid1, varid, ncid;
+       int dimids[NDIM2];
+       size_t start[1] = {0};
+       size_t count[1] = {NY};
+       size_t start2[NDIM2] = {0, 2};
+       size_t count2[NDIM2] = {1, 1};
+       int ydata[NY];
+       int data = TEST_VAL_42;
+       int x, y, i;
+
+       /* Fill Y data array. */
+       for (i = 0; i < NY; ++i)
+           ydata[i] = i;
+
+       /* Create a file. */
+       if (nc_create(FILE_NAME, NC_NETCDF4, &ncid)) ERR;
+
+       /* Define two unlimited dims. */
+       if (nc_def_dim(ncid, DIM_NAME1, NC_UNLIMITED, &dimids[0])) ERR;
+       if (nc_def_dim(ncid, DIM_NAME2, NC_UNLIMITED, &dimids[1])) ERR;
+
+       /* Define coord var for first dim Y. */
+       if (nc_def_var(ncid, DIM_NAME1, NC_INT, 1, &dimids[0], &varid1)) ERR;
+
+       /* Define a data var with dims Y,X. */
+       if (nc_def_var(ncid, VAR_NAME, NC_INT, NDIM2, dimids, &varid)) ERR;
+
+       /* Write data to coordinate var to extend Y. */
+       if (nc_put_vara_int(ncid, varid1, start, count, ydata)) ERR;
+       if (nc_sync(ncid)) ERR;
+
+       /* write a single data point to the 2d variable */
+       if (nc_put_vara_int(ncid, varid, start2, count2, &data)) ERR;
+       if (nc_close(ncid)) ERR;
+
+       /* Reopen the file and check. */
+       if (nc_open(FILE_NAME, NC_NOWRITE, &ncid)) ERR;
+
+       /* Read and check each value with nc_get_var1_int(). */
+       for (x = 0; x < NX; x++)
+       {
+           for (y = 0; y < NY; y++)
+           {
+               size_t index[NDIM2] = {y, x};
+               int data_in;
+
+               if (nc_get_var1_int(ncid, varid, index, &data_in)) ERR;
+               if (y == start2[0] && x == start2[1])
+               {
+                   if (data_in != data) ERR;
+               }
+               else
+               {
+                   if (data_in != NC_FILL_INT) ERR;
+               }
+           }
+       }
+
+       /* Read and check each row with nc_get_vara_int(). */
+       for (y = 0; y < NY; y++)
+       {
+           size_t start_row[NDIM2] = {y, 0};
+           size_t count_row[NDIM2] = {1, NX};
+           int data_in[NX];
+
+           if (nc_get_vara_int(ncid, varid, start_row, count_row,
+                               data_in)) ERR;
+           if (y == start2[0])
+           {
+               /* This row has data. */
+               for (x = 0; x < NX; x++)
+                   if (data_in[x] != (x == start2[1] ? TEST_VAL_42 : NC_FILL_INT)) ERR;
+           }
+           else
+           {
+               /* This row is all fill. */
+               for (x = 0; x < NX; x++)
+               {
+                   printf("y %d x %d data %d\n", y, x, data_in[x]);
+                   if (data_in[x] != NC_FILL_INT) ERR;
+               }
+           }
+       }
+
+       if (nc_close(ncid)) ERR;
+   }
+   SUMMARIZE_ERR;
    FINAL_RESULTS;
 }