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