source: XIOS/dev/dev_olga/extern/src_netcdf4/nc4internal.c @ 1620

Last change on this file since 1620 was 409, checked in by ymipsl, 11 years ago

Add improved nectdf internal library src

YM

  • Property svn:eol-style set to native
File size: 37.3 KB
Line 
1/** \file \internal
2Internal netcdf-4 functions.
3
4This file contains functions internal to the netcdf4 library. None of
5the functions in this file are exposed in the exetnal API. These
6functions all relate to the manipulation of netcdf-4's in-memory
7buffer of metadata information, i.e. the linked list of NC_FILE_INFO_T
8structs.
9
10Copyright 2003-2011, University Corporation for Atmospheric
11Research. See the COPYRIGHT file for copying and redistribution
12conditions.
13
14*/
15#include "config.h"
16#include "nc4internal.h"
17#include "nc.h" /* from libsrc */
18#include "ncdispatch.h" /* from libdispatch */
19#include <utf8proc.h>
20
21#define MEGABYTE 1048576
22
23/* These are the default chunk cache sizes for HDF5 files created or
24 * opened with netCDF-4. */
25extern size_t nc4_chunk_cache_size;
26extern size_t nc4_chunk_cache_nelems;
27extern float nc4_chunk_cache_preemption;
28
29/* This is to track opened HDF5 objects to make sure they are
30 * closed. */
31#ifdef EXTRA_TESTS
32extern int num_spaces;
33#endif /* EXTRA_TESTS */
34
35#ifdef LOGGING
36/* This is the severity level of messages which will be logged. Use
37   severity 0 for errors, 1 for important log messages, 2 for less
38   important, etc. */
39int nc_log_level = -1;
40
41#endif /* LOGGING */
42
43/* Check and normalize and name. */
44int
45nc4_check_name(const char *name, char *norm_name)
46{
47   char *temp;
48   int retval;
49
50   /* Check the length. */
51   if (strlen(name) > NC_MAX_NAME)
52      return NC_EMAXNAME;
53
54   /* Make sure this is a valid netcdf name. This should be done
55    * before the name is normalized, because it gives better error
56    * codes for bad utf8 strings. */
57   if ((retval = NC_check_name(name)))
58      return retval;
59
60   /* Normalize the name. */
61   if (!(temp = (char *)utf8proc_NFC((const unsigned char *)name)))
62      return NC_EINVAL;
63   strcpy(norm_name, temp);
64   free(temp);
65     
66   return NC_NOERR;
67}
68
69/* Given a varid, find its shape. For unlimited dimensions, return
70   the current number of records. */
71static int
72find_var_shape_grp(NC_GRP_INFO_T *grp, int varid, int *ndims, 
73                   int *dimid, size_t *dimlen)
74{
75   hid_t datasetid = 0, spaceid = 0;
76   NC_VAR_INFO_T *var;
77   hsize_t *h5dimlen = NULL, *h5dimlenmax = NULL;
78   int d, dataset_ndims = 0;
79   int retval = NC_NOERR;
80
81   /* Find this var. */
82   for (var = grp->var; var; var = var->next)
83      if (var->varid == varid)
84         break;
85   if (!var)
86      return NC_ENOTVAR;
87
88   /* Get the dimids and the ndims for this var. */
89   if (ndims)
90      *ndims = var->ndims;
91
92   if (dimid)
93      for (d = 0; d < var->ndims; d++)
94         dimid[d] = var->dimids[d];
95   
96   if (dimlen)
97   {
98      /* If the var hasn't been created yet, its size is 0. */
99      if (!var->created)
100      {
101         for (d = 0; d < var->ndims; d++)
102            dimlen[d] = 0;
103      }
104      else
105      {
106         /* Get the number of records in the dataset. */
107         if ((retval = nc4_open_var_grp2(grp, var->varid, &datasetid)))
108            BAIL(retval);
109         if ((spaceid = H5Dget_space(datasetid)) < 0)
110            BAIL(NC_EHDFERR);
111#ifdef EXTRA_TESTS
112         num_spaces++;
113#endif
114         /* If it's a scalar dataset, it has length one. */
115         if (H5Sget_simple_extent_type(spaceid) == H5S_SCALAR)
116         {
117            dimlen[0] = 1;
118         }
119         else
120         {
121            /* Check to make sure ndims is right, then get the len of each
122               dim in the space. */
123            if ((dataset_ndims = H5Sget_simple_extent_ndims(spaceid)) < 0)
124               BAIL(NC_EHDFERR);
125            if (ndims && dataset_ndims != *ndims)
126               BAIL(NC_EHDFERR);
127            if (!(h5dimlen = malloc(dataset_ndims * sizeof(hsize_t))))
128               BAIL(NC_ENOMEM);
129            if (!(h5dimlenmax = malloc(dataset_ndims * sizeof(hsize_t))))
130               BAIL(NC_ENOMEM);
131            if ((dataset_ndims = H5Sget_simple_extent_dims(spaceid, 
132                                                           h5dimlen, h5dimlenmax)) < 0)
133               BAIL(NC_EHDFERR);
134            LOG((5, "find_var_shape_nc: varid %d len %d max: %d", 
135                 varid, (int)h5dimlen[0], (int)h5dimlenmax[0]));
136            for (d=0; d<dataset_ndims; d++)
137               dimlen[d] = h5dimlen[d];
138         }
139      }
140   }
141
142  exit:
143   if (spaceid > 0 && H5Sclose(spaceid) < 0)
144      BAIL2(NC_EHDFERR);
145#ifdef EXTRA_TESTS
146   num_spaces--;
147#endif
148   if (h5dimlen) free(h5dimlen);
149   if (h5dimlenmax) free(h5dimlenmax);
150   return retval;
151}
152
153/* Given an NC_FILE_INFO_T pointer, add the necessary stuff for a
154 * netcdf-4 file. */
155int
156nc4_nc4f_list_add(NC_FILE_INFO_T *nc, const char *path, int mode)
157{
158   NC_HDF5_FILE_INFO_T *h5;
159   NC_GRP_INFO_T *grp;
160
161   assert(nc && !nc->nc4_info && path);
162
163   /* The NC_FILE_INFO_T was allocated and inited by
164      ncfunc.c before this function is called. We need to malloc and
165      initialize the substructure NC_HDF_FILE_INFO_T. */
166   if (!(nc->nc4_info = calloc(1, sizeof(NC_HDF5_FILE_INFO_T))))
167      return NC_ENOMEM;
168   h5 = nc->nc4_info;
169
170   /* Hang on to the filename for nc_abort. */
171   if (!(h5->path = malloc((strlen(path) + 1) * sizeof(char))))
172      return NC_ENOMEM;
173   strcpy(h5->path, path);
174
175   /* Hang on to cmode, and note that we're in define mode. */
176   h5->cmode = mode | NC_INDEF;
177
178   /* The next_typeid needs to be set beyond the end of our atomic
179    * types. */
180   h5->next_typeid = NC_FIRSTUSERTYPEID;
181
182   /* There's always at least one open group - the root
183    * group. Allocate space for one group's worth of information. Set
184    * its hdf id, name, and a pointer to it's file structure. */
185   return nc4_grp_list_add(&(h5->root_grp), h5->next_nc_grpid++, 
186                           NULL, nc, NC_GROUP_NAME, &grp);
187}
188/* /\* Given an ncid, find the relevant group and return a pointer to */
189/*  * it. *\/ */
190/* NC_GRP_INFO_T * */
191/* find_nc_grp(int ncid) */
192/* { */
193/*    NC_FILE_INFO_T *f; */
194
195/*    for (f = nc_file; f; f = f->next) */
196/*    { */
197/*       if (f->ext_ncid == (ncid & FILE_ID_MASK)) */
198/*       { */
199/*       assert(f->nc4_info && f->nc4_info->root_grp); */
200/*       return nc4_rec_find_grp(f->nc4_info->root_grp, (ncid & GRP_ID_MASK)); */
201/*       } */
202/*    } */
203
204/*    return NULL; */
205/* } */
206
207/* Given an ncid, find the relevant group and return a pointer to it,
208 * return an error of this is not a netcdf-4 file (or if strict nc3 is
209 * turned on for this file.) */
210
211
212int
213nc4_find_nc4_grp(int ncid, NC_GRP_INFO_T **grp)
214{
215   NC_FILE_INFO_T *f = nc4_find_nc_file(ncid);
216   if(f == NULL) return NC_EBADID;
217
218   /* No netcdf-3 files allowed! */
219   if (!f->nc4_info) return NC_ENOTNC4;
220   assert(f->nc4_info->root_grp);
221
222   /* This function demands netcdf-4 files without strict nc3
223    * rules.*/
224   if (f->nc4_info->cmode & NC_CLASSIC_MODEL) return NC_ESTRICTNC3;
225
226   /* If we can't find it, the grp id part of ncid is bad. */
227   if (!(*grp = nc4_rec_find_grp(f->nc4_info->root_grp, (ncid & GRP_ID_MASK))))
228      return NC_EBADID;
229   return NC_NOERR;
230}
231
232/* Given an ncid, find the relevant group and return a pointer to it,
233 * also set a pointer to the nc4_info struct of the related file. For
234 * netcdf-3 files, *h5 will be set to NULL. */
235int
236nc4_find_grp_h5(int ncid, NC_GRP_INFO_T **grp, NC_HDF5_FILE_INFO_T **h5)
237{
238    NC_FILE_INFO_T *f = nc4_find_nc_file(ncid);
239    if(f == NULL) return NC_EBADID;
240    if (f->nc4_info) {
241        assert(f->nc4_info->root_grp);
242        /* If we can't find it, the grp id part of ncid is bad. */
243        if (!(*grp = nc4_rec_find_grp(f->nc4_info->root_grp, (ncid & GRP_ID_MASK))))
244            return NC_EBADID;
245        *h5 = (*grp)->file->nc4_info;
246        assert(*h5);
247    } else {
248        *h5 = NULL;
249        *grp = NULL;
250    }
251    return NC_NOERR;
252}
253
254int
255nc4_find_nc_grp_h5(int ncid, NC_FILE_INFO_T **nc, NC_GRP_INFO_T **grp, 
256                   NC_HDF5_FILE_INFO_T **h5)
257{
258    NC_FILE_INFO_T *f = nc4_find_nc_file(ncid);
259    if(f == NULL) return NC_EBADID;
260    *nc = f;
261    if (f->nc4_info) {
262        assert(f->nc4_info->root_grp);
263        /* If we can't find it, the grp id part of ncid is bad. */
264        if (!(*grp = nc4_rec_find_grp(f->nc4_info->root_grp, (ncid & GRP_ID_MASK))))
265               return NC_EBADID;
266
267        *h5 = (*grp)->file->nc4_info;
268        assert(*h5);
269    } else {
270        *h5 = NULL;
271        *grp = NULL;
272    }
273    return NC_NOERR;
274}
275
276/* Recursively hunt for a group id. */
277NC_GRP_INFO_T *
278nc4_rec_find_grp(NC_GRP_INFO_T *start_grp, int target_nc_grpid)
279{
280   NC_GRP_INFO_T *g, *res;
281
282   assert(start_grp);
283   
284   /* Is this the group we are searching for? */
285   if (start_grp->nc_grpid == target_nc_grpid)
286      return start_grp;
287
288   /* Shake down the kids. */
289   if (start_grp->children)
290      for (g = start_grp->children; g; g = g->next)
291         if ((res = nc4_rec_find_grp(g, target_nc_grpid)))
292            return res;
293
294   /* Can't find if. Fate, why do you mock me? */
295   return NULL;
296}
297
298/* Given an ncid and varid, get pointers to the group and var
299 * metadata. */
300int
301nc4_find_g_var_nc(NC_FILE_INFO_T *nc, int ncid, int varid, 
302                  NC_GRP_INFO_T **grp, NC_VAR_INFO_T **var)
303{
304   /* Find the group info. */
305   assert(grp && var && nc && nc->nc4_info && nc->nc4_info->root_grp);
306   *grp = nc4_rec_find_grp(nc->nc4_info->root_grp, (ncid & GRP_ID_MASK));
307
308   /* Find the var info. */
309   for ((*var) = (*grp)->var; (*var); (*var) = (*var)->next)
310     if ((*var)->varid == varid)
311       break;
312   if (!(*var))
313     return NC_ENOTVAR;
314
315   return NC_NOERR;
316}
317
318/* Find a dim in a grp (or parents). */
319int
320nc4_find_dim(NC_GRP_INFO_T *grp, int dimid, NC_DIM_INFO_T **dim,
321             NC_GRP_INFO_T **dim_grp)
322{
323   NC_GRP_INFO_T *g, *dg = NULL;
324   int finished = 0; 
325   
326   assert(grp && dim);
327
328   /* Find the dim info. */
329   for (g = grp; g && !finished; g = g->parent)
330      for ((*dim) = g->dim; (*dim); (*dim) = (*dim)->next)
331         if ((*dim)->dimid == dimid)
332         {
333            dg = g;
334            finished++;
335            break;
336         }
337
338   /* If we didn't find it, return an error. */
339   if (!(*dim))
340     return NC_EBADDIM;
341   
342   /* Give the caller the group the dimension is in. */
343   if (dim_grp)
344      *dim_grp = dg;
345
346   return NC_NOERR;
347}
348
349/* Recursively hunt for a HDF type id. */
350NC_TYPE_INFO_T *
351nc4_rec_find_hdf_type(NC_GRP_INFO_T *start_grp, hid_t target_hdf_typeid)
352{
353   NC_GRP_INFO_T *g;
354   NC_TYPE_INFO_T *type, *res;
355   htri_t equal;
356
357   assert(start_grp);
358   
359   /* Does this group have the type we are searching for? */
360   for (type = start_grp->type; type; type = type->next)
361   {
362      if ((equal = H5Tequal(type->native_typeid ? type->native_typeid : type->hdf_typeid, target_hdf_typeid)) < 0)
363         return NULL;
364      if (equal)
365         return type;
366   }
367
368   /* Shake down the kids. */
369   if (start_grp->children)
370      for (g = start_grp->children; g; g = g->next)
371         if ((res = nc4_rec_find_hdf_type(g, target_hdf_typeid)))
372            return res;
373
374   /* Can't find if. Fate, why do you mock me? */
375   return NULL;
376}
377
378/* Recursively hunt for a netCDF type id. */
379NC_TYPE_INFO_T *
380nc4_rec_find_nc_type(NC_GRP_INFO_T *start_grp, nc_type target_nc_typeid)
381{
382   NC_GRP_INFO_T *g;
383   NC_TYPE_INFO_T *type, *res;
384
385   assert(start_grp);
386   
387   /* Does this group have the type we are searching for? */
388   for (type = start_grp->type; type; type = type->next)
389      if (type->nc_typeid == target_nc_typeid)
390         return type;
391
392   /* Shake down the kids. */
393   if (start_grp->children)
394      for (g = start_grp->children; g; g = g->next)
395         if ((res = nc4_rec_find_nc_type(g, target_nc_typeid)))
396            return res;
397
398   /* Can't find if. Fate, why do you mock me? */
399   return NULL;
400}
401
402/* Recursively hunt for a netCDF type by name. */
403NC_TYPE_INFO_T *
404nc4_rec_find_named_type(NC_GRP_INFO_T *start_grp, char *name)
405{
406   NC_GRP_INFO_T *g;
407   NC_TYPE_INFO_T *type, *res;
408
409   assert(start_grp);
410   
411   /* Does this group have the type we are searching for? */
412   for (type = start_grp->type; type; type = type->next)
413      if (!strcmp(type->name, name))
414         return type;
415
416   /* Search subgroups. */
417   if (start_grp->children)
418      for (g = start_grp->children; g; g = g->next)
419         if ((res = nc4_rec_find_named_type(g, name)))
420            return res;
421
422   /* Can't find if. Oh, woe is me! */
423   return NULL;
424}
425
426/* Use a netCDF typeid to find a type in a type_list. */
427int
428nc4_find_type(NC_HDF5_FILE_INFO_T *h5, nc_type typeid, NC_TYPE_INFO_T **type)
429{
430   if (typeid < 0 || !type)
431      return NC_EINVAL;
432   *type = NULL;
433
434   /* Atomic types don't have associated NC_TYPE_INFO_T struct, just
435    * return NOERR. */
436   if (typeid <= NC_STRING)
437      return NC_NOERR;
438
439   /* Find the type. */
440   if(!(*type = nc4_rec_find_nc_type(h5->root_grp, typeid)))
441      return NC_EBADTYPID;
442
443   return NC_NOERR;
444}
445
446/* Find the actual length of a dim by checking the length of that dim
447 * in all variables that use it, in grp or children. *len must be
448 * initialized to zero before this function is called. */
449int
450nc4_find_dim_len(NC_GRP_INFO_T *grp, int dimid, size_t **len)
451{
452   NC_GRP_INFO_T *g;
453   NC_VAR_INFO_T *var;
454   int d, ndims, dimids[NC_MAX_DIMS];
455   size_t dimlen[NC_MAX_DIMS];
456   int retval; 
457   
458   assert(grp && len);
459   LOG((3, "nc4_find_dim_len: grp->name %s dimid %d", grp->name, dimid));
460
461   /* If there are any groups, call this function recursively on
462    * them. */
463   for (g = grp->children; g; g = g->next)
464      if ((retval = nc4_find_dim_len(g, dimid, len)))
465         return retval;
466
467   /* For all variables in this group, find the ones that use this
468    * dimension, and remember the max length. */
469   for (var = grp->var; var; var = var->next)
470   {
471      /* Find dimensions of this var. */
472      if ((retval = find_var_shape_grp(grp, var->varid, &ndims, 
473                                       dimids, dimlen)))
474         return retval;
475
476      /* Check for any dimension that matches dimid. If found, check
477       * if its length is longer than *lenp. */
478      for (d = 0; d < ndims; d++)
479      {
480         if (dimids[d] == dimid)
481         {
482            /* Remember the max length in *lenp. */
483            **len = dimlen[d] > **len ? dimlen[d] : **len;
484            break;
485         }
486      }
487   }
488
489   return NC_NOERR;
490}
491
492/* Given a group, find an att. */
493int
494nc4_find_grp_att(NC_GRP_INFO_T *grp, int varid, const char *name, int attnum,
495                 NC_ATT_INFO_T **att)
496{
497   NC_VAR_INFO_T *var;
498   NC_ATT_INFO_T *attlist = NULL;
499
500   assert(grp && grp->name);
501   LOG((4, "nc4_find_grp_att: grp->name %s varid %d name %s attnum %d", 
502        grp->name, varid, name, attnum));
503
504   /* Get either the global or a variable attribute list. */
505   if (varid == NC_GLOBAL)
506      attlist = grp->att;
507   else
508   {
509      for(var = grp->var; var; var = var->next)
510      {
511         if (var->varid == varid)
512         {
513            attlist = var->att;
514            break;
515         }
516      }
517      if (!var)
518         return NC_ENOTVAR;
519   }
520
521   /* Now find the attribute by name or number. If a name is provided,
522    * ignore the attnum. */
523   for (*att = attlist; *att; *att = (*att)->next)
524      if ((name && !strcmp((*att)->name, name)) ||
525          (!name && (*att)->attnum == attnum))
526         return NC_NOERR;
527
528   /* If we get here, we couldn't find the attribute. */
529   return NC_ENOTATT;
530}
531
532/* Given an ncid, varid, and name or attnum, find and return pointer
533   to NC_ATT_INFO_T metadata. */
534int
535nc4_find_nc_att(int ncid, int varid, const char *name, int attnum,
536            NC_ATT_INFO_T **att)
537{
538   NC_GRP_INFO_T *grp;
539   NC_HDF5_FILE_INFO_T *h5;
540   NC_VAR_INFO_T *var;
541   NC_ATT_INFO_T *attlist = NULL;
542   int retval;
543
544   LOG((4, "nc4_find_nc_att: ncid 0x%x varid %d name %s attnum %d", 
545        ncid, varid, name, attnum));
546
547   /* Find info for this file and group, and set pointer to each. */
548   if ((retval = nc4_find_grp_h5(ncid, &grp, &h5)))
549      return retval;
550   assert(grp && h5);
551
552   /* Get either the global or a variable attribute list. */
553   if (varid == NC_GLOBAL)
554      attlist = grp->att;
555   else
556   {
557      for(var = grp->var; var; var = var->next)
558      {
559         if (var->varid == varid)
560         {
561            attlist = var->att;
562            break;
563         }
564      }
565      if (!var)
566         return NC_ENOTVAR;
567   }
568
569   /* Now find the attribute by name or number. If a name is provided, ignore the attnum. */
570   for (*att = attlist; *att; *att = (*att)->next)
571      if ((name && !strcmp((*att)->name, name)) ||
572          (!name && (*att)->attnum == attnum))
573         return NC_NOERR;
574
575   /* If we get here, we couldn't find the attribute. */
576   return NC_ENOTATT;
577}
578
579void
580nc4_file_list_free(void)
581{
582    free_NCList();
583}
584
585
586int
587NC4_new_nc(NC** ncpp)
588{
589    NC_FILE_INFO_T** ncp;
590    /* Allocate memory for this info. */
591    if (!(ncp = calloc(1, sizeof(NC_FILE_INFO_T)))) 
592       return NC_ENOMEM;
593    if(ncpp) *ncpp = (NC*)ncp;
594    return NC_NOERR;
595}
596
597int
598nc4_file_list_add(NC_FILE_INFO_T** ncp, NC_Dispatch* dispatch)
599{
600    NC_FILE_INFO_T *nc;
601    int status = NC_NOERR;
602
603    /* Allocate memory for this info; use the dispatcher to do this */
604    status = dispatch->new_nc((NC**)&nc);
605    if(status) return status;
606
607    /* Add this file to the list. */
608    if ((status = add_to_NCList((NC *)nc)))
609    {
610       if(nc && nc->ext_ncid > 0) 
611       {
612          del_from_NCList((NC *)nc);
613          free(nc);
614       }
615       return status;
616    }
617   
618    /* Return a pointer to the new struct. */
619    if(ncp) 
620       *ncp = nc;
621
622    return NC_NOERR;
623}
624
625/* Remove a NC_FILE_INFO_T from the linked list. This will nc_free the
626   memory too. */
627void
628nc4_file_list_del(NC_FILE_INFO_T *nc)
629{
630   /* Remove file from master list. */
631   del_from_NCList((NC *)nc);
632   free(nc);
633}
634
635
636/* Given an id, walk the list and find the appropriate
637   NC_FILE_INFO_T. */
638NC_FILE_INFO_T*
639nc4_find_nc_file(int ext_ncid)
640{
641   return (NC_FILE_INFO_T*)find_in_NCList(ext_ncid);
642}
643
644
645/* Add to the end of a var list. Return a pointer to the newly
646 * added var. */
647int
648nc4_var_list_add(NC_VAR_INFO_T **list, NC_VAR_INFO_T **var)
649{
650   NC_VAR_INFO_T *v;
651
652   /* Allocate storage for new variable. */
653   if (!(*var = calloc(1, sizeof(NC_VAR_INFO_T))))
654      return NC_ENOMEM;
655
656   /* Go to the end of the list and set the last one to point at our
657    * new var, or, if the list is empty, our new var becomes the
658    * list. */
659   if(*list)
660   {
661      for (v = *list; v; v = v->next)
662         if (!v->next)
663            break;
664      v->next = *var;
665      (*var)->prev = v;
666   }     
667   else
668      *list = *var;
669
670   /* These are the HDF5-1.8.4 defaults. */
671   (*var)->chunk_cache_size = nc4_chunk_cache_size;
672   (*var)->chunk_cache_nelems = nc4_chunk_cache_nelems;
673   (*var)->chunk_cache_preemption = nc4_chunk_cache_preemption;
674
675   return NC_NOERR;
676}
677
678/* Add to the beginning of a dim list. */
679int
680nc4_dim_list_add(NC_DIM_INFO_T **list)
681{
682   NC_DIM_INFO_T *dim;
683   if (!(dim = calloc(1, sizeof(NC_DIM_INFO_T))))
684      return NC_ENOMEM;
685   if(*list)
686      (*list)->prev = dim;
687   dim->next = *list;
688   *list = dim;
689   return NC_NOERR;
690}
691
692/* Add to the beginning of a dim list. */
693int
694nc4_dim_list_add2(NC_DIM_INFO_T **list, NC_DIM_INFO_T **new_dim)
695{
696   NC_DIM_INFO_T *dim;
697   if (!(dim = calloc(1, sizeof(NC_DIM_INFO_T))))
698      return NC_ENOMEM;
699   if(*list)
700      (*list)->prev = dim;
701   dim->next = *list;
702   *list = dim;
703
704   /* Return pointer to new dimension. */
705   if (new_dim)
706      *new_dim = dim;
707   return NC_NOERR;
708}
709
710/* Add to the end of an att list. */
711int
712nc4_att_list_add(NC_ATT_INFO_T **list)
713{
714   NC_ATT_INFO_T *att, *a1;
715   if (!(att = calloc(1, sizeof(NC_ATT_INFO_T))))
716      return NC_ENOMEM;
717   if (*list)
718   {
719      for (a1 = *list; a1; a1 = a1->next)
720         if (!a1->next)
721            break;
722      a1->next = att;
723      att->prev = a1;
724   }
725   else
726   {
727      *list = att;
728   }
729
730   return NC_NOERR;
731}
732
733/* Add to the end of a group list. Can't use 0 as a new_nc_grpid -
734 * it's reserverd for the root group. */
735int
736nc4_grp_list_add(NC_GRP_INFO_T **list, int new_nc_grpid, 
737                 NC_GRP_INFO_T *parent_grp, NC_FILE_INFO_T *nc, 
738                 char *name, NC_GRP_INFO_T **grp)
739{
740   NC_GRP_INFO_T *g;
741
742   LOG((3, "grp_list_add: new_nc_grpid %d name %s ", 
743        new_nc_grpid, name));
744
745   /* Get the memory to store this groups info. */
746   if (!(*grp = calloc(1, sizeof(NC_GRP_INFO_T))))
747      return NC_ENOMEM;
748
749   /* If the list is not NULL, add this group to it. Otherwise, this
750    * group structure becomes the list. */
751   if (*list)
752   {
753      /* Move to end of the list. */
754      for (g = *list; g; g = g->next)
755         if (!g->next)
756            break;
757      g->next = *grp; /* Add grp to end of list. */
758      (*grp)->prev = g;
759   }
760   else
761   {
762      *list = *grp;
763   }
764
765   /* Fill in this group's information. */
766   (*grp)->nc_grpid = new_nc_grpid;
767   (*grp)->parent = parent_grp;
768   if (!((*grp)->name = malloc((strlen(name) + 1) * sizeof(char))))
769      return NC_ENOMEM;
770   strcpy((*grp)->name, name);
771   (*grp)->file = nc;
772
773   return NC_NOERR;
774}
775
776/* Names for groups, variables, and types must not be the same. This
777 * function checks that a proposed name is not already in
778 * use. Normalzation of UTF8 strings should happen before this
779 * function is called. */
780int
781nc4_check_dup_name(NC_GRP_INFO_T *grp, char *name)
782{
783   NC_TYPE_INFO_T *type;
784   NC_GRP_INFO_T *g;
785   NC_VAR_INFO_T *var;
786
787   /* Any types of this name? */
788   for (type = grp->type; type; type = type->next)
789      if (!strcmp(type->name, name))
790         return NC_ENAMEINUSE;
791
792   /* Any child groups of this name? */
793   for (g = grp->children; g; g = g->next)
794      if (!strcmp(g->name, name))
795         return NC_ENAMEINUSE;
796
797   /* Any variables of this name? */
798   for (var = grp->var; var; var = var->next)
799      if (!strcmp(var->name, name))
800         return NC_ENAMEINUSE;
801
802   return NC_NOERR;
803}
804
805/* Add to the end of a type list. */
806int
807nc4_type_list_add(NC_TYPE_INFO_T **list, NC_TYPE_INFO_T **new_type)
808{
809   NC_TYPE_INFO_T *type, *t;
810
811   if (!(type = calloc(1, sizeof(NC_TYPE_INFO_T))))
812      return NC_ENOMEM;
813
814   if (*list)
815   {
816      for (t = *list; t; t = t->next)
817         if (!t->next)
818            break;
819      t->next = type;
820      type->prev = t;
821   }
822   else
823   {
824      *list = type;
825   }
826
827   if (new_type)
828      *new_type = type;
829
830   return NC_NOERR;
831}
832
833/* Add to the end of a compound field list. */
834int
835nc4_field_list_add(NC_FIELD_INFO_T **list, int fieldid, const char *name,
836                   size_t offset, hid_t field_hdf_typeid, hid_t native_typeid, 
837                   nc_type xtype, int ndims, const int *dim_sizesp)
838{
839   NC_FIELD_INFO_T *field, *f;
840   int i;
841
842   /* Name has already been checked and UTF8 normalized. */
843   if (!name)
844      return NC_EINVAL;
845
846   /* Allocate storage for this field information. */
847   if (!(field = calloc(1, sizeof(NC_FIELD_INFO_T))))
848      return NC_ENOMEM;
849
850   /* Add this field to list. */
851   if (*list)
852   {
853      for (f = *list; f; f = f->next)
854         if (!f->next)
855            break;
856      f->next = field;
857      field->prev = f;
858   }
859   else
860   {
861      *list = field;
862   }
863
864   /* Store the information about this field. */
865   field->fieldid = fieldid;
866   if (!(field->name = malloc((strlen(name) + 1) * sizeof(char))))
867      return NC_ENOMEM;
868   strcpy(field->name, name);
869   field->hdf_typeid = field_hdf_typeid;
870   field->native_typeid = native_typeid;
871   field->nctype = xtype;
872   field->offset = offset;
873   field->ndims = ndims;
874   if (ndims)
875   {
876      if (!(field->dim_size = malloc(ndims * sizeof(int))))
877         return NC_ENOMEM;
878      for (i = 0; i < ndims; i++)
879         field->dim_size[i] = dim_sizesp[i];
880   }
881
882   return NC_NOERR;
883}
884
885/* Add a member to an enum type. */
886int
887nc4_enum_member_add(NC_ENUM_MEMBER_INFO_T **list, size_t size, 
888                    const char *name, const void *value)
889{
890   NC_ENUM_MEMBER_INFO_T *member, *m;
891
892   /* Name has already been checked. */
893   assert(name && size > 0 && value);
894   LOG((4, "nc4_enum_member_add: size %d name %s", size, name));
895
896   /* Allocate storage for this field information. */
897   if (!(member = calloc(1, sizeof(NC_ENUM_MEMBER_INFO_T))) ||
898       !(member->value = calloc(1, size)))
899      return NC_ENOMEM;
900
901   /* Add this field to list. */
902   if (*list)
903   {
904      for (m = *list; m; m = m->next)
905         if (!m->next)
906            break;
907      m->next = member;
908      member->prev = m;
909   }
910   else
911   {
912      *list = member;
913   }
914
915   /* Store the information about this member. */
916   if (!(member->name = malloc((strlen(name) + 1) * sizeof(char))))
917      return NC_ENOMEM;
918   strcpy(member->name, name);
919   memcpy(member->value, value, size);
920
921   return NC_NOERR;
922}
923
924/* Delete a var from a var list, and free the memory. */
925static int
926var_list_del(NC_VAR_INFO_T **list, NC_VAR_INFO_T *var)
927{
928   NC_ATT_INFO_T *a, *att;
929   int ret;
930
931   /* First delete all the attributes attached to this var. */
932   att = (*list)->att;
933   while (att)
934   {
935      a = att->next;
936      if ((ret = nc4_att_list_del(&var->att, att)))
937         return ret;
938      att = a;
939   }
940
941   /* Free some things that may be allocated. */
942   if (var->chunksizes)
943      free(var->chunksizes);
944   if (var->hdf5_name)
945      free(var->hdf5_name);
946   if (var->name)
947      free(var->name);
948   if (var->dimids)
949      free(var->dimids);
950   if (var->dim)
951      free(var->dim);
952
953   /* Remove the var from the linked list. */
954   if(*list == var)
955      *list = var->next;
956   else
957      var->prev->next = var->next;
958
959   if(var->next)
960      var->next->prev = var->prev;
961
962   /* Delete any fill value allocation. This must be done before the
963    * type_info is freed. */
964   if (var->fill_value)
965   {
966      if (var->hdf_datasetid)
967      {
968         if (var->type_info->class == NC_VLEN)
969            nc_free_vlen((nc_vlen_t *)var->fill_value);
970         else if (var->type_info->nc_typeid == NC_STRING)
971            free(*(char **)var->fill_value);
972      }
973      free(var->fill_value);
974   }
975
976   /* For atomic types we have allocated space for type information. */
977/*   if (var->hdf_datasetid && var->xtype <= NC_STRING)*/
978   if (var->xtype <= NC_STRING)
979   {
980      if (var->type_info->native_typeid)
981         if ((H5Tclose(var->type_info->native_typeid)) < 0)
982            return NC_EHDFERR;
983
984      /* Only need to close the hdf_typeid when it was obtained with
985       * H5Dget_type (which happens when reading a file, but not when
986       * creating a variable). */
987      if (var->type_info->close_hdf_typeid || var->xtype == NC_STRING)
988         if ((H5Tclose(var->type_info->hdf_typeid)) < 0)
989            return NC_EHDFERR;
990
991      /* Free the name. */
992      if (var->type_info->name)
993         free(var->type_info->name);
994
995      free(var->type_info);
996   }
997   
998   /* Delete any HDF5 dimscale objid information. */
999   if (var->dimscale_hdf5_objids)
1000      free(var->dimscale_hdf5_objids);
1001
1002   /* Delete information about the attachment status of dimscales. */
1003   if (var->dimscale_attached)
1004      free(var->dimscale_attached);
1005
1006   /* Delete the var. */
1007   free(var);
1008
1009   return NC_NOERR;
1010}
1011
1012/* Delete a field from a field list, and nc_free the memory. */
1013static void
1014field_list_del(NC_FIELD_INFO_T **list, NC_FIELD_INFO_T *field)
1015{
1016
1017   /* Take this field out of the list. */
1018   if(*list == field)
1019      *list = field->next;
1020   else
1021      field->prev->next = field->next;
1022
1023   if(field->next)
1024      field->next->prev = field->prev;
1025
1026   /* Free some stuff. */
1027   if (field->name)
1028      free(field->name);
1029   if (field->dim_size)
1030      free(field->dim_size);
1031
1032   /* Nc_Free the memory. */
1033   free(field);
1034}
1035
1036/* Delete a type from a type list, and nc_free the memory. */
1037int
1038type_list_del(NC_TYPE_INFO_T **list, NC_TYPE_INFO_T *type)
1039{
1040   NC_FIELD_INFO_T *field, *f;
1041   NC_ENUM_MEMBER_INFO_T *enum_member, *em;
1042
1043   /* Close any open user-defined HDF5 typieds. */
1044   if (type->hdf_typeid)
1045   {
1046      if (H5Tclose(type->hdf_typeid) < 0)
1047         return NC_EHDFERR;
1048   }   
1049   if (type->native_typeid)
1050   {
1051      if (H5Tclose(type->native_typeid) < 0)
1052         return NC_EHDFERR;
1053   }
1054
1055   /* Free the name. */
1056   if (type->name)
1057      free(type->name);
1058
1059   /* Delete all the fields in this type (there will be some if its a
1060    * compound). */
1061   field = type->field;
1062   while (field)
1063   {
1064      f = field->next;
1065      field_list_del(&type->field, field);
1066      field = f;
1067   }
1068
1069   /* Delete all the enum_members, if any. */
1070   enum_member = type->enum_member;
1071   while (enum_member)
1072   {
1073      em = enum_member->next;
1074      free(enum_member->value);
1075      free(enum_member->name);
1076      free(enum_member);
1077      enum_member = em;
1078   }
1079
1080   /* Take this type out of the list. */
1081   if(*list == type)
1082      *list = type->next;
1083   else
1084      type->prev->next = type->next;
1085
1086   if(type->next)
1087      type->next->prev = type->prev;
1088
1089   /* Nc_Free the memory. */
1090   free(type);
1091   
1092   return NC_NOERR;
1093}
1094
1095/* Delete a del from a var list, and nc_free the memory. */
1096int
1097nc4_dim_list_del(NC_DIM_INFO_T **list, NC_DIM_INFO_T *dim)
1098{
1099   /* Take this dimension out of the list. */
1100   if(*list == dim)
1101      *list = dim->next;
1102   else
1103      dim->prev->next = dim->next;
1104
1105   if(dim->next)
1106      dim->next->prev = dim->prev;
1107
1108   /* Free memory allocated for names. */
1109   if (dim->name)
1110      free(dim->name);
1111   if (dim->old_name)
1112      free(dim->old_name);
1113
1114   free(dim);
1115   return NC_NOERR;
1116}
1117
1118/* Remove a NC_GRP_INFO_T from the linked list. This will nc_free the
1119   memory too. */
1120static void
1121grp_list_del(NC_GRP_INFO_T **list, NC_GRP_INFO_T *grp)
1122{
1123   if(*list == grp)
1124      *list = grp->next;
1125   else
1126      grp->prev->next = grp->next;
1127
1128   if(grp->next)
1129      grp->next->prev = grp->prev;
1130
1131   free(grp);
1132}
1133
1134/* Recursively delete the data for a group (and everything it
1135 * contains) in our internal metadata store. */
1136int 
1137nc4_rec_grp_del(NC_GRP_INFO_T **list, NC_GRP_INFO_T *grp) 
1138{
1139   NC_GRP_INFO_T *g, *c;
1140   NC_VAR_INFO_T *v, *var;
1141   NC_ATT_INFO_T *a, *att;
1142   NC_DIM_INFO_T *d, *dim;
1143   NC_TYPE_INFO_T *type, *t;
1144   int retval;
1145
1146   assert(grp);
1147   LOG((3, "nc4_rec_grp_del: grp->name %s", grp->name));
1148
1149   /* Recursively call this function for each child, if any, stopping
1150    * if there is an error. */
1151   g = grp->children;
1152   while(g)
1153   {
1154      c = g->next;
1155      if ((retval = nc4_rec_grp_del(&(grp->children), g)))
1156         return retval;
1157      g = c;
1158   }
1159
1160   /* Delete all the list contents for vars, dims, and atts, in each
1161    * group. */
1162   att = grp->att;
1163   while (att)
1164   {
1165      LOG((4, "nc4_rec_grp_del: deleting att %s", att->name));     
1166      a = att->next;
1167      if ((retval = nc4_att_list_del(&grp->att, att)))
1168         return retval;
1169      att = a;
1170   }
1171
1172   /* Delete all vars. */
1173   var = grp->var;
1174   while (var)
1175   {
1176      LOG((4, "nc4_rec_grp_del: deleting var %s", var->name));     
1177      /* Close HDF5 dataset associated with this var, unless it's a
1178       * scale. */
1179      if (var->hdf_datasetid && !var->dimscale && 
1180          H5Dclose(var->hdf_datasetid) < 0)
1181         return NC_EHDFERR;
1182      v = var->next;
1183      if ((retval = var_list_del(&grp->var, var)))
1184         return retval;
1185      var = v;
1186   }
1187   
1188   /* Delete all dims. */
1189   dim = grp->dim;
1190   while (dim)
1191   {
1192      LOG((4, "nc4_rec_grp_del: deleting dim %s", dim->name));     
1193      /* Close HDF5 dataset associated with this dim. */
1194      if (dim->hdf_dimscaleid && H5Dclose(dim->hdf_dimscaleid) < 0)
1195         return NC_EHDFERR;
1196      d = dim->next;
1197      if ((retval = nc4_dim_list_del(&grp->dim, dim)))
1198         return retval;
1199      dim = d;
1200   }
1201
1202   /* Delete all types. */
1203   type = grp->type; 
1204   while (type)
1205   {
1206      LOG((4, "nc4_rec_grp_del: deleting type %s", type->name));     
1207      t = type->next;
1208      if ((retval = type_list_del(&grp->type, type)))
1209         return retval;
1210      type = t;
1211   }
1212
1213   /* Tell HDF5 we're closing this group. */ 
1214   LOG((4, "nc4_rec_grp_del: closing group %s", grp->name));         
1215   if (grp->hdf_grpid && H5Gclose(grp->hdf_grpid) < 0) 
1216      return NC_EHDFERR;
1217
1218   /* Free the name. */
1219   free(grp->name);
1220
1221   /* Finally, redirect pointers around this entry in the list, and
1222    * nc_free its memory. */
1223   grp_list_del(list, grp);
1224
1225   return NC_NOERR;
1226}
1227
1228/* Remove a NC_ATT_INFO_T from the linked list. This will nc_free the
1229   memory too.
1230*/
1231int
1232nc4_att_list_del(NC_ATT_INFO_T **list, NC_ATT_INFO_T *att)
1233{
1234   int i;
1235
1236   /* Take this att out of the list. */
1237   if(*list == att)
1238      *list = att->next;
1239   else
1240      att->prev->next = att->next;
1241
1242   if(att->next)
1243      att->next->prev = att->prev;
1244
1245   /* Free memory that was malloced to hold data for this
1246    * attribute. */
1247   if (att->data)
1248      free(att->data);
1249
1250   /* Free the name. */
1251   if (att->name)
1252      free(att->name);
1253
1254   /* Close the HDF5 typeid. */
1255   if (att->native_typeid && H5Tclose(att->native_typeid) < 0)
1256      return NC_EHDFERR;
1257
1258   /* If this is a string array attribute, delete all members of the
1259    * string array, then delete the array of pointers to strings. (The
1260    * array was filled with pointers by HDF5 when the att was read,
1261    * and memory for each string was allocated by HDF5. That's why I
1262    * use free and not nc_free, because the netCDF library didn't
1263    * allocate the memory that is being freed.) */
1264   if (att->stdata)
1265   {
1266      for (i = 0; i < att->len; i++)
1267         free(att->stdata[i]);
1268      free(att->stdata);
1269   }
1270
1271   /* If this att has vlen data, release it. */
1272   if (att->vldata)
1273   {
1274      for (i = 0; i < att->len; i++)
1275         nc_free_vlen(&att->vldata[i]);
1276      free(att->vldata);
1277   }
1278
1279   free(att);
1280   return NC_NOERR;
1281}
1282
1283/* Normalize a UTF8 name. Put the result in norm_name, which can be
1284 * NC_MAX_NAME + 1 in size. This function makes sure the free() gets
1285 * called on the return from utf8proc_NFC, and also ensures that the
1286 * name is not too long. */
1287int
1288nc4_normalize_name(const char *name, char *norm_name)
1289{
1290   char *temp_name;
1291   if (!(temp_name = (char *)utf8proc_NFC((const unsigned char *)name)))
1292      return NC_EINVAL;
1293   if (strlen(temp_name) > NC_MAX_NAME)
1294   {
1295      free(temp_name);
1296      return NC_EMAXNAME;
1297   }
1298   strcpy(norm_name, temp_name);
1299   free(temp_name);
1300   return NC_NOERR;
1301}
1302
1303/* Print out a bunch of info to stderr about the metadata for
1304   debugging purposes. */
1305#ifdef LOGGING
1306/* Use this to set the global log level. Set it to NC_TURN_OFF_LOGGING
1307   (-1) to turn off all logging. Set it to 0 to show only errors, and
1308   to higher numbers to show more and more logging details. */
1309int 
1310nc_set_log_level(int new_level)
1311{
1312   /* If the user wants to completely turn off logging, turn off HDF5
1313      logging too. Now I truely can't think of what to do if this
1314      fails, so just ignore the return code. */
1315   if (new_level == NC_TURN_OFF_LOGGING)
1316   {
1317      H5Eset_auto(NULL, NULL);
1318      LOG((1, "HDF5 error messages turned off!"));
1319   }
1320
1321   /* Do we need to turn HDF5 logging back on? */
1322   if (new_level > NC_TURN_OFF_LOGGING && 
1323       nc_log_level <= NC_TURN_OFF_LOGGING)
1324   {
1325      if (H5Eset_auto((H5E_auto_t)&H5Eprint, stderr) < 0)
1326         LOG((0, "H5Eset_auto failed!"));
1327      LOG((1, "HDF5 error messages turned on."));
1328   }
1329
1330   /* Now remember the new level. */
1331   nc_log_level = new_level;
1332   LOG((4, "log_level changed to %d", nc_log_level));
1333   return 0;
1334} 
1335
1336/* Recursively print the metadata of a group. */
1337#define MAX_NESTS 10
1338static int
1339rec_print_metadata(NC_GRP_INFO_T *grp, int *tab_count)
1340{
1341   NC_GRP_INFO_T *g;
1342   NC_ATT_INFO_T *att;
1343   NC_VAR_INFO_T *var;
1344   NC_DIM_INFO_T *dim;
1345   NC_TYPE_INFO_T *type;
1346   NC_FIELD_INFO_T *field;
1347   char tabs[MAX_NESTS] = "";
1348   char dims_string[NC_MAX_DIMS*4];
1349   char temp_string[10];
1350   int t, retval, d;
1351
1352   /* Come up with a number of tabs relative to the group. */
1353   for (t = 0; t < *tab_count && t < MAX_NESTS; t++)
1354      strcat(tabs, "\t");
1355
1356   LOG((2, "%s GROUP - %s nc_grpid: %d nvars: %d natts: %d",
1357        tabs, grp->name, grp->nc_grpid, grp->nvars, grp->natts));
1358   
1359   for(att = grp->att; att; att = att->next)
1360      LOG((2, "%s GROUP ATTRIBUTE - attnum: %d name: %s type: %d len: %d",
1361           tabs, att->attnum, att->name, att->xtype, att->len));
1362
1363   /* To display dims starting with 0 and going up, go through list is
1364    * reverse order. */
1365   for(dim = grp->dim; dim && dim->next; dim = dim->next)
1366      ;
1367   for( ; dim; dim = dim->prev)
1368      LOG((2, "%s DIMENSION - dimid: %d name: %s len: %d unlimited: %d",
1369           tabs, dim->dimid, dim->name, dim->len, dim->unlimited));
1370   
1371   /* To display vars starting with 0 and going up, go through list is
1372    * reverse order. */
1373   for(var = grp->var; var && var->next; var = var->next)
1374      ;
1375   for( ; var; var = var->prev)
1376   {
1377      strcpy(dims_string, "");
1378      for (d = 0; d < var->ndims; d++)
1379      {
1380         sprintf(temp_string, " %d", var->dimids[d]);
1381         strcat(dims_string, temp_string);
1382      }
1383      LOG((2, "%s VARIABLE - varid: %d name: %s type: %d ndims: %d dimscale: %d dimids:%s",
1384           tabs, var->varid, var->name, var->xtype, var->ndims, var->dimscale, 
1385           dims_string));
1386      for(att = var->att; att; att = att->next)
1387         LOG((2, "%s VAR ATTRIBUTE - attnum: %d name: %s type: %d len: %d",
1388              tabs, att->attnum, att->name, att->xtype, att->len));
1389   }
1390   
1391   for (type = grp->type; type; type = type->next)
1392   {
1393      LOG((2, "%s TYPE - nc_typeid: %d hdf_typeid: 0x%x size: %d committed: %d "
1394           "name: %s num_fields: %d base_nc_type: %d", tabs, type->nc_typeid, 
1395           type->hdf_typeid, type->size, type->committed, type->name, 
1396           type->num_fields, type->base_nc_type));
1397      /* Is this a compound type? */
1398      if (type->class == NC_COMPOUND)
1399      {
1400         LOG((3, "compound type"));
1401         for (field = type->field; field; field = field->next)
1402            LOG((4, "field %s offset %d nctype %d ndims %d", field->name, 
1403                 field->offset, field->nctype, field->ndims));
1404      }
1405      else if (type->class == NC_VLEN)
1406         LOG((3, "VLEN type"));
1407      else if (type->class == NC_OPAQUE)
1408         LOG((3, "Opaque type"));
1409      else if (type->class == NC_ENUM)
1410         LOG((3, "Enum type"));
1411      else
1412      {
1413         LOG((0, "Unknown class: %d", type->class));
1414         return NC_EBADTYPE;
1415      }
1416   }
1417   
1418   /* Call self for each child of this group. */
1419   if (grp->children)
1420   {
1421      (*tab_count)++;
1422      for (g = grp->children; g; g = g->next)
1423         if ((retval = rec_print_metadata(g, tab_count)))
1424            return retval;
1425      (*tab_count)--;
1426   }
1427   
1428   return NC_NOERR;
1429}
1430
1431/* Print out the internal metadata for a file. This is useful to check
1432 * that netCDF is working! Nonetheless, this function will print
1433 * nothing if logging is not set to at least two. */
1434int
1435log_metadata_nc(NC_FILE_INFO_T *nc)
1436{
1437   NC_HDF5_FILE_INFO_T *h5 = nc->nc4_info;
1438   int tab_count = 0;
1439
1440   LOG((2, "*** NetCDF-4 Internal Metadata: int_ncid 0x%x ext_ncid 0x%x", 
1441        nc->int_ncid, nc->ext_ncid));
1442   if (!h5)
1443   {
1444      LOG((2, "This is a netCDF-3 file."));
1445      return NC_NOERR;
1446   }
1447   LOG((2, "FILE - hdfid: 0x%x path: %s cmode: 0x%x parallel: %d redef: %d "
1448        "fill_mode: %d no_write: %d next_nc_grpid: %d", h5->hdfid, h5->path, 
1449        h5->cmode, h5->parallel, h5->redef, h5->fill_mode, h5->no_write, 
1450        h5->next_nc_grpid));
1451   return rec_print_metadata(h5->root_grp, &tab_count);
1452}
1453
1454#endif /*LOGGING */
1455
1456/* Show the in-memory metadata for a netcdf file. */
1457int
1458NC4_show_metadata(int ncid)
1459{
1460   int retval = NC_NOERR;
1461#ifdef LOGGING
1462   NC_FILE_INFO_T *nc;
1463   int old_log_level = nc_log_level;
1464   
1465   /* Find file metadata. */
1466   if (!(nc = nc4_find_nc_file(ncid)))
1467      return NC_EBADID;
1468
1469   /* Log level must be 2 to see metadata. */
1470   nc_log_level = 2;
1471   retval = log_metadata_nc(nc);
1472   nc_log_level = old_log_level;
1473#endif /*LOGGING*/
1474   return retval;
1475}
1476
Note: See TracBrowser for help on using the repository browser.