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: 273 360 75.8 %
Date: 2025-07-05 13:22:42 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             : // 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 */

Generated by: LCOV version 1.14