bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
h5das.cc
Go to the documentation of this file.
1// This file is part of hdf5_handler a HDF5 file handler for the OPeNDAP
2// data server.
3
4// Copyright (c) 2007-2023 The HDF Group, Inc. and OPeNDAP, Inc.
5//
6// This is free software; you can redistribute it and/or modify it under the
7// terms of the GNU Lesser General Public License as published by the Free
8// Software Foundation; either version 2.1 of the License, or (at your
9// option) any later version.
10//
11// This software is distributed in the hope that it will be useful, but
12// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14// License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public
17// License along with this library; if not, write to the Free Software
18// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19//
20// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
21// You can contact The HDF Group, Inc. at 410 E University Ave,
22// Suite 200, Champaign, IL 61820
23
38#include <memory>
39#include "hdf5_handler.h"
40#include "HDF5RequestHandler.h"
41#include <BESDebug.h>
42
43using namespace std;
44using namespace libdap;
45
48
64void depth_first(hid_t pid, const char *gname, DAS & das)
65{
67 int slinkindex = 0;
68
69 // Although HDF5 comments are rarely used, we still keep this
70 // function.
71 read_comments(das, gname, pid);
72
73 H5G_info_t g_info;
74 hsize_t nelems;
75
76 if (H5Gget_info(pid, &g_info) < 0) {
77 string msg = "h5_das handler: unable to obtain the HDF5 group info. for ";
78 msg += gname;
79 throw InternalErr(__FILE__, __LINE__, msg);
80 }
81 nelems = g_info.nlinks;
82
83 ssize_t oname_size = 0;
84 for (hsize_t i = 0; i < nelems; i++) {
85
86 // Query the length of object name.
87 oname_size = H5Lget_name_by_idx(pid, ".", H5_INDEX_NAME, H5_ITER_NATIVE, i, nullptr, (size_t) DODS_NAMELEN,
88 H5P_DEFAULT);
89
90 if (oname_size <= 0) {
91 string msg = "hdf5 object name error from: ";
92 msg += gname;
93 throw InternalErr(__FILE__, __LINE__, msg);
94 }
95 // Obtain the name of the object.
96 vector<char> oname(oname_size + 1);
97 if (H5Lget_name_by_idx(pid, ".", H5_INDEX_NAME, H5_ITER_NATIVE, i, oname.data(), (size_t) (oname_size + 1),
98 H5P_DEFAULT) < 0) {
99 string msg = "hdf5 object name error from: ";
100 msg += gname;
101 throw InternalErr(__FILE__, __LINE__, msg);
102 }
103
104 // Check if it is the hard link or the soft link
105 H5L_info_t linfo;
106 if (H5Lget_info(pid, oname.data(), &linfo, H5P_DEFAULT) < 0) {
107 string msg = "hdf5 link name error from: ";
108 msg += gname;
109 throw InternalErr(__FILE__, __LINE__, msg);
110 }
111
112 // This is the soft link.
113 if (linfo.type == H5L_TYPE_SOFT) {
114 slinkindex++;
115 size_t val_size = linfo.u.val_size;
116 get_softlink(das, pid, gname, oname.data(), slinkindex, val_size);
117 continue;
118 }
119
120 // Obtain the object type
121 H5O_info_t oinfo;
122 if (H5OGET_INFO_BY_IDX(pid, ".", H5_INDEX_NAME, H5_ITER_NATIVE, i, &oinfo, H5P_DEFAULT) < 0) {
123 string msg = "Cannot obtain the object info ";
124 msg += gname;
125 throw InternalErr(__FILE__, __LINE__, msg);
126 }
127 H5O_type_t obj_type = oinfo.type;
128
129 switch (obj_type) {
130
131 case H5O_TYPE_GROUP: {
132
133 BESDEBUG("h5", "=depth_first():H5G_GROUP " << oname.data() << endl);
134
135 // This function will store the HDF5 group hierarchy into an DAP attribute.
136 add_group_structure_info(das, gname, oname.data(), true);
137
138 string full_path_name = string(gname) + string(oname.data()) + "/";
139
140 hid_t cgroup = H5Gopen(pid, full_path_name.c_str(), H5P_DEFAULT);
141 if (cgroup < 0) {
142 string msg = "opening hdf5 group failed for ";
143 msg += full_path_name;
144 throw InternalErr(__FILE__, __LINE__, msg);
145 }
146
147 // Get the object info
148 H5O_info_t obj_info;
149 if (H5OGET_INFO(cgroup, &obj_info) < 0) {
150 H5Gclose(cgroup);
151 string msg = "Obtaining the hdf5 group info. failed for ";
152 msg += full_path_name;
153 throw InternalErr(__FILE__, __LINE__, msg);
154 }
155
156 // Obtain the number of attributes
157 auto num_attr = (int)obj_info.num_attrs;
158 if (num_attr < 0) {
159 H5Gclose(cgroup);
160 string msg = "Fail to get the number of attributes for group ";
161 msg += full_path_name;
162 throw InternalErr(__FILE__, __LINE__, msg);
163 }
164
165 // Read all attributes in this group and map to DAS.
166 try {
167 read_objects(das, full_path_name.c_str(), cgroup, num_attr);
168 }
169 catch (...) {
170 H5Gclose(cgroup);
171 throw;
172 }
173
174 // Check if this group has been visited by using the hardlink
175 string oid = get_hardlink(cgroup, full_path_name.c_str());
176
177 // Break the cyclic loop created by hard links.
178 if (oid.empty()) { // The group has never been visited, go to the next level.
179 depth_first(cgroup, full_path_name.c_str(), das);
180 }
181 else {
182
183 // This group has been visited.
184 // Add the attribute table with the attribute name as HDF5_HARDLINK.
185 // The attribute value is the name of the group when it is first visited.
186 AttrTable *at = das.get_table(full_path_name);
187 if (!at) {
188 auto new_attr_table_unique = make_unique<libdap::AttrTable>();
189 at = das.add_table(full_path_name, new_attr_table_unique.release());
190 }
191
192 // Note that "paths" is a global object to find the visited path.
193 // It is defined at the beginning of this source code file.
194 at->append_attr("HDF5_HARDLINK", STRING, paths.get_name(oid));
195 }
196
197 if (H5Gclose(cgroup) < 0) {
198 throw InternalErr(__FILE__, __LINE__, "H5Gclose() failed.");
199 }
200 break;
201 } // case H5G_GROUP
202
203 case H5O_TYPE_DATASET: {
204
205 BESDEBUG("h5", "=depth_first():H5G_DATASET " << oname.data() << endl);
206
207 // This function will store the HDF5 group hierarchy into an DAP attribute.
208 add_group_structure_info(das, gname, oname.data(), false);
209
210 string full_path_name = string(gname) + string(oname.data());
211 hid_t dset = -1;
212
213 // Open the dataset
214 if ((dset = H5Dopen(pid, full_path_name.c_str(), H5P_DEFAULT)) < 0) {
215 string msg = "unable to open the hdf5 dataset of the group ";
216 msg += gname;
217 throw InternalErr(__FILE__, __LINE__, msg);
218 }
219
220 // Get the object info
221 H5O_info_t obj_info;
222 if (H5OGET_INFO(dset, &obj_info) < 0) {
223 H5Dclose(dset);
224 string msg = "Obtaining the info. failed for the dataset ";
225 msg += full_path_name;
226 throw InternalErr(__FILE__, __LINE__, msg);
227 }
228
229 // Obtain the number of attributes
230 auto num_attr = (int)(obj_info.num_attrs);
231 if (num_attr < 0) {
232 H5Dclose(dset);
233 string msg = "Fail to get the number of attributes for dataset ";
234 msg += full_path_name;
235 throw InternalErr(__FILE__, __LINE__, msg);
236 }
237
238 // Read all attributes in this dataset and map to DAS.
239 try {
240 read_objects(das, full_path_name, dset, num_attr);
241 }
242 catch (...) {
243 H5Dclose(dset);
244 throw;
245 }
246
247 string oid = get_hardlink(dset, full_path_name);
248
249 // Break the cyclic loop created by hard links
250
251 // If this HDF5 dataset has been visited,
252 // Add the DAS table with the attribute name as HDF5_HARDLINK.
253 // The attribute value is the name of the HDF5 dataset when it is first visited.
254 if (!oid.empty()) {
255 // Add attribute table with HARDLINK
256 AttrTable *at = das.get_table(full_path_name);
257 if (!at) {
258 auto new_attr_table_unique = make_unique<libdap::AttrTable>();
259 at = das.add_table(full_path_name, new_attr_table_unique.release());
260 }
261
262 // Note that "paths" is a global object to find the visited path.
263 // It is defined at the beginning of this source code file.
264 at->append_attr("HDF5_HARDLINK", STRING, paths.get_name(oid));
265 }
266
267 if (H5Dclose(dset) < 0) {
268 throw InternalErr(__FILE__, __LINE__, "Could not close the dataset.");
269 }
270 break;
271 } // case H5G_DATASET
272
273 case H5O_TYPE_NAMED_DATATYPE:
274 // ignore the named datatype
275 break;
276
277 default:
278 break;
279 }
280 } // end for
281
282 BESDEBUG("h5", "<depth_first():" << gname << endl);
283}
284
285
287// \fn read_objects(DAS & das, const string & varname, hid_t oid, int num_attr)
299void read_objects(DAS & das, const string & varname, hid_t oid, int num_attr)
300{
301
302 BESDEBUG("h5", ">read_objects():" << "varname=" << varname << " id=" << oid << endl);
303
304 // Prepare a variable for full path attribute.
305 string hdf5_path = HDF5_OBJ_FULLPATH;
306
307 // Obtain the DAS table of which the name is the variable name.
308 // If not finding the table, add a table of which the name is the variable name.
309 AttrTable *attr_table_ptr = das.get_table(varname);
310 if (!attr_table_ptr) {
311 BESDEBUG("h5", "=read_objects(): adding a table with name " << varname << endl);
312 auto new_attr_table_unique = make_unique<libdap::AttrTable>();
313 attr_table_ptr = das.add_table(varname, new_attr_table_unique.release());
314 }
315
316 // Add a DAP attribute that stores the HDF5 absolute path
317 attr_table_ptr->append_attr(hdf5_path.c_str(), STRING, varname);
318
319 // Check the number of attributes in this HDF5 object and
320 // put HDF5 attribute information into the DAS table.
321 string print_rep;
322 vector<char> temp_buf;
323
324 bool ignore_attr = false;
325 hid_t attr_id = -1;
326 for (int j = 0; j < num_attr; j++) {
327
328 // Obtain attribute information.
329 DSattr_t attr_inst;
330
331 // Ignore the attributes of which the HDF5 datatype
332 // cannot be mapped to DAP2. The ignored attribute datatypes can be found
333 // at function get_attr_info in h5get.cc.
334 attr_id = get_attr_info(oid, j, false, &attr_inst, &ignore_attr);
335 if (true == ignore_attr) {
336 H5Aclose(attr_id);
337 continue;
338 }
339
340 // Since HDF5 attribute may be in string datatype, it must be dealt
341 // properly. Get data type.
342 hid_t ty_id = H5Aget_type(attr_id);
343 string dap_type = get_dap_type(ty_id, false);
344 string attr_name = attr_inst.name;
345
346 bool is_utf8_str = false;
347 if (dap_type == STRING) {
348 H5T_cset_t c_set_type = H5Tget_cset(ty_id);
349 if (c_set_type < 0)
350 throw InternalErr(__FILE__, __LINE__, "Cannot get hdf5 character set type for the attribute.");
351 if (HDF5RequestHandler::get_escape_utf8_attr() == false && (c_set_type == H5T_CSET_UTF8))
352 is_utf8_str = true;
353 }
354
355 // We have to handle variable length string differently.
356 if (H5Tis_variable_str(ty_id))
357 write_vlen_str_attrs(attr_id,ty_id,&attr_inst,nullptr,attr_table_ptr,false);
358 else {
359 vector<char> value;
360 value.resize(attr_inst.need);
361 BESDEBUG("h5", "arttr_inst.need=" << attr_inst.need << endl);
362
363 hid_t memtype = H5Tget_native_type(ty_id, H5T_DIR_ASCEND);
364 // Read HDF5 attribute data.
365 if (H5Aread(attr_id, memtype, (void *) (value.data())) < 0)
366 throw InternalErr(__FILE__, __LINE__, "unable to read HDF5 attribute data");
367
368 H5Aclose(memtype);
369
370 // For scalar data, just read data once.
371 if (attr_inst.ndims == 0) {
372 for (int loc = 0; loc < (int) attr_inst.nelmts; loc++) {
373 print_rep = print_attr(ty_id, loc, value.data());
374 if (print_rep.c_str() != nullptr) {
375 if (is_utf8_str)
376 attr_table_ptr->append_attr(attr_name, dap_type, print_rep.c_str(),true);
377 else
378 attr_table_ptr->append_attr(attr_name, dap_type, print_rep.c_str());
379 }
380 }
381
382 }
383 else {
384 BESDEBUG("h5", "=read_objects(): ndims=" << (int) attr_inst. ndims << endl);
385
386 // Get the attribute datatype size
387 auto elesize = (int) H5Tget_size(ty_id);
388 if (elesize == 0) {
389 BESDEBUG("h5", "=read_objects(): elesize=0" << endl);
390 H5Tclose(ty_id);
391 H5Aclose(attr_id);
392 throw InternalErr(__FILE__, __LINE__, "unable to get attibute size");
393 }
394
395 // Due to the implementation of print_attr, the attribute value will be
396 // written one by one.
397 char *tempvalue = value.data();
398
399 // Write this value. the "loc" can always be set to 0 since
400 // tempvalue will be moved to the next value.
401 for (hsize_t temp_index = 0; temp_index < attr_inst.nelmts; temp_index++) {
402 print_rep = print_attr(ty_id, 0/*loc*/, tempvalue);
403 if (print_rep.c_str() != nullptr) {
404 if (is_utf8_str)
405 attr_table_ptr->append_attr(attr_name, dap_type, print_rep.c_str(),true);
406 else
407 attr_table_ptr->append_attr(attr_name, dap_type, print_rep.c_str());
408
409 tempvalue = tempvalue + elesize;
410
411 BESDEBUG("h5", "tempvalue=" << tempvalue << "elesize=" << elesize << endl);
412
413 }
414 else {
415 H5Tclose(ty_id);
416 H5Aclose(attr_id);
417 throw InternalErr(__FILE__, __LINE__, "unable to convert attibute value to DAP");
418 }
419 }
420 } // end if
421 }
422 if (H5Tclose(ty_id) < 0) {
423 H5Aclose(attr_id);
424 throw InternalErr(__FILE__, __LINE__, "unable to close HDF5 type id");
425 }
426 if (H5Aclose(attr_id) < 0) {
427 throw InternalErr(__FILE__, __LINE__, "unable to close attibute id");
428 }
429 } // end for
430 BESDEBUG("h5", "<read_objects()" << endl);
431}
432
444void find_gloattr(hid_t file, DAS & das)
445{
446 BESDEBUG("h5", ">find_gloattr()" << endl);
447
448 hid_t root = H5Gopen(file, "/", H5P_DEFAULT);
449 try {
450 if (root < 0) throw InternalErr(__FILE__, __LINE__, "unable to open the HDF5 root group");
451
452 // In the default option of the HDF5 handler, the
453 // HDF5 file structure(group hierarchy) will be mapped to
454 // a DAP attribute HDF5_ROOT_GROUP. In a sense, this created
455 // attribute can be treated as an HDF5 attribute under the root group,
456 // so to say, a global attribute.
457 auto new_attr_table_unique = make_unique<libdap::AttrTable>();
458 das.add_table("HDF5_ROOT_GROUP", new_attr_table_unique.release());
459
460 // Since the root group is the first HDF5 object to visit(in HDF5RequestHandler.cc, find_gloattr()
461 // is before the depth_first()), it will always be not visited. However, to find the cyclic groups
462 // to root, we still need to add the object name to the global variable name list "paths" defined at
463 // the beginning of the h5das.cc file.
464 get_hardlink(root, "/");
465
466 // Obtain the number of "real" attributes of the root group.
467 int num_attrs;
468 H5O_info_t obj_info;
469 if (H5OGET_INFO(root, &obj_info) < 0) {
470 H5Gclose(root);
471 string msg = "Obtaining the info. failed for the root group ";
472 throw InternalErr(__FILE__, __LINE__, msg);
473 }
474
475 // Obtain the number of attributes
476 num_attrs = obj_info.num_attrs;
477 if (num_attrs < 0) {
478 H5Gclose(root);
479 throw InternalErr(__FILE__, __LINE__, "unable to get the number of attributes for the HDF root group ");
480
481 }
482 if (num_attrs == 0) {
483 if (H5Gclose(root) < 0) {
484 throw InternalErr(__FILE__, __LINE__, "Could not close the group.");
485 }
486 BESDEBUG("h5", "<find_gloattr():no attributes" << endl);
487 return;
488 }
489
490 // Map the HDF5 root attributes to DAP and save it in a DAS table "H5_GLOBAL".
491 // In theory, we can just "/" as the table name. To help clients better understand,
492 // we use "H5_GLOBAL" which is a more meaningful name.
493 read_objects(das, "H5_GLOBAL", root, num_attrs);
494
495 BESDEBUG("h5", "=find_gloattr(): H5Gclose()" << endl);
496 if (H5Gclose(root) < 0) {
497 throw InternalErr(__FILE__, __LINE__, "Could not close the group.");
498 }
499 BESDEBUG("h5", "<find_gloattr()" << endl);
500 }
501 catch (...) {
502 if (H5Gclose(root) < 0) {
503 throw InternalErr(__FILE__, __LINE__, "Could not close the group.");
504 }
505 throw;
506 }
507}
508
523void get_softlink(DAS & das, hid_t pgroup, const char *gname, const string & oname, int index, size_t val_size)
524{
525 BESDEBUG("h5", ">get_softlink():" << oname << endl);
526
527 ostringstream oss;
528 oss << string("HDF5_SOFTLINK");
529 oss << "_";
530 oss << index;
531 string temp_varname = oss.str();
532
533
534 BESDEBUG("h5", "=get_softlink():" << temp_varname << endl);
535 AttrTable *attr_table_ptr = das.get_table(gname);
536 if (!attr_table_ptr) {
537 auto new_attr_table_unique = make_unique<libdap::AttrTable>();
538 attr_table_ptr = das.add_table(gname, new_attr_table_unique.release());
539 }
540 AttrTable *attr_softlink_ptr;
541 attr_softlink_ptr = attr_table_ptr->append_container(temp_varname);
542
543 string softlink_name = "linkname";
544 attr_softlink_ptr->append_attr(softlink_name, STRING, oname);
545 string softlink_value_name = "LINKTARGET";
546
547 // Get the link target information. We always return the link value in a string format.
548 vector<char>buf((val_size + 1) * sizeof(char));
549
550 // get link target name
551 if (H5Lget_val(pgroup, oname.c_str(), (void*) buf.data(), val_size + 1, H5P_DEFAULT) < 0)
552 throw InternalErr(__FILE__, __LINE__, "unable to get link value");
553 attr_softlink_ptr->append_attr(softlink_value_name, STRING, buf.data());
554}
555
569string get_hardlink(hid_t pgroup, const string & oname)
570{
571
572 BESDEBUG("h5", ">get_hardlink():" << oname << endl);
573
574 // Get the object info
575 H5O_info_t obj_info;
576 if (H5OGET_INFO(pgroup, &obj_info) < 0) {
577 throw InternalErr(__FILE__, __LINE__, "H5OGET_INFO() failed.");
578 }
579
580 // If the reference count is greater than 1,that means
581 // hard links are found. return the original object name this
582 // hard link points to.
583
584 if (obj_info.rc > 1) {
585 string objno;
586
587#if (H5_VERS_MAJOR == 1 && ((H5_VERS_MINOR == 12) || (H5_VERS_MINOR == 13) || (H5_VERS_MINOR ==14)))
588 char *obj_tok_str = nullptr;
589 if(H5Otoken_to_str(pgroup, &(obj_info.token), &obj_tok_str) <0) {
590 throw InternalErr(__FILE__, __LINE__, "H5Otoken_to_str failed.");
591 }
592 objno.assign(obj_tok_str,obj_tok_str+strlen(obj_tok_str));
593 H5free_memory(obj_tok_str);
594#else
595 ostringstream oss;
596 oss << hex << obj_info.addr;
597 objno = oss.str();
598#endif
599
600
601 BESDEBUG("h5", "=get_hardlink() objno=" << objno << endl);
602
603 if (!paths.add(objno, oname)) {
604 return objno;
605 }
606 else {
607 return "";
608 }
609 }
610 else {
611 return "";
612 }
613
614}
615
625void read_comments(DAS & das, const string & varname, hid_t oid)
626{
627
628 // Obtain the comment size
629 int comment_size;
630 comment_size = (int) (H5Oget_comment(oid, nullptr, 0));
631 if (comment_size < 0) {
632 throw InternalErr(__FILE__, __LINE__, "Could not retrieve the comment size.");
633 }
634
635 if (comment_size > 0) {
636 vector<char> comment;
637 comment.resize(comment_size + 1);
638 if (H5Oget_comment(oid, comment.data(), comment_size + 1) < 0) {
639 throw InternalErr(__FILE__, __LINE__, "Could not retrieve the comment.");
640 }
641
642 // Insert this comment into the das table.
643 AttrTable *at = das.get_table(varname);
644 if (!at) {
645 auto new_attr_table_unique = make_unique<libdap::AttrTable>();
646 at = das.add_table(varname, new_attr_table_unique.release());
647 }
648 at->append_attr("HDF5_COMMENT", STRING, comment.data());
649
650 }
651}
652
672void add_group_structure_info(DAS & das, const char *gname, const char *oname, bool is_group)
673{
674
675 string h5_spec_char("/");
676 string dap_notion(".");
677 string::size_type pos = 1;
678
679 if (gname == nullptr) {
680 throw InternalErr(__FILE__, __LINE__, "The wrong HDF5 group name.");
681 }
682
683 auto full_path = string(gname);
684
685 // Change the HDF5 special character '/' with DAP notion '.'
686 // to make sure the group structure can be handled by DAP properly.
687 while ((pos = full_path.find(h5_spec_char, pos)) != string::npos) {
688 full_path.replace(pos, h5_spec_char.size(), dap_notion);
689 pos++;
690 }
691
692 // If the HDF5 file includes only the root group, replacing
693 // the "/" with the string "HDF5_ROOT_GROUP".
694 // Otherwise, replacing the first "/" with the string "HDF5_ROOT_GROUP.",
695 // (note the . after "HDF5_ROOT_GROUP." . Then cutting the last "/".
696
697 if (strncmp(gname, "/", strlen(gname)) == 0) {
698 full_path.replace(0, 1, "HDF5_ROOT_GROUP");
699 }
700 else {
701 full_path.replace(0, 1, "HDF5_ROOT_GROUP.");
702 full_path = full_path.substr(0, full_path.size() - 1);
703 }
704
705 BESDEBUG("h5", full_path << endl);
706 // TODO: Not sure if we need to create a table for each group. KY 2015-07-08
707 AttrTable *at = das.get_table(full_path);
708 if (at == nullptr) {
709 throw InternalErr(__FILE__, __LINE__,
710 "Failed to add group structure information for " + full_path + " attribute table."
711 + "This happens when a group name has . character.");
712 }
713
714 // group will be mapped to a container
715 if (is_group) {
716 at->append_container(oname);
717 }
718 else {
719 at->append_attr("Dataset", "String", oname);
720 }
721}
722
include the entry functions to execute the handlers
HDF5PathFinder paths
A variable for remembering visited paths to break cyclic HDF5 groups.
Definition h5das.cc:47
void read_objects(DAS &das, const string &varname, hid_t oid, int num_attr)
Definition h5das.cc:299
string get_hardlink(hid_t pgroup, const string &oname)
Definition h5das.cc:569
void depth_first(hid_t pid, const char *gname, DAS &das)
Definition h5das.cc:64
void read_comments(DAS &das, const string &varname, hid_t oid)
Definition h5das.cc:625
void find_gloattr(hid_t file, DAS &das)
Definition h5das.cc:444
The main header of the HDF5 OPeNDAP handler.
const int DODS_NAMELEN
Maximum length of variable or attribute name(default option only).
const std::string HDF5_OBJ_FULLPATH
The special DAS attribute name for HDF5 path information from the top(root) group.
struct DSattr DSattr_t
A structure for DAS generation.
char name[DODS_NAMELEN]
Name of HDF5 group or dataset.
int ndims
Number of dimensions.
hsize_t nelmts
Number of elements.
hsize_t need
Memory space needed to hold nelmts type.