/* Copyright 2018, UCAR/Unidata See netcdf/COPYRIGHT file for copying * and redistribution conditions.*/ /** * @file * @internal The HDF4 file functions. These provide a read-only * interface to HDF4 SD files. * * @author Ed Hartnett */ #include "config.h" #include "nc4internal.h" #include "hdf4dispatch.h" #include #define NUM_TYPES 12 /**< Number of netCDF atomic types. */ extern int nc4_vararray_add(NC_GRP_INFO_T *grp, NC_VAR_INFO_T *var); /** @internal These flags may not be set for open mode. */ static const int ILLEGAL_OPEN_FLAGS = (NC_MMAP|NC_64BIT_OFFSET|NC_DISKLESS|NC_WRITE); /** @internal NetCDF atomic type names. */ static const char* nc_type_name_g[NUM_TYPES] = {"char", "byte", "short", "int", "float", "double", "ubyte", "ushort", "uint", "int64", "uint64", "string"}; /** @internal NetCDF atomic type sizes. */ static const size_t nc_type_size_g[NUM_TYPES] = {sizeof(char), sizeof(char), sizeof(short), sizeof(int), sizeof(float), sizeof(double), sizeof(unsigned char), sizeof(unsigned short), sizeof(unsigned int), sizeof(long long), sizeof(unsigned long long), sizeof(char *)}; /** * @internal Recursively delete the data for a group (and everything * it contains) in our internal metadata store. * * @param grp Pointer to group info struct. * * @return ::NC_NOERR No error. * @author Ed Hartnett */ static int hdf4_rec_grp_del(NC_GRP_INFO_T *grp) { NC_VAR_INFO_T *var; int i; assert(grp); LOG((3, "%s: grp->name %s", __func__, grp->hdr.name)); /* Delete all vars. */ for (i = 0; i < ncindexsize(grp->vars); i++) { NC_VAR_HDF4_INFO_T *hdf4_var; var = (NC_VAR_INFO_T *)ncindexith(grp->vars, i); assert(var); LOG((4, "%s: deleting var %s", __func__, var->hdr.name)); /* Get the HDF4 specific var metadata. */ hdf4_var = (NC_VAR_HDF4_INFO_T *)var->format_var_info; /* Close HDF4 dataset associated with this var, unless it's a * scale. */ if (hdf4_var->sdsid && SDendaccess(hdf4_var->sdsid) < 0) return NC_EHDFERR; nullfree(hdf4_var); } return NC_NOERR; } /** * @internal Given an HDF4 type, set a pointer to netcdf type. * * See http://www.hdfgroup.org/training/HDFtraining/UsersGuide/Fundmtls.fm3.html * for more information re: HDF4 types. * * @param h5 Pointer to HDF5 file info struct. * @param hdf4_typeid Type ID for hdf4 datatype. * @param xtype Pointer that gets netcdf type. Ignored if NULL. * @param endniannessp Pointer that gets endianness. Ignored if NULL. * @param type_sizep Pointer that gets type size. Ignored if NULL. * @param type_name Pointer that gets the type name. Ignored if NULL. * * @return ::NC_NOERR No error. * @author Ed Hartnett */ static int hdf4_type_info(NC_FILE_INFO_T *h5, int32 hdf4_typeid, nc_type* xtypep, int *endiannessp, size_t *type_sizep, char *type_name) { int t = 0; int endianness = NC_ENDIAN_BIG; nc_type xtype; assert(h5); switch(hdf4_typeid) { case DFNT_CHAR: xtype = NC_CHAR; t = 0; break; case DFNT_UCHAR: case DFNT_UINT8: xtype = NC_UBYTE; t = 6; break; case DFNT_LUINT8: xtype = NC_UBYTE; t = 6; endianness = NC_ENDIAN_LITTLE; break; case DFNT_INT8: xtype = NC_BYTE; t = 1; break; case DFNT_LINT8: xtype = NC_BYTE; t = 1; endianness = NC_ENDIAN_LITTLE; break; case DFNT_INT16: xtype = NC_SHORT; t = 2; break; case DFNT_LINT16: xtype = NC_SHORT; t = 2; endianness = NC_ENDIAN_LITTLE; break; case DFNT_UINT16: xtype = NC_USHORT; t = 7; break; case DFNT_LUINT16: xtype = NC_USHORT; t = 7; endianness = NC_ENDIAN_LITTLE; break; case DFNT_INT32: xtype = NC_INT; t = 3; break; case DFNT_LINT32: xtype = NC_INT; t = 3; endianness = NC_ENDIAN_LITTLE; break; case DFNT_UINT32: xtype = NC_UINT; t = 8; break; case DFNT_LUINT32: xtype = NC_UINT; t = 8; endianness = NC_ENDIAN_LITTLE; break; case DFNT_FLOAT32: xtype = NC_FLOAT; t = 4; break; case DFNT_LFLOAT32: xtype = NC_FLOAT; t = 4; endianness = NC_ENDIAN_LITTLE; break; case DFNT_FLOAT64: xtype = NC_DOUBLE; t = 5; break; case DFNT_LFLOAT64: xtype = NC_DOUBLE; t = 5; endianness = NC_ENDIAN_LITTLE; break; default: return NC_EBADTYPID; } /* Return results to caller. */ if (xtypep) *xtypep = xtype; if (endiannessp) *endiannessp = endianness; if (type_sizep) *type_sizep = nc_type_size_g[t]; if (type_name) strncpy(type_name, nc_type_name_g[t], NC_MAX_NAME); return NC_NOERR; } /** * @internal Set the type of a netCDF-4 variable. * * @param xtype A netcdf type. * @param endianness The endianness of the data. * @param type_size The size in bytes of one element of this type. * @param type_name A name for the type. * @param typep Pointer to a pointer that gets the TYPE_INFO_T struct. * * @return ::NC_NOERR No error. * @author Ed Hartnett */ static int nc4_set_var_type(nc_type xtype, int endianness, size_t type_size, char *type_name, NC_TYPE_INFO_T **typep) { NC_TYPE_INFO_T *type; /* Check inputs. */ assert(typep); /* Allocate space for the type info struct. */ if (!(type = calloc(1, sizeof(NC_TYPE_INFO_T)))) return NC_ENOMEM; if (!(type->hdr.name = strdup(type_name))) { free(type); return NC_ENOMEM; } type->hdr.sort = NCTYP; /* Determine the type class. */ if (xtype == NC_FLOAT) type->nc_type_class = NC_FLOAT; else if (xtype == NC_DOUBLE) type->nc_type_class = NC_DOUBLE; else if (xtype == NC_CHAR) type->nc_type_class = NC_STRING; else type->nc_type_class = NC_INT; /* Set other type info values. */ type->endianness = endianness; type->size = type_size; type->hdr.id = (size_t)xtype; /* Return to caller. */ *typep = type; return NC_NOERR; } /** * @internal Read an attribute from a HDF4 file. * * @param h5 Pointer to the file metadata struct. * @param var Pointer to variable metadata struct or NULL for global * attributes. * @param a Index of attribute to read. * * @return ::NC_NOERR No error. * @return ::NC_EHDFERR HDF4 error. * @return ::NC_EATTMETA Error reading HDF4 attribute. * @return ::NC_ENOMEM Out of memory. * @author Ed Hartnett */ static int hdf4_read_att(NC_FILE_INFO_T *h5, NC_VAR_INFO_T *var, int a) { NC_HDF4_FILE_INFO_T *hdf4_file; NC_ATT_INFO_T *att; NCindex *att_list; int32 att_data_type, att_count; size_t att_type_size; char name[NC_MAX_HDF4_NAME+1]; int sd_id; int retval; LOG((3, "%s: a %d var %s", __func__, a, var ? var->hdr.name : "global")); /* Check inputs. */ assert(h5 && h5->format_file_info); /* Get the HDF4 file info. */ hdf4_file = h5->format_file_info; /* Decide what att list to use, global or from a var. */ if (var) { NC_VAR_HDF4_INFO_T *hdf4_var; assert(var->format_var_info); att_list = var->att; hdf4_var = var->format_var_info; sd_id = hdf4_var->sdsid; } else { att_list = h5->root_grp->att; sd_id = hdf4_file->sdid; } /* Learn about this attribute. */ if (SDattrinfo(sd_id, a, name, &att_data_type, &att_count)) return NC_EATTMETA; /* Get information about the attribute type. */ nc_type xtype; if ((retval = hdf4_type_info(h5, att_data_type, &xtype, NULL, &att_type_size, NULL))) return retval; /* Add to the end of the list of atts for this var. */ if ((retval = nc4_att_list_add(att_list, name, &att))) return retval; att->nc_typeid = xtype; att->created = NC_TRUE; att->len = att_count; /* Allocate memory to hold the data. */ if (att->len) if (!(att->data = malloc(att_type_size * att->len))) return NC_ENOMEM; /* Read the data. */ if (SDreadattr(sd_id, a, att->data)) return NC_EHDFERR; return NC_NOERR; } /** * @internal Read a HDF4 dimension. As new dimensions are found, add * them to the metadata list of dimensions. * * @param h5 Pointer to the file metadata struct. * @param var Pointer to variable metadata struct or NULL for global * attributes. * @param rec_dim_len Actual length of first dim for this SD. * @param d Dimension index for this SD. * * @return ::NC_NOERR No error. * @return ::NC_EHDFERR HDF4 error. * @return ::NC_EDIMMETA Error reading HDF4 dimension info. * @return ::NC_ENOMEM Out of memory. * @return ::NC_EMAXNAME Name too long. * @author Ed Hartnett */ static int hdf4_read_dim(NC_FILE_INFO_T *h5, NC_VAR_INFO_T *var, int rec_dim_len, int d) { NC_VAR_HDF4_INFO_T *hdf4_var; NC_DIM_INFO_T *dim = NULL; int32 dimid, dim_len, dim_data_type, dim_num_attrs; char dim_name[NC_MAX_NAME + 1]; int i; int retval; assert(h5 && h5->format_file_info && var && var->format_var_info); hdf4_var = var->format_var_info; /* Get information about the dimension. */ if ((dimid = SDgetdimid(hdf4_var->sdsid, d)) == FAIL) return NC_EDIMMETA; if (SDdiminfo(dimid, dim_name, &dim_len, &dim_data_type, &dim_num_attrs)) return NC_EDIMMETA; if (strlen(dim_name) > NC_MAX_HDF4_NAME) return NC_EMAXNAME; /* Do we already have this dimension? HDF4 explicitly uses * the name to tell. */ for (i = 0; i < ncindexsize(h5->root_grp->dim); i++) { dim = (NC_DIM_INFO_T*)ncindexith(h5->root_grp->dim, i); if (!strcmp(dim->hdr.name, dim_name)) break; dim = NULL; } /* If we didn't find this dimension, add one. */ if (!dim) { LOG((4, "adding dim %s for dataset %s", dim_name, var->hdr.name)); if ((retval = nc4_dim_list_add(h5->root_grp, dim_name, (dim_len ? dim_len : rec_dim_len), -1, &dim))) return retval; } /* Tell the variable the id of this dimension. */ var->dimids[d] = dim->hdr.id; var->dim[d] = dim; return NC_NOERR; } /** * @internal Create a new variable and insert int relevant lists * * @param grp the containing group * @param name the name for the new variable * @param ndims the rank of the new variable * @param format_var_info Pointer to format-specific var info struct. * @param var Pointer in which to return a pointer to the new var. * * @return ::NC_NOERR No error. * @return ::NC_ENOMEM Out of memory. * @author Ed Hartnett */ static int nc4_var_list_add_full(NC_GRP_INFO_T* grp, const char* name, int ndims, nc_type xtype, int endianness, size_t type_size, char *type_name, void *fill_value, int contiguous, size_t *chunksizes, void *format_var_info, NC_VAR_INFO_T **var) { int d; int retval; /* Add the VAR_INFO_T struct to our list of vars. */ if ((retval = nc4_var_list_add(grp, name, ndims, var))) return retval; (*var)->created = NC_TRUE; (*var)->written_to = NC_TRUE; (*var)->format_var_info = format_var_info; (*var)->atts_read = 1; /* Fill special type_info struct for variable type information. */ if ((retval = nc4_set_var_type(xtype, endianness, type_size, type_name, &(*var)->type_info))) return retval; /* Propate the endianness to the variable */ (*var)->endianness = (*var)->type_info->endianness; (*var)->type_info->rc++; /* Handle fill value, if provided. */ if (fill_value) { if (!((*var)->fill_value = malloc(type_size))) return NC_ENOMEM; memcpy((*var)->fill_value, fill_value, type_size); } /* Var contiguous or chunked? */ if (contiguous) (*var)->storage = NC_CONTIGUOUS; else (*var)->storage = NC_CHUNKED; /* Were chunksizes provided? */ if (chunksizes) { if (!((*var)->chunksizes = malloc(ndims * sizeof(size_t)))) return NC_ENOMEM; for (d = 0; d < ndims; d++) (*var)->chunksizes[d] = chunksizes[d]; } return NC_NOERR; } /** * @internal Read a HDF4 variable, including its associated dimensions * and attributes. * * @param h5 Pointer to the file metadata struct. * @param v Index of variable to read. * * @return ::NC_NOERR No error. * @return ::NC_EHDFERR HDF4 error. * @return ::NC_EDIMMETA Error reading HDF4 dimension info. * @return ::NC_EVARMETA Error reading HDF4 dataset or att. * @return ::NC_EATTMETA Error reading HDF4 attribute. * @return ::NC_ENOMEM Out of memory. * @return ::NC_EMAXNAME Name too long. * @author Ed Hartnett */ static int hdf4_read_var(NC_FILE_INFO_T *h5, int v) { NC_HDF4_FILE_INFO_T *hdf4_file; NC_VAR_INFO_T *var; NC_VAR_HDF4_INFO_T *hdf4_var; HDF_CHUNK_DEF chunkdefs; int32 data_type, num_atts; int32 dimsize[NC_MAX_HDF4_DIMS]; int32 rec_dim_len; int32 rank; int32 sdsid; int contiguous; int d, a; int flag; char name[NC_MAX_HDF4_NAME+1]; int xtype; char type_name[NC_MAX_NAME + 1]; int endianness; size_t type_size; void *fill_value; size_t *chunksizes = NULL; int retval; /* Check inputs. */ assert(h5 && h5->format_file_info); /* Get HDF4 file metadata. */ hdf4_file = h5->format_file_info; /* Open this dataset in HDF4 file. */ if ((sdsid = SDselect(hdf4_file->sdid, v)) == FAIL) return NC_EVARMETA; /* Learn about this dataset. */ if (SDgetinfo(sdsid, name, &rank, dimsize, &data_type, &num_atts)) return NC_EVARMETA; rec_dim_len = dimsize[0]; /* Get chunking info from HDF4 file. */ if (SDgetchunkinfo(sdsid, &chunkdefs, &flag)) return NC_EVARMETA; /* Learn about the HDF4 type. */ if ((retval = hdf4_type_info(h5, data_type, &xtype, &endianness, &type_size, type_name))) return retval; /* Get the fill value. */ if (!(fill_value = malloc(type_size))) return NC_ENOMEM; if (SDgetfillvalue(sdsid, fill_value)) { /* Whoops! No fill value! */ free(fill_value); fill_value = NULL; } /* Is variable chunked or contiguous? */ if (flag == HDF_NONE) contiguous = NC_TRUE; else if (flag & HDF_CHUNK) { contiguous = NC_FALSE; if (!(chunksizes = malloc(rank * sizeof(size_t)))) return NC_ENOMEM; for (d = 0; d < rank; d++) chunksizes[d] = chunkdefs.chunk_lengths[d]; } /* Malloc a struct to hold HDF4-specific variable * information. */ if (!(hdf4_var = malloc(sizeof(NC_VAR_HDF4_INFO_T)))) return NC_ENOMEM; /* Remember these values. */ hdf4_var->hdf4_data_type = data_type; hdf4_var->sdsid = sdsid; /* Add a variable to metadata structures. */ LOG((3, "adding var for HDF4 dataset %s, rank %d netCDF type %d", name, rank, xtype)); retval = nc4_var_list_add_full(h5->root_grp, name, (int)rank, xtype, endianness, type_size, type_name, fill_value, contiguous, chunksizes, hdf4_var, &var); /* Free resources. */ if (chunksizes) free(chunksizes); if (fill_value) free(fill_value); /* Did the add fail? */ if (retval) { free(hdf4_var); return retval; } /* Find the variable's dimensions. */ for (d = 0; d < var->ndims; d++) if ((retval = hdf4_read_dim(h5, var, rec_dim_len, d))) return retval; /* Read the variable's attributes. */ for (a = 0; a < num_atts; a++) if ((retval = hdf4_read_att(h5, var, a))) return retval; return NC_NOERR; } /** * @internal Open a HDF4 SD file for read-only access. * * @param path The file name of the file. * @param mode The open mode flag. * @param basepe Ignored by this function. * @param chunksizehintp Ignored by this function. * @param parameters pointer to struct holding extra data (e.g. for * parallel I/O) layer. Ignored if NULL. Ignored by this function. * @param dispatch Pointer to the dispatch table for this file. * @param nc_file Pointer to an instance of NC. The ncid has already * been assigned, and is in nc_file->ext_ncid. * * @return ::NC_NOERR No error. * @return ::NC_EINVAL Invalid input. * @return ::NC_EHDFERR Error from HDF4 layer. * @return ::NC_ENOMEM Out of memory. * @author Ed Hartnett */ int NC_HDF4_open(const char *path, int mode, int basepe, size_t *chunksizehintp, void *parameters, const NC_Dispatch *dispatch, int ncid) { NC_FILE_INFO_T *h5; NC_HDF4_FILE_INFO_T *hdf4_file; NC *nc; int32 num_datasets, num_gatts; int32 sdid; int v, a; int retval; /* Check inputs. */ assert(path); LOG((1, "%s: path %s mode %d params %x", __func__, path, mode, parameters)); /* Find pointer to NC. */ if ((retval = NC_check_id(ncid, &nc))) return retval; /* Check the mode for validity */ if (mode & ILLEGAL_OPEN_FLAGS) return NC_EINVAL; /* Open the file and initialize SD interface. */ if ((sdid = SDstart(path, DFACC_READ)) == FAIL) return NC_EHDFERR; /* Learn how many datasets and global atts we have. */ if (SDfileinfo(sdid, &num_datasets, &num_gatts)) return NC_EHDFERR; /* Add necessary structs to hold netcdf-4 file data. */ if ((retval = nc4_file_list_add(ncid, path, mode, (void **)&h5))) return retval; assert(h5 && h5->root_grp); h5->no_write = NC_TRUE; h5->root_grp->atts_read = 1; /* Allocate data to hold HDF4 specific file data. */ if (!(hdf4_file = malloc(sizeof(NC_HDF4_FILE_INFO_T)))) return NC_ENOMEM; h5->format_file_info = hdf4_file; hdf4_file->sdid = sdid; /* Read the global atts. */ for (a = 0; a < num_gatts; a++) if ((retval = hdf4_read_att(h5, NULL, a))) break; /* Read each dataset. */ if (!retval) for (v = 0; v < num_datasets; v++) if ((retval = hdf4_read_var(h5, v))) break; /* If there is an error, free resources. */ if (retval) free(hdf4_file); #ifdef LOGGING /* This will print out the names, types, lens, etc of the vars and atts in the file, if the logging level is 2 or greater. */ log_metadata_nc(h5); #endif return retval; } /** * @internal Abort (close) the HDF4 file. * * @param ncid File ID. * * @return ::NC_NOERR No error. * @return ::NC_EBADID Bad ncid. * @return ::NC_EHDFERR Error from HDF4 layer. * @author Ed Hartnett */ int NC_HDF4_abort(int ncid) { return NC_HDF4_close(ncid, NULL); } /** * @internal Close the HDF4 file. * * @param ncid File ID. * @param ignore Ignore this pointer. * * @return ::NC_NOERR No error. * @return ::NC_EBADID Bad ncid. * @return ::NC_EHDFERR Error from HDF4 layer. * @author Ed Hartnett */ int NC_HDF4_close(int ncid, void *ignore) { NC_GRP_INFO_T *grp; NC *nc; NC_FILE_INFO_T *h5; NC_HDF4_FILE_INFO_T *hdf4_file; int retval; LOG((1, "%s: ncid 0x%x", __func__, ncid)); /* Find our metadata for this file. */ if ((retval = nc4_find_nc_grp_h5(ncid, &nc, &grp, &h5))) return retval; assert(nc && h5 && grp && !grp->parent); /* Clean up HDF4 specific allocations. */ if ((retval = hdf4_rec_grp_del(h5->root_grp))) return retval; /* Close hdf4 file and free HDF4 file info. */ hdf4_file = (NC_HDF4_FILE_INFO_T *)h5->format_file_info; if (SDend(hdf4_file->sdid)) return NC_EHDFERR; free(hdf4_file); /* Free the NC_FILE_INFO_T struct. */ if ((retval = nc4_nc4f_list_del(h5))) return retval; return NC_NOERR; }