LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - filegdbtable_freelist.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 274 365 75.1 %
Date: 2025-08-01 10:10:57 Functions: 5 5 100.0 %

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

Generated by: LCOV version 1.14