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