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