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