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