/* * Copyright 2018, University Corporation for Atmospheric Research * See netcdf/COPYRIGHT file for copying and redistribution conditions. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "nc3internal.h" #include "rnd.h" #include "ncx.h" /* * This module defines the external representation * of the "header" of a netcdf version one file and * the version two variant that uses 64-bit file * offsets instead of the 32-bit file offsets in version * one files. * For each of the components of the NC structure, * There are (static) ncx_len_XXX(), v1h_put_XXX() * and v1h_get_XXX() functions. These define the * external representation of the components. * The exported entry points for the whole NC structure * are built up from these. */ /* * "magic number" at beginning of file: 0x43444601 (big endian) * assert(sizeof(ncmagic) % X_ALIGN == 0); */ static const schar ncmagic[] = {'C', 'D', 'F', 0x02}; static const schar ncmagic1[] = {'C', 'D', 'F', 0x01}; static const schar ncmagic5[] = {'C', 'D', 'F', 0x05}; /* * v1hs == "Version 1 Header Stream" * * The netcdf file version 1 header is * of unknown and potentially unlimited size. * So, we don't know how much to get() on * the initial read. We build a stream, 'v1hs' * on top of ncio to do the header get. */ typedef struct v1hs { ncio *nciop; off_t offset; /* argument to nciop->get() */ size_t extent; /* argument to nciop->get() */ int flags; /* set to RGN_WRITE for write */ int version; /* format variant: NC_FORMAT_CLASSIC, NC_FORMAT_64BIT_OFFSET or NC_FORMAT_CDF5 */ void *base; /* beginning of current buffer */ void *pos; /* current position in buffer */ void *end; /* end of current buffer = base + extent */ } v1hs; /* * Release the stream, invalidate buffer */ static int rel_v1hs(v1hs *gsp) { int status; if(gsp->offset == OFF_NONE || gsp->base == NULL) return NC_NOERR; status = ncio_rel(gsp->nciop, gsp->offset, gsp->flags == RGN_WRITE ? RGN_MODIFIED : 0); gsp->end = NULL; gsp->pos = NULL; gsp->base = NULL; return status; } /* * Release the current chunk and get the next one. * Also used for initialization when gsp->base == NULL. */ static int fault_v1hs(v1hs *gsp, size_t extent) { int status; if(gsp->base != NULL) { const ptrdiff_t incr = (char *)gsp->pos - (char *)gsp->base; status = rel_v1hs(gsp); if(status) return status; gsp->offset += incr; } if(extent > gsp->extent) gsp->extent = extent; status = ncio_get(gsp->nciop, gsp->offset, gsp->extent, gsp->flags, &gsp->base); if(status) return status; gsp->pos = gsp->base; gsp->end = (char *)gsp->base + gsp->extent; return NC_NOERR; } /* * Ensure that 'nextread' bytes are available. */ static int check_v1hs(v1hs *gsp, size_t nextread) { #if 0 /* DEBUG */ fprintf(stderr, "nextread %lu, remaining %lu\n", (unsigned long)nextread, (unsigned long)((char *)gsp->end - (char *)gsp->pos)); #endif if((char *)gsp->pos + nextread <= (char *)gsp->end) return NC_NOERR; return fault_v1hs(gsp, nextread); } /* End v1hs */ /* Write a size_t to the header */ static int v1h_put_size_t(v1hs *psp, const size_t *sp) { int status; if (psp->version == 5) /* all integers in CDF-5 are 64 bits */ status = check_v1hs(psp, X_SIZEOF_INT64); else status = check_v1hs(psp, X_SIZEOF_SIZE_T); if(status != NC_NOERR) return status; if (psp->version == 5) { unsigned long long tmp = (unsigned long long) (*sp); return ncx_put_uint64(&psp->pos, tmp); } else return ncx_put_size_t(&psp->pos, sp); } /* Read a size_t from the header */ static int v1h_get_size_t(v1hs *gsp, size_t *sp) { int status; if (gsp->version == 5) /* all integers in CDF-5 are 64 bits */ status = check_v1hs(gsp, X_SIZEOF_INT64); else status = check_v1hs(gsp, X_SIZEOF_SIZE_T); if(status != NC_NOERR) return status; if (gsp->version == 5) { unsigned long long tmp=0; status = ncx_get_uint64((const void **)(&gsp->pos), &tmp); *sp = (size_t)tmp; return status; } else return ncx_get_size_t((const void **)(&gsp->pos), sp); } /* Begin nc_type */ #define X_SIZEOF_NC_TYPE X_SIZEOF_INT /* Write a nc_type to the header */ static int v1h_put_nc_type(v1hs *psp, const nc_type *typep) { const unsigned int itype = (unsigned int) *typep; int status = check_v1hs(psp, X_SIZEOF_INT); if(status != NC_NOERR) return status; status = ncx_put_uint32(&psp->pos, itype); return status; } /* Read a nc_type from the header */ static int v1h_get_nc_type(v1hs *gsp, nc_type *typep) { unsigned int type = 0; int status = check_v1hs(gsp, X_SIZEOF_INT); if(status != NC_NOERR) return status; status = ncx_get_uint32((const void**)(&gsp->pos), &type); if(status != NC_NOERR) return status; /* Fix 35382 assert(type == NC_BYTE || type == NC_CHAR || type == NC_SHORT || type == NC_INT || type == NC_FLOAT || type == NC_DOUBLE || type == NC_UBYTE || type == NC_USHORT || type == NC_UINT || type == NC_INT64 || type == NC_UINT64 || type == NC_STRING); */ if(type == NC_NAT || type > NC_MAX_ATOMIC_TYPE) return NC_EINVAL; else *typep = (nc_type) type; return NC_NOERR; } /* End nc_type */ /* Begin NCtype (internal tags) */ #define X_SIZEOF_NCTYPE X_SIZEOF_INT /* Write a NCtype to the header */ static int v1h_put_NCtype(v1hs *psp, NCtype type) { const unsigned int itype = (unsigned int) type; int status = check_v1hs(psp, X_SIZEOF_INT); if(status != NC_NOERR) return status; status = ncx_put_uint32(&psp->pos, itype); return status; } /* Read a NCtype from the header */ static int v1h_get_NCtype(v1hs *gsp, NCtype *typep) { unsigned int type = 0; int status = check_v1hs(gsp, X_SIZEOF_INT); if(status != NC_NOERR) return status; status = ncx_get_uint32((const void**)(&gsp->pos), &type); if(status != NC_NOERR) return status; /* else */ *typep = (NCtype) type; return NC_NOERR; } /* End NCtype */ /* Begin NC_string */ /* * How much space will the xdr'd string take. * Formerly NC_xlen_string(cdfstr) */ static size_t ncx_len_NC_string(const NC_string *ncstrp, int version) { size_t sz = (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_INT; /* nchars */ assert(ncstrp != NULL); if(ncstrp->nchars != 0) { #if 0 assert(ncstrp->nchars % X_ALIGN == 0); sz += ncstrp->nchars; #else sz += _RNDUP(ncstrp->nchars, X_ALIGN); #endif } return sz; } /* Write a NC_string to the header */ static int v1h_put_NC_string(v1hs *psp, const NC_string *ncstrp) { int status; #if 0 assert(ncstrp->nchars % X_ALIGN == 0); #endif status = v1h_put_size_t(psp, &ncstrp->nchars); if(status != NC_NOERR) return status; status = check_v1hs(psp, _RNDUP(ncstrp->nchars, X_ALIGN)); if(status != NC_NOERR) return status; status = ncx_pad_putn_text(&psp->pos, ncstrp->nchars, ncstrp->cp); if(status != NC_NOERR) return status; return NC_NOERR; } /* Read a NC_string from the header */ static int v1h_get_NC_string(v1hs *gsp, NC_string **ncstrpp) { int status = 0; size_t nchars = 0; NC_string *ncstrp = NULL; #if USE_STRICT_NULL_BYTE_HEADER_PADDING size_t padding = 0; #endif /* USE_STRICT_NULL_BYTE_HEADER_PADDING */ status = v1h_get_size_t(gsp, &nchars); if(status != NC_NOERR) return status; ncstrp = new_NC_string(nchars, NULL); if(ncstrp == NULL) { return NC_ENOMEM; } #if 0 /* assert(ncstrp->nchars == nchars || ncstrp->nchars - nchars < X_ALIGN); */ assert(ncstrp->nchars % X_ALIGN == 0); status = check_v1hs(gsp, ncstrp->nchars); #else status = check_v1hs(gsp, _RNDUP(ncstrp->nchars, X_ALIGN)); #endif if(status != NC_NOERR) goto unwind_alloc; status = ncx_pad_getn_text((const void **)(&gsp->pos), nchars, ncstrp->cp); if(status != NC_NOERR) goto unwind_alloc; #if USE_STRICT_NULL_BYTE_HEADER_PADDING padding = _RNDUP(X_SIZEOF_CHAR * ncstrp->nchars, X_ALIGN) - X_SIZEOF_CHAR * ncstrp->nchars; if (padding > 0) { /* CDF specification: Header padding uses null (\x00) bytes. */ char pad[X_ALIGN-1]; memset(pad, 0, X_ALIGN-1); if (memcmp((char*)gsp->pos-padding, pad, padding) != 0) { free_NC_string(ncstrp); return NC_ENULLPAD; } } #endif *ncstrpp = ncstrp; return NC_NOERR; unwind_alloc: free_NC_string(ncstrp); return status; } /* End NC_string */ /* Begin NC_dim */ /* * How much space will the xdr'd dim take. * Formerly NC_xlen_dim(dpp) */ static size_t ncx_len_NC_dim(const NC_dim *dimp, int version) { size_t sz; assert(dimp != NULL); sz = ncx_len_NC_string(dimp->name, version); sz += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; return(sz); } /* Write a NC_dim to the header */ static int v1h_put_NC_dim(v1hs *psp, const NC_dim *dimp) { int status; status = v1h_put_NC_string(psp, dimp->name); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &dimp->size); if(status != NC_NOERR) return status; return NC_NOERR; } /* Read a NC_dim from the header */ static int v1h_get_NC_dim(v1hs *gsp, NC_dim **dimpp) { int status; NC_string *ncstrp; NC_dim *dimp; status = v1h_get_NC_string(gsp, &ncstrp); if(status != NC_NOERR) return status; dimp = new_x_NC_dim(ncstrp); if(dimp == NULL) { status = NC_ENOMEM; goto unwind_name; } status = v1h_get_size_t(gsp, &dimp->size); if(status != NC_NOERR) { free_NC_dim(dimp); /* frees name */ return status; } *dimpp = dimp; return NC_NOERR; unwind_name: free_NC_string(ncstrp); return status; } /* How much space in the header is required for this NC_dimarray? */ static size_t ncx_len_NC_dimarray(const NC_dimarray *ncap, int version) { size_t xlen = X_SIZEOF_NCTYPE; /* type */ xlen += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; /* count */ if(ncap == NULL) return xlen; /* else */ { const NC_dim **dpp = (const NC_dim **)ncap->value; if (dpp) { const NC_dim *const *const end = &dpp[ncap->nelems]; for( /*NADA*/; dpp < end; dpp++) { xlen += ncx_len_NC_dim(*dpp,version); } } } return xlen; } /* Write a NC_dimarray to the header */ static int v1h_put_NC_dimarray(v1hs *psp, const NC_dimarray *ncap) { int status; assert(psp != NULL); if(ncap == NULL #if 1 /* Backward: * This clause is for 'byte for byte' * backward compatibility. * Strickly speaking, it is 'bug for bug'. */ || ncap->nelems == 0 #endif ) { /* * Handle empty netcdf */ const size_t nosz = 0; status = v1h_put_NCtype(psp, NC_UNSPECIFIED); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &nosz); if(status != NC_NOERR) return status; return NC_NOERR; } /* else */ status = v1h_put_NCtype(psp, NC_DIMENSION); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &ncap->nelems); if(status != NC_NOERR) return status; { const NC_dim **dpp = (const NC_dim **)ncap->value; const NC_dim *const *const end = &dpp[ncap->nelems]; for( /*NADA*/; dpp < end; dpp++) { status = v1h_put_NC_dim(psp, *dpp); if(status) return status; } } return NC_NOERR; } /* Read a NC_dimarray from the header */ static int v1h_get_NC_dimarray(v1hs *gsp, NC_dimarray *ncap) { int status; NCtype type = NC_UNSPECIFIED; assert(gsp != NULL && gsp->pos != NULL); assert(ncap != NULL); assert(ncap->value == NULL); status = v1h_get_NCtype(gsp, &type); if(status != NC_NOERR) return status; status = v1h_get_size_t(gsp, &ncap->nelems); if(status != NC_NOERR) return status; if(ncap->nelems == 0) return NC_NOERR; /* else */ if(type != NC_DIMENSION) return EINVAL; ncap->value = (NC_dim **) calloc(1,ncap->nelems * sizeof(NC_dim *)); if(ncap->value == NULL) return NC_ENOMEM; ncap->nalloc = ncap->nelems; ncap->hashmap = NC_hashmapnew(ncap->nelems); { NC_dim **dpp = ncap->value; NC_dim *const *const end = &dpp[ncap->nelems]; for( /*NADA*/; dpp < end; dpp++) { status = v1h_get_NC_dim(gsp, dpp); if(status) { ncap->nelems = (size_t)(dpp - ncap->value); free_NC_dimarrayV(ncap); return status; } { uintptr_t dimid = (uintptr_t)(dpp - ncap->value); NC_hashmapadd(ncap->hashmap, dimid, (*dpp)->name->cp, strlen((*dpp)->name->cp)); } } } return NC_NOERR; } /* End NC_dim */ /* Begin NC_attr */ /* * How much space will 'attrp' take in external representation? * Formerly NC_xlen_attr(app) */ static size_t ncx_len_NC_attr(const NC_attr *attrp, int version) { size_t sz; assert(attrp != NULL); sz = ncx_len_NC_string(attrp->name, version); sz += X_SIZEOF_NC_TYPE; /* type */ sz += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; /* nelems */ sz += attrp->xsz; return(sz); } #undef MIN #define MIN(mm,nn) (((mm) < (nn)) ? (mm) : (nn)) /*----< ncmpix_len_nctype() >------------------------------------------------*/ /* return the length of external data type */ static size_t ncmpix_len_nctype(nc_type type) { switch(type) { case NC_BYTE: case NC_CHAR: case NC_UBYTE: return X_SIZEOF_CHAR; case NC_SHORT: return X_SIZEOF_SHORT; case NC_USHORT: return X_SIZEOF_USHORT; case NC_INT: return X_SIZEOF_INT; case NC_UINT: return X_SIZEOF_UINT; case NC_FLOAT: return X_SIZEOF_FLOAT; case NC_DOUBLE: return X_SIZEOF_DOUBLE; case NC_INT64: return X_SIZEOF_INT64; case NC_UINT64: return X_SIZEOF_UINT64; default: fprintf(stderr,"ncmpix_len_nctype bad type %d\n",type); assert(0); } return 0; } /* * Put the values of an attribute * The loop is necessary since attrp->nelems * could potentially be quite large. */ static int v1h_put_NC_attrV(v1hs *psp, const NC_attr *attrp) { int status = 0; const size_t perchunk = psp->extent; size_t remaining = attrp->xsz; void *value = attrp->xvalue; size_t nbytes = 0, padding = 0; assert(psp->extent % X_ALIGN == 0); do { nbytes = MIN(perchunk, remaining); status = check_v1hs(psp, nbytes); if(status != NC_NOERR) return status; if (value) { (void) memcpy(psp->pos, value, nbytes); value = (void *)((char *)value + nbytes); } psp->pos = (void *)((char *)psp->pos + nbytes); remaining -= nbytes; } while(remaining != 0); padding = attrp->xsz - ncmpix_len_nctype(attrp->type) * attrp->nelems; if (padding > 0) { /* CDF specification: Header padding uses null (\x00) bytes. */ memset((char*)psp->pos-padding, 0, padding); } return NC_NOERR; } /* Write a NC_attr to the header */ static int v1h_put_NC_attr(v1hs *psp, const NC_attr *attrp) { int status; status = v1h_put_NC_string(psp, attrp->name); if(status != NC_NOERR) return status; status = v1h_put_nc_type(psp, &attrp->type); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &attrp->nelems); if(status != NC_NOERR) return status; status = v1h_put_NC_attrV(psp, attrp); if(status != NC_NOERR) return status; return NC_NOERR; } /* * Get the values of an attribute * The loop is necessary since attrp->nelems * could potentially be quite large. */ static int v1h_get_NC_attrV(v1hs *gsp, NC_attr *attrp) { int status; const size_t perchunk = gsp->extent; size_t remaining = attrp->xsz; void *value = attrp->xvalue; size_t nget; #if USE_STRICT_NULL_BYTE_HEADER_PADDING size_t padding; #endif /* USE_STRICT_NULL_BYTE_HEADER_PADDING */ do { nget = MIN(perchunk, remaining); status = check_v1hs(gsp, nget); if(status != NC_NOERR) return status; if (value) { (void) memcpy(value, gsp->pos, nget); value = (void *)((signed char *)value + nget); } gsp->pos = (void*)((unsigned char *)gsp->pos + nget); remaining -= nget; } while(remaining != 0); #if USE_STRICT_NULL_BYTE_HEADER_PADDING padding = attrp->xsz - ncmpix_len_nctype(attrp->type) * attrp->nelems; if (padding > 0) { /* CDF specification: Header padding uses null (\x00) bytes. */ char pad[X_ALIGN-1]; memset(pad, 0, X_ALIGN-1); if (memcmp((char*)gsp->pos-padding, pad, (size_t)padding) != 0) return NC_ENULLPAD; } #endif return NC_NOERR; } /* Read a NC_attr from the header */ static int v1h_get_NC_attr(v1hs *gsp, NC_attr **attrpp) { NC_string *strp; int status; nc_type type; size_t nelems; NC_attr *attrp; status = v1h_get_NC_string(gsp, &strp); if(status != NC_NOERR) return status; status = v1h_get_nc_type(gsp, &type); if(status != NC_NOERR) goto unwind_name; status = v1h_get_size_t(gsp, &nelems); if(status != NC_NOERR) goto unwind_name; attrp = new_x_NC_attr(strp, type, nelems); if(attrp == NULL) { status = NC_ENOMEM; goto unwind_name; } status = v1h_get_NC_attrV(gsp, attrp); if(status != NC_NOERR) { free_NC_attr(attrp); /* frees strp */ return status; } *attrpp = attrp; return NC_NOERR; unwind_name: free_NC_string(strp); return status; } /* How much space in the header is required for this NC_attrarray? */ static size_t ncx_len_NC_attrarray(const NC_attrarray *ncap, int version) { size_t xlen = X_SIZEOF_NCTYPE; /* type */ xlen += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; /* count */ if(ncap == NULL) return xlen; /* else */ { const NC_attr **app = (const NC_attr **)ncap->value; if (app) { const NC_attr *const *const end = &app[ncap->nelems]; for( /*NADA*/; app < end; app++) { xlen += ncx_len_NC_attr(*app,version); } } } return xlen; } /* Write a NC_attrarray to the header */ static int v1h_put_NC_attrarray(v1hs *psp, const NC_attrarray *ncap) { int status; assert(psp != NULL); if(ncap == NULL #if 1 /* Backward: * This clause is for 'byte for byte' * backward compatibility. * Strickly speaking, it is 'bug for bug'. */ || ncap->nelems == 0 #endif ) { /* * Handle empty netcdf */ const size_t nosz = 0; status = v1h_put_NCtype(psp, NC_UNSPECIFIED); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &nosz); if(status != NC_NOERR) return status; return NC_NOERR; } /* else */ status = v1h_put_NCtype(psp, NC_ATTRIBUTE); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &ncap->nelems); if(status != NC_NOERR) return status; { const NC_attr **app = (const NC_attr **)ncap->value; const NC_attr *const *const end = &app[ncap->nelems]; for( /*NADA*/; app < end; app++) { status = v1h_put_NC_attr(psp, *app); if(status) return status; } } return NC_NOERR; } /* Read a NC_attrarray from the header */ static int v1h_get_NC_attrarray(v1hs *gsp, NC_attrarray *ncap) { int status; NCtype type = NC_UNSPECIFIED; assert(gsp != NULL && gsp->pos != NULL); assert(ncap != NULL); assert(ncap->value == NULL); status = v1h_get_NCtype(gsp, &type); if(status != NC_NOERR) return status; status = v1h_get_size_t(gsp, &ncap->nelems); if(status != NC_NOERR) return status; if(ncap->nelems == 0) return NC_NOERR; /* else */ if(type != NC_ATTRIBUTE) return EINVAL; ncap->value = (NC_attr **) malloc(ncap->nelems * sizeof(NC_attr *)); if(ncap->value == NULL) return NC_ENOMEM; ncap->nalloc = ncap->nelems; { NC_attr **app = ncap->value; NC_attr *const *const end = &app[ncap->nelems]; for( /*NADA*/; app < end; app++) { status = v1h_get_NC_attr(gsp, app); if(status) { ncap->nelems = (size_t)(app - ncap->value); free_NC_attrarrayV(ncap); return status; } } } return NC_NOERR; } /* End NC_attr */ /* Begin NC_var */ /* * How much space will the xdr'd var take. * Formerly NC_xlen_var(vpp) */ static size_t ncx_len_NC_var(const NC_var *varp, size_t sizeof_off_t, int version) { size_t sz; assert(varp != NULL); assert(sizeof_off_t != 0); sz = ncx_len_NC_string(varp->name, version); if (version == 5) { sz += X_SIZEOF_INT64; /* ndims */ sz += ncx_len_int64(varp->ndims); /* dimids */ } else { sz += X_SIZEOF_SIZE_T; /* ndims */ sz += ncx_len_int(varp->ndims); /* dimids */ } sz += ncx_len_NC_attrarray(&varp->attrs, version); sz += X_SIZEOF_NC_TYPE; /* nc_type */ sz += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; /* vsize */ sz += sizeof_off_t; /* begin */ return(sz); } /* Write a NC_var to the header */ static int v1h_put_NC_var(v1hs *psp, const NC_var *varp) { int status; size_t vsize; status = v1h_put_NC_string(psp, varp->name); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &varp->ndims); if(status != NC_NOERR) return status; if (psp->version == 5) { status = check_v1hs(psp, ncx_len_int64(varp->ndims)); if(status != NC_NOERR) return status; status = ncx_putn_longlong_int(&psp->pos, varp->ndims, varp->dimids, NULL); if(status != NC_NOERR) return status; } else { status = check_v1hs(psp, ncx_len_int(varp->ndims)); if(status != NC_NOERR) return status; status = ncx_putn_int_int(&psp->pos, varp->ndims, varp->dimids, NULL); if(status != NC_NOERR) return status; } status = v1h_put_NC_attrarray(psp, &varp->attrs); if(status != NC_NOERR) return status; status = v1h_put_nc_type(psp, &varp->type); if(status != NC_NOERR) return status; /* write vsize to header. * CDF format specification: The vsize field is actually redundant, because * its value may be computed from other information in the header. The * 32-bit vsize field is not large enough to contain the size of variables * that require more than 2^32 - 4 bytes, so 2^32 - 1 is used in the vsize * field for such variables. */ vsize = varp->len; if (varp->len > 4294967292UL && (psp->version == NC_FORMAT_CLASSIC || psp->version == NC_FORMAT_64BIT_OFFSET)) vsize = 4294967295UL; /* 2^32-1 */ status = v1h_put_size_t(psp, &vsize); if(status != NC_NOERR) return status; status = check_v1hs(psp, psp->version == 1 ? 4 : 8); /*begin*/ if(status != NC_NOERR) return status; status = ncx_put_off_t(&psp->pos, &varp->begin, psp->version == 1 ? 4 : 8); if(status != NC_NOERR) return status; return NC_NOERR; } /* Read a NC_var from the header */ static int v1h_get_NC_var(v1hs *gsp, NC_var **varpp) { NC_string *strp; int status; size_t ndims; NC_var *varp; status = v1h_get_NC_string(gsp, &strp); if(status != NC_NOERR) return status; status = v1h_get_size_t(gsp, &ndims); if(status != NC_NOERR) goto unwind_name; varp = new_x_NC_var(strp, ndims); if(varp == NULL) { status = NC_ENOMEM; goto unwind_name; } if (gsp->version == 5) { status = check_v1hs(gsp, ncx_len_int64(ndims)); if(status != NC_NOERR) goto unwind_alloc; status = ncx_getn_longlong_int((const void **)(&gsp->pos), ndims, varp->dimids); if(status != NC_NOERR) goto unwind_alloc; } else { status = check_v1hs(gsp, ncx_len_int(ndims)); if(status != NC_NOERR) goto unwind_alloc; status = ncx_getn_int_int((const void **)(&gsp->pos), ndims, varp->dimids); if(status != NC_NOERR) goto unwind_alloc; } status = v1h_get_NC_attrarray(gsp, &varp->attrs); if(status != NC_NOERR) goto unwind_alloc; status = v1h_get_nc_type(gsp, &varp->type); if(status != NC_NOERR) goto unwind_alloc; size_t tmp; status = v1h_get_size_t(gsp, &tmp); varp->len = tmp; if(status != NC_NOERR) goto unwind_alloc; status = check_v1hs(gsp, gsp->version == 1 ? 4 : 8); if(status != NC_NOERR) goto unwind_alloc; status = ncx_get_off_t((const void **)&gsp->pos, &varp->begin, gsp->version == 1 ? 4 : 8); if(status != NC_NOERR) goto unwind_alloc; *varpp = varp; return NC_NOERR; unwind_alloc: free_NC_var(varp); /* frees name */ return status; unwind_name: free_NC_string(strp); return status; } /* How much space in the header is required for this NC_vararray? */ static size_t ncx_len_NC_vararray(const NC_vararray *ncap, size_t sizeof_off_t, int version) { size_t xlen = X_SIZEOF_NCTYPE; /* type */ xlen += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; /* count */ if(ncap == NULL) return xlen; /* else */ { const NC_var **vpp = (const NC_var **)ncap->value; if (vpp) { const NC_var *const *const end = &vpp[ncap->nelems]; for( /*NADA*/; vpp < end; vpp++) { xlen += ncx_len_NC_var(*vpp, sizeof_off_t, version); } } } return xlen; } /* Write a NC_vararray to the header */ static int v1h_put_NC_vararray(v1hs *psp, const NC_vararray *ncap) { int status; assert(psp != NULL); if(ncap == NULL #if 1 /* Backward: * This clause is for 'byte for byte' * backward compatibility. * Strickly speaking, it is 'bug for bug'. */ || ncap->nelems == 0 #endif ) { /* * Handle empty netcdf */ const size_t nosz = 0; status = v1h_put_NCtype(psp, NC_UNSPECIFIED); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &nosz); if(status != NC_NOERR) return status; return NC_NOERR; } /* else */ status = v1h_put_NCtype(psp, NC_VARIABLE); if(status != NC_NOERR) return status; status = v1h_put_size_t(psp, &ncap->nelems); if(status != NC_NOERR) return status; { const NC_var **vpp = (const NC_var **)ncap->value; const NC_var *const *const end = &vpp[ncap->nelems]; for( /*NADA*/; vpp < end; vpp++) { status = v1h_put_NC_var(psp, *vpp); if(status) return status; } } return NC_NOERR; } /* Read a NC_vararray from the header */ static int v1h_get_NC_vararray(v1hs *gsp, NC_vararray *ncap) { int status; NCtype type = NC_UNSPECIFIED; assert(gsp != NULL && gsp->pos != NULL); assert(ncap != NULL); assert(ncap->value == NULL); status = v1h_get_NCtype(gsp, &type); if(status != NC_NOERR) return status; status = v1h_get_size_t(gsp, &ncap->nelems); if(status != NC_NOERR) return status; if(ncap->nelems == 0) return NC_NOERR; /* else */ if(type != NC_VARIABLE) return EINVAL; ncap->value = (NC_var **) calloc(1,ncap->nelems * sizeof(NC_var *)); if(ncap->value == NULL) return NC_ENOMEM; ncap->nalloc = ncap->nelems; ncap->hashmap = NC_hashmapnew(ncap->nelems); { NC_var **vpp = ncap->value; NC_var *const *const end = &vpp[ncap->nelems]; for( /*NADA*/; vpp < end; vpp++) { status = v1h_get_NC_var(gsp, vpp); if(status) { ncap->nelems = (size_t)(vpp - ncap->value); free_NC_vararrayV(ncap); return status; } { uintptr_t varid = (uintptr_t)(vpp - ncap->value); NC_hashmapadd(ncap->hashmap, varid, (*vpp)->name->cp, strlen((*vpp)->name->cp)); } } } return NC_NOERR; } /* End NC_var */ /* Begin NC */ /* * Recompute the shapes of all variables * Sets ncp->begin_var to start of first variable. * Sets ncp->begin_rec to start of first record variable. * Returns -1 on error. The only possible error is a reference * to a non existent dimension, which could occur for a corrupted * netcdf file. */ static int NC_computeshapes(NC3_INFO* ncp) { NC_var **vpp = (NC_var **)ncp->vars.value; NC_var *first_var = NULL; /* first "non-record" var */ NC_var *first_rec = NULL; /* first "record" var */ int status; ncp->begin_var = (off_t) ncp->xsz; ncp->begin_rec = (off_t) ncp->xsz; ncp->recsize = 0; if(ncp->vars.nelems == 0) return(0); if (vpp) { NC_var *const *const end = &vpp[ncp->vars.nelems]; for( /*NADA*/; vpp < end; vpp++) { status = NC_var_shape(*vpp, &ncp->dims); if(status != NC_NOERR) return(status); if(IS_RECVAR(*vpp)) { if(first_rec == NULL) first_rec = *vpp; ncp->recsize += (*vpp)->len; } else { if(first_var == NULL) first_var = *vpp; /* * Overwritten each time thru. * Usually overwritten in first_rec != NULL clause below. */ ncp->begin_rec = (*vpp)->begin + (off_t)(*vpp)->len; } } } if(first_rec != NULL) { if(ncp->begin_rec > first_rec->begin) return(NC_ENOTNC); /* not a netCDF file or corrupted */ ncp->begin_rec = first_rec->begin; /* * for special case of exactly one record variable, pack value */ if(ncp->recsize == first_rec->len) ncp->recsize = *first_rec->dsizes * first_rec->xsz; } if(first_var != NULL) { ncp->begin_var = first_var->begin; } else { ncp->begin_var = ncp->begin_rec; } if(ncp->begin_var <= 0 || ncp->xsz > (size_t)ncp->begin_var || ncp->begin_rec <= 0 || ncp->begin_var > ncp->begin_rec) return(NC_ENOTNC); /* not a netCDF file or corrupted */ return(NC_NOERR); } /* How much space in the header is required for the NC data structure? */ size_t ncx_len_NC(const NC3_INFO* ncp, size_t sizeof_off_t) { int version=1; size_t xlen = sizeof(ncmagic); assert(ncp != NULL); if (fIsSet(ncp->flags, NC_64BIT_DATA)) /* CDF-5 */ version = 5; else if (fIsSet(ncp->flags, NC_64BIT_OFFSET)) /* CDF-2 */ version = 2; xlen += (version == 5) ? X_SIZEOF_INT64 : X_SIZEOF_SIZE_T; /* numrecs */ xlen += ncx_len_NC_dimarray(&ncp->dims, version); xlen += ncx_len_NC_attrarray(&ncp->attrs, version); xlen += ncx_len_NC_vararray(&ncp->vars, sizeof_off_t, version); return xlen; } /* Write the file header */ int ncx_put_NC(const NC3_INFO* ncp, void **xpp, off_t offset, size_t extent) { int status = NC_NOERR; v1hs ps; /* the get stream */ assert(ncp != NULL); /* Initialize stream ps */ ps.nciop = ncp->nciop; ps.flags = RGN_WRITE; if (ncp->flags & NC_64BIT_DATA) ps.version = 5; else if (ncp->flags & NC_64BIT_OFFSET) ps.version = 2; else ps.version = 1; if(xpp == NULL) { /* * Come up with a reasonable stream read size. */ extent = ncp->xsz; if(extent <= ((ps.version==5)?MIN_NC5_XSZ:MIN_NC3_XSZ)) { /* first time read */ extent = ncp->chunk; /* Protection for when ncp->chunk is huge; * no need to read hugely. */ if(extent > 4096) extent = 4096; } else if(extent > ncp->chunk) extent = ncp->chunk; ps.offset = 0; ps.extent = extent; ps.base = NULL; ps.pos = ps.base; status = fault_v1hs(&ps, extent); if(status) return status; } else { ps.offset = offset; ps.extent = extent; ps.base = *xpp; ps.pos = ps.base; ps.end = (char *)ps.base + ps.extent; } if (ps.version == 5) status = ncx_putn_schar_schar(&ps.pos, sizeof(ncmagic5), ncmagic5, NULL); else if (ps.version == 2) status = ncx_putn_schar_schar(&ps.pos, sizeof(ncmagic), ncmagic, NULL); else status = ncx_putn_schar_schar(&ps.pos, sizeof(ncmagic1), ncmagic1, NULL); if(status != NC_NOERR) goto release; { const size_t nrecs = NC_get_numrecs(ncp); if (ps.version == 5) { unsigned long long tmp = (unsigned long long) nrecs; status = ncx_put_uint64(&ps.pos, tmp); } else status = ncx_put_size_t(&ps.pos, &nrecs); if(status != NC_NOERR) goto release; } assert((char *)ps.pos < (char *)ps.end); status = v1h_put_NC_dimarray(&ps, &ncp->dims); if(status != NC_NOERR) goto release; status = v1h_put_NC_attrarray(&ps, &ncp->attrs); if(status != NC_NOERR) goto release; status = v1h_put_NC_vararray(&ps, &ncp->vars); if(status != NC_NOERR) goto release; release: (void) rel_v1hs(&ps); return status; } /* Make the in-memory NC structure from reading the file header */ int nc_get_NC(NC3_INFO* ncp) { int status; v1hs gs; /* the get stream */ assert(ncp != NULL); /* Initialize stream gs */ gs.nciop = ncp->nciop; gs.offset = 0; /* beginning of file */ gs.extent = 0; gs.flags = 0; gs.version = 0; gs.base = NULL; gs.pos = gs.base; { /* * Come up with a reasonable stream read size. */ off_t filesize; size_t extent = ncp->xsz; if(extent <= ((fIsSet(ncp->flags, NC_64BIT_DATA))?MIN_NC5_XSZ:MIN_NC3_XSZ)) { status = ncio_filesize(ncp->nciop, &filesize); if(status) return status; if(filesize < sizeof(ncmagic)) { /* too small, not netcdf */ status = NC_ENOTNC; return status; } /* first time read */ extent = ncp->chunk; /* Protection for when ncp->chunk is huge; * no need to read hugely. */ if(extent > 4096) extent = 4096; if(extent > filesize) extent = (size_t)filesize; } else if(extent > ncp->chunk) extent = ncp->chunk; /* * Invalidate the I/O buffers to force a read of the header * region. */ status = ncio_sync(gs.nciop); if(status) return status; status = fault_v1hs(&gs, extent); if(status) return status; } /* get the header from the stream gs */ { /* Get & check magic number */ schar magic[sizeof(ncmagic)]; (void) memset(magic, 0, sizeof(magic)); status = ncx_getn_schar_schar( (const void **)(&gs.pos), sizeof(magic), magic); if(status != NC_NOERR) goto unwind_get; if(memcmp(magic, ncmagic, sizeof(ncmagic)-1) != 0) { status = NC_ENOTNC; goto unwind_get; } /* Check version number in last byte of magic */ if (magic[sizeof(ncmagic)-1] == 0x1) { gs.version = 1; } else if (magic[sizeof(ncmagic)-1] == 0x2) { gs.version = 2; fSet(ncp->flags, NC_64BIT_OFFSET); /* Now we support version 2 file access on non-LFS systems -- rkr */ #if 0 if (sizeof(off_t) != 8) { fprintf(stderr, "NETCDF WARNING: Version 2 file on 32-bit system.\n"); } #endif } else if (magic[sizeof(ncmagic)-1] == 0x5) { gs.version = 5; fSet(ncp->flags, NC_64BIT_DATA); } else { status = NC_ENOTNC; goto unwind_get; } } { size_t nrecs = 0; if (gs.version == 5) { unsigned long long tmp = 0; status = ncx_get_uint64((const void **)(&gs.pos), &tmp); nrecs = (size_t)tmp; } else status = ncx_get_size_t((const void **)(&gs.pos), &nrecs); if(status != NC_NOERR) goto unwind_get; NC_set_numrecs(ncp, nrecs); } assert((char *)gs.pos < (char *)gs.end); status = v1h_get_NC_dimarray(&gs, &ncp->dims); if(status != NC_NOERR) goto unwind_get; status = v1h_get_NC_attrarray(&gs, &ncp->attrs); if(status != NC_NOERR) goto unwind_get; status = v1h_get_NC_vararray(&gs, &ncp->vars); if(status != NC_NOERR) goto unwind_get; ncp->xsz = ncx_len_NC(ncp, (gs.version == 1) ? 4 : 8); status = NC_computeshapes(ncp); if(status != NC_NOERR) goto unwind_get; status = NC_check_vlens(ncp); if(status != NC_NOERR) goto unwind_get; status = NC_check_voffs(ncp); if(status != NC_NOERR) goto unwind_get; unwind_get: (void) rel_v1hs(&gs); return status; }