20 bool startScan(
FTree& tree,
23 FInfo::DirectorySums& parentSums,
24 std::vector<ResultsPaths>& resultPaths);
33#if ( ( defined(__GLIBCXX__) && !defined(__MINGW32__) ) \
34 || defined(__APPLE__) \
35 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
39# if !defined(__APPLE__)
40# include <sys/sysmacros.h>
42# include <sys/types.h>
50# define DBG_CHECKERRNO \
51 ALIB_ASSERT_WARNING(errno == 0, "CAMP/FILES", "Errno set ({}){!Q}.", \
52 errno, SystemErrors(errno) ) \
54# define DBG_CHECKERRNO_WITH_PATH \
55 ALIB_ASSERT_WARNING(errno == 0, "CAMP/FILES", "Errno set ({}){!Q}. Current path: {}", \
56 errno, SystemErrors(errno), actPath ) \
59# define DBG_CHECKERRNO
60# define DBG_CHECKERRNO_WITH_PATH
64void scanFilePosix( DIR* pxDir,
69 decltype(stat::st_dev) currentDevice,
70 FInfo::DirectorySums& parentSums ,
72 std::vector<ResultsPaths>& resultPaths)
75 && ( actPath.Length()==1
78 "CAMP/FILES",
"Given path not absolute or ending with '{}': {}",
DirectorySeparator, actPath )
80 if( actPath.Buffer() == nameOrFullPath.Buffer() )
81 dbgActFile << nameOrFullPath;
84 dbgActFile << actPath;
85 if(dbgActFile.Length()>1)
87 dbgActFile << nameOrFullPath;
98 auto& value = node.Value();
99 auto oldQuality= value.Quality();
112 int statResult= pxDir ? fstatat(dirfd(pxDir), nameOrFullPath, &stats, AT_SYMLINK_NOFOLLOW
113 #
if !defined(__APPLE__)
117 : lstat ( nameOrFullPath, &stats );
121 ALIB_ASSERT_WARNING( errno != ENOENT,
"CAMP/FILES",
"File does not exist (anymore) while stating {!Q}",
123 ALIB_ASSERT_WARNING( errno == ENOENT,
"CAMP/FILES",
"Unknown error ({}) {!Q} while stating file {!Q}",
129 DBG_CHECKERRNO_WITH_PATH
132 if( currentDevice == 0)
133 currentDevice= stats.st_dev;
134 else if( currentDevice != stats.st_dev )
136 value.SetCrossingFS();
137 currentDevice= stats.st_dev;
140 if( major(stats.st_dev) == 0
141 && minor(stats.st_dev) != 35 )
142 value.SetArtificialFS();
146 bool origFileIsSymlink= (stats.st_mode & S_IFMT) == S_IFLNK;
147 if( origFileIsSymlink
153 ssize_t cntChars= pxDir ? readlinkat( dirfd(pxDir), nameOrFullPath, symLinkDest.VBuffer(), PATH_MAX)
154 : readlink ( nameOrFullPath, symLinkDest.VBuffer(), PATH_MAX);
156 if (cntChars == -1)
switch(errno)
163 "Posix raised ({}) {!Q} on reading a symbolic link which is not located on an "
164 "artificial filesystem (like /proc). File:{!Q}",
169 ALIB_ERROR(
"CAMP/FILES",
"Posix raised ({}) {!Q} on reading symbolic link {!Q}",
173 symLinkDest.SetLength(cntChars);
181 *symLinkDestReal.VBuffer()=
'\0';
182 if(! realpath(actPath.Terminate(), symLinkDestReal.VBuffer() ) )
switch (errno)
184 case ENOENT:
if( *symLinkDestReal.VBuffer() !=
'\0')
185 symLinkDestReal.DetectLength();
193 default:
ALIB_ERROR(
"CAMP/FILES",
"Posix raised ({}) {!Q} on resolving symbolic link {!Q}",
197 symLinkDestReal.DetectLength();
199 ALIB_DBG(
if( errno == EINVAL) errno= 0;)
200 DBG_CHECKERRNO_WITH_PATH
202 "CAMP/FILES",
"Real path is not absolute: ", symLinkDestReal )
205 DBG_CHECKERRNO_WITH_PATH
206 statResult= stat(symLinkDestReal.Terminate(), &stats );
207 DBG_CHECKERRNO_WITH_PATH
209 if(statResult == -1 )
217 "Unhandled error code invoking 'stat()' on resolved symbolic link: {} ({!Q})\n"
218 " Symbolic link target: {!Q}", errno,
SystemErrors(errno), dbgActFile )
227 if( major(stats.st_dev) == 0
228 && minor(stats.st_dev) != 35 )
229 value.SetTargetArtificialFS();
234 DBG_CHECKERRNO_WITH_PATH
239 auto posixType= stats.st_mode & S_IFMT;
240 if( origFileIsSymlink )
245 else switch(stats.st_mode & S_IFMT )
249 case S_IFBLK : type=
FInfo::Types::BLOCK ; break;
250 case S_IFCHR : type=
FInfo::Types::CHARACTER ; break;
251 case S_IFDIR : type=
FInfo::Types::DIRECTORY ; break;
252 case S_IFIFO : type=
FInfo::Types::FIFO ; break;
253 case S_IFREG : type=
FInfo::Types::REGULAR ; break;
254 case S_IFSOCK: type=
FInfo::Types::SOCKET ; break;
255 default:
ALIB_ERROR("CAMP/
FILES", "Internal error. 'unknown' file type can't happen. File: {!Q}
",
258 value.SetType( type );
262 value.SetPerms( FInfo::Permissions(stats.st_mode & int32_t(FInfo::Permissions::MASK)) );
266 #if !defined(__APPLE__)
267 # define st_mtime_name st_mtim
268 # define st_ctime_name st_ctim
269 # define st_atime_name st_atim
271 # define st_mtime_name st_mtimespec
272 # define st_ctime_name st_ctimespec
273 # define st_atime_name st_atimespec
277 std::chrono::system_clock::time_point {
278 std::chrono::duration_cast<std::chrono::system_clock::duration>(
279 std::chrono::seconds {stats.st_mtime_name.tv_sec }
280 + std::chrono::nanoseconds{stats.st_mtime_name.tv_nsec} ) } );
284 std::chrono::system_clock::time_point {
285 std::chrono::duration_cast<std::chrono::system_clock::duration>(
286 std::chrono::seconds {stats.st_ctime_name.tv_sec }
287 + std::chrono::nanoseconds{stats.st_ctime_name.tv_nsec} ) } );
291 std::chrono::system_clock::time_point {
292 std::chrono::duration_cast<std::chrono::system_clock::duration>(
293 std::chrono::seconds {stats.st_atime_name.tv_sec }
294 + std::chrono::nanoseconds{stats.st_atime_name.tv_nsec} ) } );
301 value.SetSize( uinteger(stats.st_size ) );
304 value.SetOwner( stats.st_uid );
305 value.SetGroup( stats.st_gid );
307 // 6. Add extended information
308 if( oldQuality < FInfo::Qualities::STATS
309 && (value.IsDirectory() || symLinkDest.IsNotEmpty()) )
310 static_cast<FTree&>(node.Tree()).AllocateExtendedInfo( node, symLinkDest, symLinkDestReal );
312 } // if scan stats (quality was just path)
314 DBG_CHECKERRNO_WITH_PATH
316 // Count broken link.
317 if(value.Quality() == FInfo::Qualities::BROKEN_LINK)
319 ++parentSums.QtyErrsBrokenLink;
323 // ------------------------------ recursion with directories? ------------------------------
324 if( !value.IsDirectory()
325 || value.Quality() >= FInfo::Qualities::RECURSIVE )
328 // stop recursion due to artificial fs?
329 if( value.IsArtificialFS() && !params.IncludeArtificialFS )
331 value.SetQuality( FInfo::Qualities::NO_AFS );
335 // stop recursion due to crossing filesystem?
336 if( value.IsCrossingFS() && !params.CrossFileSystems )
338 value.SetQuality( FInfo::Qualities::NOT_CROSSING_FS );
342 // stop recursion due to max depth?
343 if( depth >= params.MaxDepth )
345 value.SetQuality( FInfo::Qualities::MAX_DEPTH_REACHED );
346 ++parentSums.QtyStopsOnMaxDepth;
350 // stop recursion due to filter
352 && params.DirectoryFilterPreRecursion
353 && !params.DirectoryFilterPreRecursion->Includes( node, actPath ) )
356 // mark as recursively scanned
357 value.SetQuality( FInfo::Qualities::RECURSIVE );
360 if ( value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR )
362 if( params.LinkTreatment != ScanParameters::SymbolicLinks::RECURSIVE
363 || value.IsArtificialFS() ) // never recurse with symlinks RESIDING on artificial fs!
365 value.SetQuality( FInfo::Qualities::NOT_FOLLOWED );
369 if( value.TargetIsArtificialFS() && !params.IncludeArtificialFS )
371 value.SetQuality( FInfo::Qualities::NO_AFS );
375 // recurse into symlink target
376 FInfo::DirectorySums childSums;
377 if( startScan( static_cast<FTree&>(node.Tree()), value.GetRealLinkTarget(), params, childSums, resultPaths) )
378 value.SetQuality(FInfo::Qualities::DUPLICATE);
379 value.SetSums( childSums );
380 parentSums+= childSums;
385 // DIRECTORY RECURSION
386 {ALIB_STRING_RESETTER( actPath );
387 if( pxDir == nullptr )
389 ALIB_ASSERT_ERROR( actPath.Buffer() == nameOrFullPath.Buffer(), "CAMP/
FILES", "Internal error
" )
390 actPath.SetLength(nameOrFullPath.Length());
394 if( actPath.Length() > 1 ) actPath << DirectorySeparator;
395 actPath << nameOrFullPath;
399 int fd= pxDir ? openat( dirfd(pxDir), nameOrFullPath, O_RDONLY | O_DIRECTORY )
400 : open ( actPath , O_RDONLY | O_DIRECTORY );
402 if (fd != -1) // success?
404 DBG_CHECKERRNO_WITH_PATH
405 FInfo::DirectorySums subSums;
406 DIR* childDir = fdopendir(fd);
410 dirent* pxEntry = readdir(childDir);
411 if( pxEntry == nullptr )
415 // possible errors (according to documentation):
416 // EOVERFLOW One of the values in the structure to be returned cannot be represented correctly.
417 // EBADF The dirp argument does not refer to an open directory stream.
418 // ENOENT The current position of the directory stream is invalid.
420 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_DIR);
422 case EINVAL: value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR);
423 ALIB_ASSERT_ERROR(major(currentDevice) == 0, "CAMP/
FILES",
424 "Posix raised ({}) {!Q} on reading a directory which is not located on an
"
425 "artificial filesystem (like /proc). File:{!Q}
",
426 errno, SystemErrors(errno), dbgActFile )
428 default: value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
429 ALIB_ERROR("CAMP/
FILES", "Posix raised ({}) {!Q} on reading directory {!Q}
",
430 errno, SystemErrors(errno), dbgActFile )
438 if( pxEntry->d_name[0] == '.'
439 && ( pxEntry->d_name[1] == '\0'
440 || ( pxEntry->d_name[1] == '.'
441 && pxEntry->d_name[2] == '\0' ) ) )
445 auto childNode= node;
446 const CString childName(const_cast<const char*>(&pxEntry->d_name[0]));
447 childNode.GoToCreateChildIfNotExistent( childName );
448 scanFilePosix(childDir, childNode, childName,
449 depth + 1, params, currentDevice, subSums, actPath, resultPaths);
452 DBG_CHECKERRNO_WITH_PATH
454 // previously scanned in lower quality?
455 if( oldQuality != FInfo::Qualities::NONE )
457 FTree::FixSums( node );
458 parentSums+= value.Sums();
462 value.SetSums(subSums);
463 parentSums+= subSums;
467 } // success opening director
469 // error with recursion
470 ALIB_ASSERT_ERROR(errno != ENOTDIR, "CAMP/
FILES",
471 "Internal error opening directory. This must never happen
")
474 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
475 switch (SystemErrors(errno))
477 case SystemErrors::eacces:
478 ++parentSums.QtyErrsAccess;
479 value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR );
484 ALIB_ERROR("CAMP/
FILES", "Unknown error {}({!Q})
while opening directory {!Q}
",
485 errno, SystemErrors(errno), actPath)
486 value.SetQuality( FInfo::Qualities::UNKNOWN_ERROR );
491 // ------------------------------------ Apply Filter ------------------------------------------
493 // delete node only if this was a new scan. It must not be deleted, if this node was
494 // created as a path.
495 if( oldQuality == FInfo::Qualities::NONE )
497 if ( value.IsDirectory() )
500 && ( ( params.DirectoryFilterPostRecursion
501 && !params.DirectoryFilterPostRecursion->Includes(node, actPath ) )
502 || ( params.RemoveEmptyDirectories
503 && value.Sums().Count() == 0 )
506 parentSums-= value.Sums();
507 if( params.RemoveEmptyDirectories )
513 // do not return here. Still count the type below
514 node.DeleteChildren();
520 if ( params.FileFilter
521 && !params.FileFilter->Includes(node, actPath ) )
530 parentSums.Add(value);
532 ALIB_WARNINGS_RESTORE
533 DBG_CHECKERRNO_WITH_PATH
535}} // namespace [alib::files::anonymous]
536#undef DBG_CHECKERRNO_WITH_PATH
539//--------------------------------------------------------------------------------------------------
540//--- UNKNOWN platform, using C++17 filesystem (not all functionality given)
541//--------------------------------------------------------------------------------------------------
543# pragma message ("Unknown Platform. Using std::filesystem
for scanning. In file:
" __FILE__ )
546//#if !defined(TOOD_REMOVE_THIS_WITH_THE_ENDIF_ABOVE)
548#include "alib/compatibility/std_strings.hpp
"
550namespace fs = std::filesystem;
553// Note: MacOS is currently (as of 231210) missing C++ 20 library features in the area of std::clock
554#if ALIB_CPP_STANDARD == 17 || defined(__APPLE__) || defined(__ANDROID_NDK__)
557 template <typename TP>
558 std::time_t to_time_t(TP tp)
560 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(tp - TP::clock::now()
561 + std::chrono::system_clock::now());
562 return std::chrono::system_clock::to_time_t(sctp);
567# define DBG_CHECKERRNO \
568 ALIB_ASSERT_WARNING(errno == 0, "CAMP/
FILES", "Errno set ({}){!Q}.
", \
569 errno, SystemErrors(errno) ) \
571# define DBG_CHECKERRNO_WITH_PATH \
572 ALIB_ASSERT_WARNING(errno == 0, "CAMP/
FILES", "Errno set ({}){!Q}. Current path: {}
", \
573 errno, SystemErrors(errno), path.string() ) \
576# define DBG_CHECKERRNO
577# define DBG_CHECKERRNO_WITH_PATH
580namespace alib::files { namespace {
582void scanFileStdFS( const fs::path& path,
585 ScanParameters& params,
586 FInfo::DirectorySums& parentSums,
587 std::vector<ResultsPaths>& resultPaths )
589#if defined(__MINGW32__)
590 PathString pathAsCString(path.c_str());
591 pathAsCString.Terminate();
593 CString pathAsCString(path.c_str());
595 Substring parentPath= pathAsCString.Substring(0, pathAsCString.LastIndexOf(DirectorySeparator));
598 ALIB_ASSERT_ERROR( pathAsCString.CharAtStart()== DirectorySeparator
599 && ( pathAsCString.Length()==1
600 || pathAsCString.CharAtEnd() != DirectorySeparator)
601 && pathAsCString.IndexOf(String8(DirectorySeparator).Append(DirectorySeparator))<0,
602 "CAMP/
FILES","Given path not absolute or ending with
'{}': {}
", DirectorySeparator, pathAsCString )
604 ALIB_ASSERT_ERROR( ( ( pathAsCString.CharAt(1)== ':'
605 && pathAsCString.CharAt(2)== DirectorySeparator
606 && ( pathAsCString.Length()==3
607 || pathAsCString.CharAtEnd() != DirectorySeparator) )
609 || ( pathAsCString.CharAt(0)== DirectorySeparator
610 && pathAsCString.CharAt(1)== DirectorySeparator
611 && ( pathAsCString.Length()==2
612 || pathAsCString.CharAtEnd() != DirectorySeparator) )
614 && pathAsCString.IndexOf(String8(DirectorySeparator).Append(DirectorySeparator), 2)<0,
615 "CAMP/
FILES","Given path not absolute or ending with
'{}': {}
", DirectorySeparator, pathAsCString )
619 ALIB_MESSAGE( "CAMP/
FILES","[{}] {}/{} {}
", ¶ms != ¶msPathOnly ? '>':'P', depth,
620 params.MaxDepth < (std::numeric_limits<unsigned int>::max)()
621 ? String128(params.MaxDepth)
622 : String(A_CHAR("M
")),
625 std::error_code errorCode;
626 auto& value = node.Value();
627 auto oldQuality= value.Quality();
629 // ------------------------------ get stats? ------------------------------
630 if( value.Quality() == FInfo::Qualities::NONE
631 || ( value.Quality() == FInfo::Qualities::STATS
632 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE ) )
634 value.SetQuality( FInfo::Qualities::STATS );
635 PathString symLinkDest;
636 PathString symLinkDestReal;
638 // read base stats (we have to use symlink_status() which does NOT follow the symlink!)
639 fs::file_status stats= fs::symlink_status(path);
640 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
643 ALIB_ERROR("CAMP/
FILES",
644 "Unhandled error code invoking
'directory_entry::symlink_status()': {} ({!Q})\n
"
646 errorCode.value(), errorCode.message(), pathAsCString )
648 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
651 ALIB_WARNINGS_RESTORE
654 //------------ is symlink? ------------
655 bool origFileIsSymlink= (stats.type() == fs::file_type::symlink);
656 if( origFileIsSymlink
657 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE )
659 value.SetQuality( FInfo::Qualities::RESOLVED );
661 // 1. Read plain symlink target (only to be attached to the entry)
662 fs::path resolved= fs::read_symlink(path, errorCode);
665 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
666 switch( SystemErrors(errorCode.value()) )
667 { case SystemErrors::enoent: // happens with /proc files
668 case SystemErrors::eacces: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL);
671 default: ALIB_ERROR("CAMP/
FILES", "Unhandled error code invoking
'fs::read_symlink()': {} ({!Q})\n
"
672 " with file:
", errorCode.value(), errorCode.message(), pathAsCString )
674 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
677 ALIB_WARNINGS_RESTORE
679 DBG_CHECKERRNO_WITH_PATH
680 symLinkDest << resolved.c_str();
682 // 2. Read symlink's real target path (fully and recursively translated)
684 if( resolved.is_absolute() )
685 realPath= fs::canonical(resolved, errorCode);
688 symLinkDestReal << pathAsCString;
689 symLinkDestReal.ShortenTo( symLinkDestReal.LastIndexOf(DirectorySeparator) + 1);
690 symLinkDestReal << symLinkDest;
691 realPath= fs::canonical(fs::path(
692 std::basic_string_view<character>(symLinkDestReal.Buffer(),
693 size_t(symLinkDestReal.Length()))),
695 symLinkDestReal.Reset();
697 ALIB_DBG(if(errno==EINVAL && !errorCode) errno= 0;) // this happens!, we do not care, but clean up
698 ALIB_DBG(if(errno==ENOENT && !errorCode) errno= 0;)
700 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
701 if(errorCode) switch( SystemErrors(errorCode.value()) )
702 { // we ignore this: std::fs would not create the "real path
" if the final directory is not accessible.
703 case SystemErrors::eacces: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL_TARGET); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
704 case SystemErrors::enoent: value.SetQuality(FInfo::Qualities::BROKEN_LINK); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
705 case SystemErrors::eloop: value.SetQuality(FInfo::Qualities::CIRCULAR_LINK); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
706 default: ALIB_ERROR("CAMP/
FILES", "Unhandled error code invoking
'fs::canonical()': {} ({!Q})\n
"
707 " with file:
", errorCode.value(), errorCode.message(), pathAsCString )
710 ALIB_WARNINGS_RESTORE
711 DBG_CHECKERRNO_WITH_PATH
712 symLinkDestReal << realPath.c_str();
714 // 3. get resolved status
715 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
716 auto newStatus= fs::status(path, errorCode);
719 // this happens with strange /proc files...
720 if(newStatus.type() != fs::file_type::unknown)
723 else switch( SystemErrors(errorCode.value()) )
724 { case SystemErrors::eperm: value.SetQuality( FInfo::Qualities::NO_ACCESS ); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
725 case SystemErrors::enoent: value.SetQuality( FInfo::Qualities::BROKEN_LINK ); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
726 case SystemErrors::eloop: value.SetQuality( FInfo::Qualities::CIRCULAR_LINK); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
727 default: ALIB_WARNING("CAMP/
FILES",
728 "Unhandled error code invoking
'directory_entry::status()': {} ({!Q})\n
"
729 " With file: {!Q}
", errorCode.value(), errorCode.message(), pathAsCString )
732 ALIB_WARNINGS_RESTORE
734 // check for target artificial fs
735 // Not available with std::filesystem version
739 DBG_CHECKERRNO_WITH_PATH
743 auto type= FInfo::Types::UNKNOWN_OR_ERROR;
744 if( origFileIsSymlink )
746 type= is_directory(stats) ? FInfo::Types::SYMBOLIC_LINK_DIR
747 : FInfo::Types::SYMBOLIC_LINK;
749 else switch( stats.type() )
751 case fs::file_type::directory: type= FInfo::Types::DIRECTORY ; break;
752 case fs::file_type::regular : type= FInfo::Types::REGULAR ; break;
753 case fs::file_type::symlink : type= FInfo::Types::SYMBOLIC_LINK; break; // for now, this is a file.
754 case fs::file_type::block : type= FInfo::Types::BLOCK ; break;
755 case fs::file_type::character: type= FInfo::Types::CHARACTER ; break;
756 case fs::file_type::fifo : type= FInfo::Types::FIFO ; break;
757 case fs::file_type::socket : type= FInfo::Types::SOCKET ; break;
759 case fs::file_type::not_found:
760 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
761 ALIB_WARNING("CAMP/
FILES", "Internal error.
'not found' file type can
't happen. File: ", pathAsCString )
762 ALIB_DBG( errno= 0;) goto APPLY_FILTER;
763 case fs::file_type::none :
764 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
765 ALIB_WARNING("CAMP/FILES", "Internal error. 'none
' file type can't happen. File:
", pathAsCString)
766 ALIB_DBG( errno= 0;) goto APPLY_FILTER;
767 case fs::file_type::unknown :
768 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
769 ALIB_WARNING("CAMP/
FILES", "Internal error. Can
't happen. File: ", pathAsCString)
770 ALIB_DBG( errno= 0;) goto APPLY_FILTER;
772 value.SetType( type );
776 value.SetPerms( FInfo::Permissions(int32_t(stats.permissions())) );
779 // attn: This method always follows symbolic link and uses the target's time
782 auto fsTime= std::filesystem::file_time_type(std::filesystem::file_time_type::clock::now());
785 fsTime= fs::last_write_time( path, errorCode );
789 case SystemErrors::enoent:
ALIB_ERROR(
"CAMP/FILES",
790 "Internal error. This should never happen, checked above. "
791 "Undefined system error handling" )
ALIB_DBG( errno= 0;)
792 value.SetQuality(
FInfo::Qualities::UNKNOWN_ERROR);
795 default:
ALIB_ERROR( "CAMP/
FILES", "Unhandled error code invoking 'fs::last_write_time()': {} ({!Q})\n
"
796 " With file {!Q}.
", errorCode.value(), errorCode.message(), pathAsCString )
797 fsTime= (decltype(fsTime)::min)(); ALIB_DBG( errno= 0;)
800 ALIB_WARNINGS_RESTORE
804 #if ALIB_CPP_STANDARD == 17 || defined(__APPLE__) || defined(__ANDROID_NDK__)
805 value.SetMTime( DateTime::FromEpochSeconds( to_time_t( fsTime ) ) );
807 value.SetMTime( DateTime::FromEpochSeconds( std::chrono::system_clock::to_time_t(
808 std::chrono::clock_cast<std::chrono::system_clock>(fsTime) ) ) );
814 value.SetSize( symLinkDest.Length() > 0 ? uinteger(symLinkDest.Length())
815 : value.Quality() <= FInfo::Qualities::RESOLVED ? uinteger(fs::file_size(path, errorCode))
817 if( value.Size() == uinteger(-1))
820 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
821 switch( SystemErrors(errorCode.value()) )
823 // target is a directory (no error)
824 case SystemErrors::eisdir:
827 case SystemErrors::enoent: // this happens if we have a broken symbolic link
828 ALIB_ASSERT_ERROR( value.Type() == FInfo::Types::SYMBOLIC_LINK
829 || value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR , "CAMP/
FILES",
830 "Internal error. This should never happen. Undefined system error handling
" )
833 // size not supported. Happens with sockets, files in /proc, etc
834 case SystemErrors::eopnotsupp: break;
835 default: ALIB_ERROR("CAMP/
FILES", "Unhandled error code invoking
'directory_entry::file_size()':{} ({!Q})\n
"
836 " With file {!Q}.
", errorCode.value(), errorCode.message(), pathAsCString )
840 ALIB_WARNINGS_RESTORE
844 value.SetOwner( FInfo::UnknownID );
845 value.SetGroup( FInfo::UnknownID );
847 // 6. Add extended information
848 if( oldQuality < FInfo::Qualities::STATS
849 && (value.IsDirectory() || symLinkDest.IsNotEmpty()) )
850 static_cast<FTree&>(node.Tree()).AllocateExtendedInfo( node, symLinkDest, symLinkDestReal );
852 } // if scan stats (quality was just path)
854 DBG_CHECKERRNO_WITH_PATH
856 // Count broken link.
857 if(value.Quality() == FInfo::Qualities::BROKEN_LINK)
859 ++parentSums.QtyErrsBrokenLink;
863 // ------------------------------ recursion with directories? ------------------------------
864 if( !value.IsDirectory()
865 || value.Quality() >= FInfo::Qualities::RECURSIVE )
869 // stop recursion due to artificial fs?
870 // Not supported with std::filesystem!
872 // stop recursion due to crossing filesystem?
873 if( value.IsCrossingFS() && !params.CrossFileSystems )
875 value.SetQuality( FInfo::Qualities::NOT_CROSSING_FS );
879 // stop recursion due to max depth?
880 if( depth >= params.MaxDepth )
882 value.SetQuality( FInfo::Qualities::MAX_DEPTH_REACHED );
883 ++parentSums.QtyStopsOnMaxDepth;
887 // stop recursion due to filter
889 && params.DirectoryFilterPreRecursion
890 && !params.DirectoryFilterPreRecursion->Includes( node, parentPath ) )
893 // mark as recursively scanned
894 value.SetQuality( FInfo::Qualities::RECURSIVE );
897 if ( value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR )
899 if( params.LinkTreatment != ScanParameters::SymbolicLinks::RECURSIVE
900 || value.IsArtificialFS() ) // never recurse with symlinks RESIDING on artificial fs!
902 value.SetQuality( FInfo::Qualities::NOT_FOLLOWED );
907 // recurse into symlink target
908 FInfo::DirectorySums childSums;
909 if( startScan( static_cast<FTree&>(node.Tree()), value.GetRealLinkTarget(), params, childSums, resultPaths) )
910 value.SetQuality(FInfo::Qualities::DUPLICATE);
911 value.SetSums( childSums );
912 parentSums+= childSums;
917 // DIRECTORY RECURSION
919 fs::directory_iterator dit= fs::directory_iterator(path, errorCode);
920 if(!errorCode) // success?
922 FInfo::DirectorySums subSums;
923 for( const fs::directory_entry& childDir : dit )
926 #if defined(__MINGW32__)
927 PathString mingwBuf( childDir.path().c_str());
928 Substring childName(mingwBuf);
930 Substring childName(CString(childDir.path().c_str()));
932 childName.ConsumeChars(childName.LastIndexOf(DirectorySeparator) + 1);
933 auto childNode= node;
934 childNode.GoToCreateChildIfNotExistent( childName );
935 scanFileStdFS(childDir.path(), childNode, depth + 1, params, subSums, resultPaths );
938 // previously scanned in lower quality?
939 if( oldQuality != FInfo::Qualities::NONE )
941 FTree::FixSums( node );
942 parentSums+= value.Sums();
946 value.SetSums(subSums);
947 parentSums+= subSums;
954 // error with recursion
955 ALIB_ASSERT_ERROR(errorCode.value() != ENOTDIR, "CAMP/
FILES",
956 "Internal error opening directory. This must never happen
")
958 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
959 if(errorCode) switch (SystemErrors(errorCode.value()))
961 case SystemErrors::einval: // happens with /proc
962 case SystemErrors::eacces: ++parentSums.QtyErrsAccess;
963 value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR );
967 default: value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
968 ALIB_ERROR("CAMP/
FILES", "Unknown error {}({!Q})
while opening directory {!Q}
",
969 errorCode.value(), SystemErrors(errorCode.value()), pathAsCString)
973 ALIB_WARNINGS_RESTORE
976 // ------------------------------------ Apply Filter ------------------------------------------
978 // delete node only if this was a new scan. It must not be deleted, if this node was
979 // created as a path.
980 if( oldQuality == FInfo::Qualities::NONE )
982 if ( value.IsDirectory() )
985 && ( ( params.DirectoryFilterPostRecursion
986 && !params.DirectoryFilterPostRecursion->Includes(node, parentPath ) )
987 || ( params.RemoveEmptyDirectories
988 && value.Sums().Count() == 0 )
991 parentSums-= value.Sums();
992 if( params.RemoveEmptyDirectories )
998 node.DeleteChildren(); // do not return here. Still count the directories
1004 if ( params.FileFilter
1005 && !params.FileFilter->Includes(node, parentPath ) )
1014 parentSums.Add(value);
1019}} // namespace [alib::files::anonymous]
1021#undef DBG_CHECKERRNO_WITH_PATH
1022#endif // std::fs version
1024//--------------------------------------------------------------------------------------------------
1026//--------------------------------------------------------------------------------------------------
1027namespace alib::files {
1031// Creates start path nodes and invokes scanFileXXX
1032bool startScan( FTree& tree,
1034 ScanParameters& params,
1035 FInfo::DirectorySums& parentSums,
1036 std::vector<ResultsPaths>& resultPaths )
1039 ALIB_ASSERT_ERROR( realPath.CharAtStart() == DirectorySeparator,
1040 "CAMP/
FILES", "Real path is not absolute:
", realPath )
1042 PathString path(DirectorySeparator);
1043 FTree::Cursor node = tree.Root();
1045 // travel any existing portion of the path
1046 Substring pathRemainder= node.GoToTraversedPath( realPath );
1047 path << realPath.Substring(1, realPath.Length() - pathRemainder.Length() - 1);
1049 ALIB_ASSERT_ERROR( ( realPath.CharAt(2) == DirectorySeparator
1050 && realPath.CharAt(1) == ':' )
1051 || ( realPath.CharAt(0) == DirectorySeparator
1052 && realPath.CharAt(1) == DirectorySeparator ),
1053 "CAMP/
FILES", "Real path is not absolute:
", realPath )
1056 Substring pathRemainder;
1057 FTree::Cursor node = tree.Root();
1058 if(realPath.CharAt(1) == ':')
1060 path << realPath.Substring(0,3);
1061 node.GoToCreateChildIfNotExistent(realPath.Substring(0,2));
1062 pathRemainder= node.GoToTraversedPath( realPath.Substring(3) );
1063 path << realPath.Substring(3, realPath.Length() - pathRemainder.Length() -3 );
1067 integer serverNameEnd= realPath.IndexOf( DirectorySeparator, 2);
1068 if( serverNameEnd < 0)
1069 serverNameEnd= realPath.Length();
1070 path << realPath.Substring(0, serverNameEnd);
1071 node.GoToCreateChildIfNotExistent(realPath.Substring(2, serverNameEnd - 2));
1072 pathRemainder= node.GoToTraversedPath( realPath.Substring(serverNameEnd) );
1073 path << realPath.Substring(serverNameEnd, realPath.Length() - pathRemainder.Length() -serverNameEnd );
1082 if( pathRemainder.IsEmpty() )
1084 // For directories, call scan just for the case of having 'higher' scan parameters
1085 if( node.Value().IsDirectory())
1088 #if ( (defined(__GLIBCXX__) && !defined(__MINGW32__)) \
1089 || defined(__APPLE__) \
1090 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
1093 CString fullPathChildName(path);
1094 path.SetLength(path.LastIndexOf(DirectorySeparator) );
1095 scanFilePosix( nullptr, node, fullPathChildName, 0, params, 0, parentSums, path, resultPaths );
1097 scanFileStdFS( fs::path(std::basic_string_view<character>(path.Buffer(), size_t(path.Length()))),
1098 node, 0, params, parentSums, resultPaths );
1101 //resultPaths.emplace_back(ResultsPaths(realPath, node, true));
1107 // did not exist already
1108 if( path.Length() > 1 )
1109 path.DeleteEnd<false>(1);
1111 Tokenizer tknzr( pathRemainder, DirectorySeparator );
1112 while(tknzr.HasNext())
1115 if( path.Length() != 1 )
1118 node= node.CreateChild(name);
1122 bool isLastPathElement= !tknzr.HasNext();
1123 if( isLastPathElement )
1124 parentSums= FInfo::DirectorySums(); // clear the sums, because only the results of the last element is used.
1126 auto detectNodeDeletion= node.Depth();
1128 #if ( (defined(__GLIBCXX__) && !defined(__MINGW32__)) \
1129 || defined(__APPLE__) \
1130 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
1132 if( path.IsEmpty() ) path << DirectorySeparator;
1133 CString fullPathChildName;
1135 // add node name to existing path and use same buffer for fullPathChildName!
1136 ALIB_STRING_RESETTER( path );
1137 if( path.Length() > 1 ) path << DirectorySeparator;
1138 path << node.Name();
1140 fullPathChildName= path;
1143 scanFilePosix( nullptr, node, fullPathChildName,
1144 0, isLastPathElement ? params : paramsPathOnly,
1145 0, parentSums, path, resultPaths );
1146 if( fullPathChildName.Length() == 1 ) path.Reset();
1147 else { if(path.Length() > 1) path << DirectorySeparator; path << name; }
1149 if( path.Length() != 1 ) path << DirectorySeparator << name;
1150 scanFileStdFS( fs::path(std::basic_string_view<character>(path.Buffer(), size_t(path.Length()))), node, 0,
1151 isLastPathElement ? params : paramsPathOnly,
1152 parentSums, resultPaths );
1153 if( path.Length() == 1 ) path.Reset();
1156 // if the just created node was not deleted during scan, add it to the result list
1157 if( isLastPathElement && (detectNodeDeletion == node.Depth() ) )
1158 resultPaths.insert(resultPaths.begin(), ResultsPaths(realPath, node, false));
1160 // Correct quality from max depth to stats
1161 if( !isLastPathElement && node.Value().Quality() == FInfo::Qualities::MAX_DEPTH_REACHED)
1162 node.Value().SetQuality(FInfo::Qualities::STATS);
1168} // namespace alib::files[::anonymous]
1170#endif // !defined(ALIB_DOX)
1172// --------------------------------------------------------------------------------------------------
1174//--------------------------------------------------------------------------------------------------
1175enum FInfo::Qualities ScanFiles( FTree& tree,
1176 ScanParameters& parameters,
1177 std::vector<ResultsPaths>& resultPaths )
1180 // todo: add some log statements, respectively replace current commented messages
1181 ALIB_DBG( if( alib::FILES.IsBootstrapped())
1183 Log_SetDomain( "ALIB/
FILES", Scope::Path)
1184 Log_SetDomain( "SCAN
" , Scope::Filename)
1189 PathString path(parameters.StartPath);
1190 PathString realPath;
1191 realPath.Terminate();
1194 #if ( (defined(__GLIBCXX__) && !defined(__MINGW32__)) \
1195 || defined(__APPLE__) \
1196 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
1197 if(!realpath(path.Terminate(), realPath.VBuffer() ) ) switch (errno)
1199 case EACCES: ALIB_DBG(errno= 0;) return FInfo::Qualities::NO_ACCESS;
1200 case ENOENT: ALIB_DBG(errno= 0;) return FInfo::Qualities::NOT_EXISTENT;
1201 case ELOOP: ALIB_DBG(errno= 0;) return FInfo::Qualities::CIRCULAR_LINK;
1202 default: ALIB_ERROR("CAMP/
FILES", "Posix raised ({}) {!Q} on resolving start path {!Q}
",
1203 errno, SystemErrors(errno), path ) ALIB_DBG(errno= 0;)
1204 return FInfo::Qualities::UNKNOWN_ERROR;
1206 realPath.DetectLength();
1209 std::error_code errorCode;
1210 fs::path fsRealPath= fs::canonical(fs::path(std::basic_string_view<character>(path.Buffer(),
1211 size_t(path.Length()))),
1213 ALIB_DBG(if(errno==EINVAL && !errorCode) errno= 0;) // this happens!, we do not care, but clean up
1214 ALIB_DBG(if(errno==ENOENT && !errorCode) errno= 0;)
1216 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
1217 if(errorCode) switch( SystemErrors(errorCode.value()) )
1218 { // we ignore this: std::fs would not create the "real path
" if the final directory is not accessible.
1219 case SystemErrors::eacces: return FInfo::Qualities::NO_ACCESS;
1220 case SystemErrors::enoent: return FInfo::Qualities::NOT_EXISTENT;
1221 case SystemErrors::eloop: return FInfo::Qualities::CIRCULAR_LINK;
1222 default: ALIB_ERROR("CAMP/
FILES", "std::filesystem raised ({}) {!Q} on resolving start path {!Q}
",
1223 errorCode.value(), errorCode.message(), path ) ALIB_DBG(errno= 0;)
1224 return FInfo::Qualities::UNKNOWN_ERROR;
1226 ALIB_WARNINGS_RESTORE
1227 realPath << fsRealPath.c_str();
1232 path.Reset(DirectorySeparator);
1234 ALIB_DBG( errno=0; )
1236 auto firstResultPos= resultPaths.size();
1237 FInfo::DirectorySums dummySums;
1238 startScan( tree, realPath, parameters, dummySums, resultPaths );
1239 return (*(resultPaths.begin() + int(firstResultPos))).Node.Value().Quality();
1242#undef DBG_CHECKERRNO
1245} // namespace [alib::files]
@ NONE
no permission bits are set
@ STATS
Only stats (size, time, owner, etc.) read.
@ RESOLVED
Read symlink target strings.
@ NO_ACCESS_SL
Scanner failure due to limited access rights.
@ 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.
#define ALIB_WARNING(...)
#define ALIB_STRING_RESETTER(astring)
#define ALIB_WARNINGS_RESTORE
#define ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
#define ALIB_ASSERT_ERROR(cond,...)
#define ALIB_ASSERT_WARNING(cond,...)
alib::strings::TLocalString< character, 512 > PathString
files::ScanParameters ScanParameters
Type alias in namespace alib.
constexpr nchar DirectorySeparator
LocalString< 8 > String8
Type alias name for TLocalString<character,8> .
strings::TCString< character > CString
Type alias in namespace alib.
files::FInfo FInfo
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
files::FTree FTree
Type alias in namespace alib.
lang::system::SystemErrors SystemErrors
Type alias in namespace alib.
@ DONT_RESOLVE
Demands not to resolve symbolic links in any way.