mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-11-21 03:13:42 +08:00
158c790ae5
re: PR https://github.com/Unidata/netcdf-c/pull/2584 re: PR https://github.com/Unidata/netcdf-c/pull/2596 Repaired a memory leak in *netcdf-c/ncdump/utils.c*. I think introduced by PR 2584. ## Misc. Other Changes * Fixed references to *netcdf-c/docs/byterange.dox* -> *netcdf-c/docs/byterange.md* (PR 2596).
999 lines
25 KiB
C
999 lines
25 KiB
C
/*********************************************************************
|
|
* Copyright 2018, University Corporation for Atmospheric Research
|
|
* See netcdf/README file for copying and redistribution conditions.
|
|
* $Id$
|
|
*********************************************************************/
|
|
|
|
#include "config.h"
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <netcdf.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include "utils.h"
|
|
#include "nccomps.h"
|
|
#ifndef isascii
|
|
EXTERNL int isascii(int c);
|
|
#endif
|
|
|
|
/*
|
|
* Print error message to stderr and exit
|
|
*/
|
|
void
|
|
error(const char *fmt, ...)
|
|
{
|
|
va_list args ;
|
|
|
|
(void) fprintf(stderr,"%s: ", progname);
|
|
va_start(args, fmt) ;
|
|
(void) vfprintf(stderr,fmt,args) ;
|
|
va_end(args) ;
|
|
|
|
(void) fprintf(stderr, "\n") ;
|
|
(void) fflush(stderr); /* to ensure log files are current */
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
void *
|
|
emalloc ( /* check return from malloc */
|
|
size_t size)
|
|
{
|
|
void *p;
|
|
|
|
p = (void *) malloc (size==0 ? 1 : size); /* malloc(0) not portable */
|
|
if (p == 0) {
|
|
error ("out of memory\n");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void *
|
|
ecalloc ( /* check return from calloc */
|
|
size_t size)
|
|
{
|
|
void *p;
|
|
|
|
p = (void *) calloc (1,(size==0 ? 1 : size)); /* calloc(0) not portable */
|
|
if (p == 0) {
|
|
error ("out of memory\n");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void *
|
|
erealloc (void* p0, /* check return from realloc */
|
|
size_t size)
|
|
{
|
|
void *p;
|
|
|
|
if(p0 == NULL)
|
|
return emalloc(size);
|
|
if(size == 0)
|
|
error("realloc with zero size");
|
|
p = (void *) realloc (p0,size); /* realloc(0) not portable */
|
|
if (p == 0) {
|
|
error ("out of memory\n");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void
|
|
check(int err, const char* file, const char* fcn, const int line)
|
|
{
|
|
fprintf(stderr,"%s\n",nc_strerror(err));
|
|
fprintf(stderr,"Location: file %s; fcn %s line %d\n",(file?file:"?"),(fcn?fcn:"?"),line);
|
|
fflush(stderr); fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns malloced name with chars special to CDL escaped.
|
|
* Caller should free result when done with it.
|
|
*/
|
|
char*
|
|
escaped_name(const char* cp) {
|
|
char *ret; /* string returned */
|
|
char *sp;
|
|
assert(cp != NULL);
|
|
|
|
/* For some reason, and on some machines (e.g. tweety)
|
|
utf8 characters such as \343 are considered control character. */
|
|
/* if(*cp && (isspace(*cp) | iscntrl(*cp)))*/
|
|
if((*cp >= 0x01 && *cp <= 0x20) || (*cp == 0x7f))
|
|
{
|
|
error("name begins with space or control-character: %c",*cp);
|
|
}
|
|
|
|
ret = emalloc(4*strlen(cp) + 1); /* max if every char escaped */
|
|
sp = ret;
|
|
*sp = 0; /* empty name OK */
|
|
/* Special case: leading number allowed, but we must escape it for CDL */
|
|
if((*cp >= '0' && *cp <= '9'))
|
|
{
|
|
*sp++ = '\\';
|
|
}
|
|
for (; *cp; cp++) {
|
|
if (isascii((int)*cp)) {
|
|
if(iscntrl((int)*cp)) { /* render control chars as two hex digits, \%xx */
|
|
snprintf(sp, 4+1,"\\%%%.2x", *cp);
|
|
sp += 4;
|
|
} else {
|
|
switch (*cp) {
|
|
case ' ':
|
|
case '!':
|
|
case '"':
|
|
case '#':
|
|
case '$':
|
|
case '&':
|
|
case '\'':
|
|
case '(':
|
|
case ')':
|
|
case '*':
|
|
case ',':
|
|
case ':':
|
|
case ';':
|
|
case '<':
|
|
case '=':
|
|
case '>':
|
|
case '?':
|
|
case '[':
|
|
case ']':
|
|
case '\\':
|
|
case '^':
|
|
case '`':
|
|
case '{':
|
|
case '|':
|
|
case '}':
|
|
case '~':
|
|
*sp++ = '\\';
|
|
*sp++ = *cp;
|
|
break;
|
|
default: /* includes '/' */
|
|
*sp++ = *cp;
|
|
break;
|
|
}
|
|
}
|
|
} else { /* not ascii, assume just UTF-8 byte */
|
|
*sp++ = *cp;
|
|
}
|
|
}
|
|
*sp = 0;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Print name with escapes for special characters
|
|
*/
|
|
void
|
|
print_name(const char* name) {
|
|
char *ename = escaped_name(name);
|
|
fputs(ename, stdout);
|
|
free(ename);
|
|
}
|
|
|
|
/*
|
|
* Returns malloced string with selected chars escaped.
|
|
* Caller should free result when done with it.
|
|
*/
|
|
char*
|
|
escaped_string(const char* cp) {
|
|
char *ret; /* string returned */
|
|
char *sp;
|
|
assert(cp != NULL);
|
|
|
|
/* For some reason, and on some machines (e.g. tweety)
|
|
utf8 characters such as \343 are considered control character. */
|
|
|
|
ret = emalloc(4*strlen(cp) + 1); /* max if every char escaped */
|
|
sp = ret;
|
|
*sp = 0; /* empty name OK */
|
|
for (; *cp; cp++) {
|
|
if (isascii((int)*cp)) {
|
|
if(iscntrl((int)*cp)) { /* render control chars as two hex digits, \%xx */
|
|
snprintf(sp, 4+1,"\\%%%.2x", *cp);
|
|
sp += 4;
|
|
} else if(*cp == '"') {
|
|
*sp++ = '\\';
|
|
*sp++ = '"';
|
|
} else
|
|
*sp++ = *cp;
|
|
} else { /* not ascii, assume just UTF-8 byte */
|
|
*sp++ = *cp;
|
|
}
|
|
}
|
|
*sp = 0;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Convert a full path name to a group to the specific groupid. */
|
|
int
|
|
nc_inq_grpid2(int ncid, const char *grpname0, int *grpidp)
|
|
{
|
|
int ret = NC_NOERR;
|
|
char* grpname = NULL;
|
|
#ifdef USE_NETCDF4
|
|
char *sp = NULL;
|
|
#endif
|
|
|
|
grpname = strdup(grpname0);
|
|
if(grpname == NULL) {ret = NC_ENOMEM; goto done;}
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* If '/' doesn't occur in name, just return id found by nc_inq_grpid() */
|
|
sp = strrchr(grpname, '/');
|
|
if(!sp) { /* No '/' in grpname, so return nc_inq_grpid() result */
|
|
ret = nc_inq_grp_ncid(ncid, grpname, grpidp);
|
|
goto done;
|
|
}
|
|
{ /* Parse group name out and get grpid using that */
|
|
char* p, *q;
|
|
int next;
|
|
|
|
p = grpname;
|
|
if(grpname[0] == '/') {
|
|
/* get ncid of the root group */
|
|
ncid = getrootid(ncid);
|
|
p++; /* skip leading '/' */
|
|
}
|
|
/* Walk down looking for each group in path in turn */
|
|
while(*p) {
|
|
q = strchr(p,'/');
|
|
if(q == NULL) q = p+strlen(p); /* point to trailing nul */
|
|
else *q++ = '\0';
|
|
/* Lookup this path segment wrt to current group */
|
|
if((ret=nc_inq_ncid(ncid,p,&next))) goto done;
|
|
/* move to next segment */
|
|
p = q;
|
|
ncid = next;
|
|
}
|
|
if(grpidp) *grpidp = ncid;
|
|
}
|
|
|
|
#else /* !USE_NETCDF4 */
|
|
/* Just return root */
|
|
if(grpidp) *grpidp = ncid;
|
|
#endif /* USE_NETCDF4 */
|
|
done:
|
|
if(grpname) free(grpname);
|
|
return ret;
|
|
}
|
|
|
|
/* Convert a full path name to a varid to the specific varid + grpid */
|
|
int
|
|
nc_inq_varid2(int ncid, const char *path0, int* varidp, int* grpidp)
|
|
{
|
|
int ret = NC_NOERR;
|
|
int grpid, varid;
|
|
char *v, *g, *prefix;
|
|
/* If '/' doesn't occur in name, just return id found by
|
|
* nc_inq_grpid()
|
|
*/
|
|
char* path = NULL;
|
|
|
|
path = strdup(path0);
|
|
if(path == NULL) {ret = NC_ENOMEM; goto done;}
|
|
|
|
/* Find the rightmost '/' and tag the start of the path */
|
|
g = strrchr(path,'/');
|
|
if(g == NULL) {
|
|
v = path;
|
|
prefix = "/"; /* make sure not free'd */
|
|
} else {
|
|
*g++ = '\0'; /* separate out the prefix */
|
|
prefix = path;
|
|
v = g;
|
|
}
|
|
/* convert the group prefix to a group id */
|
|
if((ret=nc_inq_grpid2(ncid,prefix,&grpid)))
|
|
goto done;
|
|
/* Lookup the var in the terminal group */
|
|
if((ret=nc_inq_varid(grpid,v,&varid)))
|
|
goto done;
|
|
if(grpidp)
|
|
*grpidp = grpid;
|
|
if(varidp)
|
|
*varidp = varid;
|
|
done:
|
|
if(path) free(path);
|
|
return ret;
|
|
}
|
|
|
|
/* Missing functionality that should be in nc_inq_dimid(), to get
|
|
* dimid from a full dimension path name that may include group
|
|
* names */
|
|
int
|
|
nc_inq_dimid2(int ncid, const char *dimname, int *dimidp) {
|
|
int ret = NC_NOERR;
|
|
/* If '/' doesn't occur in dimname, just return id found by
|
|
* nc_inq_dimid() */
|
|
char *sp = strrchr(dimname, '/');
|
|
if(!sp) { /* No '/' in dimname, so return nc_inq_dimid() result */
|
|
ret = nc_inq_dimid(ncid, dimname, dimidp);
|
|
}
|
|
#ifdef USE_NETCDF4
|
|
else { /* Parse group name out and get dimid using that */
|
|
size_t grp_namelen = sp - dimname;
|
|
char *grpname = emalloc(grp_namelen+1);
|
|
|
|
int grpid;
|
|
strncpy(grpname, dimname, grp_namelen+1);
|
|
grpname[grp_namelen] = '\0';
|
|
ret = nc_inq_grp_full_ncid(ncid, grpname, &grpid);
|
|
if(ret == NC_NOERR) {
|
|
ret = nc_inq_dimid(grpid, dimname, dimidp);
|
|
}
|
|
free(grpname);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* return 1 if varid identifies a record variable
|
|
* else return 0
|
|
*/
|
|
int
|
|
isrecvar(int ncid, int varid)
|
|
{
|
|
int ndims;
|
|
int is_recvar = 0;
|
|
int *dimids;
|
|
|
|
NC_CHECK( nc_inq_varndims(ncid, varid, &ndims) );
|
|
#ifdef USE_NETCDF4
|
|
if (ndims > 0) {
|
|
int nunlimdims;
|
|
int *recdimids;
|
|
int dim, recdim;
|
|
dimids = (int *) emalloc((ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_vardimid(ncid, varid, dimids) );
|
|
NC_CHECK( nc_inq_unlimdims(ncid, &nunlimdims, NULL) );
|
|
recdimids = (int *) emalloc((nunlimdims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_unlimdims(ncid, NULL, recdimids) );
|
|
for (dim = 0; dim < ndims && is_recvar == 0; dim++) {
|
|
for(recdim = 0; recdim < nunlimdims; recdim++) {
|
|
if(dimids[dim] == recdimids[recdim]) {
|
|
is_recvar = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(dimids);
|
|
free(recdimids);
|
|
}
|
|
#else
|
|
if (ndims > 0) {
|
|
int recdimid;
|
|
dimids = (int *) emalloc((ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_vardimid(ncid, varid, dimids) );
|
|
NC_CHECK( nc_inq_unlimdim(ncid, &recdimid) );
|
|
if(dimids[0] == recdimid)
|
|
is_recvar = 1;
|
|
free(dimids);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
return is_recvar;
|
|
}
|
|
|
|
static idnode_t*
|
|
newidnode(void) {
|
|
idnode_t *newvp = (idnode_t*) emalloc(sizeof(idnode_t));
|
|
return newvp;
|
|
}
|
|
|
|
/*
|
|
* Get a new, empty variable list.
|
|
*/
|
|
idnode_t*
|
|
newidlist(void) {
|
|
idnode_t *vp = newidnode();
|
|
|
|
vp -> next = 0;
|
|
vp -> id = -1; /* bad id */
|
|
|
|
return vp;
|
|
}
|
|
|
|
void
|
|
idadd(idnode_t* vlist, int varid) {
|
|
idnode_t *newvp = newidnode();
|
|
|
|
newvp -> next = vlist -> next;
|
|
newvp -> id = varid;
|
|
vlist -> next = newvp;
|
|
}
|
|
|
|
/*
|
|
* return true if id is member of list that idlist points to.
|
|
*/
|
|
bool_t
|
|
idmember(const idnode_t* idlist, int id)
|
|
{
|
|
idnode_t *vp = idlist -> next;
|
|
|
|
for (; vp ; vp = vp->next)
|
|
if (vp->id == id)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Release a variable list.
|
|
*/
|
|
void
|
|
freeidlist(idnode_t *idlist)
|
|
{
|
|
while(idlist) {
|
|
idnode_t *vp = idlist->next;
|
|
free(idlist);
|
|
idlist = vp;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return true if group identified by grpid is member of grpids, a list of groups.
|
|
* nlgrps is number of groups in the list.
|
|
*/
|
|
bool_t
|
|
group_wanted(int grpid, int nlgrps, const idnode_t* grpids)
|
|
{
|
|
/* If -g not specified, all groups are wanted */
|
|
if(nlgrps == 0) return true;
|
|
/* if -g specified, look for match in group id list */
|
|
return idmember(grpids, grpid);
|
|
}
|
|
|
|
/* Determine whether a group named formatting_specs.lgrps[igrp] exists
|
|
* in a netCDF file or group with id ncid. If so, return the count of
|
|
* how many matching groups were found, else return a count of 0. If
|
|
* the name begins with "/", it is interpreted as an absolute group
|
|
* name, in which case only 0 or 1 is returned. Otherwise, interpret
|
|
* it as a relative name, and the total number of occurrences within
|
|
* the file/group identified by ncid is returned.
|
|
*
|
|
* Also has side effect of updating the ngrpids and the associate
|
|
* grpids array that represent the group list specified by the -g
|
|
* option. TODO: put this in its own function instead.
|
|
*/
|
|
static size_t
|
|
nc_inq_grpname_count(int ncid, int igrp, char **lgrps, idnode_t *grpids) {
|
|
size_t count = 0;
|
|
#ifdef USE_NETCDF4
|
|
int numgrps;
|
|
int *ncids;
|
|
int g;
|
|
int grpid;
|
|
int status;
|
|
#endif
|
|
char *grpname = lgrps[igrp];
|
|
|
|
/* permit empty string to also designate root group */
|
|
if(grpname[0] == '\0' || NCSTREQ(grpname,"/")) {
|
|
count = 1;
|
|
idadd(grpids, ncid);
|
|
return count;
|
|
}
|
|
#ifdef USE_NETCDF4
|
|
/* Handle absolute group names */
|
|
if(grpname[0] == '/') {
|
|
int grpid;
|
|
status = nc_inq_grp_full_ncid(ncid, grpname, &grpid);
|
|
if(status == NC_NOERR) {
|
|
count = 1;
|
|
idadd(grpids, grpid);
|
|
} else if(status == NC_ENOGRP) {
|
|
count = 0;
|
|
} else {
|
|
error("when looking up group %s: %s ", grpname, nc_strerror(status));
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* look in this group */
|
|
status = nc_inq_grp_ncid(ncid, grpname, &grpid);
|
|
if (status == NC_NOERR) {
|
|
count++;
|
|
idadd(grpids, grpid);
|
|
}
|
|
/* if this group has subgroups, call recursively on each of them */
|
|
NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) );
|
|
if(numgrps > 0) {
|
|
/* Allocate memory to hold the list of group ids. */
|
|
ncids = emalloc(numgrps * sizeof(int));
|
|
/* Get the list of group ids. */
|
|
NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
|
|
/* Call this function recursively for each group. */
|
|
for (g = 0; g < numgrps; g++) {
|
|
count += nc_inq_grpname_count(ncids[g], igrp, lgrps, grpids);
|
|
}
|
|
free(ncids);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
return count;
|
|
}
|
|
|
|
/* Check if any group names specified with "-g grp1,...,grpn" are
|
|
* missing. Returns total number of matching groups if no missing
|
|
* groups detected, otherwise exits. */
|
|
int
|
|
grp_matches(int ncid, int nlgrps, char** lgrps, idnode_t *grpids) {
|
|
int ig;
|
|
size_t total = 0;
|
|
|
|
for (ig=0; ig < nlgrps; ig++) {
|
|
size_t count = nc_inq_grpname_count(ncid, ig, lgrps, grpids);
|
|
if(count == 0) {
|
|
error("%s: No such group", lgrps[ig]);
|
|
return 0;
|
|
}
|
|
total += count;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/* Returns 1 if string s1 ends with string s2, 0 otherwise. */
|
|
int
|
|
strendswith(const char *s1, const char *s2) {
|
|
size_t m1 = strlen(s1);
|
|
size_t m2 = strlen(s2);
|
|
if (m1 < m2)
|
|
return 0;
|
|
return (strcmp(s1 + (m1 - m2), s2) == 0);
|
|
}
|
|
|
|
/* Get varid of variable with name using nested group syntax
|
|
* "gp1/gp2/var" or "/gp1/gp2/var". In the former case, grpname of
|
|
* grp corresponding to grpid must end in "gp1/gp2". In the latter
|
|
* case, grpname for grpid must be exactly "/gp1/gp2". If variable
|
|
* named "var" is not in group grpid, returns NC_ENOTVAR, else sets
|
|
* varid and returns NC_NOERR. */
|
|
int
|
|
nc_inq_gvarid(int grpid, const char *varname, int *varidp) {
|
|
/* if varname has no "/" chars, then
|
|
return varidp from nc_inq_varid(grpid, varname, varidp)
|
|
if varname begins with "/"
|
|
|
|
else
|
|
get groupname corresponding to grpid
|
|
get vargroup = substring of varname up to last "/"
|
|
get relname = substring of varname after last "/"
|
|
if (varname starts with "/" and groupname == vargroup) ||
|
|
(groupname ends with vargroup)
|
|
return nc_inq_varid(grpid, relname, varidp)
|
|
else
|
|
return NC_ENOTVAR
|
|
*/
|
|
|
|
#ifdef USE_NETCDF4
|
|
char *vargroup;
|
|
char *relname;
|
|
char *groupname;
|
|
int status;
|
|
if (varname[0] == '\0')
|
|
return NC_ENOTVAR;
|
|
vargroup = strdup(varname);
|
|
if (vargroup == NULL)
|
|
return NC_ENOMEM;
|
|
relname = strrchr(vargroup, NC_GRP_DELIM);
|
|
if (relname != NULL) { /* name has a "/" in it */
|
|
size_t len; /* length of full group name for grpid */
|
|
*relname++ = '\0'; /* split vargroup string in two,
|
|
* vargroup and relname */
|
|
if ( (status = nc_inq_grpname_full(grpid, &len, NULL)) != NC_NOERR ) {
|
|
free(vargroup);
|
|
return status;
|
|
}
|
|
groupname = (char *)emalloc(len + 1);
|
|
if ( (status = nc_inq_grpname_full(grpid, &len, groupname)) == NC_NOERR ) {
|
|
if(varname[0] == NC_GRP_DELIM) {
|
|
if( strcmp(groupname, vargroup) == 0)
|
|
status = nc_inq_varid(grpid, relname, varidp);
|
|
else
|
|
status = NC_ENOTVAR;
|
|
} else {
|
|
if(strendswith(groupname, vargroup))
|
|
status = nc_inq_varid(grpid, relname, varidp);
|
|
else
|
|
status = NC_ENOTVAR;
|
|
}
|
|
}
|
|
free(vargroup);
|
|
free(groupname);
|
|
return status;
|
|
}
|
|
free(vargroup);
|
|
#endif /* USE_NETCDF4 */
|
|
return nc_inq_varid(grpid, varname, varidp);
|
|
}
|
|
|
|
/* 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);
|
|
#ifdef USE_NETCDF4
|
|
int numgrps;
|
|
int *ncids;
|
|
int g;
|
|
#endif
|
|
|
|
if (status == NC_NOERR)
|
|
count++;
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* if this group has subgroups, call recursively on each of them */
|
|
NC_CHECK( 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. */
|
|
int
|
|
missing_vars(int ncid, int nlvars, char **lvars) {
|
|
int iv;
|
|
for (iv=0; iv < nlvars; iv++) {
|
|
if(nc_inq_varname_count(ncid, lvars[iv]) == 0) {
|
|
error("%s: No such variable", lvars[iv]);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
make_lvars(char *optarg, int *nlvarsp, char ***lvarsp)
|
|
{
|
|
char *cp = optarg;
|
|
int nvars = 1;
|
|
char ** cpp;
|
|
|
|
/* compute number of variable names in comma-delimited list */
|
|
*nlvarsp = 1;
|
|
while (*cp++)
|
|
if (*cp == ',')
|
|
nvars++;
|
|
*nlvarsp = nvars;
|
|
*lvarsp = (char **) emalloc(nvars * sizeof(char*));
|
|
cpp = *lvarsp;
|
|
/* copy variable names into list */
|
|
for (cp = strtok(optarg, ","); cp != NULL; cp = strtok((char *) NULL, ",")) {
|
|
*cpp = strdup(cp);
|
|
cpp++;
|
|
}
|
|
}
|
|
|
|
void
|
|
make_lgrps(char *optarg, int *nlgrps, char ***lgrpsp, idnode_t **grpidsp)
|
|
{
|
|
char *cp = optarg;
|
|
int ngrps = 1;
|
|
char ** cpp;
|
|
|
|
/* compute number of group names in comma-delimited list */
|
|
while (*cp++)
|
|
if (*cp == ',')
|
|
ngrps++;
|
|
*nlgrps = ngrps;
|
|
*lgrpsp = (char **) emalloc(ngrps * sizeof(char*));
|
|
cpp = *lgrpsp;
|
|
/* copy group names into list */
|
|
for (cp = strtok(optarg, ","); cp != NULL; cp = strtok((char *) NULL, ",")) {
|
|
*cpp = strdup(cp);
|
|
cpp++;
|
|
}
|
|
/* make empty list of grpids, to be filled in after input file opened */
|
|
*grpidsp = newidlist();
|
|
}
|
|
|
|
/* initialize and return a new empty stack of grpids */
|
|
static ncgiter_t *
|
|
gs_init() {
|
|
ncgiter_t *s = emalloc(sizeof(ncgiter_t));
|
|
s->ngrps = 0;
|
|
s->top = NULL;
|
|
return s;
|
|
}
|
|
|
|
/* free a stack and all its nodes */
|
|
static void
|
|
gs_free(ncgiter_t *s) {
|
|
grpnode_t *n0, *n1;
|
|
n0 = s->top;
|
|
while (n0) {
|
|
n1 = n0->next;
|
|
free(n0);
|
|
n0 = n1;
|
|
}
|
|
free(s);
|
|
}
|
|
|
|
/* test if a stack is empty */
|
|
static int
|
|
gs_empty(ncgiter_t *s)
|
|
{
|
|
return s->ngrps == 0;
|
|
}
|
|
|
|
/* push a grpid on stack */
|
|
static void
|
|
gs_push(ncgiter_t *s, int grpid)
|
|
{
|
|
grpnode_t *node = emalloc(sizeof(grpnode_t));
|
|
|
|
node->grpid = grpid;
|
|
node->next = gs_empty(s) ? NULL : s->top;
|
|
s->top = node;
|
|
s->ngrps++;
|
|
}
|
|
|
|
/* pop value off stack and return */
|
|
static int
|
|
gs_pop(ncgiter_t *s)
|
|
{
|
|
if (gs_empty(s)) {
|
|
return -1; /* underflow, stack is empty */
|
|
} else { /* pop a node */
|
|
grpnode_t *top = s->top;
|
|
int value = top->grpid;
|
|
s->top = top->next;
|
|
/* TODO: first call to free gets seg fault with libumem */
|
|
free(top);
|
|
s->ngrps--;
|
|
return value;
|
|
}
|
|
}
|
|
|
|
#ifdef UNUSED
|
|
/* Return top value on stack without popping stack. Defined for
|
|
* completeness but not used (here). */
|
|
static int
|
|
gs_top(ncgiter_t *s)
|
|
{
|
|
if (gs_empty(s)) {
|
|
return -1; /* underflow, stack is empty */
|
|
} else { /* get top value */
|
|
grpnode_t *top = s->top;
|
|
int value = top->grpid;
|
|
return value;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Like netCDF-4 function nc_inq_grps(), but can be called from
|
|
* netCDF-3 only code as well. Maybe this is what nc_inq_grps()
|
|
* should do if built without netCDF-4 data model support. */
|
|
static int
|
|
nc_inq_grps2(int ncid, int *numgrps, int *grpids)
|
|
{
|
|
int stat = NC_NOERR;
|
|
|
|
/* just check if ncid is valid id of open netCDF file */
|
|
NC_CHECK(nc_inq(ncid, NULL, NULL, NULL, NULL));
|
|
|
|
#ifdef USE_NETCDF4
|
|
NC_CHECK(nc_inq_grps(ncid, numgrps, grpids));
|
|
#else
|
|
*numgrps = 0;
|
|
#endif
|
|
return stat;
|
|
}
|
|
|
|
/* Initialize group iterator for start group and all its descendant
|
|
* groups. */
|
|
int
|
|
nc_get_giter(int grpid, /* start group id */
|
|
ncgiter_t **iterp /* returned opaque iteration state */
|
|
)
|
|
{
|
|
int stat = NC_NOERR;
|
|
|
|
stat = nc_inq(grpid, NULL, NULL, NULL, NULL); /* check if grpid is valid */
|
|
if(stat != NC_EBADGRPID && stat != NC_EBADID) {
|
|
*iterp = gs_init();
|
|
gs_push(*iterp, grpid);
|
|
}
|
|
|
|
return stat;
|
|
}
|
|
|
|
/*
|
|
* Get group id of next group. On first call gets start group id,
|
|
* subsequently returns other subgroup ids in preorder. Returns zero
|
|
* when no more groups left.
|
|
*/
|
|
int
|
|
nc_next_giter(ncgiter_t *iterp, int *grpidp) {
|
|
int stat = NC_NOERR;
|
|
int numgrps;
|
|
int *grpids;
|
|
int i;
|
|
|
|
if(gs_empty(iterp)) {
|
|
*grpidp = 0; /* not a group, signals iterator is done */
|
|
} else {
|
|
*grpidp = gs_pop(iterp);
|
|
NC_CHECK(nc_inq_grps2(*grpidp, &numgrps, NULL));
|
|
if(numgrps > 0) {
|
|
grpids = (int *)emalloc(sizeof(int) * numgrps);
|
|
NC_CHECK(nc_inq_grps2(*grpidp, &numgrps, grpids));
|
|
for(i = numgrps - 1; i >= 0; i--) { /* push ids on stack in reverse order */
|
|
gs_push(iterp, grpids[i]);
|
|
}
|
|
free(grpids);
|
|
}
|
|
}
|
|
return stat;
|
|
}
|
|
|
|
/*
|
|
* Release group iter.
|
|
*/
|
|
void
|
|
nc_free_giter(ncgiter_t *iterp)
|
|
{
|
|
gs_free(iterp);
|
|
}
|
|
|
|
/*
|
|
* Get total number of groups (including the top-level group and all
|
|
* descendant groups, recursively) and all descendant subgroup ids
|
|
* (including the input rootid of the start group) for a group and
|
|
* all its descendants, in preorder.
|
|
*
|
|
* If grpids or numgrps is NULL, it will be ignored. So typical use
|
|
* is to call with grpids NULL to get numgrps, allocate enough space
|
|
* for the group ids, then call again to get them.
|
|
*/
|
|
int
|
|
nc_inq_grps_full(int rootid, int *numgrps, int *grpids)
|
|
{
|
|
int stat = NC_NOERR;
|
|
ncgiter_t *giter; /* pointer to group iterator */
|
|
int grpid;
|
|
size_t count;
|
|
|
|
NC_CHECK(nc_get_giter(rootid, &giter));
|
|
|
|
count = 0;
|
|
NC_CHECK(nc_next_giter(giter, &grpid));
|
|
while(grpid != 0) {
|
|
if(grpids)
|
|
grpids[count] = grpid;
|
|
count++;
|
|
NC_CHECK(nc_next_giter(giter, &grpid));
|
|
}
|
|
if(numgrps)
|
|
*numgrps = count;
|
|
nc_free_giter(giter);
|
|
return stat;
|
|
}
|
|
|
|
int
|
|
getrootid(int grpid)
|
|
{
|
|
int current = grpid;
|
|
#ifdef USE_NETCDF4
|
|
int parent = current;
|
|
/* see if root id */
|
|
for(;;) {
|
|
int stat = nc_inq_grp_parent(current,&parent);
|
|
if(stat) break;
|
|
current = parent;
|
|
}
|
|
#endif
|
|
return current;
|
|
}
|
|
|
|
#if 0
|
|
static int
|
|
parseFQN(int ncid, const char* fqn0, VarID* idp)
|
|
{
|
|
int stat = NC_NOERR;
|
|
char* fqn;
|
|
VarID vid;
|
|
char* p;
|
|
char* q;
|
|
char* segment;
|
|
|
|
vid.grpid = ncid;
|
|
if(fqn0 == NULL || fqn0[1] != '/')
|
|
{stat = NC_EBADNAME; goto done;}
|
|
fqn = strdup(fqn0+1); /* skip leading '/'*/
|
|
p = fqn;
|
|
for(;;) {
|
|
int newgrp;
|
|
segment = p;
|
|
q = p;
|
|
while(*p != '\0' && *p != '/') {
|
|
if(*p == '\\') p++;
|
|
*q++ = *p++;
|
|
}
|
|
if(*p == '\0') break;
|
|
*p++ = '\0';
|
|
if((stat=nc_inq_grp_ncid(vid.grpid,segment,&newgrp))) goto done;
|
|
vid.grpid = newgrp;
|
|
}
|
|
/* Segment should point to the varname */
|
|
if((stat=nc_inq_varid(vid.grpid,segment,&vid.varid))) goto done;
|
|
done:
|
|
if(fqn) free(fqn);
|
|
if(stat == NC_NOERR && idp != NULL) *idp = vid;
|
|
return stat;
|
|
}
|
|
#endif
|
|
|
|
/*********************************************************************************/
|
|
void nc_get_att_single_string(const int ncid, const int varid,
|
|
const struct ncatt_t *att, char **str_out) {
|
|
if (att->type == NC_CHAR) {
|
|
// NC_CHAR type attribute
|
|
// Use a call to nc_get_att_text which expects to output the attribute value
|
|
// into a char * pointing to allocated memory. The number of bytes to allocate
|
|
// is the attribute length (which is the number of elements in a vector, 1 for
|
|
// scalar) times the size of each element in bytes. The attribute length is
|
|
// held in att->len, and the attribute element size is in att->tinfo->size.
|
|
*str_out = emalloc((att->len + 1) * att->tinfo->size);
|
|
(*str_out)[att->len] = '\0';
|
|
NC_CHECK(nc_get_att_text(ncid, varid, att->name, *str_out));
|
|
} else if (att->type == NC_STRING) {
|
|
// NC_STRING type attribute
|
|
// Use a call to nc_get_att_string which expects to output the attribute value
|
|
// into a vector of char pointers, where each entry points to allocated memory.
|
|
// The vector of char pointers needs to be allocated to the length (number of strings)
|
|
// times the size of each entry (size of a char *).
|
|
char **att_strings = emalloc((att->len + 1) * att->tinfo->size);
|
|
NC_CHECK(nc_get_att_string(ncid, varid, att->name, att_strings));
|
|
// str_out needs to be allocated to a size large enough to hold the string that
|
|
// the first pointer in att_strings is pointing to.
|
|
size_t att_str_len = strlen(att_strings[0]);
|
|
*str_out = emalloc((att_str_len + 1) * att->tinfo->size);
|
|
(*str_out)[att_str_len] = '\0';
|
|
strncpy(*str_out, att_strings[0], att_str_len);
|
|
nc_free_string(att->len, att_strings); /* Warning: does not free att_strings */
|
|
free(att_strings);
|
|
} else {
|
|
fprintf(stderr,"nc_get_att_single_string: unknown attribute type: %d\n", att->type);
|
|
fprintf(stderr," must use one of: NC_CHAR, NC_STRING\n");
|
|
fflush(stderr); fflush(stdout);
|
|
exit(2);
|
|
}
|
|
}
|