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