Line data Source code
1 : /*!
2 : * \file esys/repo/filesystem.cpp
3 : * \brief
4 : *
5 : * \cond
6 : * __legal_b__
7 : *
8 : * Copyright (c) 2020-2026 Michel Gillet
9 : * Distributed under the MIT License.
10 : * (See accompanying file LICENSE.txt or
11 : * copy at https://opensource.org/licenses/MIT)
12 : *
13 : * __legal_e__
14 : * \endcond
15 : *
16 : */
17 :
18 : #include "esys/repo/esysrepo_prec.h"
19 : #include "esys/repo/filesystem.h"
20 :
21 : #if ESYSREPO_HAS_STD_FILESYSTEM
22 : #include <filesystem>
23 : #endif
24 :
25 : #include <boost/filesystem.hpp>
26 : #include <boost/version.hpp>
27 :
28 : #if BOOST_VERSION <= 107100
29 : #define ESYSREPO_BOOST_FILESYSTEM_HAS_RECUR 0
30 : #else
31 : #define ESYSREPO_BOOST_FILESYSTEM_HAS_RECUR 1
32 : #endif
33 :
34 : namespace esys::repo
35 : {
36 :
37 : namespace stdcpp
38 : {
39 :
40 0 : ESYSREPO_API int copy(const std::string &src, const std::string &dst, bool recursive)
41 : {
42 : #if ESYSREPO_HAS_STD_FILESYSTEM
43 : try
44 : {
45 : auto options = std::filesystem::copy_options::copy_symlinks;
46 : if (recursive) options |= std::filesystem::copy_options::recursive;
47 : std::filesystem::copy(src, dst, options);
48 : }
49 : catch (std::exception &)
50 : {
51 : return -1;
52 : }
53 : return 0;
54 : #else
55 0 : (void)src;
56 0 : (void)dst;
57 0 : (void)recursive;
58 0 : return -1;
59 : #endif
60 : }
61 :
62 0 : ESYSREPO_API int remove_all(const std::string &path)
63 : {
64 : #if ESYSREPO_HAS_STD_FILESYSTEM
65 : try
66 : {
67 : std::filesystem::remove_all(path);
68 : }
69 : catch (std::exception &)
70 : {
71 : return -1;
72 : }
73 : return 0;
74 : #else
75 0 : (void)path;
76 0 : return -1;
77 : #endif
78 : }
79 :
80 : } // namespace stdcpp
81 :
82 : namespace boost_no_recur
83 : {
84 :
85 262 : ESYSREPO_API int copy(const std::string &src, const std::string &dst, bool recursive)
86 : {
87 262 : boost::system::error_code ec;
88 :
89 262 : if (!recursive)
90 : {
91 0 : const auto sym_stat = boost::filesystem::symlink_status(src, ec);
92 0 : if (ec) return -1;
93 0 : if (boost::filesystem::is_symlink(sym_stat))
94 : {
95 0 : boost::filesystem::copy_symlink(src, dst, ec);
96 0 : return ec ? -1 : 0;
97 : }
98 0 : try
99 : {
100 0 : boost::filesystem::copy(src, dst);
101 : }
102 0 : catch (std::exception &)
103 : {
104 0 : return -1;
105 0 : }
106 0 : return 0;
107 : }
108 :
109 262 : boost::filesystem::directory_iterator it(src, ec);
110 524 : if (ec) return -1;
111 :
112 : try
113 : {
114 911 : while (it != boost::filesystem::directory_iterator{})
115 : {
116 649 : boost::filesystem::path copy_to = dst;
117 1298 : copy_to /= it->path().filename();
118 :
119 649 : const auto sym_stat = boost::filesystem::symlink_status(*it, ec);
120 649 : if (ec) return -1;
121 :
122 649 : if (boost::filesystem::is_symlink(sym_stat))
123 : {
124 : // Preserve Git mode-120000 links (e.g. ChromiumOS default.xml -> snapshot.xml).
125 1 : boost::filesystem::copy_symlink(*it, copy_to, ec);
126 1 : if (ec) return -1;
127 : }
128 648 : else if (boost::filesystem::is_directory(sym_stat))
129 : {
130 247 : if (!boost::filesystem::create_directory(copy_to)) return -1;
131 247 : int result = copy(it->path().string(), copy_to.string(), recursive);
132 247 : if (result < 0) return result;
133 : }
134 : else
135 : {
136 401 : boost::filesystem::copy(*it, copy_to, ec);
137 401 : if (ec) return -1;
138 : }
139 :
140 649 : it.increment(ec);
141 649 : if (ec) return -1;
142 649 : }
143 : }
144 0 : catch (std::exception &)
145 : {
146 0 : return -1;
147 0 : }
148 : return 0;
149 262 : }
150 :
151 : } // namespace boost_no_recur
152 :
153 : namespace boost_recur
154 : {
155 :
156 0 : ESYSREPO_API int copy(const std::string &src, const std::string &dst, bool recursive)
157 : {
158 : #if ESYSREPO_BOOST_FILESYSTEM_HAS_RECUR
159 0 : try
160 : {
161 0 : auto options = boost::filesystem::copy_options::copy_symlinks;
162 0 : if (recursive) options |= boost::filesystem::copy_options::recursive;
163 0 : boost::filesystem::copy(src, dst, options);
164 : }
165 0 : catch (std::exception &)
166 : {
167 0 : return -1;
168 0 : }
169 0 : return 0;
170 : #else
171 : (void)src;
172 : (void)dst;
173 : (void)recursive;
174 : return -1;
175 : #endif
176 : }
177 :
178 : } // namespace boost_recur
179 :
180 : namespace boost_all
181 : {
182 :
183 0 : ESYSREPO_API int remove_all(const std::string &path)
184 : {
185 0 : try
186 : {
187 0 : boost::filesystem::remove_all(path);
188 : }
189 0 : catch (std::exception &)
190 : {
191 0 : return -1;
192 0 : }
193 0 : return 0;
194 : }
195 :
196 : } // namespace boost_all
197 :
198 : namespace boost_no_all
199 : {
200 :
201 264 : ESYSREPO_API int remove_all(const std::string &path)
202 : {
203 264 : boost::system::error_code ec;
204 264 : boost::filesystem::directory_iterator it(path, ec);
205 264 : if (ec) return -1;
206 :
207 : try
208 : {
209 913 : while (it != boost::filesystem::directory_iterator{})
210 : {
211 649 : const auto sym_stat = boost::filesystem::symlink_status(*it, ec);
212 649 : if (ec) return -1;
213 :
214 649 : if (boost::filesystem::is_symlink(sym_stat))
215 : {
216 : // Remove the link itself; do not follow into the target.
217 0 : boost::filesystem::remove(*it, ec);
218 0 : if (ec) return -1;
219 : }
220 649 : else if (boost::filesystem::is_directory(sym_stat))
221 : {
222 249 : int result = boost_no_all::remove_all(it->path().string());
223 249 : if (result < 0) return result;
224 : }
225 : else
226 : {
227 400 : auto stat = boost::filesystem::status(it->path(), ec);
228 :
229 400 : if (ec == boost::system::error_code())
230 : {
231 400 : if ((stat.permissions() & boost::filesystem::perms::others_write)
232 : != boost::filesystem::perms::others_write)
233 : {
234 400 : boost::filesystem::permissions(*it,
235 : stat.permissions() | boost::filesystem::perms::others_write);
236 : }
237 : }
238 :
239 400 : try
240 : {
241 400 : boost::filesystem::remove(*it);
242 : }
243 0 : catch (std::exception &)
244 : {
245 0 : return -1;
246 0 : }
247 : }
248 649 : it.increment(ec);
249 649 : if (ec) return -1;
250 : }
251 264 : boost::filesystem::remove(path, ec);
252 264 : if (ec) return -1;
253 : }
254 0 : catch (std::exception &)
255 : {
256 0 : return -1;
257 0 : }
258 : return 0;
259 264 : }
260 :
261 35 : ESYSREPO_API int move(const std::string &src, const std::string &dst, bool recursive)
262 : {
263 35 : boost::system::error_code ec;
264 35 : const boost::filesystem::path src_p(src);
265 35 : const boost::filesystem::path dst_p(dst);
266 :
267 35 : const bool dst_exists = boost::filesystem::exists(dst_p);
268 35 : const bool dst_empty_dir =
269 105 : dst_exists && boost::filesystem::is_directory(dst_p) && boost::filesystem::is_empty(dst_p);
270 :
271 : // Prefer rename so Git symlinks (mode 120000) stay links instead of followed copies.
272 35 : if (!dst_exists || dst_empty_dir)
273 : {
274 21 : if (dst_empty_dir)
275 : {
276 21 : boost::filesystem::remove(dst_p, ec);
277 21 : if (ec) return -1;
278 : }
279 :
280 21 : boost::filesystem::rename(src_p, dst_p, ec);
281 21 : if (!ec) return 0;
282 :
283 : // Cross-device rename failure: restore an empty destination when we removed one.
284 0 : if (dst_empty_dir)
285 : {
286 0 : boost::filesystem::create_directory(dst_p, ec);
287 0 : if (ec) return -1;
288 : }
289 : }
290 :
291 : // Recursive copy merges children into an existing destination directory.
292 14 : if (!boost::filesystem::exists(dst_p))
293 : {
294 0 : boost::filesystem::create_directories(dst_p, ec);
295 35 : if (ec) return -1;
296 : }
297 :
298 14 : int result = copy(src, dst, recursive);
299 14 : if (result < 0) return -1;
300 :
301 14 : result = remove_all(src);
302 14 : if (result < 0) return -2;
303 : return 0;
304 35 : }
305 :
306 : } // namespace boost_no_all
307 :
308 0 : ESYSREPO_API int move(const std::string &src, const std::string &dst, bool recursive)
309 : {
310 0 : return boost_no_all::move(src, dst, recursive);
311 : }
312 :
313 : } // namespace esys::repo
|