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