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

Generated by: LCOV version 1.14