Coverage Report

Created: 2026-06-30 08:33

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