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