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