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
|