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