Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "vsi copy" 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_copy.h"
14 :
15 : #include "cpl_conv.h"
16 : #include "cpl_string.h"
17 : #include "cpl_vsi.h"
18 : #include "cpl_vsi_error.h"
19 :
20 : #include <algorithm>
21 :
22 : //! @cond Doxygen_Suppress
23 :
24 : #ifndef _
25 : #define _(x) (x)
26 : #endif
27 :
28 : /************************************************************************/
29 : /* GDALVSICopyAlgorithm::GDALVSICopyAlgorithm() */
30 : /************************************************************************/
31 :
32 81 : GDALVSICopyAlgorithm::GDALVSICopyAlgorithm()
33 81 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
34 : {
35 : {
36 : auto &arg =
37 162 : AddArg("source", 0, _("Source file or directory name"), &m_source)
38 81 : .SetPositional()
39 81 : .SetMinCharCount(1)
40 81 : .SetRequired();
41 81 : SetAutoCompleteFunctionForFilename(arg, 0);
42 : }
43 : {
44 : auto &arg =
45 : AddArg("destination", 0, _("Destination file or directory name"),
46 162 : &m_destination)
47 81 : .SetPositional()
48 81 : .SetMinCharCount(1)
49 81 : .SetRequired()
50 : .AddAction(
51 38 : [this]()
52 : {
53 : // If outputting to stdout, automatically turn off progress bar
54 38 : if (m_destination == "/vsistdout/")
55 : {
56 0 : auto quietArg = GetArg(GDAL_ARG_NAME_QUIET);
57 0 : if (quietArg && quietArg->GetType() == GAAT_BOOLEAN)
58 0 : quietArg->Set(true);
59 : }
60 119 : });
61 81 : SetAutoCompleteFunctionForFilename(arg, 0);
62 : }
63 :
64 : AddArg("recursive", 'r', _("Copy subdirectories recursively"),
65 81 : &m_recursive);
66 :
67 81 : AddArg("skip-errors", 0, _("Skip errors"), &m_skip);
68 81 : AddProgressArg();
69 81 : }
70 :
71 : /************************************************************************/
72 : /* GDALVSICopyAlgorithm::RunImpl() */
73 : /************************************************************************/
74 :
75 38 : bool GDALVSICopyAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
76 : void *pProgressData)
77 : {
78 : const auto ReportSourceNotAccessible =
79 8 : [this](int nOldErrorNum, int nNewErrorNum, const std::string &filename)
80 : {
81 4 : if (nOldErrorNum != nNewErrorNum)
82 : {
83 1 : ReportError(CE_Failure, CPLE_FileIO,
84 : "'%s' cannot be accessed. %s: %s", filename.c_str(),
85 : VSIErrorNumToString(nNewErrorNum),
86 : VSIGetLastErrorMsg());
87 : }
88 : else
89 : {
90 3 : ReportError(CE_Failure, CPLE_FileIO, "'%s' cannot be accessed.",
91 : filename.c_str());
92 : }
93 42 : };
94 :
95 51 : if (m_recursive || cpl::ends_with(m_source, "/*") ||
96 51 : cpl::ends_with(m_source, "\\*"))
97 : {
98 : // Make sure that copy -r [srcdir/]lastsubdir targetdir' creates
99 : // targetdir/lastsubdir if targetdir already exists (like cp -r does).
100 25 : if (m_source.back() == '/')
101 1 : m_source.pop_back();
102 :
103 25 : if (!cpl::ends_with(m_source, "/*") && !cpl::ends_with(m_source, "\\*"))
104 : {
105 22 : const auto nOldErrorNum = VSIGetLastErrorNo();
106 22 : VSIErrorReset();
107 :
108 : VSIStatBufL statBufSrc;
109 22 : bool srcExists = VSIStatL(m_source.c_str(), &statBufSrc) == 0;
110 22 : if (!srcExists)
111 : {
112 1 : srcExists = VSIStatL(std::string(m_source).append("/").c_str(),
113 : &statBufSrc) == 0;
114 : }
115 22 : const auto nNewErrorNum = VSIGetLastErrorNo();
116 : VSIStatBufL statBufDst;
117 : const bool dstExists =
118 22 : VSIStatExL(m_destination.c_str(), &statBufDst,
119 22 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0;
120 22 : if (srcExists && VSI_ISDIR(statBufSrc.st_mode) && dstExists &&
121 7 : VSI_ISDIR(statBufDst.st_mode))
122 : {
123 7 : if (m_destination.back() == '/')
124 0 : m_destination.pop_back();
125 7 : const auto srcLastSlashPos = m_source.rfind('/');
126 7 : if (srcLastSlashPos != std::string::npos)
127 7 : m_destination += m_source.substr(srcLastSlashPos);
128 : else
129 0 : m_destination = CPLFormFilenameSafe(
130 7 : m_destination.c_str(), m_source.c_str(), nullptr);
131 : }
132 15 : else if (!srcExists)
133 : {
134 1 : ReportSourceNotAccessible(nOldErrorNum, nNewErrorNum, m_source);
135 1 : return false;
136 : }
137 : }
138 : else
139 : {
140 3 : const auto nOldErrorNum = VSIGetLastErrorNo();
141 3 : VSIErrorReset();
142 :
143 3 : m_source.resize(m_source.size() - 2);
144 : VSIStatBufL statBufSrc;
145 3 : bool srcExists = VSIStatL(m_source.c_str(), &statBufSrc) == 0;
146 3 : if (!srcExists)
147 : {
148 1 : const auto nNewErrorNum = VSIGetLastErrorNo();
149 1 : ReportSourceNotAccessible(nOldErrorNum, nNewErrorNum, m_source);
150 1 : return false;
151 : }
152 : }
153 :
154 23 : uint64_t curAmount = 0;
155 23 : return CopyRecursive(m_source, m_destination, 0, m_recursive ? -1 : 0,
156 23 : curAmount, 0, pfnProgress, pProgressData);
157 : }
158 : else
159 : {
160 13 : const auto nOldErrorNum = VSIGetLastErrorNo();
161 13 : VSIErrorReset();
162 :
163 : VSIStatBufL statBufSrc;
164 13 : bool srcExists = VSIStatL(m_source.c_str(), &statBufSrc) == 0;
165 13 : if (!srcExists)
166 : {
167 2 : const auto nNewErrorNum = VSIGetLastErrorNo();
168 2 : ReportSourceNotAccessible(nOldErrorNum, nNewErrorNum, m_source);
169 2 : return false;
170 : }
171 11 : if (VSI_ISDIR(statBufSrc.st_mode))
172 : {
173 1 : ReportError(CE_Failure, CPLE_FileIO,
174 : "%s is a directory. Use -r/--recursive option",
175 : m_source.c_str());
176 1 : return false;
177 : }
178 :
179 10 : return CopySingle(m_source, m_destination, ~(static_cast<uint64_t>(0)),
180 10 : pfnProgress, pProgressData);
181 : }
182 : }
183 :
184 : /************************************************************************/
185 : /* GDALVSICopyAlgorithm::CopySingle() */
186 : /************************************************************************/
187 :
188 92 : bool GDALVSICopyAlgorithm::CopySingle(const std::string &src,
189 : const std::string &dstIn, uint64_t size,
190 : GDALProgressFunc pfnProgress,
191 : void *pProgressData) const
192 : {
193 92 : CPLDebug("gdal_vsi_copy", "Copying file %s...", src.c_str());
194 : VSIStatBufL sStat;
195 92 : std::string dst = dstIn;
196 : const bool bExists =
197 183 : VSIStatExL(dst.back() == '/' ? dst.c_str()
198 182 : : std::string(dst).append("/").c_str(),
199 92 : &sStat, VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0;
200 178 : if ((!bExists && dst.back() == '/') ||
201 86 : (bExists && VSI_ISDIR(sStat.st_mode)))
202 : {
203 86 : const std::string filename = CPLGetFilename(src.c_str());
204 86 : dst = CPLFormFilenameSafe(dst.c_str(), filename.c_str(), nullptr);
205 : }
206 92 : return VSICopyFile(src.c_str(), dst.c_str(), nullptr, size, nullptr,
207 92 : pfnProgress, pProgressData) == 0 ||
208 184 : m_skip;
209 : }
210 :
211 : /************************************************************************/
212 : /* GDALVSICopyAlgorithm::CopyRecursive() */
213 : /************************************************************************/
214 :
215 70 : bool GDALVSICopyAlgorithm::CopyRecursive(const std::string &srcIn,
216 : const std::string &dst, int depth,
217 : int maxdepth, uint64_t &curAmount,
218 : uint64_t totalAmount,
219 : GDALProgressFunc pfnProgress,
220 : void *pProgressData) const
221 : {
222 140 : std::string src(srcIn);
223 70 : if (src.back() == '/')
224 0 : src.pop_back();
225 :
226 70 : if (pfnProgress && depth == 0)
227 : {
228 1 : CPLDebug("gdal_vsi_copy", "Listing source files...");
229 : std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
230 1 : VSIOpenDir(src.c_str(), maxdepth, nullptr), VSICloseDir);
231 1 : if (dir)
232 : {
233 4 : while (const auto entry = VSIGetNextDirEntry(dir.get()))
234 : {
235 3 : if (!(entry->pszName[0] == '.' &&
236 0 : (entry->pszName[1] == '.' || entry->pszName[1] == 0)))
237 : {
238 3 : totalAmount += entry->nSize + 1;
239 3 : if (!pfnProgress(0.0, "", pProgressData))
240 0 : return false;
241 : }
242 3 : }
243 : }
244 : }
245 70 : totalAmount = std::max<uint64_t>(1, totalAmount);
246 :
247 70 : CPLDebug("gdal_vsi_copy", "Copying directory %s...", src.c_str());
248 : std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
249 140 : VSIOpenDir(src.c_str(), 0, nullptr), VSICloseDir);
250 70 : if (dir)
251 : {
252 : VSIStatBufL sStat;
253 70 : if (VSIStatL(dst.c_str(), &sStat) != 0)
254 : {
255 70 : if (VSIMkdir(dst.c_str(), 0755) != 0)
256 : {
257 2 : ReportError(m_skip ? CE_Warning : CE_Failure, CPLE_FileIO,
258 : "Cannot create directory %s", dst.c_str());
259 2 : return m_skip;
260 : }
261 : }
262 :
263 198 : while (const auto entry = VSIGetNextDirEntry(dir.get()))
264 : {
265 130 : if (!(entry->pszName[0] == '.' &&
266 7 : (entry->pszName[1] == '.' || entry->pszName[1] == 0)))
267 : {
268 : const std::string subsrc =
269 130 : CPLFormFilenameSafe(src.c_str(), entry->pszName, nullptr);
270 130 : if (VSI_ISDIR(entry->nMode))
271 : {
272 : const std::string subdest = CPLFormFilenameSafe(
273 48 : dst.c_str(), entry->pszName, nullptr);
274 48 : if (maxdepth < 0 || depth < maxdepth)
275 : {
276 47 : if (!CopyRecursive(subsrc, subdest, depth + 1, maxdepth,
277 : curAmount, totalAmount, pfnProgress,
278 47 : pProgressData) &&
279 0 : !m_skip)
280 : {
281 0 : return false;
282 : }
283 : }
284 : else
285 : {
286 1 : if (VSIStatL(subdest.c_str(), &sStat) != 0)
287 : {
288 1 : if (VSIMkdir(subdest.c_str(), 0755) != 0)
289 : {
290 0 : ReportError(m_skip ? CE_Warning : CE_Failure,
291 : CPLE_FileIO,
292 : "Cannot create directory %s",
293 : subdest.c_str());
294 0 : if (!m_skip)
295 0 : return false;
296 : }
297 : }
298 : }
299 48 : curAmount += 1;
300 :
301 49 : if (pfnProgress &&
302 1 : !pfnProgress(
303 48 : std::min(1.0, static_cast<double>(curAmount) /
304 1 : static_cast<double>(totalAmount)),
305 : "", pProgressData))
306 : {
307 0 : return false;
308 : }
309 : }
310 : else
311 : {
312 164 : void *pScaledProgressData = GDALCreateScaledProgress(
313 82 : static_cast<double>(curAmount) /
314 82 : static_cast<double>(totalAmount),
315 0 : std::min(1.0, static_cast<double>(curAmount +
316 0 : entry->nSize + 1) /
317 82 : static_cast<double>(totalAmount)),
318 : pfnProgress, pProgressData);
319 82 : const bool bRet = CopySingle(
320 82 : subsrc, dst, entry->nSize,
321 : pScaledProgressData ? GDALScaledProgress : nullptr,
322 : pScaledProgressData);
323 82 : GDALDestroyScaledProgress(pScaledProgressData);
324 :
325 82 : curAmount += entry->nSize + 1;
326 :
327 82 : if (!bRet)
328 0 : return false;
329 : }
330 : }
331 130 : }
332 : }
333 : else
334 : {
335 0 : ReportError(m_skip ? CE_Warning : CE_Failure, CPLE_AppDefined,
336 : "%s is not a directory or cannot be opened", src.c_str());
337 0 : if (!m_skip)
338 0 : return false;
339 : }
340 68 : return true;
341 : }
342 :
343 : //! @endcond
|