Coverage Report

Created: 2025-08-28 06:57

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