1 /* Copyright 2003-2018, University Corporation for Atmospheric
2  * Research. See COPYRIGHT file for copying and redistribution
3  * conditions. */
4 /**
5  * @file
6  * @internal This file handles HDF5 attributes.
7  *
8  * @author Ed Hartnett
9  */
10 
11 #include "config.h"
12 #include "hdf5internal.h"
13 
14 /**
15  * @internal Get the attribute list for either a varid or NC_GLOBAL
16  *
17  * @param grp Group
18  * @param varid Variable ID | NC_BLOGAL
19  * @param varp Pointer that gets pointer to NC_VAR_INFO_T
20  * instance. Ignored if NULL.
21  * @param attlist Pointer that gets pointer to attribute list.
22  *
23  * @return NC_NOERR No error.
24  * @author Dennis Heimbigner, Ed Hartnett
25  */
26 static int
getattlist(NC_GRP_INFO_T * grp,int varid,NC_VAR_INFO_T ** varp,NCindex ** attlist)27 getattlist(NC_GRP_INFO_T *grp, int varid, NC_VAR_INFO_T **varp,
28             NCindex **attlist)
29 {
30    NC_VAR_INFO_T* var;
31    int retval;
32 
33    if (varid == NC_GLOBAL)
34    {
35       /* Do we need to read the atts? */
36       if (grp->atts_not_read)
37          if ((retval = nc4_read_atts(grp, NULL)))
38             return retval;
39 
40       if (varp)
41          *varp = NULL;
42       *attlist = grp->att;
43    }
44    else
45    {
46       if (!(var = (NC_VAR_INFO_T *)ncindexith(grp->vars, varid)))
47          return NC_ENOTVAR;
48       assert(var->hdr.id == varid);
49 
50       /* Do we need to read the atts? */
51       if (var->atts_not_read)
52          if ((retval = nc4_read_atts(grp, var)))
53             return retval;
54 
55       if (varp)
56          *varp = var;
57       *attlist = var->att;
58    }
59    return NC_NOERR;
60 }
61 
62 /**
63  * @internal I think all atts should be named the exact same thing, to
64  * avoid confusion!
65  *
66  * @param ncid File and group ID.
67  * @param varid Variable ID.
68  * @param name Name of attribute.
69  * @param newname New name for attribute.
70  *
71  * @return ::NC_NOERR No error.
72  * @return ::NC_EBADID Bad ncid.
73  * @return ::NC_EMAXNAME New name too long.
74  * @return ::NC_EPERM File is read-only.
75  * @return ::NC_ENAMEINUSE New name already in use.
76  * @return ::NC_ENOTINDEFINE Classic model file not in define mode.
77  * @return ::NC_EHDFERR HDF error.
78  * @return ::NC_ENOMEM Out of memory.
79  * @return ::NC_EINTERNAL Could not rebuild list.
80  * @author Ed Hartnett
81  */
82 int
NC4_rename_att(int ncid,int varid,const char * name,const char * newname)83 NC4_rename_att(int ncid, int varid, const char *name, const char *newname)
84 {
85    NC *nc;
86    NC_GRP_INFO_T *grp;
87    NC_FILE_INFO_T *h5;
88    NC_VAR_INFO_T *var = NULL;
89    NC_ATT_INFO_T *att;
90    NCindex *list;
91    char norm_newname[NC_MAX_NAME + 1], norm_name[NC_MAX_NAME + 1];
92    hid_t datasetid = 0;
93    int retval = NC_NOERR;
94 
95    if (!name || !newname)
96       return NC_EINVAL;
97 
98    LOG((2, "nc_rename_att: ncid 0x%x varid %d name %s newname %s",
99         ncid, varid, name, newname));
100 
101    /* If the new name is too long, that's an error. */
102    if (strlen(newname) > NC_MAX_NAME)
103       return NC_EMAXNAME;
104 
105    /* Find info for this file, group, and h5 info. */
106    if ((retval = nc4_find_nc_grp_h5(ncid, &nc, &grp, &h5)))
107       return retval;
108    assert(h5 && grp && h5);
109 
110    /* If the file is read-only, return an error. */
111    if (h5->no_write)
112       return NC_EPERM;
113 
114    /* Check and normalize the name. */
115    if ((retval = nc4_check_name(newname, norm_newname)))
116       return retval;
117 
118    /* Get the list of attributes. */
119    if ((retval = getattlist(grp, varid, &var, &list)))
120       return retval;
121 
122    /* Is new name in use? */
123    att = (NC_ATT_INFO_T*)ncindexlookup(list,norm_newname);
124    if(att != NULL)
125       return NC_ENAMEINUSE;
126 
127    /* Normalize name and find the attribute. */
128    if ((retval = nc4_normalize_name(name, norm_name)))
129       return retval;
130 
131    att = (NC_ATT_INFO_T*)ncindexlookup(list,norm_name);
132    if (!att)
133       return NC_ENOTATT;
134 
135    /* If we're not in define mode, new name must be of equal or
136       less size, if complying with strict NC3 rules. */
137    if (!(h5->flags & NC_INDEF) && strlen(norm_newname) > strlen(att->hdr.name) &&
138        (h5->cmode & NC_CLASSIC_MODEL))
139       return NC_ENOTINDEFINE;
140 
141    /* Delete the original attribute, if it exists in the HDF5 file. */
142    if (att->created)
143    {
144       if (varid == NC_GLOBAL)
145       {
146          if (H5Adelete(((NC_HDF5_GRP_INFO_T *)(grp->format_grp_info))->hdf_grpid,
147                        att->hdr.name) < 0)
148             return NC_EHDFERR;
149       }
150       else
151       {
152          if ((retval = nc4_open_var_grp2(grp, varid, &datasetid)))
153             return retval;
154          if (H5Adelete(datasetid, att->hdr.name) < 0)
155             return NC_EHDFERR;
156       }
157       att->created = NC_FALSE;
158    }
159 
160    /* Copy the new name into our metadata. */
161    if(att->hdr.name) free(att->hdr.name);
162    if (!(att->hdr.name = strdup(norm_newname)))
163       return NC_ENOMEM;
164    att->hdr.hashkey = NC_hashmapkey(att->hdr.name,strlen(att->hdr.name)); /* Fix hash key */
165 
166    att->dirty = NC_TRUE;
167 
168    /* Rehash the attribute list so that the new name is used */
169    if(!ncindexrebuild(list))
170       return NC_EINTERNAL;
171 
172    /* Mark attributes on variable dirty, so they get written */
173    if(var)
174       var->attr_dirty = NC_TRUE;
175 
176    return retval;
177 }
178 
179 /**
180  * @internal Delete an att. Rub it out. Push the button on
181  * it. Liquidate it. Bump it off. Take it for a one-way
182  * ride. Terminate it.
183  *
184  * @param ncid File and group ID.
185  * @param varid Variable ID.
186  * @param name Name of attribute to delete.
187  *
188  * @return ::NC_NOERR No error.
189  * @return ::NC_EBADID Bad ncid.
190  * @return ::NC_ENOTATT Attribute not found.
191  * @return ::NC_EINVAL No name provided.
192  * @return ::NC_EPERM File is read only.
193  * @return ::NC_ENOTINDEFINE Classic model not in define mode.
194  * @return ::NC_EINTERNAL Could not rebuild list.
195  * @author Ed Hartnett, Dennis Heimbigner
196  */
197 int
NC4_del_att(int ncid,int varid,const char * name)198 NC4_del_att(int ncid, int varid, const char *name)
199 {
200    NC_GRP_INFO_T *grp;
201    NC_VAR_INFO_T *var;
202    NC_FILE_INFO_T *h5;
203    NC_ATT_INFO_T *att;
204    NCindex* attlist = NULL;
205    hid_t locid = 0;
206    int i;
207    size_t deletedid;
208    int retval;
209 
210    /* Name must be provided. */
211    if (!name)
212       return NC_EINVAL;
213 
214    LOG((2, "nc_del_att: ncid 0x%x varid %d name %s", ncid, varid, name));
215 
216    /* Find info for this file, group, and h5 info. */
217    if ((retval = nc4_find_grp_h5(ncid, &grp, &h5)))
218       return retval;
219    assert(h5 && grp);
220 
221    /* If the file is read-only, return an error. */
222    if (h5->no_write)
223       return NC_EPERM;
224 
225    /* If file is not in define mode, return error for classic model
226     * files, otherwise switch to define mode. */
227    if (!(h5->flags & NC_INDEF))
228    {
229       if (h5->cmode & NC_CLASSIC_MODEL)
230          return NC_ENOTINDEFINE;
231       if ((retval = NC4_redef(ncid)))
232          return retval;
233    }
234 
235    /* Get either the global or a variable attribute list. */
236    if ((retval = getattlist(grp, varid, &var, &attlist)))
237       return retval;
238 
239    /* Determine the location id in the HDF5 file. */
240    if (varid == NC_GLOBAL)
241       locid = ((NC_HDF5_GRP_INFO_T *)(grp->format_grp_info))->hdf_grpid;
242    else if (var->created)
243       locid = var->hdf_datasetid;
244 
245    /* Now find the attribute by name. */
246    if (!(att = (NC_ATT_INFO_T*)ncindexlookup(attlist, name)))
247       return NC_ENOTATT;
248 
249    /* Delete it from the HDF5 file, if it's been created. */
250    if (att->created)
251    {
252       assert(locid);
253       if (H5Adelete(locid, att->hdr.name) < 0)
254          return NC_EATTMETA;
255    }
256 
257    deletedid = att->hdr.id;
258 
259    /* Remove this attribute in this list */
260    if ((retval = nc4_att_list_del(attlist, att)))
261       return retval;
262 
263    /* Renumber all attributes with higher indices. */
264    for (i = 0; i < ncindexsize(attlist); i++)
265    {
266       NC_ATT_INFO_T *a;
267       if (!(a = (NC_ATT_INFO_T *)ncindexith(attlist, i)))
268          continue;
269       if (a->hdr.id > deletedid)
270          a->hdr.id--;
271    }
272 
273    /* Rebuild the index. */
274    if (!ncindexrebuild(attlist))
275       return NC_EINTERNAL;
276 
277    return NC_NOERR;
278 }
279 
280 /**
281  * @internal This will return the length of a netcdf atomic data type
282  * in bytes.
283  *
284  * @param type A netcdf atomic type.
285  *
286  * @return Type size in bytes, or -1 if type not found.
287  * @author Ed Hartnett
288  */
289 static int
nc4typelen(nc_type type)290 nc4typelen(nc_type type)
291 {
292    switch(type){
293    case NC_BYTE:
294    case NC_CHAR:
295    case NC_UBYTE:
296       return 1;
297    case NC_USHORT:
298    case NC_SHORT:
299       return 2;
300    case NC_FLOAT:
301    case NC_INT:
302    case NC_UINT:
303       return 4;
304    case NC_DOUBLE:
305    case NC_INT64:
306    case NC_UINT64:
307       return 8;
308    }
309    return -1;
310 }
311 
312 /**
313  * @internal
314  * Write an attribute to a netCDF-4/HDF5 file, converting
315  * data type if necessary.
316  *
317  * @param ncid File and group ID.
318  * @param varid Variable ID.
319  * @param name Name of attribute.
320  * @param file_type Type of the attribute data in file.
321  * @param len Number of elements in attribute array.
322  * @param data Attribute data.
323  * @param mem_type Type of data in memory.
324  * @param force write even if the attribute is special
325  *
326  * @return ::NC_NOERR No error.
327  * @return ::NC_EINVAL Invalid parameters.
328  * @return ::NC_EBADID Bad ncid.
329  * @return ::NC_ENOTVAR Variable not found.
330  * @return ::NC_EBADNAME Name contains illegal characters.
331  * @return ::NC_ENAMEINUSE Name already in use.
332  * @author Ed Hartnett, Dennis Heimbigner
333  */
334 int
nc4_put_att(NC_GRP_INFO_T * grp,int varid,const char * name,nc_type file_type,size_t len,const void * data,nc_type mem_type,int force)335 nc4_put_att(NC_GRP_INFO_T* grp, int varid, const char *name, nc_type file_type,
336             size_t len, const void *data, nc_type mem_type, int force)
337 {
338    NC* nc;
339    NC_FILE_INFO_T *h5;
340    NC_VAR_INFO_T *var = NULL;
341    NCindex* attlist = NULL;
342    NC_ATT_INFO_T* att;
343    char norm_name[NC_MAX_NAME + 1];
344    nc_bool_t new_att = NC_FALSE;
345    int retval = NC_NOERR, range_error = 0;
346    size_t type_size;
347    int i;
348    int ret;
349    int ncid;
350 
351    h5 = grp->nc4_info;
352    nc = h5->controller;
353    assert(nc && grp && h5);
354 
355    ncid = nc->ext_ncid | grp->hdr.id;
356 
357    /* Find att, if it exists. (Must check varid first or nc_test will
358     * break.) */
359    if ((ret = getattlist(grp, varid, &var, &attlist)))
360       return ret;
361 
362    /* The length needs to be positive (cast needed for braindead
363       systems with signed size_t). */
364    if((unsigned long) len > X_INT_MAX)
365       return NC_EINVAL;
366 
367    /* Check name before LOG statement. */
368    if (!name || strlen(name) > NC_MAX_NAME)
369       return NC_EBADNAME;
370 
371    LOG((1, "%s: ncid 0x%x varid %d name %s file_type %d mem_type %d len %d",
372         __func__,ncid, varid, name, file_type, mem_type, len));
373 
374    /* If len is not zero, then there must be some data. */
375    if (len && !data)
376       return NC_EINVAL;
377 
378    /* If the file is read-only, return an error. */
379    if (h5->no_write)
380       return NC_EPERM;
381 
382    /* Check and normalize the name. */
383    if ((retval = nc4_check_name(name, norm_name)))
384       return retval;
385 
386    /* Check that a reserved att name is not being used improperly */
387    const NC_reservedatt* ra = NC_findreserved(name);
388    if(ra != NULL && !force) {
389       /* case 1: grp=root, varid==NC_GLOBAL, flags & READONLYFLAG */
390       if (nc->ext_ncid == ncid && varid == NC_GLOBAL && grp->parent == NULL
391           && (ra->flags & READONLYFLAG))
392          return NC_ENAMEINUSE;
393       /* case 2: grp=NA, varid!=NC_GLOBAL, flags & DIMSCALEFLAG */
394       if (varid != NC_GLOBAL && (ra->flags & DIMSCALEFLAG))
395          return NC_ENAMEINUSE;
396    }
397 
398    /* See if there is already an attribute with this name. */
399    att = (NC_ATT_INFO_T*)ncindexlookup(attlist,norm_name);
400 
401    LOG((1, "%s: ncid 0x%x varid %d name %s file_type %d mem_type %d len %d",
402         __func__, ncid, varid, name, file_type, mem_type, len));
403 
404    if (!att)
405    {
406       /* If this is a new att, require define mode. */
407       if (!(h5->flags & NC_INDEF))
408       {
409          if (h5->cmode & NC_CLASSIC_MODEL)
410             return NC_ENOTINDEFINE;
411          if ((retval = NC4_redef(ncid)))
412             BAIL(retval);
413       }
414       new_att = NC_TRUE;
415    }
416    else
417    {
418       /* For an existing att, if we're not in define mode, the len
419          must not be greater than the existing len for classic model. */
420       if (!(h5->flags & NC_INDEF) &&
421           len * nc4typelen(file_type) > (size_t)att->len * nc4typelen(att->nc_typeid))
422       {
423          if (h5->cmode & NC_CLASSIC_MODEL)
424             return NC_ENOTINDEFINE;
425          if ((retval = NC4_redef(ncid)))
426             BAIL(retval);
427       }
428    }
429 
430    /* We must have two valid types to continue. */
431    if (file_type == NC_NAT || mem_type == NC_NAT)
432       return NC_EBADTYPE;
433 
434    /* Get information about this type. */
435    if ((retval = nc4_get_typelen_mem(h5, file_type, &type_size)))
436       return retval;
437 
438    /* No character conversions are allowed. */
439    if (file_type != mem_type &&
440        (file_type == NC_CHAR || mem_type == NC_CHAR ||
441         file_type == NC_STRING || mem_type == NC_STRING))
442       return NC_ECHAR;
443 
444    /* For classic mode file, only allow atts with classic types to be
445     * created. */
446    if (h5->cmode & NC_CLASSIC_MODEL && file_type > NC_DOUBLE)
447       return NC_ESTRICTNC3;
448 
449    /* Add to the end of the attribute list, if this att doesn't
450       already exist. */
451    if (new_att)
452    {
453       LOG((3, "adding attribute %s to the list...", norm_name));
454       if ((ret = nc4_att_list_add(attlist, norm_name, &att)))
455          BAIL(ret);
456 
457       /* Allocate storage for the HDF5 specific att info. */
458       if (!(att->format_att_info = calloc(1, sizeof(NC_HDF5_ATT_INFO_T))))
459          BAIL(NC_ENOMEM);
460    }
461 
462    /* Now fill in the metadata. */
463    att->dirty = NC_TRUE;
464    att->nc_typeid = file_type;
465 
466    /* If this att has vlen or string data, release it before we lose the length value. */
467    if (att->stdata)
468    {
469       for (i = 0; i < att->len; i++)
470          if(att->stdata[i])
471             free(att->stdata[i]);
472       free(att->stdata);
473       att->stdata = NULL;
474    }
475    if (att->vldata)
476    {
477       for (i = 0; i < att->len; i++)
478          nc_free_vlen(&att->vldata[i]); /* FIX: see warning of nc_free_vlen */
479       free(att->vldata);
480       att->vldata = NULL;
481    }
482 
483    att->len = len;
484 
485    /* If this is the _FillValue attribute, then we will also have to
486     * copy the value to the fill_vlue pointer of the NC_VAR_INFO_T
487     * struct for this var. (But ignore a global _FillValue
488     * attribute). */
489    if (!strcmp(att->hdr.name, _FillValue) && varid != NC_GLOBAL)
490    {
491       int size;
492 
493       /* Fill value must be same type and have exactly one value */
494       if (att->nc_typeid != var->type_info->hdr.id)
495          return NC_EBADTYPE;
496       if (att->len != 1)
497          return NC_EINVAL;
498 
499       /* If we already wrote to the dataset, then return an error. */
500       if (var->written_to)
501          return NC_ELATEFILL;
502 
503       /* Get the length of the veriable data type. */
504       if ((retval = nc4_get_typelen_mem(grp->nc4_info, var->type_info->hdr.id,
505                                         &type_size)))
506          return retval;
507 
508       /* Already set a fill value? Now I'll have to free the old
509        * one. Make up your damn mind, would you? */
510       if (var->fill_value)
511       {
512          if (var->type_info->nc_type_class == NC_VLEN)
513          {
514             if ((retval = nc_free_vlen(var->fill_value)))
515                return retval;
516          }
517          else if (var->type_info->nc_type_class == NC_STRING)
518          {
519             if (*(char **)var->fill_value)
520                free(*(char **)var->fill_value);
521          }
522          free(var->fill_value);
523       }
524 
525       /* Determine the size of the fill value in bytes. */
526       if (var->type_info->nc_type_class == NC_VLEN)
527          size = sizeof(hvl_t);
528       else if (var->type_info->nc_type_class == NC_STRING)
529          size = sizeof(char *);
530       else
531          size = type_size;
532 
533       /* Allocate space for the fill value. */
534       if (!(var->fill_value = calloc(1, size)))
535          return NC_ENOMEM;
536 
537       /* Copy the fill_value. */
538       LOG((4, "Copying fill value into metadata for variable %s", var->hdr.name));
539       if (var->type_info->nc_type_class == NC_VLEN)
540       {
541          nc_vlen_t *in_vlen = (nc_vlen_t *)data, *fv_vlen = (nc_vlen_t *)(var->fill_value);
542          NC_TYPE_INFO_T* basetype;
543 	 size_t basetypesize = 0;
544 
545 	 /* get the basetype and its size */
546 	 basetype = var->type_info;
547          if ((retval = nc4_get_typelen_mem(grp->nc4_info, basetype->hdr.id, &basetypesize)))
548              return retval;
549 	 /* shallow clone the content of the vlen; shallow because it has only a temporary existence */
550          fv_vlen->len = in_vlen->len;
551          if (!(fv_vlen->p = malloc(basetypesize * in_vlen->len)))
552             return NC_ENOMEM;
553          memcpy(fv_vlen->p, in_vlen->p, in_vlen->len * basetypesize);
554       }
555       else if (var->type_info->nc_type_class == NC_STRING)
556       {
557          if (*(char **)data)
558          {
559             if (!(*(char **)(var->fill_value) = malloc(strlen(*(char **)data) + 1)))
560                return NC_ENOMEM;
561             strcpy(*(char **)var->fill_value, *(char **)data);
562          }
563          else
564             *(char **)var->fill_value = NULL;
565       }
566       else
567          memcpy(var->fill_value, data, type_size);
568 
569       /* Indicate that the fill value was changed, if the variable has already
570        * been created in the file, so the dataset gets deleted and re-created. */
571       if (var->created)
572          var->fill_val_changed = NC_TRUE;
573    }
574 
575    /* Copy the attribute data, if there is any. VLENs and string
576     * arrays have to be handled specially. */
577    if (att->len)
578    {
579       nc_type type_class;    /* Class of attribute's type */
580 
581       /* Get class for this type. */
582       if ((retval = nc4_get_typeclass(h5, file_type, &type_class)))
583          return retval;
584 
585       assert(data);
586       if (type_class == NC_VLEN)
587       {
588          const hvl_t *vldata1;
589          NC_TYPE_INFO_T *vltype;
590          size_t base_typelen;
591 
592          /* Get the type object for the attribute's type */
593          if ((retval = nc4_find_type(h5, file_type, &vltype)))
594             BAIL(retval);
595 
596          /* Retrieve the size of the base type */
597          if ((retval = nc4_get_typelen_mem(h5, vltype->u.v.base_nc_typeid, &base_typelen)))
598             BAIL(retval);
599 
600          vldata1 = data;
601          if (!(att->vldata = (nc_vlen_t*)malloc(att->len * sizeof(hvl_t))))
602             BAIL(NC_ENOMEM);
603          for (i = 0; i < att->len; i++)
604          {
605             att->vldata[i].len = vldata1[i].len;
606 	    /* Warning, this only works for cases described for nc_free_vlen() */
607             if (!(att->vldata[i].p = malloc(base_typelen * att->vldata[i].len)))
608                BAIL(NC_ENOMEM);
609             memcpy(att->vldata[i].p, vldata1[i].p, base_typelen * att->vldata[i].len);
610          }
611       }
612       else if (type_class == NC_STRING)
613       {
614          LOG((4, "copying array of NC_STRING"));
615          if (!(att->stdata = malloc(sizeof(char *) * att->len))) {
616             BAIL(NC_ENOMEM);
617          }
618 
619          /* If we are overwriting an existing attribute,
620             specifically an NC_CHAR, we need to clean up
621             the pre-existing att->data. */
622          if (!new_att && att->data) {
623             free(att->data);
624             att->data = NULL;
625          }
626 
627          for (i = 0; i < att->len; i++)
628          {
629             if(NULL != ((char **)data)[i]) {
630                LOG((5, "copying string %d of size %d", i, strlen(((char **)data)[i]) + 1));
631                if (!(att->stdata[i] = strdup(((char **)data)[i])))
632                   BAIL(NC_ENOMEM);
633             }
634             else
635                att->stdata[i] = ((char **)data)[i];
636          }
637       }
638       else
639       {
640          /* [Re]allocate memory for the attribute data */
641          if (!new_att)
642             free (att->data);
643          if (!(att->data = malloc(att->len * type_size)))
644             BAIL(NC_ENOMEM);
645 
646          /* Just copy the data, for non-atomic types */
647          if (type_class == NC_OPAQUE || type_class == NC_COMPOUND || type_class == NC_ENUM)
648             memcpy(att->data, data, len * type_size);
649          else
650          {
651             /* Data types are like religions, in that one can convert.  */
652             if ((retval = nc4_convert_type(data, att->data, mem_type, file_type,
653                                            len, &range_error, NULL,
654                                            (h5->cmode & NC_CLASSIC_MODEL))))
655                BAIL(retval);
656          }
657       }
658    }
659    att->dirty = NC_TRUE;
660    att->created = NC_FALSE;
661 
662    /* Mark attributes on variable dirty, so they get written */
663    if(var)
664       var->attr_dirty = NC_TRUE;
665 
666 exit:
667    /* If there was an error return it, otherwise return any potential
668       range error value. If none, return NC_NOERR as usual.*/
669    if (retval)
670       return retval;
671    if (range_error)
672       return NC_ERANGE;
673    return NC_NOERR;
674 }
675 
676 /**
677  * @internal
678  * Write an attribute to a netCDF-4/HDF5 file, converting
679  * data type if necessary.
680  * Wrapper around nc4_put_att
681  *
682  * @param ncid File and group ID.
683  * @param varid Variable ID.
684  * @param name Name of attribute.
685  * @param file_type Type of the attribute data in file.
686  * @param len Number of elements in attribute array.
687  * @param data Attribute data.
688  * @param mem_type Type of data in memory.
689  *
690  * @return ::NC_NOERR No error.
691  * @return ::NC_EINVAL Invalid parameters.
692  * @return ::NC_EBADID Bad ncid.
693  * @return ::NC_ENOTVAR Variable not found.
694  * @return ::NC_EBADNAME Name contains illegal characters.
695  * @return ::NC_ENAMEINUSE Name already in use.
696  * @author Ed Hartnett, Dennis Heimbigner
697  */
698 int
NC4_put_att(int ncid,int varid,const char * name,nc_type file_type,size_t len,const void * data,nc_type mem_type)699 NC4_put_att(int ncid, int varid, const char *name, nc_type file_type,
700             size_t len, const void *data, nc_type mem_type)
701 {
702    int ret = NC_NOERR;
703    NC *nc;
704    NC_FILE_INFO_T *h5;
705    NC_GRP_INFO_T *grp;
706 
707    /* Find info for this file, group, and h5 info. */
708    if ((ret = nc4_find_nc_grp_h5(ncid, &nc, &grp, &h5)))
709       return ret;
710    assert(nc && grp && h5);
711 
712    return nc4_put_att(grp,varid,name,file_type,len,data,mem_type,0);
713 }
714