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