1 /**
2 * @file
3 * Copyright 2018 University Corporation for Atmospheric
4 * Research/Unidata. See COPYRIGHT file for more info.
5 *
6 * This file has the var and att copy functions.
7 *
8 * @author Dennis Heimbigner
9 */
10 #include "ncdispatch.h"
11 #include "nc_logging.h"
12
13 #ifdef USE_NETCDF4
14 /**
15 * @internal Compare two netcdf types for equality. Must have the
16 * ncids as well, to find user-defined types.
17 *
18 * @param ncid1 File ID.
19 * @param typeid1 Type ID.
20 * @param ncid2 File ID.
21 * @param typeid2 Type ID.
22 * @param equalp Pointer that gets 1 of the types are equal, 0
23 * otherwise.
24 *
25 * @return ::NC_NOERR No error.
26 * @author Ed Hartnett
27 */
28 static int
NC_compare_nc_types(int ncid1,int typeid1,int ncid2,int typeid2,int * equalp)29 NC_compare_nc_types(int ncid1, int typeid1, int ncid2, int typeid2,
30 int *equalp)
31 {
32 int ret = NC_NOERR;
33
34 /* If you don't care about the answer, neither do I! */
35 if(equalp == NULL)
36 return NC_NOERR;
37
38 /* Assume the types are not equal. If we find any inequality, then
39 exit with NC_NOERR and we're done. */
40 *equalp = 0;
41
42 /* Atomic types are so easy! */
43 if (typeid1 <= NC_MAX_ATOMIC_TYPE)
44 {
45 if (typeid2 != typeid1)
46 return NC_NOERR;
47 *equalp = 1;
48 }
49 else
50 {
51 int i, ret, equal1;
52 char name1[NC_MAX_NAME];
53 char name2[NC_MAX_NAME];
54 size_t size1, size2;
55 nc_type base1, base2;
56 size_t nelems1, nelems2;
57 int class1, class2;
58 void* value1 = NULL;
59 void* value2 = NULL;
60 size_t offset1, offset2;
61 nc_type ftype1, ftype2;
62 int ndims1, ndims2;
63 int dimsizes1[NC_MAX_VAR_DIMS];
64 int dimsizes2[NC_MAX_VAR_DIMS];
65
66 /* Find out about the two types. */
67 if ((ret = nc_inq_user_type(ncid1, typeid1, name1, &size1,
68 &base1, &nelems1, &class1)))
69 return ret;
70 if ((ret = nc_inq_user_type(ncid2, typeid2, name2, &size2,
71 &base2, &nelems2, &class2)))
72 return ret;
73
74 /* Check the obvious. */
75 if(size1 != size2 || class1 != class2 || strcmp(name1,name2))
76 return NC_NOERR;
77
78 /* Check user-defined types in detail. */
79 switch(class1)
80 {
81 case NC_VLEN:
82 if((ret = NC_compare_nc_types(ncid1, base1, ncid2,
83 base1, &equal1)))
84 return ret;
85 if(!equal1)
86 return NC_NOERR;
87 break;
88 case NC_OPAQUE:
89 /* Already checked size above. */
90 break;
91 case NC_ENUM:
92 if(base1 != base2 || nelems1 != nelems2) return NC_NOERR;
93
94 if (!(value1 = malloc(size1)))
95 return NC_ENOMEM;
96 if (!(value2 = malloc(size2))) {
97 free(value1);
98 return NC_ENOMEM;
99 }
100
101 for(i = 0; i < nelems1; i++)
102 {
103 if ((ret = nc_inq_enum_member(ncid1, typeid1, i, name1,
104 value1)) ||
105 (ret = nc_inq_enum_member(ncid2, typeid2, i, name2,
106 value2)) ||
107 strcmp(name1, name2) || memcmp(value1, value2, size1))
108 {
109 free(value1);
110 free(value2);
111 return ret;
112 }
113 }
114 free(value1);
115 free(value2);
116 break;
117 case NC_COMPOUND:
118 if(nelems1 != nelems2)
119 return NC_NOERR;
120
121 /* Compare each field. Each must be equal! */
122 for(i = 0; i < nelems1; i++)
123 {
124 int j;
125 if ((ret = nc_inq_compound_field(ncid1, typeid1, i, name1, &offset1,
126 &ftype1, &ndims1, dimsizes1)))
127 return ret;
128 if ((ret = nc_inq_compound_field(ncid2, typeid2, i, name2, &offset2,
129 &ftype2, &ndims2, dimsizes2)))
130 return ret;
131 if(ndims1 != ndims2)
132 return NC_NOERR;
133 for(j = 0; j < ndims1;j++)
134 if(dimsizes1[j] != dimsizes2[j])
135 return NC_NOERR;
136
137 /* Compare user-defined field types. */
138 if((ret = NC_compare_nc_types(ncid1, ftype1, ncid2, ftype2,
139 &equal1)))
140 return ret;
141 if(!equal1)
142 return NC_NOERR;
143 }
144 break;
145 default:
146 return NC_EINVAL;
147 }
148 *equalp = 1;
149 }
150 return ret;
151 }
152
153 /**
154 * @internal Recursively hunt for a netCDF type id. (Code from
155 * nc4internal.c); Return matching typeid or 0 if not found.
156 *
157 * @param ncid1 File ID.
158 * @param tid1 Type ID.
159 * @param ncid2 File ID.
160 * @param tid2 Pointer that gets type ID of equal type.
161 *
162 * @return ::NC_NOERR No error.
163 * @author Ed Hartnett
164 */
165 static int
NC_rec_find_nc_type(int ncid1,nc_type tid1,int ncid2,nc_type * tid2)166 NC_rec_find_nc_type(int ncid1, nc_type tid1, int ncid2, nc_type* tid2)
167 {
168 int i,ret = NC_NOERR;
169 int nids;
170 int* ids = NULL;
171
172 /* Get all types in grp ncid2 */
173 if(tid2)
174 *tid2 = 0;
175 if ((ret = nc_inq_typeids(ncid2, &nids, NULL)))
176 return ret;
177 if (nids)
178 {
179 if (!(ids = (int *)malloc((size_t)nids * sizeof(int))))
180 return NC_ENOMEM;
181 if ((ret = nc_inq_typeids(ncid2, &nids, ids)))
182 return ret;
183 for(i = 0; i < nids; i++)
184 {
185 int equal = 0;
186 if ((ret = NC_compare_nc_types(ncid1, tid1, ncid2, ids[i], &equal)))
187 return ret;
188 if(equal)
189 {
190 if(tid2)
191 *tid2 = ids[i];
192 free(ids);
193 return NC_NOERR;
194 }
195 }
196 free(ids);
197 }
198
199 /* recurse */
200 if ((ret = nc_inq_grps(ncid1, &nids, NULL)))
201 return ret;
202 if (nids)
203 {
204 if (!(ids = (int *)malloc((size_t)nids * sizeof(int))))
205 return NC_ENOMEM;
206 if ((ret = nc_inq_grps(ncid1, &nids, ids)))
207 {
208 free(ids);
209 return ret;
210 }
211 for (i = 0; i < nids; i++)
212 {
213 ret = NC_rec_find_nc_type(ncid1, tid1, ids[i], tid2);
214 if (ret && ret != NC_EBADTYPE)
215 break;
216 if (tid2 && *tid2 != 0) /* found */
217 {
218 free(ids);
219 return NC_NOERR;
220 }
221 }
222 free(ids);
223 }
224 return NC_EBADTYPE; /* not found */
225 }
226
227 /**
228 * @internal Given a type in one file, find its equal (if any) in
229 * another file. It sounds so simple, but it's a real pain!
230 *
231 * @param ncid1 File ID.
232 * @param xtype1 Type ID.
233 * @param ncid2 File ID.
234 * @param xtype2 Pointer that gets type ID of equal type.
235 *
236 * @return ::NC_NOERR No error.
237 * @author Ed Hartnett
238 */
239 static int
NC_find_equal_type(int ncid1,nc_type xtype1,int ncid2,nc_type * xtype2)240 NC_find_equal_type(int ncid1, nc_type xtype1, int ncid2, nc_type *xtype2)
241 {
242 int ret = NC_NOERR;
243
244 /* Check input */
245 if(xtype1 <= NC_NAT)
246 return NC_EINVAL;
247
248 /* Handle atomic types. */
249 if (xtype1 <= NC_MAX_ATOMIC_TYPE)
250 {
251 if(xtype2)
252 *xtype2 = xtype1;
253 return NC_NOERR;
254 }
255
256 /* Recursively search group ncid2 and its children
257 to find a type that is equal (using compare_type)
258 to xtype1. */
259 ret = NC_rec_find_nc_type(ncid1, xtype1 , ncid2, xtype2);
260 return ret;
261 }
262
263 #endif /* USE_NETCDF4 */
264
265 /**
266 * This will copy a variable that is an array of primitive type and
267 * its attributes from one file to another, assuming dimensions in the
268 * output file are already defined and have same dimension IDs and
269 * length. However it doesn't work for copying netCDF-4 variables of
270 * type string or a user-defined type.
271 *
272 * This function works even if the files are different formats,
273 * (for example, one netcdf classic, the other netcdf-4).
274 *
275 * If you're copying into a classic-model file, from a netcdf-4 file,
276 * you must be copying a variable of one of the six classic-model
277 * types, and similarly for the attributes.
278 *
279 * For large netCDF-3 files, this can be a very inefficient way to
280 * copy data from one file to another, because adding a new variable
281 * to the target file may require more space in the header and thus
282 * result in moving data for other variables in the target file. This
283 * is not a problem for netCDF-4 files, which support efficient
284 * addition of variables without moving data for other variables.
285 *
286 * @param ncid_in File ID to copy from.
287 * @param varid_in Variable ID to copy.
288 * @param ncid_out File ID to copy to.
289 *
290 * @return ::NC_NOERR No error.
291 * @author Glenn Davis, Ed Hartnett, Dennis Heimbigner
292 */
293 int
nc_copy_var(int ncid_in,int varid_in,int ncid_out)294 nc_copy_var(int ncid_in, int varid_in, int ncid_out)
295 {
296 char name[NC_MAX_NAME + 1];
297 char att_name[NC_MAX_NAME + 1];
298 nc_type xtype;
299 int ndims, dimids_in[NC_MAX_VAR_DIMS], dimids_out[NC_MAX_VAR_DIMS], natts, real_ndims;
300 int varid_out;
301 int a, d;
302 void *data = NULL;
303 size_t *count = NULL, *start = NULL;
304 size_t reclen = 1;
305 size_t *dimlen = NULL;
306 int retval = NC_NOERR;
307 size_t type_size;
308 int src_format, dest_format;
309 char type_name[NC_MAX_NAME+1];
310 char dimname_in[NC_MAX_NAME + 1];
311 int i;
312
313 /* Learn about this var. */
314 if ((retval = nc_inq_var(ncid_in, varid_in, name, &xtype,
315 &ndims, dimids_in, &natts)))
316 return retval;
317 /* find corresponding dimids in the output file */
318 for(i = 0; i < ndims; i++) {
319 dimids_out[i] = dimids_in[i];
320 if ((retval = nc_inq_dimname(ncid_in, dimids_in[i], dimname_in)))
321 return retval;
322 if ((retval = nc_inq_dimid(ncid_out, dimname_in, &dimids_out[i])))
323 return retval;
324 }
325
326 LOG((2, "nc_copy_var: ncid_in 0x%x varid_in %d ncid_out 0x%x",
327 ncid_in, varid_in, ncid_out));
328
329 /* Make sure we are not trying to write into a netcdf-3 file
330 * anything that won't fit in netcdf-3. */
331 if ((retval = nc_inq_format(ncid_in, &src_format)))
332 return retval;
333 if ((retval = nc_inq_format(ncid_out, &dest_format)))
334 return retval;
335 if ((dest_format == NC_FORMAT_CLASSIC
336 || dest_format == NC_FORMAT_64BIT_DATA
337 || dest_format == NC_FORMAT_64BIT_OFFSET) &&
338 src_format == NC_FORMAT_NETCDF4 && xtype > NC_DOUBLE)
339 return NC_ENOTNC4;
340
341 /* Later on, we will need to know the size of this type. */
342 if ((retval = nc_inq_type(ncid_in, xtype, type_name, &type_size)))
343 return retval;
344 LOG((3, "type %s has size %d", type_name, type_size));
345
346 /* Switch back to define mode, and create the output var. */
347 retval = nc_redef(ncid_out);
348 if (retval && retval != NC_EINDEFINE)
349 BAIL(retval);
350 if ((retval = nc_def_var(ncid_out, name, xtype,
351 ndims, dimids_out, &varid_out)))
352 BAIL(retval);
353
354 /* Copy the attributes. */
355 for (a=0; a<natts; a++)
356 {
357 if ((retval = nc_inq_attname(ncid_in, varid_in, a, att_name)))
358 BAIL(retval);
359 if ((retval = nc_copy_att(ncid_in, varid_in, att_name,
360 ncid_out, varid_out)))
361 BAIL(retval);
362 }
363
364 /* End define mode, to write metadata and create file. */
365 nc_enddef(ncid_out);
366 nc_sync(ncid_out);
367
368 /* Allocate memory for our start and count arrays. If ndims = 0
369 this is a scalar, which I will treat as a 1-D array with one
370 element. */
371 real_ndims = ndims ? ndims : 1;
372 if (!(start = malloc((size_t)real_ndims * sizeof(size_t))))
373 BAIL(NC_ENOMEM);
374 if (!(count = malloc((size_t)real_ndims * sizeof(size_t))))
375 BAIL(NC_ENOMEM);
376
377 /* The start array will be all zeros, except the first element,
378 which will be the record number. Count will be the dimension
379 size, except for the first element, which will be one, because
380 we will copy one record at a time. For this we need the var
381 shape. */
382 if (!(dimlen = malloc((size_t)real_ndims * sizeof(size_t))))
383 BAIL(NC_ENOMEM);
384
385 /* Set to 0, to correct for an unlikely dereference
386 error reported by clang/llvm. */
387 dimlen[0] = 0;
388
389 /* Find out how much data. */
390 for (d=0; d<ndims; d++)
391 {
392 if ((retval = nc_inq_dimlen(ncid_in, dimids_in[d], &dimlen[d])))
393 BAIL(retval);
394 LOG((4, "nc_copy_var: there are %d data", dimlen[d]));
395 }
396
397 /* If this is really a scalar, then set the dimlen to 1. */
398 if (ndims == 0)
399 dimlen[0] = 1;
400
401 for (d=0; d<real_ndims; d++)
402 {
403 start[d] = 0;
404 count[d] = d ? dimlen[d] : 1;
405 if (d) reclen *= dimlen[d];
406 }
407
408 /* If there are no records, we're done. */
409 if (!dimlen[0])
410 goto exit;
411
412 /* Allocate memory for one record. */
413 if (!(data = malloc(reclen * type_size))) {
414 if(count) free(count);
415 if(dimlen) free(dimlen);
416 if(start) free(start);
417 return NC_ENOMEM;
418 }
419
420 /* Copy the var data one record at a time. */
421 for (start[0]=0; !retval && start[0]<(size_t)dimlen[0]; start[0]++)
422 {
423 switch (xtype)
424 {
425 case NC_BYTE:
426 retval = nc_get_vara_schar(ncid_in, varid_in, start, count,
427 (signed char *)data);
428 if (!retval)
429 retval = nc_put_vara_schar(ncid_out, varid_out, start, count,
430 (const signed char *)data);
431 break;
432 case NC_CHAR:
433 retval = nc_get_vara_text(ncid_in, varid_in, start, count,
434 (char *)data);
435 if (!retval)
436 retval = nc_put_vara_text(ncid_out, varid_out, start, count,
437 (char *)data);
438 break;
439 case NC_SHORT:
440 retval = nc_get_vara_short(ncid_in, varid_in, start, count,
441 (short *)data);
442 if (!retval)
443 retval = nc_put_vara_short(ncid_out, varid_out, start, count,
444 (short *)data);
445 break;
446 case NC_INT:
447 retval = nc_get_vara_int(ncid_in, varid_in, start, count,
448 (int *)data);
449 if (!retval)
450 retval = nc_put_vara_int(ncid_out, varid_out, start, count,
451 (int *)data);
452 break;
453 case NC_FLOAT:
454 retval = nc_get_vara_float(ncid_in, varid_in, start, count,
455 (float *)data);
456 if (!retval)
457 retval = nc_put_vara_float(ncid_out, varid_out, start, count,
458 (float *)data);
459 break;
460 case NC_DOUBLE:
461 retval = nc_get_vara_double(ncid_in, varid_in, start, count,
462 (double *)data);
463 if (!retval)
464 retval = nc_put_vara_double(ncid_out, varid_out, start, count,
465 (double *)data);
466 break;
467 case NC_UBYTE:
468 retval = nc_get_vara_uchar(ncid_in, varid_in, start, count,
469 (unsigned char *)data);
470 if (!retval)
471 retval = nc_put_vara_uchar(ncid_out, varid_out, start, count,
472 (unsigned char *)data);
473 break;
474 case NC_USHORT:
475 retval = nc_get_vara_ushort(ncid_in, varid_in, start, count,
476 (unsigned short *)data);
477 if (!retval)
478 retval = nc_put_vara_ushort(ncid_out, varid_out, start, count,
479 (unsigned short *)data);
480 break;
481 case NC_UINT:
482 retval = nc_get_vara_uint(ncid_in, varid_in, start, count,
483 (unsigned int *)data);
484 if (!retval)
485 retval = nc_put_vara_uint(ncid_out, varid_out, start, count,
486 (unsigned int *)data);
487 break;
488 case NC_INT64:
489 retval = nc_get_vara_longlong(ncid_in, varid_in, start, count,
490 (long long *)data);
491 if (!retval)
492 retval = nc_put_vara_longlong(ncid_out, varid_out, start, count,
493 (long long *)data);
494 break;
495 case NC_UINT64:
496 retval = nc_get_vara_ulonglong(ncid_in, varid_in, start, count,
497 (unsigned long long *)data);
498 if (!retval)
499 retval = nc_put_vara_ulonglong(ncid_out, varid_out, start, count,
500 (unsigned long long *)data);
501 break;
502 default:
503 retval = NC_EBADTYPE;
504 }
505 }
506
507 exit:
508 if (data) free(data);
509 if (dimlen) free(dimlen);
510 if (start) free(start);
511 if (count) free(count);
512 return retval;
513 }
514
515 /**
516 * Copy an attribute from one open file to another. This is called by
517 * nc_copy_att().
518 *
519 * @param ncid_in File ID to copy from.
520 * @param varid_in Variable ID to copy from.
521 * @param name Name of attribute to copy.
522 * @param ncid_out File ID to copy to.
523 * @param varid_out Variable ID to copy to.
524 *
525 * @return ::NC_NOERR No error.
526 * @author Glenn Davis, Ed Hartnett, Dennis Heimbigner
527 */
528 static int
NC_copy_att(int ncid_in,int varid_in,const char * name,int ncid_out,int varid_out)529 NC_copy_att(int ncid_in, int varid_in, const char *name,
530 int ncid_out, int varid_out)
531 {
532 nc_type xtype;
533 size_t len;
534 void *data=NULL;
535 int res;
536
537 LOG((2, "nc_copy_att: ncid_in 0x%x varid_in %d name %s",
538 ncid_in, varid_in, name));
539
540 /* Find out about the attribute to be copied. */
541 if ((res = nc_inq_att(ncid_in, varid_in, name, &xtype, &len)))
542 return res;
543
544 if (xtype < NC_STRING)
545 {
546 /* Handle non-string atomic types. */
547 if (len)
548 {
549 size_t size = NC_atomictypelen(xtype);
550
551 assert(size > 0);
552 if (!(data = malloc(len * size)))
553 return NC_ENOMEM;
554 }
555
556 res = nc_get_att(ncid_in, varid_in, name, data);
557 if (!res)
558 res = nc_put_att(ncid_out, varid_out, name, xtype,
559 len, data);
560 if (len)
561 free(data);
562 }
563 #ifdef USE_NETCDF4
564 else if (xtype == NC_STRING)
565 {
566 /* Copy string attributes. */
567 char **str_data;
568 if (!(str_data = malloc(sizeof(char *) * len)))
569 return NC_ENOMEM;
570 res = nc_get_att_string(ncid_in, varid_in, name, str_data);
571 if (!res)
572 res = nc_put_att_string(ncid_out, varid_out, name, len,
573 (const char **)str_data);
574 nc_free_string(len, str_data);
575 free(str_data);
576 }
577 else
578 {
579 /* Copy user-defined type attributes. */
580 int class;
581 size_t size;
582 void *data;
583 nc_type xtype_out = NC_NAT;
584
585 /* Find out if there is an equal type in the output file. */
586 /* Note: original code used a libsrc4 specific internal function
587 which we had to "duplicate" here */
588 if ((res = NC_find_equal_type(ncid_in, xtype, ncid_out, &xtype_out)))
589 return res;
590 if (xtype_out)
591 {
592 /* We found an equal type! */
593 if ((res = nc_inq_user_type(ncid_in, xtype, NULL, &size,
594 NULL, NULL, &class)))
595 return res;
596 if (class == NC_VLEN) /* VLENs are different... */
597 {
598 nc_vlen_t *vldata;
599 int i;
600 if (!(vldata = malloc(sizeof(nc_vlen_t) * len)))
601 return NC_ENOMEM;
602 if ((res = nc_get_att(ncid_in, varid_in, name, vldata)))
603 return res;
604 if ((res = nc_put_att(ncid_out, varid_out, name, xtype_out,
605 len, vldata)))
606 return res;
607 for (i = 0; i < len; i++)
608 if((res = nc_free_vlen(&vldata[i])))
609 return res;
610 free(vldata);
611 }
612 else /* not VLEN */
613 {
614 if (!(data = malloc(size * len)))
615 return NC_ENOMEM;
616 res = nc_get_att(ncid_in, varid_in, name, data);
617 if (!res)
618 res = nc_put_att(ncid_out, varid_out, name, xtype_out, len, data);
619 free(data);
620 }
621 }
622 }
623 #endif /*!USE_NETCDF4*/
624 return res;
625 }
626
627 /**
628 * Copy an attribute from one open file to another.
629 *
630 * Special programming challenge: this function must work even if one
631 * of the other of the files is a netcdf version 1.0 file (i.e. not
632 * HDF5). So only use top level netcdf api functions.
633 *
634 * From the netcdf-3 docs: The output netCDF dataset should be in
635 * define mode if the attribute to be copied does not already exist
636 * for the target variable, or if it would cause an existing target
637 * attribute to grow.
638 *
639 * @param ncid_in File ID to copy from.
640 * @param varid_in Variable ID to copy from.
641 * @param name Name of attribute to copy.
642 * @param ncid_out File ID to copy to.
643 * @param varid_out Variable ID to copy to.
644 *
645 * @return ::NC_NOERR No error.
646 * @author Glenn Davis, Ed Hartnett, Dennis Heimbigner
647 */
648 int
nc_copy_att(int ncid_in,int varid_in,const char * name,int ncid_out,int varid_out)649 nc_copy_att(int ncid_in, int varid_in, const char *name,
650 int ncid_out, int varid_out)
651 {
652 int format, target_natts, target_attid;
653 char att_name[NC_MAX_NAME + 1];
654 int a, retval;
655
656 /* What is the destination format? */
657 if ((retval = nc_inq_format(ncid_out, &format)))
658 return retval;
659
660 /* Can't copy to same var in same file. */
661 if (ncid_in == ncid_out && varid_in == varid_out)
662 return NC_NOERR;
663
664 /* For classic model netCDF-4 files, order of attributes must be
665 * maintained during copies. We MUST MAINTAIN ORDER! */
666 if (format == NC_FORMAT_NETCDF4_CLASSIC)
667 {
668 /* Does this attribute already exist in the target file? */
669 retval = nc_inq_attid(ncid_out, varid_out, name, &target_attid);
670 if (retval == NC_ENOTATT)
671 {
672 /* Attribute does not exist. No order to be preserved. */
673 return NC_copy_att(ncid_in, varid_in, name, ncid_out, varid_out);
674 }
675 else if (retval == NC_NOERR)
676 {
677 /* How many atts for this var? */
678 if ((retval = nc_inq_varnatts(ncid_out, varid_out, &target_natts)))
679 return retval;
680
681 /* If this is the last attribute in the target file, we are
682 * off the hook. */
683 if (target_attid == target_natts - 1)
684 return NC_copy_att(ncid_in, varid_in, name, ncid_out, varid_out);
685
686 /* Order MUST BE MAINTAINED! Copy all existing atts in the target
687 * file, stopping at our target att. */
688 for (a = 0; a < target_natts; a++)
689 {
690 if (a == target_attid)
691 {
692 if ((retval = NC_copy_att(ncid_in, varid_in, name, ncid_out, varid_out)))
693 return retval;
694 }
695 else
696 {
697 if ((retval = nc_inq_attname(ncid_out, varid_out, a, att_name)))
698 return retval;
699 if ((retval = NC_copy_att(ncid_out, varid_out, att_name,
700 ncid_out, varid_out)))
701 return retval;
702 }
703 }
704 }
705 else
706 return retval; /* Some other error occurred. */
707 }
708 else
709 return NC_copy_att(ncid_in, varid_in, name, ncid_out, varid_out);
710
711 return NC_NOERR;
712 }
713