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

Generated by: LCOV version 1.14