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