1 /******************************************************************************
2  *
3  * Project:  Hierarchical Data Format Release 5 (HDF5)
4  * Purpose:  HDF5 Datasets. Open HDF5 file, fetch metadata and list of
5  *           subdatasets.
6  *           This driver initially based on code supplied by Markus Neteler
7  * Author:  Denis Nadeau <denis.nadeau@gmail.com>
8  *
9  ******************************************************************************
10  * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
11  * Copyright (c) 2008-2018, Even Rouault <even.rouault at spatialys.com>
12  *
13  * Permission is hereby granted, free of charge, to any person obtaining a
14  * copy of this software and associated documentation files (the "Software"),
15  * to deal in the Software without restriction, including without limitation
16  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17  * and/or sell copies of the Software, and to permit persons to whom the
18  * Software is furnished to do so, subject to the following conditions:
19  *
20  * The above copyright notice and this permission notice shall be included
21  * in all copies or substantial portions of the Software.
22  *
23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29  * DEALINGS IN THE SOFTWARE.
30  ****************************************************************************/
31 
32 #include "cpl_port.h"
33 
34 #include "hdf5_api.h"
35 #include "hdf5dataset.h"
36 #include "hdf5vfl.h"
37 
38 #include <algorithm>
39 #include <stdio.h>
40 #include <string.h>
41 #include <string>
42 
43 #include "cpl_conv.h"
44 #include "cpl_error.h"
45 #include "cpl_string.h"
46 #include "gdal.h"
47 #include "gdal_frmts.h"
48 #include "gdal_priv.h"
49 
50 CPL_CVSID("$Id: hdf5dataset.cpp 95a15ba5f7ec811536faa84323d9898ea404a555 2020-11-23 17:07:35 +0100 Even Rouault $")
51 
52 constexpr size_t MAX_METADATA_LEN = 32768;
53 
54 /************************************************************************/
55 /*                          HDF5GetFileDriver()                         */
56 /************************************************************************/
57 
HDF5GetFileDriver()58 hid_t HDF5GetFileDriver()
59 {
60     return HDF5VFLGetFileDriver();
61 }
62 
63 /************************************************************************/
64 /*                        HDF5UnloadFileDriver()                        */
65 /************************************************************************/
66 
HDF5UnloadFileDriver()67 void HDF5UnloadFileDriver()
68 {
69     HDF5VFLUnloadFileDriver();
70 }
71 
72 /************************************************************************/
73 /*                     HDF5DatasetDriverUnload()                        */
74 /************************************************************************/
75 
HDF5DatasetDriverUnload(GDALDriver *)76 static void HDF5DatasetDriverUnload(GDALDriver*)
77 {
78     HDF5UnloadFileDriver();
79 }
80 
81 /************************************************************************/
82 /* ==================================================================== */
83 /*                              HDF5Dataset                             */
84 /* ==================================================================== */
85 /************************************************************************/
86 
87 /************************************************************************/
88 /*                        GDALRegister_HDF5()                           */
89 /************************************************************************/
GDALRegister_HDF5()90 void GDALRegister_HDF5()
91 
92 {
93     if( GDALGetDriverByName("HDF5") != nullptr )
94         return;
95 
96     GDALDriver *poDriver = new GDALDriver();
97 
98     poDriver->SetDescription("HDF5");
99     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
100     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
101                               "Hierarchical Data Format Release 5");
102     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/hdf5.html");
103     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "h5 hdf5");
104     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
105     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
106 
107     poDriver->SetMetadataItem( GDAL_DCAP_MULTIDIM_RASTER, "YES" );
108 
109     poDriver->pfnOpen = HDF5Dataset::Open;
110     poDriver->pfnIdentify = HDF5Dataset::Identify;
111     poDriver->pfnUnloadDriver = HDF5DatasetDriverUnload;
112     GetGDALDriverManager()->RegisterDriver(poDriver);
113 
114 #ifdef HDF5_PLUGIN
115     GDALRegister_HDF5Image();
116     GDALRegister_BAG();
117 #endif
118 }
119 
120 /************************************************************************/
121 /*                           HDF5Dataset()                              */
122 /************************************************************************/
HDF5Dataset()123 HDF5Dataset::HDF5Dataset() :
124     hHDF5(-1),
125     hGroupID(-1),
126     papszSubDatasets(nullptr),
127     bIsHDFEOS(FALSE),
128     nDatasetType(-1),
129     nSubDataCount(0),
130     poH5RootGroup(nullptr),
131     papszMetadata(nullptr),
132     poH5CurrentObject(nullptr)
133 {}
134 
135 /************************************************************************/
136 /*                            ~HDF5Dataset()                            */
137 /************************************************************************/
~HDF5Dataset()138 HDF5Dataset::~HDF5Dataset()
139 {
140     CSLDestroy(papszMetadata);
141     if( hGroupID > 0 )
142         H5Gclose(hGroupID);
143     if( hHDF5 > 0 )
144         H5Fclose(hHDF5);
145 
146     CSLDestroy(papszSubDatasets);
147     if( poH5RootGroup != nullptr )
148     {
149         DestroyH5Objects(poH5RootGroup);
150         CPLFree(poH5RootGroup->pszName);
151         CPLFree(poH5RootGroup->pszPath);
152         CPLFree(poH5RootGroup->pszUnderscorePath);
153         CPLFree(poH5RootGroup->poHchild);
154         CPLFree(poH5RootGroup);
155     }
156 }
157 
158 /************************************************************************/
159 /*                            GetDataType()                             */
160 /*                                                                      */
161 /*      Transform HDF5 datatype to GDAL datatype                        */
162 /************************************************************************/
GetDataType(hid_t TypeID)163 GDALDataType HDF5Dataset::GetDataType(hid_t TypeID)
164 {
165     //Check for native types first
166     if (H5Tget_class(TypeID) != H5T_COMPOUND)
167     {
168 
169         if( H5Tequal(H5T_NATIVE_CHAR,        TypeID) )
170             return GDT_Byte;
171         else if( H5Tequal(H5T_NATIVE_SCHAR,  TypeID) )
172             return GDT_Byte;
173         else if( H5Tequal(H5T_NATIVE_UCHAR,  TypeID) )
174             return GDT_Byte;
175         else if( H5Tequal(H5T_NATIVE_SHORT,  TypeID) )
176             return GDT_Int16;
177         else if( H5Tequal(H5T_NATIVE_USHORT, TypeID) )
178             return GDT_UInt16;
179         else if( H5Tequal(H5T_NATIVE_INT,    TypeID) )
180             return GDT_Int32;
181         else if( H5Tequal(H5T_NATIVE_UINT,   TypeID) )
182             return GDT_UInt32;
183         else if( H5Tequal(H5T_NATIVE_LONG,   TypeID) )
184         {
185 #if SIZEOF_UNSIGNED_LONG == 4
186             return GDT_Int32;
187 #else
188             return GDT_Unknown;
189 #endif
190         }
191         else if( H5Tequal(H5T_NATIVE_ULONG,  TypeID) )
192         {
193 #if SIZEOF_UNSIGNED_LONG == 4
194             return GDT_UInt32;
195 #else
196             return GDT_Unknown;
197 #endif
198         }
199         else if( H5Tequal(H5T_NATIVE_FLOAT,  TypeID) )
200             return GDT_Float32;
201         else if( H5Tequal(H5T_NATIVE_DOUBLE, TypeID) )
202             return GDT_Float64;
203         else if( H5Tequal(H5T_NATIVE_LLONG,  TypeID) )
204             return GDT_Unknown;
205         else if( H5Tequal(H5T_NATIVE_ULLONG, TypeID) )
206             return GDT_Unknown;
207     }
208     else  //Parse compound type to determine if data is complex
209     {
210         //For complex the compound type must contain 2 elements
211         if ( H5Tget_nmembers(TypeID) != 2 )
212             return GDT_Unknown;
213 
214         //For complex the native types of both elements should be the same
215         hid_t ElemTypeID = H5Tget_member_type(TypeID, 0);
216         hid_t Elem2TypeID = H5Tget_member_type(TypeID, 1);
217         const bool bTypeEqual = H5Tequal( ElemTypeID, Elem2TypeID) > 0;
218         H5Tclose(Elem2TypeID);
219         if ( !bTypeEqual )
220         {
221             H5Tclose(ElemTypeID);
222             return GDT_Unknown;
223         }
224 
225         char* pszName1 = H5Tget_member_name(TypeID, 0);
226         const bool bIsReal = pszName1 && (pszName1[0] == 'r' ||pszName1[0] == 'R');
227         H5free_memory(pszName1);
228 
229         char* pszName2 = H5Tget_member_name(TypeID, 1);
230         const bool bIsImaginary = pszName2 && (pszName2[0] == 'i' ||pszName2[0] == 'I');
231         H5free_memory(pszName2);
232 
233         if( !bIsReal || !bIsImaginary)
234         {
235             H5Tclose(ElemTypeID);
236             return GDT_Unknown;
237         }
238 
239         //Check the native types to determine CInt16, CFloat32 or CFloat64
240         GDALDataType eDataType = GDT_Unknown;
241 
242         if ( H5Tequal(H5T_NATIVE_SHORT, ElemTypeID) )
243             eDataType = GDT_CInt16;
244         else if ( H5Tequal(H5T_NATIVE_INT, ElemTypeID) )
245             eDataType = GDT_CInt32;
246         else if ( H5Tequal(H5T_NATIVE_LONG, ElemTypeID) )
247         {
248 #if SIZEOF_UNSIGNED_LONG == 4
249             eDataType = GDT_CInt32;
250 #else
251             eDataType = GDT_Unknown;
252 #endif
253         }
254         else if ( H5Tequal(H5T_NATIVE_FLOAT, ElemTypeID) )
255             eDataType = GDT_CFloat32;
256         else if ( H5Tequal(H5T_NATIVE_DOUBLE, ElemTypeID) )
257             eDataType = GDT_CFloat64;
258 
259         //Close the data type
260         H5Tclose(ElemTypeID);
261 
262         return eDataType;
263     }
264 
265     return GDT_Unknown;
266 }
267 
268 /************************************************************************/
269 /*                          GetDataTypeName()                           */
270 /*                                                                      */
271 /*      Return the human readable name of data type                     */
272 /************************************************************************/
GetDataTypeName(hid_t TypeID)273 const char *HDF5Dataset::GetDataTypeName(hid_t TypeID)
274 {
275     //Check for native types first
276     if (H5Tget_class(TypeID) != H5T_COMPOUND)
277     {
278         if( H5Tequal(H5T_NATIVE_CHAR,        TypeID) )
279             return "8-bit character";
280         else if( H5Tequal(H5T_NATIVE_SCHAR,  TypeID) )
281             return "8-bit signed character";
282         else if( H5Tequal(H5T_NATIVE_UCHAR,  TypeID) )
283             return "8-bit unsigned character";
284         else if( H5Tequal(H5T_NATIVE_SHORT,  TypeID) )
285             return "16-bit integer";
286         else if( H5Tequal(H5T_NATIVE_USHORT, TypeID) )
287             return "16-bit unsigned integer";
288         else if( H5Tequal(H5T_NATIVE_INT,    TypeID) )
289             return "32-bit integer";
290         else if( H5Tequal(H5T_NATIVE_UINT,   TypeID) )
291             return "32-bit unsigned integer";
292         else if( H5Tequal(H5T_NATIVE_LONG,   TypeID) )
293             return "32/64-bit integer";
294         else if( H5Tequal(H5T_NATIVE_ULONG,  TypeID) )
295             return "32/64-bit unsigned integer";
296         else if( H5Tequal(H5T_NATIVE_FLOAT,  TypeID) )
297             return "32-bit floating-point";
298         else if( H5Tequal(H5T_NATIVE_DOUBLE, TypeID) )
299             return "64-bit floating-point";
300         else if( H5Tequal(H5T_NATIVE_LLONG,  TypeID) )
301             return "64-bit integer";
302         else if( H5Tequal(H5T_NATIVE_ULLONG, TypeID) )
303             return "64-bit unsigned integer";
304         else if( H5Tequal(H5T_NATIVE_DOUBLE, TypeID) )
305             return "64-bit floating-point";
306     }
307     else
308     {
309         //For complex the compound type must contain 2 elements
310         if ( H5Tget_nmembers(TypeID) != 2 )
311             return "Unknown";
312 
313         //For complex the native types of both elements should be the same
314         hid_t ElemTypeID = H5Tget_member_type(TypeID, 0);
315         hid_t Elem2TypeID = H5Tget_member_type(TypeID, 1);
316         const bool bTypeEqual = H5Tequal( ElemTypeID, Elem2TypeID) > 0;
317         H5Tclose(Elem2TypeID);
318         if ( !bTypeEqual )
319         {
320             H5Tclose(ElemTypeID);
321             return "Unknown";
322         }
323 
324         //Check the native types to determine CInt16, CFloat32 or CFloat64
325         if ( H5Tequal(H5T_NATIVE_SHORT, ElemTypeID) )
326         {
327             H5Tclose(ElemTypeID);
328             return "complex, 16-bit integer";
329         }
330         else if ( H5Tequal(H5T_NATIVE_INT, ElemTypeID) )
331         {
332             H5Tclose(ElemTypeID);
333             return "complex, 32-bit integer";
334         }
335         else if ( H5Tequal(H5T_NATIVE_LONG, ElemTypeID) )
336         {
337             H5Tclose(ElemTypeID);
338             return "complex, 32/64-bit integer";
339         }
340         else if ( H5Tequal(H5T_NATIVE_FLOAT, ElemTypeID) )
341         {
342             H5Tclose(ElemTypeID);
343             return "complex, 32-bit floating-point";
344         }
345         else if ( H5Tequal(H5T_NATIVE_DOUBLE, ElemTypeID) )
346         {
347             H5Tclose(ElemTypeID);
348             return "complex, 64-bit floating-point";
349         }
350     }
351 
352 
353     return "Unknown";
354 }
355 
356 /************************************************************************/
357 /*                              Identify()                              */
358 /************************************************************************/
359 
Identify(GDALOpenInfo * poOpenInfo)360 int HDF5Dataset::Identify( GDALOpenInfo * poOpenInfo )
361 
362 {
363     if( (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) &&
364         STARTS_WITH(poOpenInfo->pszFilename, "HDF5:") )
365     {
366         return TRUE;
367     }
368 
369     // Is it an HDF5 file?
370     constexpr char achSignature[] = "\211HDF\r\n\032\n";
371 
372     if( !poOpenInfo->pabyHeader )
373         return FALSE;
374 
375     const CPLString osExt(CPLGetExtension(poOpenInfo->pszFilename));
376 
377     const auto IsRecognizedByNetCDFDriver = [&osExt, poOpenInfo]()
378     {
379         if( (EQUAL(osExt, "NC") ||
380              EQUAL(osExt, "CDF") ||
381              EQUAL(osExt, "NC4")) &&
382             GDALGetDriverByName("netCDF") != nullptr )
383         {
384             const char *const apszAllowedDriver[] = { "netCDF", nullptr };
385             CPLPushErrorHandler(CPLQuietErrorHandler);
386             GDALDatasetH hDS = GDALOpenEx(poOpenInfo->pszFilename,
387                                           GDAL_OF_RASTER | GDAL_OF_VECTOR,
388                                           apszAllowedDriver, nullptr, nullptr);
389             CPLPopErrorHandler();
390             if( hDS )
391             {
392                 GDALClose(hDS);
393                 return true;
394             }
395         }
396         return false;
397     };
398 
399     if( memcmp(poOpenInfo->pabyHeader, achSignature, 8) == 0 )
400     {
401         // The tests to avoid opening KEA and BAG drivers are not
402         // necessary when drivers are built in the core lib, as they
403         // are registered after HDF5, but in the case of plugins, we
404         // cannot do assumptions about the registration order.
405 
406         // Avoid opening kea files if the kea driver is available.
407         if( EQUAL(osExt, "KEA") && GDALGetDriverByName("KEA") != nullptr )
408         {
409             return FALSE;
410         }
411 
412         // Avoid opening BAG files if the bag driver is available.
413         if( EQUAL(osExt, "BAG") && GDALGetDriverByName("BAG") != nullptr )
414         {
415             return FALSE;
416         }
417 
418         // Avoid opening NC files if the netCDF driver is available and
419         // they are recognized by it.
420         if( IsRecognizedByNetCDFDriver() )
421         {
422             return FALSE;
423         }
424 
425         return TRUE;
426     }
427 
428     if( memcmp(poOpenInfo->pabyHeader, "<HDF_UserBlock>", 15) == 0)
429     {
430         if( H5Fis_hdf5(poOpenInfo->pszFilename) )
431           return TRUE;
432     }
433 
434     // The HDF5 signature can be at offsets 512, 1024, 2048, etc.
435     if( poOpenInfo->fpL != nullptr &&
436         (EQUAL(osExt, "h5") || EQUAL(osExt, "hdf5") ||
437          EQUAL(osExt, "nc") || EQUAL(osExt, "cdf") || EQUAL(osExt, "nc4")) )
438     {
439         vsi_l_offset nOffset = 512;
440         for(int i = 0; i < 64; i++)
441         {
442             GByte abyBuf[8];
443             if( VSIFSeekL(poOpenInfo->fpL, nOffset, SEEK_SET) != 0 ||
444                 VSIFReadL(abyBuf, 1, 8, poOpenInfo->fpL) != 8 )
445             {
446                 break;
447             }
448             if( memcmp(abyBuf, achSignature, 8) == 0 )
449             {
450                 // Avoid opening NC files if the netCDF driver is available and
451                 // they are recognized by it.
452                 if( IsRecognizedByNetCDFDriver() )
453                 {
454                     return FALSE;
455                 }
456 
457                 return TRUE;
458             }
459             nOffset *= 2;
460         }
461     }
462 
463     return FALSE;
464 }
465 
466 /************************************************************************/
467 /*                         GDAL_HDF5Open()                              */
468 /************************************************************************/
GDAL_HDF5Open(const std::string & osFilename)469 hid_t GDAL_HDF5Open(const std::string& osFilename )
470 {
471     hid_t hHDF5;
472     // Heuristics to able datasets split over several files, using the 'family'
473     // driver. If passed the first file, and it contains a single 0, or
474     // ends up with 0.h5 or 0.hdf5, replace the 0 with %d and try the family driver.
475     if( std::count(osFilename.begin(), osFilename.end(), '0') == 1 ||
476         osFilename.find("0.h5") != std::string::npos ||
477         osFilename.find("0.hdf5") != std::string::npos )
478     {
479         const auto zero_pos = osFilename.rfind('0');
480         const auto osNewName = osFilename.substr(0, zero_pos) + "%d" + osFilename.substr(zero_pos+1);
481         hid_t fapl = H5Pcreate(H5P_FILE_ACCESS);
482         H5Pset_fapl_family(fapl, H5F_FAMILY_DEFAULT, H5P_DEFAULT);
483 #ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
484 #pragma GCC diagnostic push
485 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
486 #endif
487         H5E_BEGIN_TRY {
488             hHDF5 = H5Fopen(osNewName.c_str(), H5F_ACC_RDONLY, fapl);
489         } H5E_END_TRY;
490 #ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
491 #pragma GCC diagnostic pop
492 #endif
493         H5Pclose(fapl);
494         if( hHDF5 >= 0 )
495         {
496             CPLDebug("HDF5", "Actually opening %s with 'family' driver",
497                      osNewName.c_str());
498             return hHDF5;
499         }
500     }
501 
502     hid_t fapl = H5Pcreate(H5P_FILE_ACCESS);
503     H5Pset_driver(fapl, HDF5GetFileDriver(), nullptr);
504     hHDF5 = H5Fopen(osFilename.c_str(), H5F_ACC_RDONLY, fapl);
505     H5Pclose(fapl);
506     return hHDF5;
507 }
508 
509 /************************************************************************/
510 /*                                Open()                                */
511 /************************************************************************/
Open(GDALOpenInfo * poOpenInfo)512 GDALDataset *HDF5Dataset::Open( GDALOpenInfo *poOpenInfo )
513 {
514     if( !Identify(poOpenInfo) )
515         return nullptr;
516 
517     if( poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER )
518     {
519         return OpenMultiDim(poOpenInfo);
520     }
521 
522     // Create datasource.
523     HDF5Dataset *const poDS = new HDF5Dataset();
524 
525     poDS->SetDescription(poOpenInfo->pszFilename);
526 
527     // Try opening the dataset.
528     poDS->hHDF5 = GDAL_HDF5Open(poOpenInfo->pszFilename);
529     if( poDS->hHDF5 < 0 )
530     {
531         delete poDS;
532         return nullptr;
533     }
534 
535     poDS->hGroupID = H5Gopen(poDS->hHDF5, "/");
536     if( poDS->hGroupID < 0 )
537     {
538         poDS->bIsHDFEOS = false;
539         delete poDS;
540         return nullptr;
541     }
542 
543     poDS->bIsHDFEOS = true;
544     poDS->ReadGlobalAttributes(true);
545 
546     poDS->SetMetadata(poDS->papszMetadata);
547 
548     if( STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata, "mission_name", ""), "Sentinel 3") &&
549         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata, "altimeter_sensor_name", ""), "SRAL") &&
550         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata, "radiometer_sensor_name", ""), "MWR") &&
551         GDALGetDriverByName("netCDF") != nullptr )
552     {
553         delete poDS;
554         return nullptr;
555     }
556 
557     if ( CSLCount(poDS->papszSubDatasets) / 2 >= 1 )
558         poDS->SetMetadata(poDS->papszSubDatasets, "SUBDATASETS");
559 
560     // Make sure we don't try to do any pam stuff with this dataset.
561     poDS->nPamFlags |= GPF_NOSAVE;
562 
563     // If we have single subdataset only, open it immediately.
564     int nSubDatasets = CSLCount(poDS->papszSubDatasets) / 2;
565     if( nSubDatasets == 1 )
566     {
567         CPLString osDSName =
568             CSLFetchNameValue(poDS->papszSubDatasets, "SUBDATASET_1_NAME");
569         delete poDS;
570         return GDALDataset::Open(osDSName, poOpenInfo->nOpenFlags, nullptr,
571                                  poOpenInfo->papszOpenOptions, nullptr);
572     }
573     else
574     {
575         // Confirm the requested access is supported.
576         if( poOpenInfo->eAccess == GA_Update )
577         {
578             delete poDS;
579             CPLError(CE_Failure, CPLE_NotSupported,
580                      "The HDF5 driver does not support update access to "
581                      "existing datasets.");
582             return nullptr;
583         }
584     }
585     return poDS;
586 }
587 
588 /************************************************************************/
589 /*                          DestroyH5Objects()                          */
590 /*                                                                      */
591 /*      Erase all objects                                               */
592 /************************************************************************/
DestroyH5Objects(HDF5GroupObjects * poH5Object)593 void HDF5Dataset::DestroyH5Objects( HDF5GroupObjects *poH5Object )
594 {
595     // Visit all objects.
596     for( unsigned i = 0; i < poH5Object->nbObjs; i++ )
597         DestroyH5Objects(poH5Object->poHchild + i);
598 
599     if( poH5Object->poHparent ==nullptr )
600         return;
601 
602     // Erase some data.
603     CPLFree(poH5Object->paDims);
604     poH5Object->paDims = nullptr;
605 
606     CPLFree(poH5Object->pszPath);
607     poH5Object->pszPath = nullptr;
608 
609     CPLFree(poH5Object->pszName);
610     poH5Object->pszName = nullptr;
611 
612     CPLFree(poH5Object->pszUnderscorePath);
613     poH5Object->pszUnderscorePath = nullptr;
614 
615     if( poH5Object->native > 0 )
616         H5Tclose(poH5Object->native);
617     poH5Object->native = 0;
618 
619     // All Children are visited and can be deleted.
620     if( poH5Object->nbObjs != 0 )
621     {
622         CPLFree(poH5Object->poHchild);
623         poH5Object->poHchild = nullptr;
624     }
625 }
626 
627 /************************************************************************/
628 /*                             CreatePath()                             */
629 /*                                                                      */
630 /*      Find Dataset path for HDopen                                    */
631 /************************************************************************/
CreatePath(HDF5GroupObjects * poH5Object)632 static void CreatePath( HDF5GroupObjects *poH5Object )
633 {
634     // Recurse to the root path.
635     CPLString osPath;
636     if( poH5Object->poHparent != nullptr )
637     {
638         CreatePath(poH5Object->poHparent);
639         osPath = poH5Object->poHparent->pszPath;
640     }
641 
642     // Add name to the path.
643     if( !EQUAL(poH5Object->pszName, "/") )
644     {
645         osPath.append("/");
646         osPath.append(poH5Object->pszName);
647     }
648 
649     // Fill up path for each object.
650     CPLString osUnderscoreSpaceInName;
651     if( poH5Object->pszPath == nullptr )
652     {
653         // This is completely useless but needed if we want to keep
654         // subdataset names as they have "always" been formatted,
655         // with double slash at the beginning
656         if( osPath.empty() )
657             osPath = "/";
658 
659         // Change space for underscore.
660         char **papszPath =
661             CSLTokenizeString2(osPath.c_str(), " ", CSLT_HONOURSTRINGS);
662 
663         for( int i = 0; papszPath[i] != nullptr ; i++ )
664         {
665             if( i > 0 )
666                 osUnderscoreSpaceInName.append("_");
667             osUnderscoreSpaceInName.append(papszPath[i]);
668         }
669         CSLDestroy(papszPath);
670 
671         // -1 to give room for NUL in C strings.
672         constexpr size_t MAX_PATH = 8192 - 1;
673         // TODO(schwehr): Is it an issue if the results are longer than 8192?
674         // It appears that the output can never be longer than the source.
675         if( osUnderscoreSpaceInName.size() > MAX_PATH )
676             CPLError(CE_Fatal, CPLE_AppDefined,
677                      "osUnderscoreSpaceInName longer than MAX_PATH: "
678                      "%u > %u",
679                      static_cast<unsigned int>(osUnderscoreSpaceInName.size()),
680                      static_cast<unsigned int>(MAX_PATH));
681         if( osPath.size() > MAX_PATH )
682             CPLError(CE_Fatal, CPLE_AppDefined,
683                      "osPath longer than MAX_PATH: %u > %u",
684                      static_cast<unsigned int>(osPath.size()),
685                      static_cast<unsigned int>(MAX_PATH));
686 
687         poH5Object->pszUnderscorePath =
688             CPLStrdup(osUnderscoreSpaceInName.c_str());
689         poH5Object->pszPath = CPLStrdup(osPath.c_str());
690     }
691 }
692 
693 /************************************************************************/
694 /*                      HDF5GroupCheckDuplicate()                       */
695 /*                                                                      */
696 /*      Returns TRUE if an ancestor has the same objno[] as passed      */
697 /*      in - used to avoid looping in files with "links up" #(3218).    */
698 /************************************************************************/
699 
HDF5GroupCheckDuplicate(HDF5GroupObjects * poHparent,unsigned long * objno)700 static int HDF5GroupCheckDuplicate( HDF5GroupObjects *poHparent,
701                                     unsigned long *objno )
702 
703 {
704     while( poHparent != nullptr )
705     {
706         if( poHparent->objno[0] == objno[0] &&
707             poHparent->objno[1] == objno[1] )
708             return TRUE;
709 
710         poHparent = poHparent->poHparent;
711     }
712 
713     return FALSE;
714 }
715 
716 /************************************************************************/
717 /*                      HDF5CreateGroupObjs()                           */
718 /*                                                                      */
719 /*      Create HDF5 hierarchy into a linked list                        */
720 /************************************************************************/
HDF5CreateGroupObjs(hid_t hHDF5,const char * pszObjName,void * poHObjParent)721 herr_t HDF5CreateGroupObjs( hid_t hHDF5, const char *pszObjName,
722                             void *poHObjParent)
723 {
724     HDF5GroupObjects *const poHparent =
725         static_cast<HDF5GroupObjects *>(poHObjParent);
726     HDF5GroupObjects *poHchild = poHparent->poHchild;
727     H5G_stat_t oStatbuf;
728 
729     if( H5Gget_objinfo(hHDF5, pszObjName, FALSE, &oStatbuf) < 0  )
730         return -1;
731 
732     // Look for next child.
733     unsigned idx = 0;  // idx is used after the for loop.
734     for( ; idx < poHparent->nbObjs; idx++ )
735     {
736         if( poHchild->pszName == nullptr ) break;
737         poHchild++;
738     }
739 
740     if( idx == poHparent->nbObjs )
741         return -1;  // All children parsed.
742 
743     // Save child information.
744     poHchild->pszName = CPLStrdup(pszObjName);
745 
746     poHchild->nType = oStatbuf.type;
747     poHchild->nIndex = idx;
748     poHchild->poHparent = poHparent;
749     poHchild->nRank = 0;
750     poHchild->paDims = nullptr;
751     poHchild->HDatatype = 0;
752     poHchild->objno[0] = oStatbuf.objno[0];
753     poHchild->objno[1] = oStatbuf.objno[1];
754     if( poHchild->pszPath == nullptr )
755     {
756         CreatePath(poHchild);
757     }
758     if( poHparent->pszPath == nullptr )
759     {
760         CreatePath(poHparent);
761     }
762 
763     switch( oStatbuf.type )
764     {
765     case H5G_LINK:
766     {
767         poHchild->nbAttrs = 0;
768         poHchild->nbObjs = 0;
769         poHchild->poHchild = nullptr;
770         poHchild->nRank = 0;
771         poHchild->paDims = nullptr;
772         poHchild->HDatatype = 0;
773         break;
774     }
775     case H5G_GROUP:
776     {
777         hid_t hGroupID = H5I_INVALID_HID;  // Identifier of group.
778         if( (hGroupID = H5Gopen(hHDF5, pszObjName)) == -1)
779         {
780             CPLError(CE_Failure, CPLE_AppDefined,
781                      "unable to access \"%s\" group.", pszObjName);
782             return -1;
783         }
784         // Number of attributes in object.
785         const int nbAttrs = H5Aget_num_attrs(hGroupID);
786         hsize_t nbObjs = 0;  // Number of objects in a group.
787         H5Gget_num_objs(hGroupID, &nbObjs);
788         poHchild->nbAttrs = nbAttrs;
789         poHchild->nbObjs = static_cast<int>(nbObjs);
790         poHchild->nRank = 0;
791         poHchild->paDims = nullptr;
792         poHchild->HDatatype = 0;
793 
794         if( nbObjs > 0 )
795         {
796             poHchild->poHchild = static_cast<HDF5GroupObjects *>(
797                 CPLCalloc(static_cast<int>(nbObjs), sizeof(HDF5GroupObjects)));
798             memset(poHchild->poHchild, 0,
799                    static_cast<size_t>(sizeof(HDF5GroupObjects) * nbObjs));
800         }
801         else
802         {
803             poHchild->poHchild = nullptr;
804         }
805 
806         if( !HDF5GroupCheckDuplicate(poHparent, oStatbuf.objno) )
807             H5Giterate(hHDF5, pszObjName, nullptr, HDF5CreateGroupObjs, poHchild);
808         else
809             CPLDebug("HDF5", "avoiding link looping on node '%s'.", pszObjName);
810 
811         H5Gclose(hGroupID);
812         break;
813     }
814     case H5G_DATASET:
815     {
816         hid_t hDatasetID = H5I_INVALID_HID;  // Identifier of dataset.
817         if( (hDatasetID = H5Dopen(hHDF5, pszObjName)) == -1)
818         {
819             CPLError(CE_Failure, CPLE_AppDefined,
820                      "unable to access \"%s\" dataset.", pszObjName);
821             return -1;
822         }
823         const int nbAttrs = H5Aget_num_attrs(hDatasetID);
824         const hid_t datatype = H5Dget_type(hDatasetID);
825         const hid_t dataspace = H5Dget_space(hDatasetID);
826         const int n_dims = H5Sget_simple_extent_ndims(dataspace);
827         const hid_t native = H5Tget_native_type(datatype, H5T_DIR_ASCEND);
828         hsize_t *maxdims = nullptr;
829         hsize_t *dims = nullptr;
830 
831         if( n_dims > 0 )
832         {
833             dims = static_cast<hsize_t *>(CPLCalloc(n_dims, sizeof(hsize_t)));
834             maxdims =
835                 static_cast<hsize_t *>(CPLCalloc(n_dims, sizeof(hsize_t)));
836         }
837         H5Sget_simple_extent_dims(dataspace, dims, maxdims);
838         if( maxdims != nullptr )
839             CPLFree(maxdims);
840 
841         if( n_dims > 0 )
842         {
843             poHchild->nRank = n_dims;        // rank of the array
844             poHchild->paDims = dims;         // dimension of the array.
845             poHchild->HDatatype = datatype;  // HDF5 datatype
846         }
847         else
848         {
849             poHchild->nRank = -1;
850             poHchild->paDims = nullptr;
851             poHchild->HDatatype = 0;
852         }
853         poHchild->nbAttrs = nbAttrs;
854         poHchild->nbObjs = 0;
855         poHchild->poHchild = nullptr;
856         poHchild->native = native;
857         H5Tclose(datatype);
858         H5Sclose(dataspace);
859         H5Dclose(hDatasetID);
860         break;
861     }
862     case H5G_TYPE:
863     {
864         poHchild->nbAttrs = 0;
865         poHchild->nbObjs = 0;
866         poHchild->poHchild = nullptr;
867         poHchild->nRank = 0;
868         poHchild->paDims = nullptr;
869         poHchild->HDatatype = 0;
870         break;
871     }
872     default:
873         break;
874     }
875 
876     return 0;
877 }
878 
879 /************************************************************************/
880 /*                          HDF5AttrIterate()                           */
881 /************************************************************************/
882 
HDF5AttrIterate(hid_t hH5ObjID,const char * pszAttrName,void * pDS)883 static herr_t HDF5AttrIterate( hid_t hH5ObjID,
884                                const char *pszAttrName,
885                                // TODO(schwehr): void * -> HDF5Dataset *
886                                void *pDS )
887 {
888     char **papszTokens = nullptr;
889     CPLString osKey;
890     HDF5Dataset *const poDS = static_cast<HDF5Dataset *>(pDS);
891 
892     // Convert "/" into "_" for the path component
893     const char *pszPath = poDS->poH5CurrentObject->pszUnderscorePath;
894     if(pszPath != nullptr && strlen(pszPath) > 0)
895     {
896         papszTokens = CSLTokenizeString2(pszPath, "/", CSLT_HONOURSTRINGS);
897 
898         for( hsize_t i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; ++i )
899         {
900             if( i != 0)
901                 osKey += '_';
902             osKey += papszTokens[i];
903         }
904         CSLDestroy(papszTokens);
905     }
906 
907     // Convert whitespaces into "_" for the attribute name component
908     papszTokens = CSLTokenizeString2(
909         pszAttrName, " ", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
910     for( hsize_t i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; ++i )
911     {
912         if(!osKey.empty())
913             osKey += '_';
914         osKey += papszTokens[i];
915     }
916     CSLDestroy(papszTokens);
917 
918     const hid_t hAttrID = H5Aopen_name(hH5ObjID, pszAttrName);
919     const hid_t hAttrTypeID = H5Aget_type(hAttrID);
920     const hid_t hAttrNativeType =
921         H5Tget_native_type(hAttrTypeID, H5T_DIR_DEFAULT);
922     const hid_t hAttrSpace = H5Aget_space(hAttrID);
923 
924     if( H5Tget_class(hAttrNativeType) == H5T_VLEN )
925     {
926         H5Sclose(hAttrSpace);
927         H5Tclose(hAttrNativeType);
928         H5Tclose(hAttrTypeID);
929         H5Aclose(hAttrID);
930         return 0;
931     }
932 
933     hsize_t nSize[64] = {};
934     const unsigned int nAttrDims =
935         H5Sget_simple_extent_dims(hAttrSpace, nSize, nullptr);
936 
937     unsigned int nAttrElmts = 1;
938     for( hsize_t i = 0; i < nAttrDims; i++ )
939     {
940         nAttrElmts *= static_cast<int>(nSize[i]);
941     }
942 
943     char *szData = nullptr;
944     char *szValue = nullptr;
945 
946     if( H5Tget_class(hAttrNativeType) == H5T_STRING )
947     {
948         if ( H5Tis_variable_str(hAttrNativeType) )
949         {
950             char **papszStrings =
951                 static_cast<char **>(CPLMalloc(nAttrElmts * sizeof(char *)));
952 
953             // Read the values.
954             H5Aread(hAttrID, hAttrNativeType, papszStrings);
955 
956             // Concatenate all values as one string separated by a space.
957             CPLString osVal = papszStrings[0] ? papszStrings[0] : "{NULL}";
958             for( hsize_t i = 1; i < nAttrElmts; i++ )
959             {
960                 osVal += " ";
961                 osVal += papszStrings[i] ? papszStrings[i] : "{NULL}";
962             }
963 
964             szValue = static_cast<char *>(CPLMalloc(osVal.length() + 1));
965             strcpy(szValue, osVal.c_str());
966 
967             H5Dvlen_reclaim(hAttrNativeType, hAttrSpace, H5P_DEFAULT,
968                             papszStrings);
969             CPLFree(papszStrings);
970         }
971         else
972         {
973             const hsize_t nAttrSize = H5Aget_storage_size(hAttrID);
974             szValue = static_cast<char *>(CPLMalloc((size_t)(nAttrSize + 1)));
975             H5Aread(hAttrID, hAttrNativeType, szValue);
976             szValue[nAttrSize] = '\0';
977         }
978     }
979     else
980     {
981         const size_t nDataLen = 8192;
982         void *buf = nullptr;
983 
984         if( nAttrElmts > 0 )
985         {
986             buf = CPLMalloc(nAttrElmts * H5Tget_size(hAttrNativeType));
987             szData = static_cast<char *>(CPLMalloc(nDataLen));
988             szValue = static_cast<char *>(CPLMalloc(MAX_METADATA_LEN));
989             szData[0] = '\0';
990             szValue[0] = '\0';
991             H5Aread(hAttrID, hAttrNativeType, buf);
992         }
993         const bool bIsSCHAR = H5Tequal(H5T_NATIVE_SCHAR, hAttrNativeType) > 0;
994         const bool bIsUCHAR = H5Tequal(H5T_NATIVE_UCHAR, hAttrNativeType) > 0;
995         if( (bIsSCHAR || bIsUCHAR) &&
996             CPLTestBool(CPLGetConfigOption("GDAL_HDF5_CHAR_AS_STRING", "NO")) )
997         {
998             // Compatibility mode with ancient GDAL versions where we consider
999             // array of SCHAR/UCHAR as strings. Likely inappropriate mode...
1000             for( hsize_t i = 0; i < nAttrElmts; i++ )
1001             {
1002                 snprintf(szData, nDataLen, "%c",
1003                          static_cast<char *>(buf)[i]);
1004                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1005                     MAX_METADATA_LEN )
1006                     CPLError(CE_Warning, CPLE_OutOfMemory,
1007                              "Header data too long. Truncated");
1008             }
1009         }
1010         else if( bIsSCHAR )
1011         {
1012             for( hsize_t i = 0; i < nAttrElmts; i++ )
1013             {
1014                 snprintf(szData, nDataLen, "%d ",
1015                          static_cast<signed char *>(buf)[i]);
1016                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1017                     MAX_METADATA_LEN )
1018                     CPLError(CE_Warning, CPLE_OutOfMemory,
1019                              "Header data too long. Truncated");
1020             }
1021         }
1022         else if( bIsUCHAR )
1023         {
1024             for( hsize_t i = 0; i < nAttrElmts; i++ )
1025             {
1026                 snprintf(szData, nDataLen, "%u ",
1027                          static_cast<unsigned char *>(buf)[i]);
1028                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1029                     MAX_METADATA_LEN )
1030                     CPLError(CE_Warning, CPLE_OutOfMemory,
1031                              "Header data too long. Truncated");
1032             }
1033         }
1034         else if( H5Tequal(H5T_NATIVE_SHORT, hAttrNativeType) > 0 )
1035         {
1036             for( hsize_t i = 0; i < nAttrElmts; i++ )
1037             {
1038                 snprintf(szData, nDataLen, "%d ", static_cast<short *>(buf)[i]);
1039                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1040                     MAX_METADATA_LEN )
1041                     CPLError(CE_Warning, CPLE_OutOfMemory,
1042                              "Header data too long. Truncated");
1043             }
1044         }
1045         else if( H5Tequal(H5T_NATIVE_USHORT, hAttrNativeType) > 0 )
1046         {
1047             for( hsize_t i = 0; i < nAttrElmts; i++ )
1048             {
1049               snprintf(szData, nDataLen, "%u ",
1050                        static_cast<unsigned short *>(buf)[i]);
1051                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1052                     MAX_METADATA_LEN )
1053                     CPLError(CE_Warning, CPLE_OutOfMemory,
1054                              "Header data too long. Truncated");
1055             }
1056         }
1057         else if( H5Tequal(H5T_NATIVE_INT, hAttrNativeType) > 0 )
1058         {
1059             for( hsize_t i=0; i < nAttrElmts; i++ )
1060             {
1061                 snprintf(szData, nDataLen, "%d ", static_cast<int *>(buf)[i]);
1062                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1063                     MAX_METADATA_LEN )
1064                     CPLError(CE_Warning, CPLE_OutOfMemory,
1065                              "Header data too long. Truncated");
1066             }
1067         }
1068         else if( H5Tequal(H5T_NATIVE_UINT, hAttrNativeType) > 0 )
1069         {
1070             for( hsize_t i = 0; i < nAttrElmts; i++ )
1071             {
1072                 snprintf(szData, nDataLen, "%u ",
1073                          static_cast<unsigned int *>(buf)[i]);
1074                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1075                     MAX_METADATA_LEN )
1076                     CPLError(CE_Warning, CPLE_OutOfMemory,
1077                              "Header data too long. Truncated");
1078             }
1079         }
1080         else if( H5Tequal(H5T_NATIVE_LONG, hAttrNativeType) > 0 )
1081         {
1082             for( hsize_t i = 0; i < nAttrElmts; i++ )
1083             {
1084                 snprintf(szData, nDataLen, "%ld ", static_cast<long *>(buf)[i]);
1085                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1086                     MAX_METADATA_LEN )
1087                     CPLError(CE_Warning, CPLE_OutOfMemory,
1088                              "Header data too long. Truncated");
1089             }
1090         }
1091         else if( H5Tequal(H5T_NATIVE_ULONG, hAttrNativeType) > 0 )
1092         {
1093             for( hsize_t i = 0; i < nAttrElmts; i++ ) {
1094                 snprintf(szData, nDataLen, "%lu ",
1095                          static_cast<unsigned long *>(buf)[i]);
1096                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1097                     MAX_METADATA_LEN )
1098                     CPLError(CE_Warning, CPLE_OutOfMemory,
1099                              "Header data too long. Truncated");
1100             }
1101         }
1102         else if( H5Tequal(H5T_NATIVE_FLOAT, hAttrNativeType) > 0 )
1103         {
1104             for( hsize_t i = 0; i < nAttrElmts; i++ )
1105             {
1106                 CPLsnprintf(szData, nDataLen, "%.8g ",
1107                             static_cast<float *>(buf)[i]);
1108                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1109                     MAX_METADATA_LEN )
1110                     CPLError(CE_Warning, CPLE_OutOfMemory,
1111                              "Header data too long. Truncated");
1112             }
1113         }
1114         else if( H5Tequal(H5T_NATIVE_DOUBLE, hAttrNativeType) > 0 )
1115         {
1116             for( hsize_t i = 0; i < nAttrElmts; i++ )
1117             {
1118                 CPLsnprintf(szData, nDataLen, "%.15g ",
1119                             static_cast<double *>(buf)[i]);
1120                 if( CPLStrlcat(szValue, szData, MAX_METADATA_LEN) >=
1121                     MAX_METADATA_LEN )
1122                     CPLError(CE_Warning, CPLE_OutOfMemory,
1123                              "Header data too long. Truncated");
1124             }
1125         }
1126         CPLFree(buf);
1127     }
1128     H5Sclose(hAttrSpace);
1129     H5Tclose(hAttrNativeType);
1130     H5Tclose(hAttrTypeID);
1131     H5Aclose(hAttrID);
1132     poDS->papszMetadata = CSLSetNameValue(poDS->papszMetadata, osKey, szValue);
1133 
1134     CPLFree(szData);
1135     CPLFree(szValue);
1136 
1137     return 0;
1138 }
1139 
1140 /************************************************************************/
1141 /*                           CreateMetadata()                           */
1142 /************************************************************************/
CreateMetadata(HDF5GroupObjects * poH5Object,int nType)1143 CPLErr HDF5Dataset::CreateMetadata( HDF5GroupObjects *poH5Object, int nType)
1144 {
1145 
1146     if( !poH5Object->pszPath )
1147         return CE_None;
1148 
1149     poH5CurrentObject = poH5Object;
1150 
1151     if( EQUAL(poH5Object->pszPath, "") )
1152         return CE_None;
1153 
1154     HDF5Dataset *const poDS = this;
1155     const int nbAttrs = poH5Object->nbAttrs;
1156 
1157     switch( nType )
1158     {
1159     case H5G_GROUP:
1160         if( nbAttrs > 0 )
1161         {
1162             // Identifier of group.
1163             const hid_t l_hGroupID = H5Gopen(hHDF5, poH5Object->pszPath);
1164             H5Aiterate(l_hGroupID, nullptr, HDF5AttrIterate, poDS);
1165             H5Gclose(l_hGroupID);
1166         }
1167         break;
1168     case H5G_DATASET:
1169         if( nbAttrs > 0 )
1170         {
1171             const hid_t hDatasetID = H5Dopen(hHDF5, poH5Object->pszPath);
1172             H5Aiterate(hDatasetID, nullptr, HDF5AttrIterate, poDS);
1173             H5Dclose(hDatasetID);
1174         }
1175         break;
1176 
1177     default:
1178         break;
1179     }
1180 
1181     return CE_None;
1182 }
1183 
1184 /************************************************************************/
1185 /*                       HDF5FindDatasetObjectsbyPath()                 */
1186 /*      Find object by name                                             */
1187 /************************************************************************/
1188 HDF5GroupObjects *
HDF5FindDatasetObjectsbyPath(HDF5GroupObjects * poH5Objects,const char * pszDatasetPath)1189 HDF5Dataset::HDF5FindDatasetObjectsbyPath( HDF5GroupObjects *poH5Objects,
1190                                            const char *pszDatasetPath )
1191 {
1192     if( poH5Objects->nType == H5G_DATASET &&
1193         EQUAL(poH5Objects->pszUnderscorePath, pszDatasetPath) )
1194     {
1195 
1196 #ifdef DEBUG_VERBOSE
1197         printf("found it! %p\n", poH5Objects); /*ok*/
1198 #endif
1199         return poH5Objects;
1200     }
1201 
1202     HDF5Dataset *const poDS = this;
1203 
1204     if( poH5Objects->nbObjs > 0 )
1205     {
1206         for( unsigned int i = 0; i <poH5Objects->nbObjs; i++ )   {
1207             HDF5GroupObjects *poObjectsFound =
1208                 poDS->HDF5FindDatasetObjectsbyPath(poH5Objects->poHchild + i,
1209                                                    pszDatasetPath);
1210             // Is this our dataset?
1211             if( poObjectsFound != nullptr )
1212                 return poObjectsFound;
1213         }
1214     }
1215     // Dataset has not been found.
1216     return nullptr;
1217 }
1218 
1219 /************************************************************************/
1220 /*                       HDF5FindDatasetObjects()                       */
1221 /*      Find object by name                                             */
1222 /************************************************************************/
1223 HDF5GroupObjects *
HDF5FindDatasetObjects(HDF5GroupObjects * poH5Objects,const char * pszDatasetName)1224 HDF5Dataset::HDF5FindDatasetObjects( HDF5GroupObjects *poH5Objects,
1225                                      const char *pszDatasetName )
1226 {
1227     if( poH5Objects->nType == H5G_DATASET &&
1228         EQUAL(poH5Objects->pszName, pszDatasetName) )
1229     {
1230 
1231 #ifdef DEBUG_VERBOSE
1232         printf("found it! %p\n", poH5Objects); /*ok*/
1233 #endif
1234         return poH5Objects;
1235     }
1236 
1237     HDF5Dataset *poDS = this;
1238 
1239     if( poH5Objects->nbObjs > 0 )
1240     {
1241         for( unsigned int i = 0; i <poH5Objects->nbObjs; i++ )
1242         {
1243             HDF5GroupObjects *poObjectsFound = poDS->HDF5FindDatasetObjects(
1244                 poH5Objects->poHchild + i, pszDatasetName);
1245             // Is this our dataset?
1246             if( poObjectsFound != nullptr )
1247                 return poObjectsFound;
1248         }
1249     }
1250 
1251     // Dataset has not been found.
1252     return nullptr;
1253 }
1254 
1255 /************************************************************************/
1256 /*                        HDF5ListGroupObjects()                        */
1257 /*                                                                      */
1258 /*      List all objects in HDF5                                        */
1259 /************************************************************************/
HDF5ListGroupObjects(HDF5GroupObjects * poRootGroup,int bSUBDATASET)1260 CPLErr HDF5Dataset::HDF5ListGroupObjects( HDF5GroupObjects *poRootGroup,
1261                                           int bSUBDATASET )
1262 {
1263     HDF5Dataset *poDS = this;
1264 
1265     if( poRootGroup->nbObjs > 0 )
1266         for( hsize_t i = 0; i < poRootGroup->nbObjs; i++ )
1267         {
1268             poDS->HDF5ListGroupObjects(poRootGroup->poHchild + i, bSUBDATASET);
1269         }
1270 
1271     if( poRootGroup->nType == H5G_GROUP ) {
1272         CreateMetadata(poRootGroup, H5G_GROUP);
1273     }
1274 
1275     // Create Sub dataset list.
1276 
1277     if( poRootGroup->nType == H5G_DATASET && bSUBDATASET &&
1278         poDS->GetDataType(poRootGroup->native) == GDT_Unknown )
1279     {
1280         CPLDebug("HDF5", "Skipping unsupported %s of type %s",
1281                  poRootGroup->pszUnderscorePath,
1282                  poDS->GetDataTypeName(poRootGroup->native));
1283     }
1284     else if( poRootGroup->nType == H5G_DATASET && bSUBDATASET )
1285     {
1286         CreateMetadata(poRootGroup, H5G_DATASET);
1287 
1288         char szTemp[8192];  // TODO(schwehr): Get this off of the stack.
1289         switch( poRootGroup->nRank )
1290         {
1291         case 2:
1292             snprintf(szTemp, sizeof(szTemp), "%dx%d",
1293                      static_cast<int>(poRootGroup->paDims[0]),
1294                      static_cast<int>(poRootGroup->paDims[1]));
1295             break;
1296         case 3:
1297             snprintf(szTemp, sizeof(szTemp), "%dx%dx%d",
1298                      static_cast<int>(poRootGroup->paDims[0]),
1299                      static_cast<int>(poRootGroup->paDims[1]),
1300                      static_cast<int>(poRootGroup->paDims[2]));
1301             break;
1302         default:
1303             return CE_None;
1304         }
1305 
1306         const std::string osDim = szTemp;
1307 
1308         snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
1309                  ++(poDS->nSubDataCount));
1310 
1311         poDS->papszSubDatasets =
1312             CSLSetNameValue(poDS->papszSubDatasets, szTemp,
1313                             CPLSPrintf("HDF5:\"%s\":%s",
1314                                        poDS->GetDescription(),
1315                                        poRootGroup->pszUnderscorePath));
1316 
1317         snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
1318                  poDS->nSubDataCount);
1319 
1320         poDS->papszSubDatasets = CSLSetNameValue(
1321             poDS->papszSubDatasets, szTemp,
1322             CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
1323                        poRootGroup->pszUnderscorePath,
1324                        poDS->GetDataTypeName(poRootGroup->native)));
1325     }
1326 
1327     return CE_None;
1328 }
1329 
1330 /************************************************************************/
1331 /*                       ReadGlobalAttributes()                         */
1332 /************************************************************************/
ReadGlobalAttributes(int bSUBDATASET)1333 CPLErr HDF5Dataset::ReadGlobalAttributes(int bSUBDATASET)
1334 {
1335     HDF5GroupObjects *poRootGroup =
1336         static_cast<HDF5GroupObjects *>(CPLCalloc(sizeof(HDF5GroupObjects), 1));
1337 
1338     poH5RootGroup = poRootGroup;
1339     poRootGroup->pszName = CPLStrdup("/");
1340     poRootGroup->nType = H5G_GROUP;
1341     poRootGroup->poHparent = nullptr;
1342     poRootGroup->pszPath = nullptr;
1343     poRootGroup->pszUnderscorePath = nullptr;
1344 
1345     if( hHDF5 < 0 )
1346     {
1347         CPLError(CE_Failure, CPLE_AppDefined, "hHDF5 < 0!");
1348         return CE_None;
1349     }
1350 
1351     H5G_stat_t oStatbuf = {{0, 0}, {0, 0}, 0, H5G_UNKNOWN, 0, 0, {0, 0, 0, 0}};
1352 
1353     if( H5Gget_objinfo(hHDF5, "/", FALSE, &oStatbuf) < 0 )
1354         return CE_Failure;
1355     poRootGroup->objno[0] = oStatbuf.objno[0];
1356     poRootGroup->objno[1] = oStatbuf.objno[1];
1357 
1358     if( hGroupID > 0 )
1359         H5Gclose(hGroupID);
1360     hGroupID = H5Gopen(hHDF5, "/");
1361     if( hGroupID < 0 )
1362     {
1363         CPLError(CE_Failure, CPLE_AppDefined, "hGroupId <0!");
1364         return CE_None;
1365     }
1366 
1367     poRootGroup->nbAttrs = H5Aget_num_attrs(hGroupID);
1368 
1369     H5Gget_num_objs(hGroupID, &(poRootGroup->nbObjs));
1370 
1371     if( poRootGroup->nbObjs > 0 )
1372     {
1373         poRootGroup->poHchild = static_cast<HDF5GroupObjects *>(
1374             CPLCalloc(static_cast<size_t>(poRootGroup->nbObjs),
1375                       sizeof(HDF5GroupObjects)));
1376         H5Giterate(hGroupID, "/", nullptr, HDF5CreateGroupObjs, poRootGroup);
1377     }
1378     else
1379     {
1380         poRootGroup->poHchild = nullptr;
1381     }
1382 
1383     HDF5ListGroupObjects(poRootGroup, bSUBDATASET);
1384     return CE_None;
1385 }
1386 
1387 /**
1388  * Reads an array of double attributes from the HDF5 metadata.
1389  * It reads the attributes directly on its binary form directly,
1390  * thus avoiding string conversions.
1391  *
1392  * Important: It allocates the memory for the attributes internally,
1393  * so the caller must free the returned array after using it.
1394  * @param pszAttrFullPath Name of the attribute to be read.
1395  *        the attribute name must be the form:
1396  *            root attribute name
1397  *            SUBDATASET/subdataset attribute name
1398  * @param pdfValues pointer which will store the array of doubles read.
1399  * @param nLen it stores the length of the array read. If NULL it doesn't
1400  *        inform the length of the array.
1401  * @return CPLErr CE_None in case of success, CE_Failure in case of failure
1402  */
HDF5ReadDoubleAttr(const char * pszAttrFullPath,double ** pdfValues,int * nLen)1403 CPLErr HDF5Dataset::HDF5ReadDoubleAttr(const char *pszAttrFullPath,
1404                                        double **pdfValues, int *nLen)
1405 {
1406     CPLString osAttrFullPath(pszAttrFullPath);
1407 
1408     // Search for the last "/" in order to get the path to the attribute.
1409     const size_t nSlashPos = osAttrFullPath.find_last_of("/");
1410 
1411     CPLString osObjName;
1412     CPLString osAttrName;
1413 
1414     // If objects name have been found.
1415     if( nSlashPos != CPLString::npos )
1416     {
1417         // Split Object name (dataset, group).
1418         osObjName = osAttrFullPath.substr(0, nSlashPos);
1419         // Split attribute name.
1420         osAttrName = osAttrFullPath.substr(nSlashPos + 1);
1421     }
1422     else
1423     {
1424         // By default the group is root, and
1425         // the attribute is the full path.
1426         osObjName = "/";
1427         osAttrName = pszAttrFullPath;
1428     }
1429 
1430     const hid_t hObjAttrID = H5Oopen(hHDF5, osObjName.c_str(), H5P_DEFAULT);
1431 
1432     CPLErr retVal = CE_Failure;
1433 
1434     if(hObjAttrID < 0)
1435     {
1436         CPLError(CE_Failure, CPLE_OpenFailed,
1437                  "Object %s could not be opened", pszAttrFullPath);
1438         retVal = CE_Failure;
1439     }
1440     else
1441     {
1442         // Open attribute handler by name, from the object handler opened
1443         // earlier.
1444         const hid_t hAttrID = H5Aopen_name(hObjAttrID, osAttrName.c_str());
1445 
1446         // Check for errors opening the attribute.
1447         if( hAttrID < 0 )
1448         {
1449             CPLError(CE_Failure, CPLE_OpenFailed,
1450                      "Attribute %s could not be opened", pszAttrFullPath);
1451             retVal = CE_Failure;
1452         }
1453         else
1454         {
1455             const hid_t hAttrTypeID = H5Aget_type(hAttrID);
1456             const hid_t hAttrNativeType =
1457                 H5Tget_native_type(hAttrTypeID, H5T_DIR_DEFAULT);
1458             const hid_t hAttrSpace = H5Aget_space(hAttrID);
1459             hsize_t nSize[64] = {};
1460             const unsigned int nAttrDims =
1461                 H5Sget_simple_extent_dims(hAttrSpace, nSize, nullptr);
1462 
1463             if( !H5Tequal(H5T_NATIVE_DOUBLE, hAttrNativeType) )
1464             {
1465                 CPLError(CE_Failure, CPLE_OpenFailed,
1466                          "Attribute %s is not of type double",
1467                          pszAttrFullPath);
1468                 retVal = CE_Failure;
1469             }
1470             else
1471             {
1472                 // Get the amount of elements.
1473                 unsigned int nAttrElmts = 1;
1474                 for( hsize_t i = 0; i < nAttrDims; i++ )
1475                 {
1476                     // For multidimensional attributes
1477                     nAttrElmts *= static_cast<unsigned int>(nSize[i]);
1478                 }
1479 
1480                 if(nLen != nullptr)
1481                     *nLen = nAttrElmts;
1482 
1483                 *pdfValues = static_cast<double *>(
1484                     CPLMalloc(nAttrElmts * sizeof(double)));
1485 
1486                 // Read the attribute contents
1487                 if( H5Aread(hAttrID, hAttrNativeType, *pdfValues) < 0 )
1488                 {
1489                     CPLError(CE_Failure, CPLE_OpenFailed,
1490                              "Attribute %s could not be opened",
1491                              pszAttrFullPath);
1492                     retVal = CE_Failure;
1493                 }
1494                 else
1495                 {
1496                     retVal = CE_None;
1497                 }
1498             }
1499 
1500             H5Tclose(hAttrNativeType);
1501             H5Tclose(hAttrTypeID);
1502             H5Sclose(hAttrSpace);
1503             H5Aclose(hAttrID);
1504         }
1505         H5Oclose(hObjAttrID);
1506     }
1507 
1508     return retVal;
1509 }
1510