[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[netCDF #CNJ-934792]: Possible bug in netCDF C-API



Lynton,

> I have been experiencing problems with the attached netcDF4 dataset
> "efitOut.nc". The ncdump programme crashes part way through
> interpreting this file.
>
> I suspect that the problem is due to unlimited dimensions.

A fix is in the next snapshot release, or you can use the attached ncdump.c.

Thanks again for the bug report.

--Russ


Russ Rew                                         UCAR Unidata Program
address@hidden                     http://www.unidata.ucar.edu



Ticket Details
===================
Ticket ID: CNJ-934792
Department: Support netCDF
Priority: Normal
Status: Closed
/*********************************************************************
 *   Copyright 2008, University Corporation for Atmospheric Research
 *   See netcdf/README file for copying and redistribution conditions.
 *   $Header: /upc/share/CVS/netcdf-3/ncdump/ncdump.c,v 1.146 2009/10/19 
19:16:11 russ Exp $
 *********************************************************************/

#include <config.h>
#include <stdio.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <math.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif  /* HAVE_LOCALE_H */
#include <netcdf.h>
#include "nctime.h"             /* new iso time and calendar stuff */
#include "ncdump.h"
#include "dumplib.h"
#include "vardata.h"
#include "indent.h"
#include "isnan.h"
#include "cdl.h"

#define int64_t long long
#define uint64_t unsigned long long

#define STREQ(a, b)     (*(a) == *(b) && strcmp((a), (b)) == 0)

char *progname;

static void
usage(void)
{
#define USAGE   "\
  [-c]             Coordinate variable data and header information\n\
  [-h]             Header information only, no data\n\
  [-v var1[,...]]  Data for variable(s) <var1>,... only\n\
  [-b [c|f]]       Brief annotations for C or Fortran indices in data\n\
  [-f [c|f]]       Full annotations for C or Fortran indices in data\n\
  [-l len]         Line length maximum in data section (default 80)\n\
  [-n name]        Name for netCDF (default derived from file name)\n\
  [-p n[,n]]       Display floating-point values with less precision\n\
  [-k]             Output kind of netCDF file\n\
  [-x]             Output XML (NcML) instead of CDL\n\
  [-s]             Output special (virtual) attributes\n\
  [-t]             Output time data as ISO date-time strings\n\
  file             Name of netCDF file\n"

    (void) fprintf(stderr,
                   "%s [-c|-h] [-v ...] [[-b|-f] [c|f]] [-l len] [-n name] [-p 
n[,n]] [-x] [-k] [-s] [-t] file\n%s",
                   progname,
                   USAGE);
    
    (void) fprintf(stderr,
                 "netcdf library version %s\n",
                 nc_inq_libvers());
}


/* 
 * convert pathname of netcdf file into name for cdl unit, by taking 
 * last component of path and stripping off any extension.
 * DMH: add code to handle OPeNDAP url.
 * DMH: I think this also works for UTF8.
 */
static char *
name_path(const char *path)
{
    const char *cp;
    char *new;
    char *sp;

#ifdef vms
#define FILE_DELIMITER ']'
#endif    
#if defined(WIN32) || defined(msdos)
#define FILE_DELIMITER '\\'
#endif    
#ifndef FILE_DELIMITER /* default to unix */
#define FILE_DELIMITER '/'
#endif

#ifdef USE_DAP
    /* See if this is a url */
    {
        char* base;
        extern int nc__testurl(const char*,char**);
        if(nc__testurl(path,&base)) {
            return base; /* Looks like a url */
        }
        /* else fall thru and treat like a file path */
    }
#endif /*USE_DAP*/

    cp = strrchr(path, FILE_DELIMITER);
    if (cp == 0)                /* no delimiter */
      cp = path;
    else                        /* skip delimeter */
      cp++;
    new = (char *) emalloc((unsigned) (strlen(cp)+1));
    (void) strncpy(new, cp, strlen(cp) + 1);    /* copy last component of path 
*/
    if ((sp = strrchr(new, '.')) != NULL)
      *sp = '\0';               /* strip off any extension */
    return new;
}

/* Return primitive type name */
static const char *
prim_type_name(nc_type type)
{
    switch (type) {
      case NC_BYTE:
        return "byte";
      case NC_CHAR:
        return "char";
      case NC_SHORT:
        return "short";
      case NC_INT:
        return "int";
      case NC_FLOAT:
        return "float";
      case NC_DOUBLE:
        return "double";
#ifdef USE_NETCDF4
      case NC_UBYTE:
        return "ubyte";
      case NC_USHORT:
        return "ushort";
      case NC_UINT:
        return "uint";
      case NC_INT64:
        return "int64";
      case NC_UINT64:
        return "uint64";
      case NC_STRING:
        return "string";
      case NC_VLEN:
        return "vlen";
      case NC_OPAQUE:
        return "opaque";
      case NC_COMPOUND:
        return "compound";
#endif /* USE_NETCDF4 */
      default:
        error("prim_type_name: bad type %d", type);
        return "bogus";
    }
}


/*
 * Remove trailing zeros (after decimal point) but not trailing decimal
 * point from ss, a string representation of a floating-point number that
 * might include an exponent part.
 */
static void
tztrim(char *ss)
{
    char *cp, *ep;
    
    cp = ss;
    if (*cp == '-')
      cp++;
    while(isdigit((int)*cp) || *cp == '.')
      cp++;
    if (*--cp == '.')
      return;
    ep = cp+1;
    while (*cp == '0')
      cp--;
    cp++;
    if (cp == ep)
      return;
    while (*ep)
      *cp++ = *ep++;
    *cp = '\0';
    return;
}


/* Return file type string */
static const char *
kind_string(int kind)
{
    switch (kind) {
    case NC_FORMAT_CLASSIC:
        return "classic";
    case NC_FORMAT_64BIT:
        return "64-bit offset";
    case NC_FORMAT_NETCDF4:
        return "netCDF-4";
    case NC_FORMAT_NETCDF4_CLASSIC:
        return "netCDF-4 classic model";
    default:
        error("unrecognized file format: %d");
        return "unrecognized";
    }
}

/* 
 * Emit kind of netCDF file
 */
static void 
do_nckind(int ncid, const char *path)
{
    int nc_kind;
  
   /*nc_set_log_level(3);*/
    
    NC_CHECK( nc_inq_format(ncid, &nc_kind) );
    printf ("%s\n", kind_string(nc_kind));
    NC_CHECK( nc_close(ncid) );
}


/* 
 * Emit initial line of output for NcML
 */
static void 
pr_initx(int ncid, const char *path)
{
    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<netcdf 
xmlns=\"http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2\"; 
location=\"%s\">\n", 
           path);
}


/*
 * Print attribute string, for text attributes.
 */
static void
pr_att_string(
    int kind,
    size_t len,
    const char *string
    )
{
    int iel;
    const char *cp;
    const char *sp;
    unsigned char uc;

    cp = string;
    printf ("\"");
    /* adjust len so trailing nulls don't get printed */
    sp = cp + len - 1;
    while (len != 0 && *sp-- == '\0')
        len--;
    for (iel = 0; iel < len; iel++)
        switch (uc = *cp++ & 0377) {
        case '\b':
            printf ("\\b");
            break;
        case '\f':
            printf ("\\f");
            break;
        case '\n':              
            /* Only generate linebreaks after embedded newlines for
             * classic, 64-bit offset, or classic model files.  For
             * netCDF-4 files, don't generate linebreaks, because that
             * would create an extra string in a list of strings.  */
            if (kind != NC_FORMAT_NETCDF4) {
                printf ("\\n\",\n\t\t\t\"");
            } else {
                printf("\\n");
            }
            break;
        case '\r':
            printf ("\\r");
            break;
        case '\t':
            printf ("\\t");
            break;
        case '\v':
            printf ("\\v");
            break;
        case '\\':
            printf ("\\\\");
            break;
        case '\'':
            printf ("\\'");
            break;
        case '\"':
            printf ("\\\"");
            break;
        default:
            if (iscntrl(uc))
                printf ("\\%03o",uc);
            else
                printf ("%c",uc);
            break;
        }
    printf ("\"");

}


/*
 * Print NcML attribute string, for text attributes.
 */
static void
pr_attx_string(
     size_t len,
     const char *string
     )
{
    int iel;
    const char *cp;
    const char *sp;
    unsigned char uc;

    cp = string;
    printf ("\"");
    /* adjust len so trailing nulls don't get printed */
    sp = cp + len - 1;
    while (len != 0 && *sp-- == '\0')
        len--;
    for (iel = 0; iel < len; iel++)
        switch (uc = *cp++ & 0377) {
        case '\"':
            printf ("&quot;");
            break;
        case '<':
            printf ("&lt;");
            break;
        case '>':
            printf ("&gt;");
            break;
        case '&':
            printf ("&amp;");
            break;
        case '\n':
            printf ("&#xA;");
            break;
        case '\r':
            printf ("&#xD;");
            break;
        case '\t':
            printf ("&#x9;");
            break;
        default:
            if (iscntrl(uc))
                printf ("&#%d;",uc);
            else
                printf ("%c",uc);
            break;
        }
    printf ("\"");

}


/*
 * Print list of attribute values, for attributes of primitive types.
 * Attribute values must be printed with explicit type tags for
 * netCDF-3 primitive types, because CDL doesn't require explicit
 * syntax to declare such attribute types.  
 */
static void
pr_att_valgs(
    int kind,
    nc_type type,
    size_t len,
    const void *vals
    )
{
    int iel;
    signed char sc;
    short ss;
    int ii;
    char gps[PRIM_LEN];
    float ff;
    double dd;
#ifdef USE_NETCDF4
    unsigned char uc;
    unsigned short us;
    unsigned int ui;
    int64_t i64;
    uint64_t ui64;
    char *stringp;
#endif /* USE_NETCDF4 */
    char *delim = ", "; /* delimiter between output values */

    if (len == 0)
        return;
    if (type == NC_CHAR) {
        char *cp = (char *) vals;
        pr_att_string(kind, len, cp);
        return;
    }
    /* else */
    for (iel = 0; iel < len; iel++) {
        if (iel == len - 1)
            delim = "";
        switch (type) {
        case NC_BYTE:
            sc = ((signed char *) vals)[iel];
            printf ("%db%s", sc, delim);
            break;
        case NC_SHORT:
            ss = ((short *) vals)[iel];
            printf ("%ds%s", ss, delim);
            break;
        case NC_INT:
            ii = ((int *) vals)[iel];
            printf ("%d%s", ii, delim);
            break;
        case NC_FLOAT:
            ff = ((float *) vals)[iel];
            if(isfinite(ff)) {
                int res;
                res = snprintf(gps, PRIM_LEN, float_att_fmt, ff);
                assert(res < PRIM_LEN);
                tztrim(gps);    /* trim trailing 0's after '.' */
                printf ("%s%s", gps, delim);
            } else {
                if(isnan(ff)) {
                    printf("NaNf%s", delim);
                } else if(isinf(ff)) {
                    if(ff < 0.0f) {
                        printf("-");
                    }
                    printf("Infinityf%s", delim);
                }
            }
            break;
        case NC_DOUBLE:
            dd = ((double *) vals)[iel];
            if(isfinite(dd)) {
                int res;
                res = snprintf(gps, PRIM_LEN, double_att_fmt, dd);
                assert(res < PRIM_LEN);
                tztrim(gps);
                printf ("%s%s", gps, delim);
            } else {
                if(isnan(dd)) {
                    printf("NaN%s", delim);
                } else if(isinf(dd)) {
                    if(dd < 0.0) {
                        printf("-");
                    }
                    printf("Infinity%s", delim);
                }
            }
            break;
#ifdef USE_NETCDF4
        case NC_UBYTE:
            uc = ((unsigned char *) vals)[iel];
            printf ("%uUB%s", uc, delim);
            break;
        case NC_USHORT:
            us = ((unsigned short *) vals)[iel];
            printf ("%hdUS%s", us, delim);
            break;
        case NC_UINT:
            ui = ((unsigned int *) vals)[iel];
            printf ("%uU%s", ui, delim);
            break;
        case NC_INT64:
            i64 = ((int64_t *) vals)[iel];
            printf ("%lldL%s", i64, delim);
            break;
        case NC_UINT64:
            ui64 = ((uint64_t *) vals)[iel];
            printf ("%lluUL%s", ui64, delim);
            break;
        case NC_STRING:
            stringp = ((char **) vals)[iel];
            pr_att_string(kind, strlen(stringp), stringp);
            printf("%s", delim);
            break;
#endif /* USE_NETCDF4 */
        default:
            error("pr_att_vals: bad type");
        }
    }
}


/*
 * Print list of numeric attribute values to string for use in NcML output.
 * Unlike CDL, NcML makes type explicit, so don't need type suffixes.
 */
static void
pr_att_valsx(
     nc_type type,
     size_t len,
     const double *vals,
     char *attvals,             /* returned string */
     size_t attvalslen          /* size of attvals buffer, assumed
                                   large enough to hold all len
                                   blank-separated values */
     )
{
    int iel;
    float ff;
    double dd;
    int ii;
#ifdef USE_NETCDF4
    unsigned int ui;
    int64_t i64;
    uint64_t ui64;
#endif /* USE_NETCDF4 */

    attvals[0]='\0';
    if (len == 0)
        return;
    for (iel = 0; iel < len; iel++) {
        char gps[PRIM_LEN];
        int res;
        switch (type) {
        case NC_BYTE:
        case NC_SHORT:
        case NC_INT:
            ii = vals[iel];
            res = snprintf(gps, PRIM_LEN, "%d", ii);
            assert(res < PRIM_LEN);
            (void) strlcat(attvals, gps, attvalslen);
            (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
            break;
#ifdef USE_NETCDF4
        case NC_UBYTE:
        case NC_USHORT:
        case NC_UINT:
            ui = vals[iel];
            res = snprintf(gps, PRIM_LEN, "%u", ui);
            assert(res < PRIM_LEN);
            (void) strlcat(attvals, gps, attvalslen);
            (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
            break;
        case NC_INT64:
            i64 = vals[iel];
            res = snprintf(gps, PRIM_LEN, "%lld", i64);
            assert(res < PRIM_LEN);
            (void) strlcat(attvals, gps, attvalslen);
            (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
            break;
        case NC_UINT64:
            ui64 = vals[iel];
            res = snprintf(gps, PRIM_LEN, "%llu", ui64);
            assert(res < PRIM_LEN);
            (void) strlcat(attvals, gps, attvalslen);
            (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
            break;
#endif /* USE_NETCDF4 */
        case NC_FLOAT:
            ff = vals[iel];
            res = snprintf(gps, PRIM_LEN, float_attx_fmt, ff);
            assert(res < PRIM_LEN);
            tztrim(gps);        /* trim trailing 0's after '.' */
            (void) strlcat(attvals, gps, attvalslen);
            (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
            break;
        case NC_DOUBLE:
            dd = vals[iel];
            res = snprintf(gps, PRIM_LEN, double_att_fmt, dd);
            assert(res < PRIM_LEN);
            tztrim(gps);        /* trim trailing 0's after '.' */
            (void) strlcat(attvals, gps, attvalslen);
            (void) strlcat(attvals, iel < len-1 ? " " : "", attvalslen);
            break;
        default:
            error("pr_att_valsx: bad type");
        }
    }
}

/* 
 * Print a variable attribute
 */
static void
pr_att(
    int ncid,
    int kind,
    int varid,
    const char *varname,
    int ia
    )
{
    ncatt_t att;                        /* attribute */
            
    NC_CHECK( nc_inq_attname(ncid, varid, ia, att.name) );
    NC_CHECK( nc_inq_att(ncid, varid, att.name, &att.type, &att.len) );
    att.tinfo = get_typeinfo(att.type);

    indent_out();
    printf ("\t\t");
#ifdef USE_NETCDF4
    if (is_user_defined_type(att.type) || att.type == NC_STRING)
#else
    if (is_user_defined_type(att.type))
#endif
    {
        /* TODO: omit next two lines if att_type_name not needed
         * because print_type_name() looks it up */
        char att_type_name[NC_MAX_NAME + 1];
        get_type_name(ncid, att.type, att_type_name);

        /* printf ("\t\t%s ", att_type_name); */
        /* ... but handle special characters in CDL names with escapes */
        print_type_name(ncid, att.type);
        printf(" ");
    }
    /*  printf ("\t\t%s:%s = ", varname, att.name); */
    print_name(varname);
    printf(":");
    print_name(att.name);
    printf(" = ");

    if (att.len == 0) { /* show 0-length attributes as empty strings */
        att.type = NC_CHAR;
    }

    if (! is_user_defined_type(att.type) ) {
        att.valgp = (void *) emalloc((att.len + 1) * att.tinfo->size );
        NC_CHECK( nc_get_att(ncid, varid, att.name, att.valgp ) );
        if(att.type == NC_CHAR) /* null-terminate retrieved text att value */
            ((char *)att.valgp)[att.len] = '\0';
        pr_att_valgs(kind, att.type, att.len, att.valgp);
#ifdef USE_NETCDF4
        /* If NC_STRING, need to free all the strings also */
        if(att.type == NC_STRING) {
            nc_free_string(att.len, att.valgp);
        }
#endif /* USE_NETCDF4 */
        free(att.valgp);
    }
#ifdef USE_NETCDF4
    else /* User-defined type. */
    {
       char type_name[NC_MAX_NAME + 1];
       size_t type_size, nfields;
       nc_type base_nc_type;
       int class, i;
       void *data;

       NC_CHECK( nc_inq_user_type(ncid, att.type,  type_name, &type_size, 
                                  &base_nc_type, &nfields, &class));
       switch(class)
       {
          case NC_VLEN:
              /* because size returned for vlen is base type size, but we
               * need space to read array of vlen structs into ... */
             data = emalloc(att.len * sizeof(nc_vlen_t));
             break;
          case NC_OPAQUE:
              data = emalloc(att.len * type_size);
             break;
          case NC_ENUM:
              /* a long long is ample for all base types */
             data = emalloc(att.len * sizeof(int64_t));
             break;
          case NC_COMPOUND:
              data = emalloc(att.len * type_size);
             break;
          default:
             error("unrecognized class of user defined type: %d", class);
       }

       NC_CHECK( nc_get_att(ncid, varid, att.name, data));

       switch(class) {
       case NC_VLEN:
           pr_any_att_vals(&att, data);
           free(data);
           break;
       case NC_OPAQUE: {
           char *sout = emalloc(2 * type_size + strlen("0X") + 1);
           unsigned char *cp = data;
           for (i = 0; i < att.len; i++) {
               (void) ncopaque_val_as_hex(type_size, sout, cp);
               printf("%s%s", sout, i < att.len-1 ? ", " : "");
               cp += type_size;
           }
           free(sout);
       }
           break;
       case NC_ENUM: {
           int64_t value;
           for (i = 0; i < att.len; i++) {
               char enum_name[NC_MAX_NAME + 1];
               switch(base_nc_type)
               {
               case NC_BYTE:
                   value = *((char *)data + i);
                   break;
               case NC_UBYTE:
                   value = *((unsigned char *)data + i);
                   break;
               case NC_SHORT:
                   value = *((short *)data + i);
                   break;
               case NC_USHORT:
                   value = *((unsigned short *)data + i);
                   break;
               case NC_INT:
                   value = *((int *)data + i);
                   break;
               case NC_UINT:
                   value = *((unsigned int *)data + i);
                   break;
               case NC_INT64:
                   value = *((int64_t *)data + i);
                   break;
               case NC_UINT64:
                   value = *((uint64_t *)data + i);
                   break;
               }
               NC_CHECK( nc_inq_enum_ident(ncid, att.type, value, 
                                           enum_name));
/*             printf("%s%s", enum_name, i < att.len-1 ? ", " : ""); */
               print_name(enum_name);
               printf("%s", i < att.len-1 ? ", " : "");
           }
       }
           break;
       case NC_COMPOUND:
           pr_any_att_vals(&att, data);
           free(data);
           break;
       default:
           error("unrecognized class of user defined type: %d", class);
       }
    }
#endif /* USE_NETCDF4 */

    printf (" ;\n");
}

/* Common code for printing attribute name */
static void
pr_att_name(
    int ncid,
    const char *varname,
    const char *attname
    )
{
    indent_out();
    printf ("\t\t");
    print_name(varname);
    printf(":");
    print_name(attname);
}

/* 
 * Print special _Format global attribute, a virtual attribute not
 * actually stored in the file.
 */
static void
pr_att_global_format(
    int ncid,
    int kind
    )
{
    pr_att_name(ncid, "", NC_ATT_FORMAT);
    printf(" = ");
    printf("\"%s\"", kind_string(kind));
    printf (" ;\n");
}


#ifdef USE_NETCDF4
/* 
 * Print special reserved variable attributes, such as _Chunking,
 * _DeflateLevel, ...  These are virtual, not real, attributes
 * generated from the result of inquire calls.  They are of primitive
 * type to fit into the classic model.  Currently, these only exist
 * for netCDF-4 data.
 */
static void
pr_att_specials(
    int ncid,
    int kind,
    int varid,
    const ncvar_t *varp
    )
{
    /* No special variable attributes for classic or 64-bit offset data */
    if(kind == 1 || kind == 2)
        return;
    /* _Chunking */
    if (varp->ndims > 0) {      /* no chunking for scalar variables */
        int contig = 0;
        NC_CHECK( nc_inq_var_chunking(ncid, varid, &contig, NULL ) );
        if(contig == 1) {
            pr_att_name(ncid, varp->name, NC_ATT_STORAGE);
            printf(" = \"contiguous\" ;\n");
        } else {
           size_t *chunkp;
           int i;
            pr_att_name(ncid, varp->name, NC_ATT_STORAGE);
            printf(" = \"chunked\" ;\n");
            chunkp = (size_t *) emalloc(sizeof(size_t) * varp->ndims + 1 );
            NC_CHECK( nc_inq_var_chunking(ncid, varid, NULL, chunkp) );
            /* print chunking, even if it is default */
            pr_att_name(ncid, varp->name, NC_ATT_CHUNKING);
            printf(" = ");
            for(i = 0; i < varp->ndims; i++) {
                printf("%lu%s", (unsigned long)chunkp[i], i+1 < varp->ndims ? 
", " : " ;\n");
            }
            free(chunkp);
        }
    }

    /*_Deflate, _Shuffle */
    {
        int shuffle=NC_NOSHUFFLE, deflate=0, deflate_level=0;
        NC_CHECK( nc_inq_var_deflate(ncid, varid, &shuffle,
                                     &deflate, &deflate_level) );
        if(deflate != 0) {
            pr_att_name(ncid, varp->name, NC_ATT_DEFLATE);
            printf(" = %d ;\n", deflate_level);
        }
        if(shuffle != NC_NOSHUFFLE) {
            pr_att_name(ncid, varp->name, NC_ATT_SHUFFLE);
            printf(" = \"true\" ;\n");
        }
    }
    /* _Checksum */
    {
        int fletcher32 = 0;
        NC_CHECK( nc_inq_var_fletcher32(ncid, varid, &fletcher32) );
        if(fletcher32 != 0) {
            pr_att_name(ncid, varp->name, NC_ATT_CHECKSUM);
            printf(" = \"true\" ;\n");
        }
    }
    /* _Endianness */
    if(varp->tinfo->size > 1) /* Endianness is meaningless for 1-byte types */
    {
        int endianness = 0;
        NC_CHECK( nc_inq_var_endian(ncid, varid, &endianness) );
        if(endianness != 0) {
            pr_att_name(ncid, varp->name, NC_ATT_ENDIANNESS);
            printf(" = ");
            switch (endianness) {
            case NC_ENDIAN_LITTLE:
                printf("\"little\"");
                break;
            case NC_ENDIAN_BIG:
                printf("\"big\"");
                break;
            case NC_ENDIAN_NATIVE:
                printf("\"native\"");
                break;
            default:
                error("pr_att_specials: bad endianness: %d", endianness);
                break;
            }
            printf(" ;\n");
        }
    }
    {
        int no_fill = 0;
        /* Don't get the fill_value, it's set explicitly with
         * _FillValue attribute, because nc_def_var_fill() creates a
         * _FillValue attribute, if needed, and it's value gets
         * displayed elsewhere as a normal (not special virtual)
         * attribute. */
        NC_CHECK( nc_inq_var_fill(ncid, varid, &no_fill, NULL) );
        if(no_fill != 0) {
            pr_att_name(ncid, varp->name, NC_ATT_NOFILL);
            printf(" = \"true\" ;\n");
        }
    }
    /* TODO: handle _Nbit when inquire function is available */

    /* TODO: handle _ScaleOffset when inquire is available */

    /* TODO: handle _Szip when szip inquire function is available */
}
#endif /* USE_NETCDF4 */


/* 
 * Print a variable attribute for NcML
 */
static void
pr_attx(
    int ncid,
    int varid,
    int ia
    )
{
    ncatt_t att;                        /* attribute */
    char *attvals;
    int attvalslen = 0;

    NC_CHECK( nc_inq_attname(ncid, varid, ia, att.name) );
    NC_CHECK( nc_inq_att(ncid, varid, att.name, &att.type, &att.len) );
    att.tinfo = get_typeinfo(att.type);

    /* Put attribute values into a single string, with blanks in between */

    switch (att.type) {
    case NC_CHAR:
        attvals = (char *) emalloc(att.len + 1);
        attvalslen = att.len;
        attvals[att.len] = '\0';
        NC_CHECK( nc_get_att_text(ncid, varid, att.name, attvals ) );
        break;
#ifdef USE_NETCDF4
    case NC_STRING:
        /* TODO: this only prints first string value, need to handle
           multiple strings? */
        attvals = (char *) emalloc(att.len + 1);
        attvals[att.len] = '\0';
        NC_CHECK( nc_get_att_text(ncid, varid, att.name, attvals ) );
        break;
    case NC_VLEN:
        /* TODO */
        break;
    case NC_OPAQUE:
        /* TODO */
        break;
    case NC_COMPOUND:
        /* TODO */
        break;
#endif /* USE_NETCDF4 */
    default:
        att.vals = (double *) emalloc((att.len + 1) * sizeof(double));
        NC_CHECK( nc_get_att_double(ncid, varid, att.name, att.vals ) );
        attvalslen = 20*att.len; /* max 20 chars for each value and blank 
separator */
        attvals = (char *) emalloc(attvalslen + 1);
        pr_att_valsx(att.type, att.len, att.vals, attvals, attvalslen);
        free(att.vals); 
        break;
    }

    /* Don't output type for string attributes, since that's default type */
    if(att.type == NC_CHAR
#ifdef USE_NETCDF4
                          || att.type == NC_CHAR
#endif /* USE_NETCDF4 */
       ) {
        /* TODO: XML-ish escapes for special chars in names */
        printf ("%s  <attribute name=\"%s\" value=", 
                varid != NC_GLOBAL ? "  " : "", 
                att.name);
        /* print attvals as a string with XML escapes */
        pr_attx_string(attvalslen, attvals);
    } else {                    /* non-string attribute */
        char att_type_name[NC_MAX_NAME + 1];
        get_type_name(ncid, att.type, att_type_name);
        /* TODO: print full type name with group prefix, when needed */
        printf ("%s  <attribute name=\"%s\" type=\"%s\" value=\"", 
                varid != NC_GLOBAL ? "  " : "", 
                att.name, 
                att_type_name);
        printf("%s\"",attvals);
    }
    printf (" />\n");
    free (attvals);
}


/* Print optional NcML attribute for a variable's shape */
static void
pr_shape(ncvar_t* varp, ncdim_t *dims)
{
    char *shape;
    int shapelen = 0;
    int id;

    if (varp->ndims == 0)
        return;
    for (id = 0; id < varp->ndims; id++) {
        shapelen += strlen(dims[varp->dims[id]].name) + 1;
    }
    shape = (char *) emalloc(shapelen);
    shape[0] = '\0';
    for (id = 0; id < varp->ndims; id++) {
        /* TODO: XML-ish escapes for special chars in dim names */
        strlcat(shape, dims[varp->dims[id]].name, shapelen);
        strlcat(shape, id < varp->ndims-1 ? " " : "", shapelen);
    }
    printf (" shape=\"%s\"", shape);
    free(shape);
}

#ifdef USE_NETCDF4


/* Print an enum type declaration */
static void
print_enum_type(int ncid, nc_type typeid) {
    char type_name[NC_MAX_NAME + 1];
    size_t type_size;
    nc_type base_nc_type;
    size_t type_nfields;
    int type_class;
    char base_type_name[NC_MAX_NAME + 1];
    int f;
    int64_t memval;
    char memname[NC_MAX_NAME + 1];
 /* extra space for escapes, and punctuation */
#define SAFE_BUF_LEN 4*NC_MAX_NAME+30
    char safe_buf[SAFE_BUF_LEN];
    char *delim;
    int64_t data;           /* space for data of any primitive type */
    char *esc_btn;
    char *esc_tn;
    char *esc_mn;
    int res;

    NC_CHECK( nc_inq_user_type(ncid, typeid, type_name, &type_size, 
&base_nc_type, 
                               &type_nfields, &type_class) );

    get_type_name(ncid, base_nc_type, base_type_name); 
    indent_out();
    esc_btn = escaped_name(base_type_name);
    esc_tn = escaped_name(type_name);
    res = snprintf(safe_buf, SAFE_BUF_LEN,"%s enum %s {", esc_btn, esc_tn);
    assert(res < SAFE_BUF_LEN);
    free(esc_btn);
    free(esc_tn);
    lput(safe_buf);
    delim = ", ";
    for (f = 0; f < type_nfields; f++) {
        if (f == type_nfields - 1)
            delim = "} ;\n";
        NC_CHECK( nc_inq_enum_member(ncid, typeid, f, memname, &data) );
        switch (base_nc_type) {
        case NC_BYTE:
            memval = *(char *)&data;
            break;
        case NC_SHORT:
            memval = *(short *)&data;
            break;
        case NC_INT:
            memval = *(int *)&data;
            break;
#ifdef USE_NETCDF4
        case NC_UBYTE:
            memval = *(unsigned char *)&data;
            break;
        case NC_USHORT:
            memval = *(unsigned short *)&data;
            break;
        case NC_UINT:
            memval = *(unsigned int *)&data;
            break;
        case NC_INT64:
            memval = *(int64_t *)&data;
            break;
        case NC_UINT64:
            memval = *(uint64_t *)&data;
            break;
#endif /* USE_NETCDF4 */
        default:
            error("Bad base type for enum!");
            break;
        }
        esc_mn = escaped_name(memname);
        res = snprintf(safe_buf, SAFE_BUF_LEN, "%s = %lld%s", memname, 
                       memval, delim);
        assert(res < SAFE_BUF_LEN);
        free(esc_mn);
        lput(safe_buf);
    }
}


/* Print a user-defined type declaration */
static void
print_ud_type(int ncid, nc_type typeid) {
    
    char type_name[NC_MAX_NAME + 1];
    char base_type_name[NC_MAX_NAME + 1];
    size_t type_nfields, type_size;
    nc_type base_nc_type;
    int f, type_class;
    
    NC_CHECK( nc_inq_user_type(ncid, typeid, type_name, &type_size, 
&base_nc_type, 
                               &type_nfields, &type_class) );
    switch(type_class) {
    case NC_VLEN:
        /* TODO: don't bother getting base_type_name if
         * print_type_name looks it up anyway */
        get_type_name(ncid, base_nc_type, base_type_name);
        indent_out();
/*      printf("%s(*) %s ;\n", base_type_name, type_name); */
        print_type_name(ncid, base_nc_type);
        printf("(*) ");
        print_type_name(ncid, typeid);
        printf(" ;\n");
        break;
    case NC_OPAQUE:
        indent_out();
/*      printf("opaque(%d) %s ;\n", (int)type_size, type_name); */
        printf("opaque(%d) ", (int)type_size);
        print_type_name(ncid, typeid);
        printf(" ;\n");
        break;
    case NC_ENUM:
        print_enum_type(ncid, typeid);
        break;
    case NC_COMPOUND:
        {
            char field_name[NC_MAX_NAME + 1];
            char field_type_name[NC_MAX_NAME + 1];
            size_t field_offset;
            nc_type field_type;
            int field_ndims, field_dim_sizes[NC_MAX_DIMS];
            int d;
            
            indent_out();
/*          printf("compound %s {\n", type_name); */
            printf("compound ");
            print_type_name(ncid, typeid);
            printf(" {\n");
            for (f = 0; f < type_nfields; f++)
                {
                    NC_CHECK( nc_inq_compound_field(ncid, typeid, f, 
field_name, 
                                                    &field_offset, &field_type, 
&field_ndims,
                                                    field_dim_sizes) );
                    /* TODO: don't bother if field_type_name not needed here */
                    get_type_name(ncid, field_type, field_type_name);
                    indent_out();
/*                  printf("  %s %s", field_type_name, field_name); */
                    printf("  ");
                    print_type_name(ncid, field_type);
                    printf(" ");
                    print_name(field_name);
                    if (field_ndims > 0) {
                        printf("(");
                        for (d = 0; d < field_ndims-1; d++)
                            printf("%d, ", field_dim_sizes[d]);
                        printf("%d)", field_dim_sizes[field_ndims-1]);
                    }
                    printf(" ;\n");
                }
            indent_out();
/*          printf("}; // %s\n", type_name); */
            printf("}; // ");
            print_type_name(ncid, typeid);
            printf("\n");
        }
        break;
    default:
        error("Unknown class of user-defined type!");
    }
}
#endif /* USE_NETCDF4 */

static void
get_fill_info(int ncid, int varid, ncvar_t *vp) {
    ncatt_t att;                        /* attribute */
    int nc_status;                      /* return from netcdf calls */
    void *fillvalp;
    
    vp->has_fillval = 1; /* by default, but turn off for bytes */
            
    /* get _FillValue attribute */
    nc_status = nc_inq_att(ncid,varid,_FillValue,&att.type,&att.len);
    fillvalp = emalloc(vp->tinfo->size);
    if(nc_status == NC_NOERR &&
       att.type == vp->type && att.len == 1) {
        NC_CHECK(nc_get_att(ncid, varid, _FillValue, fillvalp));
    } else {
        switch (vp->type) {
        case NC_BYTE:
            /* don't do default fill-values for bytes, too risky */
            vp->has_fillval = 0;
            fillvalp = 0;
            break;
        case NC_CHAR:
            *(char *)fillvalp = NC_FILL_CHAR;
            break;
        case NC_SHORT:
            *(short *)fillvalp = NC_FILL_SHORT;
            break;
        case NC_INT:
            *(int *)fillvalp = NC_FILL_INT;
            break;
        case NC_FLOAT:
            *(float *)fillvalp = NC_FILL_FLOAT;
            break;
        case NC_DOUBLE:
            *(double *)fillvalp = NC_FILL_DOUBLE;
            break;
#ifdef USE_NETCDF4
        case NC_UBYTE:
            /* don't do default fill-values for bytes, too risky */
            vp->has_fillval = 0;
            fillvalp = 0;
            break;
        case NC_USHORT:
            *(unsigned short *)fillvalp = NC_FILL_USHORT;
            break;
        case NC_UINT:
            *(unsigned int *)fillvalp = NC_FILL_UINT;
            break;
        case NC_INT64:
            *(int64_t *)fillvalp = NC_FILL_INT64;
            break;
        case NC_UINT64:
            *(uint64_t *)fillvalp = NC_FILL_UINT64;
            break;
        case NC_STRING:
            *((char **)fillvalp) = NC_FILL_STRING;
            break;
#endif /* USE_NETCDF4 */
        default:                /* no default fill values for NC_NAT
                                   or user-defined types */
            vp->has_fillval = 0;
            fillvalp = 0;
            break;
        }
    }
    vp->fillvalp = fillvalp;
}


/* Check for optional "calendar" attribute and return specified
 * calendar type, if present. */
cdCalenType
calendar_type(int ncid, int varid) {
    int ctype;
    int stat;
    ncatt_t catt;
    static struct {
        char* attname;
        int type;
    } calmap[] = {
        {"gregorian", cdMixed},
        {"standard", cdMixed}, /* synonym */
        {"proleptic_gregorian", cdStandard},
        {"noleap", cdNoLeap},
        {"no_leap", cdNoLeap},
        {"365_day", cdNoLeap},  /* synonym */
        {"allleap", cd366},
        {"all_leap", cd366},    /* synonym */
        {"366_day", cd366},     /* synonym */
        {"360_day", cd360},
        {"julian", cdJulian},
        {"none", cdClim}        /* TODO: test this */
    };
#define CF_CAL_ATT_NAME "calendar"
    int ncals = (sizeof calmap)/(sizeof calmap[0]);
    ctype = cdMixed;  /* default mixed Gregorian/Julian ala udunits */
    stat = nc_inq_att(ncid, varid, CF_CAL_ATT_NAME, &catt.type, &catt.len);
    if(stat == NC_NOERR && catt.type == NC_CHAR && catt.len > 0) {
        char *calstr = (char *)emalloc(catt.len + 1);
        int itype;
        NC_CHECK(nc_get_att(ncid, varid, CF_CAL_ATT_NAME, calstr));     
        calstr[catt.len] = '\0';
        for(itype = 0; itype < ncals; itype++) {
            if(strncmp(calstr, calmap[itype].attname, catt.len) == 0) {
                ctype = calmap[itype].type;
                break;
            }
        }
        free(calstr);
    }
    return ctype;
}


static void
get_timeinfo(int ncid, int varid, ncvar_t *vp) {
    ncatt_t uatt;               /* units attribute */
    int nc_status;              /* return from netcdf calls */
    char *units;
    
    vp->has_timeval = false; /* by default, turn on if criteria met */
    vp->timeinfo = 0;
            
    /* time variables must have appropriate units attribute */
    nc_status = nc_inq_att(ncid, varid, "units", &uatt.type, &uatt.len);
    if(nc_status == NC_NOERR && uatt.type == NC_CHAR) { /* TODO: NC_STRING? */
        units = emalloc(uatt.len + 1);
        NC_CHECK(nc_get_att(ncid, varid, "units", units));
        units[uatt.len] = '\0';
        /* check for calendar attribute (not required even for time vars) */
        vp->timeinfo = (timeinfo_t *)emalloc(sizeof(timeinfo_t));
        vp->timeinfo->calendar = calendar_type(ncid, varid);
        /* Parse relative units, returning the unit and base component time. */
        if(cdParseRelunits(vp->timeinfo->calendar, units, 
                           &vp->timeinfo->unit, &vp->timeinfo->origin) != 0) {
            /* error parsing units so just treat as not a time variable */
            free(vp->timeinfo);
            free(units);
            return;
        }
        /* Currently this gets reparsed for every value, need function
         * like cdRel2Comp that resuses parsed units? */
        vp->timeinfo->units = strdup(units);
        vp->has_timeval = true;
        free(units);
    }
    return;
}

/* Recursively dump the contents of a group. (Recall that only
 * netcdf-4 format files can have groups. On all other formats, there
 * is just a root group, so recursion will not take place.) 
 *
 * ncid: id of open file (first call) or group (subsequent recursive calls) 
 * path: file path name (first call) or NULL if called for a group
 * specp: formatting spec
 */
static void
do_ncdump_rec(int ncid, const char *path, fspec_t* specp)
{
   int ndims;                   /* number of dimensions */
   int nvars;                   /* number of variables */
   int ngatts;                  /* number of global attributes */
   int xdimid;                  /* id of unlimited dimension */
   int varid;                   /* variable id */
   ncdim_t *dims;               /* dimensions */
   size_t *vdims=0;             /* dimension sizes for a single variable */
   ncvar_t var;                 /* variable */
   int id;                      /* dimension number per variable */
   int ia;                      /* attribute number */
   int iv;                      /* variable number */
   vnode* vlist = 0;            /* list for vars specified with -v option */
   char type_name[NC_MAX_NAME + 1];
   int kind;            /* strings output differently for nc4 files */
   char dim_name[NC_MAX_NAME + 1];
#ifdef USE_NETCDF4
   int *dimids_grp;             /* dimids of the dims in this group. */
   int *unlimids;               /* dimids of unlimited dimensions in this group 
*/
   int d_grp, ndims_grp;
   int ntypes, *typeids;
   int nunlim;
#else
   int dimid;                   /* dimension id */
#endif /* USE_NETCDF4 */

   /*
    * If any vars were specified with -v option, get list of
    * associated variable ids relative to this group.  Assume vars
    * specified with syntax like "grp1/grp2/varname" or
    * "/grp1/grp2/varname" if they are in groups.
    */
   if (specp->nlvars > 0) {
      vlist = newvlist();       /* list for vars specified with -v option */
      for (iv=0; iv < specp->nlvars; iv++) {
          if(nc_inq_gvarid(ncid, specp->lvars[iv], &varid) == NC_NOERR)
              varadd(vlist, varid);
      }
   }

#ifdef USE_NETCDF4
   /* Are there any user defined types in this group? */
   NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) );
   if (ntypes)
   {
      int t;

      typeids = emalloc(ntypes * sizeof(int));
      NC_CHECK( nc_inq_typeids(ncid, &ntypes, typeids) );
      indent_out();
      printf("types:\n");
      indent_more();
      for (t = 0; t < ntypes; t++)
      {
         print_ud_type(ncid, typeids[t]); /* print declaration of user-defined 
type */
      }
      indent_less();
      free(typeids);
   }
#endif /* USE_NETCDF4 */

   /*
    * get number of dimensions, number of variables, number of global
    * atts, and dimension id of unlimited dimension, if any
    */
   NC_CHECK( nc_inq(ncid, &ndims, &nvars, &ngatts, &xdimid) );
   /* get dimension info */
   dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
   if (ndims > 0) {
       indent_out();
       printf ("dimensions:\n");
   }

#ifdef USE_NETCDF4
   /* In netCDF-4 files, dimids will not be sequential because they
    * may be defined in various groups, and we are only looking at one
    * group at a time. */

   /* Find the number of dimids defined in this group. */
   NC_CHECK( nc_inq_ndims(ncid, &ndims_grp) );
   dimids_grp = (int *)emalloc((ndims_grp + 1) * sizeof(int));
   
   /* Find the dimension ids in this group. */
   NC_CHECK( nc_inq_dimids(ncid, 0, dimids_grp, 0) );

   /* Find the number of unlimited dimensions and get their IDs */
   NC_CHECK( nc_inq_unlimdims(ncid, &nunlim, NULL) );
   unlimids = (int *)emalloc((nunlim + 1) * sizeof(int));
   NC_CHECK( nc_inq_unlimdims(ncid, &nunlim, unlimids) );
    
   /* For each dimension defined in this group, get and print out info. */
   for (d_grp = 0; d_grp < ndims_grp; d_grp++)
   {
      int dimid = dimids_grp[d_grp];
      int is_unlimited = 0;
      int uld;
      int stat;

      for (uld = 0; uld < nunlim; uld++) {
          if(dimid == unlimids[uld]) {
              is_unlimited = 1;
              break;
          }       
      }
      stat = nc_inq_dim(ncid, dimid, dims[d_grp].name, &dims[d_grp].size);
      /* TODO: settle issue whether NC_EBADDIM or NC_EDIMSIZE used for 
very-large dimensions */
      if ((stat == NC_EBADDIM || stat == NC_EDIMSIZE) && SIZEOF_SIZE_T < 8) {
          error("dimension \"%s\" too large for 32-bit platform, try 64-bit 
version", dims[d_grp].name);
      } else {
          NC_CHECK (stat);
      }
      indent_out();
      printf ("\t");
      print_name(dims[d_grp].name);
      printf (" = ");
      if(SIZEOF_SIZE_T >= 8) {
          if (is_unlimited) {
              printf ("UNLIMITED ; // (%llu currently)\n", 
                      dims[d_grp].size);
          } else {
              printf ("%llu ;\n", dims[d_grp].size);
          }
      } else {                  /* 32-bit platform */
          if (is_unlimited) {
              printf ("UNLIMITED ; // (%u currently)\n", 
                      (unsigned int)dims[d_grp].size);
          } else {
              printf ("%u ;\n", (unsigned int)dims[d_grp].size);
          }
      }
   }
   if(unlimids)
       free(unlimids);
   if(dimids_grp)
       free(dimids_grp);
#else /* not using netCDF-4 */
   for (dimid = 0; dimid < ndims; dimid++) {
      NC_CHECK( nc_inq_dim(ncid, dimid, dims[dimid].name, &dims[dimid].size) );
      indent_out();
      printf ("\t");
      print_name(dims[dimid].name);
      printf (" = ");
      if (dimid == xdimid) {
          printf ("UNLIMITED ; // (%u currently)\n", 
                  (unsigned int)dims[dimid].size);
      } else {
          printf ("%u ;\n", (unsigned int)dims[dimid].size);
      }
   }
#endif /* USE_NETCDF4 */

   if (nvars > 0) {
       indent_out();
       printf ("variables:\n");
   }
   /* Because netCDF-4 can have a string attribute with multiple
    * string values, we can't output strings with embedded newlines
    * as what look like multiple strings, as we do for classic and
    * 64-bit offset files.  So we need to know the output file type
    * to know how to print strings with embedded newlines. */
   NC_CHECK( nc_inq_format(ncid, &kind) );
       
   /* For each var, get and print out info. */
   for (varid = 0; varid < nvars; varid++) {
      NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
      var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
      NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
                           var.dims, &var.natts) );
      /* TODO: don't bother if type name not needed here */
      get_type_name(ncid, var.type, type_name);
      var.tinfo = get_typeinfo(var.type);
      indent_out();
/*       printf ("\t%s %s", type_name, var.name); */
      printf ("\t");
      /* TODO: if duplicate type name and not just inherited, print
       * full type name. */
      print_type_name (ncid, var.type);
      printf (" ");
      print_name (var.name);
      if (var.ndims > 0)
         printf ("(");
      for (id = 0; id < var.ndims; id++) {
/*       printf ("%s%s", dims[var.dims[id]].name, id < var.ndims-1 ? ", " : 
")"); */
         /* This dim may be in a parent group, so let's look up the
          * name. */
         NC_CHECK( nc_inq_dimname(ncid, var.dims[id], dim_name) );
         print_name (dim_name);
         printf ("%s", id < var.ndims-1 ? ", " : ")");
      }
      printf (" ;\n");

      /* print variable attributes */
      for (ia = 0; ia < var.natts; ia++) { /* print ia-th attribute */
          pr_att(ncid, kind, varid, var.name, ia);
      }
#ifdef USE_NETCDF4
      /* Print special (virtual) attributes, if option specified */
      if (specp->special_atts) {
          pr_att_specials(ncid, kind, varid, &var);
      }
      free(var.dims);
#endif /* USE_NETCDF4 */
   }

   /* get global attributes */
   if (ngatts > 0 || specp->special_atts) {
      printf ("\n");
      indent_out();
      if (path != NULL)         /* top-level, root group */
          printf("// global attributes:\n");
      else
          printf("// group attributes:\n");
   }
   for (ia = 0; ia < ngatts; ia++) { /* print ia-th global attribute */
       pr_att(ncid, kind, NC_GLOBAL, "", ia);
   }
   if (path != NULL && specp->special_atts) { /* output special attribute
                                           * for format variant */
       pr_att_global_format(ncid, kind);
   }

   /* output variable data */
   if (! specp->header_only) {
      if (nvars > 0) {
          indent_out();
          printf ("data:\n");
      }
      for (varid = 0; varid < nvars; varid++) {
         int no_records;
         /* if var list specified, test for membership */
         if (specp->nlvars > 0 && ! varmember(vlist, varid))
            continue;
         NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
         var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
         NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
                              var.dims, &var.natts) );
         var.tinfo = get_typeinfo(var.type);
         /* If coords-only option specified, don't get data for
          * non-coordinate vars */
         if (specp->coord_vals && !iscoordvar(ncid,varid)) {
            continue;
         }
         /* Collect variable's dim sizes */
         if (vdims) {
             free(vdims);
             vdims = 0;
         }
         vdims = (size_t *) emalloc((var.ndims + 1) * SIZEOF_SIZE_T);
         no_records = 0;
         for (id = 0; id < var.ndims; id++) {
             size_t len;
             NC_CHECK( nc_inq_dimlen(ncid, var.dims[id], &len) );
             if(len == 0) {
                 no_records = 1;
             }
             vdims[id] = len;
         }
         /* Don't get data for record variables if no records have
          * been written yet */
         if (no_records) {
             free(vdims);
             vdims = 0;
             continue;
         }
         get_fill_info(ncid, varid, &var); /* sets has_fillval, fillvalp mmbrs 
*/
         get_timeinfo(ncid, varid, &var); /* sets has_timeval, timeinfo mmbrs */
         /* printf format used to print each value */
         var.fmt = get_fmt(ncid, varid, var.type);
         var.locid = ncid;
         set_tostring_func(&var, specp);
         if (vardata(&var, vdims, ncid, varid, specp) == -1) {
            error("can't output data for variable %s", var.name);
            NC_CHECK(
               nc_close(ncid) );
            if (vlist)
               free(vlist);
            return;
         }
      }
      if (vdims) {
          free(vdims);
          vdims = 0;
      }
   }
    
#ifdef USE_NETCDF4
   /* For netCDF-4 compiles, check to see if the file has any
    * groups. If it does, this function is called recursively on each
    * of them. */
   {
      int g, numgrps, *ncids;
      char group_name[NC_MAX_NAME + 1];

      /* See how many groups there are. */
      NC_CHECK( nc_status = nc_inq_grps(ncid, &numgrps, NULL) );
      
      /* Allocate memory to hold the list of group ids. */
      ncids = emalloc((numgrps + 1) * sizeof(int));
      
      /* Get the list of group ids. */
      NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
      
      /* Call this function for each group. */
      for (g = 0; g < numgrps; g++)
      {
          NC_CHECK( nc_inq_grpname(ncids[g], group_name) );
          printf ("\n");
          indent_out();
/*          printf ("group: %s {\n", group_name); */
          printf ("group: ");
          print_name (group_name);
          printf (" {\n");
          indent_more();
          do_ncdump_rec(ncids[g], NULL, specp);
          indent_out();
/*          printf ("} // group %s\n", group_name); */
          printf ("} // group ");
          print_name (group_name);
          printf ("\n");
          indent_less();
      }
      
      free(ncids);
   }
#endif /* USE_NETCDF4 */

   if (vlist)
      free(vlist);
   if (dims)
      free(dims);
}


static void
do_ncdump(int ncid, const char *path, fspec_t* specp)
{
   char* esc_specname;
   /* output initial line */
   indent_init();
   indent_out();
   esc_specname=escaped_name(specp->name);
   printf ("netcdf %s {\n", esc_specname);
   free(esc_specname);
   do_ncdump_rec(ncid, path, specp);
   indent_out();
   printf ("}\n");
   NC_CHECK( nc_close(ncid) );
}


static void
do_ncdumpx(int ncid, const char *path, fspec_t* specp)
{
    int ndims;                  /* number of dimensions */
    int nvars;                  /* number of variables */
    int ngatts;                 /* number of global attributes */
    int xdimid;                 /* id of unlimited dimension */
    int dimid;                  /* dimension id */
    int varid;                  /* variable id */
    ncdim_t *dims;              /* dimensions */
    ncvar_t var;                /* variable */
    int ia;                     /* attribute number */
    int iv;                     /* variable number */
    vnode* vlist = 0;           /* list for vars specified with -v option */

    /*
     * If any vars were specified with -v option, get list of associated
     * variable ids
     */
    if (specp->nlvars > 0) {
        vlist = newvlist();     /* list for vars specified with -v option */
        for (iv=0; iv < specp->nlvars; iv++) {
            NC_CHECK( nc_inq_varid(ncid, specp->lvars[iv], &varid) );
            varadd(vlist, varid);
        }
    }

    /* output initial line */
    pr_initx(ncid, path);

    /*
     * get number of dimensions, number of variables, number of global
     * atts, and dimension id of unlimited dimension, if any
     */
    /* TODO: print names with XML-ish escapes fopr special chars */
    NC_CHECK( nc_inq(ncid, &ndims, &nvars, &ngatts, &xdimid) );
    /* get dimension info */
    dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
    for (dimid = 0; dimid < ndims; dimid++) {
        NC_CHECK( nc_inq_dim(ncid, dimid, dims[dimid].name, &dims[dimid].size) 
);
        if (dimid == xdimid)
          printf("  <dimension name=\"%s\" length=\"%d\" isUnlimited=\"true\" 
/>\n", 
                 dims[dimid].name, (int)dims[dimid].size);
        else
          printf ("  <dimension name=\"%s\" length=\"%d\" />\n", 
                  dims[dimid].name, (int)dims[dimid].size);
    }

    /* get global attributes */
    for (ia = 0; ia < ngatts; ia++)
        pr_attx(ncid, NC_GLOBAL, ia); /* print ia-th global attribute */

    /* get variable info, with variable attributes */
    for (varid = 0; varid < nvars; varid++) {
        NC_CHECK( nc_inq_varndims(ncid, varid, &var.ndims) );
        var.dims = (int *) emalloc((var.ndims + 1) * sizeof(int));
        NC_CHECK( nc_inq_var(ncid, varid, var.name, &var.type, 0,
                             var.dims, &var.natts) );
        printf ("  <variable name=\"%s\"", var.name);
        pr_shape(&var, dims);

        /* handle one-line variable elements that aren't containers
           for attributes or data values, since they need to be
           rendered as <variable ... /> instead of <variable ..>
           ... </variable> */
        if (var.natts == 0) {
            if (
                /* header-only specified */
                (specp->header_only) ||
                /* list of variables specified and this variable not in list */
                (specp->nlvars > 0 && !varmember(vlist, varid)) ||
                /* coordinate vars only and this is not a coordinate variable */
                (specp->coord_vals && !iscoordvar(ncid, varid)) ||
                /* this is a record variable, but no records have been written 
*/
                (isrecvar(ncid,varid) && dims[xdimid].size == 0)
                ) {
                printf (" type=\"%s\" />\n", prim_type_name(var.type));
                continue;
            }
        }

        /* else nest attributes values, data values in <variable> ... 
</variable> */
        printf (" type=\"%s\">\n", prim_type_name(var.type));

        /* get variable attributes */
        for (ia = 0; ia < var.natts; ia++) {
            pr_attx(ncid, varid, ia); /* print ia-th attribute */
        }
        printf ("  </variable>\n");
    }
    
    printf ("</netcdf>\n");
    NC_CHECK(
        nc_close(ncid) );
    if (vlist)
        free(vlist);
    if(dims)
        free(dims);
}


static void
make_lvars(char *optarg, fspec_t* fspecp)
{
    char *cp = optarg;
    int nvars = 1;
    char ** cpp;

    /* compute number of variable names in comma-delimited list */
    fspecp->nlvars = 1;
    while (*cp++)
      if (*cp == ',')
        nvars++;

    fspecp->lvars = (char **) emalloc(nvars * sizeof(char*));

    cpp = fspecp->lvars;
    /* copy variable names into list */
    for (cp = strtok(optarg, ",");
         cp != NULL;
         cp = strtok((char *) NULL, ",")) {
        size_t bufsiz = strlen(cp) + 1;
        
        *cpp = (char *) emalloc(bufsiz);
        strncpy(*cpp, cp, bufsiz);
        cpp++;
    }
    fspecp->nlvars = nvars;
}


/*
 * Extract the significant-digits specifiers from the (deprecated and
 * undocumented) -d argument on the command-line and update the
 * default data formats appropriately.  This only exists because an
 * old version of ncdump supported the "-d" flag which did not
 * override the C_format attributes (if any).
 */
static void
set_sigdigs(const char *optarg)
{
    char *ptr1 = 0;
    char *ptr2 = 0;
    int flt_digits = FLT_DIGITS; /* default floating-point digits */
    int dbl_digits = DBL_DIGITS; /* default double-precision digits */

    if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',')
        flt_digits = (int)strtol(optarg, &ptr1, 10);

    if (flt_digits < 1 || flt_digits > 20) {
        error("unreasonable value for float significant digits: %d",
              flt_digits);
    }
    if (*ptr1 == ',')
      dbl_digits = (int)strtol(ptr1+1, &ptr2, 10);
    if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
        error("unreasonable value for double significant digits: %d",
              dbl_digits);
    }
    set_formats(flt_digits, dbl_digits);
}


/*
 * Extract the significant-digits specifiers from the -p argument on the
 * command-line, set flags so we can override C_format attributes (if any),
 * and update the default data formats appropriately.
 */
static void
set_precision(const char *optarg)
{
    char *ptr1 = 0;
    char *ptr2 = 0;
    int flt_digits = FLT_DIGITS;        /* default floating-point digits */
    int dbl_digits = DBL_DIGITS;        /* default double-precision digits */

    if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',') {
        flt_digits = (int)strtol(optarg, &ptr1, 10);
        float_precision_specified = 1;
    }

    if (flt_digits < 1 || flt_digits > 20) {
        error("unreasonable value for float significant digits: %d",
              flt_digits);
    }
    if (*ptr1 == ',') {
        dbl_digits = (int) strtol(ptr1+1, &ptr2, 10);
        double_precision_specified = 1;
    }
    if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
        error("unreasonable value for double significant digits: %d",
              dbl_digits);
    }
    set_formats(flt_digits, dbl_digits);
}

/* Determine whether a variable named varname exists in any group in
   an open netCDF file with id ncid.  If so, return the count of how
   many matching variables were found, else return a count of 0.  The
   variable name can be absolute such as "/foo" or "/GRP1/GRP1A/foo",
   in which case there is only one group to look in, given by the path
   from the root group.  Alternatively, the variable name can be
   relative, such as "foo" or "GRPA/GRPB/foo", in which case every
   group is examined for a variable with that relative name.  */
size_t
nc_inq_varname_count(int ncid, char *varname) {
    /* 
       count = 0;
       status = nc_inq_gvarid(ncid, varname, varid);
       if (status == NC_NOERR)
          count++;
       for each subgroup gid {
          count += nc_inq_varname_count(gid, varname);
       }
       return count;
    */
    size_t count = 0;
    int varid;
    /* look in this group */
    int status = nc_inq_gvarid(ncid, varname, &varid);
    int numgrps;
    int *ncids;
    int g;

    if (status == NC_NOERR)
        count++;

#ifdef USE_NETCDF4
    /* if this group has subgroups, call recursively on each of them */
    NC_CHECK( nc_status = nc_inq_grps(ncid, &numgrps, NULL) );
         
    /* Allocate memory to hold the list of group ids. */
    ncids = emalloc((numgrps + 1) * sizeof(int));
        
    /* Get the list of group ids. */
    NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
        
    /* Call this function for each group. */
    for (g = 0; g < numgrps; g++) {
        count += nc_inq_varname_count(ncids[g], varname);
    }
    free(ncids);
#endif /* USE_NETCDF4 */
    return count;    
   
}


/* Check if any variable names specified with "-v var1,...,varn" are
 * missing.  Returns 0 if no missing variables detected, otherwise
 * exits. */
static int
missing_vars(int ncid, fspec_t *specp) {
    int iv;
    for (iv=0; iv < specp->nlvars; iv++) {
        if(nc_inq_varname_count(ncid, specp->lvars[iv]) == 0) {
            error("%s: No such variable", specp->lvars[iv]);
        }
    }
    return 0;
}

int
main(int argc, char *argv[])
{
    extern int optind;
    extern int opterr;
    extern char *optarg;
    static fspec_t fspec =      /* defaults, overridden on command line */
      {
          0,                    /* construct netcdf name from file name */
          false,                /* print header info only, no data? */
          false,                /* just print coord vars? */
          false,                /* brief  comments in data section? */
          false,                /* full annotations in data section?  */
          false,                /* human-readable output for date-time values */
          false,                /* output special attributes, eg chunking? */
          LANG_C,               /* language conventions for indices */
          0,                    /* if -v specified, number of variables */
          0                     /* if -v specified, list of variable names */
          };
    int c;
    int i;
    int max_len = 80;           /* default maximum line length */
    int nameopt = 0;
    boolean xml_out = false;    /* if true, output NcML instead of CDL */
    boolean kind_out = false;   /* if true, just output kind of netCDF file */

#ifdef HAVE_LOCALE_H
    setlocale(LC_ALL, "C");     /* CDL may be ambiguous with other locales */
#endif /* HAVE_LOCALE_H */
    opterr = 1;
    progname = argv[0];
    set_formats(FLT_DIGITS, DBL_DIGITS); /* default for float, double data */

    /* If the user called ncdump without arguments, print the usage
     * message and return peacefully. */
    if (argc <= 1)
    {
       usage();
#ifdef vms
    exit(EXIT_SUCCESS);
#else
    return EXIT_SUCCESS;
#endif
    }

    while ((c = getopt(argc, argv, "b:cd:f:hkl:n:p:stv:x")) != EOF)
      switch(c) {
        case 'h':               /* dump header only, no data */
          fspec.header_only = true;
          break;
        case 'c':               /* header, data only for coordinate dims */
          fspec.coord_vals = true;
          break;
        case 'n':               /*
                                 * provide different name than derived from
                                 * file name
                                 */
          fspec.name = optarg;
          nameopt = 1;
          break;
        case 'b':               /* brief comments in data section */
          fspec.brief_data_cmnts = true;
          switch (tolower(optarg[0])) {
            case 'c':
              fspec.data_lang = LANG_C;
              break;
            case 'f':
              fspec.data_lang = LANG_F;
              break;
            default:
              error("invalid value for -b option: %s", optarg);
          }
          break;
        case 'f':               /* full comments in data section */
          fspec.full_data_cmnts = true;
          switch (tolower(optarg[0])) {
            case 'c':
              fspec.data_lang = LANG_C;
              break;
            case 'f':
              fspec.data_lang = LANG_F;
              break;
            default:
              error("invalid value for -f option: %s", optarg);
          }
          break;
        case 'l':               /* maximum line length */
          max_len = (int) strtol(optarg, 0, 0);
          if (max_len < 10) {
              error("unreasonably small line length specified: %d", max_len);
          }
          break;
        case 'v':               /* variable names */
          /* make list of names of variables specified */
          make_lvars (optarg, &fspec);
          break;
        case 'd':               /* specify precision for floats (deprecated, 
undocumented) */
          set_sigdigs(optarg);
          break;
        case 'p':               /* specify precision for floats, overrides 
attribute specs */
          set_precision(optarg);
          break;
        case 'x':               /* XML output (NcML) */
          xml_out = true;
          break;
        case 'k':               /* just output what kind of netCDF file */
          kind_out = true;
          break;
        case 't':               /* human-readable strings for time values */
          fspec.iso_times = true;
          break;
        case 's':           /* output special (virtual) attributes for
                             * netCDF-4 files and variables, including
                             * _DeflateLevel, _Chunking, _Endianness,
                             * _Format, _Checksum, _NoFill */
          fspec.special_atts = true;
          break;
        case '?':
          usage();
          return 0;
      }

    set_max_len(max_len);
    
    argc -= optind;
    argv += optind;

    /* If no file arguments left or more than one, print usage message. */
    if (argc != 1)
    {
       usage();
       return 0;
    }

    i = 0;

    init_epsilons();

    {           
        char *path = argv[i];
        if (!nameopt) 
            fspec.name = name_path(path);
        if (argc > 0) {
            int ncid, nc_status;
            nc_status = nc_open(path, NC_NOWRITE, &ncid);
            if (nc_status != NC_NOERR) {
                error("%s: %s", path, nc_strerror(nc_status));
            }
            if (kind_out) {
                do_nckind(ncid, path);
            } else {
                /* Initialize list of types. */
                init_types(ncid);
                /* Check if any vars in -v don't exist */
                if(missing_vars(ncid, &fspec))
                    return EXIT_FAILURE;
                if (xml_out) {
                    do_ncdumpx(ncid, path, &fspec);
                } else {
                    do_ncdump(ncid, path, &fspec);
                }
            }
        }
    }
#ifdef vms
    exit(EXIT_SUCCESS);
#else
    return EXIT_SUCCESS;
#endif
}