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