Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/raw/eirdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Erdas EIR Raw Driver
4
 * Purpose:  Implementation of EIRDataset
5
 * Author:   Adam Milling, amilling@alumni.uwaterloo.ca
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2009-2010, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_string.h"
15
#include "gdal_frmts.h"
16
#include "gdal_priv.h"
17
#include "ogr_spatialref.h"
18
#include "rawdataset.h"
19
20
#include <limits>
21
22
/************************************************************************/
23
/* ==================================================================== */
24
/*              EIRDataset                                              */
25
/* ==================================================================== */
26
/************************************************************************/
27
28
class EIRDataset final : public RawDataset
29
{
30
    friend class RawRasterBand;
31
32
    VSILFILE *fpImage = nullptr;  // image data file
33
    bool bGotTransform = false;
34
    GDALGeoTransform m_gt{};
35
    bool bHDRDirty = false;
36
    CPLStringList aosHDR{};
37
    char **papszExtraFiles = nullptr;
38
39
    void ResetKeyValue(const char *pszKey, const char *pszValue);
40
#ifdef unused
41
    const char *GetKeyValue(const char *pszKey, const char *pszDefault = "");
42
#endif
43
44
    CPL_DISALLOW_COPY_ASSIGN(EIRDataset)
45
46
    CPLErr Close(GDALProgressFunc = nullptr, void * = nullptr) override;
47
48
  public:
49
    EIRDataset();
50
    ~EIRDataset() override;
51
52
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
53
54
    char **GetFileList() override;
55
56
    static int Identify(GDALOpenInfo *);
57
    static GDALDataset *Open(GDALOpenInfo *);
58
};
59
60
/************************************************************************/
61
/* ==================================================================== */
62
/*                            EIRDataset                                */
63
/* ==================================================================== */
64
/************************************************************************/
65
66
/************************************************************************/
67
/*                             EIRDataset()                             */
68
/************************************************************************/
69
70
1
EIRDataset::EIRDataset() = default;
71
72
/************************************************************************/
73
/*                            ~EIRDataset()                             */
74
/************************************************************************/
75
76
EIRDataset::~EIRDataset()
77
78
1
{
79
1
    EIRDataset::Close();
80
1
}
81
82
/************************************************************************/
83
/*                               Close()                                */
84
/************************************************************************/
85
86
CPLErr EIRDataset::Close(GDALProgressFunc, void *)
87
2
{
88
2
    CPLErr eErr = CE_None;
89
2
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
90
1
    {
91
1
        if (EIRDataset::FlushCache(true) != CE_None)
92
0
            eErr = CE_Failure;
93
94
1
        if (nBands > 0 && GetAccess() == GA_Update)
95
0
        {
96
0
            RawRasterBand *poBand =
97
0
                cpl::down_cast<RawRasterBand *>(GetRasterBand(1));
98
99
0
            int bNoDataSet = FALSE;
100
0
            const double dfNoData = poBand->GetNoDataValue(&bNoDataSet);
101
0
            if (bNoDataSet)
102
0
            {
103
0
                ResetKeyValue("NODATA", CPLString().Printf("%.8g", dfNoData));
104
0
            }
105
0
        }
106
107
1
        if (fpImage)
108
1
        {
109
1
            if (VSIFCloseL(fpImage) != 0)
110
0
                eErr = CE_Failure;
111
1
        }
112
113
1
        CSLDestroy(papszExtraFiles);
114
115
1
        if (GDALPamDataset::Close() != CE_None)
116
0
            eErr = CE_Failure;
117
1
    }
118
2
    return eErr;
119
2
}
120
121
#ifdef unused
122
/************************************************************************/
123
/*                            GetKeyValue()                             */
124
/************************************************************************/
125
126
const char *EIRDataset::GetKeyValue(const char *pszKey, const char *pszDefault)
127
128
{
129
    const char *const *papszHDR = aosHDR.List();
130
    for (int i = 0; papszHDR[i] != nullptr; i++)
131
    {
132
        if (EQUALN(pszKey, papszHDR[i], strlen(pszKey)) &&
133
            isspace((unsigned char)papszHDR[i][strlen(pszKey)]))
134
        {
135
            const char *pszValue = papszHDR[i] + strlen(pszKey);
136
            while (isspace(static_cast<unsigned char>(*pszValue)))
137
                pszValue++;
138
139
            return pszValue;
140
        }
141
    }
142
143
    return pszDefault;
144
}
145
#endif
146
147
/************************************************************************/
148
/*                           ResetKeyValue()                            */
149
/*                                                                      */
150
/*      Replace or add the keyword with the indicated value in the      */
151
/*      papszHDR list.                                                  */
152
/************************************************************************/
153
154
void EIRDataset::ResetKeyValue(const char *pszKey, const char *pszValue)
155
156
0
{
157
0
    if (strlen(pszValue) > 65)
158
0
    {
159
0
        CPLAssert(strlen(pszValue) <= 65);
160
0
        return;
161
0
    }
162
163
0
    char szNewLine[82] = {'\0'};
164
0
    snprintf(szNewLine, sizeof(szNewLine), "%-15s%s", pszKey, pszValue);
165
166
0
    char **papszHDR = aosHDR.List();
167
0
    for (int i = aosHDR.size() - 1; i >= 0; i--)
168
0
    {
169
0
        if (EQUALN(papszHDR[i], szNewLine, strlen(pszKey) + 1))
170
0
        {
171
0
            if (strcmp(papszHDR[i], szNewLine) != 0)
172
0
            {
173
0
                CPLFree(papszHDR[i]);
174
0
                papszHDR[i] = CPLStrdup(szNewLine);
175
0
                bHDRDirty = true;
176
0
            }
177
0
            return;
178
0
        }
179
0
    }
180
181
0
    bHDRDirty = true;
182
0
    aosHDR.AddString(szNewLine);
183
0
}
184
185
/************************************************************************/
186
/*                          GetGeoTransform()                           */
187
/************************************************************************/
188
189
CPLErr EIRDataset::GetGeoTransform(GDALGeoTransform &gt) const
190
191
1
{
192
1
    if (bGotTransform)
193
0
    {
194
0
        gt = m_gt;
195
0
        return CE_None;
196
0
    }
197
198
1
    return GDALPamDataset::GetGeoTransform(gt);
199
1
}
200
201
/************************************************************************/
202
/*                            GetFileList()                             */
203
/************************************************************************/
204
205
char **EIRDataset::GetFileList()
206
207
2
{
208
    // Main data file, etc.
209
2
    char **papszFileList = GDALPamDataset::GetFileList();
210
211
    // Header file.
212
2
    papszFileList = CSLInsertStrings(papszFileList, -1, papszExtraFiles);
213
214
2
    return papszFileList;
215
2
}
216
217
/************************************************************************/
218
/*                              Identify()                              */
219
/************************************************************************/
220
221
int EIRDataset::Identify(GDALOpenInfo *poOpenInfo)
222
223
540k
{
224
540k
    if (poOpenInfo->nHeaderBytes < 100)
225
452k
        return FALSE;
226
227
87.7k
    if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
228
87.7k
               "IMAGINE_RAW_FILE") == nullptr)
229
84.7k
        return FALSE;
230
231
2.95k
    return TRUE;
232
87.7k
}
233
234
/************************************************************************/
235
/*                                Open()                                */
236
/************************************************************************/
237
238
GDALDataset *EIRDataset::Open(GDALOpenInfo *poOpenInfo)
239
240
1.47k
{
241
1.47k
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
242
0
        return nullptr;
243
244
    /* header example and description
245
246
    IMAGINE_RAW_FILE // must be on first line, by itself
247
    WIDTH 581        // number of columns in the image
248
    HEIGHT 695       // number of rows in the image
249
    NUM_LAYERS 3     // number of spectral bands in the image; default 1
250
    PIXEL_FILES raw8_3n_ui_sanjack.bl // raster file
251
                                      // default: same name with no extension
252
    FORMAT BIL       // BIL BIP BSQ; default BIL
253
    DATATYPE U8      // U1 U2 U4 U8 U16 U32 S16 S32 F32 F64; default U8
254
    BYTE_ORDER       // LSB MSB; required for U16 U32 S16 S32 F32 F64
255
    DATA_OFFSET      // start of image data in raster file; default 0 bytes
256
    END_RAW_FILE     // end RAW file - stop reading
257
258
    For a true color image with three bands (R, G, B) stored using 8 bits
259
    for each pixel in each band, DATA_TYPE equals U8 and NUM_LAYERS equals
260
    3 for a total of 24 bits per pixel.
261
262
    Note that the current version of ERDAS Raw Raster Reader/Writer does
263
    not support the LAYER_SKIP_BYTES, RECORD_SKIP_BYTES, TILE_WIDTH and
264
    TILE_HEIGHT directives. Since the reader does not read the PIXEL_FILES
265
    directive, the reader always assumes that the raw binary file is the
266
    dataset, and the name of this file is the name of the header without the
267
    extension. Currently, the reader does not support multiple raw binary
268
    files in one dataset or a single file with both the header and the raw
269
    binary data at the same time.
270
    */
271
272
1.47k
    int nRows = -1;
273
1.47k
    int nCols = -1;
274
1.47k
    int nBands = 1;
275
1.47k
    int nSkipBytes = 0;
276
1.47k
    int nLineCount = 0;
277
1.47k
    GDALDataType eDataType = GDT_UInt8;
278
1.47k
    int nBits = 8;
279
1.47k
    char chByteOrder = 'M';
280
1.47k
    char szLayout[10] = "BIL";
281
1.47k
    CPLStringList aosHDR;
282
283
    // default raster file: same name with no extension
284
1.47k
    const CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
285
1.47k
    const CPLString osName = CPLGetBasenameSafe(poOpenInfo->pszFilename);
286
1.47k
    CPLString osRasterFilename = CPLFormCIFilenameSafe(osPath, osName, "");
287
288
    // parse the header file
289
1.47k
    const char *pszLine = nullptr;
290
1.47k
    VSIRewindL(poOpenInfo->fpL);
291
3.43k
    while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
292
3.40k
    {
293
3.40k
        nLineCount++;
294
295
3.40k
        if ((nLineCount == 1) && !EQUAL(pszLine, "IMAGINE_RAW_FILE"))
296
1.40k
        {
297
1.40k
            return nullptr;
298
1.40k
        }
299
300
1.99k
        if ((nLineCount > 50) || EQUAL(pszLine, "END_RAW_FILE"))
301
28
        {
302
28
            break;
303
28
        }
304
305
1.96k
        if (strlen(pszLine) > 1000)
306
3
            break;
307
308
1.96k
        aosHDR.AddString(pszLine);
309
310
1.96k
        const CPLStringList aosTokens(
311
1.96k
            CSLTokenizeStringComplex(pszLine, " \t", TRUE, FALSE));
312
1.96k
        if (aosTokens.size() < 2)
313
1.35k
        {
314
1.35k
            continue;
315
1.35k
        }
316
317
610
        if (EQUAL(aosTokens[0], "WIDTH"))
318
1
        {
319
1
            nCols = atoi(aosTokens[1]);
320
1
        }
321
609
        else if (EQUAL(aosTokens[0], "HEIGHT"))
322
2
        {
323
2
            nRows = atoi(aosTokens[1]);
324
2
        }
325
607
        else if (EQUAL(aosTokens[0], "NUM_LAYERS"))
326
2
        {
327
2
            nBands = atoi(aosTokens[1]);
328
2
        }
329
605
        else if (EQUAL(aosTokens[0], "PIXEL_FILES"))
330
1
        {
331
1
            if (CPLHasPathTraversal(aosTokens[1]))
332
0
            {
333
0
                CPLError(CE_Failure, CPLE_AppDefined,
334
0
                         "Path traversal detected in %s", aosTokens[1]);
335
0
                return nullptr;
336
0
            }
337
1
            osRasterFilename = CPLFormCIFilenameSafe(osPath, aosTokens[1], "");
338
1
        }
339
604
        else if (EQUAL(aosTokens[0], "FORMAT"))
340
0
        {
341
0
            snprintf(szLayout, sizeof(szLayout), "%s", aosTokens[1]);
342
0
        }
343
604
        else if (EQUAL(aosTokens[0], "DATATYPE") ||
344
603
                 EQUAL(aosTokens[0], "DATA_TYPE"))
345
1
        {
346
1
            if (EQUAL(aosTokens[1], "U1") || EQUAL(aosTokens[1], "U2") ||
347
1
                EQUAL(aosTokens[1], "U4") || EQUAL(aosTokens[1], "U8"))
348
1
            {
349
1
                nBits = 8;
350
1
                eDataType = GDT_UInt8;
351
1
            }
352
0
            else if (EQUAL(aosTokens[1], "U16"))
353
0
            {
354
0
                nBits = 16;
355
0
                eDataType = GDT_UInt16;
356
0
            }
357
0
            else if (EQUAL(aosTokens[1], "U32"))
358
0
            {
359
0
                nBits = 32;
360
0
                eDataType = GDT_UInt32;
361
0
            }
362
0
            else if (EQUAL(aosTokens[1], "S16"))
363
0
            {
364
0
                nBits = 16;
365
0
                eDataType = GDT_Int16;
366
0
            }
367
0
            else if (EQUAL(aosTokens[1], "S32"))
368
0
            {
369
0
                nBits = 32;
370
0
                eDataType = GDT_Int32;
371
0
            }
372
0
            else if (EQUAL(aosTokens[1], "F32"))
373
0
            {
374
0
                nBits = 32;
375
0
                eDataType = GDT_Float32;
376
0
            }
377
0
            else if (EQUAL(aosTokens[1], "F64"))
378
0
            {
379
0
                nBits = 64;
380
0
                eDataType = GDT_Float64;
381
0
            }
382
0
            else
383
0
            {
384
0
                CPLError(CE_Failure, CPLE_NotSupported,
385
0
                         "EIR driver does not support DATATYPE %s.",
386
0
                         aosTokens[1]);
387
0
                return nullptr;
388
0
            }
389
1
        }
390
603
        else if (EQUAL(aosTokens[0], "BYTE_ORDER"))
391
0
        {
392
            // M for MSB, L for LSB
393
0
            chByteOrder = static_cast<char>(
394
0
                toupper(static_cast<unsigned char>(aosTokens[1][0])));
395
0
        }
396
603
        else if (EQUAL(aosTokens[0], "DATA_OFFSET"))
397
1
        {
398
1
            nSkipBytes = atoi(aosTokens[1]);  // TBD: is this mapping right?
399
1
            if (nSkipBytes < 0)
400
0
            {
401
0
                return nullptr;
402
0
            }
403
1
        }
404
610
    }
405
67
    CPL_IGNORE_RET_VAL(nBits);
406
407
    /* -------------------------------------------------------------------- */
408
    /*      Did we get the required keywords?  If not we return with        */
409
    /*      this never having been considered to be a match. This isn't     */
410
    /*      an error!                                                       */
411
    /* -------------------------------------------------------------------- */
412
67
    if (nRows <= 0 || nCols <= 0 || nBands <= 0)
413
66
    {
414
66
        return nullptr;
415
66
    }
416
417
1
    if (!GDALCheckDatasetDimensions(nCols, nRows) ||
418
1
        !GDALCheckBandCount(nBands, FALSE))
419
0
    {
420
0
        return nullptr;
421
0
    }
422
423
    /* -------------------------------------------------------------------- */
424
    /*      Confirm the requested access is supported.                      */
425
    /* -------------------------------------------------------------------- */
426
1
    if (poOpenInfo->eAccess == GA_Update)
427
0
    {
428
0
        ReportUpdateNotSupportedByDriver("EIR");
429
0
        return nullptr;
430
0
    }
431
    /* -------------------------------------------------------------------- */
432
    /*      Create a corresponding GDALDataset.                             */
433
    /* -------------------------------------------------------------------- */
434
1
    auto poDS = std::make_unique<EIRDataset>();
435
436
    /* -------------------------------------------------------------------- */
437
    /*      Capture some information from the file that is of interest.     */
438
    /* -------------------------------------------------------------------- */
439
1
    poDS->nRasterXSize = nCols;
440
1
    poDS->nRasterYSize = nRows;
441
1
    poDS->aosHDR = std::move(aosHDR);
442
443
    /* -------------------------------------------------------------------- */
444
    /*      Open target binary file.                                        */
445
    /* -------------------------------------------------------------------- */
446
1
    poDS->fpImage = VSIFOpenL(osRasterFilename.c_str(), "rb");
447
1
    if (poDS->fpImage == nullptr)
448
0
    {
449
0
        CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s: %s",
450
0
                 osRasterFilename.c_str(), VSIStrerror(errno));
451
0
        return nullptr;
452
0
    }
453
1
    poDS->papszExtraFiles =
454
1
        CSLAddString(poDS->papszExtraFiles, osRasterFilename);
455
456
1
    poDS->eAccess = poOpenInfo->eAccess;
457
458
    /* -------------------------------------------------------------------- */
459
    /*      Compute the line offset.                                        */
460
    /* -------------------------------------------------------------------- */
461
1
    const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
462
1
    int nPixelOffset = 0;
463
1
    int nLineOffset = 0;
464
1
    vsi_l_offset nBandOffset = 0;
465
466
1
    if (EQUAL(szLayout, "BIP"))
467
0
    {
468
0
        nPixelOffset = nItemSize * nBands;
469
0
        if (nPixelOffset > INT_MAX / nCols)
470
0
        {
471
0
            return nullptr;
472
0
        }
473
0
        nLineOffset = nPixelOffset * nCols;
474
0
        nBandOffset = static_cast<vsi_l_offset>(nItemSize);
475
0
    }
476
1
    else if (EQUAL(szLayout, "BSQ"))
477
0
    {
478
0
        nPixelOffset = nItemSize;
479
0
        if (nPixelOffset > INT_MAX / nCols)
480
0
        {
481
0
            return nullptr;
482
0
        }
483
0
        nLineOffset = nPixelOffset * nCols;
484
0
        nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nRows;
485
0
    }
486
1
    else /* assume BIL */
487
1
    {
488
1
        nPixelOffset = nItemSize;
489
1
        if (nItemSize > INT_MAX / nBands ||
490
1
            nItemSize * nBands > INT_MAX / nCols)
491
0
        {
492
0
            return nullptr;
493
0
        }
494
1
        nLineOffset = nItemSize * nBands * nCols;
495
1
        nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nCols;
496
1
    }
497
498
1
    if (poDS->nBands > 1)
499
0
    {
500
0
        if (nBandOffset >
501
0
                std::numeric_limits<vsi_l_offset>::max() / (poDS->nBands - 1) ||
502
0
            static_cast<vsi_l_offset>(nSkipBytes) >
503
0
                std::numeric_limits<vsi_l_offset>::max() -
504
0
                    nBandOffset * (poDS->nBands - 1))
505
0
        {
506
0
            return nullptr;
507
0
        }
508
0
    }
509
510
1
    if (!RAWDatasetCheckMemoryUsage(
511
1
            poDS->nRasterXSize, poDS->nRasterYSize, nBands, nItemSize,
512
1
            nPixelOffset, nLineOffset, nSkipBytes, nBandOffset, poDS->fpImage))
513
0
    {
514
0
        return nullptr;
515
0
    }
516
517
1
    poDS->SetDescription(poOpenInfo->pszFilename);
518
1
    poDS->PamInitialize();
519
520
    /* -------------------------------------------------------------------- */
521
    /*      Create band information objects.                                */
522
    /* -------------------------------------------------------------------- */
523
2
    for (int i = 0; i < nBands; i++)
524
1
    {
525
1
        auto poBand = RawRasterBand::Create(
526
1
            poDS.get(), i + 1, poDS->fpImage, nSkipBytes + nBandOffset * i,
527
1
            nPixelOffset, nLineOffset, eDataType,
528
1
            chByteOrder == 'I' || chByteOrder == 'L'
529
1
                ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
530
1
                : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
531
1
            RawRasterBand::OwnFP::NO);
532
1
        if (!poBand)
533
0
            return nullptr;
534
1
        poDS->SetBand(i + 1, std::move(poBand));
535
1
    }
536
537
    /* -------------------------------------------------------------------- */
538
    /*      look for a worldfile                                            */
539
    /* -------------------------------------------------------------------- */
540
541
1
    if (!poDS->bGotTransform)
542
1
        poDS->bGotTransform = CPL_TO_BOOL(GDALReadWorldFile(
543
1
            poOpenInfo->pszFilename, nullptr, poDS->m_gt.data()));
544
545
1
    if (!poDS->bGotTransform)
546
1
        poDS->bGotTransform = CPL_TO_BOOL(GDALReadWorldFile(
547
1
            poOpenInfo->pszFilename, "wld", poDS->m_gt.data()));
548
549
    /* -------------------------------------------------------------------- */
550
    /*      Initialize any PAM information.                                 */
551
    /* -------------------------------------------------------------------- */
552
1
    poDS->TryLoadXML();
553
554
    /* -------------------------------------------------------------------- */
555
    /*      Check for overviews.                                            */
556
    /* -------------------------------------------------------------------- */
557
1
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
558
559
1
    return poDS.release();
560
1
}
561
562
/************************************************************************/
563
/*                          GDALRegister_EIR()                          */
564
/************************************************************************/
565
566
void GDALRegister_EIR()
567
568
22
{
569
22
    if (GDALGetDriverByName("EIR") != nullptr)
570
0
        return;
571
572
22
    GDALDriver *poDriver = new GDALDriver();
573
574
22
    poDriver->SetDescription("EIR");
575
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
576
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Erdas Imagine Raw");
577
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/eir.html");
578
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
579
580
22
    poDriver->pfnOpen = EIRDataset::Open;
581
22
    poDriver->pfnIdentify = EIRDataset::Identify;
582
583
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
584
22
}