Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements management of FileGDB .freelist
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_port.h"
30 :
31 : #include "filegdbtable.h"
32 : #include "filegdbtable_priv.h"
33 :
34 : #include <algorithm>
35 : #include <limits>
36 : #include <set>
37 :
38 : #include "cpl_string.h"
39 :
40 : namespace OpenFileGDB
41 : {
42 :
43 : constexpr uint32_t MINUS_ONE = 0xFFFFFFFFU;
44 :
45 : constexpr int MINIMUM_SIZE_FOR_FREELIST = 8;
46 :
47 : constexpr int nTrailerSize = 344;
48 : constexpr int nTrailerEntrySize = 2 * static_cast<int>(sizeof(uint32_t));
49 :
50 : constexpr int nPageSize = 4096;
51 : constexpr int nPageHeaderSize = 2 * static_cast<int>(sizeof(uint32_t));
52 :
53 : /************************************************************************/
54 : /* FindFreelistRangeSlot() */
55 : /************************************************************************/
56 :
57 : // Fibonacci suite
58 : static const uint32_t anHoleSizes[] = {
59 : 0, 8, 16, 24, 40, 64,
60 : 104, 168, 272, 440, 712, 1152,
61 : 1864, 3016, 4880, 7896, 12776, 20672,
62 : 33448, 54120, 87568, 141688, 229256, 370944,
63 : 600200, 971144, 1571344, 2542488, 4113832, 6656320,
64 : 10770152, 17426472, 28196624, 45623096, 73819720, 119442816,
65 : 193262536, 312705352, 505967888, 818673240, 1324641128, 2143314368,
66 : 3467955496U};
67 :
68 5301 : static int FindFreelistRangeSlot(uint32_t nSize)
69 : {
70 55044 : for (size_t i = 0; i < CPL_ARRAYSIZE(anHoleSizes) - 1; i++)
71 : {
72 55044 : if (/* nSize >= anHoleSizes[i] && */ nSize < anHoleSizes[i + 1])
73 : {
74 5301 : return static_cast<int>(i);
75 : }
76 : }
77 :
78 0 : CPLDebug("OpenFileGDB", "Hole larger than can be handled");
79 0 : return -1;
80 : }
81 :
82 : /************************************************************************/
83 : /* AddEntryToFreelist() */
84 : /************************************************************************/
85 :
86 2670 : void FileGDBTable::AddEntryToFreelist(uint64_t nOffset, uint32_t nSize)
87 : {
88 2670 : if (nSize < MINIMUM_SIZE_FOR_FREELIST)
89 2 : return;
90 :
91 : const std::string osFilename =
92 2668 : CPLResetExtension(m_osFilename.c_str(), "freelist");
93 2668 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb+");
94 2668 : if (fp == nullptr)
95 : {
96 : // Initialize an empty .freelist file
97 42 : fp = VSIFOpenL(osFilename.c_str(), "wb+");
98 42 : if (fp == nullptr)
99 0 : return;
100 42 : std::vector<GByte> abyTrailer;
101 42 : WriteUInt32(abyTrailer, 1);
102 42 : WriteUInt32(abyTrailer, MINUS_ONE);
103 1806 : for (int i = 0;
104 1806 : i < (nTrailerSize - nTrailerEntrySize) / nTrailerEntrySize; i++)
105 : {
106 1764 : WriteUInt32(abyTrailer, MINUS_ONE);
107 1764 : WriteUInt32(abyTrailer, 0);
108 : }
109 42 : CPLAssert(static_cast<int>(abyTrailer.size()) == nTrailerSize);
110 42 : if (VSIFWriteL(abyTrailer.data(), abyTrailer.size(), 1, fp) != 1)
111 : {
112 0 : VSIFCloseL(fp);
113 0 : return;
114 : }
115 : }
116 :
117 2668 : m_nHasFreeList = true;
118 :
119 : // Read trailer
120 2668 : VSIFSeekL(fp, 0, SEEK_END);
121 2668 : auto nFileSize = VSIFTellL(fp);
122 2668 : if ((nFileSize % nPageSize) != nTrailerSize)
123 : {
124 0 : VSIFCloseL(fp);
125 0 : return;
126 : }
127 :
128 2668 : VSIFSeekL(fp, nFileSize - nTrailerSize, SEEK_SET);
129 2668 : std::vector<GByte> abyTrailer(nTrailerSize);
130 2668 : if (VSIFReadL(abyTrailer.data(), abyTrailer.size(), 1, fp) != 1)
131 : {
132 0 : VSIFCloseL(fp);
133 0 : return;
134 : }
135 :
136 : // Determine in which "slot" of hole size the new entry belongs to
137 2668 : const int iSlot = FindFreelistRangeSlot(nSize);
138 2668 : if (iSlot < 0)
139 : {
140 0 : VSIFCloseL(fp);
141 0 : return;
142 : }
143 :
144 : // Read the last page index of the identified slot
145 : uint32_t nPageIdx =
146 2668 : GetUInt32(abyTrailer.data() + nTrailerEntrySize * iSlot, 0);
147 : uint32_t nPageCount;
148 :
149 2668 : std::vector<GByte> abyPage;
150 2668 : bool bRewriteTrailer = false;
151 :
152 2668 : const int nEntrySize =
153 2668 : static_cast<int>(sizeof(uint32_t)) + m_nTablxOffsetSize;
154 2668 : const int nMaxEntriesPerPage = (nPageSize - nPageHeaderSize) / nEntrySize;
155 2668 : int nNumEntries = 0;
156 :
157 2668 : if (nPageIdx == MINUS_ONE)
158 : {
159 : // There's no allocate page for that range
160 : // So allocate one.
161 :
162 65 : WriteUInt32(abyPage, nNumEntries);
163 65 : WriteUInt32(abyPage, MINUS_ONE);
164 65 : abyPage.resize(nPageSize);
165 :
166 : // Update trailer
167 65 : bRewriteTrailer = true;
168 65 : nPageIdx =
169 65 : static_cast<uint32_t>((nFileSize - nTrailerSize) / nPageSize);
170 65 : nPageCount = 1;
171 :
172 65 : nFileSize += nPageSize; // virtual extension
173 : }
174 : else
175 : {
176 2603 : nPageCount = GetUInt32(abyTrailer.data() + nTrailerEntrySize * iSlot +
177 : sizeof(uint32_t),
178 : 0);
179 :
180 2603 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
181 2603 : abyPage.resize(nPageSize);
182 2603 : if (VSIFReadL(abyPage.data(), abyPage.size(), 1, fp) != 1)
183 : {
184 0 : VSIFCloseL(fp);
185 0 : return;
186 : }
187 :
188 2603 : nNumEntries = GetUInt32(abyPage.data(), 0);
189 2603 : if (nNumEntries >= nMaxEntriesPerPage)
190 : {
191 : // Allocate new page
192 3 : abyPage.clear();
193 3 : nNumEntries = 0;
194 3 : WriteUInt32(abyPage, nNumEntries);
195 3 : WriteUInt32(abyPage, nPageIdx); // Link to previous page
196 3 : abyPage.resize(nPageSize);
197 :
198 : // Update trailer
199 3 : bRewriteTrailer = true;
200 3 : nPageIdx =
201 3 : static_cast<uint32_t>((nFileSize - nTrailerSize) / nPageSize);
202 3 : nPageCount++;
203 :
204 3 : nFileSize += nPageSize; // virtual extension
205 : }
206 : }
207 :
208 : // Add new entry into page
209 2668 : WriteUInt32(abyPage, nSize, nPageHeaderSize + nNumEntries * nEntrySize);
210 2668 : WriteFeatureOffset(nOffset, abyPage.data() + nPageHeaderSize +
211 2668 : nNumEntries * nEntrySize +
212 : sizeof(uint32_t));
213 :
214 : // Update page header
215 2668 : ++nNumEntries;
216 2668 : WriteUInt32(abyPage, nNumEntries, 0);
217 :
218 : // Flush page
219 2668 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
220 2668 : if (VSIFWriteL(abyPage.data(), abyPage.size(), 1, fp) != 1)
221 : {
222 0 : VSIFCloseL(fp);
223 0 : return;
224 : }
225 :
226 2668 : if (bRewriteTrailer)
227 : {
228 68 : WriteUInt32(abyTrailer, nPageIdx, nTrailerEntrySize * iSlot);
229 68 : WriteUInt32(abyTrailer, nPageCount,
230 68 : nTrailerEntrySize * iSlot + sizeof(uint32_t));
231 :
232 68 : VSIFSeekL(fp, nFileSize - nTrailerSize, 0);
233 68 : if (VSIFWriteL(abyTrailer.data(), abyTrailer.size(), 1, fp) != 1)
234 : {
235 0 : VSIFCloseL(fp);
236 0 : return;
237 : }
238 : }
239 :
240 2668 : m_bFreelistCanBeDeleted = false;
241 :
242 2668 : VSIFCloseL(fp);
243 : }
244 :
245 : /************************************************************************/
246 : /* GetOffsetOfFreeAreaFromFreeList() */
247 : /************************************************************************/
248 :
249 31448 : uint64_t FileGDBTable::GetOffsetOfFreeAreaFromFreeList(uint32_t nSize)
250 : {
251 31448 : if (nSize < MINIMUM_SIZE_FOR_FREELIST || m_nHasFreeList == FALSE ||
252 5173 : m_bFreelistCanBeDeleted)
253 26277 : return OFFSET_MINUS_ONE;
254 :
255 : const std::string osFilename =
256 10342 : CPLResetExtension(m_osFilename.c_str(), "freelist");
257 5171 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb+");
258 5171 : m_nHasFreeList = fp != nullptr;
259 5171 : if (fp == nullptr)
260 2538 : return OFFSET_MINUS_ONE;
261 :
262 : // Read trailer
263 2633 : VSIFSeekL(fp, 0, SEEK_END);
264 2633 : auto nFileSize = VSIFTellL(fp);
265 :
266 2633 : if ((nFileSize % nPageSize) != nTrailerSize)
267 : {
268 0 : VSIFCloseL(fp);
269 0 : return OFFSET_MINUS_ONE;
270 : }
271 :
272 2633 : VSIFSeekL(fp, nFileSize - nTrailerSize, SEEK_SET);
273 5266 : std::vector<GByte> abyTrailer(nTrailerSize);
274 2633 : if (VSIFReadL(abyTrailer.data(), abyTrailer.size(), 1, fp) != 1)
275 : {
276 0 : VSIFCloseL(fp);
277 0 : return OFFSET_MINUS_ONE;
278 : }
279 :
280 : // Determine in which "slot" of hole size the new entry belongs to
281 2633 : const int iSlot = FindFreelistRangeSlot(nSize);
282 2633 : if (iSlot < 0)
283 : {
284 0 : VSIFCloseL(fp);
285 0 : return OFFSET_MINUS_ONE;
286 : }
287 :
288 : // Read the last page index of the identified slot
289 : uint32_t nPageIdx =
290 2633 : GetUInt32(abyTrailer.data() + nTrailerEntrySize * iSlot, 0);
291 2633 : if (nPageIdx == MINUS_ONE)
292 : {
293 7 : VSIFCloseL(fp);
294 7 : return OFFSET_MINUS_ONE;
295 : }
296 :
297 2626 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
298 5252 : std::vector<GByte> abyPage(nPageSize);
299 2626 : if (VSIFReadL(abyPage.data(), abyPage.size(), 1, fp) != 1)
300 : {
301 0 : CPLDebug("OpenFileGDB", "Can't read freelist page %u", nPageIdx);
302 0 : VSIFCloseL(fp);
303 0 : return OFFSET_MINUS_ONE;
304 : }
305 :
306 2626 : const int nEntrySize =
307 2626 : static_cast<int>(sizeof(uint32_t)) + m_nTablxOffsetSize;
308 2626 : const int nMaxEntriesPerPage = (nPageSize - nPageHeaderSize) / nEntrySize;
309 :
310 : // Index of page that links to us
311 2626 : uint32_t nReferencingPage = MINUS_ONE;
312 5252 : std::vector<GByte> abyReferencingPage;
313 :
314 2626 : int nBestCandidateNumEntries = 0;
315 2626 : uint32_t nBestCandidatePageIdx = MINUS_ONE;
316 2626 : uint32_t nBestCandidateSize = std::numeric_limits<uint32_t>::max();
317 2626 : int iBestCandidateEntry = -1;
318 2626 : uint32_t nBestCandidateReferencingPage = MINUS_ONE;
319 5252 : std::vector<GByte> abyBestCandidateReferencingPage;
320 5252 : std::vector<GByte> abyBestCandidatePage;
321 :
322 7878 : std::set<uint32_t> aSetReadPages = {nPageIdx};
323 : while (true)
324 : {
325 : int nNumEntries = static_cast<int>(
326 3534 : std::min(GetUInt32(abyPage.data(), 0),
327 7068 : static_cast<uint32_t>(nMaxEntriesPerPage)));
328 3534 : bool bExactMatch = false;
329 335392 : for (int i = nNumEntries - 1; i >= 0; i--)
330 : {
331 : const uint32_t nFreeAreaSize =
332 334471 : GetUInt32(abyPage.data() + nPageHeaderSize + i * nEntrySize, 0);
333 334471 : if (nFreeAreaSize < anHoleSizes[iSlot] ||
334 334471 : nFreeAreaSize >= anHoleSizes[iSlot + 1])
335 : {
336 0 : CPLError(CE_Warning, CPLE_AppDefined,
337 : "Page %u of %s contains free area of unexpected size "
338 : "at entry %d",
339 : nPageIdx, osFilename.c_str(), i);
340 : }
341 334471 : else if (nFreeAreaSize == nSize ||
342 6235 : (nFreeAreaSize > nSize &&
343 : nFreeAreaSize < nBestCandidateSize))
344 : {
345 4365 : if (nBestCandidatePageIdx != nPageIdx)
346 : {
347 2615 : abyBestCandidatePage = abyPage;
348 2615 : abyBestCandidateReferencingPage = abyReferencingPage;
349 : }
350 4365 : nBestCandidatePageIdx = nPageIdx;
351 4365 : nBestCandidateReferencingPage = nReferencingPage;
352 4365 : iBestCandidateEntry = i;
353 4365 : nBestCandidateSize = nFreeAreaSize;
354 4365 : nBestCandidateNumEntries = nNumEntries;
355 4365 : if (nFreeAreaSize == nSize)
356 : {
357 2613 : bExactMatch = true;
358 2613 : break;
359 : }
360 : }
361 : }
362 :
363 3534 : if (!bExactMatch)
364 : {
365 : const uint32_t nPrevPage =
366 921 : GetUInt32(abyPage.data() + sizeof(uint32_t), 0);
367 921 : if (nPrevPage == MINUS_ONE)
368 : {
369 13 : break;
370 : }
371 :
372 908 : if (aSetReadPages.find(nPrevPage) != aSetReadPages.end())
373 : {
374 0 : CPLError(CE_Warning, CPLE_AppDefined,
375 : "Cyclic page refererencing in %s", osFilename.c_str());
376 0 : VSIFCloseL(fp);
377 0 : return OFFSET_MINUS_ONE;
378 : }
379 908 : aSetReadPages.insert(nPrevPage);
380 :
381 908 : abyReferencingPage = abyPage;
382 908 : nReferencingPage = nPageIdx;
383 908 : nPageIdx = nPrevPage;
384 908 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
385 908 : if (VSIFReadL(abyPage.data(), abyPage.size(), 1, fp) != 1)
386 : {
387 0 : CPLDebug("OpenFileGDB", "Can't read freelist page %u",
388 : nPageIdx);
389 0 : break;
390 : }
391 : }
392 : else
393 : {
394 2613 : break;
395 : }
396 908 : }
397 :
398 2626 : if (nBestCandidatePageIdx == MINUS_ONE)
399 : {
400 : // If we go here, it means that the trailer section references empty
401 : // pages or pages with features of unexpected size.
402 : // Shouldn't happen for well-behaved .freelist files
403 11 : VSIFCloseL(fp);
404 11 : return OFFSET_MINUS_ONE;
405 : }
406 :
407 2615 : nPageIdx = nBestCandidatePageIdx;
408 2615 : nReferencingPage = nBestCandidateReferencingPage;
409 2615 : abyPage = std::move(abyBestCandidatePage);
410 2615 : abyReferencingPage = std::move(abyBestCandidateReferencingPage);
411 :
412 : uint64_t nCandidateOffset =
413 2615 : ReadFeatureOffset(abyPage.data() + nPageHeaderSize +
414 2615 : iBestCandidateEntry * nEntrySize + sizeof(uint32_t));
415 :
416 : // Remove entry from page
417 2615 : if (iBestCandidateEntry < nBestCandidateNumEntries - 1)
418 : {
419 4752 : memmove(abyPage.data() + nPageHeaderSize +
420 1584 : iBestCandidateEntry * nEntrySize,
421 1584 : abyPage.data() + nPageHeaderSize +
422 1584 : (iBestCandidateEntry + 1) * nEntrySize,
423 1584 : cpl::fits_on<int>(
424 1584 : (nBestCandidateNumEntries - 1 - iBestCandidateEntry) *
425 : nEntrySize));
426 : }
427 2615 : memset(abyPage.data() + nPageHeaderSize +
428 2615 : (nBestCandidateNumEntries - 1) * nEntrySize,
429 : 0, nEntrySize);
430 :
431 2615 : nBestCandidateNumEntries--;
432 2615 : WriteUInt32(abyPage, nBestCandidateNumEntries, 0);
433 :
434 2615 : if (nBestCandidateNumEntries > 0)
435 : {
436 : // Rewrite updated page
437 2587 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
438 2587 : CPL_IGNORE_RET_VAL(VSIFWriteL(abyPage.data(), abyPage.size(), 1, fp));
439 : }
440 : else
441 : {
442 : const uint32_t nPrevPage =
443 28 : GetUInt32(abyPage.data() + sizeof(uint32_t), 0);
444 :
445 : // Link this newly free page to the previous one
446 : const uint32_t nLastFreePage =
447 28 : GetUInt32(abyTrailer.data() + sizeof(uint32_t), 0);
448 28 : WriteUInt32(abyPage, nLastFreePage, sizeof(uint32_t));
449 :
450 : // Rewrite updated page
451 28 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
452 28 : CPL_IGNORE_RET_VAL(VSIFWriteL(abyPage.data(), abyPage.size(), 1, fp));
453 :
454 : // Update trailer to add a new free page
455 28 : WriteUInt32(abyTrailer, nPageIdx, sizeof(uint32_t));
456 :
457 28 : if (nReferencingPage != MINUS_ONE)
458 : {
459 : // Links referencing page to previous page
460 0 : WriteUInt32(abyReferencingPage, nPrevPage, sizeof(uint32_t));
461 0 : VSIFSeekL(fp, static_cast<uint64_t>(nReferencingPage) * nPageSize,
462 : 0);
463 0 : CPL_IGNORE_RET_VAL(VSIFWriteL(abyReferencingPage.data(),
464 : abyReferencingPage.size(), 1, fp));
465 : }
466 : else
467 : {
468 : // and make the slot points to the previous page
469 28 : WriteUInt32(abyTrailer, nPrevPage, nTrailerEntrySize * iSlot);
470 : }
471 :
472 28 : uint32_t nPageCount = GetUInt32(
473 28 : abyTrailer.data() + nTrailerEntrySize * iSlot + sizeof(uint32_t),
474 : 0);
475 28 : if (nPageCount == 0)
476 : {
477 0 : CPLDebug("OpenFileGDB", "Wrong page count for %s at slot %d",
478 : osFilename.c_str(), iSlot);
479 : }
480 : else
481 : {
482 28 : nPageCount--;
483 28 : WriteUInt32(abyTrailer, nPageCount,
484 28 : nTrailerEntrySize * iSlot + sizeof(uint32_t));
485 28 : if (nPageCount == 0)
486 : {
487 : // Check if the freelist no longer contains pages with free
488 : // slots
489 25 : m_bFreelistCanBeDeleted = true;
490 333 : for (int i = 1; i < nTrailerSize / nTrailerEntrySize; i++)
491 : {
492 326 : if (GetUInt32(abyTrailer.data() + i * nTrailerEntrySize +
493 : sizeof(uint32_t),
494 326 : 0) != 0)
495 : {
496 18 : m_bFreelistCanBeDeleted = false;
497 18 : break;
498 : }
499 : }
500 : }
501 : }
502 :
503 28 : VSIFSeekL(fp, nFileSize - nTrailerSize, 0);
504 28 : CPL_IGNORE_RET_VAL(
505 28 : VSIFWriteL(abyTrailer.data(), abyTrailer.size(), 1, fp));
506 : }
507 :
508 : // Extra precaution: check that the uint32_t at offset nOffset is a
509 : // negated compatible size
510 2615 : auto nOffset = nCandidateOffset;
511 2615 : VSIFSeekL(m_fpTable, nOffset, 0);
512 2615 : uint32_t nOldSize = 0;
513 2615 : if (!ReadUInt32(m_fpTable, nOldSize) || (nOldSize >> 31) == 0)
514 : {
515 0 : nOffset = OFFSET_MINUS_ONE;
516 : }
517 : else
518 : {
519 2615 : nOldSize = static_cast<uint32_t>(-static_cast<int>(nOldSize));
520 2615 : if (nOldSize < nSize - sizeof(uint32_t))
521 : {
522 0 : nOffset = OFFSET_MINUS_ONE;
523 : }
524 : }
525 2615 : if (nOffset == OFFSET_MINUS_ONE)
526 : {
527 0 : CPLDebug("OpenFileGDB",
528 : "%s references a free area at offset " CPL_FRMT_GUIB
529 : ", but it does not appear to match a deleted "
530 : "feature",
531 : osFilename.c_str(), static_cast<GUIntBig>(nCandidateOffset));
532 : }
533 :
534 2615 : VSIFCloseL(fp);
535 2615 : return nOffset;
536 : }
537 :
538 : /************************************************************************/
539 : /* CheckFreeListConsistency() */
540 : /************************************************************************/
541 :
542 8 : bool FileGDBTable::CheckFreeListConsistency()
543 : {
544 : const std::string osFilename =
545 16 : CPLResetExtension(m_osFilename.c_str(), "freelist");
546 8 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb");
547 8 : if (fp == nullptr)
548 0 : return true;
549 :
550 : // Read trailer
551 8 : VSIFSeekL(fp, 0, SEEK_END);
552 8 : auto nFileSize = VSIFTellL(fp);
553 :
554 8 : if ((nFileSize % nPageSize) != nTrailerSize)
555 : {
556 0 : CPLError(CE_Failure, CPLE_AppDefined, "Bad file size");
557 0 : VSIFCloseL(fp);
558 0 : return false;
559 : }
560 :
561 8 : VSIFSeekL(fp, nFileSize - nTrailerSize, SEEK_SET);
562 16 : std::vector<GByte> abyTrailer(nTrailerSize);
563 8 : if (VSIFReadL(abyTrailer.data(), abyTrailer.size(), 1, fp) != 1)
564 : {
565 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot read trailer section");
566 0 : VSIFCloseL(fp);
567 0 : return false;
568 : }
569 :
570 8 : if (GetUInt32(abyTrailer.data(), 0) != 1)
571 : {
572 0 : CPLError(CE_Failure, CPLE_AppDefined,
573 : "Unexpected value for first uint32 of trailer section");
574 0 : VSIFCloseL(fp);
575 0 : return false;
576 : }
577 :
578 16 : std::vector<GByte> abyPage(nPageSize);
579 16 : std::set<uint32_t> setVisitedPages;
580 :
581 : // Check free pages
582 8 : uint32_t nFreePage = GetUInt32(abyTrailer.data() + sizeof(uint32_t), 0);
583 34 : while (nFreePage != MINUS_ONE)
584 : {
585 26 : if (setVisitedPages.find(nFreePage) != setVisitedPages.end())
586 : {
587 0 : CPLError(CE_Failure, CPLE_AppDefined,
588 : "Cyclic page refererencing in free pages");
589 0 : VSIFCloseL(fp);
590 0 : return false;
591 : }
592 :
593 26 : VSIFSeekL(fp, static_cast<uint64_t>(nFreePage) * nPageSize, 0);
594 26 : if (VSIFReadL(abyPage.data(), abyPage.size(), 1, fp) != 1)
595 : {
596 0 : CPLError(CE_Failure, CPLE_AppDefined, "Can't read freelist page %u",
597 : nFreePage);
598 0 : VSIFCloseL(fp);
599 0 : return false;
600 : }
601 :
602 26 : setVisitedPages.insert(nFreePage);
603 :
604 26 : if (GetUInt32(abyPage.data(), 0) != 0)
605 : {
606 0 : CPLError(CE_Failure, CPLE_AppDefined,
607 : "Unexpected value for first uint32 of free page");
608 0 : VSIFCloseL(fp);
609 0 : return false;
610 : }
611 :
612 26 : nFreePage = GetUInt32(abyPage.data() + sizeof(uint32_t), 0);
613 : }
614 :
615 : // Check active pages
616 8 : const int nEntrySize =
617 8 : static_cast<int>(sizeof(uint32_t)) + m_nTablxOffsetSize;
618 8 : const int nMaxEntriesPerPage = (nPageSize - nPageHeaderSize) / nEntrySize;
619 :
620 16 : std::set<uint64_t> aSetOffsets;
621 :
622 344 : for (int iSlot = 1; iSlot < (nTrailerSize / nTrailerEntrySize); iSlot++)
623 : {
624 : uint32_t nPageIdx =
625 336 : GetUInt32(abyTrailer.data() + iSlot * nTrailerEntrySize, 0);
626 336 : uint32_t nActualCount = 0;
627 361 : while (nPageIdx != MINUS_ONE)
628 : {
629 25 : if (setVisitedPages.find(nPageIdx) != setVisitedPages.end())
630 : {
631 0 : CPLError(CE_Failure, CPLE_AppDefined,
632 : "Cyclic page refererencing or page referenced more "
633 : "than once");
634 0 : VSIFCloseL(fp);
635 0 : return false;
636 : }
637 :
638 25 : VSIFSeekL(fp, static_cast<uint64_t>(nPageIdx) * nPageSize, 0);
639 25 : if (VSIFReadL(abyPage.data(), abyPage.size(), 1, fp) != 1)
640 : {
641 0 : CPLError(CE_Failure, CPLE_AppDefined,
642 : "Can't read active page %u", nPageIdx);
643 0 : VSIFCloseL(fp);
644 0 : return false;
645 : }
646 :
647 25 : setVisitedPages.insert(nPageIdx);
648 25 : nActualCount++;
649 :
650 25 : const uint32_t nEntries = GetUInt32(abyPage.data(), 0);
651 25 : if (nEntries == 0 ||
652 25 : nEntries > static_cast<uint32_t>(nMaxEntriesPerPage))
653 : {
654 0 : CPLError(
655 : CE_Failure, CPLE_AppDefined,
656 : "Unexpected value for entries count of active page %u: %d",
657 : nPageIdx, nEntries);
658 0 : VSIFCloseL(fp);
659 0 : return false;
660 : }
661 :
662 2628 : for (uint32_t i = 0; i < nEntries; ++i)
663 : {
664 2603 : const uint32_t nFreeAreaSize = GetUInt32(
665 2603 : abyPage.data() + nPageHeaderSize + i * nEntrySize, 0);
666 2603 : if (nFreeAreaSize < anHoleSizes[iSlot] ||
667 2603 : nFreeAreaSize >= anHoleSizes[iSlot + 1])
668 : {
669 0 : CPLError(CE_Failure, CPLE_AppDefined,
670 : "Page %u contains free area of unexpected size at "
671 : "entry %u",
672 : nPageIdx, i);
673 0 : VSIFCloseL(fp);
674 0 : return false;
675 : }
676 :
677 : const uint64_t nOffset =
678 2603 : ReadFeatureOffset(abyPage.data() + nPageHeaderSize +
679 2603 : i * nEntrySize + sizeof(uint32_t));
680 :
681 2603 : VSIFSeekL(m_fpTable, nOffset, 0);
682 2603 : uint32_t nOldSize = 0;
683 2603 : if (!ReadUInt32(m_fpTable, nOldSize))
684 : {
685 0 : CPLError(CE_Failure, CPLE_AppDefined,
686 : "Page %u contains free area that points to "
687 : "invalid offset " CPL_FRMT_GUIB,
688 : nPageIdx, static_cast<GUIntBig>(nOffset));
689 0 : VSIFCloseL(fp);
690 0 : return false;
691 : }
692 5206 : if ((nOldSize >> 31) == 0 ||
693 2603 : (nOldSize = static_cast<uint32_t>(-static_cast<int>(
694 2603 : nOldSize))) != nFreeAreaSize - sizeof(uint32_t))
695 : {
696 0 : CPLError(CE_Failure, CPLE_AppDefined,
697 : "Page %u contains free area that points to dead "
698 : "zone at offset " CPL_FRMT_GUIB
699 : " of unexpected size: %u",
700 : nPageIdx, static_cast<GUIntBig>(nOffset),
701 : nOldSize);
702 0 : VSIFCloseL(fp);
703 0 : return false;
704 : }
705 :
706 2603 : if (aSetOffsets.find(nOffset) != aSetOffsets.end())
707 : {
708 0 : CPLError(CE_Failure, CPLE_AppDefined,
709 : "Page %u contains free area that points to "
710 : "offset " CPL_FRMT_GUIB " already referenced",
711 : nPageIdx, static_cast<GUIntBig>(nOffset));
712 0 : VSIFCloseL(fp);
713 0 : return false;
714 : }
715 2603 : aSetOffsets.insert(nOffset);
716 : }
717 :
718 25 : nPageIdx = GetUInt32(abyPage.data() + sizeof(uint32_t), 0);
719 : }
720 :
721 336 : const uint32_t nPageCount = GetUInt32(
722 336 : abyTrailer.data() + iSlot * nTrailerEntrySize + sizeof(uint32_t),
723 : 0);
724 336 : if (nPageCount != nActualCount)
725 : {
726 0 : CPLError(CE_Failure, CPLE_AppDefined,
727 : "Unexpected value for page count of slot %d: %u vs %u",
728 : iSlot, nPageCount, nActualCount);
729 0 : VSIFCloseL(fp);
730 0 : return false;
731 : }
732 : }
733 :
734 8 : const auto nExpectedPageCount = (nFileSize - nTrailerSize) / nPageSize;
735 8 : if (setVisitedPages.size() != nExpectedPageCount)
736 : {
737 0 : CPLError(CE_Failure, CPLE_AppDefined,
738 : "%u pages have been visited, but there are %u pages in total",
739 0 : static_cast<uint32_t>(setVisitedPages.size()),
740 : static_cast<uint32_t>(nExpectedPageCount));
741 0 : VSIFCloseL(fp);
742 0 : return false;
743 : }
744 :
745 8 : VSIFCloseL(fp);
746 8 : return true;
747 : }
748 :
749 : /************************************************************************/
750 : /* DeleteFreeList() */
751 : /************************************************************************/
752 :
753 46 : void FileGDBTable::DeleteFreeList()
754 : {
755 46 : m_bFreelistCanBeDeleted = false;
756 46 : m_nHasFreeList = -1;
757 46 : VSIUnlink(CPLResetExtension(m_osFilename.c_str(), "freelist"));
758 46 : }
759 :
760 : } /* namespace OpenFileGDB */
|