Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/gcore/gdalopeninfo.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Core
4
 * Purpose:  Implementation of GDALOpenInfo class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 **********************************************************************
8
 * Copyright (c) 2002, Frank Warmerdam
9
 * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "gdal_priv.h"  // Must be included first for mingw VSIStatBufL.
15
#include "cpl_port.h"
16
#include "cpl_vsi_virtual.h"
17
18
#include <cstdlib>
19
#include <cstring>
20
#ifdef HAVE_UNISTD_H
21
#include <unistd.h>
22
#endif
23
24
#include <algorithm>
25
#include <map>
26
#include <mutex>
27
#include <vector>
28
29
#include "cpl_config.h"
30
#include "cpl_conv.h"
31
#include "cpl_error.h"
32
#include "cpl_string.h"
33
#include "cpl_vsi.h"
34
#include "gdal.h"
35
36
// Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
37
// ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
38
void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
39
                                      const GByte *pabyHeader,
40
                                      int nHeaderBytes);
41
void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
42
43
/************************************************************************/
44
45
/* This whole section helps for SQLite/GPKG, especially with write-ahead
46
 * log enabled. The issue is that sqlite3 relies on POSIX advisory locks to
47
 * properly work and decide when to create/delete the wal related files.
48
 * One issue with POSIX advisory locks is that if within the same process
49
 * you do
50
 * f1 = open('somefile')
51
 * set locks on f1
52
 * f2 = open('somefile')
53
 * close(f2)
54
 * The close(f2) will cancel the locks set on f1. The work on f1 is done by
55
 * libsqlite3 whereas the work on f2 is done by GDALOpenInfo.
56
 * So as soon as sqlite3 has opened a file we should make sure not to re-open
57
 * it (actually close it) ourselves.
58
 */
59
60
namespace
61
{
62
struct FileNotToOpen
63
{
64
    CPLString osFilename{};
65
    int nRefCount{};
66
    GByte *pabyHeader{nullptr};
67
    int nHeaderBytes{0};
68
};
69
}  // namespace
70
71
static std::mutex sFNTOMutex;
72
static std::map<CPLString, FileNotToOpen> *pMapFNTO = nullptr;
73
74
void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
75
                                      const GByte *pabyHeader, int nHeaderBytes)
76
0
{
77
0
    std::lock_guard<std::mutex> oLock(sFNTOMutex);
78
0
    if (pMapFNTO == nullptr)
79
0
        pMapFNTO = new std::map<CPLString, FileNotToOpen>();
80
0
    auto oIter = pMapFNTO->find(pszFilename);
81
0
    if (oIter != pMapFNTO->end())
82
0
    {
83
0
        oIter->second.nRefCount++;
84
0
    }
85
0
    else
86
0
    {
87
0
        FileNotToOpen fnto;
88
0
        fnto.osFilename = pszFilename;
89
0
        fnto.nRefCount = 1;
90
0
        fnto.pabyHeader = static_cast<GByte *>(CPLMalloc(nHeaderBytes + 1));
91
0
        memcpy(fnto.pabyHeader, pabyHeader, nHeaderBytes);
92
0
        fnto.pabyHeader[nHeaderBytes] = 0;
93
0
        fnto.nHeaderBytes = nHeaderBytes;
94
0
        (*pMapFNTO)[pszFilename] = std::move(fnto);
95
0
    }
96
0
}
97
98
void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename)
99
0
{
100
0
    std::lock_guard<std::mutex> oLock(sFNTOMutex);
101
0
    CPLAssert(pMapFNTO);
102
0
    auto oIter = pMapFNTO->find(pszFilename);
103
0
    CPLAssert(oIter != pMapFNTO->end());
104
0
    oIter->second.nRefCount--;
105
0
    if (oIter->second.nRefCount == 0)
106
0
    {
107
0
        CPLFree(oIter->second.pabyHeader);
108
0
        pMapFNTO->erase(oIter);
109
0
    }
110
0
    if (pMapFNTO->empty())
111
0
    {
112
0
        delete pMapFNTO;
113
0
        pMapFNTO = nullptr;
114
0
    }
115
0
}
116
117
static GByte *GDALOpenInfoGetFileNotToOpen(const char *pszFilename,
118
                                           int *pnHeaderBytes)
119
0
{
120
0
    std::lock_guard<std::mutex> oLock(sFNTOMutex);
121
0
    *pnHeaderBytes = 0;
122
0
    if (pMapFNTO == nullptr)
123
0
    {
124
0
        return nullptr;
125
0
    }
126
0
    auto oIter = pMapFNTO->find(pszFilename);
127
0
    if (oIter == pMapFNTO->end())
128
0
    {
129
0
        return nullptr;
130
0
    }
131
0
    *pnHeaderBytes = oIter->second.nHeaderBytes;
132
0
    GByte *pabyHeader = static_cast<GByte *>(CPLMalloc(*pnHeaderBytes + 1));
133
0
    memcpy(pabyHeader, oIter->second.pabyHeader, *pnHeaderBytes);
134
0
    pabyHeader[*pnHeaderBytes] = 0;
135
0
    return pabyHeader;
136
0
}
137
138
/************************************************************************/
139
/* ==================================================================== */
140
/*                             GDALOpenInfo                             */
141
/* ==================================================================== */
142
/************************************************************************/
143
144
/************************************************************************/
145
/*                            GDALOpenInfo()                            */
146
/************************************************************************/
147
148
/** Constructor/
149
 * @param pszFilenameIn filename
150
 * @param nOpenFlagsIn open flags
151
 * @param papszSiblingFilesIn list of sibling files, or NULL.
152
 */
153
GDALOpenInfo::GDALOpenInfo(const char *pszFilenameIn, int nOpenFlagsIn,
154
                           const char *const *papszSiblingFilesIn)
155
0
    : pszFilename(CPLStrdup(pszFilenameIn)),
156
0
      osExtension(CPLGetExtensionSafe(pszFilenameIn)),
157
0
      eAccess(nOpenFlagsIn & GDAL_OF_UPDATE ? GA_Update : GA_ReadOnly),
158
0
      nOpenFlags(nOpenFlagsIn)
159
0
{
160
/* -------------------------------------------------------------------- */
161
/*      Ensure that C: is treated as C:\ so we can stat it on           */
162
/*      Windows.  Similar to what is done in CPLStat().                 */
163
/* -------------------------------------------------------------------- */
164
#ifdef _WIN32
165
    if (strlen(pszFilename) == 2 && pszFilename[1] == ':')
166
    {
167
        char szAltPath[10];
168
169
        strcpy(szAltPath, pszFilename);
170
        strcat(szAltPath, "\\");
171
        CPLFree(pszFilename);
172
        pszFilename = CPLStrdup(szAltPath);
173
    }
174
#endif  // WIN32
175
176
0
    Init(papszSiblingFilesIn, nullptr);
177
0
}
178
179
/************************************************************************/
180
/*                            GDALOpenInfo()                            */
181
/************************************************************************/
182
183
/** Constructor/
184
 * @param pszFilenameIn filename
185
 * @param nOpenFlagsIn open flags
186
 * @param poFile already opened file
187
 * @since 3.13
188
 */
189
GDALOpenInfo::GDALOpenInfo(const char *pszFilenameIn, int nOpenFlagsIn,
190
                           std::unique_ptr<VSIVirtualHandle> poFile)
191
0
    : pszFilename(CPLStrdup(pszFilenameIn)),
192
0
      osExtension(CPLGetExtensionSafe(pszFilenameIn)),
193
0
      eAccess(nOpenFlagsIn & GDAL_OF_UPDATE ? GA_Update : GA_ReadOnly),
194
0
      nOpenFlags(nOpenFlagsIn)
195
0
{
196
0
    Init(nullptr, std::move(poFile));
197
0
}
198
199
/************************************************************************/
200
/*                                Init()                                */
201
/************************************************************************/
202
203
void GDALOpenInfo::Init(const char *const *papszSiblingFilesIn,
204
                        std::unique_ptr<VSIVirtualHandle> poFile)
205
0
{
206
0
    if (STARTS_WITH(pszFilename, "MVT:/vsi"))
207
0
        return;
208
209
    /* -------------------------------------------------------------------- */
210
    /*      Collect information about the file.                             */
211
    /* -------------------------------------------------------------------- */
212
213
0
#if !defined(_WIN32)
214
0
    bool bHasRetried = false;
215
216
0
retry:  // TODO(schwehr): Stop using goto.
217
218
0
#endif  //  !defined(_WIN32)
219
220
#if !(defined(_WIN32) || defined(__linux__) || defined(__ANDROID__) ||         \
221
      (defined(__MACH__) && defined(__APPLE__)))
222
    /* On BSDs, fread() on a directory returns non zero, so we have to */
223
    /* do a stat() before to check the nature of pszFilename. */
224
    bool bPotentialDirectory = (eAccess == GA_ReadOnly);
225
#else
226
0
    bool bPotentialDirectory = false;
227
0
#endif
228
229
    /* Check if the filename might be a directory of a special virtual file
230
     * system */
231
0
    if (STARTS_WITH(pszFilename, "/vsizip/") ||
232
0
        STARTS_WITH(pszFilename, "/vsitar/") ||
233
0
        STARTS_WITH(pszFilename, "/vsi7z/") ||
234
0
        STARTS_WITH(pszFilename, "/vsirar/"))
235
0
    {
236
0
        const char *pszExt = osExtension.c_str();
237
0
        if (EQUAL(pszExt, "zip") || EQUAL(pszExt, "tar") ||
238
0
            EQUAL(pszExt, "gz") || EQUAL(pszExt, "7z") ||
239
0
            EQUAL(pszExt, "rar") ||
240
0
            pszFilename[strlen(pszFilename) - 1] == '}'
241
0
#ifdef DEBUG
242
            // For AFL, so that .cur_input is detected as the archive filename.
243
0
            || EQUAL(CPLGetFilename(pszFilename), ".cur_input")
244
0
#endif  // DEBUG
245
0
        )
246
0
        {
247
0
            bPotentialDirectory = true;
248
0
        }
249
0
    }
250
0
    else if (STARTS_WITH(pszFilename, "/vsicurl/"))
251
0
    {
252
0
        bPotentialDirectory = true;
253
0
    }
254
255
0
    if (bPotentialDirectory && !poFile)
256
0
    {
257
0
        int nStatFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
258
0
        if (nOpenFlags & GDAL_OF_VERBOSE_ERROR)
259
0
            nStatFlags |= VSI_STAT_SET_ERROR_FLAG;
260
261
        // For those special files, opening them with VSIFOpenL() might result
262
        // in content, even if they should be considered as directories, so
263
        // use stat.
264
0
        VSIStatBufL sStat;
265
266
0
        if (VSIStatExL(pszFilename, &sStat, nStatFlags) == 0)
267
0
        {
268
0
            bStatOK = TRUE;
269
0
            if (VSI_ISDIR(sStat.st_mode))
270
0
                bIsDirectory = TRUE;
271
0
        }
272
0
    }
273
274
0
    if (poFile)
275
0
    {
276
0
        fpL = poFile.release();
277
0
    }
278
0
    else
279
0
    {
280
0
        pabyHeader = GDALOpenInfoGetFileNotToOpen(pszFilename, &nHeaderBytes);
281
282
0
        if (!bIsDirectory && pabyHeader == nullptr)
283
0
        {
284
0
            fpL =
285
0
                VSIFOpenExL(pszFilename, (eAccess == GA_Update) ? "r+b" : "rb",
286
0
                            (nOpenFlags & GDAL_OF_VERBOSE_ERROR) > 0);
287
0
        }
288
0
    }
289
290
0
    if (pabyHeader)
291
0
    {
292
0
        bStatOK = TRUE;
293
0
        nHeaderBytesTried = nHeaderBytes;
294
0
    }
295
0
    else if (fpL != nullptr)
296
0
    {
297
0
        bStatOK = TRUE;
298
0
        int nBufSize =
299
0
            atoi(CPLGetConfigOption("GDAL_INGESTED_BYTES_AT_OPEN", "1024"));
300
0
        if (nBufSize < 1024)
301
0
            nBufSize = 1024;
302
0
        else if (nBufSize > 10 * 1024 * 1024)
303
0
            nBufSize = 10 * 1024 * 1024;
304
0
        pabyHeader = static_cast<GByte *>(CPLCalloc(nBufSize + 1, 1));
305
0
        nHeaderBytesTried = nBufSize;
306
0
        nHeaderBytes =
307
0
            static_cast<int>(VSIFReadL(pabyHeader, 1, nHeaderBytesTried, fpL));
308
0
        VSIRewindL(fpL);
309
310
        /* If we cannot read anything, check if it is not a directory instead */
311
0
        VSIStatBufL sStat;
312
0
        if (nHeaderBytes == 0 &&
313
0
            VSIStatExL(pszFilename, &sStat,
314
0
                       VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
315
0
            VSI_ISDIR(sStat.st_mode))
316
0
        {
317
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
318
0
            fpL = nullptr;
319
0
            CPLFree(pabyHeader);
320
0
            pabyHeader = nullptr;
321
0
            bIsDirectory = TRUE;
322
0
        }
323
0
    }
324
0
    else if (!bStatOK)
325
0
    {
326
0
        VSIStatBufL sStat;
327
0
        if (!bPotentialDirectory &&
328
0
            VSIStatExL(pszFilename, &sStat,
329
0
                       VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0)
330
0
        {
331
0
            bStatOK = TRUE;
332
0
            if (VSI_ISDIR(sStat.st_mode))
333
0
                bIsDirectory = TRUE;
334
0
        }
335
0
#if !defined(_WIN32)
336
0
        else if (!bHasRetried && !STARTS_WITH(pszFilename, "/vsi"))
337
0
        {
338
            // If someone creates a file with "ln -sf
339
            // /vsicurl/http://download.osgeo.org/gdal/data/gtiff/utm.tif
340
            // my_remote_utm.tif" we will be able to open it by passing
341
            // my_remote_utm.tif.  This helps a lot for GDAL based readers that
342
            // only provide file explorers to open datasets.
343
0
            const int nBufSize = 2048;
344
0
            std::vector<char> oFilename(nBufSize);
345
0
            char *szPointerFilename = &oFilename[0];
346
0
            int nBytes = static_cast<int>(
347
0
                readlink(pszFilename, szPointerFilename, nBufSize));
348
0
            if (nBytes != -1)
349
0
            {
350
0
                szPointerFilename[std::min(nBytes, nBufSize - 1)] = 0;
351
0
                CPLFree(pszFilename);
352
0
                pszFilename = CPLStrdup(szPointerFilename);
353
0
                osExtension = CPLGetExtensionSafe(pszFilename);
354
0
                papszSiblingFilesIn = nullptr;
355
0
                bHasRetried = true;
356
0
                goto retry;
357
0
            }
358
0
        }
359
0
#endif  //  !defined(_WIN32)
360
0
    }
361
362
    /* -------------------------------------------------------------------- */
363
    /*      Capture sibling list either from passed in values, or by        */
364
    /*      scanning for them only if requested through GetSiblingFiles().  */
365
    /* -------------------------------------------------------------------- */
366
0
    if (papszSiblingFilesIn != nullptr)
367
0
    {
368
0
        papszSiblingFiles = CSLDuplicate(papszSiblingFilesIn);
369
0
        bHasGotSiblingFiles = true;
370
0
    }
371
0
    else if (bStatOK && !bIsDirectory)
372
0
    {
373
0
        papszSiblingFiles = VSISiblingFiles(pszFilename);
374
0
        if (papszSiblingFiles != nullptr)
375
0
        {
376
0
            bHasGotSiblingFiles = true;
377
0
        }
378
0
        else
379
0
        {
380
0
            const char *pszOptionVal = VSIGetPathSpecificOption(
381
0
                pszFilename, "GDAL_DISABLE_READDIR_ON_OPEN", "NO");
382
0
            if (EQUAL(pszOptionVal, "EMPTY_DIR"))
383
0
            {
384
0
                papszSiblingFiles =
385
0
                    CSLAddString(nullptr, CPLGetFilename(pszFilename));
386
0
                bHasGotSiblingFiles = true;
387
0
            }
388
0
            else if (CPLTestBool(pszOptionVal))
389
0
            {
390
                /* skip reading the directory */
391
0
                papszSiblingFiles = nullptr;
392
0
                bHasGotSiblingFiles = true;
393
0
            }
394
0
            else
395
0
            {
396
                /* will be lazy loaded */
397
0
                papszSiblingFiles = nullptr;
398
0
                bHasGotSiblingFiles = false;
399
0
            }
400
0
        }
401
0
    }
402
0
    else
403
0
    {
404
0
        papszSiblingFiles = nullptr;
405
0
        bHasGotSiblingFiles = true;
406
0
    }
407
0
}
408
409
/************************************************************************/
410
/*                           ~GDALOpenInfo()                            */
411
/************************************************************************/
412
413
GDALOpenInfo::~GDALOpenInfo()
414
415
0
{
416
0
    VSIFree(pabyHeader);
417
0
    CPLFree(pszFilename);
418
419
0
    if (fpL != nullptr)
420
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
421
0
    CSLDestroy(papszSiblingFiles);
422
0
}
423
424
/************************************************************************/
425
/*                          GetSiblingFiles()                           */
426
/************************************************************************/
427
428
/** Return sibling files.
429
 *
430
 * If the list of sibling files has not already been established, it will be,
431
 * unless the GDAL_DISABLE_READDIR_ON_OPEN configuration option has been set to
432
 * YES or EMPTY_DIR when this instance was constructed.
433
 *
434
 * @return sibling files. Ownership belongs to "this".
435
 */
436
char **GDALOpenInfo::GetSiblingFiles()
437
0
{
438
0
    if (bHasGotSiblingFiles)
439
0
        return papszSiblingFiles;
440
0
    bHasGotSiblingFiles = true;
441
442
0
    papszSiblingFiles = VSISiblingFiles(pszFilename);
443
0
    if (papszSiblingFiles != nullptr)
444
0
    {
445
0
        return papszSiblingFiles;
446
0
    }
447
448
0
    const CPLString osDir = CPLGetDirnameSafe(pszFilename);
449
0
    const int nMaxFiles = atoi(VSIGetPathSpecificOption(
450
0
        pszFilename, "GDAL_READDIR_LIMIT_ON_OPEN", "1000"));
451
0
    papszSiblingFiles = VSIReadDirEx(osDir, nMaxFiles);
452
0
    if (nMaxFiles > 0 && CSLCount(papszSiblingFiles) > nMaxFiles)
453
0
    {
454
0
        CPLDebug("GDAL", "GDAL_READDIR_LIMIT_ON_OPEN reached on %s",
455
0
                 osDir.c_str());
456
0
        CSLDestroy(papszSiblingFiles);
457
0
        papszSiblingFiles = nullptr;
458
0
    }
459
460
0
    return papszSiblingFiles;
461
0
}
462
463
/************************************************************************/
464
/*                         StealSiblingFiles()                          */
465
/*                                                                      */
466
/*      Same as GetSiblingFiles() except that the list is stealed       */
467
/*      (ie ownership transferred to the caller) and the associated     */
468
/*      member variable is set to NULL.                                 */
469
/************************************************************************/
470
471
/** Return sibling files and steal reference
472
 * @return sibling files. Ownership below to the caller (must be freed with
473
 * CSLDestroy)
474
 */
475
char **GDALOpenInfo::StealSiblingFiles()
476
0
{
477
0
    char **papszRet = GetSiblingFiles();
478
0
    papszSiblingFiles = nullptr;
479
0
    return papszRet;
480
0
}
481
482
/************************************************************************/
483
/*                       AreSiblingFilesLoaded()                        */
484
/************************************************************************/
485
486
/** Return whether sibling files have been loaded.
487
 * @return true or false.
488
 */
489
bool GDALOpenInfo::AreSiblingFilesLoaded() const
490
0
{
491
0
    return bHasGotSiblingFiles;
492
0
}
493
494
/************************************************************************/
495
/*                            TryToIngest()                             */
496
/************************************************************************/
497
498
/** Ingest bytes from the file.
499
 * @param nBytes number of bytes to ingest.
500
 * @return TRUE if successful
501
 */
502
int GDALOpenInfo::TryToIngest(int nBytes)
503
0
{
504
0
    if (fpL == nullptr)
505
0
        return FALSE;
506
0
    if (nHeaderBytes < nHeaderBytesTried)
507
0
        return TRUE;
508
0
    pabyHeader = static_cast<GByte *>(CPLRealloc(pabyHeader, nBytes + 1));
509
0
    memset(pabyHeader, 0, nBytes + 1);
510
0
    VSIRewindL(fpL);
511
0
    nHeaderBytesTried = nBytes;
512
0
    nHeaderBytes = static_cast<int>(VSIFReadL(pabyHeader, 1, nBytes, fpL));
513
0
    VSIRewindL(fpL);
514
515
0
    return TRUE;
516
0
}
517
518
/************************************************************************/
519
/*                       IsSingleAllowedDriver()                        */
520
/************************************************************************/
521
522
/** Returns true if the driver name is the single in the list of allowed
523
 * drivers.
524
 *
525
 * @param pszDriverName Driver name to test.
526
 * @return true if the driver name is the single in the list of allowed
527
 * drivers.
528
 * @since GDAL 3.10
529
 */
530
bool GDALOpenInfo::IsSingleAllowedDriver(const char *pszDriverName) const
531
0
{
532
0
    return papszAllowedDrivers && papszAllowedDrivers[0] &&
533
0
           !papszAllowedDrivers[1] &&
534
0
           EQUAL(papszAllowedDrivers[0], pszDriverName);
535
0
}