Coverage Report

Created: 2025-06-13 06:18

/src/gdal/gcore/gdalpamproxydb.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Core
4
 * Purpose:  Implementation of the GDAL PAM Proxy database interface.
5
 *           The proxy db is used to associate .aux.xml files in a temp
6
 *           directory - used for files for which aux.xml files can't be
7
 *           created (i.e. read-only file systems).
8
 * Author:   Frank Warmerdam, warmerdam@pobox.com
9
 *
10
 ******************************************************************************
11
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
16
#include "cpl_port.h"
17
#include "gdal_pam.h"
18
19
#include <cerrno>
20
#include <cstddef>
21
#include <cstdio>
22
#include <cstdlib>
23
#include <cstring>
24
#if HAVE_FCNTL_H
25
#include <fcntl.h>
26
#endif
27
28
#include <memory>
29
#include <string>
30
#include <vector>
31
32
#include "cpl_conv.h"
33
#include "cpl_error.h"
34
#include "cpl_multiproc.h"
35
#include "cpl_string.h"
36
#include "cpl_vsi.h"
37
#include "gdal_pam.h"
38
#include "ogr_spatialref.h"
39
40
/************************************************************************/
41
/* ==================================================================== */
42
/*                            GDALPamProxyDB                            */
43
/* ==================================================================== */
44
/************************************************************************/
45
46
class GDALPamProxyDB
47
{
48
  public:
49
    CPLString osProxyDBDir{};
50
51
    int nUpdateCounter = -1;
52
53
    std::vector<CPLString> aosOriginalFiles{};
54
    std::vector<CPLString> aosProxyFiles{};
55
56
    void CheckLoadDB();
57
    void LoadDB();
58
    void SaveDB();
59
};
60
61
static bool bProxyDBInitialized = FALSE;
62
static GDALPamProxyDB *poProxyDB = nullptr;
63
static CPLMutex *hProxyDBLock = nullptr;
64
65
/************************************************************************/
66
/*                            CheckLoadDB()                             */
67
/*                                                                      */
68
/*      Eventually we want to check if the file has changed, and if     */
69
/*      so, force it to be reloaded.  TODO:                             */
70
/************************************************************************/
71
72
void GDALPamProxyDB::CheckLoadDB()
73
74
0
{
75
0
    if (nUpdateCounter == -1)
76
0
        LoadDB();
77
0
}
78
79
/************************************************************************/
80
/*                               LoadDB()                               */
81
/*                                                                      */
82
/*      It is assumed the caller already holds the lock.                */
83
/************************************************************************/
84
85
void GDALPamProxyDB::LoadDB()
86
87
0
{
88
    /* -------------------------------------------------------------------- */
89
    /*      Open the database relating original names to proxy .aux.xml     */
90
    /*      file names.                                                     */
91
    /* -------------------------------------------------------------------- */
92
0
    const std::string osDBName =
93
0
        CPLFormFilenameSafe(osProxyDBDir, "gdal_pam_proxy", "dat");
94
0
    VSILFILE *fpDB = VSIFOpenL(osDBName.c_str(), "r");
95
96
0
    nUpdateCounter = 0;
97
0
    if (fpDB == nullptr)
98
0
        return;
99
100
    /* -------------------------------------------------------------------- */
101
    /*      Read header, verify and extract update counter.                 */
102
    /* -------------------------------------------------------------------- */
103
0
    const size_t nHeaderSize = 100;
104
0
    GByte abyHeader[nHeaderSize] = {'\0'};
105
106
0
    if (VSIFReadL(abyHeader, 1, nHeaderSize, fpDB) != nHeaderSize ||
107
0
        !STARTS_WITH(reinterpret_cast<char *>(abyHeader), "GDAL_PROXY"))
108
0
    {
109
0
        CPLError(CE_Failure, CPLE_AppDefined,
110
0
                 "Problem reading %s header - short or corrupt?",
111
0
                 osDBName.c_str());
112
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
113
0
        return;
114
0
    }
115
116
0
    nUpdateCounter = atoi(reinterpret_cast<char *>(abyHeader) + 10);
117
118
    /* -------------------------------------------------------------------- */
119
    /*      Read the file in one gulp.                                      */
120
    /* -------------------------------------------------------------------- */
121
0
    if (VSIFSeekL(fpDB, 0, SEEK_END) != 0)
122
0
    {
123
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
124
0
        return;
125
0
    }
126
0
    const int nBufLength = static_cast<int>(VSIFTellL(fpDB) - nHeaderSize);
127
0
    if (VSIFSeekL(fpDB, nHeaderSize, SEEK_SET) != 0)
128
0
    {
129
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
130
0
        return;
131
0
    }
132
0
    char *pszDBData = static_cast<char *>(CPLCalloc(1, nBufLength + 1));
133
0
    if (VSIFReadL(pszDBData, 1, nBufLength, fpDB) !=
134
0
        static_cast<size_t>(nBufLength))
135
0
    {
136
0
        CPLFree(pszDBData);
137
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
138
0
        return;
139
0
    }
140
141
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
142
143
    /* -------------------------------------------------------------------- */
144
    /*      Parse the list of in/out names.                                 */
145
    /* -------------------------------------------------------------------- */
146
0
    int iNext = 0;
147
148
0
    while (iNext < nBufLength)
149
0
    {
150
0
        CPLString osOriginal;
151
0
        osOriginal.assign(pszDBData + iNext);
152
153
0
        for (; iNext < nBufLength && pszDBData[iNext] != '\0'; iNext++)
154
0
        {
155
0
        }
156
157
0
        if (iNext == nBufLength)
158
0
            break;
159
160
0
        iNext++;
161
162
0
        CPLString osProxy = osProxyDBDir;
163
0
        osProxy += "/";
164
0
        osProxy += pszDBData + iNext;
165
166
0
        for (; iNext < nBufLength && pszDBData[iNext] != '\0'; iNext++)
167
0
        {
168
0
        }
169
0
        iNext++;
170
171
0
        aosOriginalFiles.push_back(std::move(osOriginal));
172
0
        aosProxyFiles.push_back(std::move(osProxy));
173
0
    }
174
175
0
    CPLFree(pszDBData);
176
0
}
177
178
/************************************************************************/
179
/*                               SaveDB()                               */
180
/************************************************************************/
181
182
void GDALPamProxyDB::SaveDB()
183
184
0
{
185
    /* -------------------------------------------------------------------- */
186
    /*      Open the database relating original names to proxy .aux.xml     */
187
    /*      file names.                                                     */
188
    /* -------------------------------------------------------------------- */
189
0
    const std::string osDBName =
190
0
        CPLFormFilenameSafe(osProxyDBDir, "gdal_pam_proxy", "dat");
191
192
0
    void *hLock = CPLLockFile(osDBName.c_str(), 1.0);
193
194
    // proceed even if lock fails - we need CPLBreakLockFile()!
195
0
    if (hLock == nullptr)
196
0
    {
197
0
        CPLError(CE_Warning, CPLE_AppDefined,
198
0
                 "GDALPamProxyDB::SaveDB() - "
199
0
                 "Failed to lock %s file, proceeding anyways.",
200
0
                 osDBName.c_str());
201
0
    }
202
203
0
    VSILFILE *fpDB = VSIFOpenL(osDBName.c_str(), "w");
204
0
    if (fpDB == nullptr)
205
0
    {
206
0
        if (hLock)
207
0
            CPLUnlockFile(hLock);
208
0
        CPLError(CE_Failure, CPLE_AppDefined,
209
0
                 "Failed to save %s Pam Proxy DB.\n%s", osDBName.c_str(),
210
0
                 VSIStrerror(errno));
211
0
        return;
212
0
    }
213
214
    /* -------------------------------------------------------------------- */
215
    /*      Write header.                                                   */
216
    /* -------------------------------------------------------------------- */
217
0
    const size_t nHeaderSize = 100;
218
0
    GByte abyHeader[nHeaderSize] = {'\0'};
219
220
0
    memset(abyHeader, ' ', sizeof(abyHeader));
221
0
    memcpy(reinterpret_cast<char *>(abyHeader), "GDAL_PROXY", 10);
222
0
    snprintf(reinterpret_cast<char *>(abyHeader) + 10, sizeof(abyHeader) - 10,
223
0
             "%9d", nUpdateCounter);
224
225
0
    if (VSIFWriteL(abyHeader, 1, nHeaderSize, fpDB) != nHeaderSize)
226
0
    {
227
0
        CPLError(CE_Failure, CPLE_AppDefined,
228
0
                 "Failed to write complete %s Pam Proxy DB.\n%s",
229
0
                 osDBName.c_str(), VSIStrerror(errno));
230
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
231
0
        VSIUnlink(osDBName.c_str());
232
0
        if (hLock)
233
0
            CPLUnlockFile(hLock);
234
0
        return;
235
0
    }
236
237
    /* -------------------------------------------------------------------- */
238
    /*      Write names.                                                    */
239
    /* -------------------------------------------------------------------- */
240
0
    for (unsigned int i = 0; i < aosOriginalFiles.size(); i++)
241
0
    {
242
0
        size_t nCount =
243
0
            VSIFWriteL(aosOriginalFiles[i].c_str(),
244
0
                       strlen(aosOriginalFiles[i].c_str()) + 1, 1, fpDB);
245
246
0
        const char *pszProxyFile = CPLGetFilename(aosProxyFiles[i]);
247
0
        nCount += VSIFWriteL(pszProxyFile, strlen(pszProxyFile) + 1, 1, fpDB);
248
249
0
        if (nCount != 2)
250
0
        {
251
0
            CPLError(CE_Failure, CPLE_AppDefined,
252
0
                     "Failed to write complete %s Pam Proxy DB.\n%s",
253
0
                     osDBName.c_str(), VSIStrerror(errno));
254
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpDB));
255
0
            VSIUnlink(osDBName.c_str());
256
0
            if (hLock)
257
0
                CPLUnlockFile(hLock);
258
0
            return;
259
0
        }
260
0
    }
261
262
0
    if (VSIFCloseL(fpDB) != 0)
263
0
    {
264
0
        CPLError(CE_Failure, CPLE_FileIO, "I/O error");
265
0
    }
266
267
0
    if (hLock)
268
0
        CPLUnlockFile(hLock);
269
0
}
270
271
/************************************************************************/
272
/*                            InitProxyDB()                             */
273
/*                                                                      */
274
/*      Initialize ProxyDB (if it isn't already initialized).           */
275
/************************************************************************/
276
277
static void InitProxyDB()
278
279
0
{
280
0
    if (!bProxyDBInitialized)
281
0
    {
282
0
        CPLMutexHolderD(&hProxyDBLock);
283
        // cppcheck-suppress identicalInnerCondition
284
        // cppcheck-suppress knownConditionTrueFalse
285
0
        if (!bProxyDBInitialized)
286
0
        {
287
0
            const char *pszProxyDir =
288
0
                CPLGetConfigOption("GDAL_PAM_PROXY_DIR", nullptr);
289
290
0
            if (pszProxyDir)
291
0
            {
292
0
                poProxyDB = new GDALPamProxyDB();
293
0
                poProxyDB->osProxyDBDir = pszProxyDir;
294
0
            }
295
0
        }
296
297
0
        bProxyDBInitialized = true;
298
0
    }
299
0
}
300
301
/************************************************************************/
302
/*                          PamCleanProxyDB()                           */
303
/************************************************************************/
304
305
void PamCleanProxyDB()
306
307
0
{
308
0
    {
309
0
        CPLMutexHolderD(&hProxyDBLock);
310
311
0
        bProxyDBInitialized = false;
312
313
0
        delete poProxyDB;
314
0
        poProxyDB = nullptr;
315
0
    }
316
317
0
    CPLDestroyMutex(hProxyDBLock);
318
0
    hProxyDBLock = nullptr;
319
0
}
320
321
/************************************************************************/
322
/*                            PamGetProxy()                             */
323
/************************************************************************/
324
325
const char *PamGetProxy(const char *pszOriginal)
326
327
0
{
328
0
    InitProxyDB();
329
330
0
    if (poProxyDB == nullptr)
331
0
        return nullptr;
332
333
0
    CPLMutexHolderD(&hProxyDBLock);
334
335
0
    poProxyDB->CheckLoadDB();
336
337
0
    for (unsigned int i = 0; i < poProxyDB->aosOriginalFiles.size(); i++)
338
0
    {
339
0
        if (strcmp(poProxyDB->aosOriginalFiles[i], pszOriginal) == 0)
340
0
            return poProxyDB->aosProxyFiles[i];
341
0
    }
342
343
0
    return nullptr;
344
0
}
345
346
/************************************************************************/
347
/*                          PamAllocateProxy()                          */
348
/************************************************************************/
349
350
const char *PamAllocateProxy(const char *pszOriginal)
351
352
0
{
353
0
    InitProxyDB();
354
355
0
    if (poProxyDB == nullptr)
356
0
        return nullptr;
357
358
0
    CPLMutexHolderD(&hProxyDBLock);
359
360
0
    poProxyDB->CheckLoadDB();
361
362
    /* -------------------------------------------------------------------- */
363
    /*      Form the proxy filename based on the original path if           */
364
    /*      possible, but dummy out any questionable characters, path       */
365
    /*      delimiters and such.  This is intended to make the proxy        */
366
    /*      name be identifiable by folks digging around in the proxy       */
367
    /*      database directory.                                             */
368
    /*                                                                      */
369
    /*      We also need to be careful about length.                        */
370
    /* -------------------------------------------------------------------- */
371
0
    CPLString osRevProxyFile;
372
373
0
    int i = static_cast<int>(strlen(pszOriginal)) - 1;
374
0
    while (i >= 0 && osRevProxyFile.size() < 220)
375
0
    {
376
0
        if (i > 6 && STARTS_WITH_CI(pszOriginal + i - 5, ":::OVR"))
377
0
            i -= 6;
378
379
        // make some effort to break long names at path delimiters.
380
0
        if ((pszOriginal[i] == '/' || pszOriginal[i] == '\\') &&
381
0
            osRevProxyFile.size() > 200)
382
0
            break;
383
384
0
        if ((pszOriginal[i] >= 'A' && pszOriginal[i] <= 'Z') ||
385
0
            (pszOriginal[i] >= 'a' && pszOriginal[i] <= 'z') ||
386
0
            (pszOriginal[i] >= '0' && pszOriginal[i] <= '9') ||
387
0
            pszOriginal[i] == '.')
388
0
            osRevProxyFile += pszOriginal[i];
389
0
        else
390
0
            osRevProxyFile += '_';
391
392
0
        i--;
393
0
    }
394
395
0
    CPLString osOriginal = pszOriginal;
396
0
    CPLString osProxy = poProxyDB->osProxyDBDir + "/";
397
398
0
    CPLString osCounter;
399
0
    osCounter.Printf("%06d_", poProxyDB->nUpdateCounter++);
400
0
    osProxy += osCounter;
401
402
0
    for (i = static_cast<int>(osRevProxyFile.size()) - 1; i >= 0; i--)
403
0
        osProxy += osRevProxyFile[i];
404
405
0
    if (!osOriginal.endsWith(".gmac"))
406
0
    {
407
0
        if (osOriginal.find(":::OVR") != CPLString::npos)
408
0
            osProxy += ".ovr";
409
0
        else
410
0
            osProxy += ".aux.xml";
411
0
    }
412
413
    /* -------------------------------------------------------------------- */
414
    /*      Add the proxy and the original to the proxy list and resave     */
415
    /*      the database.                                                   */
416
    /* -------------------------------------------------------------------- */
417
0
    poProxyDB->aosOriginalFiles.push_back(std::move(osOriginal));
418
0
    poProxyDB->aosProxyFiles.push_back(std::move(osProxy));
419
420
0
    poProxyDB->SaveDB();
421
422
0
    return PamGetProxy(pszOriginal);
423
0
}