1 /*********************************************************************
2  *   Copyright 2018, University Corporation for Atmospheric Research
3  *   See netcdf/README file for copying and redistribution conditions.
4  *   $Header: /upc/share/CVS/netcdf-3/ncdump/dumplib.c,v 1.85 2010/05/05 22:15:39 dmh Exp $
5  *********************************************************************/
6 
7 /*
8  * We potentially include <stdarg.h> before <stdio.h> in order to obtain a
9  * definition for va_list from the GNU C compiler.
10  */
11 
12 #include "config.h"
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <assert.h>
19 #ifndef NO_FLOAT_H
20 #include <float.h>		/* for FLT_EPSILON, DBL_EPSILON */
21 #endif /* NO_FLOAT_H */
22 #include <math.h>
23 #include <netcdf.h>
24 #include "utils.h"
25 #include "nccomps.h"
26 #include "dumplib.h"
27 #include "ncdump.h"
28 #include "isnan.h"
29 #include "nctime0.h"
30 
31 static float float_eps;
32 static double double_eps;
33 
34 extern fspec_t formatting_specs; /* set from command-line options */
35 
36 static float
float_epsilon(void)37 float_epsilon(void)
38 {
39     float float_eps;
40 #ifndef NO_FLOAT_H
41     float_eps = FLT_EPSILON;
42 #else /* NO_FLOAT_H */
43     {
44 	float etop, ebot, eps;
45 	float one = 1.0;
46 	float two = 2.0;
47 	etop = 1.0;
48 	ebot = 0.0;
49 	eps = ebot + (etop - ebot)/two;
50 	while (eps != ebot && eps != etop) {
51 	    float epsp1;
52 
53 	    epsp1 = one + eps;
54 	    if (epsp1 > one)
55 		etop = eps;
56 	    else
57 		ebot = eps;
58 	    eps = ebot + (etop - ebot)/two;
59 	}
60 	float_eps = two * etop;
61     }
62 #endif /* NO_FLOAT_H */
63     return float_eps;
64 }
65 
66 
67 static double
double_epsilon(void)68 double_epsilon(void)
69 {
70     double double_eps;
71 #ifndef NO_FLOAT_H
72     double_eps = DBL_EPSILON;
73 #else /* NO_FLOAT_H */
74     {
75 	double etop, ebot, eps;
76 	double one = 1.0;
77 	double two = 2.0;
78 	etop = 1.0;
79 	ebot = 0.0;
80 	eps = ebot + (etop - ebot)/two;
81 	while (eps != ebot && eps != etop) {
82 	    double epsp1;
83 
84 	    epsp1 = one + eps;
85 	    if (epsp1 > one)
86 		etop = eps;
87 	    else
88 		ebot = eps;
89 	    eps = ebot + (etop - ebot)/two;
90 	}
91 	double_eps = two * etop;
92     }
93 #endif /* NO_FLOAT_H */
94     return double_eps;
95 }
96 
97 
98 void
init_epsilons(void)99 init_epsilons(void)
100 {
101     float_eps = float_epsilon();
102     double_eps = double_epsilon();
103 }
104 
105 
106 static char* has_c_format_att(int ncid, int varid);
107 
108 int float_precision_specified = 0; /* -p option specified float precision */
109 int double_precision_specified = 0; /* -p option specified double precision */
110 char float_var_fmt[] = "%.NNg";
111 char double_var_fmt[] = "%.NNg";
112 char float_att_fmt[] = "%#.NNgf";
113 char float_attx_fmt[] = "%#.NNg";
114 char double_att_fmt[] = "%#.NNg";
115 
116 /* magic number stored in a safebuf and checked, hoping it will be
117  * changed if buffer was overwritten inadvertently */
118 #define SAFEBUF_CERT 2147114711
119 
120 /* expression for where SAFEBUF_CERT is stored within safebuf (at end
121  * of buffer, after data) */
122 #define SAFEBUF_EXPR(sbuf) (*(int *)((sbuf)->buf + (sbuf)->len))
123 
124 /* expression to be checked whenever a safebuf is used */
125 #define SAFEBUF_CHECK(sbuf) (SAFEBUF_EXPR(sbuf) == SAFEBUF_CERT)
126 
127 /* somewhat arbitrary initial size of safebufs, grow as needed */
128 #define SAFEBUF_INIT_LEN 128
129 
130 /* initialize safe buffer */
131 safebuf_t *
sbuf_new()132 sbuf_new() {
133     size_t len = SAFEBUF_INIT_LEN;
134     safebuf_t *sb;
135     sb = (safebuf_t *) emalloc(sizeof(safebuf_t));
136     sb->buf = (char *)emalloc(len + sizeof(int));
137     sb->len = len;
138     /* write a "stamp" in last 4 bytes of buffer for id and to check for overflow */
139     SAFEBUF_EXPR(sb) = SAFEBUF_CERT;
140     sb->buf[0] = 0;
141     sb->cl = strlen(sb->buf);
142     assert(SAFEBUF_CHECK(sb));
143     return sb;
144 }
145 
146 
147 /* grow buffer to at least len bytes, copying previous contents if
148  * necessary */
149 void
sbuf_grow(safebuf_t * sb,size_t len)150 sbuf_grow(safebuf_t *sb, size_t len) {
151     size_t m = sb->len;
152     void *tmp;
153     assert(SAFEBUF_CHECK(sb));
154     if (len <= m)
155 	return;
156 
157     /* Make sure we at least double size of buffer to get what's
158      * needed.  If we just used realloc(), no guarantee that length
159      * would be expanded by a multiple, which we want. */
160     while(len > m) {
161 	m *= 2;
162     }
163     tmp = emalloc(m + sizeof(int));
164     memcpy(tmp, sb->buf, sb->len);
165     sb->len = m;
166     free(sb->buf);
167     sb->buf = tmp;
168     SAFEBUF_EXPR(sb) = SAFEBUF_CERT;
169     assert(SAFEBUF_CHECK(sb));
170 }
171 
172 /* Copy string s2 to safe buffer, growing if necessary */
173 void
sbuf_cpy(safebuf_t * sb,const char * s2)174 sbuf_cpy(safebuf_t *sb, const char *s2) {
175     size_t s2len;
176     assert(SAFEBUF_CHECK(sb));
177     s2len = strlen(s2);
178     sbuf_grow(sb, 1 + s2len);
179     strncpy(sb->buf, s2, sb->len);
180     sb->cl = s2len;
181     assert(SAFEBUF_CHECK(sb));
182 }
183 
184 /* Concatenate string s2 to end of string in safe buffer, growing if necessary */
185 void
sbuf_cat(safebuf_t * sb,const char * s2)186 sbuf_cat(safebuf_t *sb, const char *s2) {
187     size_t s2len;
188     size_t res;
189     assert(SAFEBUF_CHECK(sb));
190     s2len = strlen(s2);
191     sbuf_grow(sb, 1 + sb->cl + s2len);
192     res = strlcat(sb->buf + sb->cl, s2, sb->len);
193     assert( res < sb->len );
194     sb->cl += s2len;
195     assert(SAFEBUF_CHECK(sb));
196 }
197 
198 /* Concatenate string in safebuf s2 to end of string in safebuf s1,
199  * growing if necessary */
200 void
sbuf_catb(safebuf_t * s1,const safebuf_t * s2)201 sbuf_catb(safebuf_t *s1, const safebuf_t *s2) {
202     size_t s2len;
203     size_t res;
204     assert(SAFEBUF_CHECK(s1));
205     assert(SAFEBUF_CHECK(s2));
206     s2len = sbuf_len(s2);
207     sbuf_grow(s1, 1 + s1->cl + s2len);
208     res = strlcat(s1->buf + s1->cl, s2->buf, s1->len);
209     assert( res < s1->len );
210     s1->cl += s2len;
211     assert(SAFEBUF_CHECK(s1));
212 }
213 
214 /* Return length of string in sbuf */
215 size_t
sbuf_len(const safebuf_t * sb)216 sbuf_len(const safebuf_t *sb) {
217     assert(SAFEBUF_CHECK(sb));
218     return sb->cl;
219 }
220 
221 /* Return C string in an sbuf */
222 char *
sbuf_str(const safebuf_t * sb)223 sbuf_str(const safebuf_t *sb) {
224     assert(SAFEBUF_CHECK(sb));
225     return sb->buf;
226 }
227 
228 /* free safe buffer */
229 void
sbuf_free(safebuf_t * sb)230 sbuf_free(safebuf_t *sb) {
231     assert(SAFEBUF_CHECK(sb));
232     free(sb->buf);
233     free(sb);
234 }
235 
236 
237 /* In case different formats specified with -d option, set them here. */
238 void
set_formats(int float_digits,int double_digits)239 set_formats(int float_digits, int double_digits)
240 {
241     int res;
242     res = snprintf(float_var_fmt, strlen(float_var_fmt) + 1, "%%.%dg",
243 		   float_digits) + 1;
244     assert(res <= sizeof(float_var_fmt));
245     res = snprintf(double_var_fmt, strlen(double_var_fmt) + 1, "%%.%dg",
246 		   double_digits) + 1;
247     assert(res <= sizeof(double_var_fmt));
248     res = snprintf(float_att_fmt, strlen(float_att_fmt) + 1, "%%#.%dgf",
249 		   float_digits) + 1;
250     assert(res <= sizeof(float_att_fmt));
251     res = snprintf(float_attx_fmt, strlen(float_attx_fmt) + 1, "%%#.%dg",
252 		   float_digits) + 1;
253     assert(res <= sizeof(float_attx_fmt));
254     res = snprintf(double_att_fmt, strlen(double_att_fmt) + 1, "%%#.%dg",
255 		   double_digits) + 1;
256     assert(res <= sizeof(double_att_fmt));
257 }
258 
259 
260 static char *
has_c_format_att(int ncid,int varid)261 has_c_format_att(
262     int ncid,			/* netcdf id */
263     int varid			/* variable id */
264     )
265 {
266     nc_type cfmt_type;
267     size_t cfmt_len;
268 #define C_FMT_NAME	"C_format" /* name of C format attribute */
269 #define	MAX_CFMT_LEN	100	/* max length of C format attribute */
270     static char cfmt[MAX_CFMT_LEN];
271 
272     /* we expect nc_inq_att to fail if there is no "C_format" attribute */
273     int nc_stat = nc_inq_att(ncid, varid, "C_format", &cfmt_type, &cfmt_len);
274 
275     switch(nc_stat) {
276     case NC_NOERR:
277 	if (cfmt_type == NC_CHAR && cfmt_len != 0 && cfmt_len < MAX_CFMT_LEN) {
278 	    int nc_stat = nc_get_att_text(ncid, varid, "C_format", cfmt);
279 	    if(nc_stat != NC_NOERR) {
280 		fprintf(stderr, "Getting 'C_format' attribute %s\n",
281 			nc_strerror(nc_stat));
282 		(void) fflush(stderr);
283 	    }
284 	    cfmt[cfmt_len] = '\0';
285 	    return &cfmt[0];
286 	}
287 	break;
288     case NC_ENOTATT:
289 	break;
290     default:
291 	fprintf(stderr, "Inquiring about 'C_format' attribute %s\n",
292 		nc_strerror(nc_stat));
293 	(void) fflush(stderr);
294 	break;
295     }
296     return 0;
297 }
298 
299 
300 /* Return default format to use for a primitive type */
301 const char *
get_default_fmt(nc_type typeid)302 get_default_fmt(nc_type typeid) {
303     /* Otherwise return sensible default. */
304     switch (typeid) {
305     case NC_BYTE:
306 	return "%d";
307     case NC_CHAR:
308 	return "%s";
309     case NC_SHORT:
310 	return "%d";
311     case NC_INT:
312 	return "%d";
313     case NC_FLOAT:
314 	return float_var_fmt;
315     case NC_DOUBLE:
316 	return double_var_fmt;
317     case NC_UBYTE:
318 	return "%u";
319     case NC_USHORT:
320 	return "%u";
321     case NC_UINT:
322 	return "%u";
323     case NC_INT64:
324 	return "%lld";
325     case NC_UINT64:
326 	return "%llu";
327     case NC_STRING:
328 	return "\"%s\"";
329     default:
330 	break;
331     }
332     return "";		/* user-defined types don't use fmt member */
333 }
334 
335 /*
336  * Determine print format to use for each primitive value for this
337  * variable.  Use value of attribute C_format if it exists, otherwise
338  * a sensible default.
339  */
340 const char *
get_fmt(int ncid,int varid,nc_type typeid)341 get_fmt(
342      int ncid,
343      int varid,
344      nc_type typeid
345      )
346 {
347     char *c_format_att;
348 
349     /* float or double precision specified with -p option overrides any
350        C_format attribute value, so check for that first. */
351     if (float_precision_specified && typeid == NC_FLOAT)
352 	return float_var_fmt;
353     if (double_precision_specified && typeid == NC_DOUBLE)
354 	return double_var_fmt;
355     /* If C_format attribute exists, return it */
356     c_format_att = has_c_format_att(ncid, varid);
357     if (c_format_att)
358       return c_format_att;
359     return get_default_fmt(typeid);
360 }
361 
362 /* Return primitive type name */
363 static const char *
prim_type_name(nc_type type)364 prim_type_name(nc_type type)
365 {
366     switch (type) {
367       case NC_BYTE:
368 	return "byte";
369       case NC_CHAR:
370 	return "char";
371       case NC_SHORT:
372 	return "short";
373       case NC_INT:
374 	return "int";
375       case NC_FLOAT:
376 	return "float";
377       case NC_DOUBLE:
378 	return "double";
379       case NC_UBYTE:
380 	return "ubyte";
381       case NC_USHORT:
382 	return "ushort";
383       case NC_UINT:
384 	return "uint";
385       case NC_INT64:
386 	return "int64";
387       case NC_UINT64:
388 	return "uint64";
389       case NC_STRING:
390 	return "string";
391       default:
392 	error("prim_type_name: bad type %d", type);
393 	return "bogus";
394     }
395 }
396 
397 static int max_type = 0;
398 static int max_atomic_type = 0;
399 static nctype_t **nctypes = 0;	/* holds all types in a netCDF dataset */
400 
401 
402 #ifdef USE_NETCDF4
403 /* return number of user-defined types in a group and all its subgroups */
404 static int
count_udtypes(int ncid)405 count_udtypes(int ncid) {
406     int ntypes = 0;
407     int numgrps;
408     int *ncids;
409     int i;
410     int format;
411 
412     NC_CHECK( nc_inq_format(ncid, &format) );
413 
414     if (format == NC_FORMAT_NETCDF4) {
415 	/* Get number of types in this group */
416 	NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) ) ;
417 	NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) ) ;
418 	ncids = (int *) emalloc(sizeof(int) * (numgrps + 1));
419 	NC_CHECK( nc_inq_grps(ncid, NULL, ncids) ) ;
420 	/* Add number of types in each subgroup, if any */
421 	for (i=0; i < numgrps; i++) {
422 	    ntypes += count_udtypes(ncids[i]);
423 	}
424 	free(ncids);
425     }
426     return ntypes;
427 }
428 #endif /*USE_NETCDF4*/
429 
430 /* This routine really is intended to return the max atomic typeid */
431 static int
max_typeid(int ncid)432 max_typeid(int ncid) {
433     int maxtypes = NC_NAT;
434     int maxatomictypes = NC_NAT;
435     int format = 0;
436     int err = NC_NOERR;
437 
438     /* get the file type */
439     err = nc_inq_format(ncid,&format);
440     if(err) {
441 	fprintf(stderr,"%s: Cannot get file format.\n",nc_strerror(err));
442 	return 0;
443     }
444     switch (format) {
445     case NC_FORMAT_CLASSIC:
446     case NC_FORMAT_NETCDF4_CLASSIC:
447     case NC_FORMAT_64BIT_OFFSET:
448         maxatomictypes = (maxtypes = NC_DOUBLE); /*ignore NC_NAT?*/
449 	break;
450     case NC_FORMAT_64BIT_DATA:
451 	maxatomictypes = (maxtypes = NC_UINT64);
452 	break;
453     case NC_FORMAT_NETCDF4:
454 #ifdef USE_NETCDF4
455 	{
456         int nuser = 0;
457         maxatomictypes = (maxtypes = NC_STRING); /* extra netCDF-4 primitive types */
458         maxtypes += 4;		/* user-defined classes */
459         nuser = count_udtypes(ncid);
460         if(nuser > 0)
461 	    maxtypes = NC_FIRSTUSERTYPEID + (nuser - 1);
462 	} break;
463 #else
464 	/* fallthru */
465 #endif
466     default:
467 	fprintf(stderr,"Unexpected file format: %d\n",format);
468 	return 0;
469     }
470     max_type = maxtypes;
471     max_atomic_type = maxatomictypes;
472     return maxtypes;
473 }
474 
typeadd(nctype_t * typep)475 void typeadd(nctype_t *typep) {
476     nctypes[typep->tid] = typep;
477 }
478 
479 /* From type id, get full type info */
480 nctype_t *
get_typeinfo(int typeid)481 get_typeinfo ( int typeid ) {
482     if(typeid < 0 || typeid > max_type)
483 	error("ncdump: %d is an invalid type id", typeid);
484     return nctypes[typeid];
485 }
486 
487 /* void */
488 /* xfree_typeinfo(int ncid) { */
489 /*     int i; */
490 /*     for (i = 0; i < number_of_types; i++) { */
491 /* 	nctype_t *tinfop = nctypes[i]; */
492 /* 	if (tinfop) { */
493 /* 	    if(tinfop->name) */
494 /* 		free(tinfop->name); */
495 /* 	    if(tinfop->grps) */
496 /* 		free(tinfop->grps); */
497 /* 	    free(tinfop); */
498 /* 	} */
499 /*     } */
500 /* } */
501 
502 
503 bool_t
ncbyte_val_equals(const nctype_t * this,const void * v1p,const void * v2p)504 ncbyte_val_equals(const nctype_t *this,
505 		  const void *v1p, const void *v2p) {
506     return ( *(signed char* )v1p == *(signed char* )v2p);
507 }
508 
509 bool_t
ncchar_val_equals(const nctype_t * this,const void * v1p,const void * v2p)510 ncchar_val_equals(const nctype_t *this,
511 		  const void *v1p, const void *v2p) {
512     return ( *(char* )v1p == *(char* )v2p);
513 }
514 
515 bool_t
ncshort_val_equals(const nctype_t * this,const void * v1p,const void * v2p)516 ncshort_val_equals(const nctype_t *this,
517 		   const void *v1p, const void *v2p) {
518     return ( *(short* )v1p == *(short* )v2p);
519 }
520 
521 bool_t
ncint_val_equals(const nctype_t * this,const void * v1p,const void * v2p)522 ncint_val_equals(const nctype_t *this,
523 		 const void *v1p, const void *v2p) {
524     return ( *(int* )v1p == *(int* )v2p);
525 }
526 
527 #define absval(x)  ( (x) < 0 ? -(x) : (x) )
528 
529 /*
530  * Return ( *(float* )v1p == *(float* )v2p);
531  * except use floating epsilon to compare very close vals as equal
532  * and handle IEEE NaNs and infinities.
533  */
534 bool_t
ncfloat_val_equals(const nctype_t * this,const void * v1p,const void * v2p)535 ncfloat_val_equals(const nctype_t *this,
536 		   const void *v1p, const void *v2p) {
537     float v1 = *(float* )v1p;
538     float v2 = *(float* )v2p;
539     if((v1 > 0.0f) != (v2 > 0.0f))	/* avoid overflow */
540 	return false;
541     if(isfinite(v1) && isfinite(v2))
542 	return (absval(v1 - v2) <= absval(float_eps * v2)) ;
543     if(isnan(v1) && isnan(v2))
544 	return true;
545     if(isinf(v1) && isinf(v2))
546 	return true;
547     return false;
548 }
549 
550 /*
551  * Return ( *(double* )v1p == *(double* )v2p);
552  * except use floating epsilon to compare very close vals as equal
553  * and handle IEEE NaNs and infinities.
554  */
555 bool_t
ncdouble_val_equals(const nctype_t * this,const void * v1p,const void * v2p)556 ncdouble_val_equals(const nctype_t *this,
557 		    const void *v1p, const void *v2p) {
558     double v1 = *(double* )v1p;
559     double v2 = *(double* )v2p;
560     if((v1 > 0.0) != (v2 > 0.0))	/* avoid overflow */
561 	return false;
562     if(isfinite(v1) && isfinite(v2))
563 	return (absval(v1 - v2) <= absval(double_eps * v2)) ;
564     if(isnan(v1) && isnan(v2))
565 	return true;
566     if(isinf(v1) && isinf(v2))
567 	return true;
568     return false;
569 }
570 
571 bool_t
ncubyte_val_equals(const nctype_t * this,const void * v1p,const void * v2p)572 ncubyte_val_equals(const nctype_t *this,
573 		   const void *v1p, const void *v2p) {
574     return ( *(unsigned char* )v1p == *(unsigned char* )v2p);
575 }
576 
577 bool_t
ncushort_val_equals(const nctype_t * this,const void * v1p,const void * v2p)578 ncushort_val_equals(const nctype_t *this,
579 		    const void *v1p, const void *v2p) {
580     return ( *(unsigned short* )v1p == *(unsigned short* )v2p);
581 }
582 
583 bool_t
ncuint_val_equals(const nctype_t * this,const void * v1p,const void * v2p)584 ncuint_val_equals(const nctype_t *this,
585 		  const void *v1p, const void *v2p) {
586     return ( *(unsigned int* )v1p == *(unsigned int* )v2p);
587 }
588 
589 bool_t
ncint64_val_equals(const nctype_t * this,const void * v1p,const void * v2p)590 ncint64_val_equals(const nctype_t *this,
591 		   const void *v1p, const void *v2p) {
592     return ( *(long long* )v1p == *(long long* )v2p);
593 }
594 
595 bool_t
ncuint64_val_equals(const nctype_t * this,const void * v1p,const void * v2p)596 ncuint64_val_equals(const nctype_t *this,
597 		    const void *v1p, const void *v2p) {
598     return ( *(unsigned long long* )v1p == *(unsigned long long* )v2p);
599 }
600 
601 bool_t
ncstring_val_equals(const nctype_t * this,const void * v1p,const void * v2p)602 ncstring_val_equals(const nctype_t *this,
603 		    const void *v1p, const void *v2p) {
604     if (NULL == *((char **)v1p) && NULL == *((char **)v2p))
605         return(1);
606     else if (NULL != *((char **)v1p) && NULL == *((char **)v2p))
607         return(0);
608     else if (NULL == *((char **)v1p) && NULL != *((char **)v2p))
609         return(0);
610     return (strcmp(*((char **)v1p), *((char **)v2p)) == 0);
611 }
612 
613 #ifdef USE_NETCDF4
614 bool_t
ncopaque_val_equals(const nctype_t * this,const void * v1p,const void * v2p)615 ncopaque_val_equals(const nctype_t *this,
616 		    const void *v1p, const void *v2p) {
617     size_t nbytes = this->size;
618     const char *c1p = (const char *) v1p;
619     const char *c2p = (const char *) v2p;
620     int i;
621     for (i=0; i < nbytes; i++) {
622 	if (*c1p++ != *c2p++)
623 	    return false;
624     }
625     return true;
626 }
627 
628 bool_t
ncvlen_val_equals(const nctype_t * this,const void * v1p,const void * v2p)629 ncvlen_val_equals(const nctype_t *this,
630 		  const void *v1p, const void *v2p) {
631     size_t v1len = ((nc_vlen_t *)v1p)->len;
632     size_t v2len = ((nc_vlen_t *)v2p)->len;
633     if (v1len != v2len)
634 	return false;
635     {
636 	size_t base_size = this->size;
637 	nc_type base_type = this->base_tid;
638 	nctype_t *base_info = get_typeinfo(base_type);
639 	val_equals_func base_val_equals = base_info->val_equals;
640 	const char *v1dat = ((nc_vlen_t *)v1p)->p;
641 	const char *v2dat = ((nc_vlen_t *)v2p)->p;
642 	size_t i;
643 	for(i = 0; i < v1len; i++) {
644 	    if (base_val_equals(base_info, (const void *)v1dat,
645 				(const void *)v2dat) != true)
646 		return false;
647 	    v1dat += base_size;
648 	    v2dat += base_size;
649 	}
650     }
651     return true;
652 }
653 
654 /* Determine if two compound values are equal, by testing equality of
655  * each member field. */
656 bool_t
nccomp_val_equals(const nctype_t * this,const void * v1p,const void * v2p)657 nccomp_val_equals(const nctype_t *this,
658 		  const void *v1p, const void *v2p) {
659     int nfields = this->nfields;
660     int fidx;			/* field id */
661 
662     for (fidx = 0; fidx < nfields; fidx++) {
663 	size_t offset = this->offsets[fidx];
664 	nc_type fid = this->fids[fidx];		/* field type id */
665 	nctype_t *finfo = get_typeinfo(fid);
666 	if(finfo->ranks == 0 || finfo->ranks[fidx] == 0) {
667 	    if(! finfo->val_equals(finfo,
668 				   (char *)v1p + offset, (char *)v2p + offset))
669 		return false;
670 	} else {		/* this field is an array */
671 	    int i;		/* array element counter when rank > 0 */
672 	    void *v1elem = (char *)v1p + offset;
673 	    void *v2elem = (char *)v2p + offset;
674 	    for(i = 0; i < finfo->nvals[fidx]; i++) {
675 		if(! finfo->val_equals(finfo, v1elem, v2elem))
676 		    return false;
677 		v1elem = (char *)v1elem + finfo->size;
678 		v2elem = (char *)v1elem + finfo->size;
679 	    }
680 	}
681     }
682     return true;
683 }
684 #endif /* USE_NETCDF4 */
685 
686 int
ncbyte_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)687 ncbyte_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
688     char sout[PRIM_LEN];
689     int res;
690     res = snprintf(sout, PRIM_LEN, typ->fmt, *(signed char *)valp);
691     assert(res < PRIM_LEN);
692     sbuf_cpy(sfbf, sout);
693     return sbuf_len(sfbf);
694 }
695 
696 int
ncchar_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)697 ncchar_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
698     char sout[PRIM_LEN];
699     int res;
700     res = snprintf(sout, PRIM_LEN, typ->fmt, *(char *)valp);
701     assert(res < PRIM_LEN);
702     sbuf_cpy(sfbf, sout);
703     return sbuf_len(sfbf);
704 }
705 
706 int
ncshort_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)707 ncshort_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
708     char sout[PRIM_LEN];
709     int res;
710     res = snprintf(sout, PRIM_LEN, typ->fmt, *(short *)valp);
711     assert(res < PRIM_LEN);
712     sbuf_cpy(sfbf, sout);
713     return sbuf_len(sfbf);
714 }
715 
716 int
ncint_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)717 ncint_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
718     char sout[PRIM_LEN];
719     int res;
720     res = snprintf(sout, PRIM_LEN, typ->fmt, *(int *)valp);
721     assert(res < PRIM_LEN);
722     sbuf_cpy(sfbf, sout);
723     return sbuf_len(sfbf);
724 }
725 
726 /* CDL canonical representations of some special floating point values */
727 #define NCDL_NANF "NaNf"
728 #define NCDL_NAN "NaN"
729 #define NCDL_INFF "Infinityf"
730 #define NCDL_INF "Infinity"
731 
732 /* Convert a float NaN or Infinity to an allocated string large enough
733  * to hold it (at least PRIM_LEN chars) */
734 static void
float_special_tostring(float vv,char * sout)735 float_special_tostring(float vv, char *sout) {
736     if(isnan(vv)) {
737 	snprintf(sout, PRIM_LEN, "%s", NCDL_NANF);
738     } else if(isinf(vv)) {
739 	if(vv < 0.0) {
740 	    snprintf(sout, PRIM_LEN, "-%s", NCDL_INFF);
741 	} else {
742 	    snprintf(sout, PRIM_LEN, "%s", NCDL_INFF);
743 	}
744     } else
745 	assert(false);		/* vv was finite */
746 }
747 
748 /* Convert a double NaN or Infinity to an allocated string large enough
749  * to hold it (at least PRIM_LEN chars) */
750 static void
double_special_tostring(double vv,char * sout)751 double_special_tostring(double vv, char *sout) {
752     if(isnan(vv)) {
753 	    snprintf(sout, PRIM_LEN, "%s", NCDL_NAN);
754     } else if(isinf(vv)) {
755 	if(vv < 0.0) {
756 	    snprintf(sout, PRIM_LEN, "-%s", NCDL_INF);
757 	} else {
758 	    snprintf(sout, PRIM_LEN, "%s", NCDL_INF);
759 	}
760     } else
761 	assert(false);		/* vv was finite */
762 }
763 
764 int
ncfloat_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)765 ncfloat_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
766     char sout[PRIM_LEN];
767     float vv = *(float *)valp;
768     if(isfinite(vv)) {
769 	int res;
770 	res = snprintf(sout, PRIM_LEN, typ->fmt, vv);
771 	assert(res < PRIM_LEN);
772     } else {
773 	float_special_tostring(vv, sout);
774     }
775     sbuf_cpy(sfbf, sout);
776     return sbuf_len(sfbf);
777 }
778 
779 int
ncdouble_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)780 ncdouble_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
781     char sout[PRIM_LEN];
782     double vv = *(double *)valp;
783     if(isfinite(vv)) {
784 	int res;
785 	res = snprintf(sout, PRIM_LEN, typ->fmt, vv);
786 	assert(res < PRIM_LEN);
787     } else {
788 	double_special_tostring(vv, sout);
789     }
790     sbuf_cpy(sfbf, sout);
791     return sbuf_len(sfbf);
792 }
793 
794 int
ncubyte_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)795 ncubyte_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
796     char sout[PRIM_LEN];
797     int res;
798     res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned char *)valp);
799     assert(res < PRIM_LEN);
800     sbuf_cpy(sfbf, sout);
801     return sbuf_len(sfbf);
802 }
803 
804 int
ncushort_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)805 ncushort_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
806     char sout[PRIM_LEN];
807     int res;
808     res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned short *)valp);
809     assert(res < PRIM_LEN);
810     sbuf_cpy(sfbf, sout);
811     return sbuf_len(sfbf);
812 }
813 
814 int
ncuint_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)815 ncuint_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
816     char sout[PRIM_LEN];
817     int res;
818     res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned int *)valp);
819     assert(res < PRIM_LEN);
820     sbuf_cpy(sfbf, sout);
821     return sbuf_len(sfbf);
822 }
823 
824 int
ncint64_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)825 ncint64_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
826     char sout[PRIM_LEN];
827     int res;
828     res = snprintf(sout, PRIM_LEN, typ->fmt, *(long long *)valp);
829     assert(res < PRIM_LEN);
830     sbuf_cpy(sfbf, sout);
831     return sbuf_len(sfbf);
832 }
833 
834 int
ncuint64_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)835 ncuint64_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
836     char sout[PRIM_LEN];
837     int res;
838     res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned long long *)valp);
839     assert(res < PRIM_LEN);
840     sbuf_cpy(sfbf, sout);
841     return sbuf_len(sfbf);
842 }
843 
ncstring_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)844 int ncstring_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp)
845 {
846     const char *cp;
847 
848     cp = ((char **)valp)[0];
849     if(cp) {
850         size_t slen;
851         char *sout;
852         char *sp;
853         unsigned char uc;
854 
855         slen = 4 + 5 * strlen(cp); /* need "'s around string, and extra space to escape control characters */
856 	slen++; /* nul term */
857         sout = emalloc(slen);
858         sp = sout;
859         *sp++ = '"' ;
860         while(*cp) {
861             switch (uc = *cp++ & 0377) {
862             case '\b':
863                 *sp++ = '\\';
864                 *sp++ = 'b' ;
865                 break;
866             case '\f':
867                 *sp++ = '\\';
868                 *sp++ = 'f';
869                 break;
870             case '\n':
871                 *sp++ = '\\';
872                 *sp++ = 'n';
873                 break;
874             case '\r':
875                 *sp++ = '\\';
876                 *sp++ = 'r';
877                 break;
878             case '\t':
879                 *sp++ = '\\';
880                 *sp++ = 't';
881                 break;
882             case '\v':
883                 *sp++ = '\\';
884                 *sp++ = 'n';
885                 break;
886             case '\\':
887                 *sp++ = '\\';
888                 *sp++ = '\\';
889                 break;
890             case '\'':
891                 *sp++ = '\\';
892                 *sp++ = '\'';
893                 break;
894             case '\"':
895                 *sp++ = '\\';
896                 *sp++ = '\"';
897                 break;
898             default:
899                 if (iscntrl(uc)) {
900                     snprintf(sp,4+1,"\\%03o",uc); /* +1 for nul */
901                     sp += 4;
902                 }
903                 else
904                     *sp++ = uc;
905                 break;
906             }
907         }
908         *sp++ = '"' ;
909         *sp = '\0' ;
910         sbuf_cpy(sfbf, sout);
911         free(sout);
912     }
913     else {
914         sbuf_cpy(sfbf, "NIL");
915     }
916     return sbuf_len(sfbf);
917 }
918 
919 #ifdef USE_NETCDF4
920 int
ncenum_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)921 ncenum_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
922     char symbol[NC_MAX_NAME + 1];
923     long long val = 0;
924 
925     switch (typ->base_tid) {
926     case NC_BYTE:
927 	val = *(signed char *)valp;
928 	break;
929     case NC_UBYTE:
930 	val = *(unsigned char *)valp;
931 	break;
932     case NC_SHORT:
933 	val = *(short *)valp;
934 	break;
935     case NC_USHORT:
936 	val = *(unsigned short *)valp;
937 	break;
938     case NC_INT:
939 	val = *(int *)valp;
940 	break;
941     case NC_UINT:
942 	val = *(unsigned int *)valp;
943 	break;
944     case NC_INT64:
945 	val = *(long long *)valp;
946 	break;
947     case NC_UINT64:
948 	val = *(long long *)valp;
949 	break;
950     default:
951 	error("bad base type for enum");
952 	break;
953     }
954     NC_CHECK( nc_inq_enum_ident(typ->ncid, typ->tid, val, symbol));
955     sbuf_cpy(sfbf, symbol);
956     return sbuf_len(sfbf);
957 }
958 
959 /* Given an opaque type size and opaque value, convert to a string,
960  * represented as hexadecimal characters, returning number of chars in
961  * output string */
962 int
ncopaque_val_as_hex(size_t size,char * sout,const void * valp)963 ncopaque_val_as_hex(size_t size, char *sout, const void *valp) {
964     const unsigned char *cp = valp;
965     char *sp = sout;
966     int i;
967     char *prefix = "0X";
968     int prelen = strlen(prefix);
969 
970     snprintf(sp, prelen + 1, "%s", prefix);
971     sp += prelen;
972     for(i = 0; i < size; i++) {
973 	int res;
974 	res = snprintf(sp, prelen + 1, "%.2X", *cp++);
975 	assert (res == 2);
976 	sp += 2;
977     }
978     *sp = '\0';
979     return 2*size + prelen;
980 }
981 
982 /* Convert an opaque value to a string, represented as hexadecimal
983  * characters */
984 int
ncopaque_typ_tostring(const nctype_t * typ,safebuf_t * sfbf,const void * valp)985 ncopaque_typ_tostring(const nctype_t *typ, safebuf_t *sfbf,
986 		      const void *valp) {
987     char* sout = (char *) emalloc(2 * typ->size + strlen("0X") + 1);
988     (void) ncopaque_val_as_hex(typ->size, sout, valp);
989     sbuf_cpy(sfbf, sout);
990     free(sout);
991     return sbuf_len(sfbf);
992 }
993 
994 /* Convert a vlen value to a string, by using tostring function for base type */
995 int
ncvlen_typ_tostring(const nctype_t * tinfo,safebuf_t * sfbf,const void * valp)996 ncvlen_typ_tostring(const nctype_t *tinfo, safebuf_t *sfbf, const void *valp) {
997     nc_type base_type = tinfo->base_tid;
998     nctype_t *base_info = get_typeinfo(base_type);
999     size_t base_size = base_info->size;
1000     size_t len = ((nc_vlen_t *)valp)->len;
1001     typ_tostring_func base_typ_tostring = base_info->typ_tostring;
1002     size_t i;
1003     const char *vp;		/* instead of void* so can increment to next */
1004     safebuf_t* sout2 = sbuf_new();
1005 
1006     sbuf_cpy(sfbf, "{");
1007     /* put each val in sout2, then append sout2 to sfbf */
1008     vp = ((nc_vlen_t *)valp)->p;
1009     for(i = 0; i < len; i++) {
1010 	(void) base_typ_tostring(base_info, sout2, vp);
1011 	sbuf_catb(sfbf, sout2);
1012 	if(i < len - 1) {
1013 	    sbuf_cat(sfbf, ", ");
1014 	}
1015 	vp += base_size;
1016     }
1017     sbuf_cat(sfbf, "}");
1018     sbuf_free(sout2);
1019     return sbuf_len(sfbf);
1020 }
1021 
1022 /*
1023  * Print a number of char values as a text string.
1024  */
1025 static int
chars_tostring(safebuf_t * sbuf,size_t len,const char * vals)1026 chars_tostring(
1027     safebuf_t *sbuf,		/* for output */
1028     size_t len,			/* number of characters */
1029     const char *vals		/* pointer to block of values */
1030     )
1031 {
1032     long iel;
1033     const char *sp;
1034     char *sout = (char *)emalloc(4*len + 5); /* max len of string */
1035     char *cp = sout;
1036     *cp++ = '"';
1037 
1038     /* adjust len so trailing nulls don't get printed */
1039     sp = vals + len;
1040     while (len != 0 && *--sp == '\0')
1041 	len--;
1042     for (iel = 0; iel < len; iel++) {
1043 	unsigned char uc;
1044 	switch (uc = *vals++ & 0377) {
1045 	case '\b':
1046 	case '\f':
1047 	case '\n':
1048 	case '\r':
1049 	case '\t':
1050 	case '\v':
1051 	case '\\':
1052 	case '\'':
1053 	case '\"':
1054 	    *cp++ = '\\';
1055 	    *cp++ = *(char *)&uc; /* just copy, even if char is signed */
1056 	    break;
1057 	default:
1058 	    if (isprint(uc))
1059 		*cp++ = *(char *)&uc; /* just copy, even if char is signed */
1060 	    else {
1061 		sprintf(cp,"\\%.3o",uc);
1062 		cp += 4;
1063 	    }
1064 	    break;
1065 	}
1066     }
1067     *cp++ = '"';
1068     *cp = '\0';
1069     sbuf_cpy(sbuf, sout);
1070     free(sout);
1071     return sbuf_len(sbuf);
1072 }
1073 
1074 
1075 /* Convert a compound value to a string, by using tostring function for
1076    each member field */
1077 int
nccomp_typ_tostring(const nctype_t * tinfo,safebuf_t * sfbf,const void * valp)1078 nccomp_typ_tostring(const nctype_t *tinfo, safebuf_t *sfbf, const void *valp) {
1079     int nfields = tinfo->nfields;
1080     int fidx;			/* field id */
1081     safebuf_t* sout2 = sbuf_new();
1082 
1083     sbuf_cpy(sfbf, "{");
1084     /* put each val in sout2, then append sout2 to sfbf if enough room */
1085     for (fidx = 0; fidx < nfields; fidx++) {
1086 	size_t offset = tinfo->offsets[fidx];
1087 	nc_type fid = tinfo->fids[fidx];		/* field type id */
1088 	nctype_t *finfo = get_typeinfo(fid);
1089 
1090 	if(tinfo->ranks[fidx] == 0) {
1091 	    if(finfo->tid == NC_CHAR) { /* aggregate char rows into strings */
1092 		chars_tostring(sout2, 1, ((char *)valp + offset));
1093 	    } else {
1094 		finfo->typ_tostring(finfo, sout2, ((char *)valp + offset));
1095 	    }
1096 	} else {		/* this field is an array */
1097 	    int i;		/* array element counter when rank > 0 */
1098 	    void *vp = (char *)valp + offset;
1099 	    safebuf_t *sout3 = sbuf_new();
1100 	    sbuf_cpy(sout2, "{");
1101 	    if(finfo->tid == NC_CHAR) { /* aggregate char rows into strings */
1102 		int rank = tinfo->ranks[fidx];
1103 		size_t nstrings;
1104 		size_t slen;
1105 		int j;
1106 		slen = tinfo->sides[fidx][rank-1];
1107 		nstrings = 1;	/* product of all but last array dimension */
1108 		for(j=0; j < rank-1; j++) {
1109 		    nstrings *= tinfo->sides[fidx][j];
1110 		}
1111 		for(i=0; i < nstrings; i++) { /* loop on product of all but
1112 						 last index of array */
1113 		    chars_tostring(sout3, slen, (char *)vp);
1114 		    vp = (char *)vp + slen;
1115 		    if(i < nstrings - 1) {
1116 			sbuf_cat(sout3, ", ");
1117 		    }
1118 		    sbuf_catb(sout2, sout3);
1119 		}
1120 	    } else {
1121 		for(i = 0; i < tinfo->nvals[fidx]; i++) {
1122 		    (void) finfo->typ_tostring(finfo, sout3, vp);
1123 		    vp = (char *)vp + finfo->size;
1124 		    if(i < tinfo->nvals[fidx] - 1) {
1125 			sbuf_cat(sout3, ", ");
1126 		    }
1127 		    sbuf_catb(sout2, sout3);
1128 		}
1129 	    }
1130 	    sbuf_cat(sout2, "}");
1131 	    sbuf_free(sout3);
1132 	}
1133 	sbuf_catb(sfbf, sout2);
1134 	if(fidx < nfields - 1) {
1135 	    sbuf_cat(sfbf, ", ");
1136 	}
1137     }
1138     sbuf_cat(sfbf, "}");
1139     sbuf_free(sout2);
1140     return sbuf_len(sfbf);
1141 }
1142 #endif	/* USE_NETCDF4 */
1143 
1144 int
ncbyte_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1145 ncbyte_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1146     char sout[PRIM_LEN];
1147     int res;
1148     res = snprintf(sout, PRIM_LEN, varp->fmt, *(signed char *)valp);
1149     assert(res < PRIM_LEN);
1150     sbuf_cpy(sfbf, sout);
1151     return sbuf_len(sfbf);
1152 }
1153 
1154 int
ncchar_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1155 ncchar_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1156     char sout[PRIM_LEN];
1157     int res;
1158     res = snprintf(sout, PRIM_LEN, varp->fmt, *(char *)valp);
1159     assert(res < PRIM_LEN);
1160     sbuf_cpy(sfbf, sout);
1161     return sbuf_len(sfbf);
1162 }
1163 
1164 int
ncshort_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1165 ncshort_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1166     char sout[PRIM_LEN];
1167     int res;
1168     res = snprintf(sout, PRIM_LEN, varp->fmt, *(short *)valp);
1169     assert(res < PRIM_LEN);
1170     sbuf_cpy(sfbf, sout);
1171     return sbuf_len(sfbf);
1172 }
1173 
1174 int
ncint_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1175 ncint_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1176     char sout[PRIM_LEN];
1177     int res;
1178     res = snprintf(sout, PRIM_LEN, varp->fmt, *(int *)valp);
1179     assert(res < PRIM_LEN);
1180     sbuf_cpy(sfbf, sout);
1181     return sbuf_len(sfbf);
1182 }
1183 
1184 int
ncfloat_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1185 ncfloat_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1186     char sout[PRIM_LEN];
1187     float vv = *(float *)valp;
1188     if(isfinite(vv)) {
1189 	int res;
1190 	res = snprintf(sout, PRIM_LEN, varp->fmt, vv);
1191 	assert(res < PRIM_LEN);
1192     } else {
1193 	float_special_tostring(vv, sout);
1194     }
1195     sbuf_cpy(sfbf, sout);
1196     return sbuf_len(sfbf);
1197 }
1198 
1199 int
ncdouble_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1200 ncdouble_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1201     char sout[PRIM_LEN];
1202     double vv = *(double *)valp;
1203     if(isfinite(vv)) {
1204 	int res;
1205 	res = snprintf(sout, PRIM_LEN, varp->fmt, vv);
1206 	assert(res < PRIM_LEN);
1207     } else {
1208 	double_special_tostring(vv, sout);
1209     }
1210     sbuf_cpy(sfbf, sout);
1211     return sbuf_len(sfbf);
1212 }
1213 
1214 /* Convert value of any numeric type to a double.  Beware, this may
1215  * lose precision for values of type NC_INT64 or NC_UINT64 */
1216 static
to_double(const ncvar_t * varp,const void * valp)1217 double to_double(const ncvar_t *varp, const void *valp) {
1218     double dd = 0.0;
1219     switch (varp->type) {
1220     case NC_BYTE:
1221 	dd = *(signed char *)valp;
1222 	break;
1223     case NC_SHORT:
1224 	dd = *(short *)valp;
1225 	break;
1226     case NC_INT:
1227 	dd = *(int *)valp;
1228 	break;
1229     case NC_FLOAT:
1230 	dd = *(float *)valp;
1231 	break;
1232     case NC_DOUBLE:
1233 	dd = *(double *)valp;
1234 	break;
1235     case NC_UBYTE:
1236 	dd = *(unsigned char *)valp;
1237 	break;
1238     case NC_USHORT:
1239 	dd = *(unsigned short *)valp;
1240 	break;
1241     case NC_UINT:
1242 	dd = *(unsigned int *)valp;
1243 	break;
1244     case NC_INT64:
1245 	dd = *(long long *)valp;
1246 	break;
1247     case NC_UINT64:
1248 	dd = *(unsigned long long *)valp;
1249 	break;
1250     default:
1251 	error("to_double: type not numeric primitive");
1252     }
1253     return dd;
1254 }
1255 
1256 int
nctime_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1257 nctime_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1258     char sout[PRIM_LEN];
1259     double vv = to_double(varp, valp);
1260     int separator = formatting_specs.iso_separator ? 'T' : ' ';
1261     if(isfinite(vv)) {
1262 	int oldopts = 0;
1263 	int newopts = 0;
1264 	int res;
1265 	sout[0]='"';
1266 	/* Make nctime dump error messages */
1267 	oldopts = cdSetErrOpts(0);
1268 	newopts = oldopts | CU_VERBOSE;
1269 	cdSetErrOpts(newopts);
1270 	cdRel2Iso(varp->timeinfo->calendar, varp->timeinfo->units, separator, vv, &sout[1]);
1271 	cdSetErrOpts(oldopts);
1272 	res = strlen(sout);
1273 	sout[res++] = '"';
1274 	sout[res] = '\0';
1275 	assert(res < PRIM_LEN);
1276     } else {
1277 	double_special_tostring(vv, sout);
1278     }
1279     sbuf_cpy(sfbf, sout);
1280     return sbuf_len(sfbf);
1281 }
1282 
1283 int
ncubyte_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1284 ncubyte_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1285     char sout[PRIM_LEN];
1286     int res;
1287     res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned char *)valp);
1288     assert(res < PRIM_LEN);
1289     sbuf_cpy(sfbf, sout);
1290     return sbuf_len(sfbf);
1291 }
1292 
1293 int
ncushort_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1294 ncushort_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1295     char sout[PRIM_LEN];
1296     int res;
1297     res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned short *)valp);
1298     assert(res < PRIM_LEN);
1299     sbuf_cpy(sfbf, sout);
1300     return sbuf_len(sfbf);
1301 }
1302 
1303 int
ncuint_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1304 ncuint_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1305     char sout[PRIM_LEN];
1306     int res;
1307     res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned int *)valp);
1308     assert(res < PRIM_LEN);
1309     sbuf_cpy(sfbf, sout);
1310     return sbuf_len(sfbf);
1311 }
1312 
1313 int
ncint64_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1314 ncint64_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1315     char sout[PRIM_LEN];
1316     int res;
1317     res = snprintf(sout, PRIM_LEN, varp->fmt, *(long long *)valp);
1318     assert(res < PRIM_LEN);
1319     sbuf_cpy(sfbf, sout);
1320     return sbuf_len(sfbf);
1321 }
1322 
1323 int
ncuint64_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1324 ncuint64_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1325     char sout[PRIM_LEN];
1326     int res;
1327     res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned long long *)valp);
1328     assert(res < PRIM_LEN);
1329     sbuf_cpy(sfbf, sout);
1330     return sbuf_len(sfbf);
1331 }
1332 
1333 int
ncstring_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1334 ncstring_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1335     return ncstring_typ_tostring(varp->tinfo, sfbf, valp);
1336 }
1337 
1338 #ifdef USE_NETCDF4
1339 int
ncenum_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1340 ncenum_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1341     return ncenum_typ_tostring(varp->tinfo, sfbf, valp);
1342 }
1343 
1344 /* Convert an opaque value to a string, represented as hexadecimal
1345  * characters */
1346 int
ncopaque_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1347 ncopaque_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1348     return ncopaque_typ_tostring(varp->tinfo, sfbf, valp);
1349 }
1350 
1351 /* Convert a vlen value to a string, by using tostring function for base type */
1352 int
ncvlen_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1353 ncvlen_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1354     return ncvlen_typ_tostring(varp->tinfo, sfbf, valp);
1355 }
1356 
1357 int
nccomp_val_tostring(const ncvar_t * varp,safebuf_t * sfbf,const void * valp)1358 nccomp_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
1359     return nccomp_typ_tostring(varp->tinfo, sfbf, valp);
1360 }
1361 #endif /*USE_NETCDF4*/
1362 
1363 static val_equals_func eq_funcs[] = {
1364 	ncbyte_val_equals,
1365 	ncchar_val_equals,
1366 	ncshort_val_equals,
1367 	ncint_val_equals,
1368 	ncfloat_val_equals,
1369 	ncdouble_val_equals,
1370 	ncubyte_val_equals,
1371 	ncushort_val_equals,
1372 	ncuint_val_equals,
1373 	ncint64_val_equals,
1374 	ncuint64_val_equals,
1375 	ncstring_val_equals
1376     };
1377 
1378 static typ_tostring_func ts_funcs[] = {
1379 	ncbyte_typ_tostring,
1380 	ncchar_typ_tostring,
1381 	ncshort_typ_tostring,
1382 	ncint_typ_tostring,
1383 	ncfloat_typ_tostring,
1384 	ncdouble_typ_tostring,
1385 	ncubyte_typ_tostring,
1386 	ncushort_typ_tostring,
1387 	ncuint_typ_tostring,
1388 	ncint64_typ_tostring,
1389 	ncuint64_typ_tostring,
1390 	ncstring_typ_tostring
1391     };
1392 
1393 
1394 /* Set function pointer of function to convert a value to a string for
1395  * the variable pointed to by varp. */
1396 void
set_tostring_func(ncvar_t * varp)1397 set_tostring_func(ncvar_t *varp) {
1398     val_tostring_func tostring_funcs[] = {
1399 	ncbyte_val_tostring,
1400 	ncchar_val_tostring,
1401 	ncshort_val_tostring,
1402 	ncint_val_tostring,
1403 	ncfloat_val_tostring,
1404 	ncdouble_val_tostring,
1405 	ncubyte_val_tostring,
1406 	ncushort_val_tostring,
1407 	ncuint_val_tostring,
1408 	ncint64_val_tostring,
1409 	ncuint64_val_tostring,
1410 	ncstring_val_tostring
1411     };
1412     if(varp->has_timeval && formatting_specs.string_times) {
1413 	varp->val_tostring = (val_tostring_func) nctime_val_tostring;
1414 	return;
1415     }
1416     if( !is_user_defined_type(varp->type) ) {
1417 	varp->val_tostring = tostring_funcs[varp->type - 1];
1418 	return;
1419     }
1420 #ifdef USE_NETCDF4
1421     switch(varp->tinfo->class) {
1422     case NC_VLEN:
1423 	varp->val_tostring = (val_tostring_func) ncvlen_val_tostring;
1424 	break;
1425     case NC_OPAQUE:
1426 	varp->val_tostring = (val_tostring_func) ncopaque_val_tostring;
1427 	break;
1428     case NC_ENUM:
1429 	varp->val_tostring = (val_tostring_func) ncenum_val_tostring;
1430 	break;
1431     case NC_COMPOUND:
1432 	varp->val_tostring = (val_tostring_func) nccomp_val_tostring;
1433 	break;
1434     default:
1435 	error("unrecognized class of user defined type: %d",
1436 	      varp->tinfo->class);
1437     }
1438 #endif /* USE_NETCDF4 */
1439     return;
1440 }
1441 
1442 
1443 /* Initialize typelist with primitive types.  For netCDF-3 only need primitive
1444    types. */
1445 static void
init_prim_types(int ncid)1446 init_prim_types(int ncid) {
1447     nctype_t *tp;
1448     int i;
1449     int types[] =
1450 {
1451 	NC_BYTE,
1452 	NC_CHAR,
1453 	NC_SHORT,
1454 	NC_INT,
1455 	NC_FLOAT,
1456 	NC_DOUBLE,
1457 	NC_UBYTE,
1458 	NC_USHORT,
1459 	NC_UINT,
1460 	NC_INT64,
1461 	NC_UINT64,
1462 	NC_STRING
1463     };
1464     size_t sizes[] = {
1465 	sizeof(char),
1466 	sizeof(char),
1467 	sizeof(short),
1468 	sizeof(int),
1469 	sizeof(float),
1470 	sizeof(double),
1471 	sizeof(unsigned char),
1472 	sizeof(unsigned short),
1473 	sizeof(unsigned int),
1474 	sizeof(long long),
1475 	sizeof(unsigned long long),
1476 	sizeof(char **)
1477     };
1478 
1479 #if 0
1480     for(i=0; i < sizeof(types)/sizeof(int); i++) {
1481 #else
1482     for(i=0; i < max_atomic_type; i++) {
1483 #endif
1484 	tp = (nctype_t *)emalloc(sizeof(nctype_t));
1485 	tp->ncid = ncid;
1486 	tp->tid = types[i];
1487 	tp->name = strdup(prim_type_name(tp->tid));
1488 	tp->grps = 0;
1489 	tp->class = 0;		/* primitive type */
1490 	tp->size = sizes[i];
1491 	tp->base_tid = NC_NAT;	/* not used for primitive types */
1492 	tp->nfields = 0;	/* not used for primitive types */
1493 	tp->fmt = get_default_fmt(types[i]);
1494 	tp->fids = 0;		/* not used for primitive types */
1495 	tp->offsets = 0;	/* not used for primitive types */
1496 	tp->ranks = 0;		/* not used for primitive types */
1497 	tp->sides = 0;		/* not used for primitive types */
1498 	tp->nvals = 0;		/* not used for primitive types */
1499 	tp->val_equals = (val_equals_func) eq_funcs[i];
1500 	tp->typ_tostring = (typ_tostring_func) ts_funcs[i];
1501 	typeadd(tp);
1502     }
1503 }
1504 
1505 /* Initialize typelist.
1506  *
1507  * This must be done over all groups in netCDF-4, because
1508  * variables in one group may be declared using types in a
1509  * different group.  For netCDF-3, this is just the info about
1510  * primitive types.
1511  */
1512 void
1513 init_types(int ncid) {
1514 #ifdef USE_NETCDF4
1515     int ntypes;
1516 #endif
1517     if (max_type == 0) {	/* if called for first time */
1518 	int maxtype = max_typeid(ncid);
1519 	int i;
1520 	nctypes = (nctype_t **) emalloc((maxtype + 2) * sizeof(nctype_t *));
1521 	for(i=0; i < maxtype+1; i++)
1522 	    nctypes[i] = NULL;	/* so can later skip over unused type slots */
1523 	init_prim_types(ncid);
1524     }
1525 
1526 #ifdef USE_NETCDF4
1527    /* Are there any user defined types in this group? */
1528    NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) );
1529    if (ntypes)
1530    {
1531       int t;
1532       int *typeids = emalloc((ntypes + 1) * sizeof(int));
1533       NC_CHECK( nc_inq_typeids(ncid, NULL, typeids) );
1534       for (t = 0; t < ntypes; t++) {
1535 	  nctype_t *tinfo;	/* details about the type */
1536 	  char type_name[NC_MAX_NAME + 1];
1537 	  size_t group_name_len;
1538 	  char* group_name;
1539 	  int fidx;		/* for compound type, field index */
1540 
1541 	  tinfo = (nctype_t *) emalloc(sizeof(nctype_t));
1542 
1543 	  NC_CHECK( nc_inq_user_type(ncid, typeids[t], type_name, &tinfo->size,
1544 		                     &tinfo->base_tid, &tinfo->nfields,
1545 				     &tinfo->class) );
1546 	  tinfo->tid = typeids[t];
1547 	  tinfo->ncid = ncid;
1548 	  tinfo->name = strdup(type_name);
1549 	  tinfo->grps = 0;
1550 	  if(tinfo->class == NC_VLEN) {
1551 	      tinfo->size = sizeof(nc_vlen_t); /* not size of base type */
1552 	  }
1553 	  NC_CHECK( nc_inq_grpname_full(ncid, &group_name_len, NULL) );
1554 	  group_name = (char *) emalloc(group_name_len + 1);
1555 	  NC_CHECK( nc_inq_grpname_full(ncid, &group_name_len, group_name) );
1556 
1557 	  tinfo->grps = strdup(group_name);
1558 	  free(group_name);
1559 	  switch(tinfo->class) {
1560 	  case NC_ENUM:
1561 	      tinfo->val_equals = eq_funcs[tinfo->base_tid-1];
1562 	      tinfo->typ_tostring = (typ_tostring_func) ncenum_typ_tostring;
1563 	      break;
1564 	  case NC_COMPOUND:
1565 	      tinfo->val_equals = (val_equals_func) nccomp_val_equals;
1566 	      tinfo->typ_tostring = (typ_tostring_func) nccomp_typ_tostring;
1567 	      tinfo->fids = (nc_type *) emalloc((tinfo->nfields + 1)
1568 						  * sizeof(nc_type));
1569 	      tinfo->offsets = (size_t *) emalloc((tinfo->nfields + 1)
1570 						  * sizeof(size_t));
1571 	      tinfo->ranks = (int *) emalloc((tinfo->nfields + 1)
1572 					     * sizeof(int));
1573 	      tinfo->sides = (int **) emalloc((tinfo->nfields + 1)
1574 						 * sizeof(int *));
1575 	      tinfo->nvals = (int *) emalloc((tinfo->nfields + 1)
1576 					     * sizeof(int));
1577 	      for (fidx = 0; fidx < tinfo->nfields; fidx++) {
1578 		  size_t offset;
1579 		  nc_type ftype;
1580 		  int rank;
1581 		  int *sides;
1582 		  int i;
1583 		  sides = NULL;
1584 		  NC_CHECK( nc_inq_compound_field(ncid, tinfo->tid, fidx, NULL,
1585 						  &offset, &ftype, &rank,
1586 						  sides) );
1587 		  if(rank > 0) sides = (int *) emalloc(rank * sizeof(int));
1588 		  NC_CHECK( nc_inq_compound_field(ncid, tinfo->tid, fidx, NULL,
1589 						  NULL, NULL, NULL, sides) );
1590 		  tinfo->fids[fidx] = ftype;
1591 		  tinfo->offsets[fidx] = offset;
1592 		  tinfo->ranks[fidx] = rank;
1593 		  if (rank > 0)
1594 		      tinfo->sides[fidx] = (int *) emalloc(rank * sizeof(int));
1595 		  tinfo->nvals[fidx] = 1;
1596 		  for(i = 0; i < rank; i++) {
1597 		      tinfo->sides[fidx][i] = sides[i];
1598 		      tinfo->nvals[fidx] *= sides[i];
1599 		  }
1600 		  if (rank > 0)
1601 		      free(sides);
1602 	      }
1603 	      break;
1604 	  case NC_VLEN:
1605 	      tinfo->val_equals = (val_equals_func) ncvlen_val_equals;
1606 	      tinfo->typ_tostring = (typ_tostring_func) ncvlen_typ_tostring;
1607 	      break;
1608 	  case NC_OPAQUE:
1609 	      tinfo->val_equals = (val_equals_func) ncopaque_val_equals;
1610 	      tinfo->typ_tostring = (typ_tostring_func) ncopaque_typ_tostring;
1611 	      break;
1612 	  default:
1613 	      error("bad class: %d", tinfo->class);
1614 	      break;
1615 	  }
1616 
1617 	  typeadd(tinfo);
1618       }
1619       free(typeids);
1620    }
1621    /* For netCDF-4, check to see if this group has any subgroups and call
1622     * recursively on each of them. */
1623    {
1624       int g, numgrps, *ncids;
1625 
1626       /* See how many groups there are. */
1627       NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) );
1628       if (numgrps > 0) {
1629 	  ncids = (int *) emalloc(numgrps * sizeof(int));
1630 	  /* Get the list of group ids. */
1631 	  NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
1632 	  /* Call this function for each group. */
1633 	  for (g = 0; g < numgrps; g++) {
1634 	      init_types(ncids[g]);
1635 	  }
1636 	  free(ncids);
1637       }
1638    }
1639 #endif /* USE_NETCDF4 */
1640 }
1641 
1642 
1643 /*
1644  * return 1 if varid identifies a coordinate variable
1645  * else return 0
1646  */
1647 int
1648 iscoordvar(int ncid, int varid)
1649 {
1650     int ndims, ndims1;
1651     int dimid;
1652     int* dimids = 0;
1653     ncdim_t *dims = 0;
1654 #ifdef USE_NETCDF4
1655     int include_parents = 1;
1656 #endif
1657     int is_coord = 0;		/* true if variable is a coordinate variable */
1658     char varname[NC_MAX_NAME];
1659     int varndims;
1660 
1661     do {	  /* be safe in case someone is currently adding
1662 		   * dimensions */
1663 #ifdef USE_NETCDF4
1664 	NC_CHECK( nc_inq_dimids(ncid, &ndims, NULL, include_parents ) );
1665 #else
1666 	NC_CHECK( nc_inq_ndims(ncid, &ndims) );
1667 #endif
1668 	if (dims)
1669 	    free(dims);
1670 	dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
1671 	if (dimids)
1672 	    free(dimids);
1673 	dimids = (int *) emalloc((ndims + 1) * sizeof(int));
1674 #ifdef USE_NETCDF4
1675 	NC_CHECK( nc_inq_dimids(ncid, &ndims1, dimids, include_parents ) );
1676 #else
1677 	{
1678 	    int i;
1679 	    for(i = 0; i < ndims; i++) {
1680 		dimids[i] = i;	/* for netCDF-3, dimids are 0, 1, ..., ndims-1 */
1681 	    }
1682 	    NC_CHECK( nc_inq_ndims(ncid, &ndims1) );
1683 	}
1684 #endif	/* USE_NETCDF4 */
1685     } while (ndims != ndims1);
1686 
1687     for (dimid = 0; dimid < ndims; dimid++) {
1688 	NC_CHECK( nc_inq_dimname(ncid, dimids[dimid], dims[dimid].name) );
1689     }
1690     NC_CHECK( nc_inq_varname(ncid, varid, varname) );
1691     NC_CHECK( nc_inq_varndims(ncid, varid, &varndims) );
1692 
1693     for (dimid = 0; dimid < ndims; dimid++) {
1694 	if (strcmp(dims[dimid].name, varname) == 0 && varndims == 1) {
1695 	    is_coord = 1;
1696 	    break;
1697 	}
1698     }
1699     if(dims)
1700 	free(dims);
1701     if(dimids)
1702 	free(dimids);
1703     return is_coord;
1704 }
1705 
1706 
1707 /* Return true if user-defined type */
1708 int
1709 is_user_defined_type(nc_type type) {
1710     nctype_t *typeinfop = get_typeinfo(type);
1711     return (typeinfop->class > 0);
1712 }
1713 
1714 
1715 /*
1716  * Return name of type in user-allocated space, whether built-in
1717  * primitive type or user-defined type.  Note: name must have enough
1718  * space allocated to hold type name.
1719  */
1720 void
1721 get_type_name(int ncid, nc_type type, char *name)
1722 {
1723 #ifdef USE_NETCDF4
1724     if (is_user_defined_type(type)) {
1725 	NC_CHECK(nc_inq_user_type(ncid, type, name, NULL, NULL, NULL, NULL));
1726     } else {
1727 	strncpy(name, prim_type_name(type), NC_MAX_NAME + 1);
1728     }
1729 #else
1730     strncpy(name, prim_type_name(type), NC_MAX_NAME + 1);
1731 #endif /* USE_NETCDF4 */
1732 }
1733 
1734 /*
1735  * Print type name with CDL escapes for special characters.  locid is
1736  * the id of the group in which the type is referenced, which is
1737  * needed to determine whether an absolute type name must be printed.
1738  * If the type is defined in the referenced group or in some ancestor
1739  * group, only the simple type name is printed.  If the type is
1740  * defined in some other non-ancestor group, an absolute path for the
1741  * typename is printed instead.
1742  */
1743 void
1744 print_type_name(int locid, int typeid) {
1745     char *ename;
1746 #ifdef USE_NETCDF4
1747     char name[NC_MAX_NAME+1];
1748     int type_inherited = 0;
1749     int curlocid;		/* group we are searching in */
1750     int parent_groupid = locid;
1751     int ntypes;
1752     int stat;
1753 #endif
1754 
1755     assert(typeid > 0 && typeid <= max_type);
1756     ename = escaped_name(nctypes[typeid]->name);
1757 #ifdef USE_NETCDF4
1758     if(is_user_defined_type(typeid)) {
1759 	/* determine if type is inherited, that is if defined in this
1760 	 * group or any ancestor group */
1761 	name[NC_MAX_NAME] = '\0';
1762 	strncpy(name,nctypes[typeid]->name,NC_MAX_NAME);
1763 	do {
1764 	    curlocid = parent_groupid;
1765 	    NC_CHECK( nc_inq_typeids(curlocid, &ntypes, NULL) );
1766 	    if(ntypes > 0) {
1767 		int *typeids = (int *) emalloc((ntypes + 1) * sizeof(int));
1768 		int i;
1769 		NC_CHECK( nc_inq_typeids(curlocid, &ntypes, typeids) );
1770 		for(i = 0; i < ntypes; i++) {
1771 		    char curname[NC_MAX_NAME];
1772 		    NC_CHECK( nc_inq_type(curlocid, typeids[i], curname, NULL) );
1773 		    if(strncmp(name, curname, NC_MAX_NAME) == 0) {
1774 			type_inherited = 1;
1775 			break;
1776 		    }
1777 		}
1778 		free(typeids);
1779 		if(type_inherited)
1780 		    break;
1781 	    }
1782 	    stat = nc_inq_grp_parent(curlocid, &parent_groupid);
1783 	} while (stat != NC_ENOGRP && stat != NC_ENOTNC4);
1784 
1785 	if (type_inherited == 0) {
1786 	    char *gname = nctypes[typeid]->grps;
1787 	    print_name(gname);
1788 	    fputs("/", stdout);
1789 	}
1790     }
1791 #endif	/*  USE_NETCDF4 */
1792     fputs(ename, stdout);
1793     free(ename);
1794 }
1795 
1796 /* Allocate and initialize table of unlimited dimensions for ncid, for
1797  * use by is_unlim_dim() function.  If ncid is a subgroup of a netCDF
1798  * dataset, the table will still be initialized for the whole dataset
1799  * in which the subgroup resides. */
1800 #ifdef USE_NETCDF4
1801 static int
1802 init_is_unlim(int ncid, int **is_unlim_p)
1803 {
1804     int num_grps;	 /* total number of groups */
1805     int num_dims = 0;    /* total number of dimensions in all groups */
1806     int num_undims = 0;  /* total number of unlimited dimensions in all groups */
1807     int *grpids = NULL;	 /* temporary list of all grpids */
1808     int igrp;
1809     int grpid;
1810 
1811     /* if ncid is not root group, find its ancestor root group id */
1812     int status = nc_inq_grp_parent(ncid, &grpid);
1813     while(status == NC_NOERR && grpid != ncid) {
1814 	ncid = grpid;
1815 	status = nc_inq_grp_parent(ncid, &grpid);
1816     }
1817     if (status != NC_ENOGRP)
1818 	return NC_EBADGRPID;
1819     /* Now ncid is root group.  Get total number of groups and their ids */
1820     NC_CHECK( nc_inq_grps_full(ncid, &num_grps, NULL) );
1821     grpids = emalloc((num_grps + 1) * sizeof(int));
1822     NC_CHECK( nc_inq_grps_full(ncid, &num_grps, grpids) );
1823 #define DONT_INCLUDE_PARENTS 0
1824     /* Get all dimensions in groups and info about which ones are unlimited */
1825     for(igrp = 0; igrp < num_grps; igrp++) {
1826 	int ndims;
1827 	grpid = grpids[igrp];
1828 	NC_CHECK( nc_inq_dimids(grpid, &ndims, NULL, DONT_INCLUDE_PARENTS) );
1829 	num_dims += ndims;
1830     }
1831     *is_unlim_p = emalloc((num_dims + 1) * sizeof(int));
1832     for(igrp = 0; igrp < num_grps; igrp++) {
1833 	int ndims, idim, *dimids, nundims;
1834 	grpid = grpids[igrp];
1835 	NC_CHECK( nc_inq_dimids(grpid, &ndims, NULL, DONT_INCLUDE_PARENTS) );
1836 	dimids = emalloc((ndims + 1) * sizeof(int));
1837 	NC_CHECK( nc_inq_dimids(grpid, &ndims, dimids, DONT_INCLUDE_PARENTS) );
1838 	/* mark all dims in this group as fixed-size */
1839 	for(idim = 0; idim < ndims; idim++) {
1840 	    (*is_unlim_p)[dimids[idim]] = 0;
1841 	}
1842 	NC_CHECK( nc_inq_unlimdims(grpid, &nundims, dimids) );
1843 	assert(nundims <= ndims);
1844 	/* mark the subset of dims in this group that are unlimited */
1845 	for(idim = 0; idim < nundims; idim++) {
1846 	    (*is_unlim_p)[dimids[idim]] = 1;
1847 	    num_undims++;
1848 	}
1849 	if(dimids)
1850 	    free(dimids);
1851     }
1852     free(grpids);
1853     return NC_NOERR;
1854 }
1855 #endif	/*  USE_NETCDF4 */
1856 
1857 /* TODO: make list of these arrays for multiple open datasets, such as
1858  * the idnode_t lists above.  For now, we just have one of these, for
1859  * the unique input dataset for this invocation of ncdump. */
1860 
1861 #define UNLIM_NOT_INITIALIZED (-1)
1862 
1863 /* Is dimid the dimension ID of an unlimited dimension?  */
1864 bool_t
1865 is_unlim_dim(int ncid, int dimid) {
1866     bool_t result;		/* 0 if fixed, 1 if unlimited size */
1867     static int for_ncid = UNLIM_NOT_INITIALIZED; /* ensure only ever called for one ncid */
1868 #ifdef USE_NETCDF4
1869     static int *is_unlim = NULL; /* gets allocated by init_is_unlim() */
1870     if(for_ncid == UNLIM_NOT_INITIALIZED) {
1871       NC_CHECK(init_is_unlim(ncid, &is_unlim));
1872       for_ncid = ncid;
1873     }
1874     assert(is_unlim);
1875     result = is_unlim[dimid];	/* 0 if fixed, 1 if unlimited size */
1876 #else
1877     static int unlimdimid;
1878     if(for_ncid == UNLIM_NOT_INITIALIZED) {
1879 	NC_CHECK( nc_inq_unlimdim(ncid, &unlimdimid) );
1880 	for_ncid = ncid;
1881     }
1882     result = (dimid == unlimdimid) ;
1883 #endif 	 /* USE_NETCDF4 */
1884     return result;
1885 }
1886