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 325 92.0 %
Date: 2026-02-12 06:20:29 Functions: 11 12 91.7 %

          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       43914 : static bool IsEitherSlash(char c)
      35             : {
      36       43914 :     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        5255 : static std::string GetStrippedFilename(const std::string &osFileName,
      74             :                                        bool &bIsDir)
      75             : {
      76        5255 :     bIsDir = false;
      77        5255 :     int nStartPos = 0;
      78             : 
      79             :     // Remove ./ pattern at the beginning of a filename.
      80        5255 :     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       10510 :     std::string ret(osFileName, nStartPos);
      88      186009 :     for (char &c : ret)
      89             :     {
      90      180754 :         if (c == '\\')
      91           1 :             c = '/';
      92             :     }
      93             : 
      94        5255 :     bIsDir = !ret.empty() && ret.back() == '/';
      95        5255 :     if (bIsDir)
      96             :     {
      97             :         // Remove trailing slash.
      98         242 :         ret.pop_back();
      99             :     }
     100        5255 :     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        6594 :     for (int i = 0; i < nEntries; i++)
     112             :     {
     113        6369 :         const char *fileName = content->entries[i].fileName.c_str();
     114       12738 :         std::string parentDir = CPLGetPathSafe(fileName);
     115        6369 :         content->dirIndex[parentDir].push_back(i);
     116             :     }
     117         225 : }
     118             : 
     119             : /************************************************************************/
     120             : /*                        GetContentOfArchive()                         */
     121             : /************************************************************************/
     122             : 
     123             : const VSIArchiveContent *
     124        8694 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
     125             :                                                  VSIArchiveReader *poReader)
     126             : {
     127       17388 :     std::unique_lock oLock(oMutex);
     128             : 
     129             :     VSIStatBufL sStat;
     130        8694 :     if (VSIStatL(archiveFilename, &sStat) != 0)
     131           0 :         return nullptr;
     132             : 
     133        8694 :     auto oIter = oFileList.find(archiveFilename);
     134        8694 :     if (oIter != oFileList.end())
     135             :     {
     136        8469 :         const VSIArchiveContent *content = oIter->second.get();
     137        8469 :         if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
     138        8468 :             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        8461 :             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        4885 :     do
     172             :     {
     173        5110 :         bool bIsDir = false;
     174             :         std::string osStrippedFilename =
     175        5110 :             GetStrippedFilename(poReader->GetFileName(), bIsDir);
     176       10220 :         if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
     177        5110 :             osStrippedFilename.find("//") != std::string::npos)
     178             :         {
     179           0 :             continue;
     180             :         }
     181             : 
     182        5110 :         if (oSet.find(osStrippedFilename) == oSet.end())
     183             :         {
     184        5110 :             oSet.insert(osStrippedFilename);
     185             : 
     186             :             // Add intermediate directory structure.
     187      183811 :             for (size_t i = 0; i < osStrippedFilename.size(); ++i)
     188             :             {
     189      178701 :                 if (osStrippedFilename[i] == '/')
     190             :                 {
     191       12940 :                     std::string osSubdirName(osStrippedFilename, 0, i);
     192        6470 :                     if (oSet.find(osSubdirName) == oSet.end())
     193             :                     {
     194        1259 :                         oSet.insert(osSubdirName);
     195             : 
     196        2518 :                         VSIArchiveEntry entry;
     197        1259 :                         entry.fileName = std::move(osSubdirName);
     198        1259 :                         entry.nModifiedTime = poReader->GetModifiedTime();
     199        1259 :                         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        1259 :                         content->entries.push_back(std::move(entry));
     207             :                     }
     208             :                 }
     209             :             }
     210             : 
     211       10220 :             VSIArchiveEntry entry;
     212        5110 :             entry.fileName = std::move(osStrippedFilename);
     213        5110 :             entry.nModifiedTime = poReader->GetModifiedTime();
     214        5110 :             entry.uncompressed_size = poReader->GetFileSize();
     215        5110 :             entry.bIsDir = bIsDir;
     216        5110 :             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        5110 :             content->entries.push_back(std::move(entry));
     223             :         }
     224             : 
     225        5110 :     } 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        7291 : bool VSIArchiveFilesystemHandler::FindFileInArchive(
     241             :     const char *archiveFilename, const char *fileInArchiveName,
     242             :     const VSIArchiveEntry **archiveEntry)
     243             : {
     244        7291 :     CPLAssert(fileInArchiveName);
     245             : 
     246        7291 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     247        7291 :     if (content)
     248             :     {
     249        7288 :         const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
     250             : 
     251             :         // Use directory index to search within parent directory's children
     252        7288 :         auto dirIter = content->dirIndex.find(parentDir);
     253        7288 :         if (dirIter != content->dirIndex.end())
     254             :         {
     255        7276 :             const std::vector<int> &childIndices = dirIter->second;
     256      557812 :             for (int childIdx : childIndices)
     257             :             {
     258      557362 :                 if (content->entries[childIdx].fileName == fileInArchiveName)
     259             :                 {
     260        6826 :                     if (archiveEntry)
     261        6826 :                         *archiveEntry = &content->entries[childIdx];
     262        6826 :                     return true;
     263             :                 }
     264             :             }
     265             :         }
     266             :     }
     267         465 :     return false;
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                          CompactFilename()                           */
     272             : /************************************************************************/
     273             : 
     274       13656 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
     275             : {
     276       27312 :     std::string osRet(pszArchiveInFileNameIn);
     277             : 
     278             :     // Replace a/../b by b and foo/a/../b by foo/b.
     279             :     while (true)
     280             :     {
     281       13656 :         size_t nSlashDotDot = osRet.find("/../");
     282       13656 :         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       13656 :     return osRet;
     294             : }
     295             : 
     296             : /************************************************************************/
     297             : /*                           SplitFilename()                            */
     298             : /************************************************************************/
     299             : 
     300             : std::unique_ptr<char, VSIFreeReleaser>
     301       15513 : VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
     302             :                                            CPLString &osFileInArchive,
     303             :                                            bool bCheckMainFileExists,
     304             :                                            bool bSetError) const
     305             : {
     306             :     // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
     307       15513 :     if (strcmp(pszFilename, GetPrefix()) == 0)
     308           4 :         return nullptr;
     309             : 
     310       15509 :     int i = 0;
     311             : 
     312             :     // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
     313       15509 :     if (pszFilename[strlen(GetPrefix()) + 1] == '{')
     314             :     {
     315        1378 :         pszFilename += strlen(GetPrefix()) + 1;
     316        1378 :         int nCountCurlies = 0;
     317       53253 :         while (pszFilename[i])
     318             :         {
     319       53252 :             if (pszFilename[i] == '{')
     320        1381 :                 nCountCurlies++;
     321       51871 :             else if (pszFilename[i] == '}')
     322             :             {
     323        1380 :                 nCountCurlies--;
     324        1380 :                 if (nCountCurlies == 0)
     325        1377 :                     break;
     326             :             }
     327       51875 :             i++;
     328             :         }
     329        1378 :         if (nCountCurlies > 0)
     330           1 :             return nullptr;
     331        1377 :         char *archiveFilename = CPLStrdup(pszFilename + 1);
     332        1377 :         archiveFilename[i - 1] = 0;
     333             : 
     334        1377 :         bool bArchiveFileExists = false;
     335        1377 :         if (!bCheckMainFileExists)
     336             :         {
     337          40 :             bArchiveFileExists = true;
     338             :         }
     339             :         else
     340             :         {
     341        2674 :             std::unique_lock oLock(oMutex);
     342             : 
     343        1337 :             if (oFileList.find(archiveFilename) != oFileList.end())
     344             :             {
     345        1195 :                 bArchiveFileExists = true;
     346             :             }
     347             :         }
     348             : 
     349        1377 :         if (!bArchiveFileExists)
     350             :         {
     351             :             VSIStatBufL statBuf;
     352             :             VSIFilesystemHandler *poFSHandler =
     353         142 :                 VSIFileManager::GetHandler(archiveFilename);
     354         142 :             int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
     355         142 :             if (bSetError)
     356          31 :                 nFlags |= VSI_STAT_SET_ERROR_FLAG;
     357         283 :             if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
     358         141 :                 !VSI_ISDIR(statBuf.st_mode))
     359             :             {
     360         141 :                 bArchiveFileExists = true;
     361             :             }
     362             :         }
     363             : 
     364        1377 :         if (bArchiveFileExists)
     365             :         {
     366        1376 :             if (IsEitherSlash(pszFilename[i + 1]))
     367             :             {
     368        1309 :                 osFileInArchive = CompactFilename(pszFilename + i + 2);
     369             :             }
     370          67 :             else if (pszFilename[i + 1] == '\0')
     371             :             {
     372          66 :                 osFileInArchive = "";
     373             :             }
     374             :             else
     375             :             {
     376           1 :                 CPLFree(archiveFilename);
     377           1 :                 return nullptr;
     378             :             }
     379             : 
     380             :             // Remove trailing slash.
     381        1375 :             if (!osFileInArchive.empty())
     382             :             {
     383        1305 :                 const char lastC = osFileInArchive.back();
     384        1305 :                 if (IsEitherSlash(lastC))
     385           2 :                     osFileInArchive.pop_back();
     386             :             }
     387             : 
     388        1375 :             return std::unique_ptr<char, VSIFreeReleaser>(archiveFilename);
     389             :         }
     390             : 
     391           1 :         CPLFree(archiveFilename);
     392           1 :         return nullptr;
     393             :     }
     394             : 
     395             :     // Allow natural chaining of VSI drivers without requiring double slash.
     396             : 
     397       28262 :     CPLString osDoubleVsi(GetPrefix());
     398       14131 :     osDoubleVsi += "/vsi";
     399             : 
     400       14131 :     if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
     401        4061 :         pszFilename += strlen(GetPrefix());
     402             :     else
     403       10070 :         pszFilename += strlen(GetPrefix()) + 1;
     404             : 
     405             :     // Parsing strings like
     406             :     // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
     407             :     // takes a huge amount of time, so limit the number of nesting of such
     408             :     // file systems.
     409       14131 :     int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
     410       14131 :     if (pnCounter == nullptr)
     411             :     {
     412          25 :         pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
     413          25 :         *pnCounter = 0;
     414          25 :         CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
     415             :     }
     416       14131 :     if (*pnCounter == 3)
     417             :     {
     418          64 :         CPLError(CE_Failure, CPLE_AppDefined,
     419             :                  "Too deep recursion level in "
     420             :                  "VSIArchiveFilesystemHandler::SplitFilename()");
     421          64 :         return nullptr;
     422             :     }
     423             : 
     424       28134 :     const std::vector<CPLString> oExtensions = GetExtensions();
     425       14067 :     int nAttempts = 0;
     426      443758 :     while (pszFilename[i])
     427             :     {
     428      443535 :         int nToSkip = 0;
     429             : 
     430     3009080 :         for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
     431     5574620 :              iter != oExtensions.end(); ++iter)
     432             :         {
     433     2580060 :             const CPLString &osExtension = *iter;
     434     2580060 :             if (EQUALN(pszFilename + i, osExtension.c_str(),
     435             :                        osExtension.size()))
     436             :             {
     437       14522 :                 nToSkip = static_cast<int>(osExtension.size());
     438       14522 :                 break;
     439             :             }
     440             :         }
     441             : 
     442             : #ifdef DEBUG
     443             :         // For AFL, so that .cur_input is detected as the archive filename.
     444      443535 :         if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
     445             :         {
     446          14 :             nToSkip = static_cast<int>(strlen(".cur_input"));
     447             :         }
     448             : #endif
     449             : 
     450      443535 :         if (nToSkip != 0)
     451             :         {
     452       14536 :             nAttempts++;
     453             :             // Arbitrary threshold to avoid DoS with things like
     454             :             // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
     455       14536 :             if (nAttempts == 5)
     456             :             {
     457          21 :                 break;
     458             :             }
     459             :             VSIStatBufL statBuf;
     460       14515 :             char *archiveFilename = CPLStrdup(pszFilename);
     461       14515 :             bool bArchiveFileExists = false;
     462             : 
     463       14515 :             if (IsEitherSlash(archiveFilename[i + nToSkip]))
     464             :             {
     465       12833 :                 archiveFilename[i + nToSkip] = 0;
     466             :             }
     467             : 
     468       14515 :             if (!bCheckMainFileExists)
     469             :             {
     470        1016 :                 bArchiveFileExists = true;
     471             :             }
     472             :             else
     473             :             {
     474       26998 :                 std::unique_lock oLock(oMutex);
     475             : 
     476       13499 :                 if (oFileList.find(archiveFilename) != oFileList.end())
     477             :                 {
     478       12374 :                     bArchiveFileExists = true;
     479             :                 }
     480             :             }
     481             : 
     482       14515 :             if (!bArchiveFileExists)
     483             :             {
     484        1125 :                 (*pnCounter)++;
     485             : 
     486             :                 VSIFilesystemHandler *poFSHandler =
     487        1125 :                     VSIFileManager::GetHandler(archiveFilename);
     488        3375 :                 if (poFSHandler->Stat(archiveFilename, &statBuf,
     489             :                                       VSI_STAT_EXISTS_FLAG |
     490        1978 :                                           VSI_STAT_NATURE_FLAG) == 0 &&
     491         853 :                     !VSI_ISDIR(statBuf.st_mode))
     492             :                 {
     493         433 :                     bArchiveFileExists = true;
     494             :                 }
     495             : 
     496        1125 :                 (*pnCounter)--;
     497             :             }
     498             : 
     499       14515 :             if (bArchiveFileExists)
     500             :             {
     501       13823 :                 if (IsEitherSlash(pszFilename[i + nToSkip]))
     502             :                 {
     503             :                     osFileInArchive =
     504       12347 :                         CompactFilename(pszFilename + i + nToSkip + 1);
     505             :                 }
     506             :                 else
     507             :                 {
     508        1476 :                     osFileInArchive = "";
     509             :                 }
     510             : 
     511             :                 // Remove trailing slash.
     512       13823 :                 if (!osFileInArchive.empty())
     513             :                 {
     514       12339 :                     const char lastC = osFileInArchive.back();
     515       12339 :                     if (IsEitherSlash(lastC))
     516          63 :                         osFileInArchive.resize(osFileInArchive.size() - 1);
     517             :                 }
     518             : 
     519       13823 :                 return std::unique_ptr<char, VSIFreeReleaser>(archiveFilename);
     520             :             }
     521         692 :             CPLFree(archiveFilename);
     522             :         }
     523      429691 :         i++;
     524             :     }
     525         244 :     return nullptr;
     526             : }
     527             : 
     528             : /************************************************************************/
     529             : /*                          OpenArchiveFile()                           */
     530             : /************************************************************************/
     531             : 
     532             : std::unique_ptr<VSIArchiveReader>
     533        4449 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
     534             :                                              const char *fileInArchiveName)
     535             : {
     536        8898 :     auto poReader = CreateReader(archiveFilename);
     537             : 
     538        4449 :     if (poReader == nullptr)
     539             :     {
     540           6 :         return nullptr;
     541             :     }
     542             : 
     543        4443 :     if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
     544             :     {
     545          73 :         if (poReader->GotoFirstFile() == FALSE)
     546             :         {
     547           1 :             return nullptr;
     548             :         }
     549             : 
     550             :         // Skip optional leading subdir.
     551          73 :         const CPLString osFileName = poReader->GetFileName();
     552          73 :         if (osFileName.empty() || IsEitherSlash(osFileName.back()))
     553             :         {
     554           2 :             if (poReader->GotoNextFile() == FALSE)
     555             :             {
     556           0 :                 return nullptr;
     557             :             }
     558             :         }
     559             : 
     560          73 :         if (poReader->GotoNextFile())
     561             :         {
     562           2 :             CPLString msg;
     563             :             msg.Printf("Support only 1 file in archive file %s when "
     564             :                        "no explicit in-archive filename is specified",
     565           1 :                        archiveFilename);
     566             :             const VSIArchiveContent *content =
     567           1 :                 GetContentOfArchive(archiveFilename, poReader.get());
     568           1 :             if (content)
     569             :             {
     570           1 :                 msg += "\nYou could try one of the following :\n";
     571           6 :                 for (const auto &entry : content->entries)
     572             :                 {
     573          10 :                     msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
     574             :                                               archiveFilename,
     575           5 :                                               entry.fileName.c_str());
     576             :                 }
     577             :             }
     578             : 
     579           1 :             CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
     580             : 
     581           1 :             return nullptr;
     582          72 :         }
     583             :     }
     584             :     else
     585             :     {
     586             :         // Optimization: instead of iterating over all files which can be
     587             :         // slow on .tar.gz files, try reading the first one first.
     588             :         // This can help if it is really huge.
     589             :         {
     590        4370 :             std::unique_lock oLock(oMutex);
     591             : 
     592        4370 :             if (oFileList.find(archiveFilename) == oFileList.end())
     593             :             {
     594         145 :                 if (poReader->GotoFirstFile() == FALSE)
     595             :                 {
     596          57 :                     return nullptr;
     597             :                 }
     598             : 
     599         145 :                 const CPLString osFileName = poReader->GetFileName();
     600         145 :                 bool bIsDir = false;
     601             :                 const CPLString osStrippedFilename =
     602         145 :                     GetStrippedFilename(osFileName, bIsDir);
     603         145 :                 if (!osStrippedFilename.empty())
     604             :                 {
     605             :                     const bool bMatch =
     606         145 :                         strcmp(osStrippedFilename, fileInArchiveName) == 0;
     607         145 :                     if (bMatch)
     608             :                     {
     609          57 :                         if (bIsDir)
     610             :                         {
     611           2 :                             return nullptr;
     612             :                         }
     613          55 :                         return poReader;
     614             :                     }
     615             :                 }
     616             :             }
     617             :         }
     618             : 
     619        4313 :         const VSIArchiveEntry *archiveEntry = nullptr;
     620        4313 :         if (FindFileInArchive(archiveFilename, fileInArchiveName,
     621        8428 :                               &archiveEntry) == FALSE ||
     622        4115 :             archiveEntry->bIsDir)
     623             :         {
     624         202 :             return nullptr;
     625             :         }
     626        4111 :         if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
     627             :         {
     628           0 :             return nullptr;
     629             :         }
     630             :     }
     631        4183 :     return poReader;
     632             : }
     633             : 
     634             : /************************************************************************/
     635             : /*                                Stat()                                */
     636             : /************************************************************************/
     637             : 
     638        3551 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
     639             :                                       VSIStatBufL *pStatBuf, int nFlags)
     640             : {
     641        3551 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     642             : 
     643        7102 :     CPLString osFileInArchive;
     644             :     auto archiveFilename =
     645             :         SplitFilename(pszFilename, osFileInArchive, true,
     646        7102 :                       (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
     647        3551 :     if (archiveFilename == nullptr)
     648          85 :         return -1;
     649             : 
     650        3466 :     int ret = -1;
     651        3466 :     if (!osFileInArchive.empty())
     652             :     {
     653             : #ifdef DEBUG_VERBOSE
     654             :         CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename.get(),
     655             :                  osFileInArchive.c_str());
     656             : #endif
     657             : 
     658        2978 :         const VSIArchiveEntry *archiveEntry = nullptr;
     659        2978 :         if (FindFileInArchive(archiveFilename.get(), osFileInArchive,
     660             :                               &archiveEntry))
     661             :         {
     662             :             // Patching st_size with uncompressed file size.
     663        2711 :             pStatBuf->st_size = archiveEntry->uncompressed_size;
     664        2711 :             pStatBuf->st_mtime =
     665        2711 :                 static_cast<time_t>(archiveEntry->nModifiedTime);
     666        2711 :             if (archiveEntry->bIsDir)
     667        1154 :                 pStatBuf->st_mode = S_IFDIR;
     668             :             else
     669        1557 :                 pStatBuf->st_mode = S_IFREG;
     670        2711 :             ret = 0;
     671             :         }
     672             :     }
     673             :     else
     674             :     {
     675         488 :         auto poReader = CreateReader(archiveFilename.get());
     676             : 
     677         488 :         if (poReader != nullptr && poReader->GotoFirstFile())
     678             :         {
     679             :             // Skip optional leading subdir.
     680         483 :             const CPLString osFileName = poReader->GetFileName();
     681         483 :             if (IsEitherSlash(osFileName.back()))
     682             :             {
     683          11 :                 if (poReader->GotoNextFile() == FALSE)
     684             :                 {
     685           0 :                     return -1;
     686             :                 }
     687             :             }
     688             : 
     689         483 :             if (poReader->GotoNextFile())
     690             :             {
     691             :                 // Several files in archive --> treat as dir.
     692         455 :                 pStatBuf->st_size = 0;
     693         455 :                 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         483 :             ret = 0;
     705             :         }
     706             :     }
     707             : 
     708        3466 :     return ret;
     709             : }
     710             : 
     711             : /************************************************************************/
     712             : /*                             ReadDirEx()                              */
     713             : /************************************************************************/
     714             : 
     715        1402 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
     716             :                                               int nMaxFiles)
     717             : {
     718        2804 :     CPLString osInArchiveSubDir;
     719             :     auto archiveFilename =
     720        2804 :         SplitFilename(pszDirname, osInArchiveSubDir, true, true);
     721        1402 :     if (archiveFilename == nullptr)
     722           0 :         return nullptr;
     723             : 
     724        1402 :     const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
     725             : 
     726        2804 :     CPLStringList oDir;
     727             : 
     728             :     const VSIArchiveContent *content =
     729        1402 :         GetContentOfArchive(archiveFilename.get());
     730        1402 :     if (!content)
     731             :     {
     732           5 :         return nullptr;
     733             :     }
     734             : 
     735             : #ifdef DEBUG_VERBOSE
     736             :     CPLDebug("VSIArchive", "Read dir %s", pszDirname);
     737             : #endif
     738             : 
     739        2794 :     std::string searchDir;
     740        1397 :     if (lenInArchiveSubDir != 0)
     741        1166 :         searchDir = std::move(osInArchiveSubDir);
     742             : 
     743             :     // Use directory index to find the list of children for this directory
     744        1397 :     auto dirIter = content->dirIndex.find(searchDir);
     745        1397 :     if (dirIter == content->dirIndex.end())
     746             :     {
     747             :         // Directory not found in index - no children
     748           0 :         return oDir.StealList();
     749             :     }
     750        1397 :     const std::vector<int> &childIndices = dirIter->second;
     751             : 
     752             :     // Scan the children of this directory
     753       42367 :     for (int childIdx : childIndices)
     754             :     {
     755       40971 :         const char *fileName = content->entries[childIdx].fileName.c_str();
     756             : 
     757       40971 :         const char *baseName = fileName;
     758       40971 :         if (lenInArchiveSubDir != 0)
     759             :         {
     760             :             // Skip the directory prefix and slash to get just the child name
     761       38527 :             baseName = fileName + lenInArchiveSubDir + 1;
     762             :         }
     763       40971 :         oDir.AddStringDirectly(CPLStrdup(baseName));
     764             : 
     765       40971 :         if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
     766           1 :             break;
     767             :     }
     768        1397 :     return oDir.StealList();
     769             : }
     770             : 
     771             : /************************************************************************/
     772             : /*                              IsLocal()                               */
     773             : /************************************************************************/
     774             : 
     775           4 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
     776             : {
     777           4 :     if (!STARTS_WITH(pszPath, GetPrefix()))
     778           0 :         return false;
     779           4 :     const char *pszBaseFileName = pszPath + strlen(GetPrefix());
     780             :     VSIFilesystemHandler *poFSHandler =
     781           4 :         VSIFileManager::GetHandler(pszBaseFileName);
     782           4 :     return poFSHandler->IsLocal(pszPath);
     783             : }
     784             : 
     785             : /************************************************************************/
     786             : /*                             IsArchive()                              */
     787             : /************************************************************************/
     788             : 
     789           0 : bool VSIArchiveFilesystemHandler::IsArchive(const char *pszPath) const
     790             : {
     791           0 :     if (!STARTS_WITH(pszPath, GetPrefix()))
     792           0 :         return false;
     793           0 :     CPLString osFileInArchive;
     794           0 :     return SplitFilename(pszPath, osFileInArchive, false, false) != nullptr &&
     795           0 :            osFileInArchive.empty();
     796             : }
     797             : 
     798             : //! @endcond

Generated by: LCOV version 1.14