jaulib v1.3.6
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
file_util.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 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 <jau/debug.hpp>
26#include <jau/enum_util.hpp>
27#include <jau/file_util.hpp>
28#include <jau/base_codec.hpp>
29#include <jau/os/os_support.hpp>
30#include <jau/secmem.hpp>
31
32#include <cstdint>
33#include <cstdlib>
34#include <cinttypes>
35#include <limits>
36#include <cstring>
37#include <cstdio>
38#include <random>
39
40extern "C" {
41 #include <unistd.h>
42 #include <dirent.h>
43 #include <fcntl.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #include <sys/mount.h>
48 #if defined(__linux__)
49 #include <sys/sendfile.h>
50 #include <linux/loop.h>
51 #endif
52}
53
54#ifndef O_BINARY
55#define O_BINARY 0
56#endif
57#ifndef O_NONBLOCK
58#define O_NONBLOCK 0
59#endif
60
61inline constexpr const int _open_dir_flags = O_RDONLY|O_BINARY|O_NOCTTY|O_DIRECTORY;
62
63using namespace jau;
64using namespace jau::fs;
65
66#if defined(__linux__) && defined(__GLIBC__)
67 #define _USE_STATX_ 1
68 #define _USE_SENDFILE_ 1
69#elif defined(__linux__)
70 #define _USE_SENDFILE_ 1
71#endif
72
73#if defined(__FreeBSD__)
74 typedef struct ::stat struct_stat64;
75 typedef off_t off64_t;
76 #define __posix_fstatat64 ::fstatat
77 #define __posix_openat64 ::openat
78#else
79 typedef struct ::stat64 struct_stat64;
80 #define __posix_fstatat64 ::fstatat64
81 #define __posix_openat64 ::openat64
82#endif
83
84std::string jau::fs::get_cwd() noexcept {
85 const size_t bsz = PATH_MAX; // including EOS
86 std::string str;
87 str.reserve(bsz); // incl. EOS
88 str.resize(bsz-1); // excl. EOS
89
90 char* res = ::getcwd(&str[0], bsz);
91 if( res == &str[0] ) {
92 str.resize(::strnlen(res, bsz));
93 str.shrink_to_fit();
94 return str;
95 } else {
96 return std::string();
97 }
98}
99
100bool jau::fs::chdir(const std::string& path) noexcept {
101 return 0 == ::chdir(path.c_str());
102}
103
104std::string jau::fs::absolute(const std::string_view& relpath) noexcept {
105 const size_t bsz = PATH_MAX; // including EOS
106 std::string str;
107 str.reserve(bsz); // incl. EOS
108 str.resize(bsz-1); // excl. EOS
109
110 char *res = ::realpath(&relpath[0], &str[0]);
111 if( res == &str[0] ) {
112 str.resize(::strnlen(res, bsz));
113 str.shrink_to_fit();
114 return str;
115 } else {
116 return std::string();
117 }
118}
119
120static const char c_slash('/');
121static const char c_backslash('\\');
122static const std::string s_slash("/");
123static const std::string s_slash_dot_slash("/./");
124static const std::string s_slash_dot("/.");
125static const std::string s_dot_slash("./");
126static const std::string s_dot(".");
127static const std::string s_slash_dotdot_slash("/../");
128static const std::string s_slash_dotdot("/..");
129static const std::string s_dotdot("..");
130
131std::string jau::fs::dirname(const std::string_view& path) noexcept {
132 if( 0 == path.size() ) {
133 return s_dot;
134 }
135 size_t end_pos;
136 if( c_slash == path[path.size()-1] ) {
137 if( 1 == path.size() ) { // maintain a single '/'
138 return std::string(path);
139 }
140 end_pos = path.size()-2;
141 } else {
142 end_pos = path.size()-1;
143 }
144 size_t idx = path.find_last_of(c_slash, end_pos);
145 if( idx == std::string_view::npos ) {
146 return s_dot;
147 } else {
148 // ensure `/lala` -> '/', i.e. don't cut off single '/'
149 return std::string( path.substr(0, std::max<size_t>(1, idx)) );
150 }
151}
152
153std::string jau::fs::basename(const std::string_view& path) noexcept {
154 if( 0 == path.size() ) {
155 return s_dot;
156 }
157 size_t end_pos;
158 if( c_slash == path[path.size()-1] ) {
159 if( 1 == path.size() ) { // maintain a single '/'
160 return std::string(path);
161 }
162 end_pos = path.size()-2;
163 } else {
164 end_pos = path.size()-1;
165 }
166 size_t idx = path.find_last_of(c_slash, end_pos);
167 if( idx == std::string_view::npos ) {
168 return std::string( path.substr(0, end_pos+1));
169 } else {
170 return std::string( path.substr(idx+1, end_pos-idx) );
171 }
172}
173
174bool jau::fs::isAbsolute(const std::string_view& path) noexcept {
175 return path.size() > 0 &&
176 ( c_slash == path[0] || ( jau::os::is_windows() && c_backslash == path[0] ) );
177}
178
179bool jau::fs::exists(const std::string& path, bool verbose_on_error) noexcept {
181 ::memset(&s, 0, sizeof(s));
182 errno = 0;
183 int stat_res = __posix_fstatat64(AT_FDCWD, path.c_str(), &s, 0); // lstat64 compatible
184 if( 0 != stat_res && verbose_on_error ) {
185 fprintf(stderr, "exists '%s': %d: %d %s\n", path.c_str(), stat_res, errno, strerror(errno));
186 }
187 return 0 == stat_res; // errno EACCES=no access, ENOENT=not existing
188}
189
190std::string jau::fs::lookup_asset_dir(const char* exe_path, const char* asset_file, const char* asset_install_subdir) noexcept {
191 if( !asset_file ) {
192 return "";
193 }
194 // cwd in source root of developer directory - or - WASM (emscripten)
195 std::string assetdir0 = "resources";
196 if( exists( assetdir0+"/"+asset_file ) ) {
197 return assetdir0;
198 }
199 if( !exe_path || !asset_install_subdir ) {
200 return "";
201 }
202 // launched from installed package
203 std::string exedir = jau::fs::dirname(exe_path);
204 std::string cwd = jau::fs::get_cwd();
205 std::string adir = jau::fs::isAbsolute(exedir) ? exedir : cwd+"/"+exedir;
206 std::string assetdir1 = jau::fs::absolute( adir+"/../share/"+asset_install_subdir );
207 if( exists( assetdir1+"/"+asset_file ) ) {
208 return assetdir1;
209 }
210 fprintf(stderr, "asset_dir: Not found: dir '%s', file '%s', exe[path '%s', dir '%s'], cwd '%s', adir '%s'\n",
211 assetdir1.c_str(), asset_file, exe_path, exedir.c_str(), cwd.c_str(), adir.c_str());
212 return "";
213}
214
215std::unique_ptr<dir_item::backed_string_view> dir_item::reduce(const std::string_view& path_) noexcept {
216 constexpr const bool _debug = false;
217
218 if constexpr ( _debug ) {
219 jau::fprintf_td(stderr, "X.0: path '%s'\n", std::string(path_).c_str());
220 }
221
222 std::unique_ptr<dir_item::backed_string_view> path2 = std::make_unique<dir_item::backed_string_view>(path_);
223
224 if( s_dot == path_ || s_slash == path_ ) {
225 return path2;
226 }
227
228 // remove initial './'
229 while( path2->view.starts_with(s_dot_slash) ) {
230 path2->view = path2->view.substr(2, path2->view.size()-2);
231 }
232
233 // remove trailing slash if not ending with '/./' or '/../'
234 if( c_slash == path2->view[path2->view.size()-1] &&
235 ( path2->view.size() < 3 || std::string_view::npos == path2->view.find(s_slash_dot_slash, path2->view.size()-3) ) &&
236 ( path2->view.size() < 4 || std::string_view::npos == path2->view.find(s_slash_dotdot_slash, path2->view.size()-4) )
237 )
238 {
239 path2->view = path2->view.substr(0, path2->view.size()-1);
240 }
241
242 // append final '/' to complete '/../' or '/./' sequence
243 if( ( path2->view.size() >= 3 && std::string_view::npos != path2->view.find(s_slash_dotdot, path2->view.size()-3) ) ||
244 ( path2->view.size() >= 2 && std::string_view::npos != path2->view.find(s_slash_dot, path2->view.size()-2) ) ) {
245 path2->backup();
246 path2->view = path2->backing.append(s_slash);
247 }
248
249 if constexpr ( _debug ) {
250 jau::fprintf_td(stderr, "X.1: path2 '%s'\n", path2->to_string(true).c_str());
251 }
252
253 // resolve '/./'
254 size_t spos=0;
255 size_t idx;
256 do {
257 idx = path2->view.find(s_slash_dot_slash, spos);
258 if constexpr ( _debug ) {
259 jau::fprintf_td(stderr, "X.2.1: path2: spos %zu, idx %zu, '%s'\n", spos, idx, path2->to_string(true).c_str());
260 }
261 if( std::string_view::npos == idx ) {
262 break;
263 }
264 std::string_view pre = path2->view.substr(0, idx);
265 if( 0 == pre.size() ) {
266 // case '/./bbb' -> '/bbb'
267 path2->view = path2->view.substr(idx+2);
268 spos = 0;
269 } else {
270 // case '/zzz/aaa/./bbb' -> '/zzz/aaa/bbb'
271 const std::string post( path2->view.substr(idx+2) );
272 path2->backup_and_append( pre, post );
273 spos = pre.size();
274 }
275 if constexpr ( _debug ) {
276 jau::fprintf_td(stderr, "X.2.2: path2: spos %zu, '%s'\n", spos, path2->to_string(true).c_str());
277 }
278 } while( spos <= path2->view.size()-3 );
279 if constexpr ( _debug ) {
280 jau::fprintf_td(stderr, "X.2.X: path2: '%s'\n", path2->to_string(true).c_str());
281 }
282
283 // resolve '/../'
284 spos=0;
285 do {
286 idx = path2->view.find(s_slash_dotdot_slash, spos);
287 if constexpr ( _debug ) {
288 jau::fprintf_td(stderr, "X.3.1: path2: spos %zu, idx %zu, '%s'\n", spos, idx, path2->to_string(true).c_str());
289 }
290 if( std::string_view::npos == idx ) {
291 break;
292 }
293 if( 0 == idx ) {
294 // case '/../bbb' -> Error, End
295 WARN_PRINT("dir_item::resolve: '..' resolution error: '%s' -> '%s'", std::string(path_).c_str(), path2->to_string().c_str());
296 return path2;
297 }
298 std::string_view pre = path2->view.substr(0, idx);
299 if( 2 == idx && s_dotdot == pre ) {
300 // case '../../bbb' -> '../../bbb' unchanged
301 spos = idx+4;
302 } else if( 3 <= idx && s_slash_dotdot == path2->view.substr(idx-3, 3) ) {
303 // case '../../../bbb' -> '../../../bbb' unchanged
304 spos = idx+4;
305 } else {
306 std::string pre_str = jau::fs::dirname( pre );
307 if( s_slash == pre_str ) {
308 // case '/aaa/../bbb' -> '/bbb'
309 path2->view = path2->view.substr(idx+3);
310 spos = 0;
311 } else if( s_dot == pre_str ) {
312 // case 'aaa/../bbb' -> 'bbb'
313 path2->view = path2->view.substr(idx+4);
314 spos = 0;
315 } else {
316 // case '/zzz/aaa/../bbb' -> '/zzz/bbb'
317 const std::string post( path2->view.substr(idx+3) );
318 path2->backup_and_append( pre_str, post );
319 spos = pre_str.size();
320 }
321 }
322 if constexpr ( _debug ) {
323 jau::fprintf_td(stderr, "X.3.2: path2: spos %zu, '%s'\n", spos, path2->to_string(true).c_str());
324 }
325 } while( spos <= path2->view.size()-4 );
326 if constexpr ( _debug ) {
327 jau::fprintf_td(stderr, "X.3.X: path2: '%s'\n", path2->to_string(true).c_str());
328 }
329
330 // remove trailing slash in path2
331 if( c_slash == path2->view[path2->view.size()-1] ) {
332 path2->view = path2->view.substr(0, path2->view.size()-1);
333 }
334 if constexpr ( _debug ) {
335 jau::fprintf_td(stderr, "X.X: path2: '%s'\n", path2->to_string(true).c_str());
336 }
337 return path2;
338}
339
340dir_item::dir_item(std::unique_ptr<backed_string_view> cleanpath) noexcept
341: dirname_(jau::fs::dirname(cleanpath->view)), basename_(jau::fs::basename(cleanpath->view)), empty_( cleanpath->view.empty() ) {
342 if( s_slash == dirname_ && s_slash == basename_ ) { // remove duplicate '/' in basename
343 basename_ = s_dot;
344 }
345}
346
347dir_item::dir_item(std::string dirname__, std::string basename__) noexcept
348: dirname_(std::move(dirname__)), basename_(std::move(basename__)), empty_(dirname_.empty() && basename_.empty()) {
349}
350
352: dirname_(s_dot), basename_(s_dot), empty_(true) {}
353
354dir_item::dir_item(const std::string_view& path_) noexcept
355: dir_item( reduce(path_) )
356{ }
357
358
359std::string dir_item::path() const noexcept {
360 if( s_dot == dirname_ ) {
361 return basename_;
362 }
363 if( s_dot == basename_ ) {
364 return dirname_;
365 }
366 if( s_slash == dirname_ ) {
367 return dirname_ + basename_;
368 }
369 return dirname_ + s_slash + basename_;
370}
371
372std::string dir_item::to_string() const noexcept {
373 return "['"+dirname()+"', '"+basename()+"']";
374}
375
376
377static void _append_bitstr(std::string& out, fmode_t mask, fmode_t bit, const std::string& bitstr) {
378 if( is_set( mask, bit )) {
379 out.append(bitstr);
380 } else {
381 out.append("-");
382 }
383}
384
385std::string jau::fs::to_string(const fmode_t mask, const bool show_rwx) noexcept {
386 std::string out = jau::fs::to_string(mask);
388 out.append(", ");
389 if( show_rwx ) {
390 if( has_any( mask, fmode_t::ugs_set ) ) {
391 _append_bitstr(out, mask, fmode_t::set_uid, "u");
392 _append_bitstr(out, mask, fmode_t::set_gid, "g");
393 _append_bitstr(out, mask, fmode_t::sticky, "s");
394 }
395 const std::string r("r");
396 const std::string w("w");
397 const std::string x("x");
398 _append_bitstr(out, mask, fmode_t::read_usr, r);
399 _append_bitstr(out, mask, fmode_t::write_usr, w);
400 _append_bitstr(out, mask, fmode_t::exec_usr, x);
401 _append_bitstr(out, mask, fmode_t::read_grp, r);
402 _append_bitstr(out, mask, fmode_t::write_grp, w);
403 _append_bitstr(out, mask, fmode_t::exec_grp, x);
404 _append_bitstr(out, mask, fmode_t::read_oth, r);
405 _append_bitstr(out, mask, fmode_t::write_oth, w);
406 _append_bitstr(out, mask, fmode_t::exec_oth, x);
407 } else {
408 char buf[8];
409 int len = snprintf(buf, sizeof(buf), "0%o", (unsigned int)(mask & fmode_t::protection_mask));
410 out.append(std::string(buf, len));
411 }
412 }
413 return out;
414}
415
416std::string jau::fs::to_named_fd(const int fd) noexcept {
417 if( 0 > fd ) {
418 return "";
419 }
420 std::string res("/dev/fd/");
421 res.append(std::to_string(fd));
422 return res;
423}
424
425int jau::fs::from_named_fd(const std::string& named_fd) noexcept {
426 int scan_value = -1;
427 if( 1 == sscanf(named_fd.c_str(), "/dev/fd/%d", &scan_value) ) {
428 // GNU/Linux, FreeBSD, ... ?
429 return scan_value;
430 } else if( 1 == sscanf(named_fd.c_str(), "/proc/self/fd/%d", &scan_value) ) {
431 // GNU/Linux only?
432 return scan_value;
433 }
434 return -1;
435}
436
438: has_fields_(field_t::none), item_(), link_target_path_(), link_target_(), mode_(fmode_t::not_existing), fd_(-1),
439 uid_(0), gid_(0), size_(0), btime_(), atime_(), ctime_(), mtime_(),
440 errno_res_(0)
441{}
442
443#if _USE_STATX_
444 static constexpr bool jau_has_stat(const uint32_t mask, const uint32_t bit) { return bit == ( mask & bit ); }
445#endif
446
447file_stats::file_stats(const ctor_cookie& cc, int dirfd, const dir_item& item, const bool dirfd_is_item_dirname) noexcept
448: has_fields_(field_t::none), item_(), link_target_path_(), link_target_(), mode_(fmode_t::none), fd_(-1),
449 uid_(0), gid_(0), size_(0), btime_(), atime_(), ctime_(), mtime_(), errno_res_(0)
450{
451 constexpr const bool _debug = false;
452 (void)cc;
453 const std::string full_path( item.empty() ? "" : item.path() );
454 if( item.empty() && AT_FDCWD != dirfd ) {
455 if( 0 <= dirfd ) {
456 has_fields_ |= field_t::fd;
457 fd_ = dirfd;
458 item_ = dir_item(jau::fs::to_named_fd(fd_));
459 } else {
460 ERR_PRINT("rec_level %d, dirfd %d < 0, %s, dirfd_is_item_dirname %d, AT_EMPTY_PATH",
461 (int)cc.rec_level, dirfd, item.to_string().c_str(), dirfd_is_item_dirname);
462 return;
463 }
464 } else {
465 item_ = item;
466 int scan_value = jau::fs::from_named_fd(full_path);
467 if( 0 <= scan_value ) {
468 has_fields_ |= field_t::fd;
469 dirfd = scan_value; // intentional overwrite
470 fd_ = dirfd;
471 } else if( full_path.starts_with("/dev/fd/pipe:") ) {
472 // Last resort and should-be unreachable,
473 // since above `jau::fs::from_named_fd()` shall hit!
474 //
475 // fifo/pipe object used in GNU/Linux (at least),
476 // which can't be stat'ed / accessed
477 has_fields_ |= field_t::type;
478 mode_ |= fmode_t::fifo;
479 if constexpr ( _debug ) {
480 jau::fprintf_td(stderr, "file_stats(%d): FIFO: '%s', errno %d (%s)\n", (int)cc.rec_level, to_string().c_str(), errno, ::strerror(errno));
481 }
482 return;
483 }
484 }
485 const std::string dirfd_path = has( field_t::fd ) ? "" : ( dirfd_is_item_dirname ? item_.basename() : full_path );
486
487#if _USE_STATX_
488 struct ::statx s;
489 jau::zero_bytes_sec(&s, sizeof(s));
490 int stat_res = ::statx(dirfd, dirfd_path.c_str(),
491 ( AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | ( has( field_t::fd ) ? AT_EMPTY_PATH : 0 ) ),
492 ( STATX_BASIC_STATS | STATX_BTIME ), &s);
493 if( 0 != stat_res ) {
494 if constexpr ( _debug ) {
495 jau::fprintf_td(stderr, "file_stats(%d): Test ERROR: '%s', %d, errno %d (%s)\n", (int)cc.rec_level, full_path.c_str(), stat_res, errno, ::strerror(errno));
496 }
497 switch( errno ) {
498 case EACCES:
499 mode_ |= fmode_t::no_access;
500 break;
501 case ENOENT:
502 mode_ |= fmode_t::not_existing;
503 break;
504 default:
505 break;
506 }
507 if( has_access() && exists() ) {
508 errno_res_ = errno;
509 }
510 } else {
511 if( jau_has_stat( s.stx_mask, STATX_TYPE ) ) {
512 has_fields_ |= field_t::type;
513 }
514 if( has( field_t::type ) ) {
515 if( S_ISLNK( s.stx_mode ) ) {
516 mode_ |= fmode_t::link;
517 }
518 if( S_ISREG( s.stx_mode ) ) {
519 mode_ |= fmode_t::file;
520 } else if( S_ISDIR( s.stx_mode ) ) {
521 mode_ |= fmode_t::dir;
522 } else if( S_ISFIFO( s.stx_mode ) ) {
523 mode_ |= fmode_t::fifo;
524 } else if( S_ISCHR( s.stx_mode ) ) {
525 mode_ |= fmode_t::chr;
526 } else if( S_ISSOCK( s.stx_mode ) ) {
527 mode_ |= fmode_t::sock;
528 } else if( S_ISBLK( s.stx_mode ) ) {
529 mode_ |= fmode_t::blk;
530 }
531 }
532 if( jau_has_stat( s.stx_mask, STATX_MODE ) ) {
533 has_fields_ |= field_t::mode;
534 // Append POSIX protection bits
535 mode_ |= static_cast<fmode_t>( s.stx_mode & ( S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX ) );
536 }
537 if( jau_has_stat( s.stx_mask, STATX_NLINK ) ) {
538 has_fields_ |= field_t::nlink;
539 }
540 if( jau_has_stat( s.stx_mask, STATX_UID ) ) {
541 has_fields_ |= field_t::uid;
542 uid_ = s.stx_uid;
543 }
544 if( jau_has_stat( s.stx_mask, STATX_GID ) ) {
545 has_fields_ |= field_t::gid;
546 gid_ = s.stx_gid;
547 }
548 if( jau_has_stat( s.stx_mask, STATX_ATIME ) || 0 != s.stx_atime.tv_sec || 0 != s.stx_atime.tv_nsec ) { // if STATX_ATIME is not reported but has its value (duh?)
549 has_fields_ |= field_t::atime;
550 atime_ = jau::fraction_timespec( s.stx_atime.tv_sec, s.stx_atime.tv_nsec );
551 }
552 if( jau_has_stat( s.stx_mask, STATX_MTIME ) ) {
553 has_fields_ |= field_t::mtime;
554 mtime_ = jau::fraction_timespec( s.stx_mtime.tv_sec, s.stx_mtime.tv_nsec );
555 }
556 if( jau_has_stat( s.stx_mask, STATX_CTIME ) ) {
557 has_fields_ |= field_t::ctime;
558 ctime_ = jau::fraction_timespec( s.stx_ctime.tv_sec, s.stx_ctime.tv_nsec );
559 }
560 if( jau_has_stat( s.stx_mask, STATX_INO ) ) {
561 has_fields_ |= field_t::ino;
562 }
563 if( jau_has_stat( s.stx_mask, STATX_SIZE ) ) {
564 if( !is_link() && is_file() ) {
565 has_fields_ |= field_t::size;
566 size_ = s.stx_size;
567 }
568 }
569 if( jau_has_stat( s.stx_mask, STATX_BLOCKS ) ) {
570 has_fields_ |= field_t::blocks;
571 }
572 if( jau_has_stat( s.stx_mask, STATX_BTIME ) ) {
573 has_fields_ |= field_t::btime;
574 btime_ = jau::fraction_timespec( s.stx_btime.tv_sec, s.stx_btime.tv_nsec );
575 }
576 if( is_link() ) {
577 // follow symbolic link recursively until !exists(), is_file() or is_dir()
578 std::string link_path;
579 {
580 const size_t path_link_max_len = 0 < s.stx_size ? s.stx_size + 1 : PATH_MAX;
581 std::vector<char> buffer;
582 buffer.reserve(path_link_max_len);
583 buffer.resize(path_link_max_len);
584 const ssize_t path_link_len = ::readlinkat(dirfd, dirfd_path.c_str(), buffer.data(), path_link_max_len);
585 if( 0 > path_link_len ) {
586 errno_res_ = errno;
587 link_target_ = std::make_shared<file_stats>();
588 goto errorout;
589 }
590 // Note: if( path_link_len == path_link_max_len ) then buffer may have been truncated
591 link_path = std::string(buffer.data(), path_link_len);
592 }
593 link_target_path_ = std::make_shared<std::string>(link_path);
594 if( 0 == cc.rec_level ) {
595 // Initial symbolic followed: Test recursive loop-error
596 jau::zero_bytes_sec(&s, sizeof(s));
597 stat_res = ::statx(dirfd, dirfd_path.c_str(), AT_NO_AUTOMOUNT | ( has( field_t::fd ) ? AT_EMPTY_PATH : 0 ), STATX_BASIC_STATS, &s);
598 if( 0 != stat_res ) {
599 if constexpr ( _debug ) {
600 jau::fprintf_td(stderr, "file_stats(%d): Test link ERROR: '%s', %d, errno %d (%s)\n", (int)cc.rec_level, full_path.c_str(), stat_res, errno, ::strerror(errno));
601 }
602 switch( errno ) {
603 case EACCES:
604 mode_ |= fmode_t::no_access;
605 break;
606 case ELOOP:
607 // Too many symbolic links encountered while traversing the pathname
608 [[fallthrough]];
609 case ENOENT:
610 // A component of pathname does not exist
611 [[fallthrough]];
612 default:
613 // Anything else ..
614 mode_ |= fmode_t::not_existing;
615 break;
616 }
617 goto errorout;
618 }
619 }
620 if( 0 < link_path.size() && c_slash == link_path[0] ) {
621 link_target_ = std::make_shared<file_stats>(ctor_cookie(cc.rec_level+1), dirfd, dir_item( link_path ), false /* dirfd_is_item_dirname */); // absolute link_path
622 } else {
623 link_target_ = std::make_shared<file_stats>(ctor_cookie(cc.rec_level+1), dirfd, dir_item( jau::fs::dirname(full_path), link_path ), dirfd_is_item_dirname );
624 }
625 if( link_target_->has_fd() ) {
626 has_fields_ |= field_t::fd;
627 fd_ = link_target_->fd();
628 }
629 if( link_target_->is_socket() ) {
630 mode_ |= fmode_t::sock;
631 } else if( link_target_->is_block() ) {
632 mode_ |= fmode_t::blk;
633 } else if( link_target_->is_char() ) {
634 mode_ |= fmode_t::chr;
635 } else if( link_target_->is_fifo() ) {
636 mode_ |= fmode_t::fifo;
637 } else if( link_target_->is_dir() ) {
638 mode_ |= fmode_t::dir;
639 } else if( link_target_->is_file() ) {
640 mode_ |= fmode_t::file;
641 if( link_target_->has( field_t::size ) ) {
642 has_fields_ |= field_t::size;
643 size_ = link_target_->size();
644 }
645 } else if( !link_target_->exists() ) {
646 mode_ |= fmode_t::not_existing;
647 } else if( !link_target_->has_access() ) {
648 mode_ |= fmode_t::no_access;
649 }
650 }
651 if constexpr ( _debug ) {
652 jau::fprintf_td(stderr, "file_stats(%d): '%s', %d, errno %d (%s)\n", (int)cc.rec_level, to_string().c_str(), stat_res, errno, ::strerror(errno));
653 }
654 }
655#else /* _USE_STATX_ */
657 jau::zero_bytes_sec(&s, sizeof(s));
658 int stat_res = __posix_fstatat64(dirfd, dirfd_path.c_str(), &s, AT_SYMLINK_NOFOLLOW | ( has( field_t::fd ) ? AT_EMPTY_PATH : 0 )); // lstat64 compatible
659 if( 0 != stat_res ) {
660 if constexpr ( _debug ) {
661 jau::fprintf_td(stderr, "file_stats(%d): Test ERROR: '%s', %d, errno %d (%s)\n", (int)cc.rec_level, full_path.c_str(), stat_res, errno, ::strerror(errno));
662 }
663 switch( errno ) {
664 case EACCES:
665 mode_ |= fmode_t::no_access;
666 break;
667 case ENOENT:
668 mode_ |= fmode_t::not_existing;
669 break;
670 default:
671 break;
672 }
673 if( has_access() && exists() ) {
674 errno_res_ = errno;
675 }
676 } else {
679
680 if( S_ISLNK( s.st_mode ) ) {
681 mode_ |= fmode_t::link;
682 }
683 if( S_ISREG( s.st_mode ) ) {
684 mode_ |= fmode_t::file;
685 if( !is_link() ) {
686 has_fields_ |= field_t::size;
687 size_ = s.st_size;
688 }
689 } else if( S_ISDIR( s.st_mode ) ) {
690 mode_ |= fmode_t::dir;
691 } else if( S_ISFIFO( s.st_mode ) ) {
692 mode_ |= fmode_t::fifo;
693 } else if( S_ISCHR( s.st_mode ) ) {
694 mode_ |= fmode_t::chr;
695 } else if( S_ISSOCK( s.st_mode ) ) {
696 mode_ |= fmode_t::sock;
697 } else if( S_ISBLK( s.st_mode ) ) {
698 mode_ |= fmode_t::blk;
699 }
700
701 // Append POSIX protection bits
702 mode_ |= static_cast<fmode_t>( s.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX ) );
703
704 uid_ = s.st_uid;
705 gid_ = s.st_gid;
706 atime_ = jau::fraction_timespec( s.st_atim.tv_sec, s.st_atim.tv_nsec );
707 ctime_ = jau::fraction_timespec( s.st_ctim.tv_sec, s.st_ctim.tv_nsec );
708 mtime_ = jau::fraction_timespec( s.st_mtim.tv_sec, s.st_mtim.tv_nsec );
709
710 if( is_link() ) {
711 // follow symbolic link recursively until !exists(), is_file() or is_dir()
712 std::string link_path;
713 {
714 const size_t path_link_max_len = 0 < s.st_size ? s.st_size + 1 : PATH_MAX;
715 std::vector<char> buffer;
716 buffer.reserve(path_link_max_len);
717 buffer.resize(path_link_max_len);
718 const ssize_t path_link_len = ::readlinkat(dirfd, dirfd_path.c_str(), buffer.data(), path_link_max_len);
719 if( 0 > path_link_len ) {
720 errno_res_ = errno;
721 link_target_ = std::make_shared<file_stats>();
722 goto errorout;
723 }
724 // Note: if( path_link_len == path_link_max_len ) then buffer may have been truncated
725 link_path = std::string(buffer.data(), path_link_len);
726 }
727 link_target_path_ = std::make_shared<std::string>(link_path);
728 if( 0 == cc.rec_level ) {
729 // Initial symbolic followed: Test recursive loop-error
730 jau::zero_bytes_sec(&s, sizeof(s));
731 stat_res = __posix_fstatat64(dirfd, dirfd_path.c_str(), &s, has( field_t::fd ) ? AT_EMPTY_PATH : 0); // stat64 compatible
732 if( 0 != stat_res ) {
733 if constexpr ( _debug ) {
734 jau::fprintf_td(stderr, "file_stats(%d): Test link ERROR: '%s', %d, errno %d (%s)\n", (int)cc.rec_level, full_path.c_str(), stat_res, errno, ::strerror(errno));
735 }
736 switch( errno ) {
737 case EACCES:
738 mode_ |= fmode_t::no_access;
739 break;
740 case ELOOP:
741 // Too many symbolic links encountered while traversing the pathname
742 [[fallthrough]];
743 case ENOENT:
744 // A component of pathname does not exist
745 [[fallthrough]];
746 default:
747 // Anything else ..
748 mode_ |= fmode_t::not_existing;
749 break;
750 }
751 goto errorout;
752 }
753 }
754 if( 0 < link_path.size() && c_slash == link_path[0] ) {
755 link_target_ = std::make_shared<file_stats>(ctor_cookie(cc.rec_level+1), dirfd, dir_item( link_path ), false /* dirfd_is_item_dirname */); // absolute link_path
756 } else {
757 link_target_ = std::make_shared<file_stats>(ctor_cookie(cc.rec_level+1), dirfd, dir_item( jau::fs::dirname(full_path), link_path ), dirfd_is_item_dirname );
758 }
759 if( link_target_->has_fd() ) {
760 has_fields_ |= field_t::fd;
761 fd_ = link_target_->fd();
762 }
763 if( link_target_->is_socket() ) {
764 mode_ |= fmode_t::sock;
765 } else if( link_target_->is_block() ) {
766 mode_ |= fmode_t::blk;
767 } else if( link_target_->is_char() ) {
768 mode_ |= fmode_t::chr;
769 } else if( link_target_->is_fifo() ) {
770 mode_ |= fmode_t::fifo;
771 } else if( link_target_->is_dir() ) {
772 mode_ |= fmode_t::dir;
773 } else if( link_target_->is_file() ) {
774 mode_ |= fmode_t::file;
775 has_fields_ |= field_t::size;
776 size_ = link_target_->size();
777 } else if( !link_target_->exists() ) {
778 mode_ |= fmode_t::not_existing;
779 } else if( !link_target_->has_access() ) {
780 mode_ |= fmode_t::no_access;
781 }
782 }
783 if constexpr ( _debug ) {
784 jau::fprintf_td(stderr, "file_stats(%d): '%s', %d, errno %d (%s)\n", (int)cc.rec_level, to_string().c_str(), stat_res, errno, ::strerror(errno));
785 }
786 }
787#endif /* _USE_STATX_ */
788
789 errorout: ;
790}
791
793: file_stats(ctor_cookie(0), AT_FDCWD, item, false /* dirfd_is_item_dirname */)
794{}
795
796file_stats::file_stats(const int dirfd, const dir_item& item, const bool dirfd_is_item_dirname) noexcept
797: file_stats(ctor_cookie(0), dirfd, item, dirfd_is_item_dirname)
798{}
799
800file_stats::file_stats(const std::string& _path) noexcept
801: file_stats(ctor_cookie(0), AT_FDCWD, dir_item(_path), false /* dirfd_is_item_dirname */)
802{}
803
804file_stats::file_stats(const int dirfd, const std::string& _path) noexcept
805: file_stats(ctor_cookie(0), dirfd, dir_item(_path), false /* dirfd_is_item_dirname */)
806{}
807
808file_stats::file_stats(const int fd) noexcept
809: file_stats(ctor_cookie(0), fd, dir_item(), false /* dirfd_is_item_dirname */)
810{}
811
812const file_stats* file_stats::final_target(size_t* link_count) const noexcept {
813 size_t count = 0;
814 const file_stats* fs0 = this;
815 const file_stats* fs1 = fs0->link_target().get();
816 while( nullptr != fs1 ) {
817 ++count;
818 fs0 = fs1;
819 fs1 = fs0->link_target().get();
820 }
821 if( nullptr != link_count ) {
822 *link_count = count;
823 }
824 return fs0;
825}
826
827bool file_stats::has(const field_t fields) const noexcept {
828 return fields == ( has_fields_ & fields );
829}
830
831bool file_stats::operator ==(const file_stats& rhs) const noexcept {
832 if( this == &rhs ) {
833 return true;
834 }
835 return item_ == rhs.item_ &&
836 has_fields_ == rhs.has_fields_ &&
837 mode_ == rhs.mode_ &&
838 uid_ == rhs.uid_ && gid_ == rhs.gid_ &&
839 errno_res_ == rhs.errno_res_ &&
840 size_ == rhs.size_ &&
841 btime_ == rhs.btime_ &&
842 atime_ == rhs.atime_ &&
843 ctime_ == rhs.ctime_ &&
844 mtime_ == rhs.mtime_ &&
845 ( !is_link() ||
846 ( link_target_path_ == rhs.link_target_path_&&
847 link_target_ == rhs.link_target_
848 )
849 );
850}
851
852std::string file_stats::to_string() const noexcept {
853 std::string stored_path, link_detail;
854 {
855 if( nullptr != link_target_path_ ) {
856 stored_path = " [-> "+*link_target_path_+"]";
857 }
858 size_t link_count;
859 const file_stats* final_target_ = final_target(&link_count);
860 if( 0 < link_count ) {
861 link_detail = " -(" + std::to_string(link_count) + ")-> '" + final_target_->path() + "'";
862 }
863 }
864 std::string res( "file_stats[");
865 res.append(jau::fs::to_string(mode_))
866 .append(", '"+item_.path()+"'"+stored_path+link_detail );
867 if( 0 == errno_res_ ) {
868 if( has( field_t::fd ) ) {
869 res.append( ", fd " ).append( std::to_string(fd_) );
870 }
871 if( has( field_t::uid ) ) {
872 res.append( ", uid " ).append( std::to_string(uid_) );
873 }
874 if( has( field_t::gid ) ) {
875 res.append( ", gid " ).append( std::to_string(gid_) );
876 }
877 if( has( field_t::size ) ) {
878 res.append( ", size " ).append( jau::to_decstring( size_ ) );
879 } else {
880 res.append( ", size n/a" );
881 }
882 if( has( field_t::btime ) ) {
883 res.append( ", btime " ).append( btime_.to_iso8601_string() );
884 }
885 if( has( field_t::atime ) ) {
886 res.append( ", atime " ).append( atime_.to_iso8601_string() );
887 }
888 if( has( field_t::ctime ) ) {
889 res.append( ", ctime " ).append( ctime_.to_iso8601_string() );
890 }
891 if( has( field_t::mtime ) ) {
892 res.append( ", mtime " ).append( mtime_.to_iso8601_string() );
893 }
894 // res.append( ", fields ").append( jau::fs::to_string( has_fields_ ) );
895 } else {
896 res.append( ", errno " ).append( std::to_string(errno_res_) ).append( ", " ).append( std::string(::strerror(errno_res_)) );
897 }
898 res.append("]");
899 return res;
900}
901
902bool jau::fs::mkdir(const std::string& path, const fmode_t mode, const bool verbose) noexcept {
903 file_stats stats(path);
904
905 if( stats.is_dir() ) {
906 if( verbose ) {
907 jau::fprintf_td(stderr, "mkdir: dir already exists: %s\n", stats.to_string().c_str());
908 }
909 return true;
910 } else if( !stats.exists() ) {
911 const int dir_err = ::mkdir(path.c_str(), posix_protection_bits(mode));
912 if ( 0 != dir_err ) {
913 ERR_PRINT("%s, failure", stats.to_string().c_str());
914 return false;
915 } else {
916 return true;
917 }
918 } else {
919 ERR_PRINT("%s, exists but is no dir", stats.to_string().c_str());
920 return false;
921 }
922}
923
924bool jau::fs::touch(const std::string& path, const jau::fraction_timespec& atime, const jau::fraction_timespec& mtime, const fmode_t mode) noexcept {
925 int fd = ::open(path.c_str(), O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, posix_protection_bits(mode));
926 if( 0 > fd ) {
927 ERR_PRINT("Couldn't open/create file '%s'", path.c_str());
928 return false;
929 }
930 struct timespec ts2[2] = { atime.to_timespec(), mtime.to_timespec() };
931 bool res;
932 if( 0 != ::futimens(fd, ts2) ) {
933 ERR_PRINT("Couldn't update time of file '%s'", path.c_str());
934 res = false;
935 } else {
936 res = true;
937 }
938 ::close(fd);
939 return res;
940}
941
942bool jau::fs::touch(const std::string& path, const fmode_t mode) noexcept {
943 int fd = ::open(path.c_str(), O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, posix_protection_bits(mode));
944 if( 0 > fd ) {
945 ERR_PRINT("Couldn't open/create file '%s'", path.c_str());
946 return false;
947 }
948 bool res;
949 if( 0 != ::futimens(fd, nullptr /* current time */) ) {
950 ERR_PRINT("Couldn't update time of file '%s'", path.c_str());
951 res = false;
952 } else {
953 res = true;
954 }
955 ::close(fd);
956 return res;
957}
958
959bool jau::fs::get_dir_content(const std::string& path, const consume_dir_item& digest) noexcept {
960 DIR *dir;
961 struct dirent *ent;
962
963 if( ( dir = ::opendir( path.c_str() ) ) != nullptr ) {
964 while ( ( ent = ::readdir( dir ) ) != nullptr ) {
965 std::string fname( ent->d_name );
966 if( s_dot != fname && s_dotdot != fname ) { // avoid '.' and '..'
967 digest( dir_item( path, fname ) );
968 }
969 }
970 ::closedir (dir);
971 return true;
972 } else {
973 return false;
974 }
975}
976
977bool jau::fs::get_dir_content(const int dirfd, const std::string& path, const consume_dir_item& digest) noexcept {
978 DIR *dir;
979 struct dirent *ent;
980 int dirfd2 = ::dup(dirfd);
981 if( 0 > dirfd2 ) {
982 ERR_PRINT("Couldn't duplicate given dirfd %d for path '%s'", dirfd, path.c_str());
983 return false;
984 }
985 if( ( dir = ::fdopendir( dirfd2 ) ) != nullptr ) {
986 while ( ( ent = ::readdir( dir ) ) != nullptr ) {
987 std::string fname( ent->d_name );
988 if( s_dot != fname && s_dotdot != fname ) { // avoid '.' and '..'
989 digest( dir_item( path, fname ) );
990 }
991 }
992 ::closedir (dir);
993 return true;
994 } else {
995 return false;
996 }
997}
998
999static bool _dir_item_basename_compare(const dir_item& a, const dir_item& b) {
1000 return a.basename() < b.basename();
1001}
1002
1003static bool _visit(const file_stats& item_stats, const traverse_options topts, const path_visitor& visitor, std::vector<int>& dirfds) noexcept {
1004 const size_t depth = dirfds.size();
1005 if( item_stats.is_dir() ) {
1006 if( item_stats.is_link() && !is_set(topts, traverse_options::follow_symlinks) ) {
1007 return visitor( traverse_event::dir_symlink, item_stats, depth );
1008 }
1009 if( !is_set(topts, traverse_options::recursive) ) {
1010 return visitor( traverse_event::dir_non_recursive, item_stats, depth );
1011 }
1012 if( dirfds.size() < 1 ) {
1013 ERR_PRINT("dirfd stack error: count %zu] @ %s", dirfds.size(), item_stats.to_string().c_str());
1014 return false;
1015 }
1016 const int parent_dirfd = dirfds.back();
1017 const int this_dirfd = __posix_openat64(parent_dirfd, item_stats.item().basename().c_str(), _open_dir_flags);
1018 if ( 0 > this_dirfd ) {
1019 ERR_PRINT("entered path dir couldn't be opened, source %s", item_stats.to_string().c_str());
1020 return false;
1021 }
1022 dirfds.push_back(this_dirfd);
1023
1025 if( !visitor( traverse_event::dir_check_entry, item_stats, depth ) ) {
1026 ::close(this_dirfd);
1027 dirfds.pop_back();
1028 return true; // keep traversing in parent, but skip this directory
1029 }
1030 }
1031 if( is_set(topts, traverse_options::dir_entry) ) {
1032 if( !visitor( traverse_event::dir_entry, item_stats, depth ) ) {
1033 ::close(this_dirfd);
1034 dirfds.pop_back();
1035 return false;
1036 }
1037 }
1038 std::vector<dir_item> content;
1040 ( void(*)(std::vector<dir_item>*, const dir_item&) ) /* help template type deduction of function-ptr */
1041 ( [](std::vector<dir_item>* receiver, const dir_item& item) -> void { receiver->push_back( item ); } )
1042 );
1043 if( get_dir_content(this_dirfd, item_stats.path(), cs) && content.size() > 0 ) {
1045 std::sort(content.begin(), content.end(), _dir_item_basename_compare);
1046 }
1047 for (const dir_item& element : content) {
1048 const file_stats element_stats( this_dirfd, element, true /* dirfd_is_item_dirname */ );
1049 if( element_stats.is_dir() ) { // an OK dir
1050 if( element_stats.is_link() && !is_set(topts, traverse_options::follow_symlinks) ) {
1051 if( !visitor( traverse_event::dir_symlink, element_stats, depth ) ) {
1052 ::close(this_dirfd);
1053 dirfds.pop_back();
1054 return false;
1055 }
1056 } else if( !_visit(element_stats, topts, visitor, dirfds) ) { // recursive
1057 ::close(this_dirfd);
1058 dirfds.pop_back();
1059 return false;
1060 }
1061 } else if( !visitor( ( element_stats.is_file() ? traverse_event::file : traverse_event::none ) |
1063 element_stats, depth ) )
1064 {
1065 ::close(this_dirfd);
1066 dirfds.pop_back();
1067 return false;
1068 }
1069 }
1070 }
1071 if( dirfds.size() < 2 ) {
1072 ERR_PRINT("dirfd stack error: count %zu] @ %s", dirfds.size(), item_stats.to_string().c_str());
1073 return false;
1074 }
1075 bool res = true;
1076 if( is_set(topts, traverse_options::dir_exit) ) {
1077 res = visitor( traverse_event::dir_exit, item_stats, depth ); // keep traversing in parent
1078 }
1079 ::close(this_dirfd);
1080 dirfds.pop_back();
1081 return res;
1082 } // endif item_stats.is_dir()
1083 else if( item_stats.is_file() || !item_stats.ok() ) { // file or error-alike
1084 return visitor( ( item_stats.is_file() ? traverse_event::file : traverse_event::none ) |
1085 ( item_stats.is_link() ? traverse_event::symlink : traverse_event::none),
1086 item_stats, depth);
1087 }
1088 return true;
1089}
1090
1091bool jau::fs::visit(const file_stats& item_stats, const traverse_options topts, const path_visitor& visitor, std::vector<int>* dirfds) noexcept {
1092 const bool user_dirfds = nullptr != dirfds;
1093 if( !user_dirfds ) {
1094 try {
1095 dirfds = new std::vector<int>();
1096 } catch (const std::bad_alloc &e) {
1097 ABORT("Error: bad_alloc: dirfds allocation failed");
1098 return false; // unreachable
1099 }
1100 }
1101 if( 0 != dirfds->size() ) {
1102 ERR_PRINT("dirfd stack error: count %zu @ %s", dirfds->size(), item_stats.to_string().c_str());
1103 return false;
1104 }
1105 // initial parent directory dirfd of initial item_stats (a directory)
1106 const int dirfd = __posix_openat64(AT_FDCWD, item_stats.item().dirname().c_str(), _open_dir_flags);
1107 if ( 0 > dirfd ) {
1108 ERR_PRINT("path dirname couldn't be opened, source %s", item_stats.to_string().c_str());
1109 return false;
1110 }
1111 dirfds->push_back(dirfd);
1112
1113 bool res = _visit(item_stats, topts, visitor, *dirfds);
1114
1115 if( dirfds->size() != 1 && res ) {
1116 ERR_PRINT("dirfd stack error: count %zu", dirfds->size());
1117 res = false;
1118 }
1119 while( !dirfds->empty() ) {
1120 ::close(dirfds->back());
1121 dirfds->pop_back();
1122 }
1123 if( !user_dirfds ) {
1124 delete dirfds;
1125 }
1126 return res;
1127}
1128
1129bool jau::fs::visit(const std::string& path, const traverse_options topts, const path_visitor& visitor, std::vector<int>* dirfds) noexcept {
1130 return jau::fs::visit(file_stats(path), topts, visitor, dirfds);
1131}
1132
1133bool jau::fs::remove(const std::string& path, const traverse_options topts) noexcept {
1134 file_stats path_stats(path);
1135 if( is_set(topts, traverse_options::verbose) ) {
1136 jau::fprintf_td(stderr, "remove: '%s' -> %s\n", path.c_str(), path_stats.to_string().c_str());
1137 }
1138 if( !path_stats.exists() ) {
1139 if( is_set(topts, traverse_options::verbose) ) {
1140 jau::fprintf_td(stderr, "remove: failed: path doesn't exist: %s\n", path_stats.to_string().c_str());
1141 }
1142 return false;
1143 }
1144 if( path_stats.has_fd() ) {
1145 if( is_set(topts, traverse_options::verbose) ) {
1146 jau::fprintf_td(stderr, "remove: failed: path is fd: %s\n", path_stats.to_string().c_str());
1147 }
1148 return false;
1149 }
1150 if( path_stats.is_file() ||
1151 ( path_stats.is_dir() && path_stats.is_link() && !is_set(topts, traverse_options::follow_symlinks) )
1152 )
1153 {
1154 int res = ::unlink( path_stats.path().c_str() );
1155 if( 0 != res ) {
1156 ERR_PRINT("remove failed: %s, res %d", path_stats.to_string().c_str(), res);
1157 return false;
1158 }
1159 if( is_set(topts, traverse_options::verbose) ) {
1160 jau::fprintf_td(stderr, "removed: %s\n", path_stats.to_string().c_str());
1161 }
1162 return true;
1163 }
1164 if( !path_stats.is_dir() ) {
1165 ERR_PRINT("remove: Error: path is neither file nor dir: %s\n", path_stats.to_string().c_str());
1166 return false;
1167 }
1168 // directory ...
1169 if( !is_set(topts, traverse_options::recursive) ) {
1170 if( is_set(topts, traverse_options::verbose) ) {
1171 jau::fprintf_td(stderr, "remove: Error: path is dir but !recursive, %s\n", path_stats.to_string().c_str());
1172 }
1173 return false;
1174 }
1175 struct remove_context_t {
1176 traverse_options topts;
1177 std::vector<int> dirfds;
1178 };
1179 remove_context_t ctx = { topts | jau::fs::traverse_options::dir_exit, std::vector<int>() };
1180
1182 ( bool(*)(remove_context_t*, traverse_event, const file_stats&, size_t) ) /* help template type deduction of function-ptr */
1183 ( [](remove_context_t* ctx_ptr, traverse_event tevt, const file_stats& element_stats, size_t depth) -> bool {
1184 (void)tevt;
1185 (void)depth;
1186
1187 if( !element_stats.has_access() ) {
1188 if( is_set(ctx_ptr->topts, traverse_options::verbose) ) {
1189 jau::fprintf_td(stderr, "remove: Error: remove failed: no access, %s\n", element_stats.to_string().c_str());
1190 }
1191 return false;
1192 }
1193 const int dirfd = ctx_ptr->dirfds.back();
1194 const std::string& basename_ = element_stats.item().basename();
1195 if( is_set(tevt, traverse_event::dir_entry) ) {
1196 // NOP
1197 } else if( is_set(tevt, traverse_event::dir_exit) ) {
1198 const int dirfd2 = *( ctx_ptr->dirfds.end() - 2 );
1199 const int res = ::unlinkat( dirfd2, basename_.c_str(), AT_REMOVEDIR );
1200 if( 0 != res ) {
1201 ERR_PRINT("remove failed: %s, res %d", element_stats.to_string().c_str(), res);
1202 return false;
1203 }
1204 if( is_set(ctx_ptr->topts, traverse_options::verbose) ) {
1205 jau::fprintf_td(stderr, "remove: %s removed\n", element_stats.to_string().c_str());
1206 }
1208 const int res = ::unlinkat( dirfd, basename_.c_str(), 0 );
1209 if( 0 != res ) {
1210 ERR_PRINT("remove failed: %s, res %d", element_stats.to_string().c_str(), res);
1211 return false;
1212 }
1213 if( is_set(ctx_ptr->topts, traverse_options::verbose) ) {
1214 jau::fprintf_td(stderr, "removed: %s\n", element_stats.to_string().c_str());
1215 }
1216 }
1217 return true;
1218 } ) );
1219 return jau::fs::visit(path_stats, ctx.topts, pv, &ctx.dirfds);
1220}
1221
1222bool jau::fs::compare(const std::string& source1, const std::string& source2, const bool verbose) noexcept {
1223 const file_stats s1(source1);
1224 const file_stats s2(source2);
1225 return compare(s1, s2, verbose);
1226}
1227
1228bool jau::fs::compare(const file_stats& source1, const file_stats& source2, const bool verbose) noexcept {
1229 if( !source1.is_file() ) {
1230 ERR_PRINT("source1_stats is not a file: %s", source1.to_string().c_str());
1231 return false;
1232 }
1233 if( !source2.is_file() ) {
1234 ERR_PRINT("source2_stats is not a file: %s", source2.to_string().c_str());
1235 return false;
1236 }
1237
1238 if( source1.size() != source2.size() ) {
1239 if( verbose ) {
1240 jau::fprintf_td(stderr, "compare: Source files size mismatch, %s != %s\n",
1241 source1.to_string().c_str(), source2.to_string().c_str());
1242 }
1243 return false;
1244 }
1245
1246 int src1=-1, src2=-1;
1247 int src_flags = O_RDONLY|O_BINARY|O_NOCTTY;
1248 uint64_t offset = 0;
1249
1250 bool res = false;
1251 src1 = __posix_openat64(AT_FDCWD, source1.path().c_str(), src_flags);
1252 if ( 0 > src1 ) {
1253 ERR_PRINT("Failed to open source1 %s, errno %d, %s", source1.to_string().c_str());
1254 goto errout;
1255 }
1256 src2 = __posix_openat64(AT_FDCWD, source2.path().c_str(), src_flags);
1257 if ( 0 > src2 ) {
1258 ERR_PRINT("Failed to open source2 %s, errno %d, %s", source2.to_string().c_str());
1259 goto errout;
1260 }
1261 while ( offset < source1.size()) {
1262 ssize_t rc1, rc2=0;
1263 char buffer1[BUFSIZ];
1264 char buffer2[BUFSIZ];
1265
1266 if( ( rc1 = ::read(src1, buffer1, sizeof(buffer1)) ) > 0 ) {
1267 ssize_t bytes_to_write = rc1;
1268 size_t buffer_offset = 0;
1269 while( 0 <= rc2 && 0 < bytes_to_write ) { // src2-read required src1 size in chunks, allowing potential multiple read-ops to match src1 size
1270 while( ( rc2 = ::read(src2, buffer2+buffer_offset, bytes_to_write) ) < 0 ) {
1271 if ( errno == EAGAIN || errno == EINTR ) {
1272 // cont temp unavail or interruption
1273 continue;
1274 }
1275 break; // error, exist inner (break) and outter loop (rc2<0)
1276 }
1277 buffer_offset += rc2;
1278 bytes_to_write -= rc2;
1279 offset += (uint64_t)rc2;
1280 }
1281 } else if ( 0 > rc1 && ( errno == EAGAIN || errno == EINTR ) ) {
1282 // cont temp unavail or interruption
1283 continue;
1284 }
1285 if ( 0 > rc1 || 0 > rc2 ) {
1286 if ( 0 > rc1 ) {
1287 ERR_PRINT("Failed to read source1 bytes @ %s / %s, %s",
1288 jau::to_decstring(offset).c_str(), jau::to_decstring(source1.size()).c_str(),
1289 source1.to_string().c_str());
1290 } else if ( 0 > rc2 ) {
1291 ERR_PRINT("Failed to read source2 bytes @ %s / %s, %s",
1292 jau::to_decstring(offset).c_str(), jau::to_decstring(source2.size()).c_str(),
1293 source2.to_string().c_str());
1294 }
1295 goto errout;
1296 }
1297 if( 0 != ::memcmp(buffer1, buffer2, rc1) ) {
1298 if( verbose ) {
1299 jau::fprintf_td(stderr, "compare: Difference within %s bytes @ %s / %s, %s != %s\n",
1300 jau::to_decstring(rc1).c_str(), jau::to_decstring(offset-rc1).c_str(), jau::to_decstring(source1.size()).c_str(),
1301 source1.to_string().c_str(), source2.to_string().c_str());
1302 }
1303 goto errout;
1304 }
1305 if ( 0 == rc1 ) {
1306 break;
1307 }
1308 }
1309 if( offset < source1.size() ) {
1310 ERR_PRINT("Incomplete transfer %s / %s, %s != %s\n",
1311 jau::to_decstring(offset).c_str(), jau::to_decstring(source1.size()).c_str(),
1312 source1.to_string().c_str(), source2.to_string().c_str());
1313 goto errout;
1314 }
1315 res = true;
1316errout:
1317 if( 0 <= src1 ) {
1318 ::close(src1);
1319 }
1320 if( 0 <= src2 ) {
1321 ::close(src2);
1322 }
1323 return res;
1324}
1325
1329 std::vector<int> src_dirfds;
1330 std::vector<int> dst_dirfds;
1331};
1332
1333static bool copy_file(const int src_dirfd, const file_stats& src_stats,
1334 const int dst_dirfd, const std::string& dst_basename, const copy_options copts) noexcept {
1335 file_stats dst_stats(dst_dirfd, dst_basename);
1336
1337 // overwrite: remove pre-existing file, if copy_options::overwrite set
1338 if( dst_stats.is_file() ) {
1339 if( !is_set(copts, copy_options::overwrite) ) {
1340 if( is_set(copts, copy_options::verbose) ) {
1341 jau::fprintf_td(stderr, "copy: Error: dest_path exists but copy_options::overwrite not set: source %s, dest '%s', copts %s\n",
1342 src_stats.to_string().c_str(), dst_stats.to_string().c_str(), to_string( copts ).c_str());
1343 }
1344 return false;
1345 }
1346 const int res = ::unlinkat(dst_dirfd, dst_basename.c_str(), 0);
1347 if( 0 != res ) {
1348 ERR_PRINT("remove existing dest_path for symbolic-link failed: source %s, dest '%s'",
1349 src_stats.to_string().c_str(), dst_stats.to_string().c_str());
1350 return false;
1351 }
1352 }
1353
1354 // copy as symbolic link
1355 if( src_stats.is_link() && !is_set(copts, copy_options::follow_symlinks) ) {
1356 const std::shared_ptr<std::string>& link_target_path = src_stats.link_target_path();
1357 if( nullptr == link_target_path || 0 == link_target_path->size() ) {
1358 ERR_PRINT("Symbolic link-path is empty %s", src_stats.to_string().c_str());
1359 return false;
1360 }
1361 // symlink
1362 const int res = ::symlinkat(link_target_path->c_str(), dst_dirfd, dst_basename.c_str());
1363 if( 0 > res ) {
1364 if( EPERM == errno && is_set(copts, copy_options::ignore_symlink_errors ) ) {
1365 if( is_set(copts, copy_options::verbose) ) {
1366 jau::fprintf_td(stderr, "copy: Ignored: Failed to create symink %s -> %s, %s, errno %d, %s\n",
1367 dst_basename.c_str(), link_target_path->c_str(), src_stats.to_string().c_str(), errno, ::strerror(errno));
1368 }
1369 return true;
1370 }
1371 ERR_PRINT("Creating symlink failed %s -> %s, %s", dst_basename.c_str(), link_target_path->c_str(), src_stats.to_string().c_str());
1372 return false;
1373 }
1374 if( is_set(copts, copy_options::preserve_all) ) {
1375 // preserve time
1376 struct timespec ts2[2] = { src_stats.atime().to_timespec(), src_stats.mtime().to_timespec() };
1377 if( 0 != ::utimensat(dst_dirfd, dst_basename.c_str(), ts2, AT_SYMLINK_NOFOLLOW) ) {
1378 ERR_PRINT("Couldn't preserve time of symlink, source %s, dest '%s'", src_stats.to_string().c_str(), dst_basename.c_str());
1379 return false;
1380 }
1381 // preserve ownership
1382 const uid_t caller_uid = ::geteuid();
1383 const ::uid_t source_uid = 0 == caller_uid ? src_stats.uid() : -1;
1384 if( 0 != ::fchownat(dst_dirfd, dst_basename.c_str(), source_uid, src_stats.gid(), AT_SYMLINK_NOFOLLOW) ) {
1385 if( errno != EPERM && errno != EINVAL ) {
1386 ERR_PRINT("Couldn't preserve ownership of symlink, source %s, dest '%s'", src_stats.to_string().c_str(), dst_basename.c_str());
1387 return false;
1388 }
1389 // OK to fail due to permissions
1390 if( is_set(copts, copy_options::verbose) ) {
1391 jau::fprintf_td(stderr, "copy: Warn: Couldn't preserve ownership of symlink, source %s, dest '%s', errno %d (%s)\n",
1392 src_stats.to_string().c_str(), dst_basename.c_str(), errno, ::strerror(errno));
1393 }
1394 }
1395 }
1396 return true;
1397 }
1398 // copy actual file bytes
1399 const file_stats* target_stats = src_stats.final_target(); // follows symlinks up to definite item
1400 const fmode_t dest_mode = target_stats->prot_mode();
1401 const fmode_t omitted_permissions = dest_mode & ( fmode_t::rwx_grp | fmode_t::rwx_oth );
1402
1403 const uid_t caller_uid = ::geteuid();
1404 int src=-1, dst=-1;
1405 int src_flags = O_RDONLY|O_BINARY|O_NOCTTY;
1406 uint64_t offset = 0;
1407
1408 bool res = false;
1409#if defined(__linux__)
1410 if( caller_uid == target_stats->uid() ) {
1411 src_flags |= O_NOATIME;
1412 } // else we are not allowed to not use O_NOATIME
1413#endif /* defined(__linux__) */
1414 src = __posix_openat64(src_dirfd, src_stats.item().basename().c_str(), src_flags);
1415 if ( 0 > src ) {
1416 if( src_stats.is_link() ) {
1418 }
1419 if( !res ) {
1420 ERR_PRINT("Failed to open source %s", src_stats.to_string().c_str());
1421 } else if( is_set(copts, copy_options::verbose) ) {
1422 jau::fprintf_td(stderr, "copy: Ignored: Failed to open source %s, errno %d, %s\n", src_stats.to_string().c_str(), errno, ::strerror(errno));
1423 }
1424 goto errout;
1425 }
1426 dst = __posix_openat64 (dst_dirfd, dst_basename.c_str(), O_CREAT|O_EXCL|O_WRONLY|O_BINARY|O_NOCTTY, jau::fs::posix_protection_bits( dest_mode & ~omitted_permissions ) );
1427 if ( 0 > dst ) {
1428 ERR_PRINT("Failed to open target_path '%s'", dst_basename.c_str());
1429 goto errout;
1430 }
1431 while ( offset < src_stats.size()) {
1432 ssize_t rc1, rc2=0;
1433#ifdef _USE_SENDFILE_
1434 off64_t offset_i = (off64_t)offset; // we drop 1 bit of value-range as off64_t is int64_t
1435 const uint64_t count = std::max<uint64_t>(std::numeric_limits<ssize_t>::max(), src_stats.size() - offset);
1436 if( ( rc1 = ::sendfile64(dst, src, &offset_i, (size_t)count) ) >= 0 ) {
1437 offset = (uint64_t)offset_i;
1438 }
1439#else /* _USE_SENDFILE_ */
1440 char buffer[BUFSIZ];
1441 if( ( rc1 = ::read(src, buffer, sizeof(buffer)) ) > 0 ) {
1442 ssize_t bytes_to_write = rc1;
1443 size_t buffer_offset = 0;
1444 while( 0 <= rc2 && 0 < bytes_to_write ) { // write the read chunk, allowing potential multiple write-ops
1445 while( ( rc2 = ::write(dst, buffer+buffer_offset, bytes_to_write) ) < 0 ) {
1446 if ( errno == EAGAIN || errno == EINTR ) {
1447 // cont temp unavail or interruption
1448 continue;
1449 }
1450 break; // error, exist inner (break) and outter loop (rc2<0)
1451 }
1452 buffer_offset += rc2;
1453 bytes_to_write -= rc2;
1454 offset += rc2;
1455 }
1456 } else if ( 0 > rc1 && ( errno == EAGAIN || errno == EINTR ) ) {
1457 // cont temp unavail or interruption
1458 continue;
1459 }
1460#endif/* _USE_SENDFILE_ */
1461 if ( 0 > rc1 || 0 > rc2 ) {
1462#ifdef _USE_SENDFILE_
1463 ERR_PRINT("Failed to copy bytes @ %s / %s, %s -> '%s'",
1464 jau::to_decstring(offset).c_str(), jau::to_decstring(src_stats.size()).c_str(),
1465 src_stats.to_string().c_str(), dst_basename.c_str());
1466#else /* _USE_SENDFILE_ */
1467 if ( 0 > rc1 ) {
1468 ERR_PRINT("Failed to read bytes @ %s / %s, %s",
1469 jau::to_decstring(offset).c_str(), jau::to_decstring(src_stats.size()).c_str(),
1470 src_stats.to_string().c_str());
1471 } else if ( 0 > rc2 ) {
1472 ERR_PRINT("Failed to write bytes @ %s / %s, %s",
1473 jau::to_decstring(offset).c_str(), jau::to_decstring(src_stats.size()).c_str(),
1474 dst_basename.c_str());
1475 }
1476#endif/* _USE_SENDFILE_ */
1477 goto errout;
1478 }
1479 if ( 0 == rc1 ) {
1480 break;
1481 }
1482 }
1483 if( offset < src_stats.size() ) {
1484 ERR_PRINT("Incomplete transfer %s / %s, %s -> '%s'",
1485 jau::to_decstring(offset).c_str(), jau::to_decstring(src_stats.size()).c_str(),
1486 src_stats.to_string().c_str(), dst_basename.c_str());
1487 goto errout;
1488 }
1489 res = true;
1490 if( omitted_permissions != fmode_t::none ) {
1491 // restore omitted permissions
1492 if( 0 != ::fchmod(dst, jau::fs::posix_protection_bits( dest_mode )) ) {
1493 ERR_PRINT("Couldn't restore omitted permissions, source %s, dest '%s'",
1494 src_stats.to_string().c_str(), dst_basename.c_str());
1495 res = false;
1496 }
1497 }
1498 if( is_set(copts, copy_options::preserve_all) ) {
1499 // preserve time
1500 struct timespec ts2[2] = { target_stats->atime().to_timespec(), target_stats->mtime().to_timespec() };
1501 if( 0 != ::futimens(dst, ts2) ) {
1502 ERR_PRINT("Couldn't preserve time of file, source %s, dest '%s'",
1503 src_stats.to_string().c_str(), dst_basename.c_str());
1504 res = false;
1505 }
1506 // preserve ownership
1507 ::uid_t source_uid = 0 == caller_uid ? target_stats->uid() : -1;
1508 if( 0 != ::fchown(dst, source_uid, target_stats->gid()) ) {
1509 if( errno != EPERM && errno != EINVAL ) {
1510 ERR_PRINT("Couldn't preserve ownership of file, uid(caller %" PRIu32 ", chown %" PRIu32 "), source %s, dest '%s'",
1511 caller_uid, source_uid, src_stats.to_string().c_str(), dst_basename.c_str());
1512 res = false;
1513 } else {
1514 // OK to fail due to permissions
1515 if( is_set(copts, copy_options::verbose) ) {
1516 jau::fprintf_td(stderr, "copy: Ignored: Preserve ownership of file failed, uid(caller %" PRIu32 ", chown %" PRIu32 "), source %s, dest '%s', errno %d (%s)\n",
1517 caller_uid, source_uid, src_stats.to_string().c_str(), dst_stats.to_string().c_str(), errno, ::strerror(errno));
1518 }
1519 }
1520 }
1521 }
1522 if( is_set(copts, copy_options::sync) ) {
1523 if( 0 != ::fsync(dst) ) {
1524 ERR_PRINT("Couldn't synchronize destination file, source %s, dest '%s'",
1525 src_stats.to_string().c_str(), dst_basename.c_str());
1526 res = false;
1527 }
1528 }
1529errout:
1530 if( 0 <= src ) {
1531 ::close(src);
1532 }
1533 if( 0 <= dst ) {
1534 ::close(dst);
1535 }
1536 return res;
1537}
1538
1539static bool copy_push_mkdir(const file_stats& dst_stats, copy_context_t& ctx) noexcept
1540{
1541 // atomically, using unpredictable '.'+rand temp dir (if target dir non-existent)
1542 // and drops read-permissions for user and all of group and others after fetching its dirfd.
1543 bool new_dir = false;
1544 std::string basename_;
1545 const int dest_dirfd = ctx.dst_dirfds.back();
1546 if( dst_stats.is_dir() ) {
1547 if( is_set(ctx.copts, copy_options::verbose) ) {
1548 jau::fprintf_td(stderr, "copy: mkdir directory already exist: %s\n", dst_stats.to_string().c_str());
1549 }
1550 basename_.append( dst_stats.item().basename() );
1551 } else if( !dst_stats.exists() ) {
1552 new_dir = true;
1553 constexpr const int32_t val_min = 888;
1554 constexpr const int32_t val_max = std::numeric_limits<int32_t>::max(); // 6 digits base 38 > INT_MAX
1555 uint64_t mkdir_cntr = 0;
1556 std::mt19937_64 prng;
1557 std::uniform_int_distribution<int32_t> prng_dist(val_min, val_max);
1558 bool mkdir_ok = false;
1559 do {
1560 ++mkdir_cntr;
1561 const int32_t val_d = prng_dist(prng);
1562 basename_.clear();
1563 basename_.append(".").append( jau::codec::base::encode(val_d, jau::codec::base::ascii38_alphabet(), 6) ); // base 38, 6 digits
1564 if( 0 == ::mkdirat(dest_dirfd, basename_.c_str(), jau::fs::posix_protection_bits(fmode_t::rwx_usr)) ) {
1565 mkdir_ok = true;
1566 } else if (errno != EINTR && errno != EEXIST) {
1567 ERR_PRINT("mkdir failed: %s, temp '%s'", dst_stats.to_string().c_str(), basename_.c_str());
1568 return false;
1569 } // else continue on EINTR or EEXIST
1570 } while( !mkdir_ok && mkdir_cntr < val_max );
1571 if( !mkdir_ok ) {
1572 ERR_PRINT("mkdir failed: %s", dst_stats.to_string().c_str());
1573 return false;
1574 }
1575 } else {
1576 ERR_PRINT("mkdir failed: %s, exists but is no dir", dst_stats.to_string().c_str());
1577 return false;
1578 }
1579 // open dirfd
1580 const int new_dirfd = __posix_openat64(dest_dirfd, basename_.c_str(), _open_dir_flags);
1581 if ( 0 > new_dirfd ) {
1582 if( new_dir ) {
1583 ERR_PRINT("Couldn't open new dir %s, temp '%s'", dst_stats.to_string().c_str(), basename_.c_str());
1584 } else {
1585 ERR_PRINT("Couldn't open new dir %s", dst_stats.to_string().c_str());
1586 }
1587 if( new_dir ) {
1588 ::unlinkat(dest_dirfd, basename_.c_str(), AT_REMOVEDIR);
1589 }
1590 return false;
1591 }
1592 // drop read permissions to render destination directory transaction safe until copy is done
1593 if( 0 != ::fchmod(new_dirfd, jau::fs::posix_protection_bits(fmode_t::write_usr | fmode_t::exec_usr)) ) {
1594 if( new_dir ) {
1595 ::unlinkat(dest_dirfd, basename_.c_str(), AT_REMOVEDIR);
1596 ERR_PRINT("zero permissions on dest %s, temp '%s'", dst_stats.to_string().c_str(), basename_.c_str());
1597 } else {
1598 ERR_PRINT("zero permissions on dest %s", dst_stats.to_string().c_str());
1599 }
1600 ::close(new_dirfd);
1601 return false;
1602 }
1603 if( new_dir ) {
1604#if defined(__linux__) && defined(__GLIBC__)
1605 const int rename_flags = 0; // Not supported on all fs: RENAME_NOREPLACE
1606 const int rename_res = ::renameat2(dest_dirfd, basename_.c_str(), dest_dirfd, dst_stats.item().basename().c_str(), rename_flags);
1607#else /* defined(__linux__) && defined(__GLIBC__) */
1608 const int rename_res = ::renameat(dest_dirfd, basename_.c_str(), dest_dirfd, dst_stats.item().basename().c_str());
1609#endif /* defined(__linux__) && defined(__GLIBC__) */
1610 if( 0 != rename_res ) {
1611 ERR_PRINT("rename temp to dest, temp '%s', dest %s", basename_.c_str(), dst_stats.to_string().c_str());
1612 ::unlinkat(dest_dirfd, basename_.c_str(), AT_REMOVEDIR);
1613 ::close(new_dirfd);
1614 return false;
1615 }
1616 }
1617 ctx.dst_dirfds.push_back(new_dirfd);
1618 return true;
1619}
1620
1621static bool copy_dir_preserve(const file_stats& src_stats, const int dst_dirfd, const std::string& dst_basename, const copy_options copts) noexcept {
1622 const file_stats* target_stats = src_stats.is_link() ? src_stats.link_target().get() : &src_stats;
1623
1624 // restore permissions
1625 const fmode_t dest_mode = target_stats->prot_mode();
1626 if( 0 != ::fchmod(dst_dirfd, jau::fs::posix_protection_bits( dest_mode )) ) {
1627 ERR_PRINT("restore permissions, source %s, dest '%s'", src_stats.to_string().c_str(), dst_basename.c_str());
1628 return false;
1629 }
1630
1631 if( is_set(copts, copy_options::preserve_all) ) {
1632 // preserve time
1633 struct timespec ts2[2] = { target_stats->atime().to_timespec(), target_stats->mtime().to_timespec() };
1634 if( 0 != ::futimens(dst_dirfd, ts2) ) {
1635 ERR_PRINT("preserve time of file failed, source %s, dest '%s'", src_stats.to_string().c_str(), dst_basename.c_str());
1636 return false;
1637 }
1638 // preserve ownership
1639 const uid_t caller_uid = ::geteuid();
1640 const ::uid_t source_uid = 0 == caller_uid ? target_stats->uid() : -1;
1641 if( 0 != ::fchown(dst_dirfd, source_uid, target_stats->gid()) ) {
1642 if( errno != EPERM && errno != EINVAL ) {
1643 ERR_PRINT("dir_preserve ownership of file failed, uid(caller %" PRIu32 ", chown %" PRIu32 "), source %s, dest '%s'",
1644 caller_uid, source_uid, src_stats.to_string().c_str(), dst_basename.c_str());
1645 return false;
1646 }
1647 // OK to fail due to permissions
1648 if( is_set(copts, copy_options::verbose) ) {
1649 jau::fprintf_td(stderr, "copy: Ignored: dir_preserve ownership of file failed, uid(caller %" PRIu32 ", chown %" PRIu32 "), source %s, dest '%s', errno %d (%s)\n",
1650 caller_uid, source_uid, src_stats.to_string().c_str(), dst_basename.c_str(), errno, ::strerror(errno));
1651 }
1652 }
1653 }
1654 if( is_set(copts, copy_options::sync) ) {
1655 if( 0 != ::fsync(dst_dirfd) ) {
1656 ERR_PRINT("Couldn't synchronize destination file '%s'", dst_basename.c_str());
1657 return false;
1658 }
1659 }
1660 return true;
1661}
1662
1663bool jau::fs::copy(const std::string& source_path, const std::string& target_path, const copy_options copts) noexcept {
1665 if( is_set(copts, copy_options::recursive) ) {
1667 }
1670 }
1671 if( is_set(copts, copy_options::verbose) ) {
1673 }
1674 file_stats source_stats(source_path);
1675 file_stats target_stats(target_path);
1676
1677 if( source_stats.is_file() ) {
1678 //
1679 // single file copy
1680 //
1681 if( target_stats.exists() ) {
1682 if( target_stats.is_file() ) {
1683 if( !is_set(copts, copy_options::overwrite)) {
1684 if( is_set(copts, copy_options::verbose) ) {
1685 jau::fprintf_td(stderr, "copy: Error: source_path is file, target_path existing file w/o overwrite, source %s, target %s\n",
1686 source_stats.to_string().c_str(), target_stats.to_string().c_str());
1687 }
1688 return false;
1689 }
1690 } // else file2file to directory
1691 } // else file2file to explicit new file
1692 const int src_dirfd = __posix_openat64(AT_FDCWD, source_stats.item().dirname().c_str(), _open_dir_flags);
1693 if ( 0 > src_dirfd ) {
1694 ERR_PRINT("source_path dir couldn't be opened, source %s", source_stats.to_string().c_str());
1695 return false;
1696 }
1697
1698 std::string dst_basename;
1699 int dst_dirfd = -1;
1700 if( target_stats.is_dir() ) {
1701 // file2file to directory
1702 dst_dirfd = __posix_openat64(AT_FDCWD, target_stats.path().c_str(), _open_dir_flags);
1703 if ( 0 > dst_dirfd ) {
1704 ERR_PRINT("target dir couldn't be opened, target %s", target_stats.to_string().c_str());
1705 ::close(src_dirfd);
1706 return false;
1707 }
1708 dst_basename = source_stats.item().basename();
1709 } else {
1710 // file2file to file
1711 file_stats target_parent_stats(target_stats.item().dirname());
1712 if( !target_parent_stats.is_dir() ) {
1713 if( is_set(copts, copy_options::verbose) ) {
1714 jau::fprintf_td(stderr, "copy: Error: target parent is not an existing directory, target %s, target_parent %s\n",
1715 target_stats.to_string().c_str(), target_parent_stats.to_string().c_str());
1716 }
1717 ::close(src_dirfd);
1718 return false;
1719 }
1720 dst_dirfd = __posix_openat64(AT_FDCWD, target_parent_stats.path().c_str(), _open_dir_flags);
1721 if ( 0 > dst_dirfd ) {
1722 ERR_PRINT("target_parent dir couldn't be opened, target %s, target_parent %s",
1723 target_stats.to_string().c_str(), target_parent_stats.to_string().c_str());
1724 ::close(src_dirfd);
1725 return false;
1726 }
1727 dst_basename = target_stats.item().basename();
1728 }
1729 if( !copy_file(src_dirfd, source_stats, dst_dirfd, dst_basename, copts) ) {
1730 return false;
1731 }
1732 ::close(src_dirfd);
1733 ::close(dst_dirfd);
1734 return true;
1735 }
1736 if( !source_stats.is_dir() ) {
1737 if( is_set(copts, copy_options::verbose) ) {
1738 jau::fprintf_td(stderr, "copy: Error: source_path is neither file nor dir, source %s, target %s\n",
1739 source_stats.to_string().c_str(), target_stats.to_string().c_str());
1740 }
1741 return false;
1742 }
1743
1744 //
1745 // directory copy
1746 //
1747 copy_context_t ctx { copts, 0, std::vector<int>(), std::vector<int>() };
1748
1749 if( !is_set(copts, copy_options::recursive) ) {
1750 if( is_set(copts, copy_options::verbose) ) {
1751 jau::fprintf_td(stderr, "copy: Error: source_path is dir but !recursive, %s\n", source_stats.to_string().c_str());
1752 }
1753 return false;
1754 }
1755 if( target_stats.exists() && !target_stats.is_dir() ) {
1756 if( is_set(copts, copy_options::verbose) ) {
1757 jau::fprintf_td(stderr, "copy: Error: source_path is dir but target_path exist and is no dir, source %s, target %s\n",
1758 source_stats.to_string().c_str(), target_stats.to_string().c_str());
1759 }
1760 return false;
1761 }
1762 // src_dirfd of 'source_stats.item().dirname().c_str()' will be pushed by visit() itself
1763 if( target_stats.is_dir() && !is_set(copts, copy_options::into_existing_dir) ) {
1764 // Case: If dest_path exists as a directory, source_path dir will be copied below the dest_path directory
1765 // _if_ copy_options::into_existing_dir is not set. Otherwise its content is copied into the existing dest_path.
1766 const int dst_dirfd = __posix_openat64(AT_FDCWD, target_stats.path().c_str(), _open_dir_flags);
1767 if ( 0 > dst_dirfd ) {
1768 ERR_PRINT("target dir couldn't be opened, target %s", target_stats.to_string().c_str());
1769 return false;
1770 }
1771 ctx.dst_dirfds.push_back(dst_dirfd);
1772 } else {
1773 // Case: If dest_path doesn't exist, source_path dir content is copied into the newly created dest_path.
1774 // - OR - dest_path does exist and copy_options::into_existing_dir is set
1775 file_stats target_parent_stats(target_stats.item().dirname());
1776 if( !target_parent_stats.is_dir() ) {
1777 if( is_set(copts, copy_options::verbose) ) {
1778 jau::fprintf_td(stderr, "copy: Error: target parent is not an existing directory, target %s, target_parent %s\n",
1779 target_stats.to_string().c_str(), target_parent_stats.to_string().c_str());
1780 }
1781 return false;
1782 }
1783 const int dst_parent_dirfd = __posix_openat64(AT_FDCWD, target_parent_stats.path().c_str(), _open_dir_flags);
1784 if ( 0 > dst_parent_dirfd ) {
1785 ERR_PRINT("target dirname couldn't be opened, target %s, target_parent %s",
1786 target_stats.to_string().c_str(), target_parent_stats.to_string().c_str());
1787 return false;
1788 }
1789 ctx.dst_dirfds.push_back(dst_parent_dirfd);
1790
1791 if( target_stats.is_dir() ) {
1792 // Case: copy_options::into_existing_dir
1793 const int dst_dirfd = __posix_openat64(AT_FDCWD, target_stats.path().c_str(), _open_dir_flags);
1794 if ( 0 > dst_dirfd ) {
1795 ERR_PRINT("target dir couldn't be opened, target %s", target_stats.to_string().c_str());
1796 return false;
1797 }
1798 ctx.dst_dirfds.push_back(dst_dirfd);
1799 } else {
1800 if( !copy_push_mkdir(target_stats, ctx) ) {
1801 return false;
1802 }
1803 }
1804 ctx.skip_dst_dir_mkdir = 1;
1805 }
1807 ( bool(*)(copy_context_t*, traverse_event, const file_stats&, size_t) ) /* help template type deduction of function-ptr */
1808 ( [](copy_context_t* ctx_ptr, traverse_event tevt, const file_stats& element_stats, size_t depth) -> bool {
1809 (void)depth;
1810 if( !element_stats.has_access() ) {
1811 if( is_set(ctx_ptr->copts, copy_options::verbose) ) {
1812 jau::fprintf_td(stderr, "copy: Error: remove failed: no access, %s\n", element_stats.to_string().c_str());
1813 }
1814 return false;
1815 }
1816 if( ctx_ptr->dst_dirfds.size() < 1 ) {
1817 ERR_PRINT("dirfd stack error: count[src %zu, dst %zu, dst_skip %d] @ %s",
1818 ctx_ptr->src_dirfds.size(), ctx_ptr->dst_dirfds.size(), ctx_ptr->skip_dst_dir_mkdir, element_stats.to_string().c_str());
1819 return false;
1820 }
1821 const int src_dirfd = ctx_ptr->src_dirfds.back();
1822 const int dst_dirfd = ctx_ptr->dst_dirfds.back();
1823 const std::string& basename_ = element_stats.item().basename();
1824 if( is_set(tevt, traverse_event::dir_entry) ) {
1825 if( 0 < ctx_ptr->skip_dst_dir_mkdir ) {
1826 --ctx_ptr->skip_dst_dir_mkdir;
1827 } else {
1828 const file_stats target_stats_(dst_dirfd, basename_);
1829 if( !copy_push_mkdir(target_stats_, *ctx_ptr) ) {
1830 return false;
1831 }
1832 }
1833 } else if( is_set(tevt, traverse_event::dir_exit) ) {
1834 if( ctx_ptr->dst_dirfds.size() < 2 ) {
1835 ERR_PRINT("dirfd stack error: count[src %zu, dst %zu] @ %s",
1836 ctx_ptr->src_dirfds.size(), ctx_ptr->dst_dirfds.size(), element_stats.to_string().c_str());
1837 return false;
1838 }
1839 if( !copy_dir_preserve( element_stats, dst_dirfd, basename_, ctx_ptr->copts ) ) {
1840 return false;
1841 }
1842 ::close(dst_dirfd);
1843 ctx_ptr->dst_dirfds.pop_back();
1845 if( !copy_file(src_dirfd, element_stats, dst_dirfd, basename_, ctx_ptr->copts) ) {
1846 return false;
1847 }
1848 }
1849 return true;
1850 } ) );
1851 bool res = jau::fs::visit(source_stats, topts, pv, &ctx.src_dirfds);
1852 while( !ctx.dst_dirfds.empty() ) {
1853 ::close(ctx.dst_dirfds.back());
1854 ctx.dst_dirfds.pop_back();
1855 }
1856 return res;
1857}
1858
1859bool jau::fs::rename(const std::string& oldpath, const std::string& newpath) noexcept {
1860 file_stats oldpath_stats(oldpath);
1861 file_stats newpath_stats(newpath);
1862 if( !oldpath_stats.is_link() && !oldpath_stats.exists() ) {
1863 ERR_PRINT("oldpath doesn't exist, oldpath %s, newpath %s\n",
1864 oldpath_stats.to_string().c_str(), newpath_stats.to_string().c_str());
1865 return false;
1866 }
1867 if( 0 != ::rename(oldpath_stats.path().c_str(), newpath_stats.path().c_str()) ) {
1868 ERR_PRINT("rename failed, oldpath %s, newpath %s\n",
1869 oldpath_stats.to_string().c_str(), newpath_stats.to_string().c_str());
1870 return false;
1871 }
1872 return true;
1873}
1874
1875void jau::fs::sync() noexcept {
1876 ::sync();
1877}
1878
1879static bool set_effective_uid(::uid_t user_id) {
1880 if( 0 != ::seteuid(user_id) ) {
1881 ERR_PRINT("seteuid(%" PRIu32 ") failed", user_id);
1882 return false;
1883 }
1884 return true;
1885}
1886
1887jau::fs::mount_ctx jau::fs::mount_image(const std::string& image_path, const std::string& target, const std::string& fs_type,
1888 const mountflags_t flags, const std::string& fs_options)
1889{
1890 file_stats image_stats(image_path);
1891 if( !image_stats.is_file()) {
1892 ERR_PRINT("image_path not a file: %s", image_stats.to_string().c_str());
1893 return mount_ctx();
1894 }
1895 file_stats target_stats(target);
1896 if( !target_stats.is_dir()) {
1897 ERR_PRINT("target not a dir: %s", target_stats.to_string().c_str());
1898 return mount_ctx();
1899 }
1900 const std::string target_path(target_stats.path());
1901 int backingfile = __posix_openat64(AT_FDCWD, image_stats.path().c_str(), O_RDWR);
1902 if( 0 > backingfile ) {
1903 ERR_PRINT("Couldn't open image-file '%s': res %d", image_stats.to_string().c_str(), backingfile);
1904 return mount_ctx();
1905 }
1906#if defined(__linux__)
1907 const ::uid_t caller_uid = ::geteuid();
1908 int loop_device_id = -1;
1909
1910 ::pid_t pid = ::fork();
1911 if( 0 == pid ) {
1912 int loop_ctl_fd = -1, loop_device_fd = -1;
1913 char loopname[4096];
1914 int mount_res = -1;
1915 void* fs_options_cstr = nullptr;
1916
1917 if( 0 != caller_uid ) {
1918 if( !set_effective_uid(0) ) {
1919 goto errout_child;
1920 }
1921 }
1922 loop_ctl_fd = __posix_openat64(AT_FDCWD, "/dev/loop-control", O_RDWR);
1923 if( 0 > loop_ctl_fd ) {
1924 ERR_PRINT("Couldn't open loop-control: res %d", loop_ctl_fd);
1925 goto errout_child;
1926 }
1927
1928 loop_device_id = ::ioctl(loop_ctl_fd, LOOP_CTL_GET_FREE);
1929 if( 0 > loop_device_id ) {
1930 ERR_PRINT("Couldn't get free loop-device: res %d", loop_device_id);
1931 goto errout_child;
1932 }
1933 if( 254 < loop_device_id ) { // _exit() encoding
1934 ERR_PRINT("loop-device %d out of valid range [0..254]", loop_device_id);
1935 goto errout_child;
1936 }
1937 ::close(loop_ctl_fd);
1938 loop_ctl_fd = -1;
1939
1940 snprintf(loopname, sizeof(loopname), "/dev/loop%d", loop_device_id);
1941 jau::INFO_PRINT("mount: Info: Using loop-device '%s'", loopname);
1942
1943 loop_device_fd = __posix_openat64(AT_FDCWD, loopname, O_RDWR);
1944 if( 0 > loop_device_fd ) {
1945 ERR_PRINT("Couldn't open loop-device '%s': res %d", loopname, loop_device_fd);
1946 goto errout_child;
1947 }
1948 if( 0 > ::ioctl(loop_device_fd, LOOP_SET_FD, backingfile) ) {
1949 ERR_PRINT("Couldn't attach image-file '%s' to loop-device '%s'", image_stats.to_string().c_str(), loopname);
1950 goto errout_child;
1951 }
1952
1953 if( fs_options.size() > 0 ) {
1954 fs_options_cstr = (void*) fs_options.data();
1955 }
1956 mount_res = ::mount(loopname, target_path.c_str(), fs_type.c_str(), flags, fs_options_cstr);
1957 if( 0 != mount_res ) {
1958 ERR_PRINT("source_path %s, target_path %s, fs_type %s, res %d",
1959 image_stats.path().c_str(), target_path.c_str(), fs_type.c_str(), mount_res);
1960 ::ioctl(loop_device_fd, LOOP_CLR_FD, 0);
1961 goto errout_child;
1962 }
1963 ::close(loop_device_fd);
1964 ::_exit(loop_device_id+1);
1965
1966errout_child:
1967 if( 0 <= loop_ctl_fd ) {
1968 ::close(loop_ctl_fd);
1969 }
1970 if( 0 <= loop_device_fd ) {
1971 ::close(loop_device_fd);
1972 }
1973 ::_exit(0);
1974 } else if( 0 < pid ) {
1975 int pid_status = 0;
1976 ::pid_t child_pid = ::waitpid(pid, &pid_status, 0);
1977 if( 0 > child_pid ) {
1978 ERR_PRINT("wait(%d) failed: child_pid %d", pid, child_pid);
1979 } else {
1980 if( child_pid != pid ) {
1981 WARN_PRINT("wait(%d) terminated child_pid %d", pid, child_pid);
1982 }
1983 if( !WIFEXITED(pid_status) ) {
1984 WARN_PRINT("wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status);
1985 goto errout;
1986 }
1987 loop_device_id = WEXITSTATUS(pid_status);
1988 if( 0 >= loop_device_id ) {
1989 goto errout;
1990 }
1991 --loop_device_id;
1992 }
1993 } else {
1994 ERR_PRINT("Couldn't fork() process: res %d", pid);
1995 goto errout;
1996 }
1997 if( 0 <= backingfile ) {
1998 ::close(backingfile);
1999 }
2000 return mount_ctx(target_path, loop_device_id);
2001
2002errout:
2003#else
2004 (void)fs_type;
2005 (void)flags;
2006 (void)fs_options;
2007#endif
2008 if( 0 <= backingfile ) {
2009 ::close(backingfile);
2010 }
2011 return mount_ctx();
2012}
2013
2014mount_ctx jau::fs::mount(const std::string& source, const std::string& target, const std::string& fs_type,
2015 const mountflags_t flags, const std::string& fs_options)
2016{
2017 if( source.empty() ) {
2018 ERR_PRINT("source is an empty string ");
2019 return mount_ctx();
2020 }
2021 file_stats source_stats(source);
2022 file_stats target_stats(target);
2023 if( !target_stats.is_dir()) {
2024 ERR_PRINT("target not a dir: %s", target_stats.to_string().c_str());
2025 return mount_ctx();
2026 }
2027 const std::string target_path(target_stats.path());
2028 const ::uid_t caller_uid = ::geteuid();
2029
2030 ::pid_t pid = ::fork();
2031 if( 0 == pid ) {
2032 void* fs_options_cstr = nullptr;
2033
2034 if( 0 != caller_uid ) {
2035 if( !set_effective_uid(0) ) {
2036 ::_exit( EXIT_FAILURE );
2037 }
2038 }
2039 if( fs_options.size() > 0 ) {
2040 fs_options_cstr = (void*) fs_options.data();
2041 }
2042#if defined(__linux__)
2043 const int mount_res = ::mount(source_stats.path().c_str(), target_path.c_str(), fs_type.c_str(), flags, fs_options_cstr);
2044#elif defined(__FreeBSD__)
2045 /**
2046 * Non generic due to the 'fstype'_args structure passed via data
2047 * ufs_args data = { .fsspec=source_stats.path().c_str() };
2048 * ::mount(fs_type.c_str(), target_path.c_str(), mountflags, data);
2049 */
2050 (void)flags;
2051 (void)fs_options_cstr;
2052 const int mount_res = -1;
2053#else
2054 #if !defined(JAU_OS_TYPE_WASM)
2055 #warning Add OS support
2056 #endif
2057 (void)flags;
2058 (void)fs_options_cstr;
2059 const int mount_res = -1;
2060#endif
2061 if( 0 != mount_res ) {
2062 ERR_PRINT("source_path %s, target_path %s, fs_type %s, flags %" PRIu64 ", res %d",
2063 source_stats.path().c_str(), target_path.c_str(), fs_type.c_str(), flags, mount_res);
2064 ::_exit( EXIT_FAILURE );
2065 } else {
2066 ::_exit( EXIT_SUCCESS );
2067 }
2068
2069 } else if( 0 < pid ) {
2070 int pid_status = 0;
2071 ::pid_t child_pid = ::waitpid(pid, &pid_status, 0);
2072 if( 0 > child_pid ) {
2073 ERR_PRINT("wait(%d) failed: child_pid %d", pid, child_pid);
2074 } else {
2075 if( child_pid != pid ) {
2076 WARN_PRINT("wait(%d) terminated child_pid %d", pid, child_pid);
2077 }
2078 if( !WIFEXITED(pid_status) ) {
2079 WARN_PRINT("wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status);
2080 } else if( EXIT_SUCCESS == WEXITSTATUS(pid_status) ) {
2081 return mount_ctx(target_path, -1);
2082 } // else mount failed
2083 }
2084 } else {
2085 ERR_PRINT("Couldn't fork() process: res %d", pid);
2086 }
2087 return mount_ctx();
2088}
2089
2090bool jau::fs::umount(const mount_ctx& context, const umountflags_t flags)
2091{
2092 if( !context.mounted) {
2093 return false;
2094 }
2095 file_stats target_stats(context.target);
2096 if( !target_stats.is_dir()) {
2097 return false;
2098 }
2099 const ::uid_t caller_uid = ::geteuid();
2100
2101 ::pid_t pid = ::fork();
2102 if( 0 == pid ) {
2103 if( 0 != caller_uid ) {
2104 if( !set_effective_uid(0) ) {
2105 ::_exit( EXIT_FAILURE );
2106 }
2107 }
2108#if defined(__linux__)
2109 const int umount_res = ::umount2(target_stats.path().c_str(), flags);
2110#elif defined(__FreeBSD__)
2111 const int umount_res = ::unmount(target_stats.path().c_str(), flags);
2112#else
2113 #if !defined(JAU_OS_TYPE_WASM)
2114 #warning Add OS support
2115 #endif
2116 const int umount_res = -1;
2117#endif
2118 if( 0 != umount_res ) {
2119 ERR_PRINT("Couldn't umount '%s', flags %d: res %d\n", target_stats.to_string().c_str(), flags, umount_res);
2120 }
2121 if( 0 > context.loop_device_id ) {
2122 // mounted w/o loop-device, done
2123 ::_exit(0 == umount_res ? EXIT_SUCCESS : EXIT_FAILURE);
2124 }
2125#if defined(__linux__)
2126 int loop_device_fd = -1;
2127 char loopname[4096];
2128
2129 snprintf(loopname, sizeof(loopname), "/dev/loop%d", context.loop_device_id);
2130 jau::INFO_PRINT("umount: Info: Using loop-device '%s'", loopname);
2131
2132 loop_device_fd = __posix_openat64(AT_FDCWD, loopname, O_RDWR);
2133 if( 0 > loop_device_fd ) {
2134 ERR_PRINT("Couldn't open loop-device '%s': res %d", loopname, loop_device_fd);
2135 goto errout_child;
2136 }
2137 if( 0 > ::ioctl(loop_device_fd, LOOP_CLR_FD, 0) ) {
2138 ERR_PRINT("Couldn't detach loop-device '%s'", loopname);
2139 goto errout_child;
2140 }
2141 ::close(loop_device_fd);
2142 ::_exit(0 == umount_res ? EXIT_SUCCESS : EXIT_FAILURE);
2143#endif
2144
2145 // No loop-device handling for OS
2146 ::_exit( EXIT_FAILURE );
2147
2148#if defined(__linux__)
2149errout_child:
2150 if( 0 <= loop_device_fd ) {
2151 ::close(loop_device_fd);
2152 }
2153 ::_exit( EXIT_FAILURE );
2154#endif
2155 } else if( 0 < pid ) {
2156 int pid_status = 0;
2157 ::pid_t child_pid = ::waitpid(pid, &pid_status, 0);
2158 if( 0 > child_pid ) {
2159 ERR_PRINT("wait(%d) failed: child_pid %d", pid, child_pid);
2160 } else {
2161 if( child_pid != pid ) {
2162 WARN_PRINT("wait(%d) terminated child_pid %d", pid, child_pid);
2163 }
2164 if( !WIFEXITED(pid_status) ) {
2165 WARN_PRINT("wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status);
2166 } else if( EXIT_SUCCESS == WEXITSTATUS(pid_status) ) {
2167 return true;
2168 } // else umount failed
2169 }
2170 } else {
2171 ERR_PRINT("Couldn't fork() process: res %d", pid);
2172 }
2173 return false;
2174}
2175
2176bool jau::fs::umount(const std::string& target, const umountflags_t flags)
2177{
2178 if( target.empty() ) {
2179 return false;
2180 }
2181 file_stats target_stats(target);
2182 if( !target_stats.is_dir()) {
2183 return false;
2184 }
2185 const ::uid_t caller_uid = ::geteuid();
2186
2187 ::pid_t pid = ::fork();
2188 if( 0 == pid ) {
2189 if( 0 != caller_uid ) {
2190 if( !set_effective_uid(0) ) {
2191 ::_exit( EXIT_FAILURE );
2192 }
2193 }
2194#if defined(__linux__)
2195 const int umount_res = ::umount2(target_stats.path().c_str(), flags);
2196#elif defined(__FreeBSD__)
2197 const int umount_res = ::unmount(target_stats.path().c_str(), flags);
2198#else
2199 #if !defined(JAU_OS_TYPE_WASM)
2200 #warning Add OS support
2201 #endif
2202 const int umount_res = -1;
2203#endif
2204 if( 0 == umount_res ) {
2205 ::_exit( EXIT_SUCCESS );
2206 } else {
2207 ERR_PRINT("Couldn't umount '%s', flags %d: res %d\n", target_stats.to_string().c_str(), flags, umount_res);
2208 ::_exit( EXIT_FAILURE );
2209 }
2210 } else if( 0 < pid ) {
2211 int pid_status = 0;
2212 ::pid_t child_pid = ::waitpid(pid, &pid_status, 0);
2213 if( 0 > child_pid ) {
2214 ERR_PRINT("wait(%d) failed: child_pid %d", pid, child_pid);
2215 } else {
2216 if( child_pid != pid ) {
2217 WARN_PRINT("wait(%d) terminated child_pid %d", pid, child_pid);
2218 }
2219 if( !WIFEXITED(pid_status) ) {
2220 WARN_PRINT("wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status);
2221 } else if( EXIT_SUCCESS == WEXITSTATUS(pid_status) ) {
2222 return true;
2223 } // else umount failed
2224 }
2225 } else {
2226 ERR_PRINT("Couldn't fork() process: res %d", pid);
2227 }
2228 return false;
2229}
#define O_BINARY
#define O_NONBLOCK
#define __posix_openat64
Safe base 38 alphabet with ASCII code-point sorting order.
Representing a directory item split into dirname() and basename().
std::string path() const noexcept
Returns a full unix path representation combining dirname() and basename().
const std::string & basename() const noexcept
Return the basename, shall not be empty nor contain a dirname.
std::string to_string() const noexcept
Returns a comprehensive string representation of this item.
dir_item() noexcept
Empty item w/ .
const std::string & dirname() const noexcept
Returns the dirname, shall not be empty and denotes .
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
int fd() const noexcept
Returns the file descriptor if has_fd(), otherwise -1 for no file descriptor.
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
constexpr bool has_fd() const noexcept
Returns true if entity has a file descriptor.
const fraction_timespec & mtime() const noexcept
Returns the last modification time of this element since Unix Epoch.
constexpr bool is_link() const noexcept
Returns true if entity is a symbolic link, might be in combination with is_file(),...
constexpr bool is_file() const noexcept
Returns true if entity is a file, might be in combination with is_link().
const file_stats * final_target(size_t *link_count=nullptr) const noexcept
Returns the final target element, either a pointer to this instance if not a symbolic-link or the fin...
gid_t gid() const noexcept
Returns the group id, owning the element.
constexpr bool is_dir() const noexcept
Returns true if entity is a directory, might be in combination with is_link().
const dir_item & item() const noexcept
Returns the dir_item.
bool has(const field_t fields) const noexcept
Returns true if the given field_t fields were retrieved, otherwise false.
field_t
Field identifier which bit-mask indicates field_t fields.
@ mode
POSIX file protection mode bits.
@ type
File type mode bits.
constexpr bool has_access() const noexcept
Returns true if entity gives no access to user, exclusive bit.
file_stats() noexcept
Instantiate an empty file_stats with fmode_t::not_existing set.
const std::shared_ptr< file_stats > & link_target() const noexcept
Returns the link-target this symbolic-link points to if instance is a symbolic-link,...
std::string to_string() const noexcept
Returns a comprehensive string representation of this element.
uid_t uid() const noexcept
Returns the user id, owning the element.
bool operator==(const file_stats &rhs) const noexcept
fmode_t prot_mode() const noexcept
Returns the POSIX protection bit portion of fmode_t, i.e.
const fraction_timespec & atime() const noexcept
Returns the last access time of this element since Unix Epoch.
constexpr field_t fields() const noexcept
Returns the retrieved field_t fields.
std::string path() const noexcept
Returns the unix path representation.
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition debug.hpp:109
#define ABORT(...)
Use for unconditional ::abort() call with given messages, prefix '[elapsed_time] ABORT @ file:line fu...
Definition debug.hpp:101
#define WARN_PRINT(...)
Use for unconditional warning messages, prefix '[elapsed_time] Warning @ FILE:LINE FUNC: '.
Definition debug.hpp:123
static bool set_effective_uid(::uid_t user_id)
static bool copy_file(const int src_dirfd, const file_stats &src_stats, const int dst_dirfd, const std::string &dst_basename, const copy_options copts) noexcept
static const std::string s_dot_slash("./")
static const std::string s_slash("/")
static bool copy_dir_preserve(const file_stats &src_stats, const int dst_dirfd, const std::string &dst_basename, const copy_options copts) noexcept
static const char c_backslash('\\')
static const char c_slash('/')
static const std::string s_slash_dotdot_slash("/../")
static const std::string s_slash_dot("/.")
static void _append_bitstr(std::string &out, fmode_t mask, fmode_t bit, const std::string &bitstr)
static const std::string s_slash_dot_slash("/./")
static const std::string s_dotdot("..")
struct ::stat64 struct_stat64
Definition file_util.cpp:79
#define __posix_fstatat64
Definition file_util.cpp:80
constexpr const int _open_dir_flags
Definition file_util.cpp:61
static const std::string s_slash_dotdot("/..")
static bool _visit(const file_stats &item_stats, const traverse_options topts, const path_visitor &visitor, std::vector< int > &dirfds) noexcept
static bool _dir_item_basename_compare(const dir_item &a, const dir_item &b)
static const std::string s_dot(".")
static bool copy_push_mkdir(const file_stats &dst_stats, copy_context_t &ctx) noexcept
std::string to_string(const endian_t v) noexcept
Return std::string representation of the given endian.
std::string encode(int num, const alphabet &aspec, const unsigned int min_width=0) noexcept
Encodes a given positive decimal number to a symbolic string representing a given alphabet and its ba...
constexpr bool has_any(const E mask, const E bits) noexcept
constexpr bool is_set(const E mask, const E bits) noexcept
constexpr E & write(E &store, const E bits, bool set) noexcept
If set==true, sets the bits in store, i.e.
bool exists(const std::string &path, bool verbose_on_error=false) noexcept
Returns true if path exists and is accessible.
bool mkdir(const std::string &path, const fmode_t mode=jau::fs::fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
uint64_t mountflags_t
Generic flag bit values for mount() flags.
std::string lookup_asset_dir(const char *exe_path, const char *asset_file, const char *asset_install_subdir) noexcept
Returns located asset directory if found, otherwise an empty string.
std::string to_named_fd(const int fd) noexcept
Returns platform dependent named file descriptor of given file descriptor, if supported.
mount_ctx mount(const std::string &source, const std::string &target, const std::string &fs_type, const mountflags_t flags, const std::string &fs_options="")
Attach the filesystem named in source to target using the given filesystem source directly.
mount_ctx mount_image(const std::string &image_path, const std::string &target, const std::string &fs_type, const mountflags_t flags, const std::string &fs_options="")
Attach the filesystem image named in image_path to target using an intermediate platform specific fil...
std::string to_string(const fmode_t mask, const bool show_rwx) noexcept
Return the string representation of fmode_t.
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.
fmode_t
Generic file type and POSIX protection mode bits as used in file_stats, touch(), mkdir() etc.
std::string basename(const std::string_view &path) noexcept
Return stripped leading directory components from given path separated by /.
std::string get_cwd() noexcept
Return the current working directory or empty on failure.
Definition file_util.cpp:84
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.
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.
std::string absolute(const std::string_view &relpath) noexcept
Returns the absolute path of given relpath if existing, otherwise an empty string.
int umountflags_t
Generic flag bit values for umount() flags.
std::string dirname(const std::string_view &path) noexcept
Return stripped last component from given path separated by /, excluding the trailing separator /.
copy_options
Filesystem copy options used to copy() path elements.
bool get_dir_content(const std::string &path, const consume_dir_item &digest) noexcept
Returns a list of directory elements excluding .
bool touch(const std::string &path, const jau::fraction_timespec &atime, const jau::fraction_timespec &mtime, const fmode_t mode=jau::fs::fmode_t::def_file_prot) noexcept
Touch the file with given atime and mtime and create file if not existing yet.
traverse_options
Filesystem traverse options used to visit() path elements.
bool rename(const std::string &oldpath, const std::string &newpath) noexcept
Rename oldpath to newpath using POSIX rename(), with the following combinations.
jau::function< void(const dir_item &)> consume_dir_item
void consume_dir_item(const dir_item& item)
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
void sync() noexcept
Synchronizes filesystems, i.e.
int from_named_fd(const std::string &named_fd) noexcept
Returns the file descriptor from the given named file descriptor.
bool umount(const mount_ctx &context, const umountflags_t flags)
Detach the given mount_ctx context
bool chdir(const std::string &path) noexcept
Change working directory.
bool isAbsolute(const std::string_view &path) noexcept
Returns true if first character is / or - in case of Windows - \\.
traverse_event
Filesystem traverse event used to call path_visitor for path elements from visit().
constexpr ::mode_t posix_protection_bits(const fmode_t mask) noexcept
Returns the POSIX protection bits: rwx_all | set_uid | set_gid | sticky, i.e.
jau::function< bool(traverse_event, const file_stats &, size_t)> path_visitor
path_visitor jau::FunctionDef definition
@ exec_usr
Protection bit: POSIX S_IXUSR.
@ exec_grp
Protection bit: POSIX S_IXGRP.
@ write_grp
Protection bit: POSIX S_IWGRP.
@ no_access
Type: Entity gives no access to user, exclusive bit.
@ set_uid
Protection bit: POSIX S_ISUID.
@ write_usr
Protection bit: POSIX S_IWUSR.
@ ugs_set
Protection bit: POSIX S_ISUID | S_ISGID | S_ISVTX.
@ link
Type: Entity is a symbolic link, might be in combination with file or dir, fifo, chr,...
@ sock
Type: Entity is a socket, might be in combination with link.
@ none
No mode bit set.
@ chr
Type: Entity is a character device, might be in combination with link.
@ rwx_oth
Protection bit: POSIX S_IRWXO.
@ sticky
Protection bit: POSIX S_ISVTX.
@ rwx_usr
Protection bit: POSIX S_IRWXU.
@ read_usr
Protection bit: POSIX S_IRUSR.
@ dir
Type: Entity is a directory, might be in combination with link.
@ file
Type: Entity is a file, might be in combination with link.
@ protection_mask
12 bit protection bit mask 07777 for rwx_all | set_uid | set_gid | sticky .
@ blk
Type: Entity is a block device, might be in combination with link.
@ read_grp
Protection bit: POSIX S_IRGRP.
@ read_oth
Protection bit: POSIX S_IROTH.
@ not_existing
Type: Entity does not exist, exclusive bit.
@ rwx_grp
Protection bit: POSIX S_IRWXG.
@ fifo
Type: Entity is a fifo/pipe, might be in combination with link.
@ write_oth
Protection bit: POSIX S_IWOTH.
@ set_gid
Protection bit: POSIX S_ISGID.
@ exec_oth
Protection bit: POSIX S_IXOTH.
@ ignore_symlink_errors
Ignore errors from erroneous symlinks, e.g.
@ verbose
Enable verbosity mode, show error messages on stderr.
@ follow_symlinks
Copy referenced symbolic linked files or directories instead of just the symbolic link with property ...
@ sync
Ensure data and meta-data file synchronization is performed via ::fsync() after asynchronous copy ope...
@ overwrite
Overwrite existing destination files.
@ preserve_all
Preserve uid and gid if allowed and access- and modification-timestamps, i.e.
@ into_existing_dir
Copy source dir content into an already existing destination directory as if destination directory di...
@ recursive
Traverse through directories, i.e.
@ lexicographical_order
Traverse through elements in lexicographical order.
@ verbose
Enable verbosity mode, potentially used by a path_visitor implementation like remove().
@ follow_symlinks
Traverse through symbolic linked directories if traverse_options::recursive is set,...
@ dir_check_entry
Call path_visitor at directory entry, allowing path_visitor to skip traversal of this directory if re...
@ dir_entry
Call path_visitor at directory entry.
@ dir_exit
Call path_visitor at directory exit.
@ recursive
Traverse through directories, i.e.
@ dir_symlink
Visiting a symbolic-link to a directory which is not followed, i.e.
@ none
No value, neither file, symlink nor dir_entry or dir_exit.
@ dir_non_recursive
Visiting a directory non-recursive, i.e.
@ file
Visiting a file, may be in conjunction with symlink, i.e.
@ dir_check_entry
Visiting a directory on entry, see traverse_options::dir_check_entry.
@ dir_entry
Visiting a directory on entry, see traverse_options::dir_entry.
@ symlink
Visiting a symbolic-link, either to a file or a non-existing entity.
@ dir_exit
Visiting a directory on exit, see traverse_options::dir_exit.
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 bool is_windows() noexcept
Evaluates true if platform os_type::native contains os_type::Windows.
void zero_bytes_sec(void *s, size_t n) noexcept __attrdecl_no_optimize__
Wrapper to ::explicit_bzero(), ::bzero() or ::memset(), whichever is available in that order.
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.
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2022 Gothel Software e.K.
Definition file_util.hpp:41
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition backtrace.hpp:32
void INFO_PRINT(const char *format,...) noexcept
Use for unconditional informal messages, prefix '[elapsed_time] Info: '.
Definition debug.cpp:248
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
copy_options copts
std::vector< int > dst_dirfds
std::vector< int > src_dirfds
Timespec structure using int64_t for its components in analogy to struct timespec_t on 64-bit platfor...
constexpr struct timespec to_timespec() const noexcept
Return conversion to POSIX struct timespec, potentially narrowing the components if underlying system...