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