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