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