LCOV - code coverage report
Current view: top level - apps - gdalalg_vsi_list.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 137 147 93.2 %
Date: 2026-07-09 08:35:43 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "vsi list" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vsi_list.h"
      14             : 
      15             : #include "cpl_string.h"
      16             : #include "cpl_time.h"
      17             : #include "cpl_vsi.h"
      18             : #include "cpl_vsi_error.h"
      19             : 
      20             : #include <cinttypes>
      21             : 
      22             : //! @cond Doxygen_Suppress
      23             : 
      24             : #ifndef _
      25             : #define _(x) (x)
      26             : #endif
      27             : 
      28             : /************************************************************************/
      29             : /*             GDALVSIListAlgorithm::GDALVSIListAlgorithm()             */
      30             : /************************************************************************/
      31             : 
      32          63 : GDALVSIListAlgorithm::GDALVSIListAlgorithm()
      33          63 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL), m_oWriter(JSONPrint, this)
      34             : {
      35          63 :     AddProgressArg(/* hidden = */ true);
      36             : 
      37         126 :     auto &arg = AddArg("filename", 0, _("File or directory name"), &m_filename)
      38          63 :                     .SetPositional()
      39          63 :                     .SetRequired();
      40          63 :     SetAutoCompleteFunctionForFilename(arg, 0);
      41             : 
      42          63 :     AddOutputFormatArg(&m_format).SetChoices("json", "text");
      43             : 
      44         126 :     AddArg("long-listing", 'l', _("Use a long listing format"), &m_longListing)
      45          63 :         .AddAlias("long");
      46             :     AddArg("recursive", 'R', _("List subdirectories recursively"),
      47          63 :            &m_recursive);
      48         126 :     AddArg("depth", 0, _("Maximum depth in recursive mode"), &m_depth)
      49          63 :         .SetMinValueIncluded(1);
      50         126 :     AddArg("absolute-path", 0, _("Display absolute path"), &m_absolutePath)
      51          63 :         .AddAlias("abs");
      52             :     AddArg("tree", 0, _("Use a hierarchical presentation for JSON output"),
      53          63 :            &m_JSONAsTree);
      54             : 
      55          63 :     AddOutputStringArg(&m_output);
      56          63 :     AddStdoutArg(&m_stdout);
      57          63 : }
      58             : 
      59             : /************************************************************************/
      60             : /*                    GDALVSIListAlgorithm::Print()                     */
      61             : /************************************************************************/
      62             : 
      63        1978 : void GDALVSIListAlgorithm::Print(const char *str)
      64             : {
      65        1978 :     if (m_stdout)
      66           0 :         fwrite(str, 1, strlen(str), stdout);
      67             :     else
      68        1978 :         m_output += str;
      69        1978 : }
      70             : 
      71             : /************************************************************************/
      72             : /*                  GDALVSIListAlgorithm::JSONPrint()                   */
      73             : /************************************************************************/
      74             : 
      75        1900 : /* static */ void GDALVSIListAlgorithm::JSONPrint(const char *pszTxt,
      76             :                                                   void *pUserData)
      77             : {
      78        1900 :     static_cast<GDALVSIListAlgorithm *>(pUserData)->Print(pszTxt);
      79        1900 : }
      80             : 
      81             : /************************************************************************/
      82             : /*                              GetDepth()                              */
      83             : /************************************************************************/
      84             : 
      85          12 : static int GetDepth(const std::string &filename)
      86             : {
      87          12 :     int depth = 0;
      88          12 :     const char sep = VSIGetDirectorySeparator(filename.c_str())[0];
      89         112 :     for (size_t i = 0; i < filename.size(); ++i)
      90             :     {
      91         106 :         if ((filename[i] == sep || filename[i] == '/') &&
      92           6 :             i != filename.size() - 1)
      93           6 :             ++depth;
      94             :     }
      95          12 :     return depth;
      96             : }
      97             : 
      98             : /************************************************************************/
      99             : /*                  GDALVSIListAlgorithm::PrintEntry()                  */
     100             : /************************************************************************/
     101             : 
     102         406 : void GDALVSIListAlgorithm::PrintEntry(const VSIDIREntry *entry)
     103             : {
     104         812 :     std::string filename;
     105         406 :     if (m_format == "json" && m_JSONAsTree)
     106             :     {
     107          12 :         filename = CPLGetFilename(entry->pszName);
     108             :     }
     109         394 :     else if (m_absolutePath)
     110             :     {
     111          72 :         if (CPLIsFilenameRelative(m_filename.c_str()))
     112             :         {
     113          66 :             char *pszCurDir = CPLGetCurrentDir();
     114          66 :             if (!pszCurDir)
     115           0 :                 pszCurDir = CPLStrdup(".");
     116          66 :             if (m_filename == ".")
     117           0 :                 filename = pszCurDir;
     118             :             else
     119             :                 filename =
     120          66 :                     CPLFormFilenameSafe(pszCurDir, m_filename.c_str(), nullptr);
     121          66 :             CPLFree(pszCurDir);
     122             :         }
     123             :         else
     124             :         {
     125           6 :             filename = m_filename;
     126             :         }
     127             :         filename =
     128          72 :             CPLFormFilenameSafe(filename.c_str(), entry->pszName, nullptr);
     129             :     }
     130             :     else
     131             :     {
     132         322 :         filename = entry->pszName;
     133             :     }
     134             : 
     135         406 :     char permissions[1 + 3 + 3 + 3 + 1] = "----------";
     136             :     struct tm bdt;
     137         406 :     memset(&bdt, 0, sizeof(bdt));
     138             : 
     139         406 :     if (m_longListing)
     140             :     {
     141          81 :         if (entry->bModeKnown)
     142             :         {
     143          81 :             if (VSI_ISDIR(entry->nMode))
     144           5 :                 permissions[0] = 'd';
     145         810 :             for (int i = 0; i < 9; ++i)
     146             :             {
     147         729 :                 if (entry->nMode & (1 << i))
     148         528 :                     permissions[9 - i] = (i % 3) == 0   ? 'x'
     149         264 :                                          : (i % 3) == 1 ? 'w'
     150             :                                                         : 'r';
     151             :             }
     152             :         }
     153           0 :         else if (VSI_ISDIR(entry->nMode))
     154             :         {
     155           0 :             strcpy(permissions, "dr-xr-xr-x");
     156             :         }
     157             :         else
     158             :         {
     159           0 :             strcpy(permissions, "-r--r--r--");
     160             :         }
     161             : 
     162          81 :         CPLUnixTimeToYMDHMS(entry->nMTime, &bdt);
     163             :     }
     164             : 
     165         406 :     if (m_format == "json")
     166             :     {
     167         334 :         if (m_JSONAsTree)
     168             :         {
     169          18 :             while (!m_stackNames.empty() &&
     170          18 :                    GetDepth(m_stackNames.back()) >= GetDepth(entry->pszName))
     171             :             {
     172           0 :                 m_oWriter.EndArray();
     173           0 :                 m_oWriter.EndObj();
     174           0 :                 m_stackNames.pop_back();
     175             :             }
     176             :         }
     177             : 
     178         334 :         if (m_longListing)
     179             :         {
     180          15 :             m_oWriter.StartObj();
     181          15 :             m_oWriter.AddObjKey("name");
     182          15 :             m_oWriter.Add(filename);
     183          15 :             m_oWriter.AddObjKey("type");
     184          15 :             m_oWriter.Add(VSI_ISDIR(entry->nMode) ? "directory" : "file");
     185          15 :             m_oWriter.AddObjKey("size");
     186          15 :             m_oWriter.Add(static_cast<uint64_t>(entry->nSize));
     187          15 :             if (entry->bMTimeKnown)
     188             :             {
     189          15 :                 m_oWriter.AddObjKey("last_modification_date");
     190          15 :                 m_oWriter.Add(CPLSPrintf("%04d-%02d-%02d %02d:%02d:%02dZ",
     191          15 :                                          bdt.tm_year + 1900, bdt.tm_mon + 1,
     192             :                                          bdt.tm_mday, bdt.tm_hour, bdt.tm_min,
     193             :                                          bdt.tm_sec));
     194             :             }
     195          15 :             if (entry->bModeKnown)
     196             :             {
     197          15 :                 m_oWriter.AddObjKey("permissions");
     198          15 :                 m_oWriter.Add(permissions);
     199             :             }
     200          15 :             if (m_JSONAsTree && VSI_ISDIR(entry->nMode))
     201             :             {
     202           2 :                 m_stackNames.push_back(entry->pszName);
     203           2 :                 m_oWriter.AddObjKey("entries");
     204           2 :                 m_oWriter.StartArray();
     205             :             }
     206             :             else
     207             :             {
     208          13 :                 m_oWriter.EndObj();
     209             :             }
     210             :         }
     211             :         else
     212             :         {
     213         319 :             if (m_JSONAsTree && VSI_ISDIR(entry->nMode))
     214             :             {
     215           2 :                 m_oWriter.StartObj();
     216           2 :                 m_oWriter.AddObjKey("name");
     217           2 :                 m_oWriter.Add(filename);
     218             : 
     219           2 :                 m_stackNames.push_back(entry->pszName);
     220           2 :                 m_oWriter.AddObjKey("entries");
     221           2 :                 m_oWriter.StartArray();
     222             :             }
     223             :             else
     224             :             {
     225         317 :                 m_oWriter.Add(filename);
     226             :             }
     227             :         }
     228             :     }
     229          72 :     else if (m_longListing)
     230             :     {
     231         132 :         Print(CPLSPrintf("%s 1 unknown unknown %12" PRIu64
     232             :                          " %04d-%02d-%02d %02d:%02d %s\n",
     233          66 :                          permissions, static_cast<uint64_t>(entry->nSize),
     234          66 :                          bdt.tm_year + 1900, bdt.tm_mon + 1, bdt.tm_mday,
     235             :                          bdt.tm_hour, bdt.tm_min, filename.c_str()));
     236             :     }
     237             :     else
     238             :     {
     239           6 :         Print(filename.c_str());
     240           6 :         Print("\n");
     241             :     }
     242         406 : }
     243             : 
     244             : /************************************************************************/
     245             : /*                   GDALVSIListAlgorithm::RunImpl()                    */
     246             : /************************************************************************/
     247             : 
     248          19 : bool GDALVSIListAlgorithm::RunImpl(GDALProgressFunc, void *)
     249             : {
     250          19 :     if (m_format.empty())
     251          17 :         m_format = IsCalledFromCommandLine() ? "text" : "json";
     252             : 
     253             :     VSIStatBufL sStat;
     254          19 :     VSIErrorReset();
     255          19 :     const auto nOldErrorNum = VSIGetLastErrorNo();
     256          19 :     if (VSIStatL(m_filename.c_str(), &sStat) != 0)
     257             :     {
     258           2 :         if (nOldErrorNum != VSIGetLastErrorNo())
     259             :         {
     260           1 :             ReportError(CE_Failure, CPLE_FileIO,
     261             :                         "'%s' cannot be accessed. %s: %s", m_filename.c_str(),
     262             :                         VSIErrorNumToString(VSIGetLastErrorNo()),
     263             :                         VSIGetLastErrorMsg());
     264             :         }
     265             :         else
     266             :         {
     267           1 :             ReportError(CE_Failure, CPLE_FileIO,
     268             :                         "'%s' does not exist or cannot be accessed",
     269             :                         m_filename.c_str());
     270             :         }
     271           2 :         return false;
     272             :     }
     273             : 
     274          17 :     bool ret = false;
     275          17 :     if (VSI_ISDIR(sStat.st_mode))
     276             :     {
     277             :         std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
     278             :             VSIOpenDir(m_filename.c_str(),
     279           7 :                        m_recursive ? (m_depth == 0  ? 0
     280           7 :                                       : m_depth > 0 ? m_depth - 1
     281             :                                                     : -1)
     282             :                                    : 0,
     283             :                        nullptr),
     284          39 :             VSICloseDir);
     285          16 :         if (dir)
     286             :         {
     287          16 :             ret = true;
     288          16 :             if (m_format == "json")
     289          14 :                 m_oWriter.StartArray();
     290         421 :             while (const auto entry = VSIGetNextDirEntry(dir.get()))
     291             :             {
     292         405 :                 if (!(entry->pszName[0] == '.' &&
     293           0 :                       (entry->pszName[1] == '.' || entry->pszName[1] == 0)))
     294             :                 {
     295         405 :                     PrintEntry(entry);
     296             :                 }
     297         405 :             }
     298          20 :             while (!m_stackNames.empty())
     299             :             {
     300           4 :                 m_stackNames.pop_back();
     301           4 :                 m_oWriter.EndArray();
     302           4 :                 m_oWriter.EndObj();
     303             :             }
     304          16 :             if (m_format == "json")
     305          14 :                 m_oWriter.EndArray();
     306             :         }
     307             :     }
     308             :     else
     309             :     {
     310           1 :         ret = true;
     311           2 :         VSIDIREntry sEntry;
     312           1 :         sEntry.pszName = CPLStrdup(m_filename.c_str());
     313           1 :         sEntry.bModeKnown = true;
     314           1 :         sEntry.nMode = sStat.st_mode;
     315           1 :         sEntry.nSize = sStat.st_size;
     316           1 :         sEntry.nMTime = sStat.st_mtime;
     317           1 :         sEntry.bMTimeKnown = true;
     318           1 :         PrintEntry(&sEntry);
     319             :     }
     320             : 
     321          17 :     return ret;
     322             : }
     323             : 
     324             : //! @endcond

Generated by: LCOV version 1.14