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