/* * Copyright 2018, University Corporation for Atmospheric Research * See netcdf/COPYRIGHT file for copying and redistribution conditions. */ /* $Id: var.c,v 1.144 2010/05/30 00:50:35 russ Exp $ */ #if HAVE_CONFIG_H #include #endif #include "nc3internal.h" #include #include #include #include #include "ncx.h" #include "rnd.h" #include "ncutf8.h" #include "nc3dispatch.h" #ifndef OFF_T_MAX #if 0 #define OFF_T_MAX (~ (off_t) 0 - (~ (off_t) 0 << (CHAR_BIT * sizeof (off_t) - 1))) #endif /* The behavior above is undefined, re: bitshifting a negative value, according to warnings thrown by clang/gcc. An alternative OFF_T_MAX was written based on info found at: * http://stackoverflow.com/questions/4514572/c-question-off-t-and-other-signed-integer-types-minimum-and-maximum-values */ #define MAX_INT_VAL_STEP(t) \ ((t) 1 << (CHAR_BIT * sizeof(t) - 1 - ((t) -1 < 1))) #define MAX_INT_VAL(t) \ ((MAX_INT_VAL_STEP(t) - 1) + MAX_INT_VAL_STEP(t)) #define MIN_INT_VAL(t) \ ((t) -MAX_INT_VAL(t) - 1) #define OFF_T_MAX MAX_INT_VAL(off_t) #endif /* * Free var * Formerly NC_free_var(var) */ void free_NC_var(NC_var *varp) { if(varp == NULL) return; free_NC_attrarrayV(&varp->attrs); free_NC_string(varp->name); #ifndef MALLOCHACK if(varp->dimids != NULL) free(varp->dimids); if(varp->shape != NULL) free(varp->shape); if(varp->dsizes != NULL) free(varp->dsizes); #endif /*!MALLOCHACK*/ free(varp); } /* * Common code for new_NC_var() * and ncx_get_NC_var() */ NC_var * new_x_NC_var( NC_string *strp, size_t ndims) { NC_var *varp; const size_t o1 = M_RNDUP(ndims * sizeof(int)); const size_t o2 = M_RNDUP(ndims * sizeof(size_t)); #ifdef MALLOCHACK const size_t sz = M_RNDUP(sizeof(NC_var)) + o1 + o2 + ndims * sizeof(off_t); #else /*!MALLOCHACK*/ const size_t o3 = ndims * sizeof(off_t); const size_t sz = sizeof(NC_var); #endif /*!MALLOCHACK*/ varp = (NC_var *) malloc(sz); if(varp == NULL ) return NULL; (void) memset(varp, 0, sz); varp->name = strp; varp->ndims = ndims; if(ndims != 0) { #ifdef MALLOCHACK /* * NOTE: lint may complain about the next 3 lines: * "pointer cast may result in improper alignment". * We use the M_RNDUP() macro to get the proper alignment. */ varp->dimids = (int *)((char *)varp + M_RNDUP(sizeof(NC_var))); varp->shape = (size_t *)((char *)varp->dimids + o1); varp->dsizes = (off_t *)((char *)varp->shape + o2); #else /*!MALLOCHACK*/ varp->dimids = (int*)malloc(o1); varp->shape = (size_t*)malloc(o2); varp->dsizes = (off_t*)malloc(o3); #endif /*!MALLOCHACK*/ } else { varp->dimids = NULL; varp->shape = NULL; varp->dsizes=NULL; } varp->xsz = 0; varp->len = 0; varp->begin = 0; return varp; } /* * Formerly NC_new_var() */ static NC_var * new_NC_var(const char *uname, nc_type type, size_t ndims, const int *dimids) { NC_string *strp = NULL; NC_var *varp = NULL; int stat; char* name; stat = nc_utf8_normalize((const unsigned char *)uname,(unsigned char **)&name); if(stat != NC_NOERR) return NULL; strp = new_NC_string(strlen(name), name); free(name); if(strp == NULL) return NULL; varp = new_x_NC_var(strp, ndims); if(varp == NULL ) { free_NC_string(strp); return NULL; } varp->type = type; if( ndims != 0 && dimids != NULL) (void) memcpy(varp->dimids, dimids, ndims * sizeof(int)); else varp->dimids=NULL; return(varp); } static NC_var * dup_NC_var(const NC_var *rvarp) { NC_var *varp = new_NC_var(rvarp->name->cp, rvarp->type, rvarp->ndims, rvarp->dimids); if(varp == NULL) return NULL; if(dup_NC_attrarrayV(&varp->attrs, &rvarp->attrs) != NC_NOERR) { free_NC_var(varp); return NULL; } (void) memcpy(varp->shape, rvarp->shape, rvarp->ndims * sizeof(size_t)); (void) memcpy(varp->dsizes, rvarp->dsizes, rvarp->ndims * sizeof(off_t)); varp->xsz = rvarp->xsz; varp->len = rvarp->len; varp->begin = rvarp->begin; return varp; } /* vararray */ /* * Free the stuff "in" (referred to by) an NC_vararray. * Leaves the array itself allocated. */ void free_NC_vararrayV0(NC_vararray *ncap) { assert(ncap != NULL); if(ncap->nelems == 0) return; assert(ncap->value != NULL); { NC_var **vpp = ncap->value; NC_var *const *const end = &vpp[ncap->nelems]; for( /*NADA*/; vpp < end; vpp++) { free_NC_var(*vpp); *vpp = NULL; } } ncap->nelems = 0; } /* * Free NC_vararray values. * formerly NC_free_array() */ void free_NC_vararrayV(NC_vararray *ncap) { assert(ncap != NULL); if(ncap->nalloc == 0) return; NC_hashmapfree(ncap->hashmap); ncap->hashmap = NULL; assert(ncap->value != NULL); free_NC_vararrayV0(ncap); free(ncap->value); ncap->value = NULL; ncap->nalloc = 0; } int dup_NC_vararrayV(NC_vararray *ncap, const NC_vararray *ref) { int status = NC_NOERR; assert(ref != NULL); assert(ncap != NULL); if(ref->nelems != 0) { const size_t sz = ref->nelems * sizeof(NC_var *); ncap->value = (NC_var **) malloc(sz); if(ncap->value == NULL) return NC_ENOMEM; (void) memset(ncap->value, 0, sz); ncap->nalloc = ref->nelems; } ncap->nelems = 0; { NC_var **vpp = ncap->value; const NC_var **drpp = (const NC_var **)ref->value; NC_var *const *const end = &vpp[ref->nelems]; for( /*NADA*/; vpp < end; drpp++, vpp++, ncap->nelems++) { *vpp = dup_NC_var(*drpp); if(*vpp == NULL) { status = NC_ENOMEM; break; } } } if(status != NC_NOERR) { free_NC_vararrayV(ncap); return status; } assert(ncap->nelems == ref->nelems); return NC_NOERR; } /* * Add a new handle on the end of an array of handles * Formerly NC_incr_array(array, tail) */ static int incr_NC_vararray(NC_vararray *ncap, NC_var *newelemp) { NC_var **vp; assert(ncap != NULL); if(ncap->nalloc == 0) { assert(ncap->nelems == 0); vp = (NC_var **) malloc(NC_ARRAY_GROWBY * sizeof(NC_var *)); if(vp == NULL) return NC_ENOMEM; ncap->value = vp; ncap->nalloc = NC_ARRAY_GROWBY; ncap->hashmap = NC_hashmapnew(0); } else if(ncap->nelems +1 > ncap->nalloc) { vp = (NC_var **) realloc(ncap->value, (ncap->nalloc + NC_ARRAY_GROWBY) * sizeof(NC_var *)); if(vp == NULL) return NC_ENOMEM; ncap->value = vp; ncap->nalloc += NC_ARRAY_GROWBY; } if(newelemp != NULL) { NC_hashmapadd(ncap->hashmap, (uintptr_t)ncap->nelems, newelemp->name->cp, strlen(newelemp->name->cp)); ncap->value[ncap->nelems] = newelemp; ncap->nelems++; } return NC_NOERR; } static NC_var * elem_NC_vararray(const NC_vararray *ncap, size_t elem) { assert(ncap != NULL); /* cast needed for braindead systems with signed size_t */ if(ncap->nelems == 0 || (unsigned long)elem >= ncap->nelems) return NULL; assert(ncap->value != NULL); return ncap->value[elem]; } /* End vararray per se */ /* * Step thru NC_VARIABLE array, seeking match on name. * Return varid or -1 on not found. * *varpp is set to the appropriate NC_var. * Formerly (sort of) NC_hvarid */ int NC_findvar(const NC_vararray *ncap, const char *uname, NC_var **varpp) { int hash_var_id = -1; uintptr_t data; char *name = NULL; assert(ncap != NULL); if(ncap->nelems == 0) goto done; /* normalized version of uname */ if(nc_utf8_normalize((const unsigned char *)uname,(unsigned char **)&name)) goto done; if(NC_hashmapget(ncap->hashmap, name, strlen(name), &data) == 0) goto done; hash_var_id = (int)data; if (varpp != NULL) *varpp = ncap->value[hash_var_id]; done: if(name != NULL) free(name); return(hash_var_id); /* Normal return */ } /* * For a netcdf type * return the size of one element in the external representation. * Note that arrays get rounded up to X_ALIGN boundaries. * Formerly NC_xtypelen * See also ncx_len() */ size_t ncx_szof(nc_type type) { switch(type){ case NC_BYTE: case NC_CHAR: case NC_UBYTE: return(1); case NC_SHORT : return(2); case NC_INT: return X_SIZEOF_INT; case NC_FLOAT: return X_SIZEOF_FLOAT; case NC_DOUBLE : return X_SIZEOF_DOUBLE; case NC_USHORT : return X_SIZEOF_USHORT; case NC_UINT : return X_SIZEOF_UINT; case NC_INT64 : return X_SIZEOF_INT64; case NC_UINT64 : return X_SIZEOF_UINT64; default: assert("ncx_szof invalid type" == 0); return 0; } } /* * 'compile' the shape and len of a variable * Formerly NC_var_shape(var, dims) */ int NC_var_shape(NC_var *varp, const NC_dimarray *dims) { size_t *shp, *op; off_t *dsp; int *ip = NULL; const NC_dim *dimp; off_t product = 1; varp->xsz = ncx_szof(varp->type); if(varp->ndims == 0 || varp->dimids == NULL) { goto out; } /* * use the user supplied dimension indices * to determine the shape */ for(ip = varp->dimids, op = varp->shape ; ip < &varp->dimids[varp->ndims]; ip++, op++) { if(*ip < 0 || (size_t) (*ip) >= ((dims != NULL) ? dims->nelems : 1) ) return NC_EBADDIM; dimp = elem_NC_dimarray(dims, (size_t)*ip); *op = dimp->size; if(*op == NC_UNLIMITED && ip != varp->dimids) return NC_EUNLIMPOS; } /* * Compute the dsizes */ /* ndims is > 0 here */ for(shp = varp->shape + varp->ndims -1, dsp = varp->dsizes + varp->ndims -1; shp >= varp->shape; shp--, dsp--) { /*if(!(shp == varp->shape && IS_RECVAR(varp)))*/ if( shp != NULL && (shp != varp->shape || !IS_RECVAR(varp))) { if( ((off_t)(*shp)) <= OFF_T_MAX / product ) { product *= (*shp > 0 ? *shp : 1); } else { product = OFF_T_MAX ; } } *dsp = product; } out : /* * For CDF-1 and CDF-2 formats, the total number of array elements * cannot exceed 2^32, unless this variable is the last fixed-size * variable, there is no record variable, and the file starting * offset of this variable is less than 2GiB. * This will be checked in NC_check_vlens() during NC_endef() */ varp->len = product * varp->xsz; if (varp->len % 4 > 0) varp->len += 4 - varp->len % 4; /* round up */ #if 0 arrayp("\tshape", varp->ndims, varp->shape); arrayp("\tdsizes", varp->ndims, varp->dsizes); #endif return NC_NOERR; } /* * Check whether variable size is less than or equal to vlen_max, * without overflowing in arithmetic calculations. If OK, return 1, * else, return 0. For CDF1 format or for CDF2 format on non-LFS * platforms, vlen_max should be 2^31 - 4, but for CDF2 format on * systems with LFS it should be 2^32 - 4. */ int NC_check_vlen(NC_var *varp, long long vlen_max) { int ii; long long prod=varp->xsz; /* product of xsz and dimensions so far */ assert(varp != NULL); for(ii = IS_RECVAR(varp) ? 1 : 0; ii < varp->ndims; ii++) { if(!varp->shape) return 0; /* Shape is undefined/NULL. */ if ((long long)varp->shape[ii] > vlen_max / prod) { return 0; /* size in bytes won't fit in a 32-bit int */ } prod *= varp->shape[ii]; } return 1; /* OK */ } /*! Look up a variable by varid. * * Given a valid ncp structure and varid, return the var. * * Formerly NC_hlookupvar() * * @param[in] ncp NC3_INFO data structure. * @param[in] varid The varid key for the var we are looking up. * @param[out] varp Data structure to contain the varp pointer. * @return Error code, if one exists, 0 otherwise. */ int NC_lookupvar(NC3_INFO* ncp, int varid, NC_var **varp) { if(varid == NC_GLOBAL) { /* Global is error in this context */ return NC_EGLOBAL; } if(varp) *varp = elem_NC_vararray(&ncp->vars, (size_t)varid); else return NC_ENOTVAR; if(*varp == NULL) return NC_ENOTVAR; return NC_NOERR; } /* Public */ int NC3_def_var( int ncid, const char *name, nc_type type, int ndims, const int *dimids, int *varidp) { int status; NC *nc; NC3_INFO* ncp; int varid; NC_var *varp = NULL; status = NC_check_id(ncid, &nc); if(status != NC_NOERR) return status; ncp = NC3_DATA(nc); if(!NC_indef(ncp)) { return NC_ENOTINDEFINE; } status = NC_check_name(name); if(status != NC_NOERR) return status; status = nc3_cktype(nc->mode, type); if(status != NC_NOERR) return status; if (ndims > NC_MAX_VAR_DIMS) return NC_EMAXDIMS; /* cast needed for braindead systems with signed size_t */ if((unsigned long) ndims > X_INT_MAX) /* Backward compat */ { return NC_EINVAL; } varid = NC_findvar(&ncp->vars, name, &varp); if(varid != -1) { return NC_ENAMEINUSE; } varp = new_NC_var(name, type, ndims, dimids); if(varp == NULL) return NC_ENOMEM; status = NC_var_shape(varp, &ncp->dims); if(status != NC_NOERR) { free_NC_var(varp); return status; } status = incr_NC_vararray(&ncp->vars, varp); if(status != NC_NOERR) { free_NC_var(varp); return status; } if(varidp != NULL) *varidp = (int)ncp->vars.nelems -1; /* varid */ /* set the variable's fill mode */ if (NC_dofill(ncp)) varp->no_fill = 0; else varp->no_fill = 1; return NC_NOERR; } int NC3_inq_varid(int ncid, const char *name, int *varid_ptr) { int status; NC *nc; NC3_INFO* ncp; NC_var *varp; int varid; status = NC_check_id(ncid, &nc); if(status != NC_NOERR) return status; ncp = NC3_DATA(nc); varid = NC_findvar(&ncp->vars, name, &varp); if(varid == -1) { return NC_ENOTVAR; } *varid_ptr = varid; return NC_NOERR; } int NC3_inq_var(int ncid, int varid, char *name, nc_type *typep, int *ndimsp, int *dimids, int *nattsp, int *no_fillp, void *fill_valuep) { int status; NC *nc; NC3_INFO* ncp; NC_var *varp; size_t ii; status = NC_check_id(ncid, &nc); if(status != NC_NOERR) return status; ncp = NC3_DATA(nc); varp = elem_NC_vararray(&ncp->vars, (size_t)varid); if(varp == NULL) return NC_ENOTVAR; if(name != NULL) { (void) strncpy(name, varp->name->cp, varp->name->nchars); name[varp->name->nchars] = 0; } if(typep != 0) *typep = varp->type; if(ndimsp != 0) { *ndimsp = (int) varp->ndims; } if(dimids != 0) { for(ii = 0; ii < varp->ndims; ii++) { dimids[ii] = varp->dimids[ii]; } } if(nattsp != 0) { *nattsp = (int) varp->attrs.nelems; } if (no_fillp != NULL) *no_fillp = varp->no_fill; if (fill_valuep != NULL) { status = nc_get_att(ncid, varid, _FillValue, fill_valuep); if (status != NC_NOERR && status != NC_ENOTATT) return status; if (status == NC_ENOTATT) { status = NC3_inq_default_fill_value(varp->type, fill_valuep); if (status != NC_NOERR) return status; } } return NC_NOERR; } int NC3_rename_var(int ncid, int varid, const char *unewname) { int status = NC_NOERR; NC *nc; NC3_INFO* ncp; uintptr_t intdata; NC_var *varp; NC_string *old, *newStr; int other; char *newname = NULL; /* normalized */ status = NC_check_id(ncid, &nc); if(status != NC_NOERR) goto done; ncp = NC3_DATA(nc); if(NC_readonly(ncp)) {status = NC_EPERM; goto done;} status = NC_check_name(unewname); if(status != NC_NOERR) goto done; /* check for name in use */ other = NC_findvar(&ncp->vars, unewname, &varp); if(other != -1) {status = NC_ENAMEINUSE; goto done;} status = NC_lookupvar(ncp, varid, &varp); if(status != NC_NOERR) goto done; /* invalid varid */ old = varp->name; status = nc_utf8_normalize((const unsigned char *)unewname,(unsigned char **)&newname); if(status != NC_NOERR) goto done; if(NC_indef(ncp)) { /* Remove old name from hashmap; add new... */ /* WARNING: strlen(NC_string.cp) may be less than NC_string.nchars */ NC_hashmapremove(ncp->vars.hashmap,old->cp,strlen(old->cp),NULL); newStr = new_NC_string(strlen(newname),newname); if(newStr == NULL) {status = NC_ENOMEM; goto done;} varp->name = newStr; intdata = (uintptr_t)varid; NC_hashmapadd(ncp->vars.hashmap, intdata, varp->name->cp, strlen(varp->name->cp)); free_NC_string(old); goto done; } /* else, not in define mode */ /* If new name is longer than old, then complain, but otherwise, no change (test is same as set_NC_string)*/ if(varp->name->nchars < strlen(newname)) {status = NC_ENOTINDEFINE; goto done;} /* WARNING: strlen(NC_string.cp) may be less than NC_string.nchars */ /* Remove old name from hashmap; add new... */ NC_hashmapremove(ncp->vars.hashmap,old->cp,strlen(old->cp),NULL); /* WARNING: strlen(NC_string.cp) may be less than NC_string.nchars */ status = set_NC_string(varp->name, newname); if(status != NC_NOERR) goto done; intdata = (uintptr_t)varid; NC_hashmapadd(ncp->vars.hashmap, intdata, varp->name->cp, strlen(varp->name->cp)); set_NC_hdirty(ncp); if(NC_doHsync(ncp)) { status = NC_sync(ncp); if(status != NC_NOERR) goto done; } done: if(newname) free(newname); return status; } int NC3_def_var_fill(int ncid, int varid, int no_fill, const void *fill_value) { int status; NC *nc; NC3_INFO* ncp; NC_var *varp; status = NC_check_id(ncid, &nc); if(status != NC_NOERR) return status; ncp = NC3_DATA(nc); if(NC_readonly(ncp)) { return NC_EPERM; } if(!NC_indef(ncp)) { return NC_ENOTINDEFINE; } varp = elem_NC_vararray(&ncp->vars, (size_t)varid); if(varp == NULL) return NC_ENOTVAR; if (no_fill) varp->no_fill = 1; else varp->no_fill = 0; /* Are we setting a fill value? */ if (fill_value != NULL && !varp->no_fill) { /* If there's a _FillValue attribute, delete it. */ status = NC3_del_att(ncid, varid, _FillValue); if (status != NC_NOERR && status != NC_ENOTATT) return status; /* Create/overwrite attribute _FillValue */ status = NC3_put_att(ncid, varid, _FillValue, varp->type, 1, fill_value, varp->type); if (status != NC_NOERR) return status; } return NC_NOERR; }