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