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
|