Coverage Report

Created: 2026-02-14 06:52

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