jaulib v1.3.0
Jau Support Library (C++, Java, ..)
test_fileutils_copy_r_p.hpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25#include "test_fileutils.hpp"
26
27void testxx_copy_r_p(const std::string& title,
28 const jau::fs::file_stats& source, const int source_added_dead_links,
29 const std::string& dest,
30 const jau::fs::copy_options copts,
31 const bool dest_is_vfat)
32{
33 REQUIRE( true == source.exists() );
34 REQUIRE( true == source.is_dir() );
35
36 bool dest_is_parent;
37 std::string dest_root;
38 {
39 jau::fs::file_stats dest_stats(dest);
40 if( dest_stats.exists() ) {
41 // If dest_path exists as a directory, source_path dir will be copied below the dest_path directory
42 // _if_ copy_options::into_existing_dir is not set. Otherwise its content is copied into the existing dest_path.
43 REQUIRE( true == dest_stats.is_dir() );
45 dest_is_parent = false;
46 dest_root = dest;
47 } else {
48 dest_is_parent = true;
49 dest_root = dest + "/" + source.item().basename();
50 }
51 } else {
52 // If dest_path doesn't exist, source_path dir content is copied into the newly created dest_path.
53 dest_is_parent = false;
54 dest_root = dest;
55 }
56 }
57 jau::fprintf_td(stdout, "%s: source %s, dest[arg %s, is_parent %d, dest_root %s], copts %s, dest_is_vfat %d\n",
58 title.c_str(), source.to_string().c_str(),
59 dest.c_str(), dest_is_parent, dest_root.c_str(),
60 to_string(copts).c_str(), dest_is_vfat);
61
62 const bool opt_follow_links = is_set(copts, jau::fs::copy_options::follow_symlinks );
63 const bool opt_drop_dest_links = !opt_follow_links && is_set(copts, jau::fs::copy_options::ignore_symlink_errors );
64
65 REQUIRE( true == jau::fs::copy(source.path(), dest, copts) );
66
67 jau::fs::file_stats dest_stats(dest_root);
68 REQUIRE( true == dest_stats.exists() );
69 REQUIRE( true == dest_stats.ok() );
70 REQUIRE( true == dest_stats.is_dir() );
71
72 bool(*pv_capture)(visitor_stats*, jau::fs::traverse_event, const jau::fs::file_stats&, size_t) =
73 ( [](visitor_stats* stats_ptr, jau::fs::traverse_event tevt, const jau::fs::file_stats& element_stats, size_t depth) -> bool {
74 (void)tevt;
75 (void)depth;
76 stats_ptr->add(element_stats);
77 return true;
78 } );
79 {
80
83 visitor_stats stats(topts);
84 visitor_stats stats_copy(topts);
85 const jau::fs::path_visitor pv_orig = jau::bind_capref(&stats, pv_capture);
86 const jau::fs::path_visitor pv_copy = jau::bind_capref(&stats_copy, pv_capture);
87 REQUIRE( true == jau::fs::visit(source, topts, pv_orig) );
88 REQUIRE( true == jau::fs::visit(dest_stats, topts, pv_copy) );
89
90 jau::fprintf_td(stdout, "%s: copy %s, traverse %s\n",
91 title.c_str(), to_string(copts).c_str(), to_string(topts).c_str());
92
93 jau::fprintf_td(stdout, "%s: source visitor stats\n%s\n", title.c_str(), stats.to_string().c_str());
94 jau::fprintf_td(stdout, "%s: destination visitor stats\n%s\n", title.c_str(), stats_copy.to_string().c_str());
95
96 REQUIRE( 7 == stats.total_real );
97 REQUIRE( 10 - source_added_dead_links == stats.total_sym_links_existing );
98 REQUIRE( 4 + source_added_dead_links == stats.total_sym_links_not_existing );
99 REQUIRE( 0 == stats.total_no_access );
100 REQUIRE( 4 + source_added_dead_links == stats.total_not_existing );
101 REQUIRE( 60 == stats.total_file_bytes );
102 REQUIRE( 4 == stats.files_real );
103 REQUIRE( 9 - source_added_dead_links == stats.files_sym_link );
104 REQUIRE( 3 == stats.dirs_real );
105 REQUIRE( 1 == stats.dirs_sym_link );
106
107 if( ( !opt_follow_links && !opt_drop_dest_links ) ||
108 ( opt_drop_dest_links && 0 < stats_copy.total_sym_links_existing )
109 )
110 {
111 // 1:1 exact copy
112 REQUIRE( 7 == stats_copy.total_real );
113 REQUIRE( 9 == stats_copy.total_sym_links_existing );
114 REQUIRE( 5 == stats_copy.total_sym_links_not_existing ); // symlink ../README.txt + 4 dead_link*
115 REQUIRE( 0 == stats_copy.total_no_access );
116 REQUIRE( 5 == stats_copy.total_not_existing ); // symlink ../README.txt + 4 dead_link*
117 REQUIRE( 60 == stats_copy.total_file_bytes );
118 REQUIRE( 4 == stats_copy.files_real );
119 REQUIRE( 8 == stats_copy.files_sym_link );
120 REQUIRE( 3 == stats_copy.dirs_real );
121 REQUIRE( 1 == stats_copy.dirs_sym_link );
122 } else if( opt_drop_dest_links ) {
123 // destination filesystem has no symlink support, i.e. vfat
124 REQUIRE( 7 == stats_copy.total_real );
125 REQUIRE( 0 == stats_copy.total_sym_links_existing );
126 REQUIRE( 0 == stats_copy.total_sym_links_not_existing ); // symlink ../README.txt + 4 dead_link*
127 REQUIRE( 0 == stats_copy.total_no_access );
128 REQUIRE( 0 == stats_copy.total_not_existing ); // symlink ../README.txt + 4 dead_link*
129 REQUIRE( 60 == stats_copy.total_file_bytes );
130 REQUIRE( 4 == stats_copy.files_real );
131 REQUIRE( 0 == stats_copy.files_sym_link );
132 REQUIRE( 3 == stats_copy.dirs_real );
133 REQUIRE( 0 == stats_copy.dirs_sym_link );
134 } else if( opt_follow_links ) {
135 // followed symlinks
136 REQUIRE( 20 == stats_copy.total_real );
137 REQUIRE( 0 == stats_copy.total_sym_links_existing );
138 REQUIRE( 0 == stats_copy.total_sym_links_not_existing );
139 REQUIRE( 0 == stats_copy.total_no_access );
140 REQUIRE( 0 == stats_copy.total_not_existing );
141 REQUIRE( 60 < stats_copy.total_file_bytes ); // some followed symlink files are of unknown size, e.g. /etc/fstab
142 REQUIRE( 16 == stats_copy.files_real );
143 REQUIRE( 0 == stats_copy.files_sym_link );
144 REQUIRE( 4 == stats_copy.dirs_real );
145 REQUIRE( 0 == stats_copy.dirs_sym_link );
146 }
147 }
148 {
149 // compare each file in detail O(n*n)
152 struct source_visitor_params {
153 std::string title;
154 std::string source_folder_path;
156 bool dest_is_vfat;
157 bool opt_drop_dest_links;
158 };
159 struct dest_visitor_params {
160 std::string title;
161 std::string source_folder_path;
162 std::string dest_folder_path;
163 std::string source_basename;
165 bool dest_is_vfat;
166 bool match;
167 };
168 source_visitor_params svp { title, source.path(), dest_stats, dest_is_vfat, opt_drop_dest_links };
169 const jau::fs::path_visitor pv1 = jau::bind_capref<bool, source_visitor_params, jau::fs::traverse_event, const jau::fs::file_stats&, size_t>(&svp,
170 ( bool(*)(source_visitor_params*, jau::fs::traverse_event, const jau::fs::file_stats&, size_t) ) /* help template type deduction of function-ptr */
171 ( [](source_visitor_params* _svp, jau::fs::traverse_event tevt1, const jau::fs::file_stats& element_stats1, size_t depth) -> bool {
172 (void)tevt1;
173 (void)depth;
174 dest_visitor_params dvp { _svp->title, _svp->source_folder_path, _svp->dest.path(), jau::fs::basename( element_stats1.path() ), element_stats1, _svp->dest_is_vfat, false };
175 const jau::fs::path_visitor pv2 = jau::bind_capref<bool, dest_visitor_params, jau::fs::traverse_event, const jau::fs::file_stats&, size_t>(&dvp,
176 ( bool(*)(dest_visitor_params*, jau::fs::traverse_event, const jau::fs::file_stats&, size_t depth) ) /* help template type deduction of function-ptr */
177 ( [](dest_visitor_params* _dvp, jau::fs::traverse_event tevt2, const jau::fs::file_stats& element_stats2, size_t depth2) -> bool {
178 (void)tevt2;
179 (void)depth2;
180 const std::string path2 = element_stats2.path();
181 const std::string basename2 = jau::fs::basename( path2 );
182 const std::string source_folder_basename = jau::fs::basename( _dvp->source_folder_path );
183 if( basename2 == _dvp->source_basename ||
184 ( source_folder_basename == _dvp->source_basename && _dvp->dest_folder_path == path2 )
185 )
186 {
187 bool attr_equal, bit_equal;
188 if( "README_slink08_relext.txt" == basename2 || 0 == basename2.find("dead_link") ) {
189 // symlink to ../README.txt not existent on target
190 // dead_link* files intentionally not existant
191 attr_equal = element_stats2.is_link() &&
192 !element_stats2.exists();
193
194 bit_equal = true; // pretend
195 } else {
196 if( !_dvp->dest_is_vfat ) {
197 // full attribute check
198 attr_equal =
199 element_stats2.mode() == _dvp->stats.mode() &&
200 // element_stats2.atime() == _dvp->stats.atime() && // destination access-time may differ due to processing post copy
201 element_stats2.mtime() == _dvp->stats.mtime() &&
202 element_stats2.uid() == _dvp->stats.uid() &&
203 element_stats2.gid() == _dvp->stats.gid() &&
204 element_stats2.size() == _dvp->stats.size();
205 } else {
206 // minimal vfat attribute check
207 const jau::fraction_timespec td(5_s);
208
209 attr_equal =
210 // ( element_stats2.mode() & jau::fs::fmode_t::rwx_usr ) == ( _dvp->stats.mode() & jau::fs::fmode_t::rwx_usr ) &&
211 // element_stats2.atime().tv_sec == _dvp->stats.atime().tv_sec && // destination access-time may differ due to processing post copy
212 abs( element_stats2.mtime() - _dvp->stats.mtime() ) <= td &&
213 element_stats2.uid() == _dvp->stats.uid() &&
214 // element_stats2.gid() == _dvp->stats.gid() &&
215 element_stats2.size() == _dvp->stats.size();
216 }
217 if( !attr_equal ) {
218 jau::fprintf_td(stdout, "%s.check: '%s'\n mode %s == %s\n mtime %s == %s, d %s\n uid %s == %s\n gid %s == %s\n size %s == %s\n",
219 _dvp->title.c_str(), basename2.c_str(),
220 jau::fs::to_string(element_stats2.mode()).c_str(), jau::fs::to_string(_dvp->stats.mode()).c_str(),
221 // element_stats2.atime().to_string().c_str(), _dvp->stats.atime().to_string().c_str(),
222 element_stats2.mtime().to_string().c_str(), _dvp->stats.mtime().to_string().c_str(),
223 abs( element_stats2.mtime() - _dvp->stats.mtime() ).to_string().c_str(),
224 std::to_string(element_stats2.uid()).c_str(), std::to_string(_dvp->stats.uid()).c_str(),
225 std::to_string(element_stats2.gid()).c_str(), std::to_string(_dvp->stats.gid()).c_str(),
226 jau::to_decstring(element_stats2.size()).c_str(), jau::to_decstring(_dvp->stats.size()).c_str() );
227 }
228
229 if( _dvp->stats.is_file() ) {
230 bit_equal = jau::fs::compare(_dvp->stats, element_stats2, true);
231 } else {
232 bit_equal = true; // pretend
233 }
234 }
235 _dvp->match = attr_equal && bit_equal;
236 jau::fprintf_td(stdout, "%s.check: '%s', match [attr %d, bit %d -> %d]\n\t source %s\n\t dest__ %s\n\n",
237 _dvp->title.c_str(), basename2.c_str(), attr_equal, bit_equal, _dvp->match,
238 _dvp->stats.to_string().c_str(),
239 element_stats2.to_string().c_str());
240 return false; // done
241 } else {
242 return true; // continue search
243 }
244 } ) );
245 if( jau::fs::visit(_svp->dest, topts, pv2) ) {
246 // not found
247 const bool ignore = element_stats1.is_link() && _svp->opt_drop_dest_links;
248 jau::fprintf_td(stdout, "%s.check: %s: '%s', not found!\n\t source %s\n\n",
249 _svp->title.c_str(), ignore ? "Ignored" : "Error", dvp.source_basename.c_str(),
250 element_stats1.to_string().c_str());
251 return ignore;
252 } else {
253 // found
254 if( dvp.match ) {
255 return true; // found and matching, continue
256 } else {
257 return false; // found not matching, abort
258 }
259 }
260 } ) );
261 REQUIRE( true == jau::fs::visit(source, topts, pv1) );
262 }
263}
264
const std::string & basename() const noexcept
Return the basename, shall not be empty nor contain a dirname.
Definition: file_util.hpp:199
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
Definition: file_util.hpp:406
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
Definition: file_util.hpp:648
const fraction_timespec & mtime() const noexcept
Returns the last modification time of this element since Unix Epoch.
Definition: file_util.hpp:606
constexpr bool is_link() const noexcept
Returns true if entity is a symbolic link, might be in combination with is_file(),...
Definition: file_util.hpp:642
gid_t gid() const noexcept
Returns the group id, owning the element.
Definition: file_util.hpp:590
constexpr bool is_dir() const noexcept
Returns true if entity is a directory, might be in combination with is_link().
Definition: file_util.hpp:636
const dir_item & item() const noexcept
Returns the dir_item.
Definition: file_util.hpp:519
uint64_t size() const noexcept
Returns the size in bytes of this element if is_file(), otherwise zero.
Definition: file_util.hpp:597
constexpr bool ok() const noexcept
Returns true if no error occurred.
Definition: file_util.hpp:612
fmode_t mode() const noexcept
Returns the fmode_t, file type and mode.
Definition: file_util.hpp:571
std::string to_string() const noexcept
Returns a comprehensive string representation of this element.
Definition: file_util.cpp:859
uid_t uid() const noexcept
Returns the user id, owning the element.
Definition: file_util.hpp:587
std::string path() const noexcept
Returns the unix path representation.
Definition: file_util.hpp:530
Class template jau::function is a general-purpose static-polymorphic function wrapper.
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
bool copy(const std::string &source_path, const std::string &dest_path, const copy_options copts=copy_options::none) noexcept
Copy the given source_path to dest_path using copy_options.
Definition: file_util.cpp:1720
std::string basename(const std::string_view &path) noexcept
Return stripped leading directory components from given path separated by /.
Definition: file_util.cpp:151
bool compare(const file_stats &source1, const file_stats &source2, const bool verbose=false) noexcept
Compare the bytes of both files, denoted by source1 and source2.
Definition: file_util.cpp:1268
bool visit(const std::string &path, const traverse_options topts, const path_visitor &visitor, std::vector< int > *dirfds=nullptr) noexcept
Visit element(s) of a given path, see traverse_options for detailed settings.
Definition: file_util.cpp:1169
copy_options
Filesystem copy options used to copy() path elements.
Definition: file_util.hpp:1035
traverse_options
Filesystem traverse options used to visit() path elements.
Definition: file_util.hpp:879
traverse_event
Filesystem traverse event used to call path_visitor for path elements from visit().
Definition: file_util.hpp:763
std::string to_string(const fmode_t mask, const bool show_rwx=false) noexcept
Return the string representation of fmode_t.
Definition: file_util.cpp:368
@ ignore_symlink_errors
Ignore errors from erroneous symlinks, e.g.
@ follow_symlinks
Copy referenced symbolic linked files or directories instead of just the symbolic link with property ...
@ into_existing_dir
Copy source dir content into an already existing destination directory as if destination directory di...
@ dir_entry
Call path_visitor at directory entry.
@ recursive
Traverse through directories, i.e.
jau::function< R(A...)> bind_capref(I *data_ptr, R(*func)(I *, A...)) noexcept
Bind given data by passing the captured reference (pointer) to the value and non-void function to an ...
constexpr T abs(const T x) noexcept
Returns the absolute value of an arithmetic number (w/ branching) in O(1)
Definition: base_math.hpp:155
std::string to_decstring(const value_type &v, const char separator=',', const nsize_t width=0) noexcept
Produce a decimal string representation of an integral integer value.
constexpr bool is_set(const cpu_family_t mask, const cpu_family_t bit) noexcept
Definition: cpuid.hpp:122
int fprintf_td(const uint64_t elapsed_ms, FILE *stream, const char *format,...) noexcept
Convenient fprintf() invocation, prepending the given elapsed_ms timestamp.
Definition: debug.cpp:270
Timespec structure using int64_t for its components in analogy to struct timespec_t on 64-bit platfor...
std::string to_string() const noexcept
Return simple string representation in seconds and nanoseconds.
Definition: basic_types.cpp:77
size_t total_file_bytes
std::string to_string() const noexcept
int total_sym_links_not_existing
int total_sym_links_existing
void add(const jau::fs::file_stats &element_stats)
void testxx_copy_r_p(const std::string &title, const jau::fs::file_stats &source, const int source_added_dead_links, const std::string &dest, const jau::fs::copy_options copts, const bool dest_is_vfat)