43 #include <sys/types.h>
45 #include <sys/mount.h>
46 #if defined(__linux__)
47 #include <sys/sendfile.h>
48 #include <linux/loop.h>
64#if defined(__linux__) && defined(__GLIBC__)
66 #define _USE_SENDFILE_ 1
67#elif defined(__linux__)
68 #define _USE_SENDFILE_ 1
71#if defined(__FreeBSD__)
73 typedef off_t off64_t;
74 #define __posix_fstatat64 ::fstatat
75 #define __posix_openat64 ::openat
78 #define __posix_fstatat64 ::fstatat64
79 #define __posix_openat64 ::openat64
83 const size_t bsz = PATH_MAX;
88 char* res = ::getcwd(&str[0], bsz);
89 if( res == &str[0] ) {
90 str.resize(::strnlen(res, bsz));
99 return 0 ==
::chdir(path.c_str());
103 const size_t bsz = PATH_MAX;
108 char *res = ::realpath(&relpath[0], &str[0]);
109 if( res == &str[0] ) {
110 str.resize(::strnlen(res, bsz));
114 return std::string();
130 if( 0 == path.size() ) {
134 if(
c_slash == path[path.size()-1] ) {
135 if( 1 == path.size() ) {
136 return std::string(path);
138 end_pos = path.size()-2;
140 end_pos = path.size()-1;
142 size_t idx = path.find_last_of(
c_slash, end_pos);
143 if( idx == std::string_view::npos ) {
147 return std::string( path.substr(0, std::max<size_t>(1, idx)) );
152 if( 0 == path.size() ) {
156 if(
c_slash == path[path.size()-1] ) {
157 if( 1 == path.size() ) {
158 return std::string(path);
160 end_pos = path.size()-2;
162 end_pos = path.size()-1;
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));
168 return std::string( path.substr(idx+1, end_pos-idx) );
173 return path.size() > 0 &&
177std::unique_ptr<dir_item::backed_string_view> dir_item::reduce(
const std::string_view& path_)
noexcept {
178 constexpr const bool _debug =
false;
180 if constexpr ( _debug ) {
181 jau::fprintf_td(stderr,
"X.0: path '%s'\n", std::string(path_).c_str());
184 std::unique_ptr<dir_item::backed_string_view> path2 = std::make_unique<dir_item::backed_string_view>(path_);
192 path2->view = path2->view.substr(2, path2->view.size()-2);
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) )
201 path2->view = path2->view.substr(0, path2->view.size()-1);
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) ) ) {
208 path2->view = path2->backing.append(
s_slash);
211 if constexpr ( _debug ) {
212 jau::fprintf_td(stderr,
"X.1: path2 '%s'\n", path2->to_string(
true).c_str());
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());
223 if( std::string_view::npos == idx ) {
226 std::string_view pre = path2->view.substr(0, idx);
227 if( 0 == pre.size() ) {
229 path2->view = path2->view.substr(idx+2);
233 const std::string post( path2->view.substr(idx+2) );
234 path2->backup_and_append( pre, post );
237 if constexpr ( _debug ) {
238 jau::fprintf_td(stderr,
"X.2.2: path2: spos %zu, '%s'\n", spos, path2->to_string(
true).c_str());
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());
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());
252 if( std::string_view::npos == idx ) {
257 WARN_PRINT(
"dir_item::resolve: '..' resolution error: '%s' -> '%s'", std::string(path_).c_str(), path2->to_string().c_str());
260 std::string_view pre = path2->view.substr(0, idx);
264 }
else if( 3 <= idx &&
s_slash_dotdot == path2->view.substr(idx-3, 3) ) {
271 path2->view = path2->view.substr(idx+3);
273 }
else if(
s_dot == pre_str ) {
275 path2->view = path2->view.substr(idx+4);
279 const std::string post( path2->view.substr(idx+3) );
280 path2->backup_and_append( pre_str, post );
281 spos = pre_str.size();
284 if constexpr ( _debug ) {
285 jau::fprintf_td(stderr,
"X.3.2: path2: spos %zu, '%s'\n", spos, path2->to_string(
true).c_str());
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());
293 if(
c_slash == path2->view[path2->view.size()-1] ) {
294 path2->view = path2->view.substr(0, path2->view.size()-1);
296 if constexpr ( _debug ) {
297 jau::fprintf_td(stderr,
"X.X: path2: '%s'\n", path2->to_string(
true).c_str());
302dir_item::dir_item(std::unique_ptr<backed_string_view> cleanpath) noexcept
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()) {
313dir_item::dir_item() noexcept
314: dirname_(
s_dot), basename_(
s_dot), empty_(true) {}
322 if(
s_dot == dirname_ ) {
325 if(
s_dot == basename_ ) {
329 return dirname_ + basename_;
331 return dirname_ +
s_slash + basename_;
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;
347#define APPEND_BITSTR(U,V,M) append_bitstr(out, M, U::V, #V, comma);
349#define FMODEBITS_ENUM(X,M) \
357 X(fmode_t,no_access,M) \
358 X(fmode_t,not_existing,M)
361 if(
is_set( mask, bit )) {
380 const std::string r(
"r");
381 const std::string w(
"w");
382 const std::string x(
"x");
395 out.append(std::string(buf, len));
405 std::string res(
"/dev/fd/");
412 if( 1 == sscanf(named_fd.c_str(),
"/dev/fd/%d", &scan_value) ) {
415 }
else if( 1 == sscanf(named_fd.c_str(),
"/proc/self/fd/%d", &scan_value) ) {
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)
437 std::string out(
"[");
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_(),
451 static constexpr bool jau_has_stat(
const uint32_t mask,
const uint32_t bit) {
return bit == ( mask & bit ); }
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)
458 constexpr const bool _debug =
false;
460 const std::string full_path( item.empty() ?
"" : item.path() );
461 if( item.empty() && AT_FDCWD != dirfd ) {
463 has_fields_ |= field_t::fd;
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);
474 if( 0 <= scan_value ) {
475 has_fields_ |= field_t::fd;
478 }
else if( 0 == full_path.find(
"/dev/fd/pipe:") ) {
484 has_fields_ |= field_t::type;
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));
492 const std::string dirfd_path = has( field_t::fd ) ?
"" : ( dirfd_is_item_dirname ? item_.basename() : full_path );
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));
514 if( has_access() && exists() ) {
518 if( jau_has_stat( s.stx_mask, STATX_TYPE ) ) {
519 has_fields_ |= field_t::type;
521 if( has( field_t::type ) ) {
522 if( S_ISLNK( s.stx_mode ) ) {
525 if( S_ISREG( s.stx_mode ) ) {
527 }
else if( S_ISDIR( s.stx_mode ) ) {
529 }
else if( S_ISFIFO( s.stx_mode ) ) {
531 }
else if( S_ISCHR( s.stx_mode ) ) {
533 }
else if( S_ISSOCK( s.stx_mode ) ) {
535 }
else if( S_ISBLK( s.stx_mode ) ) {
539 if( jau_has_stat( s.stx_mask, STATX_MODE ) ) {
540 has_fields_ |= field_t::mode;
542 mode_ |=
static_cast<fmode_t>( s.stx_mode & ( S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX ) );
544 if( jau_has_stat( s.stx_mask, STATX_NLINK ) ) {
545 has_fields_ |= field_t::nlink;
547 if( jau_has_stat( s.stx_mask, STATX_UID ) ) {
548 has_fields_ |= field_t::uid;
551 if( jau_has_stat( s.stx_mask, STATX_GID ) ) {
552 has_fields_ |= field_t::gid;
555 if( jau_has_stat( s.stx_mask, STATX_ATIME ) || 0 != s.stx_atime.tv_sec || 0 != s.stx_atime.tv_nsec ) {
556 has_fields_ |= field_t::atime;
559 if( jau_has_stat( s.stx_mask, STATX_MTIME ) ) {
560 has_fields_ |= field_t::mtime;
563 if( jau_has_stat( s.stx_mask, STATX_CTIME ) ) {
564 has_fields_ |= field_t::ctime;
567 if( jau_has_stat( s.stx_mask, STATX_INO ) ) {
568 has_fields_ |= field_t::ino;
570 if( jau_has_stat( s.stx_mask, STATX_SIZE ) ) {
571 if( !is_link() && is_file() ) {
572 has_fields_ |= field_t::size;
576 if( jau_has_stat( s.stx_mask, STATX_BLOCKS ) ) {
577 has_fields_ |= field_t::blocks;
579 if( jau_has_stat( s.stx_mask, STATX_BTIME ) ) {
580 has_fields_ |= field_t::btime;
585 std::string link_path;
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 ) {
594 link_target_ = std::make_shared<file_stats>();
598 link_path = std::string(buffer.data(), path_link_len);
600 link_target_path_ = std::make_shared<std::string>(link_path);
601 if( 0 == cc.rec_level ) {
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));
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 );
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 );
632 if( link_target_->has_fd() ) {
633 has_fields_ |= field_t::fd;
634 fd_ = link_target_->fd();
636 if( link_target_->is_socket() ) {
638 }
else if( link_target_->is_block() ) {
640 }
else if( link_target_->is_char() ) {
642 }
else if( link_target_->is_fifo() ) {
644 }
else if( link_target_->is_dir() ) {
646 }
else if( link_target_->is_file() ) {
648 if( link_target_->has( field_t::size ) ) {
649 has_fields_ |= field_t::size;
650 size_ = link_target_->size();
652 }
else if( !link_target_->exists() ) {
654 }
else if( !link_target_->has_access() ) {
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));
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 ));
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));
680 if( has_access() && exists() ) {
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;
687 if( S_ISLNK( s.st_mode ) ) {
690 if( S_ISREG( s.st_mode ) ) {
693 has_fields_ |= field_t::size;
696 }
else if( S_ISDIR( s.st_mode ) ) {
698 }
else if( S_ISFIFO( s.st_mode ) ) {
700 }
else if( S_ISCHR( s.st_mode ) ) {
702 }
else if( S_ISSOCK( s.st_mode ) ) {
704 }
else if( S_ISBLK( s.st_mode ) ) {
709 mode_ |=
static_cast<fmode_t>( s.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX ) );
719 std::string link_path;
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 ) {
728 link_target_ = std::make_shared<file_stats>();
732 link_path = std::string(buffer.data(), path_link_len);
734 link_target_path_ = std::make_shared<std::string>(link_path);
735 if( 0 == cc.rec_level ) {
737 ::bzero(&s,
sizeof(s));
738 stat_res =
__posix_fstatat64(dirfd, dirfd_path.c_str(), &s, has( field_t::fd ) ? AT_EMPTY_PATH : 0);
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));
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 );
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 );
766 if( link_target_->has_fd() ) {
767 has_fields_ |= field_t::fd;
768 fd_ = link_target_->fd();
770 if( link_target_->is_socket() ) {
772 }
else if( link_target_->is_block() ) {
774 }
else if( link_target_->is_char() ) {
776 }
else if( link_target_->is_fifo() ) {
778 }
else if( link_target_->is_dir() ) {
780 }
else if( link_target_->is_file() ) {
782 has_fields_ |= field_t::size;
783 size_ = link_target_->size();
784 }
else if( !link_target_->exists() ) {
786 }
else if( !link_target_->has_access() ) {
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));
800:
file_stats(ctor_cookie(0), AT_FDCWD, item,
false )
804:
file_stats(ctor_cookie(0), dirfd, item, dirfd_is_item_dirname)
823 while(
nullptr != fs1 ) {
828 if(
nullptr != link_count ) {
835 return fields == ( has_fields_ & fields );
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_ &&
853 ( link_target_path_ == rhs.link_target_path_&&
854 link_target_ == rhs.link_target_
860 std::string stored_path, link_detail;
862 if(
nullptr != link_target_path_ ) {
863 stored_path =
" [-> "+*link_target_path_+
"]";
867 if( 0 < link_count ) {
868 link_detail =
" -(" +
std::to_string(link_count) +
")-> '" + final_target_->
path() +
"'";
871 std::string res(
"file_stats[");
873 .append(
", '"+item_.
path()+
"'"+stored_path+link_detail );
874 if( 0 == errno_res_ ) {
887 res.append(
", size n/a" );
903 res.append(
", errno " ).append(
std::to_string(errno_res_) ).append(
", " ).append( std::string(::strerror(errno_res_)) );
917 }
else if( !stats.
exists() ) {
919 if ( 0 != dir_err ) {
934 ERR_PRINT(
"Couldn't open/create file '%s'", path.c_str());
937 struct timespec ts2[2] = { atime.to_timespec(), mtime.to_timespec() };
939 if( 0 != ::futimens(fd, ts2) ) {
940 ERR_PRINT(
"Couldn't update time of file '%s'", path.c_str());
952 ERR_PRINT(
"Couldn't open/create file '%s'", path.c_str());
956 if( 0 != ::futimens(fd,
nullptr ) ) {
957 ERR_PRINT(
"Couldn't update time of file '%s'", path.c_str());
970 if( (
dir = ::opendir( path.c_str() ) ) !=
nullptr ) {
971 while ( ( ent = ::readdir(
dir ) ) !=
nullptr ) {
972 std::string fname( ent->d_name );
987 int dirfd2 = ::dup(dirfd);
989 ERR_PRINT(
"Couldn't duplicate given dirfd %d for path '%s'", dirfd, path.c_str());
992 if( (
dir = ::fdopendir( dirfd2 ) ) !=
nullptr ) {
993 while ( ( ent = ::readdir(
dir ) ) !=
nullptr ) {
994 std::string fname( ent->d_name );
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)
1015 std::string out(
"[");
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)
1032 std::string out(
"[");
1044 const size_t depth = dirfds.size();
1045 if( item_stats.is_dir() ) {
1052 if( dirfds.size() < 1 ) {
1053 ERR_PRINT(
"dirfd stack error: count %zu] @ %s", dirfds.size(), item_stats.to_string().c_str());
1056 const int parent_dirfd = dirfds.back();
1058 if ( 0 > this_dirfd ) {
1059 ERR_PRINT(
"entered path dir couldn't be opened, source %s", item_stats.to_string().c_str());
1062 dirfds.push_back(this_dirfd);
1066 ::close(this_dirfd);
1073 ::close(this_dirfd);
1078 std::vector<dir_item> content;
1080 ( void(*)(std::vector<dir_item>*,
const dir_item&) )
1081 ( [](std::vector<dir_item>* receiver,
const dir_item& item) ->
void { receiver->push_back( item ); } )
1083 if(
get_dir_content(this_dirfd, item_stats.path(), cs) && content.size() > 0 ) {
1087 for (
const dir_item& element : content) {
1088 const file_stats element_stats( this_dirfd, element,
true );
1089 if( element_stats.
is_dir() ) {
1092 ::close(this_dirfd);
1096 }
else if( !
_visit(element_stats, topts, visitor, dirfds) ) {
1097 ::close(this_dirfd);
1103 element_stats, depth ) )
1105 ::close(this_dirfd);
1111 if( dirfds.size() < 2 ) {
1112 ERR_PRINT(
"dirfd stack error: count %zu] @ %s", dirfds.size(), item_stats.to_string().c_str());
1119 ::close(this_dirfd);
1123 else if( item_stats.is_file() || !item_stats.ok() ) {
1132 const bool user_dirfds =
nullptr != dirfds;
1133 if( !user_dirfds ) {
1135 dirfds =
new std::vector<int>();
1136 }
catch (
const std::bad_alloc &e) {
1137 ABORT(
"Error: bad_alloc: dirfds allocation failed");
1141 if( 0 != dirfds->size() ) {
1142 ERR_PRINT(
"dirfd stack error: count %zu @ %s", dirfds->size(), item_stats.to_string().c_str());
1148 ERR_PRINT(
"path dirname couldn't be opened, source %s", item_stats.to_string().c_str());
1151 dirfds->push_back(dirfd);
1153 bool res =
_visit(item_stats, topts, visitor, *dirfds);
1155 if( dirfds->size() != 1 && res ) {
1156 ERR_PRINT(
"dirfd stack error: count %zu", dirfds->size());
1159 while( !dirfds->empty() ) {
1160 ::close(dirfds->back());
1163 if( !user_dirfds ) {
1178 if( !path_stats.
exists() ) {
1184 if( path_stats.
has_fd() ) {
1194 int res = ::unlink( path_stats.
path().c_str() );
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());
1215 struct remove_context_t {
1217 std::vector<int> dirfds;
1221 const path_visitor pv = jau::bind_capref<bool, remove_context_t, traverse_event, const file_stats&, size_t>(&ctx,
1233 const int dirfd = ctx_ptr->dirfds.back();
1234 const std::string& basename_ = element_stats.
item().
basename();
1238 const int dirfd2 = *( ctx_ptr->dirfds.end() - 2 );
1239 const int res = ::unlinkat( dirfd2, basename_.c_str(), AT_REMOVEDIR );
1248 const int res = ::unlinkat( dirfd, basename_.c_str(), 0 );
1262bool jau::fs::compare(
const std::string& source1,
const std::string& 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());
1273 if( !source2.is_file() ) {
1274 ERR_PRINT(
"source2_stats is not a file: %s", source2.to_string().c_str());
1278 if( source1.size() != source2.size() ) {
1280 jau::fprintf_td(stderr,
"compare: Source files size mismatch, %s != %s\n",
1281 source1.to_string().c_str(), source2.to_string().c_str());
1286 int src1=-1, src2=-1;
1287 int src_flags = O_RDONLY|
O_BINARY|O_NOCTTY;
1288 uint64_t offset = 0;
1293 ERR_PRINT(
"Failed to open source1 %s, errno %d, %s", source1.to_string().c_str());
1298 ERR_PRINT(
"Failed to open source2 %s, errno %d, %s", source2.to_string().c_str());
1301 while ( offset < source1.size()) {
1303 char buffer1[BUFSIZ];
1304 char buffer2[BUFSIZ];
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 ) {
1310 while( ( rc2 = ::read(src2, buffer2+buffer_offset, bytes_to_write) ) < 0 ) {
1311 if ( errno == EAGAIN || errno == EINTR ) {
1317 buffer_offset += rc2;
1318 bytes_to_write -= rc2;
1319 offset += (uint64_t)rc2;
1321 }
else if ( 0 > rc1 && ( errno == EAGAIN || errno == EINTR ) ) {
1325 if ( 0 > rc1 || 0 > rc2 ) {
1327 ERR_PRINT(
"Failed to read source1 bytes @ %s / %s, %s",
1329 source1.to_string().c_str());
1330 }
else if ( 0 > rc2 ) {
1331 ERR_PRINT(
"Failed to read source2 bytes @ %s / %s, %s",
1333 source2.to_string().c_str());
1337 if( 0 != ::memcmp(buffer1, buffer2, rc1) ) {
1339 jau::fprintf_td(stderr,
"compare: Difference within %s bytes @ %s / %s, %s != %s\n",
1341 source1.to_string().c_str(), source2.to_string().c_str());
1349 if( offset < source1.size() ) {
1350 ERR_PRINT(
"Incomplete transfer %s / %s, %s != %s\n",
1352 source1.to_string().c_str(), source2.to_string().c_str());
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)
1376 std::string out(
"[");
1391 const int dst_dirfd,
const std::string& dst_basename,
const copy_options copts)
noexcept {
1392 file_stats dst_stats(dst_dirfd, dst_basename);
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());
1403 const int res = ::unlinkat(dst_dirfd, dst_basename.c_str(), 0);
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());
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());
1419 const int res = ::symlinkat(link_target_path->c_str(), dst_dirfd, dst_basename.c_str());
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));
1428 ERR_PRINT(
"Creating symlink failed %s -> %s, %s", dst_basename.c_str(), link_target_path->c_str(), src_stats.to_string().c_str());
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());
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());
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));
1460 const uid_t caller_uid = ::geteuid();
1462 int src_flags = O_RDONLY|
O_BINARY|O_NOCTTY;
1463 uint64_t offset = 0;
1466#if defined(__linux__)
1467 if( caller_uid == target_stats->
uid() ) {
1468 src_flags |= O_NOATIME;
1471 src =
__posix_openat64(src_dirfd, src_stats.item().basename().c_str(), src_flags);
1473 if( src_stats.is_link() ) {
1477 ERR_PRINT(
"Failed to open source %s", src_stats.to_string().c_str());
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));
1485 ERR_PRINT(
"Failed to open target_path '%s'", dst_basename.c_str());
1488 while ( offset < src_stats.size()) {
1490#ifdef _USE_SENDFILE_
1491 off64_t offset_i = (off64_t)offset;
1493 if( ( rc1 = ::sendfile64(dst, src, &offset_i, (
size_t)count) ) >= 0 ) {
1494 offset = (uint64_t)offset_i;
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 ) {
1502 while( ( rc2 = ::write(dst, buffer+buffer_offset, bytes_to_write) ) < 0 ) {
1503 if ( errno == EAGAIN || errno == EINTR ) {
1509 buffer_offset += rc2;
1510 bytes_to_write -= rc2;
1513 }
else if ( 0 > rc1 && ( errno == EAGAIN || errno == EINTR ) ) {
1518 if ( 0 > rc1 || 0 > rc2 ) {
1519#ifdef _USE_SENDFILE_
1520 ERR_PRINT(
"Failed to copy bytes @ %s / %s, %s -> '%s'",
1522 src_stats.to_string().c_str(), dst_basename.c_str());
1525 ERR_PRINT(
"Failed to read bytes @ %s / %s, %s",
1527 src_stats.to_string().c_str());
1528 }
else if ( 0 > rc2 ) {
1529 ERR_PRINT(
"Failed to write bytes @ %s / %s, %s",
1531 dst_basename.c_str());
1540 if( offset < src_stats.size() ) {
1541 ERR_PRINT(
"Incomplete transfer %s / %s, %s -> '%s'",
1543 src_stats.to_string().c_str(), dst_basename.c_str());
1550 ERR_PRINT(
"Couldn't restore omitted permissions, source %s, dest '%s'",
1551 src_stats.to_string().c_str(), dst_basename.c_str());
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());
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());
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));
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());
1600 bool new_dir =
false;
1601 std::string basename_;
1602 const int dest_dirfd = ctx.dst_dirfds.back();
1603 if( dst_stats.is_dir() ) {
1605 jau::fprintf_td(stderr,
"copy: mkdir directory already exist: %s\n", dst_stats.to_string().c_str());
1607 basename_.append( dst_stats.item().basename() );
1608 }
else if( !dst_stats.exists() ) {
1610 constexpr const int32_t val_min = 888;
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;
1618 const int32_t val_d = prng_dist(prng);
1623 }
else if (errno != EINTR && errno != EEXIST) {
1624 ERR_PRINT(
"mkdir failed: %s, temp '%s'", dst_stats.to_string().c_str(), basename_.c_str());
1627 }
while( !mkdir_ok && mkdir_cntr < val_max );
1629 ERR_PRINT(
"mkdir failed: %s", dst_stats.to_string().c_str());
1633 ERR_PRINT(
"mkdir failed: %s, exists but is no dir", dst_stats.to_string().c_str());
1638 if ( 0 > new_dirfd ) {
1640 ERR_PRINT(
"Couldn't open new dir %s, temp '%s'", dst_stats.to_string().c_str(), basename_.c_str());
1642 ERR_PRINT(
"Couldn't open new dir %s", dst_stats.to_string().c_str());
1645 ::unlinkat(dest_dirfd, basename_.c_str(), AT_REMOVEDIR);
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());
1655 ERR_PRINT(
"zero permissions on dest %s", dst_stats.to_string().c_str());
1661#if defined(__linux__) && defined(__GLIBC__)
1662 const int rename_flags = 0;
1663 const int rename_res = ::renameat2(dest_dirfd, basename_.c_str(), dest_dirfd, dst_stats.item().basename().c_str(), rename_flags);
1665 const int rename_res = ::renameat(dest_dirfd, basename_.c_str(), dest_dirfd, dst_stats.item().basename().c_str());
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);
1674 ctx.dst_dirfds.push_back(new_dirfd);
1679 const file_stats* target_stats = src_stats.
is_link() ? src_stats.link_target().get() : &src_stats;
1684 ERR_PRINT(
"restore permissions, source %s, dest '%s'", src_stats.to_string().c_str(), dst_basename.c_str());
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());
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());
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));
1712 if( 0 != ::fsync(dst_dirfd) ) {
1713 ERR_PRINT(
"Couldn't synchronize destination file '%s'", dst_basename.c_str());
1734 if( source_stats.
is_file() ) {
1738 if( target_stats.
exists() ) {
1739 if( target_stats.
is_file() ) {
1742 jau::fprintf_td(stderr,
"copy: Error: source_path is file, target_path existing file w/o overwrite, source %s, target %s\n",
1750 if ( 0 > src_dirfd ) {
1751 ERR_PRINT(
"source_path dir couldn't be opened, source %s", source_stats.
to_string().c_str());
1755 std::string dst_basename;
1757 if( target_stats.
is_dir() ) {
1760 if ( 0 > dst_dirfd ) {
1761 ERR_PRINT(
"target dir couldn't be opened, target %s", target_stats.
to_string().c_str());
1769 if( !target_parent_stats.
is_dir() ) {
1771 jau::fprintf_td(stderr,
"copy: Error: target parent is not an existing directory, target %s, target_parent %s\n",
1778 if ( 0 > dst_dirfd ) {
1779 ERR_PRINT(
"target_parent dir couldn't be opened, target %s, target_parent %s",
1786 if( !
copy_file(src_dirfd, source_stats, dst_dirfd, dst_basename, copts) ) {
1793 if( !source_stats.
is_dir() ) {
1795 jau::fprintf_td(stderr,
"copy: Error: source_path is neither file nor dir, source %s, target %s\n",
1804 copy_context_t ctx { copts, 0, std::vector<int>(), std::vector<int>() };
1812 if( target_stats.
exists() && !target_stats.
is_dir() ) {
1814 jau::fprintf_td(stderr,
"copy: Error: source_path is dir but target_path exist and is no dir, source %s, target %s\n",
1824 if ( 0 > dst_dirfd ) {
1825 ERR_PRINT(
"target dir couldn't be opened, target %s", target_stats.
to_string().c_str());
1828 ctx.dst_dirfds.push_back(dst_dirfd);
1833 if( !target_parent_stats.
is_dir() ) {
1835 jau::fprintf_td(stderr,
"copy: Error: target parent is not an existing directory, target %s, target_parent %s\n",
1841 if ( 0 > dst_parent_dirfd ) {
1842 ERR_PRINT(
"target dirname couldn't be opened, target %s, target_parent %s",
1846 ctx.dst_dirfds.push_back(dst_parent_dirfd);
1848 if( target_stats.
is_dir() ) {
1851 if ( 0 > dst_dirfd ) {
1852 ERR_PRINT(
"target dir couldn't be opened, target %s", target_stats.
to_string().c_str());
1855 ctx.dst_dirfds.push_back(dst_dirfd);
1861 ctx.skip_dst_dir_mkdir = 1;
1863 const path_visitor pv = jau::bind_capref<bool, copy_context_t, traverse_event, const file_stats&, size_t>(&ctx,
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());
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());
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();
1883 --ctx_ptr->skip_dst_dir_mkdir;
1885 const file_stats target_stats_(dst_dirfd, basename_);
1886 if( !copy_push_mkdir(target_stats_, *ctx_ptr) ) {
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());
1896 if( !
copy_dir_preserve( element_stats, dst_dirfd, basename_, ctx_ptr->copts ) ) {
1900 ctx_ptr->dst_dirfds.pop_back();
1902 if( !
copy_file(src_dirfd, element_stats, dst_dirfd, basename_, ctx_ptr->copts) ) {
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();
1920 ERR_PRINT(
"oldpath doesn't exist, oldpath %s, newpath %s\n",
1924 if( 0 !=
::rename(oldpath_stats.
path().c_str(), newpath_stats.
path().c_str()) ) {
1925 ERR_PRINT(
"rename failed, oldpath %s, newpath %s\n",
1937 if( 0 != ::seteuid(user_id) ) {
1938 ERR_PRINT(
"seteuid(%" PRIu32
") failed", user_id);
1945 const mountflags_t flags,
const std::string& fs_options)
1953 if( !target_stats.
is_dir()) {
1957 const std::string target_path(target_stats.
path());
1959 if( 0 > backingfile ) {
1960 ERR_PRINT(
"Couldn't open image-file '%s': res %d", image_stats.
to_string().c_str(), backingfile);
1963#if defined(__linux__)
1964 const ::uid_t caller_uid = ::geteuid();
1965 int loop_device_id = -1;
1967 ::pid_t pid = ::fork();
1969 int loop_ctl_fd = -1, loop_device_fd = -1;
1970 char loopname[4096];
1972 void* fs_options_cstr =
nullptr;
1974 if( 0 != caller_uid ) {
1980 if( 0 > loop_ctl_fd ) {
1981 ERR_PRINT(
"Couldn't open loop-control: res %d", loop_ctl_fd);
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);
1990 if( 254 < loop_device_id ) {
1991 ERR_PRINT(
"loop-device %d out of valid range [0..254]", loop_device_id);
1994 ::close(loop_ctl_fd);
1997 snprintf(loopname,
sizeof(loopname),
"/dev/loop%d", loop_device_id);
2001 if( 0 > loop_device_fd ) {
2002 ERR_PRINT(
"Couldn't open loop-device '%s': res %d", loopname, loop_device_fd);
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);
2010 if( fs_options.size() > 0 ) {
2011 fs_options_cstr = (
void*) fs_options.data();
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);
2020 ::close(loop_device_fd);
2021 ::_exit(loop_device_id+1);
2024 if( 0 <= loop_ctl_fd ) {
2025 ::close(loop_ctl_fd);
2027 if( 0 <= loop_device_fd ) {
2028 ::close(loop_device_fd);
2031 }
else if( 0 < pid ) {
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);
2037 if( child_pid != pid ) {
2038 WARN_PRINT(
"wait(%d) terminated child_pid %d", pid, child_pid);
2040 if( !WIFEXITED(pid_status) ) {
2041 WARN_PRINT(
"wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status);
2044 loop_device_id = WEXITSTATUS(pid_status);
2045 if( 0 >= loop_device_id ) {
2051 ERR_PRINT(
"Couldn't fork() process: res %d", pid);
2054 if( 0 <= backingfile ) {
2055 ::close(backingfile);
2057 return mount_ctx(target_path, loop_device_id);
2065 if( 0 <= backingfile ) {
2066 ::close(backingfile);
2072 const mountflags_t flags,
const std::string& fs_options)
2074 if( source.empty() ) {
2075 ERR_PRINT(
"source is an empty string ");
2080 if( !target_stats.
is_dir()) {
2084 const std::string target_path(target_stats.
path());
2085 const ::uid_t caller_uid = ::geteuid();
2087 ::pid_t pid = ::fork();
2089 void* fs_options_cstr =
nullptr;
2091 if( 0 != caller_uid ) {
2093 ::_exit( EXIT_FAILURE );
2096 if( fs_options.size() > 0 ) {
2097 fs_options_cstr = (
void*) fs_options.data();
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__)
2108 (void)fs_options_cstr;
2109 const int mount_res = -1;
2111 #if !defined(JAU_OS_TYPE_WASM)
2112 #warning Add OS support
2115 (void)fs_options_cstr;
2116 const int mount_res = -1;
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 );
2123 ::_exit( EXIT_SUCCESS );
2126 }
else if( 0 < pid ) {
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);
2132 if( child_pid != pid ) {
2133 WARN_PRINT(
"wait(%d) terminated child_pid %d", pid, child_pid);
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) ) {
2142 ERR_PRINT(
"Couldn't fork() process: res %d", pid);
2153 if( !target_stats.
is_dir()) {
2156 const ::uid_t caller_uid = ::geteuid();
2158 ::pid_t pid = ::fork();
2160 if( 0 != caller_uid ) {
2162 ::_exit( EXIT_FAILURE );
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);
2170 #if !defined(JAU_OS_TYPE_WASM)
2171 #warning Add OS support
2173 const int umount_res = -1;
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);
2180 ::_exit(0 == umount_res ? EXIT_SUCCESS : EXIT_FAILURE);
2182#if defined(__linux__)
2183 int loop_device_fd = -1;
2184 char loopname[4096];
2186 snprintf(loopname,
sizeof(loopname),
"/dev/loop%d", context.
loop_device_id);
2190 if( 0 > loop_device_fd ) {
2191 ERR_PRINT(
"Couldn't open loop-device '%s': res %d", loopname, loop_device_fd);
2194 if( 0 > ::ioctl(loop_device_fd, LOOP_CLR_FD, 0) ) {
2195 ERR_PRINT(
"Couldn't detach loop-device '%s'", loopname);
2198 ::close(loop_device_fd);
2199 ::_exit(0 == umount_res ? EXIT_SUCCESS : EXIT_FAILURE);
2203 ::_exit( EXIT_FAILURE );
2205#if defined(__linux__)
2207 if( 0 <= loop_device_fd ) {
2208 ::close(loop_device_fd);
2210 ::_exit( EXIT_FAILURE );
2212 }
else if( 0 < pid ) {
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);
2218 if( child_pid != pid ) {
2219 WARN_PRINT(
"wait(%d) terminated child_pid %d", pid, child_pid);
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) ) {
2228 ERR_PRINT(
"Couldn't fork() process: res %d", pid);
2235 if( target.empty() ) {
2239 if( !target_stats.
is_dir()) {
2242 const ::uid_t caller_uid = ::geteuid();
2244 ::pid_t pid = ::fork();
2246 if( 0 != caller_uid ) {
2248 ::_exit( EXIT_FAILURE );
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);
2256 #if !defined(JAU_OS_TYPE_WASM)
2257 #warning Add OS support
2259 const int umount_res = -1;
2261 if( 0 == umount_res ) {
2262 ::_exit( EXIT_SUCCESS );
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 );
2267 }
else if( 0 < pid ) {
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);
2273 if( child_pid != pid ) {
2274 WARN_PRINT(
"wait(%d) terminated child_pid %d", pid, child_pid);
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) ) {
2283 ERR_PRINT(
"Couldn't fork() process: res %d", pid);
Safe base 38 alphabet with ASCII code-point sorting order.
Representing a directory item split into dirname() and basename().
std::string path() const noexcept
Returns a full unix path representation combining dirname() and basename().
const std::string & basename() const noexcept
Return the basename, shall not be empty nor contain a dirname.
std::string to_string() const noexcept
Returns a comprehensive string representation of this item.
dir_item() noexcept
Empty item w/ .
const std::string & dirname() const noexcept
Returns the dirname, shall not be empty and denotes .
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
constexpr bool has_fd() const noexcept
Returns true if entity has a file descriptor.
const fraction_timespec & mtime() const noexcept
Returns the last modification time of this element since Unix Epoch.
constexpr bool is_link() const noexcept
Returns true if entity is a symbolic link, might be in combination with is_file(),...
constexpr bool is_file() const noexcept
Returns true if entity is a file, might be in combination with is_link().
const file_stats * final_target(size_t *link_count=nullptr) const noexcept
Returns the final target element, either a pointer to this instance if not a symbolic-link or the fin...
gid_t gid() const noexcept
Returns the group id, owning the element.
constexpr bool is_dir() const noexcept
Returns true if entity is a directory, might be in combination with is_link().
const dir_item & item() const noexcept
Returns the dir_item.
bool has(const field_t fields) const noexcept
Returns true if the given field_t fields were retrieved, otherwise false.
field_t
Field identifier which bit-mask indicates field_t fields.
constexpr bool has_access() const noexcept
Returns true if entity gives no access to user, exclusive bit.
file_stats() noexcept
Instantiate an empty file_stats with fmode_t::not_existing set.
const std::shared_ptr< file_stats > & link_target() const noexcept
Returns the link-target this symbolic-link points to if instance is a symbolic-link,...
std::string to_string() const noexcept
Returns a comprehensive string representation of this element.
uid_t uid() const noexcept
Returns the user id, owning the element.
bool operator==(const file_stats &rhs) const noexcept
fmode_t prot_mode() const noexcept
Returns the POSIX protection bit portion of fmode_t, i.e.
const fraction_timespec & atime() const noexcept
Returns the last access time of this element since Unix Epoch.
std::string path() const noexcept
Returns the unix path representation.
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: '.
#define ABORT(...)
Use for unconditional ::abort() call with given messages, prefix '[elapsed_time] ABORT @ file:line fu...
#define WARN_PRINT(...)
Use for unconditional warning messages, prefix '[elapsed_time] Warning @ FILE:LINE FUNC: '.
#define TRAVERSEOPTIONS_ENUM(X, M)
static bool set_effective_uid(::uid_t user_id)
static bool copy_file(const int src_dirfd, const file_stats &src_stats, const int dst_dirfd, const std::string &dst_basename, const copy_options copts) noexcept
static void append_bitstr(std::string &out, T mask, T bit, const std::string &bitstr, bool &comma)
static const std::string s_dot_slash("./")
#define COPYOPTIONS_BIT_ENUM(X, M)
static const std::string s_slash("/")
static bool copy_dir_preserve(const file_stats &src_stats, const int dst_dirfd, const std::string &dst_basename, const copy_options copts) noexcept
static const char c_backslash('\\')
static const char c_slash('/')
static const std::string s_slash_dotdot_slash("/../")
#define FILESTATS_FIELD_ENUM(X, M)
#define TRAVERSEEVENT_ENUM(X, M)
static const std::string s_slash_dot("/.")
static const std::string s_slash_dot_slash("/./")
#define FMODEBITS_ENUM(X, M)
static const std::string s_dotdot("..")
struct ::stat64 struct_stat64
#define __posix_fstatat64
constexpr const int _open_dir_flags
static const std::string s_slash_dotdot("/..")
static bool _visit(const file_stats &item_stats, const traverse_options topts, const path_visitor &visitor, std::vector< int > &dirfds) noexcept
static bool _dir_item_basename_compare(const dir_item &a, const dir_item &b)
#define APPEND_BITSTR(U, V, M)
static const std::string s_dot(".")
static bool copy_push_mkdir(const file_stats &dst_stats, copy_context_t &ctx) noexcept
std::string encode(int num, const alphabet &aspec, const unsigned int min_width=0) noexcept
Encodes a given positive decimal number to a symbolic string representing a given alphabet and its ba...
std::string to_string(const alphabet &v) noexcept
bool mkdir(const std::string &path, const fmode_t mode=jau::fs::fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
uint64_t mountflags_t
Generic flag bit values for mount() flags.
std::string to_named_fd(const int fd) noexcept
Returns platform dependent named file descriptor of given file descriptor, if supported.
mount_ctx mount(const std::string &source, const std::string &target, const std::string &fs_type, const mountflags_t flags, const std::string &fs_options="")
Attach the filesystem named in source to target using the given filesystem source directly.
mount_ctx mount_image(const std::string &image_path, const std::string &target, const std::string &fs_type, const mountflags_t flags, const std::string &fs_options="")
Attach the filesystem image named in image_path to target using an intermediate platform specific fil...
bool copy(const std::string &source_path, const std::string &dest_path, const copy_options copts=copy_options::none) noexcept
Copy the given source_path to dest_path using copy_options.
fmode_t
Generic file type and POSIX protection mode bits as used in file_stats, touch(), mkdir() etc.
std::string basename(const std::string_view &path) noexcept
Return stripped leading directory components from given path separated by /.
std::string get_cwd() noexcept
Return the current working directory or empty on failure.
bool compare(const file_stats &source1, const file_stats &source2, const bool verbose=false) noexcept
Compare the bytes of both files, denoted by source1 and source2.
bool visit(const std::string &path, const traverse_options topts, const path_visitor &visitor, std::vector< int > *dirfds=nullptr) noexcept
Visit element(s) of a given path, see traverse_options for detailed settings.
std::string absolute(const std::string_view &relpath) noexcept
Returns the absolute path of given relpath if existing, otherwise an empty string.
int umountflags_t
Generic flag bit values for umount() flags.
std::string dirname(const std::string_view &path) noexcept
Return stripped last component from given path separated by /, excluding the trailing separator /.
copy_options
Filesystem copy options used to copy() path elements.
bool get_dir_content(const std::string &path, const consume_dir_item &digest) noexcept
Returns a list of directory elements excluding .
bool touch(const std::string &path, const jau::fraction_timespec &atime, const jau::fraction_timespec &mtime, const fmode_t mode=jau::fs::fmode_t::def_file_prot) noexcept
Touch the file with given atime and mtime and create file if not existing yet.
traverse_options
Filesystem traverse options used to visit() path elements.
bool rename(const std::string &oldpath, const std::string &newpath) noexcept
Rename oldpath to newpath using POSIX rename(), with the following combinations.
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
void sync() noexcept
Synchronizes filesystems, i.e.
int from_named_fd(const std::string &named_fd) noexcept
Returns the file descriptor from the given named file descriptor.
constexpr bool is_set(const fmode_t mask, const fmode_t bits) noexcept
bool umount(const mount_ctx &context, const umountflags_t flags)
Detach the given mount_ctx context
bool chdir(const std::string &path) noexcept
Change working directory.
bool isAbsolute(const std::string_view &path) noexcept
Returns true if first character is / or - in case of Windows - \\.
traverse_event
Filesystem traverse event used to call path_visitor for path elements from visit().
std::string to_string(const fmode_t mask, const bool show_rwx=false) noexcept
Return the string representation of fmode_t.
constexpr ::mode_t posix_protection_bits(const fmode_t mask) noexcept
Returns the POSIX protection bits: rwx_all | set_uid | set_gid | sticky, i.e.
@ 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.
@ 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)
constexpr bool is_windows() noexcept
Evaluates true if platform os_type::native contains os_type::Windows.
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.
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
void INFO_PRINT(const char *format,...) noexcept
Use for unconditional informal messages, prefix '[elapsed_time] Info: '.
int fprintf_td(const uint64_t elapsed_ms, FILE *stream, const char *format,...) noexcept
Convenient fprintf() invocation, prepending the given elapsed_ms timestamp.
std::vector< int > dst_dirfds
std::vector< int > src_dirfds
Timespec structure using int64_t for its components in analogy to struct timespec_t on 64-bit platfor...
std::string to_iso8601_string() const noexcept
Convenience string conversion interpreted since Unix Epoch in UTC to ISO 8601 YYYY-mm-ddTHH:MM:SS....
constexpr struct timespec to_timespec() const noexcept
Return conversion to POSIX struct timespec, potentially narrowing the components if underlying system...