1 //  Copyright Andrey Semashev 2020.
2 
3 //  Distributed under the Boost Software License, Version 1.0.
4 //  See http://www.boost.org/LICENSE_1_0.txt
5 
6 //  Library home page: http://www.boost.org/libs/filesystem
7 
8 // This test verifies copy operation behavior.
9 
10 #include <boost/filesystem/operations.hpp>
11 #include <boost/filesystem/path.hpp>
12 #include <boost/filesystem/directory.hpp>
13 #include <boost/filesystem/exception.hpp>
14 #include <boost/system/error_code.hpp>
15 
16 #include <set>
17 #include <string>
18 #include <fstream>
19 #include <iostream>
20 #include <stdexcept>
21 
22 #include <boost/throw_exception.hpp>
23 #include <boost/exception/diagnostic_information.hpp>
24 #include <boost/core/lightweight_test.hpp>
25 
26 //  on Windows, except for standard libaries known to have wchar_t overloads for
27 //  file stream I/O, use path::string() to get a narrow character c_str()
28 #if defined(BOOST_WINDOWS_API) \
29   && (!defined(_CPPLIB_VER) || _CPPLIB_VER < 405)  // not Dinkumware || no wide overloads
30 # define BOOST_FILESYSTEM_C_STR string().c_str()  // use narrow, since wide not available
31 #else  // use the native c_str, which will be narrow on POSIX, wide on Windows
32 # define BOOST_FILESYSTEM_C_STR c_str()
33 #endif
34 
35 namespace fs = boost::filesystem;
36 
37 namespace {
38 
create_file(fs::path const & ph,std::string const & contents=std::string ())39 void create_file(fs::path const& ph, std::string const& contents = std::string())
40 {
41     std::ofstream f(ph.BOOST_FILESYSTEM_C_STR, std::ios_base::out | std::ios_base::trunc);
42     if (!f)
43         BOOST_THROW_EXCEPTION(std::runtime_error("Failed to create file: " + ph.string()));
44     if (!contents.empty())
45         f << contents;
46 }
47 
verify_file(fs::path const & ph,std::string const & expected)48 void verify_file(fs::path const& ph, std::string const& expected)
49 {
50     std::ifstream f(ph.BOOST_FILESYSTEM_C_STR);
51     if (!f)
52         BOOST_THROW_EXCEPTION(std::runtime_error("Failed to open file: " + ph.string()));
53     std::string contents;
54     f >> contents;
55     BOOST_TEST_EQ(contents, expected);
56     if (contents != expected)
57     {
58         BOOST_THROW_EXCEPTION(std::runtime_error("verify_file failed: contents \"" + contents  + "\" != \"" + expected + "\" in " + ph.string()));
59     }
60 }
61 
create_tree()62 fs::path create_tree()
63 {
64     fs::path root_dir = fs::unique_path();
65 
66     fs::create_directory(root_dir);
67     create_file(root_dir / "f1", "f1");
68     create_file(root_dir / "f2", "f2");
69 
70     fs::create_directory(root_dir / "d1");
71     create_file(root_dir / "d1/f1", "d1f1");
72 
73     fs::create_directory(root_dir / "d1/d1");
74     create_file(root_dir / "d1/d1/f1", "d1d1f1");
75 
76     fs::create_directory(root_dir / "d1/d2");
77 
78     fs::create_directory(root_dir / "d2");
79     create_file(root_dir / "d2/f1", "d2f1");
80 
81     return root_dir;
82 }
83 
84 typedef std::set< fs::path > directory_tree;
85 
collect_directory_tree(fs::path const & root_dir)86 directory_tree collect_directory_tree(fs::path const& root_dir)
87 {
88     std::cout << "Collecting directory tree in: " << root_dir << '\n';
89 
90     directory_tree tree;
91     fs::recursive_directory_iterator it(root_dir, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink | fs::directory_options::skip_dangling_symlinks), end;
92     while (it != end)
93     {
94         fs::path p = fs::relative(it->path(), root_dir);
95         std::cout << p << '\n';
96         tree.insert(p);
97         ++it;
98     }
99 
100     std::cout << "done." << std::endl;
101 
102     return tree;
103 }
104 
test_copy_file_default(fs::path const & root_dir)105 void test_copy_file_default(fs::path const& root_dir)
106 {
107     std::cout << "test_copy_file_default" << std::endl;
108 
109     fs::path target_dir = fs::unique_path();
110     fs::create_directory(target_dir);
111 
112     fs::copy(root_dir / "f1", target_dir);
113     fs::copy(root_dir / "f2", target_dir / "f3");
114 
115     directory_tree tree = collect_directory_tree(target_dir);
116 
117     BOOST_TEST_EQ(tree.size(), 2u);
118     BOOST_TEST(tree.find("f1") != tree.end());
119     BOOST_TEST(tree.find("f3") != tree.end());
120 
121     verify_file(target_dir / "f1", "f1");
122     verify_file(target_dir / "f3", "f2");
123 
124     fs::remove_all(target_dir);
125 }
126 
test_copy_dir_default(fs::path const & root_dir,bool with_symlinks)127 void test_copy_dir_default(fs::path const& root_dir, bool with_symlinks)
128 {
129     std::cout << "test_copy_dir_default" << std::endl;
130 
131     fs::path target_dir = fs::unique_path();
132 
133     fs::copy(root_dir, target_dir);
134 
135     directory_tree tree = collect_directory_tree(target_dir);
136 
137     BOOST_TEST_EQ(tree.size(), 4u + with_symlinks);
138     BOOST_TEST(tree.find("f1") != tree.end());
139     BOOST_TEST(tree.find("f2") != tree.end());
140     BOOST_TEST(tree.find("d1") != tree.end());
141     BOOST_TEST(tree.find("d2") != tree.end());
142     if (with_symlinks)
143     {
144         BOOST_TEST(tree.find("s1") != tree.end());
145     }
146 
147     verify_file(target_dir / "f1", "f1");
148     verify_file(target_dir / "f2", "f2");
149 
150     fs::remove_all(target_dir);
151 }
152 
test_copy_dir_default_ec(fs::path const & root_dir,bool with_symlinks)153 void test_copy_dir_default_ec(fs::path const& root_dir, bool with_symlinks)
154 {
155     // This test is similar to test_copy_dir_default, but uses an error_code overload of the operation.
156     // Tests for https://github.com/boostorg/filesystem/issues/152 fix.
157 
158     std::cout << "test_copy_dir_default_ec" << std::endl;
159 
160     fs::path target_dir = fs::unique_path();
161 
162     boost::system::error_code ec;
163     fs::copy(root_dir, target_dir, ec);
164     BOOST_TEST(!ec);
165 
166     directory_tree tree = collect_directory_tree(target_dir);
167 
168     BOOST_TEST_EQ(tree.size(), 4u + with_symlinks);
169     BOOST_TEST(tree.find("f1") != tree.end());
170     BOOST_TEST(tree.find("f2") != tree.end());
171     BOOST_TEST(tree.find("d1") != tree.end());
172     BOOST_TEST(tree.find("d2") != tree.end());
173     if (with_symlinks)
174     {
175         BOOST_TEST(tree.find("s1") != tree.end());
176     }
177 
178     verify_file(target_dir / "f1", "f1");
179     verify_file(target_dir / "f2", "f2");
180 
181     fs::remove_all(target_dir);
182 }
183 
test_copy_dir_recursive(fs::path const & root_dir)184 void test_copy_dir_recursive(fs::path const& root_dir)
185 {
186     std::cout << "test_copy_dir_recursive" << std::endl;
187 
188     fs::path target_dir = fs::unique_path();
189 
190     fs::copy(root_dir, target_dir, fs::copy_options::recursive);
191 
192     directory_tree tree = collect_directory_tree(target_dir);
193 
194     BOOST_TEST_EQ(tree.size(), 9u);
195     BOOST_TEST(tree.find("f1") != tree.end());
196     BOOST_TEST(tree.find("f2") != tree.end());
197     BOOST_TEST(tree.find("d1") != tree.end());
198     BOOST_TEST(tree.find(fs::path("d1") / "f1") != tree.end());
199     BOOST_TEST(tree.find(fs::path("d1") / "d1") != tree.end());
200     BOOST_TEST(tree.find(fs::path("d1") / "d1" / "f1") != tree.end());
201     BOOST_TEST(tree.find(fs::path("d1") / "d2") != tree.end());
202     BOOST_TEST(tree.find("d2") != tree.end());
203     BOOST_TEST(tree.find(fs::path("d2") / "f1") != tree.end());
204 
205     verify_file(target_dir / "f1", "f1");
206     verify_file(target_dir / "f2", "f2");
207     verify_file(target_dir / "d1/f1", "d1f1");
208     verify_file(target_dir / "d1/d1/f1", "d1d1f1");
209     verify_file(target_dir / "d2/f1", "d2f1");
210 
211     fs::remove_all(target_dir);
212 }
213 
test_copy_dir_recursive_tree(fs::path const & root_dir)214 void test_copy_dir_recursive_tree(fs::path const& root_dir)
215 {
216     std::cout << "test_copy_dir_recursive_tree" << std::endl;
217 
218     fs::path target_dir = fs::unique_path();
219 
220     fs::copy(root_dir, target_dir, fs::copy_options::recursive | fs::copy_options::directories_only);
221 
222     directory_tree tree = collect_directory_tree(target_dir);
223 
224     BOOST_TEST_EQ(tree.size(), 4u);
225     BOOST_TEST(tree.find("d1") != tree.end());
226     BOOST_TEST(tree.find(fs::path("d1") / "d1") != tree.end());
227     BOOST_TEST(tree.find(fs::path("d1") / "d2") != tree.end());
228     BOOST_TEST(tree.find("d2") != tree.end());
229 
230     fs::remove_all(target_dir);
231 }
232 
test_copy_file_symlinks(fs::path const & root_dir)233 void test_copy_file_symlinks(fs::path const& root_dir)
234 {
235     std::cout << "test_copy_file_symlinks" << std::endl;
236 
237     fs::path target_dir = fs::unique_path();
238     fs::create_directory(target_dir);
239 
240     fs::copy(root_dir / "f1", target_dir);
241 
242     fs::path prev_cur_dir = fs::current_path();
243     fs::current_path(target_dir);
244     fs::copy(".." / root_dir / "f2", "f2", fs::copy_options::create_symlinks);
245     fs::current_path(prev_cur_dir);
246 
247     // Copying from a relative path with copy_options::create_symlinks to a directory other than current directory is a non-standard extension
248     fs::copy(target_dir / "f1", target_dir / "f3", fs::copy_options::create_symlinks);
249 
250     verify_file(target_dir / "f1", "f1");
251 
252     fs::path link_target = fs::read_symlink(target_dir / "f2");
253     if (link_target != (".." / root_dir / "f2"))
254     {
255         BOOST_ERROR("Incorrect f2 symlink in test_copy_file_symlinks");
256         std::cout << (target_dir / "f2") << " => " << link_target << std::endl;
257     }
258 
259     link_target = fs::read_symlink(target_dir / "f3");
260     if (link_target != "f1" && link_target != (fs::path(".") / "f1"))
261     {
262         BOOST_ERROR("Incorrect f3 symlink in test_copy_file_symlinks");
263         std::cout << (target_dir / "f3") << " => " << link_target << std::endl;
264     }
265 
266     fs::remove_all(target_dir);
267 }
268 
test_copy_errors(fs::path const & root_dir,bool symlinks_supported)269 void test_copy_errors(fs::path const& root_dir, bool symlinks_supported)
270 {
271     std::cout << "test_copy_errors" << std::endl;
272 
273     fs::path target_dir = fs::unique_path();
274     fs::create_directory(target_dir);
275 
276     BOOST_TEST_THROWS(fs::copy(root_dir / "non-existing", target_dir), fs::filesystem_error);
277 
278     create_file(target_dir / "f1");
279 
280     BOOST_TEST_THROWS(fs::copy(root_dir / "f1", target_dir), fs::filesystem_error);
281     BOOST_TEST_THROWS(fs::copy(root_dir / "f1", target_dir / "f1"), fs::filesystem_error);
282     BOOST_TEST_THROWS(fs::copy(root_dir / "d1", target_dir / "f1"), fs::filesystem_error);
283 
284     BOOST_TEST_THROWS(fs::copy(target_dir, target_dir), fs::filesystem_error);
285     BOOST_TEST_THROWS(fs::copy(target_dir / "f1", target_dir / "f1"), fs::filesystem_error);
286 
287     if (symlinks_supported)
288     {
289         // Should fail with is_a_directory error code
290         BOOST_TEST_THROWS(fs::copy(root_dir, target_dir, fs::copy_options::create_symlinks), fs::filesystem_error);
291     }
292 
293     fs::remove_all(target_dir);
294 }
295 
296 } // namespace
297 
main()298 int main()
299 {
300     try
301     {
302         fs::path root_dir = create_tree();
303 
304         test_copy_file_default(root_dir);
305         test_copy_dir_default(root_dir, false);
306         test_copy_dir_default_ec(root_dir, false);
307         test_copy_dir_recursive(root_dir);
308         test_copy_dir_recursive_tree(root_dir);
309 
310         bool symlinks_supported = false;
311         try
312         {
313             fs::create_symlink("f1", root_dir / "s1");
314             symlinks_supported = true;
315             std::cout <<
316                 "     *** For information only ***\n"
317                 "     create_symlink() attempt succeeded" << std::endl;
318         }
319         catch (fs::filesystem_error& e)
320         {
321             std::cout <<
322                 "     *** For information only ***\n"
323                 "     create_symlink() attempt failed\n"
324                 "     filesystem_error.what() reports: " << e.what() << "\n"
325                 "     create_symlink() may not be supported on this operating system or file system" << std::endl;
326         }
327 
328         if (symlinks_supported)
329         {
330             test_copy_dir_default(root_dir, true);
331             test_copy_file_symlinks(root_dir);
332         }
333 
334         test_copy_errors(root_dir, symlinks_supported);
335 
336         fs::remove_all(root_dir);
337 
338         return boost::report_errors();
339     }
340     catch (std::exception& e)
341     {
342         std::cout << "FAIL, exception caught: " << boost::diagnostic_information(e) << std::endl;
343         return 1;
344     }
345 }
346