7#include <oneapi/tbb/partitioner.h>
10#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
11# error "Configuration MACRO ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
22#if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
24# if defined(__linux__)
25# include <asm/unistd.h>
28# if defined(__linux__)
29# include <linux/stat.h>
32# if !defined(__APPLE__)
33# include <sys/sysmacros.h>
35# include <sys/types.h>
48 import ALib.Characters.Functions;
51 import ALib.Strings.Tokenizer;
54 import ALib.Expressions;
58 import ALib.ALox.Impl;
84makeCanonicalRecursion(
Path& sourcePath,
97 A_CHAR(
" {:ta h{2,r} on{10,r} gn{10,r} s(IEC){10,r} dm qqq FxFa (rd{3r}' D' rf{3r}' F' re{2r}' EA' rb{2r}'BL) 'nf l}");
106#if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
109# define DBG_CHECKERRNO_WITH_PATH \
110 ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\". Current path: {}", \
111 errno, std::errc(errno), actPath ) \
114# define DBG_CHECKERRNO_WITH_PATH
118#if defined(__NR_statx)
119# define TMP_STATX_AVAILABLE 1
120# define STATMEMBER(Name) stats.stx_ ## Name
121# define STAT_DEV_MAJOR stats.stx_dev_major
122# define STAT_DEV_MINOR stats.stx_dev_minor
124# define TMP_STATX_AVAILABLE 0
125# define STATMEMBER(Name) stats.st_ ## Name
126# define STAT_DEV_MAJOR major(stats.st_dev)
127# define STAT_DEV_MINOR minor(stats.st_dev)
131void scanFilePosix( DIR* pxDir,
133 const CPathString& nameOrFullPath,
136 uint64_t currentDevice,
142 || actPath.
CharAtEnd() != DIRECTORY_SEPARATOR )
143 && actPath.
IndexOf(strings::TLocalString<system::PathCharType,4>(
144 DIRECTORY_SEPARATOR).Append(DIRECTORY_SEPARATOR)) < 0 ,
145 "FILES",
"Given path \"{}\" not absolute or ending with '{}'",
146 actPath, DIRECTORY_SEPARATOR )
149 dbgActFile << nameOrFullPath;
151 dbgActFile << actPath;
152 if(dbgActFile.Length()>1)
153 dbgActFile << DIRECTORY_SEPARATOR;
154 dbgActFile << nameOrFullPath;
158 BoxesMA verboseLogables( verboseAllocator);
161 if( verboseLoggers ) {
162 verboseLogables.Add(
"{!AWidth:>} ");
163 if( ¶ms == ¶msPathOnly )
164 verboseLogables.Add(
"PO");
166 auto& depthString= *verboseAllocator().New<
String128>();
168 << ( params.
MaxDepth < std::numeric_limits<unsigned>::max()
171 verboseLogables.Add(depthString);
178 errno, std::errc(errno), dbgActFile )
182 auto oldScanState= value.ScanState();
187 if( value.ScanState() ==
FInfo::ScanStates::NONE
188 || ( value.ScanState() ==
FInfo::ScanStates::STATS
189 && params.LinkTreatment !=
ScanParameters::SymbolicLinks::DONT_RESOLVE ) )
193 Path symLinkDestReal;
197 #
if TMP_STATX_AVAILABLE
199 int statResult= statx( pxDir ? dirfd(pxDir) : 0,
201 AT_STATX_DONT_SYNC | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW,
202 STATX_BASIC_STATS | STATX_BTIME,
207 int statResult= pxDir ? fstatat(dirfd(pxDir), nNameOrFullPath, &stats,
209 #
if !defined(__APPLE__)
213 : lstat ( nNameOrFullPath, &stats );
216 ALIB_ASSERT_WARNING( errno != ENOENT,
"FILES",
"File does not exist (anymore) while stating \"{}\"",
218 ALIB_ASSERT_WARNING( errno == ENOENT,
"FILES",
"Unknown error ({}) \"{}\" while stating file \"{}\"",
219 errno, std::errc(errno), dbgActFile )
223 goto APPLY_POST_RECURSION_FILTER;
225 DBG_CHECKERRNO_WITH_PATH
229 uint64_t device= (uint64_t(STAT_DEV_MAJOR) << 32L) + STAT_DEV_MINOR;
230 if( currentDevice == 0) currentDevice= device;
231 else if( currentDevice != device) { currentDevice= device; value.SetCrossingFS(); }
234 if( STAT_DEV_MAJOR == 0
235 && STAT_DEV_MINOR != 35 )
236 value.SetArtificialFS();
239 bool origFileIsSymlink= (STATMEMBER(mode) & S_IFMT) == S_IFLNK;
240 if( origFileIsSymlink
243 value.SetScanState( FInfo::ScanStates::RESOLVED );
246 ALIB_STRINGS_TO_NARROW(symLinkDest, nSymLinkDest, 512)
247 ssize_t cntChars= pxDir ? readlinkat( dirfd(pxDir), nNameOrFullPath, nSymLinkDest.VBuffer(), PATH_MAX)
248 : readlink ( nNameOrFullPath, nSymLinkDest.VBuffer(), PATH_MAX);
250 if (cntChars == -1) switch(errno) {
251 case EACCES: value.SetScanState(FInfo::ScanStates::NO_ACCESS_SL); ALIB_DBG(errno= 0;)
254 case ENOENT: value.SetScanState(FInfo::ScanStates::NO_ACCESS_SL);
255 ALIB_ASSERT_ERROR(STAT_DEV_MAJOR == 0,
"FILES",
256 "Posix raised ({}) \"{}\" on reading a symbolic link which is not located on "
257 "an artificial filesystem (like /proc). File:\"{}\"",
258 errno, std::errc(errno), dbgActFile ) ALIB_DBG(errno= 0;)
261 default: value.SetScanState(FInfo::ScanStates::UNKNOWN_ERROR);
262 ALIB_ERROR(
"FILES",
"Posix raised ({}) \"{}\" on reading symbolic link \"{}\"",
263 errno, std::errc(errno), dbgActFile ) ALIB_DBG(errno= 0;)
266 nSymLinkDest.SetLength(cntChars);
268 symLinkDest.
Reset(nSymLinkDest);
274 actPath << DIRECTORY_SEPARATOR << nameOrFullPath;
278 *nSymLinkDestReal.VBuffer()=
'\0';
279 if(! realpath(nActPath.Terminate(), nSymLinkDestReal.VBuffer() ) ) switch (errno)
281 case ENOENT:
if( *nSymLinkDestReal.VBuffer() !=
'\0')
290 default:
ALIB_ERROR(
"FILES",
"Posix raised ({}) \"{}\" on resolving symbolic link \"{}\"",
291 errno, std::errc(errno), dbgActFile )
ALIB_DBG(errno= 0;)
294 nSymLinkDestReal.DetectLength();
296 ALIB_DBG(
if( errno == EINVAL) errno= 0;)
297 DBG_CHECKERRNO_WITH_PATH
299 "Real path is not absolute: ", nSymLinkDestReal )
302 DBG_CHECKERRNO_WITH_PATH
303 #if TMP_STATX_AVAILABLE
304 statResult= statx( 0,
305 nSymLinkDestReal.Terminate(),
306 AT_STATX_DONT_SYNC | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW,
310 statResult= stat(nSymLinkDestReal.Terminate(), &stats );
312 DBG_CHECKERRNO_WITH_PATH
313 #if ALIB_CHARACTERS_WIDE
314 symLinkDestReal.Reset(nSymLinkDestReal);
317 if(statResult == -1 ) {
319 if(errno)
switch( std::errc(errno) )
320 {
case std::errc::no_such_file_or_directory:
323 goto APPLY_POST_RECURSION_FILTER;
327 "Unhandled error code invoking 'stat()' on resolved symbolic "
328 "link: {} (\"{}\")\n Symbolic link target: \"{}\"",
329 errno, std::errc(errno), dbgActFile )
332 goto APPLY_POST_RECURSION_FILTER;
338 if( STAT_DEV_MAJOR == 0
339 && STAT_DEV_MINOR != 35 )
340 value.SetTargetArtificialFS();
345 DBG_CHECKERRNO_WITH_PATH
353 auto posixType= STATMEMBER(mode) & S_IFMT;
354 if( origFileIsSymlink ) {
358 else switch(STATMEMBER(mode) & S_IFMT ) {
361 case S_IFBLK : type=
FInfo::Types::BLOCK ; break;
362 case S_IFCHR : type=
FInfo::Types::CHARACTER ; break;
363 case S_IFDIR : type=
FInfo::Types::DIRECTORY ; break;
364 case S_IFIFO : type=
FInfo::Types::FIFO ; break;
365 case S_IFREG : type=
FInfo::Types::REGULAR ; break;
366 case S_IFSOCK: type=
FInfo::Types::SOCKET ; break;
368 "Internal error. 'unknown' file type can't happen. File: \"{}\"",
371 value.SetType( type );
375 value.SetPerms(
FInfo::Permissions(STATMEMBER(mode) & int32_t(
FInfo::Permissions::MASK)) );
379 #if defined(__APPLE__)
380 # define st_mtime_name STATMEMBER(mtimespec)
381 # define st_ctime_name STATMEMBER(ctimespec)
382 # define st_atime_name STATMEMBER(atimespec)
384 # if TMP_STATX_AVAILABLE
385 # define st_mtime_name STATMEMBER(mtime)
386 # define st_ctime_name STATMEMBER(ctime)
387 # define st_atime_name STATMEMBER(atime)
388 # define st_btime_name STATMEMBER(btime)
390 # define st_mtime_name STATMEMBER(mtim)
391 # define st_ctime_name STATMEMBER(ctim)
392 # define st_atime_name STATMEMBER(atim)
397 std::chrono::system_clock::time_point {
398 std::chrono::duration_cast<std::chrono::system_clock::duration>(
399 std::chrono::seconds {st_mtime_name.tv_sec }
400 + std::chrono::nanoseconds{st_mtime_name.tv_nsec} ) } );
404 std::chrono::system_clock::time_point {
405 std::chrono::duration_cast<std::chrono::system_clock::duration>(
406 std::chrono::seconds {st_ctime_name.tv_sec }
407 + std::chrono::nanoseconds{st_ctime_name.tv_nsec} ) } );
411 std::chrono::system_clock::time_point {
412 std::chrono::duration_cast<std::chrono::system_clock::duration>(
413 std::chrono::seconds {st_atime_name.tv_sec }
414 + std::chrono::nanoseconds{st_atime_name.tv_nsec} ) } );
417 #if TMP_STATX_AVAILABLE
418 if( STATMEMBER(mask) & STATX_BTIME ) {
420 std::chrono::system_clock::time_point {
421 std::chrono::duration_cast<std::chrono::system_clock::duration>(
422 std::chrono::seconds {st_btime_name.tv_sec }
423 + std::chrono::nanoseconds{st_btime_name.tv_nsec} ) } );
427 auto btime= value.MDate();
428 if( btime > value.CDate() ) btime= value.CDate();
429 if( btime > value.ADate() ) btime= value.ADate();
430 value.SetBDate( btime );
435 auto btime= value.MDate();
436 if( btime > value.CDate() ) btime= value.CDate();
437 if( btime > value.ADate() ) btime= value.ADate();
438 value.SetBDate( btime );
448 value.SetSize(
uinteger(STATMEMBER(size) ) );
451 value.SetOwner( STATMEMBER(uid) );
452 value.SetGroup( STATMEMBER(gid) );
455 value.SetQtyHardlinks( STATMEMBER(nlink) );
459 && (value.IsDirectory() || symLinkDest.
IsNotEmpty()) ) {
465 DBG_CHECKERRNO_WITH_PATH
470 goto APPLY_POST_RECURSION_FILTER;
476 if( !value.IsDirectory()
478 goto APPLY_POST_RECURSION_FILTER;
482 Log_Prune(
if( verboseLogables.Size() ) verboseLogables.Add(
" NO_AFS"); )
483 value.SetScanState(
FInfo::ScanStates::NO_AFS );
484 goto APPLY_POST_RECURSION_FILTER;
488 if( value.IsCrossingFS() && !params.CrossFileSystems ) {
489 Log_Prune(
if( verboseLogables.Size() ) verboseLogables.Add(
" NOT_CROSSING_FS"); )
490 value.SetScanState(
FInfo::ScanStates::NOT_CROSSING_FS );
491 goto APPLY_POST_RECURSION_FILTER;
495 if( depth >= params.MaxDepth ) {
496 Log_Prune(
if( verboseLogables.Size() && (¶ms != ¶msPathOnly) ) verboseLogables.Add(
" MAX_DEPTH_REACHED"); )
497 value.SetScanState(
FInfo::ScanStates::MAX_DEPTH_REACHED );
498 ++parentSums.QtyStopsOnMaxDepth;
499 goto APPLY_POST_RECURSION_FILTER;
504 && params.DirectoryFilterPreRecursion
505 && !params.DirectoryFilterPreRecursion->Includes( node, actPath ) )
507 Log_Prune(
if( verboseLogables.Size() ) verboseLogables.Add(
" FILTERED(Pre)"); )
508 goto APPLY_POST_RECURSION_FILTER;
512 value.SetScanState(
FInfo::ScanStates::RECURSIVE );
515 if ( value.Type() ==
FInfo::Types::SYMBOLIC_LINK_DIR ) {
517 || value.IsArtificialFS() ) {
518 value.SetScanState( FInfo::ScanStates::NOT_FOLLOWED );
519 goto APPLY_POST_RECURSION_FILTER;
523 value.SetScanState( FInfo::ScanStates::NO_AFS );
524 goto APPLY_POST_RECURSION_FILTER;
528 Path realTargetPath= actPath;
529 auto targetNode= node.
Parent();
530 Path sourcePath= value.GetLinkTarget();
532 MakeCanonical(sourcePath, targetNode, realTargetPath, resultPaths);
533 if(targetNode.IsInvalid()) {
534 node->SetScanState(FInfo::ScanStates::BROKEN_LINK);
536 File(targetNode).SetSymbolicParent(node.Export());
538 CPathString cRealTarget= realTargetPath.Terminate();
539 FInfo::DirectorySums childSums;
540 scanFilePosix(nullptr, targetNode, cRealTarget, depth + 1, params, currentDevice, parentSums,
541 realTargetPath, resultPaths );
545 value.SetSums(childSums);
546 parentSums+= childSums;
548 goto APPLY_POST_RECURSION_FILTER;
553 if( pxDir ==
nullptr ) {
555 actPath.SetLength(nameOrFullPath.Length());
557 if( actPath.Length() > 1 ) actPath << DIRECTORY_SEPARATOR;
558 actPath << nameOrFullPath;
564 fd= openat( dirfd(pxDir), nNameOrFullPath, O_RDONLY | O_DIRECTORY );
567 fd= open( nActPath , O_RDONLY | O_DIRECTORY );
571 DBG_CHECKERRNO_WITH_PATH
573 DIR* childDir = fdopendir(fd);
576 dirent* pxEntry = readdir(childDir);
577 if( pxEntry ==
nullptr ) {
588 "Posix raised ({}) \"{}\" on reading a directory which is not "
589 "located on an artificial filesystem (like /proc). File:\"{}\"",
590 errno, std::errc(errno), dbgActFile )
593 ALIB_ERROR(
"FILES",
"Posix raised ({}) \"{}\" on reading "
594 "directory \"{}\"", errno, std::errc(errno), dbgActFile )
602 if( pxEntry->d_name[0] ==
'.'
603 && ( pxEntry->d_name[1] ==
'\0'
604 || ( pxEntry->d_name[1] ==
'.'
605 && pxEntry->d_name[2] ==
'\0' ) ) )
609 auto childNode= node;
610 #if ALIB_CHARACTERS_WIDE
611 Path childName(
const_cast<const char*
>(&pxEntry->d_name[0]));
613 const CString childName(
const_cast<const char*
>(&pxEntry->d_name[0]));
615 childNode.GoToCreateChildIfNotExistent( childName );
616 scanFilePosix( childDir, childNode, childName,
617 depth + 1, params, currentDevice, subSums, actPath,
621 DBG_CHECKERRNO_WITH_PATH
626 parentSums+= value.Sums();
628 value.SetSums(subSums);
629 parentSums+= subSums;
632 goto APPLY_POST_RECURSION_FILTER;
637 "Internal error opening directory. This must never happen")
641 switch (
std::errc(errno)) {
642 case std::errc::permission_denied:
649 ALIB_ERROR(
"FILES",
"Unknown error {}(\"{}\") while opening directory \"{}\"",
650 errno, std::errc(errno), actPath)
658 APPLY_POST_RECURSION_FILTER:
662 if ( value.IsDirectory() ) {
667 && value.Sums().Count() == 0 )
670 Log_Prune(
if( verboseLogables.Size() ) { verboseLogables.Add(
" FILTERED(Post)");
671 Log_Verbose( verboseLogables )
672 verboseLogables.clear(); } )
673 parentSums-= value.Sums();
674 value.Sums()=
FInfo::DirectorySums();
677 if( node.HasChildren() && node.Tree<
FTree>().HasListeners() ) {
678 integer oldLen= actPath.Length();
679 auto it= node.FirstChild();
680 while ( it.IsValid() ) {
681 actPath << DIRECTORY_SEPARATOR << it.Name();
682 node.Tree<FTree>().Notify( FTreeListener::Event::DeleteNode, node, actPath );
683 it.GoToNextSibling();
684 actPath.ShortenTo(oldLen);
690 node.Tree<
FTree>().Notify( FTreeListener::Event::DeleteNode, node, actPath );
696 node.DeleteChildren();
706 node.Tree<
FTree>().Notify( FTreeListener::Event::DeleteNode, node, actPath );
708 Log_Prune(
if( verboseLogables.Size() ) { verboseLogables.Add(
" FILTERED(Post)");
709 Log_Verbose( verboseLogables ) } )
717 parentSums.Add(value);
718 if(node.Tree<
FTree>().HasListeners()) {
721 node.Tree<
FTree>().Notify( FTreeListener::Event::CreateNode, node, actPath );
725 DBG_CHECKERRNO_WITH_PATH
729#undef DBG_CHECKERRNO_WITH_PATH
730#undef TMP_STATX_AVAILABLE
738#if ALIB_FILES_FORCE_STD_SCANNER
739# pragma message ("ALIB_FILES_FORCE_STD_SCANNER given. Using std::filesystem for scanning. In file: " __FILE__ )
741# pragma message ("Unknown Platform. Using std::filesystem for scanning. In file: " __FILE__ )
744namespace fs = std::filesystem;
747#if defined(__APPLE__) || defined(_LIBCPP_VERSION) || defined(__ANDROID_NDK__)
750 template <
typename TP>
751 std::time_t to_time_t(TP tp)
753 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(tp - TP::clock::now()
754 + std::chrono::system_clock::now());
755 return std::chrono::system_clock::to_time_t(sctp);
760# define DBG_CHECKERRNO_WITH_PATH \
761 ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\". Current path: {}", \
762 errno, std::errc(errno), path.string() ) \
765# define DBG_CHECKERRNO_WITH_PATH
768namespace alib::files {
namespace {
770void scanFileStdFS(
const fs::path& path,
777#if defined(__MINGW32__)
778Path pathAsCString(path.c_str());
779pathAsCString.Terminate();
783const PathSubstring parentPath= pathAsCString.
Substring(0, pathAsCString.LastIndexOf(DIRECTORY_SEPARATOR));
787 && ( pathAsCString.Length()==1
788 || pathAsCString.CharAtEnd() != DIRECTORY_SEPARATOR)
789 && pathAsCString.IndexOf(
NString8(DIRECTORY_SEPARATOR).Append(DIRECTORY_SEPARATOR))<0,
790 "FILES",
"Given path not absolute or ending with '{}': {}", DIRECTORY_SEPARATOR, pathAsCString )
793 && pathAsCString.CharAt(2)== DIRECTORY_SEPARATOR
794 && ( pathAsCString.Length()==3
795 || pathAsCString.CharAtEnd() != DIRECTORY_SEPARATOR) )
797 || ( pathAsCString.CharAt(0)== DIRECTORY_SEPARATOR
798 && pathAsCString.CharAt(1)== DIRECTORY_SEPARATOR
799 && ( pathAsCString.Length()==2
800 || pathAsCString.CharAtEnd() != DIRECTORY_SEPARATOR) )
802 && pathAsCString.IndexOf( strings::TLocalString<PathCharType, 8>(
803 DIRECTORY_SEPARATOR).Append(DIRECTORY_SEPARATOR),
805 "FILES",
"Given path not absolute or ending with '{}': {}", DIRECTORY_SEPARATOR, pathAsCString )
809Log_Verbose(
"[{}] {}/{} {}", ¶ms != ¶msPathOnly ?
'>':
'P', depth,
810 params.
MaxDepth < (std::numeric_limits<unsigned>::max)()
815std::error_code errorCode;
816auto& value = node.Value();
817auto oldScanState= value.ScanState();
826 Path symLinkDestReal;
829 fs::file_status stats= fs::symlink_status(path);
834 "Unhandled error code invoking 'fs::symlink_status()': {} (\"{}\")\n"
835 " With file: \"{}\"",
836 errorCode.value(), errorCode.message(), pathAsCString )
839 goto APPLY_POST_RECURSION_FILTER;
845 bool origFileIsSymlink= (stats.type() == fs::file_type::symlink);
846 if( origFileIsSymlink
852 fs::path resolved= fs::read_symlink(path, errorCode);
856 switch( std::errc(errorCode.value()) )
857 {
case std::errc::no_such_file_or_directory:
858 case std::errc::permission_denied:
864 "Unhandled error code invoking 'fs::read_symlink()': {} (\"{}\")\n"
865 " with file: ", errorCode.value(),
866 errorCode.message(), pathAsCString )
869 goto APPLY_POST_RECURSION_FILTER;
873 DBG_CHECKERRNO_WITH_PATH
874 symLinkDest << resolved.c_str();
878 if( resolved.is_absolute() )
879 realPath= fs::canonical(resolved, errorCode);
882 symLinkDestReal << pathAsCString;
884 symLinkDestReal << symLinkDest;
885 realPath= fs::canonical(fs::path(
886 std::basic_string_view<PathCharType>(symLinkDestReal.
Buffer(),
887 size_t(symLinkDestReal.
Length()))),
889 symLinkDestReal.
Reset();
891 ALIB_DBG(
if(errno==EINVAL && !errorCode) errno= 0;)
892 ALIB_DBG(
if(errno==ENOENT && !errorCode) errno= 0;)
895 if(errorCode)
switch( std::errc(errorCode.value()) )
900 default:
ALIB_ERROR(
"FILES",
"Unhandled error code invoking 'fs::canonical()': {} (\"{}\")\n"
901 " with file: ", errorCode.value(), errorCode.message(), pathAsCString )
905 DBG_CHECKERRNO_WITH_PATH
906 symLinkDestReal << realPath.c_str();
910 auto newStatus= fs::status(path, errorCode);
914 if(newStatus.type() != fs::file_type::unknown)
917 else switch( std::errc(errorCode.value()) )
922 "Unhandled error code invoking 'directory_entry::status()': {} (\"{}\")\n"
923 " With file: \"{}\"",
924 errorCode.value(), errorCode.message(), pathAsCString )
934 DBG_CHECKERRNO_WITH_PATH
942 if( origFileIsSymlink )
947 else switch( stats.type() )
957 case fs::file_type::not_found:
959 ALIB_WARNING(
"FILES",
"Internal error. 'not found' file type can't happen. File: ", pathAsCString )
960 ALIB_DBG( errno= 0;)
goto APPLY_POST_RECURSION_FILTER;
961 case fs::file_type::none :
963 ALIB_WARNING(
"FILES",
"Internal error. 'none' file type can't happen. File: ", pathAsCString)
964 ALIB_DBG( errno= 0;)
goto APPLY_POST_RECURSION_FILTER;
965 case fs::file_type::unknown :
967 ALIB_WARNING(
"FILES",
"Internal error. Can't happen. File: ", pathAsCString)
968 ALIB_DBG( errno= 0;)
goto APPLY_POST_RECURSION_FILTER;
971 ALIB_WARNING(
"FILES",
"Unknown fs::file_status::type '{}' with file {}.", stats.type(), pathAsCString)
972 ALIB_DBG( errno= 0;)
goto APPLY_POST_RECURSION_FILTER;
974 value.SetType( type );
984 auto fsTime= std::filesystem::file_time_type(std::filesystem::file_time_type::clock::now());
987 fsTime= fs::last_write_time( path, errorCode );
989 if(errorCode)
switch( std::errc(errorCode.value()) )
991 case std::errc::no_such_file_or_directory:
992 ALIB_ERROR(
"FILES",
"Internal error. This should never happen, checked above. "
993 "Undefined system error handling" )
ALIB_DBG( errno= 0;)
994 value.SetScanState(
FInfo::ScanStates::UNKNOWN_ERROR);
999 "Unhandled error code invoking 'fs::last_write_time()': {} (\"{}\")\n"
1000 " With file \"{}\".", errorCode.value(), errorCode.message(), pathAsCString )
1001 fsTime= (decltype(fsTime)::min)();
ALIB_DBG( errno= 0;)
1008 #if defined(__APPLE__) || defined(_LIBCPP_VERSION) || defined(__ANDROID_NDK__)
1012 std::chrono::clock_cast<std::chrono::system_clock>(fsTime) ) ) );
1014 value.SetBDate( value.MDate() );
1015 value.SetCDate( value.MDate() );
1016 value.SetADate( value.MDate() );
1020 value.SetSize( symLinkDest.Length() > 0 ?
uinteger(symLinkDest.Length())
1027 switch( std::errc(errorCode.value()) )
1030 case std::errc::is_a_directory:
1033 case std::errc::no_such_file_or_directory:
1036 "Internal error. This should never happen. Undefined system error handling" )
1040 case
std::errc::operation_not_supported: break;
1042 "Unhandled error code invoking 'directory_entry::file_size()':{} (\"{}\")\n"
1043 " With file \"{}\".",
1044 errorCode.value(), errorCode.message(), pathAsCString )
ALIB_DBG( errno= 0;)
1051 value.SetOwner(
FInfo::UnknownID );
1052 value.SetGroup(
FInfo::UnknownID );
1055 uint32_t qtyHardLinks= uint32_t( fs::hard_link_count(path, errorCode ) );
1060 "Unhandled error code invoking 'fs::hard_link_count()': {} (\"{}\")\n"
1061 " With file: \"{}\"",
1062 errorCode.value(), errorCode.message(), pathAsCString )
1066 value.SetQtyHardlinks( qtyHardLinks );
1070 && (value.IsDirectory() || symLinkDest.IsNotEmpty()) )
1074 DBG_CHECKERRNO_WITH_PATH
1079 ++parentSums.QtyErrsBrokenLink;
1080 goto APPLY_POST_RECURSION_FILTER;
1084 if( !value.IsDirectory()
1086 goto APPLY_POST_RECURSION_FILTER;
1095 value.SetScanState( FInfo::ScanStates::NOT_CROSSING_FS );
1096 goto APPLY_POST_RECURSION_FILTER;
1102 value.SetScanState( FInfo::ScanStates::MAX_DEPTH_REACHED );
1103 ++parentSums.QtyStopsOnMaxDepth;
1104 goto APPLY_POST_RECURSION_FILTER;
1111 goto APPLY_POST_RECURSION_FILTER;
1119 if( params.LinkTreatment != ScanParameters::SymbolicLinks::RECURSIVE
1120 || value.IsArtificialFS() ) {
1121 value.SetScanState( FInfo::ScanStates::NOT_FOLLOWED );
1122 goto APPLY_POST_RECURSION_FILTER;
1126 Path realTargetPath= pathAsCString;
1127 auto targetNode= node.Parent();
1128 Path sourcePath= value.GetLinkTarget();
1130 MakeCanonical(sourcePath, targetNode, realTargetPath, resultPaths);
1131 if(targetNode.IsInvalid()) {
1132 node->SetScanState(FInfo::ScanStates::BROKEN_LINK);
1134 File(targetNode).SetSymbolicParent(node.Export());
1136 FInfo::DirectorySums childSums;
1137 scanFileStdFS( fs::path(std::basic_string_view<PathCharType>(realTargetPath.Buffer(),
1138 size_t(realTargetPath.Length()))),
1139 targetNode, depth + 1, params, childSums, resultPaths );
1143 value.SetSums(childSums);
1144 parentSums+= childSums;
1146 goto APPLY_POST_RECURSION_FILTER;
1151 fs::directory_iterator dit= fs::directory_iterator(path, errorCode);
1155 for(
const fs::directory_entry& childDir : dit )
1159 Path mingwBuf( childDir.path().c_str());
1164 childName.ConsumeChars(childName.LastIndexOf(DIRECTORY_SEPARATOR) + 1);
1165 auto childNode= node;
1166 childNode.GoToCreateChildIfNotExistent( childName );
1167 scanFileStdFS( childDir.path(), childNode, depth + 1, params, subSums,
1175 parentSums+= value.Sums();
1179 value.SetSums(subSums);
1180 parentSums+= subSums;
1183 goto APPLY_POST_RECURSION_FILTER;
1189 "Internal error opening directory. This must never happen" )
1192 if(errorCode) switch (
std::errc(errorCode.value()))
1194 case std::errc::invalid_argument:
1195 case std::errc::permission_denied:
1199 goto APPLY_POST_RECURSION_FILTER;
1202 ALIB_ERROR(
"FILES",
"Unknown error {}(\"{}\") while opening directory \"{}\"",
1203 errorCode.value(), errorCode.message(), pathAsCString)
1205 goto APPLY_POST_RECURSION_FILTER;
1211 APPLY_POST_RECURSION_FILTER:
1214 if( oldScanState ==
FInfo::ScanStates::
NONE ) {
1215 if ( value.IsDirectory() ) {
1217 && ( ( params.DirectoryFilterPostRecursion
1218 && !params.DirectoryFilterPostRecursion->Includes(node, parentPath ) )
1219 || ( params.RemoveEmptyDirectories
1220 && value.Sums().Count() == 0 )
1223 parentSums-= value.Sums();
1224 value.Sums()= FInfo::DirectorySums();
1227 if( node.HasChildren() && node.Tree<FTree>().HasListeners() ) {
1228 Path p= pathAsCString;
1229 auto it= node.FirstChild();
1230 while ( it.IsValid() ) {
1231 p.ShortenTo(pathAsCString.Length());
1232 p << DIRECTORY_SEPARATOR << it.Name();
1233 node.Tree<FTree>().Notify( FTreeListener::Event::DeleteNode, node, p );
1234 it.GoToNextSibling();
1238 if( params.RemoveEmptyDirectories )
1240 node.Tree<FTree>().Notify(FTreeListener::Event::DeleteNode, node, pathAsCString);
1247 node.DeleteChildren();
1252 node.Tree<
FTree>().Notify(FTreeListener::Event::DeleteNode, node, pathAsCString) ;
1260 parentSums.
Add(value);
1261 node.Tree<
FTree>().Notify( FTreeListener::Event::CreateNode, node, pathAsCString );
1268#undef DBG_CHECKERRNO_WITH_PATH
1274namespace alib::files {
1278enum class UnrealPathRootKinds : uint8_t {
1287struct UnrealPathRootInfo {
1288 UnrealPathRootKinds kind;
1293inline bool IsAsciiAlpha(PathCharType c) {
1294 return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z');
1297inline bool IsSchemeChar(PathCharType c) {
1298 return IsAsciiAlpha(c) || (c >=
'0' && c <=
'9') || c ==
'+' || c ==
'-' || c ==
'.';
1301inline bool IsAsciiNoCaseEqual(PathCharType c, PathCharType cmp) {
1302 return c == cmp || c == (cmp ^ 0x20);
1309UnrealPathRootInfo DetectAndNormalizeUnrealRoot(
Path& sourcePath,
1311 Path& pathToNode ) {
1312 UnrealPathRootInfo info{ UnrealPathRootKinds::Relative, 0,
PathSubstring() };
1319 if( sourcePath.
Length() >= 4
1320 && ( (sourcePath.
CharAt(0) ==
'\\' && sourcePath.
CharAt(1) ==
'\\')
1321 || (sourcePath.
CharAt(0) ==
'/' && sourcePath.
CharAt(1) ==
'/') )
1322 && sourcePath.
CharAt(2) ==
'.'
1323 && (sourcePath.
CharAt(3) ==
'\\' || sourcePath.
CharAt(3) ==
'/') ) {
1325 info.kind= UnrealPathRootKinds::Device;
1327 pathToNode.
Reset(DIRECTORY_SEPARATOR);
1329 if( sourcePath.
Length() >= 4
1330 && IsAsciiNoCaseEqual(sourcePath.
CharAt(0),
'U')
1331 && IsAsciiNoCaseEqual(sourcePath.
CharAt(1),
'N')
1332 && IsAsciiNoCaseEqual(sourcePath.
CharAt(2),
'C')
1333 && (sourcePath.
CharAt(3) ==
'\\' || sourcePath.
CharAt(3) ==
'/') ) {
1338 }
else if( sourcePath.
IsEmpty() ) {
1341 info.kind= UnrealPathRootKinds::Device;
1342 info.prefixLength= 4;
1350 if( schemeEnd > 0 && schemeEnd + 2 < sourcePath.
Length()
1351 && sourcePath.
CharAt(schemeEnd + 1) ==
'/'
1352 && sourcePath.
CharAt(schemeEnd + 2) ==
'/' ) {
1353 bool schemeValid= IsAsciiAlpha(sourcePath.
CharAt(0));
1354 for(
integer i= 1; schemeValid && i < schemeEnd; ++i )
1355 schemeValid= IsSchemeChar(sourcePath.
CharAt(i));
1357 info.kind= UnrealPathRootKinds::Url;
1358 info.prefixLength= schemeEnd + 3;
1359 info.scheme= sourcePath.
Substring(0, schemeEnd);
1365 if( sourcePath.
Length() >= 2 && IsAsciiAlpha(sourcePath.
CharAt(0))
1366 && sourcePath.
CharAt(1) ==
':' ) {
1367 info.kind= UnrealPathRootKinds::DriveLetter;
1368 info.prefixLength= 2;
1372 node.GoToCreateChildIfNotExistent(drive);
1374 pathToNode << drive;
1376 if( sourcePath.
Length() > 2
1377 && (sourcePath.
CharAt(2) ==
'/' || sourcePath.
CharAt(2) ==
'\\') ) {
1378 ++info.prefixLength;
1388 if( sourcePath.
Length() >= 2
1389 && ( (sourcePath.
CharAt(0) ==
'/' && sourcePath.
CharAt(1) ==
'/')
1390 || (sourcePath.
CharAt(0) ==
'\\' && sourcePath.
CharAt(1) ==
'\\') ) ) {
1391 info.kind= UnrealPathRootKinds::UNC;
1392 info.prefixLength= 2;
1401 ALIB_ASSERT_ERROR(absoluteEnd <= 1,
"FILES",
"Not implemented absolute path != directory separator"
1402 ". See note above! Path=", sourcePath)
1403 if(absoluteEnd==1) {
1404 info.kind= UnrealPathRootKinds::AbsoluteRoot;
1405 info.prefixLength= 1;
1408 pathToNode.
Reset(DIRECTORY_SEPARATOR);
1414makeCanonicalRecursion(
Path& sourcePath,
1420 auto rootInfo= DetectAndNormalizeUnrealRoot(sourcePath, node, pathToNode);
1421 if( rootInfo.kind == UnrealPathRootKinds::Url ) {
1423 node.GoToCreateChildIfNotExistent(rootInfo.scheme);
1427 pathToNode << rootInfo.scheme <<
A_CHAR(
"://");
1432 if( rootInfo.kind == UnrealPathRootKinds::Device ) {
1435 devName <<
A_CHAR(
"DEV");
1440 devName << ( (c ==
'/' || c ==
'\\') ?
PathCharType(
':') : c );
1443 node.GoToCreateChildIfNotExistent(devName);
1447 pathToNode << devName;
1464 if(name.Equals(
".") || name.Equals(
"/")) {
1468 if(name.Equals(
"..")) {
1472 node= node.Parent();
1479 isNew= node.GoToCreateChildIfNotExistent(name);
1480 if(pathToNode.
Length() > 1)
1484 }
while(!isNew && !node->IsSymbolicLink());
1489 #if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
1494 fullPathChildName= pathToNode;
1496 scanFilePosix(
nullptr, node, fullPathChildName,
1498 0, dummySums, pathToNode, resultPaths );
1500 scanFileStdFS( fs::path(std::basic_string_view<PathCharType>(pathToNode.
Buffer(),
1501 size_t(pathToNode.
Length()))),
1504 dummySums, resultPaths );
1507 auto quality= node->ScanState();
1512 sourcePath.
InsertAt(node.Name(), 0);
1532 if(node->IsSymbolicLink()) {
1535 Path unrealPathToSymlink= node->GetLinkTarget();
1536 auto targetNode= node.Parent();
1539 makeCanonicalRecursion(unrealPathToSymlink, targetNode, pathToNode, resultPaths, node );
1543 if(callingNode.IsValid())
1562 FInfo::ScanStates result= makeCanonicalRecursion(sourcePath, node, pathToNode, resultPaths,
1566 resultPaths->
Add(node);
1575 Path* remainingStart) {
1588 " F={} DPre={} DPost={} XFS={} AFS={} Depth={}",
1599 Path sourcePath(parameters.StartPath);
1600 if(!sourcePath.IsAbsolute()) {
1601 sourcePath.
Change(SystemFolders::Current);
1603 Log_Info(
"Changed relative start path to {}", sourcePath)
1608 Log_Prune(
auto firstResultPos= resultPaths->size(); ))
1615 if(remainingStart) remainingStart->
Reset(sourcePath);
1617 if( targetNode.IsInvalid() ) {
1618 Log_Info(
"Scan path not resolved. Unresolved portion {!Q}", realPath )
1622 #if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
1624 scanFilePosix(
nullptr, targetNode, fullPathChildName, 0 ,parameters, device, dummySums,
1625 realPath, resultPaths );
1627 scanFileStdFS( fs::path(std::basic_string_view<PathCharType>(realPath.
Buffer(),
1628 size_t(realPath.
Length()))),
1629 targetNode, 0, parameters, dummySums, resultPaths );
1632 Log_Info(
"Scan Results: ", resultPaths->size() - firstResultPos )
1634 for(
auto& it : *resultPaths ) {
1637 Log_Info(
" Path {}: {} {} (Q={} D={}/F={}}",
1645 return resultPaths && resultPaths->size() > 0 ? resultPaths->back()->ScanState()
1651 for(
auto existingIt= begin(); existingIt != end(); ++existingIt) {
1655 while( nodeParents.IsValid() ) {
1656 if( nodeParents == node ){
1660 nodeParents= nodeParents.Parent();
1664 nodeParents= node.Parent();
1665 while( nodeParents.IsValid() ) {
1666 if( nodeParents == *existingIt )
1668 nodeParents= nodeParents.Parent();
1671 insert(end(), node);
#define ALIB_MESSAGE(domain,...)
#define ALIB_ALLOW_SPARSE_ENUM_SWITCH
#define ALIB_CHARACTERS_WIDE
#define ALIB_WARNING(domain,...)
#define ALIB_ASSERT_WARNING(cond, domain,...)
#define ALIB_ERROR(domain,...)
#define ALIB_POP_ALLOWANCE
#define ALIB_ASSERT_ERROR(cond, domain,...)
#define Log_IsActive(result,...)
#define Log_SetDomain(...)
The entry type which is embedded in each tree node.
ScanStates
Per-entry information about how a node was scanned.
@ RECURSIVE
Follow symlink target strings.
@ STATS
Only stats (size, date, owner, etc.) read.
@ NO_ACCESS_DIR
Scanner failure due to limited access rights on a directory.
@ MAX_DEPTH_REACHED
Scanning stopped because maximum depth was reached.
@ RESOLVED
Read symlink target strings.
@ NO_ACCESS_SL
Scanner failure due to limited access rights on a symbolic link.
@ UNKNOWN_ERROR
Unknown scanner failure.
@ BROKEN_LINK
A symbolic link targets a non-existent file or directory.
@ NO_ACCESS_SL_TARGET
Scanner failure due to limited access rights on a symbolic link's target.
@ NO_ACCESS
Scanner failure due to limited access rights.
@ NOT_EXISTENT
Set if a given start path does not exist.
@ DIRECTORY
Directory/folder.
@ CHARACTER
A character special file.
@ BLOCK
A block special file.
@ FIFO
A FIFO (also known as pipe) file.
Permissions
Permission flags. Compatible with posix* definition.
@ NONE
no permission bits are set
void AllocateExtendedInfo(Cursor &node, const system::PathString &symLinkDest, const system::PathString &symLinkRealPath)
static void FixSums(Cursor directory)
void SetSymbolicParent(FTree::CursorHandle handle, bool overwrite=false)
TAString & InsertAt(const TString< TChar > &src, integer pos)
constexpr const TChar * Terminate() const
TAString & DeleteStart(integer regionLength)
TAString & ShortenTo(integer newLength)
integer DetectLength(integer offset=0)
constexpr integer Length() const
constexpr bool IsEmpty() const
TChar CharAtStart() const
integer IndexOf(TChar needle, integer startIdx=0) const
TChar CharAt(integer idx) const
constexpr bool IsNotEmpty() const
constexpr const TChar * Buffer() const
integer LastIndexOf(TChar needle, integer startIndex=MAX_LEN) const
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
integer IndexOfOrLength(TChar needle) const
PathString Parent() const
bool Change(const PathString &path)
static DateTime FromEpochSeconds(time_t epochSeconds)
void Import(TTimePoint timePoint)
String DBG_FILES_SCAN_VERBOSE_LOG_FORMAT
FInfo::ScanStates MakeCanonical(Path &sourcePath, FTree::Cursor &node, Path &pathToNode, CanonicalPathList *resultPaths=nullptr)
FInfo::ScanStates ScanFiles(FTree &tree, ScanParameters ¶meters, CanonicalPathList *resultPaths=nullptr, Path *remainingStart=nullptr)
platform_specific integer
@ Relative
Referring to a relative value.
strings::TCString< PathCharType > CPathString
The string-type used with this ALib Module.
strings::TSubstring< PathCharType > PathSubstring
The string-type used with this ALib Module.
std::filesystem::path::value_type PathCharType
constexpr PathCharType DIRECTORY_SEPARATOR
The standard path separator character. Defaults to '\' on Windows OS, '/' else.
monomem::TLocalAllocator< TCapacityInKB > LocalAllocator
Type alias in namespace alib.
strings::TCString< character > CString
Type alias in namespace alib.
strings::TCString< nchar > NCString
Type alias in namespace alib.
files::File File
Type alias in namespace alib.
LocalString< 8 > String8
Type alias name for #"TLocalString;TLocalString<character,8>".
lang::integer integer
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
system::Path Path
Type alias in namespace alib.
NLocalString< 8 > NString8
Type alias name for #"TLocalString;TLocalString<nchar,8>".
strings::TSubstring< nchar > NSubstring
Type alias in namespace alib.
LocalString< 128 > String128
Type alias name for #"TLocalString;TLocalString<character,128>".
files::FilesCamp FILES
The singleton instance of ALib Camp class #"FilesCamp".
boxing::TBoxes< MonoAllocator > BoxesMA
Type alias in namespace alib.
lang::uinteger uinteger
Type alias in namespace alib.
time::DateTime DateTime
Type alias in namespace alib.
#define ALIB_STRING_RESETTER(astring)
#define ALIB_STRINGS_TO_NARROW( src, dest, bufSize)
void Add(FTree::Cursor node)
virtual bool Includes(const File &file, const system::PathString &parentPath)=0
Recursively accumulated values for directories.
uint32_t QtyErrsAccess
Number of access errors in the folder and subfolders.
uint32_t QtyErrsBrokenLink
Number of broken symbolic links in the directory and its subfolders.
constexpr DirectorySums & Add(const FInfo &finfo) noexcept
Input parameters to function #"ScanFiles(FTree&)".
unsigned MaxDepth
The maximum recursion depth. Defaults to #"InfiniteRecursion".
SPFileFilter DirectoryFilterPreRecursion
static constexpr unsigned InfiniteRecursion
Denotes 'infinite' recursion if set to field #"MaxDepth".
SymbolicLinks LinkTreatment
Denotes how symbolic links are treated.
@ DONT_RESOLVE
Demands not to resolve symbolic links in any way.
@ RESOLVE_BUT_DONT_FOLLOW
Path StartPath
The path to be scanned.
SPFileFilter DirectoryFilterPostRecursion
bool RemoveEmptyDirectories