LCOV - code coverage report
Current view: top level - port - cpl_vsil_abstract_archive.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 299 332 90.1 %
Date: 2025-09-10 17:48:50 Functions: 10 12 83.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement VSI large file api for archive files.
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "cpl_vsi_virtual.h"
      15             : 
      16             : #include <cstring>
      17             : #include <ctime>
      18             : #include <fcntl.h>
      19             : #include <map>
      20             : #include <memory>
      21             : #include <set>
      22             : #include <string>
      23             : #include <utility>
      24             : #include <vector>
      25             : 
      26             : #include "cpl_conv.h"
      27             : #include "cpl_error.h"
      28             : #include "cpl_multiproc.h"
      29             : #include "cpl_string.h"
      30             : #include "cpl_vsi.h"
      31             : 
      32             : //! @cond Doxygen_Suppress
      33             : 
      34       43701 : static bool IsEitherSlash(char c)
      35             : {
      36       43701 :     return c == '/' || c == '\\';
      37             : }
      38             : 
      39             : /************************************************************************/
      40             : /*                    ~VSIArchiveEntryFileOffset()                      */
      41             : /************************************************************************/
      42             : 
      43             : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
      44             : 
      45             : /************************************************************************/
      46             : /*                        ~VSIArchiveReader()                           */
      47             : /************************************************************************/
      48             : 
      49             : VSIArchiveReader::~VSIArchiveReader() = default;
      50             : 
      51             : /************************************************************************/
      52             : /*                        ~VSIArchiveContent()                          */
      53             : /************************************************************************/
      54             : 
      55             : VSIArchiveContent::~VSIArchiveContent() = default;
      56             : 
      57             : /************************************************************************/
      58             : /*                   VSIArchiveFilesystemHandler()                      */
      59             : /************************************************************************/
      60             : 
      61             : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler() = default;
      62             : 
      63             : /************************************************************************/
      64             : /*                   ~VSIArchiveFilesystemHandler()                     */
      65             : /************************************************************************/
      66             : 
      67             : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler() = default;
      68             : 
      69             : /************************************************************************/
      70             : /*                       GetStrippedFilename()                          */
      71             : /************************************************************************/
      72             : 
      73        5260 : static std::string GetStrippedFilename(const std::string &osFileName,
      74             :                                        bool &bIsDir)
      75             : {
      76        5260 :     bIsDir = false;
      77        5260 :     int nStartPos = 0;
      78             : 
      79             :     // Remove ./ pattern at the beginning of a filename.
      80        5260 :     if (osFileName.size() >= 2 && osFileName[0] == '.' && osFileName[1] == '/')
      81             :     {
      82           0 :         nStartPos = 2;
      83           0 :         if (osFileName.size() == 2)
      84           0 :             return std::string();
      85             :     }
      86             : 
      87       10520 :     std::string ret(osFileName, nStartPos);
      88      186095 :     for (char &c : ret)
      89             :     {
      90      180835 :         if (c == '\\')
      91           0 :             c = '/';
      92             :     }
      93             : 
      94        5260 :     bIsDir = !ret.empty() && ret.back() == '/';
      95        5260 :     if (bIsDir)
      96             :     {
      97             :         // Remove trailing slash.
      98         241 :         ret.pop_back();
      99             :     }
     100        5260 :     return ret;
     101             : }
     102             : 
     103             : /************************************************************************/
     104             : /*                        BuildDirectoryIndex()                         */
     105             : /************************************************************************/
     106             : 
     107         225 : static void BuildDirectoryIndex(VSIArchiveContent *content)
     108             : {
     109         225 :     content->dirIndex.clear();
     110         225 :     const int nEntries = static_cast<int>(content->entries.size());
     111        6598 :     for (int i = 0; i < nEntries; i++)
     112             :     {
     113        6373 :         const char *fileName = content->entries[i].fileName.c_str();
     114       12746 :         std::string parentDir = CPLGetPathSafe(fileName);
     115        6373 :         content->dirIndex[parentDir].push_back(i);
     116             :     }
     117         225 : }
     118             : 
     119             : /************************************************************************/
     120             : /*                       GetContentOfArchive()                          */
     121             : /************************************************************************/
     122             : 
     123             : const VSIArchiveContent *
     124        8642 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
     125             :                                                  VSIArchiveReader *poReader)
     126             : {
     127       17284 :     std::unique_lock oLock(oMutex);
     128             : 
     129             :     VSIStatBufL sStat;
     130        8642 :     if (VSIStatL(archiveFilename, &sStat) != 0)
     131           0 :         return nullptr;
     132             : 
     133        8642 :     auto oIter = oFileList.find(archiveFilename);
     134        8642 :     if (oIter != oFileList.end())
     135             :     {
     136        8417 :         const VSIArchiveContent *content = oIter->second.get();
     137        8417 :         if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
     138        8416 :             static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
     139             :         {
     140           8 :             CPLDebug("VSIArchive",
     141             :                      "The content of %s has changed since it was cached",
     142             :                      archiveFilename);
     143           8 :             oFileList.erase(archiveFilename);
     144             :         }
     145             :         else
     146             :         {
     147        8409 :             return content;
     148             :         }
     149             :     }
     150             : 
     151         233 :     std::unique_ptr<VSIArchiveReader> temporaryReader;  // keep in that scope
     152         233 :     if (poReader == nullptr)
     153             :     {
     154         232 :         temporaryReader = CreateReader(archiveFilename);
     155         232 :         poReader = temporaryReader.get();
     156         232 :         if (!poReader)
     157           8 :             return nullptr;
     158             :     }
     159             : 
     160         225 :     if (poReader->GotoFirstFile() == FALSE)
     161             :     {
     162           0 :         return nullptr;
     163             :     }
     164             : 
     165         450 :     auto content = std::make_unique<VSIArchiveContent>();
     166         225 :     content->mTime = sStat.st_mtime;
     167         225 :     content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
     168             : 
     169         225 :     std::set<std::string> oSet;
     170             : 
     171        4891 :     do
     172             :     {
     173        5116 :         bool bIsDir = false;
     174             :         std::string osStrippedFilename =
     175        5116 :             GetStrippedFilename(poReader->GetFileName(), bIsDir);
     176       10232 :         if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
     177        5116 :             osStrippedFilename.find("//") != std::string::npos)
     178             :         {
     179           0 :             continue;
     180             :         }
     181             : 
     182        5116 :         if (oSet.find(osStrippedFilename) == oSet.end())
     183             :         {
     184        5116 :             oSet.insert(osStrippedFilename);
     185             : 
     186             :             // Add intermediate directory structure.
     187      183907 :             for (size_t i = 0; i < osStrippedFilename.size(); ++i)
     188             :             {
     189      178791 :                 if (osStrippedFilename[i] == '/')
     190             :                 {
     191       12946 :                     std::string osSubdirName(osStrippedFilename, 0, i);
     192        6473 :                     if (oSet.find(osSubdirName) == oSet.end())
     193             :                     {
     194        1257 :                         oSet.insert(osSubdirName);
     195             : 
     196        2514 :                         VSIArchiveEntry entry;
     197        1257 :                         entry.fileName = std::move(osSubdirName);
     198        1257 :                         entry.nModifiedTime = poReader->GetModifiedTime();
     199        1257 :                         entry.bIsDir = true;
     200             : #ifdef DEBUG_VERBOSE
     201             :                         CPLDebug(
     202             :                             "VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
     203             :                             static_cast<int>(content->entries.size() + 1),
     204             :                             entry.fileName.c_str(), entry.uncompressed_size);
     205             : #endif
     206        1257 :                         content->entries.push_back(std::move(entry));
     207             :                     }
     208             :                 }
     209             :             }
     210             : 
     211       10232 :             VSIArchiveEntry entry;
     212        5116 :             entry.fileName = std::move(osStrippedFilename);
     213        5116 :             entry.nModifiedTime = poReader->GetModifiedTime();
     214        5116 :             entry.uncompressed_size = poReader->GetFileSize();
     215        5116 :             entry.bIsDir = bIsDir;
     216        5116 :             entry.file_pos.reset(poReader->GetFileOffset());
     217             : #ifdef DEBUG_VERBOSE
     218             :             CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
     219             :                      static_cast<int>(content->entries.size() + 1),
     220             :                      entry.fileName.c_str(), entry.uncompressed_size);
     221             : #endif
     222        5116 :             content->entries.push_back(std::move(entry));
     223             :         }
     224             : 
     225        5116 :     } while (poReader->GotoNextFile());
     226             : 
     227             :     // Build directory index for fast lookups
     228         225 :     BuildDirectoryIndex(content.get());
     229             : 
     230             :     return oFileList
     231         450 :         .insert(std::pair<CPLString, std::unique_ptr<VSIArchiveContent>>(
     232         450 :             archiveFilename, std::move(content)))
     233         225 :         .first->second.get();
     234             : }
     235             : 
     236             : /************************************************************************/
     237             : /*                        FindFileInArchive()                           */
     238             : /************************************************************************/
     239             : 
     240        7242 : bool VSIArchiveFilesystemHandler::FindFileInArchive(
     241             :     const char *archiveFilename, const char *fileInArchiveName,
     242             :     const VSIArchiveEntry **archiveEntry)
     243             : {
     244        7242 :     CPLAssert(fileInArchiveName);
     245             : 
     246        7242 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     247        7242 :     if (content)
     248             :     {
     249        7239 :         const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
     250             : 
     251             :         // Use directory index to search within parent directory's children
     252        7239 :         auto dirIter = content->dirIndex.find(parentDir);
     253        7239 :         if (dirIter != content->dirIndex.end())
     254             :         {
     255        7226 :             const std::vector<int> &childIndices = dirIter->second;
     256      557566 :             for (int childIdx : childIndices)
     257             :             {
     258      557160 :                 if (content->entries[childIdx].fileName == fileInArchiveName)
     259             :                 {
     260        6820 :                     if (archiveEntry)
     261        6820 :                         *archiveEntry = &content->entries[childIdx];
     262        6820 :                     return true;
     263             :                 }
     264             :             }
     265             :         }
     266             :     }
     267         422 :     return false;
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                           CompactFilename()                          */
     272             : /************************************************************************/
     273             : 
     274       13593 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
     275             : {
     276       27186 :     std::string osRet(pszArchiveInFileNameIn);
     277             : 
     278             :     // Replace a/../b by b and foo/a/../b by foo/b.
     279             :     while (true)
     280             :     {
     281       13593 :         size_t nSlashDotDot = osRet.find("/../");
     282       13593 :         if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
     283             :             break;
     284           0 :         size_t nPos = nSlashDotDot - 1;
     285           0 :         while (nPos > 0 && osRet[nPos] != '/')
     286           0 :             --nPos;
     287           0 :         if (nPos == 0)
     288           0 :             osRet = osRet.substr(nSlashDotDot + strlen("/../"));
     289             :         else
     290           0 :             osRet = osRet.substr(0, nPos + 1) +
     291           0 :                     osRet.substr(nSlashDotDot + strlen("/../"));
     292           0 :     }
     293       13593 :     return osRet;
     294             : }
     295             : 
     296             : /************************************************************************/
     297             : /*                           SplitFilename()                            */
     298             : /************************************************************************/
     299             : 
     300       15431 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
     301             :                                                  CPLString &osFileInArchive,
     302             :                                                  bool bCheckMainFileExists,
     303             :                                                  bool bSetError) const
     304             : {
     305             :     // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
     306       15431 :     if (strcmp(pszFilename, GetPrefix()) == 0)
     307           4 :         return nullptr;
     308             : 
     309       15427 :     int i = 0;
     310             : 
     311             :     // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
     312       15427 :     if (pszFilename[strlen(GetPrefix()) + 1] == '{')
     313             :     {
     314        1368 :         pszFilename += strlen(GetPrefix()) + 1;
     315        1368 :         int nCountCurlies = 0;
     316       52628 :         while (pszFilename[i])
     317             :         {
     318       52627 :             if (pszFilename[i] == '{')
     319        1371 :                 nCountCurlies++;
     320       51256 :             else if (pszFilename[i] == '}')
     321             :             {
     322        1370 :                 nCountCurlies--;
     323        1370 :                 if (nCountCurlies == 0)
     324        1367 :                     break;
     325             :             }
     326       51260 :             i++;
     327             :         }
     328        1368 :         if (nCountCurlies > 0)
     329           1 :             return nullptr;
     330        1367 :         char *archiveFilename = CPLStrdup(pszFilename + 1);
     331        1367 :         archiveFilename[i - 1] = 0;
     332             : 
     333        1367 :         bool bArchiveFileExists = false;
     334        1367 :         if (!bCheckMainFileExists)
     335             :         {
     336          40 :             bArchiveFileExists = true;
     337             :         }
     338             :         else
     339             :         {
     340        2654 :             std::unique_lock oLock(oMutex);
     341             : 
     342        1327 :             if (oFileList.find(archiveFilename) != oFileList.end())
     343             :             {
     344        1186 :                 bArchiveFileExists = true;
     345             :             }
     346             :         }
     347             : 
     348        1367 :         if (!bArchiveFileExists)
     349             :         {
     350             :             VSIStatBufL statBuf;
     351             :             VSIFilesystemHandler *poFSHandler =
     352         141 :                 VSIFileManager::GetHandler(archiveFilename);
     353         141 :             int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
     354         141 :             if (bSetError)
     355          31 :                 nFlags |= VSI_STAT_SET_ERROR_FLAG;
     356         281 :             if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
     357         140 :                 !VSI_ISDIR(statBuf.st_mode))
     358             :             {
     359         140 :                 bArchiveFileExists = true;
     360             :             }
     361             :         }
     362             : 
     363        1367 :         if (bArchiveFileExists)
     364             :         {
     365        1366 :             if (IsEitherSlash(pszFilename[i + 1]))
     366             :             {
     367        1299 :                 osFileInArchive = CompactFilename(pszFilename + i + 2);
     368             :             }
     369          67 :             else if (pszFilename[i + 1] == '\0')
     370             :             {
     371          66 :                 osFileInArchive = "";
     372             :             }
     373             :             else
     374             :             {
     375           1 :                 CPLFree(archiveFilename);
     376           1 :                 return nullptr;
     377             :             }
     378             : 
     379             :             // Remove trailing slash.
     380        1365 :             if (!osFileInArchive.empty())
     381             :             {
     382        1295 :                 const char lastC = osFileInArchive.back();
     383        1295 :                 if (IsEitherSlash(lastC))
     384           2 :                     osFileInArchive.pop_back();
     385             :             }
     386             : 
     387        1365 :             return archiveFilename;
     388             :         }
     389             : 
     390           1 :         CPLFree(archiveFilename);
     391           1 :         return nullptr;
     392             :     }
     393             : 
     394             :     // Allow natural chaining of VSI drivers without requiring double slash.
     395             : 
     396       28118 :     CPLString osDoubleVsi(GetPrefix());
     397       14059 :     osDoubleVsi += "/vsi";
     398             : 
     399       14059 :     if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
     400        4071 :         pszFilename += strlen(GetPrefix());
     401             :     else
     402        9988 :         pszFilename += strlen(GetPrefix()) + 1;
     403             : 
     404             :     // Parsing strings like
     405             :     // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
     406             :     // takes a huge amount of time, so limit the number of nesting of such
     407             :     // file systems.
     408       14059 :     int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
     409       14059 :     if (pnCounter == nullptr)
     410             :     {
     411          26 :         pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
     412          26 :         *pnCounter = 0;
     413          26 :         CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
     414             :     }
     415       14059 :     if (*pnCounter == 3)
     416             :     {
     417          64 :         CPLError(CE_Failure, CPLE_AppDefined,
     418             :                  "Too deep recursion level in "
     419             :                  "VSIArchiveFilesystemHandler::SplitFilename()");
     420          64 :         return nullptr;
     421             :     }
     422             : 
     423       27990 :     const std::vector<CPLString> oExtensions = GetExtensions();
     424       13995 :     int nAttempts = 0;
     425      442238 :     while (pszFilename[i])
     426             :     {
     427      442022 :         int nToSkip = 0;
     428             : 
     429     3000120 :         for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
     430     5558210 :              iter != oExtensions.end(); ++iter)
     431             :         {
     432     2572540 :             const CPLString &osExtension = *iter;
     433     2572540 :             if (EQUALN(pszFilename + i, osExtension.c_str(),
     434             :                        osExtension.size()))
     435             :             {
     436       14450 :                 nToSkip = static_cast<int>(osExtension.size());
     437       14450 :                 break;
     438             :             }
     439             :         }
     440             : 
     441             : #ifdef DEBUG
     442             :         // For AFL, so that .cur_input is detected as the archive filename.
     443      442022 :         if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
     444             :         {
     445          14 :             nToSkip = static_cast<int>(strlen(".cur_input"));
     446             :         }
     447             : #endif
     448             : 
     449      442022 :         if (nToSkip != 0)
     450             :         {
     451       14464 :             nAttempts++;
     452             :             // Arbitrary threshold to avoid DoS with things like
     453             :             // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
     454       14464 :             if (nAttempts == 5)
     455             :             {
     456          21 :                 break;
     457             :             }
     458             :             VSIStatBufL statBuf;
     459       14443 :             char *archiveFilename = CPLStrdup(pszFilename);
     460       14443 :             bool bArchiveFileExists = false;
     461             : 
     462       14443 :             if (IsEitherSlash(archiveFilename[i + nToSkip]))
     463             :             {
     464       12773 :                 archiveFilename[i + nToSkip] = 0;
     465             :             }
     466             : 
     467       14443 :             if (!bCheckMainFileExists)
     468             :             {
     469        1016 :                 bArchiveFileExists = true;
     470             :             }
     471             :             else
     472             :             {
     473       26854 :                 std::unique_lock oLock(oMutex);
     474             : 
     475       13427 :                 if (oFileList.find(archiveFilename) != oFileList.end())
     476             :                 {
     477       12307 :                     bArchiveFileExists = true;
     478             :                 }
     479             :             }
     480             : 
     481       14443 :             if (!bArchiveFileExists)
     482             :             {
     483        1120 :                 (*pnCounter)++;
     484             : 
     485             :                 VSIFilesystemHandler *poFSHandler =
     486        1120 :                     VSIFileManager::GetHandler(archiveFilename);
     487        3360 :                 if (poFSHandler->Stat(archiveFilename, &statBuf,
     488             :                                       VSI_STAT_EXISTS_FLAG |
     489        1975 :                                           VSI_STAT_NATURE_FLAG) == 0 &&
     490         855 :                     !VSI_ISDIR(statBuf.st_mode))
     491             :                 {
     492         435 :                     bArchiveFileExists = true;
     493             :                 }
     494             : 
     495        1120 :                 (*pnCounter)--;
     496             :             }
     497             : 
     498       14443 :             if (bArchiveFileExists)
     499             :             {
     500       13758 :                 if (IsEitherSlash(pszFilename[i + nToSkip]))
     501             :                 {
     502             :                     osFileInArchive =
     503       12294 :                         CompactFilename(pszFilename + i + nToSkip + 1);
     504             :                 }
     505             :                 else
     506             :                 {
     507        1464 :                     osFileInArchive = "";
     508             :                 }
     509             : 
     510             :                 // Remove trailing slash.
     511       13758 :                 if (!osFileInArchive.empty())
     512             :                 {
     513       12286 :                     const char lastC = osFileInArchive.back();
     514       12286 :                     if (IsEitherSlash(lastC))
     515          63 :                         osFileInArchive.resize(osFileInArchive.size() - 1);
     516             :                 }
     517             : 
     518       13758 :                 return archiveFilename;
     519             :             }
     520         685 :             CPLFree(archiveFilename);
     521             :         }
     522      428243 :         i++;
     523             :     }
     524         237 :     return nullptr;
     525             : }
     526             : 
     527             : /************************************************************************/
     528             : /*                           OpenArchiveFile()                          */
     529             : /************************************************************************/
     530             : 
     531             : std::unique_ptr<VSIArchiveReader>
     532        4426 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
     533             :                                              const char *fileInArchiveName)
     534             : {
     535        8852 :     auto poReader = CreateReader(archiveFilename);
     536             : 
     537        4426 :     if (poReader == nullptr)
     538             :     {
     539           6 :         return nullptr;
     540             :     }
     541             : 
     542        4420 :     if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
     543             :     {
     544          73 :         if (poReader->GotoFirstFile() == FALSE)
     545             :         {
     546           1 :             return nullptr;
     547             :         }
     548             : 
     549             :         // Skip optional leading subdir.
     550          73 :         const CPLString osFileName = poReader->GetFileName();
     551          73 :         if (osFileName.empty() || IsEitherSlash(osFileName.back()))
     552             :         {
     553           2 :             if (poReader->GotoNextFile() == FALSE)
     554             :             {
     555           0 :                 return nullptr;
     556             :             }
     557             :         }
     558             : 
     559          73 :         if (poReader->GotoNextFile())
     560             :         {
     561           2 :             CPLString msg;
     562             :             msg.Printf("Support only 1 file in archive file %s when "
     563             :                        "no explicit in-archive filename is specified",
     564           1 :                        archiveFilename);
     565             :             const VSIArchiveContent *content =
     566           1 :                 GetContentOfArchive(archiveFilename, poReader.get());
     567           1 :             if (content)
     568             :             {
     569           1 :                 msg += "\nYou could try one of the following :\n";
     570           6 :                 for (const auto &entry : content->entries)
     571             :                 {
     572          10 :                     msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
     573             :                                               archiveFilename,
     574           5 :                                               entry.fileName.c_str());
     575             :                 }
     576             :             }
     577             : 
     578           1 :             CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
     579             : 
     580           1 :             return nullptr;
     581          72 :         }
     582             :     }
     583             :     else
     584             :     {
     585             :         // Optimization: instead of iterating over all files which can be
     586             :         // slow on .tar.gz files, try reading the first one first.
     587             :         // This can help if it is really huge.
     588             :         {
     589        4347 :             std::unique_lock oLock(oMutex);
     590             : 
     591        4347 :             if (oFileList.find(archiveFilename) == oFileList.end())
     592             :             {
     593         144 :                 if (poReader->GotoFirstFile() == FALSE)
     594             :                 {
     595          57 :                     return nullptr;
     596             :                 }
     597             : 
     598         144 :                 const CPLString osFileName = poReader->GetFileName();
     599         144 :                 bool bIsDir = false;
     600             :                 const CPLString osStrippedFilename =
     601         144 :                     GetStrippedFilename(osFileName, bIsDir);
     602         144 :                 if (!osStrippedFilename.empty())
     603             :                 {
     604             :                     const bool bMatch =
     605         144 :                         strcmp(osStrippedFilename, fileInArchiveName) == 0;
     606         144 :                     if (bMatch)
     607             :                     {
     608          57 :                         if (bIsDir)
     609             :                         {
     610           2 :                             return nullptr;
     611             :                         }
     612          55 :                         return poReader;
     613             :                     }
     614             :                 }
     615             :             }
     616             :         }
     617             : 
     618        4290 :         const VSIArchiveEntry *archiveEntry = nullptr;
     619        4290 :         if (FindFileInArchive(archiveFilename, fileInArchiveName,
     620        8398 :                               &archiveEntry) == FALSE ||
     621        4108 :             archiveEntry->bIsDir)
     622             :         {
     623         186 :             return nullptr;
     624             :         }
     625        4104 :         if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
     626             :         {
     627           0 :             return nullptr;
     628             :         }
     629             :     }
     630        4176 :     return poReader;
     631             : }
     632             : 
     633             : /************************************************************************/
     634             : /*                                 Stat()                               */
     635             : /************************************************************************/
     636             : 
     637        3522 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
     638             :                                       VSIStatBufL *pStatBuf, int nFlags)
     639             : {
     640        3522 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     641             : 
     642        7044 :     CPLString osFileInArchive;
     643             :     char *archiveFilename =
     644        7044 :         SplitFilename(pszFilename, osFileInArchive, true,
     645        3522 :                       (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
     646        3522 :     if (archiveFilename == nullptr)
     647          85 :         return -1;
     648             : 
     649        3437 :     int ret = -1;
     650        3437 :     if (!osFileInArchive.empty())
     651             :     {
     652             : #ifdef DEBUG_VERBOSE
     653             :         CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
     654             :                  osFileInArchive.c_str());
     655             : #endif
     656             : 
     657        2952 :         const VSIArchiveEntry *archiveEntry = nullptr;
     658        2952 :         if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
     659             :         {
     660             :             // Patching st_size with uncompressed file size.
     661        2712 :             pStatBuf->st_size = archiveEntry->uncompressed_size;
     662        2712 :             pStatBuf->st_mtime =
     663        2712 :                 static_cast<time_t>(archiveEntry->nModifiedTime);
     664        2712 :             if (archiveEntry->bIsDir)
     665        1156 :                 pStatBuf->st_mode = S_IFDIR;
     666             :             else
     667        1556 :                 pStatBuf->st_mode = S_IFREG;
     668        2712 :             ret = 0;
     669             :         }
     670             :     }
     671             :     else
     672             :     {
     673         485 :         auto poReader = CreateReader(archiveFilename);
     674         485 :         CPLFree(archiveFilename);
     675         485 :         archiveFilename = nullptr;
     676             : 
     677         485 :         if (poReader != nullptr && poReader->GotoFirstFile())
     678             :         {
     679             :             // Skip optional leading subdir.
     680         480 :             const CPLString osFileName = poReader->GetFileName();
     681         480 :             if (IsEitherSlash(osFileName.back()))
     682             :             {
     683          11 :                 if (poReader->GotoNextFile() == FALSE)
     684             :                 {
     685           0 :                     return -1;
     686             :                 }
     687             :             }
     688             : 
     689         480 :             if (poReader->GotoNextFile())
     690             :             {
     691             :                 // Several files in archive --> treat as dir.
     692         452 :                 pStatBuf->st_size = 0;
     693         452 :                 pStatBuf->st_mode = S_IFDIR;
     694             :             }
     695             :             else
     696             :             {
     697             :                 // Patching st_size with uncompressed file size.
     698          28 :                 pStatBuf->st_size = poReader->GetFileSize();
     699          28 :                 pStatBuf->st_mtime =
     700          28 :                     static_cast<time_t>(poReader->GetModifiedTime());
     701          28 :                 pStatBuf->st_mode = S_IFREG;
     702             :             }
     703             : 
     704         480 :             ret = 0;
     705             :         }
     706             :     }
     707             : 
     708        3437 :     CPLFree(archiveFilename);
     709        3437 :     return ret;
     710             : }
     711             : 
     712             : /************************************************************************/
     713             : /*                             ReadDirEx()                              */
     714             : /************************************************************************/
     715             : 
     716        1399 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
     717             :                                               int nMaxFiles)
     718             : {
     719        2798 :     CPLString osInArchiveSubDir;
     720             :     char *archiveFilename =
     721        1399 :         SplitFilename(pszDirname, osInArchiveSubDir, true, true);
     722        1399 :     if (archiveFilename == nullptr)
     723           0 :         return nullptr;
     724             : 
     725        1399 :     const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
     726             : 
     727        2798 :     CPLStringList oDir;
     728             : 
     729        1399 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     730        1399 :     if (!content)
     731             :     {
     732           5 :         CPLFree(archiveFilename);
     733           5 :         return nullptr;
     734             :     }
     735             : 
     736             : #ifdef DEBUG_VERBOSE
     737             :     CPLDebug("VSIArchive", "Read dir %s", pszDirname);
     738             : #endif
     739             : 
     740        2788 :     std::string searchDir;
     741        1394 :     if (lenInArchiveSubDir != 0)
     742        1167 :         searchDir = std::move(osInArchiveSubDir);
     743             : 
     744             :     // Use directory index to find the list of children for this directory
     745        1394 :     auto dirIter = content->dirIndex.find(searchDir);
     746        1394 :     if (dirIter == content->dirIndex.end())
     747             :     {
     748             :         // Directory not found in index - no children
     749           0 :         CPLFree(archiveFilename);
     750           0 :         return oDir.StealList();
     751             :     }
     752        1394 :     const std::vector<int> &childIndices = dirIter->second;
     753             : 
     754             :     // Scan the children of this directory
     755       42354 :     for (int childIdx : childIndices)
     756             :     {
     757       40961 :         const char *fileName = content->entries[childIdx].fileName.c_str();
     758             : 
     759       40961 :         const char *baseName = fileName;
     760       40961 :         if (lenInArchiveSubDir != 0)
     761             :         {
     762             :             // Skip the directory prefix and slash to get just the child name
     763       38531 :             baseName = fileName + lenInArchiveSubDir + 1;
     764             :         }
     765       40961 :         oDir.AddStringDirectly(CPLStrdup(baseName));
     766             : 
     767       40961 :         if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
     768           1 :             break;
     769             :     }
     770        1394 :     CPLFree(archiveFilename);
     771        1394 :     return oDir.StealList();
     772             : }
     773             : 
     774             : /************************************************************************/
     775             : /*                               IsLocal()                              */
     776             : /************************************************************************/
     777             : 
     778           0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
     779             : {
     780           0 :     if (!STARTS_WITH(pszPath, GetPrefix()))
     781           0 :         return false;
     782           0 :     const char *pszBaseFileName = pszPath + strlen(GetPrefix());
     783             :     VSIFilesystemHandler *poFSHandler =
     784           0 :         VSIFileManager::GetHandler(pszBaseFileName);
     785           0 :     return poFSHandler->IsLocal(pszPath);
     786             : }
     787             : 
     788             : /************************************************************************/
     789             : /*                               IsArchive()                            */
     790             : /************************************************************************/
     791             : 
     792           0 : bool VSIArchiveFilesystemHandler::IsArchive(const char *pszPath) const
     793             : {
     794           0 :     if (!STARTS_WITH(pszPath, GetPrefix()))
     795           0 :         return false;
     796           0 :     CPLString osFileInArchive;
     797           0 :     return SplitFilename(pszPath, osFileInArchive, false, false) != nullptr &&
     798           0 :            osFileInArchive.empty();
     799             : }
     800             : 
     801             : //! @endcond

Generated by: LCOV version 1.14