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).SetDefault("json").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 1807 : void GDALVSIListAlgorithm::Print(const char *str)
66 : {
67 1807 : if (m_stdout)
68 0 : fwrite(str, 1, strlen(str), stdout);
69 : else
70 1807 : m_output += str;
71 1807 : }
72 :
73 : /************************************************************************/
74 : /* GDALVSIListAlgorithm::JSONPrint() */
75 : /************************************************************************/
76 :
77 1734 : /* static */ void GDALVSIListAlgorithm::JSONPrint(const char *pszTxt,
78 : void *pUserData)
79 : {
80 1734 : static_cast<GDALVSIListAlgorithm *>(pUserData)->Print(pszTxt);
81 1734 : }
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 361 : void GDALVSIListAlgorithm::PrintEntry(const VSIDIREntry *entry)
105 : {
106 722 : std::string filename;
107 361 : if (m_format == "json" && m_JSONAsTree)
108 : {
109 12 : filename = CPLGetFilename(entry->pszName);
110 : }
111 349 : else if (m_absolutePath)
112 : {
113 67 : if (CPLIsFilenameRelative(m_filename.c_str()))
114 : {
115 61 : char *pszCurDir = CPLGetCurrentDir();
116 61 : if (!pszCurDir)
117 0 : pszCurDir = CPLStrdup(".");
118 61 : if (m_filename == ".")
119 0 : filename = pszCurDir;
120 : else
121 : filename =
122 61 : CPLFormFilenameSafe(pszCurDir, m_filename.c_str(), nullptr);
123 61 : CPLFree(pszCurDir);
124 : }
125 : else
126 : {
127 6 : filename = m_filename;
128 : }
129 : filename =
130 67 : CPLFormFilenameSafe(filename.c_str(), entry->pszName, nullptr);
131 : }
132 : else
133 : {
134 282 : filename = entry->pszName;
135 : }
136 :
137 361 : char permissions[1 + 3 + 3 + 3 + 1] = "----------";
138 : struct tm bdt;
139 361 : memset(&bdt, 0, sizeof(bdt));
140 :
141 361 : if (m_longListing)
142 : {
143 76 : if (entry->bModeKnown)
144 : {
145 76 : if (VSI_ISDIR(entry->nMode))
146 5 : permissions[0] = 'd';
147 760 : for (int i = 0; i < 9; ++i)
148 : {
149 684 : if (entry->nMode & (1 << i))
150 488 : permissions[9 - i] = (i % 3) == 0 ? 'x'
151 244 : : (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 76 : CPLUnixTimeToYMDHMS(entry->nMTime, &bdt);
165 : }
166 :
167 361 : if (m_format == "json")
168 : {
169 294 : 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 294 : 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 279 : 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 277 : m_oWriter.Add(filename);
228 : }
229 : }
230 : }
231 67 : else if (m_longListing)
232 : {
233 122 : Print(CPLSPrintf("%s 1 unknown unknown %12" PRIu64
234 : " %04d-%02d-%02d %02d:%02d %s\n",
235 61 : permissions, static_cast<uint64_t>(entry->nSize),
236 61 : 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 361 : }
245 :
246 : /************************************************************************/
247 : /* GDALVSIListAlgorithm::RunImpl() */
248 : /************************************************************************/
249 :
250 17 : bool GDALVSIListAlgorithm::RunImpl(GDALProgressFunc, void *)
251 : {
252 : VSIStatBufL sStat;
253 17 : VSIErrorReset();
254 17 : const auto nOldErrorNum = VSIGetLastErrorNo();
255 17 : if (VSIStatL(m_filename.c_str(), &sStat) != 0)
256 : {
257 2 : if (nOldErrorNum != VSIGetLastErrorNo())
258 : {
259 1 : ReportError(CE_Failure, CPLE_FileIO,
260 : "'%s' cannot be accessed. %s: %s", m_filename.c_str(),
261 : VSIErrorNumToString(VSIGetLastErrorNo()),
262 : VSIGetLastErrorMsg());
263 : }
264 : else
265 : {
266 1 : ReportError(CE_Failure, CPLE_FileIO,
267 : "'%s' does not exist or cannot be accessed",
268 : m_filename.c_str());
269 : }
270 2 : return false;
271 : }
272 :
273 15 : bool ret = false;
274 15 : if (VSI_ISDIR(sStat.st_mode))
275 : {
276 : std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
277 : VSIOpenDir(m_filename.c_str(),
278 7 : m_recursive ? (m_depth == 0 ? 0
279 7 : : m_depth > 0 ? m_depth - 1
280 : : -1)
281 : : 0,
282 : nullptr),
283 35 : VSICloseDir);
284 14 : if (dir)
285 : {
286 14 : ret = true;
287 14 : if (m_format == "json")
288 12 : m_oWriter.StartArray();
289 374 : while (const auto entry = VSIGetNextDirEntry(dir.get()))
290 : {
291 360 : if (!(entry->pszName[0] == '.' &&
292 0 : (entry->pszName[1] == '.' || entry->pszName[1] == 0)))
293 : {
294 360 : PrintEntry(entry);
295 : }
296 360 : }
297 18 : while (!m_stackNames.empty())
298 : {
299 4 : m_stackNames.pop_back();
300 4 : m_oWriter.EndArray();
301 4 : m_oWriter.EndObj();
302 : }
303 14 : if (m_format == "json")
304 12 : m_oWriter.EndArray();
305 : }
306 : }
307 : else
308 : {
309 1 : ret = true;
310 2 : VSIDIREntry sEntry;
311 1 : sEntry.pszName = CPLStrdup(m_filename.c_str());
312 1 : sEntry.bModeKnown = true;
313 1 : sEntry.nMode = sStat.st_mode;
314 1 : sEntry.nSize = sStat.st_size;
315 1 : PrintEntry(&sEntry);
316 : }
317 :
318 15 : return ret;
319 : }
320 :
321 : //! @endcond
|