1/*********************************************************************
2 *   Copyright 2011, University Corporation for Atmospheric Research
3 *   See netcdf/README file for copying and redistribution conditions.
4 *   $Id$
5 *********************************************************************/
6
7#include "config.h"
8#include <stdarg.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <netcdf.h>
13#include <assert.h>
14#include <ctype.h>
15#include "utils.h"
16
17/*
18 * Print error message to stderr and exit
19 */
20void
21error(const char *fmt, ...)
22{
23    va_list args ;
24
25    (void) fprintf(stderr,"%s: ", progname);
26    va_start(argsfmt) ;
27    (void) vfprintf(stderr,fmt,args) ;
28    va_end(args) ;
29
30    (void) fprintf(stderr, "\n") ;
31    (void) fflush(stderr); /* to ensure log files are current */
32    exit(EXIT_FAILURE);
33}
34
35void *
36emalloc ( /* check return from malloc */
37 size_t size)
38{
39    void   *p;
40
41    p = (void *) malloc (size==0 ? 1 : size); /* malloc(0) not portable */
42    if (p == 0) {
43 error ("out of memory\n");
44    }
45    return p;
46}
47
48
49void
50check(int err, const char* file, const int line)
51{
52    fprintf(stderr,"%s\n",nc_strerror(err));
53    fprintf(stderr,"Location: file %s; line %d\n", file,line);
54    fflush(stderr); fflush(stdout);
55    exit(1);
56}
57
58
59/*
60 * Returns malloced name with chars special to CDL escaped.
61 * Caller should free result when done with it.
62 */
63char*
64escaped_name(const char* cp) {
65    char *ret; /* string returned */
66    char *sp;
67    assert(cp != NULL);
68
69    /* For some reason, and on some machines (e.g. tweety)
70       utf8 characters such as \343 are considered control character. */
71/*    if(*cp && (isspace(*cp) | iscntrl(*cp)))*/
72    if((*cp >= 0x01 && *cp <= 0x20) || (*cp == 0x7f))
73    {
74 error("name begins with space or control-character: %c",*cp);
75    }
76
77    ret = emalloc(4*strlen(cp) + 1); /* max if every char escaped */
78    sp = ret;
79    *sp = 0;     /* empty name OK */
80    /* Special case: leading number allowed, but we must escape it for CDL */
81    if((*cp >= '0' && *cp <= '9'))
82    {
83 *sp++ = '\\';
84    }
85    for (; *cpcp++) {
86 if (isascii((int)*cp)) {
87     if(iscntrl((int)*cp)) { /* render control chars as two hex digits, \%xx */
88 snprintf(sp, 4,"\\%%%.2x", *cp);
89 sp += 4;
90     } else {
91 switch (*cp) {
92 case ' ':
93 case '!':
94 case '"':
95 case '#':
96 case '$':
97 case '&':
98 case '\'':
99 case '(':
100 case ')':
101 case '*':
102 case ',':
103 case ':':
104 case ';':
105 case '<':
106 case '=':
107 case '>':
108 case '?':
109 case '[':
110 case ']':
111 case '\\':
112 case '^':
113 case '`':
114 case '{':
115 case '|':
116 case '}':
117 case '~':
118     *sp++ = '\\';
119     *sp++ = *cp;
120     break;
121 default: /* includes '/' */
122     *sp++ = *cp;
123     break;
124 }
125     }
126 } else {  /* not ascii, assume just UTF-8 byte */
127     *sp++ = *cp;
128 }
129    }
130    *sp = 0;
131    return ret;
132}
133
134
135/*
136 * Print name with escapes for special characters
137 */
138void
139print_name(const char* name) {
140    char *ename = escaped_name(name);
141    fputs(enamestdout);
142    free(ename);
143}
144
145/* Missing functionality that should be in nc_inq_dimid(), to get
146 * dimid from a full dimension path name that may include group
147 * names */
148int
149nc_inq_dimid2(int ncid, const char *dimname, int *dimidp) {
150    int ret = NC_NOERR;
151    /* If '/' doesn't occur in dimname, just return id found by
152     * nc_inq_dimid() */
153    char *sp = strrchr(dimname, '/');
154    if(!sp) { /* No '/' in dimname, so return nc_inq_dimid() result */
155 ret = nc_inq_dimid(nciddimnamedimidp);
156    }
157#ifdef USE_NETCDF4
158    else {  /* Parse group name out and get dimid using that */
159      size_t grp_namelen = sp - dimname;
160      char *grpname = emalloc(grp_namelen+1);
161
162      int grpid;
163      strncpy(grpnamedimnamegrp_namelen+1);
164      grpname[grp_namelen] = '\0';
165      ret = nc_inq_grp_full_ncid(ncidgrpname, &grpid);
166      if(ret == NC_NOERR) {
167 ret = nc_inq_dimid(grpiddimnamedimidp);
168      }
169      free(grpname);
170    }
171#endif /* USE_NETCDF4 */
172    return ret;
173}
174
175
176/*
177 * return 1 if varid identifies a record variable
178 * else return 0
179 */
180int
181isrecvar(int ncid, int varid)
182{
183    int ndims;
184    int is_recvar = 0;
185    int *dimids;
186
187    NC_CHECKnc_inq_varndims(ncidvarid, &ndims) );
188#ifdef USE_NETCDF4
189    if (ndims > 0) {
190 int nunlimdims;
191 int *recdimids;
192 int dimrecdim;
193 dimids = (int *) emalloc((ndims + 1) * sizeof(int));
194 NC_CHECKnc_inq_vardimid(ncidvariddimids) );
195 NC_CHECKnc_inq_unlimdims(ncid, &nunlimdimsNULL) );
196 recdimids = (int *) emalloc((nunlimdims + 1) * sizeof(int));
197 NC_CHECKnc_inq_unlimdims(ncidNULLrecdimids) );
198 for (dim = 0; dim < ndims && is_recvar == 0; dim++) {
199     for(recdim = 0; recdim < nunlimdimsrecdim++) {
200 if(dimids[dim] == recdimids[recdim]) {
201     is_recvar = 1;
202     break;
203 }
204     }
205 }
206 free(dimids);
207 free(recdimids);
208    }
209#else
210    if (ndims > 0) {
211 int recdimid;
212 dimids = (int *) emalloc((ndims + 1) * sizeof(int));
213 NC_CHECKnc_inq_vardimid(ncidvariddimids) );
214 NC_CHECKnc_inq_unlimdim(ncid, &recdimid) );
215 if(dimids[0] == recdimid)
216     is_recvar = 1;
217 free(dimids);
218    }
219#endif /* USE_NETCDF4 */
220    return is_recvar;
221}
222
223static idnode_t*
224newidnode(void) {
225    idnode_t *newvp = (idnode_t*) emalloc(sizeof(idnode_t));
226    return newvp;
227}
228
229/*
230 * Get a new, empty variable list.
231 */
232idnode_t*
233newidlist(void) {
234    idnode_t *vp = newidnode();
235
236    vp -> next = 0;
237    vp -> id = -1; /* bad id */
238
239    return vp;
240}
241
242void
243idadd(idnode_tvlist, int varid) {
244    idnode_t *newvp = newidnode();
245
246    newvp -> next = vlist -> next;
247    newvp -> id = varid;
248    vlist -> next = newvp;
249}
250
251/*
252 * return true if id is member of list that idlist points to.
253 */
254bool_t
255idmember(const idnode_tidlist, int id)
256{
257    idnode_t *vp = idlist -> next;
258
259    for (; vp ; vp = vp->next)
260      if (vp->id == id)
261 return true;
262    return false;
263}
264
265/*
266 * Release a variable list.
267 */
268void
269freeidlist(idnode_t *idlist)
270{
271   while(idlist) {
272      idnode_t *vp = idlist->next;
273      free(idlist);
274      idlist = vp;
275   }
276}
277
278/*
279 * Return true if group identified by grpid is member of grpids, a list of groups.
280 * nlgrps is number of groups in the list.
281 */
282bool_t
283group_wanted(int grpid, int nlgrps, const idnode_tgrpids)
284{
285    /* If -g not specified, all groups are wanted */
286    if(nlgrps == 0) return true;
287    /* if -g specified, look for match in group id list */
288    return idmember(grpidsgrpid);
289}
290
291/* Determine whether a group named formatting_specs.lgrps[igrp] exists
292 * in a netCDF file or group with id ncid.  If so, return the count of
293 * how many matching groups were found, else return a count of 0.  If
294 * the name begins with "/", it is interpreted as an absolute group
295 * name, in which case only 0 or 1 is returned.  Otherwise, interpret
296 * it as a relative name, and the total number of occurrences within
297 * the file/group identified by ncid is returned.
298 *
299 * Also has side effect of updating the ngrpids and the associate
300 * grpids array that represent the group list specified by the -g
301 * option.  TODO: put this in its own function instead.
302 */
303static size_t
304nc_inq_grpname_count(int ncid, int igrp, char **lgrpsidnode_t *grpids) {
305    size_t count = 0;
306#ifdef USE_NETCDF4
307    int numgrps;
308    int *ncids;
309    int g;
310    int grpid;
311    int status;
312#endif
313    char *grpname = lgrps[igrp];
314
315    /* permit empty string to also designate root group */
316    if(grpname[0] == '\0' || STREQ(grpname,"/")) {
317 count = 1;
318 idadd(grpidsncid);
319 return count;
320    }
321#ifdef USE_NETCDF4
322    /* Handle absolute group names */
323    if(grpname[0] == '/') {
324 int grpid;
325 status = nc_inq_grp_full_ncid(ncidgrpname, &grpid);
326 if(status == NC_NOERR) {
327     count = 1;
328     idadd(grpidsgrpid);
329 } else if(status == NC_ENOGRP) {
330     count = 0;
331 } else {
332     error("when looking up group %s: %s ", grpnamenc_strerror(status));
333 }
334 return count;
335    }
336
337    /* look in this group */
338    status = nc_inq_grp_ncid(ncidgrpname, &grpid);
339    if (status == NC_NOERR) {
340 count++;
341 idadd(grpidsgrpid);
342    }
343    /* if this group has subgroups, call recursively on each of them */
344    NC_CHECKnc_inq_grps(ncid, &numgrpsNULL) );
345    if(numgrps > 0) {
346 /* Allocate memory to hold the list of group ids. */
347 ncids = emalloc(numgrps * sizeof(int));
348 /* Get the list of group ids. */
349 NC_CHECKnc_inq_grps(ncidNULLncids) );
350 /* Call this function recursively for each group. */
351 for (g = 0; g < numgrpsg++) {
352     count += nc_inq_grpname_count(ncids[g], igrplgrpsgrpids);
353 }
354 free(ncids);
355    }
356#endif /* USE_NETCDF4 */
357    return count;
358}
359
360/* Check if any group names specified with "-g grp1,...,grpn" are
361 * missing.  Returns total number of matching groups if no missing
362 * groups detected, otherwise exits. */
363int
364grp_matches(int ncid, int nlgrps, char** lgrpsidnode_t *grpids) {
365    int ig;
366    size_t total = 0;
367
368    for (ig=0; ig < nlgrpsig++) {
369 size_t count = nc_inq_grpname_count(ncidiglgrpsgrpids);
370 if(count == 0) {
371     error("%s: No such group", lgrps[ig]);
372     return 0;
373 }
374 total += count;
375    }
376    return total;
377}
378
379/* Returns 1 if string s1 ends with string s2, 0 otherwise. */
380int
381strendswith(const char *s1, const char *s2) {
382    size_t m1 = strlen(s1);
383    size_t m2 = strlen(s2);
384    if (m1 < m2)
385 return 0;
386    return (strcmp(s1 + (m1 - m2), s2) == 0);
387}
388
389/* Get varid of variable with name using nested group syntax
390 * "gp1/gp2/var" or "/gp1/gp2/var".  In the former case, grpname of
391 * grp corresponding to grpid must end in "gp1/gp2".  In the latter
392 * case, grpname for grpid must be exactly "/gp1/gp2".  If variable
393 * named "var" is not in group grpid, returns NC_ENOTVAR, else sets
394 * varid and returns NC_NOERR.  */
395int
396nc_inq_gvarid(int grpid, const char *varname, int *varidp) {
397    /* if varname has no "/" chars, then
398          return varidp from nc_inq_varid(grpid, varname, varidp)
399       if varname begins with "/"
400
401       else
402          get groupname corresponding to grpid
403          get vargroup = substring of varname up to last "/"
404          get relname = substring of varname after last "/"
405          if (varname starts with "/" and groupname == vargroup) ||
406             (groupname ends with vargroup)
407             return nc_inq_varid(grpid, relname, varidp)
408          else
409             return NC_ENOTVAR
410    */
411
412#ifdef USE_NETCDF4
413    char *vargroup;
414    char *relname;
415    char *groupname;
416    int status;
417    if (varname[0] == '\0')
418 return NC_ENOTVAR;
419    vargroup = strdup(varname);
420    if (vargroup == NULL)
421 return NC_ENOMEM;
422    relname = strrchr(vargroupNC_GRP_DELIM);
423    if (relname != NULL) { /* name has a "/" in it */
424 size_t len; /* length of full group name for grpid */
425 *relname++ = '\0'; /* split vargroup string in two,
426  * vargroup and relname */
427 if ( (status = nc_inq_grpname_full(grpid, &lenNULL)) != NC_NOERR ) {
428     free(vargroup);
429     return status;
430 }
431 groupname = (char *)emalloc(len + 1);
432 if ( (status = nc_inq_grpname_full(grpid, &lengroupname)) == NC_NOERR ) {
433     if(varname[0] == NC_GRP_DELIM) {
434 if( strcmp(groupnamevargroup) == 0)
435     status = nc_inq_varid(grpidrelnamevaridp);
436 else
437     status = NC_ENOTVAR;
438     } else {
439 if(strendswith(groupnamevargroup))
440     status = nc_inq_varid(grpidrelnamevaridp);
441 else
442     status = NC_ENOTVAR;
443     }
444 }
445 free(vargroup);
446 free(groupname);
447 return status;
448    }
449    free(vargroup);
450#endif /* USE_NETCDF4 */
451    return nc_inq_varid(grpidvarnamevaridp);
452}
453
454/* Determine whether a variable named varname exists in any group in
455   an open netCDF file with id ncid.  If so, return the count of how
456   many matching variables were found, else return a count of 0.  The
457   variable name can be absolute such as "/foo" or "/GRP1/GRP1A/foo",
458   in which case there is only one group to look in, given by the path
459   from the root group.  Alternatively, the variable name can be
460   relative, such as "foo" or "GRPA/GRPB/foo", in which case every
461   group is examined for a variable with that relative name.  */
462size_t
463nc_inq_varname_count(int ncid, char *varname) {
464    /*
465       count = 0;
466       status = nc_inq_gvarid(ncid, varname, varid);
467       if (status == NC_NOERR)
468          count++;
469       for each subgroup gid {
470          count += nc_inq_varname_count(gid, varname);
471       }
472       return count;
473    */
474    size_t count = 0;
475    int varid;
476    /* look in this group */
477    int status = nc_inq_gvarid(ncidvarname, &varid);
478#ifdef USE_NETCDF4
479    int numgrps;
480    int *ncids;
481    int g;
482#endif
483
484    if (status == NC_NOERR)
485 count++;
486
487#ifdef USE_NETCDF4
488    /* if this group has subgroups, call recursively on each of them */
489    NC_CHECKnc_inq_grps(ncid, &numgrpsNULL) );
490
491    /* Allocate memory to hold the list of group ids. */
492    ncids = emalloc((numgrps + 1) * sizeof(int));
493
494    /* Get the list of group ids. */
495    NC_CHECKnc_inq_grps(ncidNULLncids) );
496
497    /* Call this function for each group. */
498    for (g = 0; g < numgrpsg++) {
499 count += nc_inq_varname_count(ncids[g], varname);
500    }
501    free(ncids);
502#endif /* USE_NETCDF4 */
503    return count;
504
505}
506
507/* Check if any variable names specified with "-v var1,...,varn" are
508 * missing.  Returns 0 if no missing variables detected, otherwise
509 * exits. */
510int
511missing_vars(int ncid, int nlvars, char **lvars) {
512    int iv;
513    for (iv=0; iv < nlvarsiv++) {
514 if(nc_inq_varname_count(ncidlvars[iv]) == 0) {
515     error("%s: No such variable", lvars[iv]);
516 }
517    }
518    return 0;
519}
520
521void
522make_lvars(char *optarg, int *nlvarsp, char ***lvarsp)
523{
524    char *cp = optarg;
525    int nvars = 1;
526    char ** cpp;
527
528    /* compute number of variable names in comma-delimited list */
529    *nlvarsp = 1;
530    while (*cp++)
531      if (*cp == ',')
532  nvars++;
533    *nlvarsp = nvars;
534    *lvarsp = (char **) emalloc(nvars * sizeof(char*));
535    cpp = *lvarsp;
536    /* copy variable names into list */
537    for (cp = strtok(optarg, ","); cp != NULLcp = strtok((char *) NULL, ",")) {
538 *cpp = strdup(cp);
539 cpp++;
540    }
541}
542
543void
544make_lgrps(char *optarg, int *nlgrps, char ***lgrpspidnode_t **grpidsp)
545{
546    char *cp = optarg;
547    int ngrps = 1;
548    char ** cpp;
549
550    /* compute number of group names in comma-delimited list */
551    while (*cp++)
552      if (*cp == ',')
553  ngrps++;
554    *nlgrps = ngrps;
555    *lgrpsp = (char **) emalloc(ngrps * sizeof(char*));
556    cpp = *lgrpsp;
557    /* copy group names into list */
558    for (cp = strtok(optarg, ","); cp != NULLcp = strtok((char *) NULL, ",")) {
559 *cpp = strdup(cp);
560 cpp++;
561    }
562    /* make empty list of grpids, to be filled in after input file opened */
563    *grpidsp = newidlist();
564}
565
566/* initialize and return a new empty stack of grpids */
567static ncgiter_t *
568gs_init() {
569    ncgiter_t *s = emalloc(sizeof(ncgiter_t));
570    s->ngrps = 0;
571    s->top = NULL;
572    return s;
573}
574
575/* free a stack and all its nodes */
576static void
577gs_free(ncgiter_t *s) {
578    grpnode_t *n0, *n1;
579    n0 = s->top;
580    while (n0) {
581 n1 = n0->next;
582 free(n0);
583 n0 = n1;
584    }
585    free(s);
586}
587
588/* test if a stack is empty */
589static int
590gs_empty(ncgiter_t *s)
591{
592    return s->ngrps == 0;
593}
594
595/* push a grpid on stack */
596static void
597gs_push(ncgiter_t *s, int grpid)
598{
599    grpnode_t *node = emalloc(sizeof(grpnode_t));
600
601    node->grpid = grpid;
602    node->next = gs_empty(s) ? NULL : s->top;
603    s->top = node;
604    s->ngrps++;
605}
606
607/* pop value off stack and return */
608static int
609gs_pop(ncgiter_t *s)
610{
611    if (gs_empty(s)) {
612 return -1; /* underflow, stack is empty */
613    } else { /* pop a node */
614 grpnode_t *top = s->top;
615 int value = top->grpid;
616 s->top = top->next;
617 /* TODO: first call to free gets seg fault with libumem */
618 free(top);
619 s->ngrps--;
620 return value;
621    }
622}
623
624#ifdef UNUSED
625/* Return top value on stack without popping stack.  Defined for
626 * completeness but not used (here). */
627static int
628gs_top(ncgiter_t *s)
629{
630    if (gs_empty(s)) {
631 return -1; /* underflow, stack is empty */
632    } else { /* get top value */
633 grpnode_t *top = s->top;
634 int value = top->grpid;
635 return value;
636    }
637}
638#endif
639
640/* Like netCDF-4 function nc_inq_grps(), but can be called from
641 * netCDF-3 only code as well.  Maybe this is what nc_inq_grps()
642 * should do if built without netCDF-4 data model support. */
643static int
644nc_inq_grps2(int ncid, int *numgrps, int *grpids)
645{
646    int stat = NC_NOERR;
647
648    /* just check if ncid is valid id of open netCDF file */
649    NC_CHECK(nc_inq(ncidNULLNULLNULLNULL));
650
651#ifdef USE_NETCDF4
652    NC_CHECK(nc_inq_grps(ncidnumgrpsgrpids));
653#else
654    *numgrps = 0;
655#endif
656    return stat;
657}
658
659/* Initialize group iterator for start group and all its descendant
660 * groups. */
661int
662nc_get_giter(int grpid,        /* start group id */
663     ncgiter_t **iterp  /* returned opaque iteration state */
664    )
665{
666    int stat = NC_NOERR;
667
668    stat = nc_inq(grpidNULLNULLNULLNULL); /* check if grpid is valid */
669    if(stat != NC_EBADGRPID && stat != NC_EBADID) {
670 *iterp = gs_init();
671 gs_push(*iterpgrpid);
672    }
673
674    return stat;
675}
676
677/*
678 * Get group id of next group.  On first call gets start group id,
679 * subsequently returns other subgroup ids in preorder.  Returns zero
680 * when no more groups left.
681 */
682int
683nc_next_giter(ncgiter_t *iterp, int *grpidp) {
684    int stat = NC_NOERR;
685    int numgrps;
686    int *grpids;
687    int i;
688
689    if(gs_empty(iterp)) {
690 *grpidp = 0; /* not a group, signals iterator is done */
691    } else {
692 *grpidp = gs_pop(iterp);
693 NC_CHECK(nc_inq_grps2(*grpidp, &numgrpsNULL));
694 if(numgrps > 0) {
695     grpids = (int *)emalloc(sizeof(int) * numgrps);
696     NC_CHECK(nc_inq_grps2(*grpidp, &numgrpsgrpids));
697     for(i = numgrps - 1; i >= 0; i--) { /* push ids on stack in reverse order */
698 gs_push(iterpgrpids[i]);
699     }
700     free(grpids);
701 }
702    }
703    return stat;
704}
705
706/*
707 * Release group iter.
708 */
709void
710nc_free_giter(ncgiter_t *iterp)
711{
712    gs_free(iterp);
713}
714
715/*
716 * Get total number of groups (including the top-level group and all
717 * descendant groups, recursively) and all descendant subgroup ids
718 * (including the input rootid of the start group) for a group and
719 * all its descendants, in preorder.
720 *
721 * If grpids or numgrps is NULL, it will be ignored.  So typical use
722 * is to call with grpids NULL to get numgrps, allocate enough space
723 * for the group ids, then call again to get them.
724 */
725int
726nc_inq_grps_full(int rootid, int *numgrps, int *grpids)
727{
728    int stat = NC_NOERR;
729    ncgiter_t *giter; /* pointer to group iterator */
730    int grpid;
731    size_t count;
732
733    NC_CHECK(nc_get_giter(rootid, &giter));
734
735    count = 0;
736    NC_CHECK(nc_next_giter(giter, &grpid));
737    while(grpid != 0) {
738 if(grpids)
739     grpids[count] = grpid;
740 count++;
741 NC_CHECK(nc_next_giter(giter, &grpid));
742    }
743    if(numgrps)
744 *numgrps = count;
745    nc_free_giter(giter);
746    return stat;
747}
748
749int
750getrootid(int grpid)
751{
752    int current = grpid;
753#ifdef USE_NETCDF4
754    int parent = current;
755    /* see if root id */
756    for(;;) {
757        int stat = nc_inq_grp_parent(current,&parent);
758        if(stat) break;
759 current = parent;
760    }
761#endif
762    return current;
763}
764


HyperKWIC - Version 7.20DA executed at 11:37 on 27 Oct 2017 | Polyhedron Solutions - INTERNAL USE | COMMERCIAL (Any O/S) SN 4AKIed