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