ALib C++ Library
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
fscanner.cpp
1//##################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software lbicense, see LICENSE.txt)
6//##################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14//========================================= Global Fragment ========================================
15
19#include <vector>
20#if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
21# include <unistd.h>
22# if defined(__linux__)
23# include <asm/unistd.h>
24# endif
25# include <dirent.h>
26# if defined(__linux__)
27# include <linux/stat.h>
28# endif
29# include <sys/stat.h>
30# if !defined(__APPLE__)
31# include <sys/sysmacros.h>
32# else
33# include <sys/types.h>
34# endif
35# include <pwd.h>
36# include <fcntl.h>
37# include <pwd.h>
38# include <grp.h>
39#else
40# include <filesystem>
41#endif
42//============================================== Module ============================================
43#if ALIB_C20_MODULES
44 module ALib.Files;
45 import ALib.Lang;
46 import ALib.Characters.Functions;
47 import ALib.Monomem;
48 import ALib.Strings;
49 import ALib.Strings.Tokenizer;
50 import ALib.System;
51# if ALIB_EXPRESSIONS
52 import ALib.Expressions;
53# endif
54# if ALIB_ALOX
55 import ALib.ALox;
56 import ALib.ALox.Impl;
57# endif
58#else
59# include "ALib.Lang.H"
61# include "ALib.Monomem.H"
62# include "ALib.Strings.H"
64# include "ALib.System.H"
65# include "ALib.Expressions.H"
66# include "ALib.ALox.H"
67# include "ALib.ALox.Impl.H"
68# include "ALib.Files.H"
69#endif
70//========================================== Implementation ========================================
71#if !DOXYGEN
73
74
75using namespace alib::system;
76
77namespace alib::files { namespace {
78
79 // forward declaration of startScan()
80bool startScan( FTree& tree,
81 PathString realPath,
82 ScanParameters& params,
83 FInfo::DirectorySums& parentSums,
84 std::vector<ResultsPaths>& resultPaths
85 IF_ALIB_THREADS(, SharedLock* lock ) );
86
87// scan parameters used with startScan to evaluate directory entries
88ScanParameters paramsPathOnly( nullptr, ScanParameters::SymbolicLinks::DONT_RESOLVE, 0, true, true );
89} // namespace alib::files[::anonymous]
90
91#if ALIB_DEBUG
93 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}");
94#endif
95
96} // namespace [alib::files]
97
98
99//--------------------------------------------------------------------------------------------------
100//--- scanFilePosix
101//--------------------------------------------------------------------------------------------------
102#if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
103
104#if ALIB_DEBUG
105# define DBG_CHECKERRNO \
106 ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\".", \
107 errno, SystemErrors(errno) ) \
108 errno= 0;
109# define DBG_CHECKERRNO_WITH_PATH \
110 ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\". Current path: {}", \
111 errno, SystemErrors(errno), actPath ) \
112 errno= 0;
113#else
114# define DBG_CHECKERRNO
115# define DBG_CHECKERRNO_WITH_PATH
116#endif
117
118// Since Kernel 4.11 Linux/glibc has "statx". We use it if available on the current platform.
119#if defined(__NR_statx)
120# define TMP_STATX_AVAILABLE 1
121# define STATMEMBER(Name) stats.stx_ ## Name
122# define STAT_DEV_MAJOR stats.stx_dev_major
123# define STAT_DEV_MINOR stats.stx_dev_minor
124#else
125# define TMP_STATX_AVAILABLE 0
126# define STATMEMBER(Name) stats.st_ ## Name
127# define STAT_DEV_MAJOR major(stats.st_dev)
128# define STAT_DEV_MINOR minor(stats.st_dev)
129#endif
130
131namespace alib::files { namespace {
132void scanFilePosix( DIR* pxDir,
133 FTree::Cursor& node,
134 const CPathString& nameOrFullPath, // if full path, this has the same buffer as actPath!
135 unsigned depth,
136 ScanParameters& params,
137 uint64_t currentDevice,
138 FInfo::DirectorySums& parentSums ,
139 Path& actPath,
140 std::vector<ResultsPaths>& resultPaths
141 IF_ALIB_THREADS(, SharedLock* lock) ) {
142ALIB_ASSERT_ERROR( actPath.CharAtStart()== DIRECTORY_SEPARATOR
143 && ( actPath.Length()==1
144 || actPath.CharAtEnd() != DIRECTORY_SEPARATOR )
145 && actPath.IndexOf(strings::TLocalString<system::PathCharType,4>(
146 DIRECTORY_SEPARATOR).Append(DIRECTORY_SEPARATOR)) < 0 ,
147 "FILES","Given path not absolute or ending with '{}': {}",
148 DIRECTORY_SEPARATOR, actPath )
149ALIB_DBG( Path dbgActFile;
150 if( actPath.Buffer() == nameOrFullPath.Buffer() )
151 dbgActFile << nameOrFullPath;
152 else {
153 dbgActFile << actPath;
154 if(dbgActFile.Length()>1)
155 dbgActFile << DIRECTORY_SEPARATOR;
156 dbgActFile << nameOrFullPath;
157 } )
159 LocalAllocator<1> verboseAllocator;
160 BoxesMA verboseLogables( verboseAllocator);
161 int verboseLoggers;
162 Log_IsActive(verboseLoggers, Verbosity::Verbose)
163 if( verboseLoggers ) {
164 verboseLogables.Add("{!AWidth:>} ");
165 if( &params == &paramsPathOnly )
166 verboseLogables.Add("PO"); // 'Path Only'
167 else {
168 auto& depthString= *verboseAllocator().New<String128>();
169 depthString << depth << DIRECTORY_SEPARATOR
170 << ( params.MaxDepth < std::numeric_limits<unsigned>::max()
171 ? String128(params.MaxDepth)
172 : String(A_CHAR("M")) );
173 verboseLogables.Add(depthString);
174 }
175
176 verboseLogables.Add(files::DBG_FILES_SCAN_VERBOSE_LOG_FORMAT, File(node) );
177 }
178)
179ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\" with current file: {}",
180 errno, SystemErrors(errno), dbgActFile )
181ALIB_DBG( errno= 0;)
182
183auto& value = *node;
184auto oldQuality= value.Quality();
185
186ALIB_STRINGS_TO_NARROW(nameOrFullPath, nNameOrFullPath, 512)
187
188 //------------------------------------------- get stats? -----------------------------------------
189if( value.Quality() == FInfo::Qualities::NONE
190 || ( value.Quality() == FInfo::Qualities::STATS
191 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE ) )
192{
193 value.SetQuality(FInfo::Qualities::STATS);
194 Path symLinkDest;
195 Path symLinkDestReal;
196
197 // read base stats
198 ALIB_DBG( errno= 0;)
199 #if TMP_STATX_AVAILABLE
200 struct statx stats;
201 int statResult= statx( pxDir ? dirfd(pxDir) : 0,
202 nNameOrFullPath,
203 AT_STATX_DONT_SYNC | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW,
204 STATX_BASIC_STATS | STATX_BTIME,
205 &stats );
206
207 #else
208 struct stat stats;
209 int statResult= pxDir ? fstatat(dirfd(pxDir), nNameOrFullPath, &stats,
210 AT_SYMLINK_NOFOLLOW
211 #if !defined(__APPLE__)
212 | AT_NO_AUTOMOUNT
213 #endif
214 )
215 : lstat ( nNameOrFullPath, &stats );
216 #endif
217 if( statResult ) {
218 ALIB_ASSERT_WARNING( errno != ENOENT, "FILES", "File does not exist (anymore) while stating \"{}\"",
219 dbgActFile )
220 ALIB_ASSERT_WARNING( errno == ENOENT, "FILES", "Unknown error ({}) \"{}\" while stating file \"{}\"",
221 errno, SystemErrors(errno), dbgActFile )
222 value.SetQuality(errno == ENOENT ? FInfo::Qualities::NOT_EXISTENT
224 ALIB_DBG( errno= 0;)
225 goto APPLY_POST_RECURSION_FILTER;
226 }
227 DBG_CHECKERRNO_WITH_PATH
228
229 // check filesystem type (artificial fs & mount point)
230 {
231 uint64_t device= (uint64_t(STAT_DEV_MAJOR) << 32L) + STAT_DEV_MINOR;
232 if( currentDevice == 0) currentDevice= device;
233 else if( currentDevice != device) { currentDevice= device; value.SetCrossingFS(); }
234 }
235
236 if( STAT_DEV_MAJOR == 0 // artificial?
237 && STAT_DEV_MINOR != 35 ) // tmpfs included, not considered artificial!
238 value.SetArtificialFS();
239
240 //------------------------------------------ is symlink? -----------------------------------------
241 bool origFileIsSymlink= (STATMEMBER(mode) & S_IFMT) == S_IFLNK;
242 if( origFileIsSymlink
244 {
245 value.SetQuality( FInfo::Qualities::RESOLVED );
246
247 // 1. Read plain symlink target (only to be attached to the entry)
248 ALIB_STRINGS_TO_NARROW(symLinkDest, nSymLinkDest, 512)
249 ssize_t cntChars= pxDir ? readlinkat( dirfd(pxDir), nNameOrFullPath, nSymLinkDest.VBuffer(), PATH_MAX)
250 : readlink ( nNameOrFullPath, nSymLinkDest.VBuffer(), PATH_MAX);
251
252 if (cntChars == -1) switch(errno) {
253 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL); ALIB_DBG(errno= 0;)
254 goto ABORT_SYMLINK;
255
256 case ENOENT: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL);
257 ALIB_ASSERT_ERROR(STAT_DEV_MAJOR == 0, "FILES",
258 "Posix raised ({}) \"{}\" on reading a symbolic link which is not located on "
259 "an artificial filesystem (like /proc). File:\"{}\"",
260 errno, SystemErrors(errno), dbgActFile ) ALIB_DBG(errno= 0;)
261 goto ABORT_SYMLINK;
262
263 default: value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
264 ALIB_ERROR("FILES", "Posix raised ({}) \"{}\" on reading symbolic link \"{}\"",
265 errno, SystemErrors(errno), dbgActFile ) ALIB_DBG(errno= 0;)
266 goto ABORT_SYMLINK;
267 }
268 nSymLinkDest.SetLength(cntChars);
270 symLinkDest.Reset(nSymLinkDest);
271 #endif
272
273 // 2. Read symlink's real target path (fully and recursively translated)
274 ALIB_STRING_RESETTER(actPath);
275 if( pxDir )
276 actPath << DIRECTORY_SEPARATOR << nameOrFullPath;
277 errno= 0;
278 ALIB_STRINGS_TO_NARROW(actPath , nActPath , 512)
279 ALIB_STRINGS_TO_NARROW(symLinkDestReal, nSymLinkDestReal, 512)
280 *nSymLinkDestReal.VBuffer()= '\0';
281 if(! realpath(nActPath.Terminate(), nSymLinkDestReal.VBuffer() ) ) switch (errno)
282 { // The named file does not exist.
283 case ENOENT: if( *nSymLinkDestReal.VBuffer() != '\0')
284 nSymLinkDestReal.DetectLength();
285 value.SetQuality(FInfo::Qualities::BROKEN_LINK); ALIB_DBG(errno= 0;)
286 goto ABORT_SYMLINK;
287 case ELOOP: value.SetQuality(FInfo::Qualities::CIRCULAR_LINK); ALIB_DBG(errno= 0;)
288 goto ABORT_SYMLINK;
289 // this might happen with strange system files
290 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL_TARGET); ALIB_DBG(errno= 0;)
291 goto ABORT_SYMLINK;
292 default: ALIB_ERROR("FILES", "Posix raised ({}) \"{}\" on resolving symbolic link \"{}\"",
293 errno, SystemErrors(errno), dbgActFile ) ALIB_DBG(errno= 0;)
294 goto ABORT_SYMLINK;
295 }
296 nSymLinkDestReal.DetectLength();
297
298 ALIB_DBG( if( errno == EINVAL) errno= 0;) // this happens, even though realpath() above returned 'OK'
299 DBG_CHECKERRNO_WITH_PATH
300 ALIB_ASSERT_ERROR( Path::IsAbsolute(symLinkDestReal), "FILES",
301 "Real path is not absolute: ", nSymLinkDestReal )
302
303 // 3. get resolved status
304 DBG_CHECKERRNO_WITH_PATH
305 #if TMP_STATX_AVAILABLE
306 statResult= statx( 0,
307 nSymLinkDestReal.Terminate(),
308 AT_STATX_DONT_SYNC | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW,
309 STATX_ALL,
310 &stats );
311 #else
312 statResult= stat(nSymLinkDestReal.Terminate(), &stats );
313 #endif
314 DBG_CHECKERRNO_WITH_PATH
315 #if ALIB_CHARACTERS_WIDE
316 symLinkDestReal.Reset(nSymLinkDestReal);
317 #endif
318
319 if(statResult == -1 ) {
321 if(errno) switch( SystemErrors(errno) )
322 { case SystemErrors::enoent: value.SetQuality(FInfo::Qualities::BROKEN_LINK);
323 ALIB_DBG(errno= 0;)
324 goto APPLY_POST_RECURSION_FILTER;
325 default: ALIB_WARNING("FILES",
326 "Unhandled error code invoking 'stat()' on resolved symbolic "
327 "link: {} (\"{}\")\n Symbolic link target: \"{}\"",
328 errno, SystemErrors(errno), dbgActFile )
329 ALIB_DBG(errno= 0;)
330 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
331 goto APPLY_POST_RECURSION_FILTER;
332 }
334 }
335
336 // check for target artificial fs
337 if( STAT_DEV_MAJOR == 0 // artificial?
338 && STAT_DEV_MINOR != 35 ) // tempfs included!
339 value.SetTargetArtificialFS();
340
341 } // if is symlink && resolve symlinks
342
343 ABORT_SYMLINK:
344 DBG_CHECKERRNO_WITH_PATH
345
346 //================================================================================================
347 //=========================================== Copy Stats =========================================
348 //================================================================================================
349 // 1. type
350 {
352 auto posixType= STATMEMBER(mode) & S_IFMT;
353 if( origFileIsSymlink ) {
354 type= posixType == S_IFDIR ? FInfo::Types::SYMBOLIC_LINK_DIR
356 }
357 else switch(STATMEMBER(mode) & S_IFMT ) {
358 case S_IFLNK : type= FInfo::Types::SYMBOLIC_LINK; ALIB_ERROR( "FILES", "Impossible")
359 break;
360 case S_IFBLK : type= FInfo::Types::BLOCK ; break;
361 case S_IFCHR : type= FInfo::Types::CHARACTER ; break;
362 case S_IFDIR : type= FInfo::Types::DIRECTORY ; break;
363 case S_IFIFO : type= FInfo::Types::FIFO ; break;
364 case S_IFREG : type= FInfo::Types::REGULAR ; break;
365 case S_IFSOCK: type= FInfo::Types::SOCKET ; break;
366 default: ALIB_ERROR("FILES",
367 "Internal error. 'unknown' file type can't happen. File: \"{}\"",
368 dbgActFile ) break;
369 }
370 value.SetType( type );
371 }
372
373 // 2. perms
374 value.SetPerms( FInfo::Permissions(STATMEMBER(mode) & int32_t(FInfo::Permissions::MASK)) );
375
376 // 3. timestamps
377 {
378 #if defined(__APPLE__)
379 # define st_mtime_name STATMEMBER(mtimespec)
380 # define st_ctime_name STATMEMBER(ctimespec)
381 # define st_atime_name STATMEMBER(atimespec)
382 #else
383 # if TMP_STATX_AVAILABLE
384 # define st_mtime_name STATMEMBER(mtime)
385 # define st_ctime_name STATMEMBER(ctime)
386 # define st_atime_name STATMEMBER(atime)
387 # define st_btime_name STATMEMBER(btime)
388 # else
389 # define st_mtime_name STATMEMBER(mtim)
390 # define st_ctime_name STATMEMBER(ctim)
391 # define st_atime_name STATMEMBER(atim)
392 # endif
393 #endif
394 DateTime dt;
395 dt.Import(
396 std::chrono::system_clock::time_point {
397 std::chrono::duration_cast<std::chrono::system_clock::duration>(
398 std::chrono::seconds {st_mtime_name.tv_sec }
399 + std::chrono::nanoseconds{st_mtime_name.tv_nsec} ) } );
400 value.SetMDate(dt);
401
402 dt.Import(
403 std::chrono::system_clock::time_point {
404 std::chrono::duration_cast<std::chrono::system_clock::duration>(
405 std::chrono::seconds {st_ctime_name.tv_sec }
406 + std::chrono::nanoseconds{st_ctime_name.tv_nsec} ) } );
407 value.SetCDate(dt);
408
409 dt.Import(
410 std::chrono::system_clock::time_point {
411 std::chrono::duration_cast<std::chrono::system_clock::duration>(
412 std::chrono::seconds {st_atime_name.tv_sec }
413 + std::chrono::nanoseconds{st_atime_name.tv_nsec} ) } );
414 value.SetADate(dt);
415
416 #if TMP_STATX_AVAILABLE
417 if( STATMEMBER(mask) & STATX_BTIME ) { // file systems supports "btime"?
418 dt.Import(
419 std::chrono::system_clock::time_point {
420 std::chrono::duration_cast<std::chrono::system_clock::duration>(
421 std::chrono::seconds {st_btime_name.tv_sec }
422 + std::chrono::nanoseconds{st_btime_name.tv_nsec} ) } );
423 value.SetBDate(dt);
424 } else {
425 // use smallest of other times for "btime"
426 auto btime= value.MDate();
427 if( btime > value.CDate() ) btime= value.CDate();
428 if( btime > value.ADate() ) btime= value.ADate();
429 value.SetBDate( btime );
430
431 }
432 #else
433 // use smallest of other times for "btime"
434 auto btime= value.MDate();
435 if( btime > value.CDate() ) btime= value.CDate();
436 if( btime > value.ADate() ) btime= value.ADate();
437 value.SetBDate( btime );
438 #endif
439
440
441 #undef st_mtime_name
442 #undef st_ctime_name
443 #undef st_atime_name
444 }
445
446 // 4. size
447 value.SetSize( uinteger(STATMEMBER(size) ) );
448
449 // 5. uid/gid
450 value.SetOwner( STATMEMBER(uid) );
451 value.SetGroup( STATMEMBER(gid) );
452
453 // 6. qty of symlinks
454 value.SetQtyHardlinks( STATMEMBER(nlink) );
455
456 // 7. Add extended information
457 if( oldQuality < FInfo::Qualities::STATS
458 && (value.IsDirectory() || symLinkDest.IsNotEmpty()) ) {
459 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
460 File(node).GetFTree().AllocateExtendedInfo( node, symLinkDest, symLinkDestReal );
461 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
462 }
463
464} // if scan stats (quality was just path)
465
466DBG_CHECKERRNO_WITH_PATH
467
468// Count broken link.
469if(value.Quality() == FInfo::Qualities::BROKEN_LINK) {
470 ++parentSums.QtyErrsBrokenLink;
471 goto APPLY_POST_RECURSION_FILTER;
472}
473
474 //================================================================================================
475 //================================== recursion with directories? =================================
476 //================================================================================================
477if( !value.IsDirectory()
478 || value.Quality() >= FInfo::Qualities::RECURSIVE )
479 goto APPLY_POST_RECURSION_FILTER;
480
481// stop recursion due to artificial fs?
482if( value.IsArtificialFS() && !params.IncludeArtificialFS ) {
483 Log_Prune( if( verboseLogables.Size() ) verboseLogables.Add(" NO_AFS"); )
484 value.SetQuality( FInfo::Qualities::NO_AFS );
485 goto APPLY_POST_RECURSION_FILTER;
486}
487
488// stop recursion due to crossing filesystem?
489if( value.IsCrossingFS() && !params.CrossFileSystems ) {
490 Log_Prune( if( verboseLogables.Size() ) verboseLogables.Add(" NOT_CROSSING_FS"); )
491 value.SetQuality( FInfo::Qualities::NOT_CROSSING_FS );
492 goto APPLY_POST_RECURSION_FILTER;
493}
494
495// stop recursion due to max depth?
496if( depth >= params.MaxDepth ) {
497 Log_Prune( if( verboseLogables.Size() && (&params != &paramsPathOnly) ) verboseLogables.Add(" MAX_DEPTH_REACHED"); )
498 value.SetQuality( FInfo::Qualities::MAX_DEPTH_REACHED );
499 ++parentSums.QtyStopsOnMaxDepth;
500 goto APPLY_POST_RECURSION_FILTER;
501}
502
503// stop recursion due to filter
504if( depth > 0
505 && params.DirectoryFilterPreRecursion
506 && !params.DirectoryFilterPreRecursion->Includes( node, actPath ) )
507{
508 Log_Prune( if( verboseLogables.Size() ) verboseLogables.Add(" FILTERED(Pre)"); )
509 goto APPLY_POST_RECURSION_FILTER;
510}
511
512// mark as recursively scanned
513value.SetQuality( FInfo::Qualities::RECURSIVE );
514
515// SYMLINK RECURSION
516if ( value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR ) {
518 || value.IsArtificialFS() ) // never recurse with symlinks RESIDING on artificial fs!
519 {
520 value.SetQuality( FInfo::Qualities::NOT_FOLLOWED );
521 goto APPLY_POST_RECURSION_FILTER;
522 }
523
524 if( value.TargetIsArtificialFS() && !params.IncludeArtificialFS ) {
525 value.SetQuality( FInfo::Qualities::NO_AFS );
526 goto APPLY_POST_RECURSION_FILTER;
527 }
528
529 // recurse into symlink target
530 FInfo::DirectorySums childSums;
531 if( startScan( node.Tree<FTree>(), value.GetRealLinkTarget(), params, childSums,
532 resultPaths IF_ALIB_THREADS(,lock) ) )
533 value.SetQuality(FInfo::Qualities::DUPLICATE);
534 value.SetSums( childSums );
535 parentSums+= childSums;
536
537 goto APPLY_POST_RECURSION_FILTER;
538}
539
540// DIRECTORY RECURSION
541{ALIB_STRING_RESETTER( actPath );
542 if( pxDir == nullptr ) {
543 ALIB_ASSERT_ERROR(actPath.Buffer() == nameOrFullPath.Buffer(),"FILES","Internal error")
544 actPath.SetLength(nameOrFullPath.Length());
545 } else {
546 if( actPath.Length() > 1 ) actPath << DIRECTORY_SEPARATOR;
547 actPath << nameOrFullPath;
548 }
549
550 errno= 0;
551 int fd;
552 if( pxDir)
553 fd= openat( dirfd(pxDir), nNameOrFullPath, O_RDONLY | O_DIRECTORY );
554 else {
555 ALIB_STRINGS_TO_NARROW(actPath, nActPath, 512)
556 fd= open( nActPath , O_RDONLY | O_DIRECTORY );
557 }
558
559 if (fd != -1) { // success?
560 DBG_CHECKERRNO_WITH_PATH
561 FInfo::DirectorySums subSums;
562 DIR* childDir = fdopendir(fd);
563 for(;;) {
564 errno= 0;
565 dirent* pxEntry = readdir(childDir);
566 if( pxEntry == nullptr ) {
567 switch(errno) {
568 // possible errors (according to documentation):
569 // EOVERFLOW One of the values in the structure to be returned cannot be represented correctly.
570 // EBADF The dirp argument does not refer to an open directory stream.
571 // ENOENT The current position of the directory stream is invalid.
572 case 0: break;
573 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_DIR);
574 break;
575 case EINVAL: value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR);
576 ALIB_ASSERT_ERROR(major(currentDevice) == 0, "FILES",
577 "Posix raised ({}) \"{}\" on reading a directory which is not "
578 "located on an artificial filesystem (like /proc). File:\"{}\"",
579 errno, SystemErrors(errno), dbgActFile )
580 break;
581 default: value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
582 ALIB_ERROR("FILES", "Posix raised ({}) \"{}\" on reading "
583 "directory \"{}\"", errno, SystemErrors(errno), dbgActFile )
584 break;
585 }
586 errno= 0;
587 break;
588 }
589
590 // skip "." and ".."
591 if( pxEntry->d_name[0] == '.'
592 && ( pxEntry->d_name[1] == '\0'
593 || ( pxEntry->d_name[1] == '.'
594 && pxEntry->d_name[2] == '\0' ) ) )
595 continue;
596
597 //----- recursive call -----
598 auto childNode= node;
599#if ALIB_CHARACTERS_WIDE
600 Path childName(const_cast<const char*>(&pxEntry->d_name[0]));
601#else
602 const CString childName(const_cast<const char*>(&pxEntry->d_name[0]));
603#endif
604 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
605 childNode.GoToCreateChildIfNotExistent( childName );
606 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
607 scanFilePosix( childDir, childNode, childName,
608 depth + 1, params, currentDevice, subSums, actPath,
609 resultPaths IF_ALIB_THREADS(,lock) );
610 } // dir entry loop
611 closedir(childDir);
612 DBG_CHECKERRNO_WITH_PATH
613
614 // previously scanned in lower quality?
615 if( oldQuality != FInfo::Qualities::NONE ) {
616 FTree::FixSums( node );
617 parentSums+= value.Sums();
618 } else {
619 value.SetSums(subSums);
620 parentSums+= subSums;
621 }
622 ALIB_DBG( errno= 0;)
623 goto APPLY_POST_RECURSION_FILTER;
624 } // success opening director
625
626 // error with recursion
627 ALIB_ASSERT_ERROR(errno != ENOTDIR, "FILES",
628 "Internal error opening directory. This must never happen")
629
630
632 switch (SystemErrors(errno)) {
633 case SystemErrors::eacces:
634 ++parentSums.QtyErrsAccess;
635 value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR );
636 errno= 0;
637 break;
638
639 default:
640 ALIB_ERROR("FILES", "Unknown error {}(\"{}\") while opening directory \"{}\"",
641 errno, SystemErrors(errno), actPath)
642 value.SetQuality( FInfo::Qualities::UNKNOWN_ERROR );
643 break;
644} }
645
646 //================================================================================================
647 //========================= Apply Post Filter and remove empty directories =======================
648 //================================================================================================
649APPLY_POST_RECURSION_FILTER:
650// delete node only if this was a new scan.
651// It must not be deleted if this node was created as a path.
652if( oldQuality == FInfo::Qualities::NONE ) {
653 if ( value.IsDirectory() ) {
654 if( depth > 0
655 && ( ( params.DirectoryFilterPostRecursion
656 && !params.DirectoryFilterPostRecursion->Includes(node, actPath ) )
657 || ( params.RemoveEmptyDirectories
658 && value.Sums().Count() == 0 )
659 ) )
660 {
661 Log_Prune( if( verboseLogables.Size() ) { verboseLogables.Add(" FILTERED(Post)");
662 Log_Verbose( verboseLogables )
663 verboseLogables.clear(); } )
664 parentSums-= value.Sums();
665 if( params.RemoveEmptyDirectories ) {
666 File file(node);
667 node.Tree<FTree>().Notify( FTreeListener::Event::DeleteNode,
668 file,
669 IF_ALIB_THREADS(lock,)
670 actPath );
671 node.Delete();
672 return;
673 }
674
675 // Notify deletion of all children.
676 auto it= node.FirstChild();
677 while ( it.IsValid() ) {
678 File file(node);
679 node.Tree<FTree>().Notify( FTreeListener::Event::DeleteNode,
680 file,
681 IF_ALIB_THREADS(lock,)
682 actPath );
683 it.GoToNextSibling();
684 }
685
686 // do not return here. Still count the type below
687 node.DeleteChildren();
688 }
689
690 } else {
691 if ( params.FileFilter
692 && !params.FileFilter->Includes(node, actPath ) )
693 {
694 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
695 Log_Prune( if( verboseLogables.Size() ) { verboseLogables.Add(" FILTERED(Post)");
696 Log_Verbose( verboseLogables ) } )
697 node.Delete();
698 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
699 return;
700} } }
701
702Log_Prune( if( verboseLogables.Size() ) {
704 Log_Verbose( verboseLogables )
706})
707
708// cnt file type
709parentSums.Add(value);
710File file(node);
711node.Tree<FTree>().Notify( FTreeListener::Event::CreateNode, file, IF_ALIB_THREADS(lock,) actPath );
712
714DBG_CHECKERRNO_WITH_PATH
715} // scanFilePosix()
716
717}} // namespace [alib::files::anonymous]
718#undef DBG_CHECKERRNO_WITH_PATH
719#undef TMP_STATX_AVAILABLE
720#undef STATMEMBER
721
722
723//--------------------------------------------------------------------------------------------------
724//--- UNKNOWN platform, using C++17 filesystem (not all functionality given)
725//--------------------------------------------------------------------------------------------------
726#else
727#if ALIB_FILES_FORCE_STD_SCANNER
728# pragma message ("ALIB_FILES_FORCE_STD_SCANNER given. Using std::filesystem for scanning. In file: " __FILE__ )
729#else
730# pragma message ("Unknown Platform. Using std::filesystem for scanning. In file: " __FILE__ )
731#endif
732
733namespace fs = std::filesystem;
734
735// Note: MacOS is currently (as of 231210) missing C++20 library features in the area of std::clock
736#if defined(__APPLE__) || defined(_LIBCPP_VERSION) || defined(__ANDROID_NDK__)
737namespace
738{
739 template <typename TP>
740 std::time_t to_time_t(TP tp)
741 {
742 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(tp - TP::clock::now()
743 + std::chrono::system_clock::now());
744 return std::chrono::system_clock::to_time_t(sctp);
745 }
746}
747#endif
748#if ALIB_DEBUG
749# define DBG_CHECKERRNO \
750 ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\".", \
751 errno, SystemErrors(errno) ) \
752 errno= 0;
753# define DBG_CHECKERRNO_WITH_PATH \
754 ALIB_ASSERT_WARNING(errno == 0, "FILES", "Errno set ({})\"{}\". Current path: {}", \
755 errno, SystemErrors(errno), path.string() ) \
756 errno= 0;
757#else
758# define DBG_CHECKERRNO
759# define DBG_CHECKERRNO_WITH_PATH
760#endif
761
762namespace alib::files { namespace {
763
764void scanFileStdFS( const fs::path& path,
765 FTree::Cursor& node,
766 unsigned depth,
767 ScanParameters& params,
768 FInfo::DirectorySums& parentSums,
769 std::vector<ResultsPaths>& resultPaths
771{
772#if defined(__MINGW32__)
773Path pathAsCString(path.c_str());
774pathAsCString.Terminate();
775#else
776CPathString pathAsCString(path.c_str());
777#endif
778const PathSubstring parentPath= pathAsCString.Substring(0, pathAsCString.LastIndexOf(DIRECTORY_SEPARATOR));
779
780#if !defined(_WIN32)
781ALIB_ASSERT_ERROR( pathAsCString.CharAtStart()== DIRECTORY_SEPARATOR
782 && ( pathAsCString.Length()==1
783 || pathAsCString.CharAtEnd() != DIRECTORY_SEPARATOR)
784 && pathAsCString.IndexOf(NString8(DIRECTORY_SEPARATOR).Append(DIRECTORY_SEPARATOR))<0,
785 "FILES","Given path not absolute or ending with '{}': {}", DIRECTORY_SEPARATOR, pathAsCString )
786#else
787ALIB_ASSERT_ERROR( ( ( pathAsCString.CharAt(1)== ':'
788 && pathAsCString.CharAt(2)== DIRECTORY_SEPARATOR
789 && ( pathAsCString.Length()==3
790 || pathAsCString.CharAtEnd() != DIRECTORY_SEPARATOR) )
791
792 || ( pathAsCString.CharAt(0)== DIRECTORY_SEPARATOR
793 && pathAsCString.CharAt(1)== DIRECTORY_SEPARATOR
794 && ( pathAsCString.Length()==2
795 || pathAsCString.CharAtEnd() != DIRECTORY_SEPARATOR) )
796 )
797 && pathAsCString.IndexOf( strings::TLocalString<PathCharType, 8>(
798 DIRECTORY_SEPARATOR).Append(DIRECTORY_SEPARATOR),
799 2 ) < 0,
800 "FILES","Given path not absolute or ending with '{}': {}", DIRECTORY_SEPARATOR, pathAsCString )
801#endif
802
803
804Log_Verbose( "[{}] {}/{} {}", &params != &paramsPathOnly ? '>':'P', depth,
805 params.MaxDepth < (std::numeric_limits<unsigned>::max)()
806 ? String128(params.MaxDepth)
807 : String(A_CHAR("M")),
808 pathAsCString )
809
810std::error_code errorCode;
811auto& value = node.Value();
812auto oldQuality= value.Quality();
813
814 //------------------------------------------- get stats? -----------------------------------------
815if( value.Quality() == FInfo::Qualities::NONE
816 || ( value.Quality() == FInfo::Qualities::STATS
818{
819 value.SetQuality( FInfo::Qualities::STATS );
820 Path symLinkDest;
821 Path symLinkDestReal;
822
823 // read base stats (we have to use symlink_status() which does NOT follow the symlink!)
824 fs::file_status stats= fs::symlink_status(path);
826 if(errorCode)
827 {
828 ALIB_ERROR("FILES",
829 "Unhandled error code invoking 'fs::symlink_status()': {} (\"{}\")\n"
830 " With file: \"{}\"",
831 errorCode.value(), errorCode.message(), pathAsCString )
832 ALIB_DBG( errno= 0;)
833 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
834 goto APPLY_POST_RECURSION_FILTER;
835 }
837 ALIB_DBG(errno= 0;)
838
839 //------------------------------------------ is symlink? -----------------------------------------
840 bool origFileIsSymlink= (stats.type() == fs::file_type::symlink);
841 if( origFileIsSymlink
843 {
844 value.SetQuality( FInfo::Qualities::RESOLVED );
845
846 // 1. Read plain symlink target (only to be attached to the entry)
847 fs::path resolved= fs::read_symlink(path, errorCode);
848 if(errorCode)
849 {
851 switch( SystemErrors(errorCode.value()) )
852 { case SystemErrors::enoent: // happens with /proc files
853 case SystemErrors::eacces: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL);
854 ALIB_DBG(errno= 0;)
855 goto ABORT_SYMLINK;
856 default: ALIB_ERROR("FILES",
857 "Unhandled error code invoking 'fs::read_symlink()': {} (\"{}\")\n"
858 " with file: ", errorCode.value(),
859 errorCode.message(), pathAsCString )
860 ALIB_DBG( errno= 0;)
861 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
862 goto APPLY_POST_RECURSION_FILTER;
863 }
865 }
866 DBG_CHECKERRNO_WITH_PATH
867 symLinkDest << resolved.c_str();
868
869 // 2. Read symlink's real target path (fully and recursively translated)
870 fs::path realPath;
871 if( resolved.is_absolute() )
872 realPath= fs::canonical(resolved, errorCode);
873 else
874 {
875 symLinkDestReal << pathAsCString;
876 symLinkDestReal.ShortenTo( symLinkDestReal.LastIndexOf(DIRECTORY_SEPARATOR) + 1);
877 symLinkDestReal << symLinkDest;
878 realPath= fs::canonical(fs::path(
879 std::basic_string_view<PathCharType>(symLinkDestReal.Buffer(),
880 size_t(symLinkDestReal.Length()))),
881 errorCode);
882 symLinkDestReal.Reset();
883 }
884 ALIB_DBG(if(errno==EINVAL && !errorCode) errno= 0;) // this happens!, we do not care, but clean up
885 ALIB_DBG(if(errno==ENOENT && !errorCode) errno= 0;)
886
888 if(errorCode) switch( SystemErrors(errorCode.value()) )
889 { // we ignore this: std::fs would not create the "real path" if the final directory is not accessible.
890 case SystemErrors::eacces: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL_TARGET); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
891 case SystemErrors::enoent: value.SetQuality(FInfo::Qualities::BROKEN_LINK); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
892 case SystemErrors::eloop: value.SetQuality(FInfo::Qualities::CIRCULAR_LINK); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
893 default: ALIB_ERROR("FILES", "Unhandled error code invoking 'fs::canonical()': {} (\"{}\")\n"
894 " with file: ", errorCode.value(), errorCode.message(), pathAsCString )
895 goto ABORT_SYMLINK;
896 }
898 DBG_CHECKERRNO_WITH_PATH
899 symLinkDestReal << realPath.c_str();
900
901 // 3. get resolved status
903 auto newStatus= fs::status(path, errorCode);
904 if(!errorCode)
905 {
906 // this happens with strange /proc files...
907 if(newStatus.type() != fs::file_type::unknown)
908 stats= newStatus;
909 }
910 else switch( SystemErrors(errorCode.value()) )
911 { case SystemErrors::eperm: value.SetQuality( FInfo::Qualities::NO_ACCESS ); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
912 case SystemErrors::enoent: value.SetQuality( FInfo::Qualities::BROKEN_LINK ); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
913 case SystemErrors::eloop: value.SetQuality( FInfo::Qualities::CIRCULAR_LINK); ALIB_DBG(errno= 0;) goto ABORT_SYMLINK;
914 default: ALIB_WARNING("FILES",
915 "Unhandled error code invoking 'directory_entry::status()': {} (\"{}\")\n"
916 " With file: \"{}\"",
917 errorCode.value(), errorCode.message(), pathAsCString )
918 goto ABORT_SYMLINK;
919 }
921
922 // check for target artificial fs
923 // -/- Not available with std::filesystem version
924 }
925
926 ABORT_SYMLINK:
927 DBG_CHECKERRNO_WITH_PATH
928
929 //================================================================================================
930 //=========================================== Copy Stats =========================================
931 //================================================================================================
932 // 1. type
933 {
935 if( origFileIsSymlink )
936 {
937 type= is_directory(stats) ? FInfo::Types::SYMBOLIC_LINK_DIR
939 }
940 else switch( stats.type() )
941 {
942 case fs::file_type::directory: type= FInfo::Types::DIRECTORY ; break;
943 case fs::file_type::regular : type= FInfo::Types::REGULAR ; break;
944 case fs::file_type::symlink : type= FInfo::Types::SYMBOLIC_LINK; break; // for now, this is a file.
945 case fs::file_type::block : type= FInfo::Types::BLOCK ; break;
946 case fs::file_type::character: type= FInfo::Types::CHARACTER ; break;
947 case fs::file_type::fifo : type= FInfo::Types::FIFO ; break;
948 case fs::file_type::socket : type= FInfo::Types::SOCKET ; break;
949
950 case fs::file_type::not_found:
951 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
952 ALIB_WARNING("FILES", "Internal error. 'not found' file type can't happen. File: ", pathAsCString )
953 ALIB_DBG( errno= 0;) goto APPLY_POST_RECURSION_FILTER;
954 case fs::file_type::none :
955 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
956 ALIB_WARNING("FILES", "Internal error. 'none' file type can't happen. File: ", pathAsCString)
957 ALIB_DBG( errno= 0;) goto APPLY_POST_RECURSION_FILTER;
958 case fs::file_type::unknown :
959 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
960 ALIB_WARNING("FILES", "Internal error. Can't happen. File: ", pathAsCString)
961 ALIB_DBG( errno= 0;) goto APPLY_POST_RECURSION_FILTER;
962 default:
963 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
964 ALIB_WARNING("FILES", "Unknown fs::file_status::type '{}' with file {}.", stats.type(), pathAsCString)
965 ALIB_DBG( errno= 0;) goto APPLY_POST_RECURSION_FILTER;
966 }
967 value.SetType( type );
968 }
969
970 // 2. perms
971 value.SetPerms( FInfo::Permissions(int32_t(stats.permissions())) );
972
973 // 3. timestamps
974 // attn: This method always follows symbolic link and uses the target's time
975 // This seems to be a confirmed behavior:
976 // https://stackoverflow.com/questions/50778660/boost-filesystem-how-to-get-last-write-time-for-symlink-without-resolving
977 auto fsTime= std::filesystem::file_time_type(std::filesystem::file_time_type::clock::now());
978 if ( value.Quality() <= FInfo::Qualities::RESOLVED ) // no error
979 {
980 fsTime= fs::last_write_time( path, errorCode );
982 if(errorCode) switch( SystemErrors(errorCode.value()) )
983 { // This happens if with symbolic links that point to nowhere.
984 case SystemErrors::enoent: ALIB_ERROR( "FILES",
985 "Internal error. This should never happen, checked above. "
986 "Undefined system error handling" ) ALIB_DBG( errno= 0;)
987 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
988 break;
989
990 default: ALIB_ERROR( "FILES",
991 "Unhandled error code invoking 'fs::last_write_time()': {} (\"{}\")\n"
992 " With file \"{}\".", errorCode.value(), errorCode.message(),
993 pathAsCString )
994 fsTime= (decltype(fsTime)::min)(); ALIB_DBG( errno= 0;)
995 break;
996 }
998 }
999
1000
1001 #if defined(__APPLE__) || defined(_LIBCPP_VERSION) || defined(__ANDROID_NDK__)
1002 value.SetMDate( DateTime::FromEpochSeconds( to_time_t( fsTime ) ) );
1003 #else
1004 value.SetMDate( DateTime::FromEpochSeconds( std::chrono::system_clock::to_time_t(
1005 std::chrono::clock_cast<std::chrono::system_clock>(fsTime) ) ) );
1006 #endif
1007 value.SetBDate( value.MDate() );
1008 value.SetCDate( value.MDate() );
1009 value.SetADate( value.MDate() );
1010
1011 // 4. size
1012 errorCode.clear();
1013 value.SetSize( symLinkDest.Length() > 0 ? uinteger(symLinkDest.Length())
1014 : value.Quality() <= FInfo::Qualities::RESOLVED ? uinteger(fs::file_size(path, errorCode))
1015 : 0 );
1016 if( value.Size() == uinteger(-1))
1017 {
1018 value.SetSize(0);
1020 switch( SystemErrors(errorCode.value()) )
1021 {
1022 // target is a directory (no error)
1023 case SystemErrors::eisdir:
1024 break;
1025
1026 case SystemErrors::enoent: // this happens if we have a broken symbolic link
1028 || value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR , "FILES",
1029 "Internal error. This should never happen. Undefined system error handling" )
1030 break;
1031
1032 // size not supported. Happens with sockets, files in /proc, etc
1033 case SystemErrors::eopnotsupp: break;
1034 default: ALIB_ERROR("FILES",
1035 "Unhandled error code invoking 'directory_entry::file_size()':{} (\"{}\")\n"
1036 " With file \"{}\".",
1037 errorCode.value(), errorCode.message(), pathAsCString ) ALIB_DBG( errno= 0;)
1038 break;
1039 }
1041 }
1042
1043 // 5. uid/gid
1044 value.SetOwner( FInfo::UnknownID );
1045 value.SetGroup( FInfo::UnknownID );
1046
1047 // 6. qty of symlinks
1048 uint32_t qtyHardLinks= uint32_t( fs::hard_link_count(path, errorCode ) );
1050 if(errorCode)
1051 {
1052 ALIB_MESSAGE("FILES",
1053 "Unhandled error code invoking 'fs::hard_link_count()': {} (\"{}\")\n"
1054 " With file: \"{}\"",
1055 errorCode.value(), errorCode.message(), pathAsCString )
1056 ALIB_DBG( errno= 0;)
1057 }
1059 value.SetQtyHardlinks( qtyHardLinks );
1060
1061 // 7. Add extended information
1062 if( oldQuality < FInfo::Qualities::STATS
1063 && (value.IsDirectory() || symLinkDest.IsNotEmpty()) ) {
1064 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
1065 File(node).GetFTree().AllocateExtendedInfo( node, symLinkDest, symLinkDestReal );
1066 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
1067 }
1068
1069} // if scan stats (quality was just path)
1070
1071DBG_CHECKERRNO_WITH_PATH
1072
1073// Count broken link.
1074if(value.Quality() == FInfo::Qualities::BROKEN_LINK)
1075{
1076 ++parentSums.QtyErrsBrokenLink;
1077 goto APPLY_POST_RECURSION_FILTER;
1078}
1079
1080 //---------------------------------- recursion with directories? ---------------------------------
1081if( !value.IsDirectory()
1082 || value.Quality() >= FInfo::Qualities::RECURSIVE )
1083 goto APPLY_POST_RECURSION_FILTER;
1084
1085
1086// stop recursion due to artificial fs?
1087 // Not supported with std::filesystem!
1088
1089// stop recursion due to crossing filesystem?
1090if( value.IsCrossingFS() && !params.CrossFileSystems )
1091{
1092 value.SetQuality( FInfo::Qualities::NOT_CROSSING_FS );
1093 goto APPLY_POST_RECURSION_FILTER;
1094}
1095
1096// stop recursion due to max depth?
1097if( depth >= params.MaxDepth )
1098{
1099 value.SetQuality( FInfo::Qualities::MAX_DEPTH_REACHED );
1100 ++parentSums.QtyStopsOnMaxDepth;
1101 goto APPLY_POST_RECURSION_FILTER;
1102}
1103
1104// stop recursion due to filter
1105if( depth > 0
1107 && !params.DirectoryFilterPreRecursion->Includes( node, parentPath ) )
1108 goto APPLY_POST_RECURSION_FILTER;
1109
1110// mark as recursively scanned
1111value.SetQuality( FInfo::Qualities::RECURSIVE );
1112
1113// SYMLINK RECURSION
1114if ( value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR )
1115{
1116 if( params.LinkTreatment != ScanParameters::SymbolicLinks::RECURSIVE
1117 || value.IsArtificialFS() ) // never recurse with symlinks RESIDING on artificial fs!
1118 {
1119 value.SetQuality( FInfo::Qualities::NOT_FOLLOWED );
1120 goto APPLY_POST_RECURSION_FILTER;
1121 }
1122 else
1123 {
1124 // recurse into symlink target
1125 FInfo::DirectorySums childSums;
1126 if( startScan( File(node).GetFTree(), value.GetRealLinkTarget(), params, childSums,
1127 resultPaths IF_ALIB_THREADS(,lock) ) )
1128 value.SetQuality(FInfo::Qualities::DUPLICATE);
1129 value.SetSums( childSums );
1130 parentSums+= childSums;
1131 goto APPLY_POST_RECURSION_FILTER;
1132 }
1133}
1134
1135// DIRECTORY RECURSION
1136{
1137 fs::directory_iterator dit= fs::directory_iterator(path, errorCode);
1138 if(!errorCode) // success?
1139 {
1140 FInfo::DirectorySums subSums;
1141 for( const fs::directory_entry& childDir : dit )
1142 {
1143 // recursive call
1144 #if defined(_WIN32)
1145 Path mingwBuf( childDir.path().c_str());
1146 PathSubstring childName(mingwBuf);
1147 #else
1148 NSubstring childName(NCString(childDir.path().c_str()));
1149 #endif
1150 childName.ConsumeChars(childName.LastIndexOf(DIRECTORY_SEPARATOR) + 1);
1151 auto childNode= node;
1152 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
1153 childNode.GoToCreateChildIfNotExistent( childName );
1154 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
1155 scanFileStdFS( childDir.path(), childNode, depth + 1, params, subSums,
1156 resultPaths IF_ALIB_THREADS(,lock) );
1157 }
1158
1159 // previously scanned in lower quality?
1160 if( oldQuality != FInfo::Qualities::NONE )
1161 {
1162 FTree::FixSums( node );
1163 parentSums+= value.Sums();
1164 }
1165 else
1166 {
1167 value.SetSums(subSums);
1168 parentSums+= subSums;
1169 }
1170 ALIB_DBG( errno= 0;)
1171 goto APPLY_POST_RECURSION_FILTER;
1172 }
1173}
1174
1175// error with recursion
1176ALIB_ASSERT_ERROR( errorCode.value() != ENOTDIR, "FILES",
1177 "Internal error opening directory. This must never happen" )
1178
1180if(errorCode) switch (SystemErrors(errorCode.value()))
1181{
1182 case SystemErrors::einval: // happens with /proc
1183 case SystemErrors::eacces: ++parentSums.QtyErrsAccess;
1184 value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR );
1185 ALIB_DBG( errno= 0;)
1186 goto APPLY_POST_RECURSION_FILTER;
1187
1188 default: value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
1189 ALIB_ERROR("FILES", "Unknown error {}(\"{}\") while opening directory \"{}\"",
1190 errorCode.value(), SystemErrors(errorCode.value()), pathAsCString)
1191 ALIB_DBG( errno= 0;)
1192 goto APPLY_POST_RECURSION_FILTER;
1193}
1195ALIB_DBG( errno= 0;)
1196
1197 //------------------------------------------ Apply Filter ----------------------------------------
1198APPLY_POST_RECURSION_FILTER:
1199// delete node only if this was a new scan. It must not be deleted if this node was
1200// created as a path.
1201if( oldQuality == FInfo::Qualities::NONE )
1202{
1203 if ( value.IsDirectory() )
1204 {
1205 if( depth > 0
1206 && ( ( params.DirectoryFilterPostRecursion
1207 && !params.DirectoryFilterPostRecursion->Includes(node, parentPath ) )
1208 || ( params.RemoveEmptyDirectories
1209 && value.Sums().Count() == 0 )
1210 ) )
1211 {
1212 parentSums-= value.Sums();
1213 if( params.RemoveEmptyDirectories )
1214 {
1215 File file(node);
1216 node.Tree<FTree>().Notify( FTreeListener::Event::DeleteNode,
1217 file,
1218 IF_ALIB_THREADS( lock, )
1219 parentPath );
1220 node.Delete();
1221 return;
1222 }
1223
1224 // Notify deletion of all children.
1225 auto it= node.FirstChild();
1226 while ( it.IsValid() )
1227 {
1228 File file(node);
1229 node.Tree<FTree>().Notify( FTreeListener::Event::DeleteNode,
1230 file,
1231 IF_ALIB_THREADS( lock, )
1232 parentPath );
1233 it.GoToNextSibling();
1234 }
1235
1236 // do not return here. Still count the type below
1237 node.DeleteChildren();
1238 }
1239
1240 }
1241 else
1242 {
1243 if ( params.FileFilter
1244 && !params.FileFilter->Includes(node, parentPath ) )
1245 {
1246 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
1247 node.Delete();
1248 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
1249 return;
1250 }
1251 }
1252}
1253
1254// cnt file type
1255parentSums.Add(value);
1256File file(node);
1257node.Tree<FTree>().Notify( FTreeListener::Event::CreateNode, file, IF_ALIB_THREADS(lock,) parentPath );
1258
1259} // scanFileStdFS
1260
1261
1262}} // namespace [alib::files::anonymous]
1263
1264#undef DBG_CHECKERRNO_WITH_PATH
1265#endif // std::fs version
1266
1267//--------------------------------------------------------------------------------------------------
1268//--- ALL Platforms
1269//--------------------------------------------------------------------------------------------------
1270namespace alib::files {
1271
1272namespace {
1273
1274// Creates start path nodes and invokes scanFileXXX
1275bool startScan( FTree& tree,
1276 PathString realPath,
1277 ScanParameters& params,
1278 FInfo::DirectorySums& parentSums,
1279 std::vector<ResultsPaths>& resultPaths
1280 IF_ALIB_THREADS(, SharedLock* lock) ) {
1281ALIB_ASSERT_ERROR( Path::IsAbsolute(realPath), "FILES","Real path is not absolute: ", realPath )
1282
1283FTree::Cursor node= tree.Root().AsCursor();
1284#if !defined(_WIN32)
1285Path path(DIRECTORY_SEPARATOR);
1286
1287// travel any existing portion of the path
1289PathSubstring pathRemainder= node.GoTo( realPath );
1291path << realPath.Substring(1, realPath.Length() - pathRemainder.Length() - 1);
1292#else
1293Path path;
1294PathSubstring pathRemainder;
1295if(realPath.CharAt(1) == ':') {
1296 path << realPath.Substring(0,3);
1297 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
1298 node.GoToCreateChildIfNotExistent(realPath.Substring(0,2));
1299 pathRemainder= node.GoTo( realPath.Substring(3) );
1300 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
1301 path << realPath.Substring(3, realPath.Length() - pathRemainder.Length() -3 );
1302} else {
1303 integer serverNameEnd= realPath.IndexOf( DIRECTORY_SEPARATOR, 2);
1304 if( serverNameEnd < 0)
1305 serverNameEnd= realPath.Length();
1306 path << realPath.Substring(0, serverNameEnd);
1307 IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
1308 node.GoToCreateChildIfNotExistent(realPath.Substring(2, serverNameEnd - 2));
1309 pathRemainder= node.GoTo( realPath.Substring(serverNameEnd + 1) );
1310 IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
1311 path << realPath.Substring(serverNameEnd, realPath.Length() - pathRemainder.Length() -serverNameEnd );
1312}
1313
1314#endif
1315
1316DBG_CHECKERRNO
1317
1318
1319// existed already?
1320if( pathRemainder.IsEmpty() ) {
1321 // For directories, call scan just for the case of having 'higher' scan parameters
1322 if( node->IsDirectory()) {
1323
1324 #if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
1325 path.Terminate();
1326 CPathString fullPathChildName(path);
1327 path.SetLength(path.LastIndexOf(DIRECTORY_SEPARATOR) );
1328 scanFilePosix( nullptr, node, fullPathChildName, 0, params, 0, parentSums, path,
1329 resultPaths IF_ALIB_THREADS(,lock));
1330 #else
1331 scanFileStdFS( fs::path(std::basic_string_view<PathCharType>(path.Buffer(),
1332 size_t(path.Length()))),
1333 node, 0, params, parentSums, resultPaths IF_ALIB_THREADS(,lock) );
1334 #endif
1335
1336 //resultPaths.emplace_back(ResultsPaths(realPath, node, true));
1337 }
1338
1339 return true;
1340}
1341
1342// did not exist already
1343if( path.Length() > 1 )
1344 path.DeleteEnd<NC>(1);
1345
1346strings::util::TTokenizer<PathCharType> tknzr( pathRemainder, DIRECTORY_SEPARATOR );
1347while(tknzr.HasNext()) {
1348 PathString name;
1349 if( path.Length() != 1 ) {
1350 name= tknzr.Next();
1351IF_ALIB_THREADS( if (lock) lock->Acquire(ALIB_CALLER_PRUNED); )
1352 node= node.CreateChild(name);
1353IF_ALIB_THREADS( if (lock) lock->Release(ALIB_CALLER_PRUNED); )
1354 }
1355
1356 bool isLastPathElement= !tknzr.HasNext();
1357 if( isLastPathElement )
1358 parentSums= FInfo::DirectorySums(); // clear the sums, because only the results of the last element are used.
1359
1361 auto detectNodeDeletion= node.Depth();
1363
1364 #if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
1365
1366 if( path.IsEmpty() ) path << DIRECTORY_SEPARATOR;
1367 CPathString fullPathChildName;
1368 {
1369 // add node name to existing path and use same buffer for fullPathChildName!
1370 ALIB_STRING_RESETTER( path );
1371 if( path.Length() > 1 ) path << DIRECTORY_SEPARATOR;
1372 path << node.Name();
1373 path.Terminate();
1374 fullPathChildName= path;
1375 }
1376
1377 scanFilePosix( nullptr, node, fullPathChildName,
1378 0, isLastPathElement ? params : paramsPathOnly,
1379 0, parentSums, path, resultPaths IF_ALIB_THREADS(,lock) );
1380 if( fullPathChildName.Length() == 1 ) path.Reset();
1381 else { if(path.Length() > 1) path << DIRECTORY_SEPARATOR; path << name; }
1382 #else
1383 if( path.Length() != 1 ) path << DIRECTORY_SEPARATOR << name;
1384 scanFileStdFS( fs::path(std::basic_string_view<PathCharType>(path.Buffer(),
1385 size_t(path.Length()))),
1386 node, 0,
1387 isLastPathElement ? params : paramsPathOnly,
1388 parentSums, resultPaths IF_ALIB_THREADS(,lock) );
1389 if( path.Length() == 1 ) path.Reset();
1390 #endif
1391
1392 // if the just created node was not deleted during scan, add it to the result list
1393 if( isLastPathElement) {
1395 if (detectNodeDeletion == node.Depth() )
1396 resultPaths.insert(resultPaths.begin(), ResultsPaths(realPath, node, false));
1398 }
1399
1400 // Correct quality from max depth to stats
1401 if( !isLastPathElement && node->Quality() == FInfo::Qualities::MAX_DEPTH_REACHED)
1402 node->SetQuality(FInfo::Qualities::STATS);
1403}
1404
1405return false;
1406}
1407
1408} // namespace alib::files[::anonymous]
1409
1410#endif // !DOXYGEN
1411
1412//--------------------------------------------------------------------------------------------------
1413//--- ScanFiles()
1414//--------------------------------------------------------------------------------------------------
1415enum FInfo::Qualities ScanFiles( FTree& tree,
1416 ScanParameters& parameters,
1417 std::vector<ResultsPaths>& resultPaths
1418 IF_ALIB_THREADS( , SharedLock* lock) ) {
1419 Log_SetDomain( "ALIB/FILES", Scope::Path)
1420 Log_SetDomain( "SCAN" , Scope::Filename)
1421
1422 ALIB_DBG( if( alib::FILES.IsBootstrapped())
1423 {
1424 Log_SetDomain( "ALIB/FILES", Scope::Path)
1425 Log_SetDomain( "SCAN" , Scope::Filename)
1426 } )
1427
1428
1429 //----------------------------------------- get real path ----------------------------------------
1430 Path path(parameters.StartPath);
1431 Path realPath;
1432 realPath.Terminate();
1433
1434 #if ALIB_FILES_SCANNER_IMPL == ALIB_FILES_SCANNER_POSIX
1435 ALIB_STRINGS_TO_NARROW(path , nPath , 512)
1436 ALIB_STRINGS_TO_NARROW(realPath, nRealPath, 512)
1437 if(!realpath(nPath.Terminate(), nRealPath.VBuffer() ) ) switch (errno) {
1438 case EACCES: ALIB_DBG(errno= 0;) return FInfo::Qualities::NO_ACCESS;
1439 case ENOENT: ALIB_DBG(errno= 0;) return FInfo::Qualities::NOT_EXISTENT;
1440 case ELOOP: ALIB_DBG(errno= 0;) return FInfo::Qualities::CIRCULAR_LINK;
1441 default: ALIB_ERROR("FILES", "Posix raised ({}) \"{}\" on resolving start path \"{}\"",
1442 errno, SystemErrors(errno), path ) ALIB_DBG(errno= 0;)
1444 }
1445 nRealPath.DetectLength();
1446 #if ALIB_CHARACTERS_WIDE
1447 realPath.Reset(nRealPath);
1448 #endif
1449 #else
1450 {
1451 std::error_code errorCode;
1452 fs::path fsRealPath= fs::canonical(fs::path(std::basic_string_view<PathCharType>(path.Buffer(),
1453 size_t(path.Length()))),
1454 errorCode);
1455 ALIB_DBG(if(errno==EINVAL && !errorCode) errno= 0;) // this happens!, we do not care, but clean up
1456 ALIB_DBG(if(errno==ENOENT && !errorCode) errno= 0;)
1457
1459 if(errorCode) switch( SystemErrors(errorCode.value()) )
1460 { // we ignore this: std::fs would not create the "real path" if the final directory is not accessible.
1461 case SystemErrors::eacces: return FInfo::Qualities::NO_ACCESS;
1462 case SystemErrors::enoent: return FInfo::Qualities::NOT_EXISTENT;
1463 case SystemErrors::eloop: return FInfo::Qualities::CIRCULAR_LINK;
1464 default: ALIB_ERROR("FILES", "std::filesystem raised ({}) \"{}\" on resolving start path \"{}\"",
1465 errorCode.value(), errorCode.message(), path ) ALIB_DBG(errno= 0;)
1467 }
1469 realPath << fsRealPath.c_str();
1470 }
1471 #endif
1472
1473 Log_Info( "Scanning: P= {}\n"
1474 " RP= {}\n"
1475 " F={} DPre={} DPost={} XFS={} AFS={} Depth={}",
1476 parameters.StartPath, realPath,
1477 parameters.FileFilter .get() ? 'Y':'N',
1478 parameters.DirectoryFilterPreRecursion .get() ? 'Y':'N',
1479 parameters.DirectoryFilterPostRecursion.get() ? 'Y':'N',
1480 parameters.CrossFileSystems ? 'Y':'N', parameters.IncludeArtificialFS ? 'Y':'N',
1482 : String128(parameters.MaxDepth)
1483 )
1484
1485 //----------------------------------------- start scanning ---------------------------------------
1486 ALIB_DBG( errno=0;
1487 Log_Prune(auto firstResultPos= resultPaths.size(); ))
1488 FInfo::DirectorySums dummySums;
1489
1490 startScan( tree, realPath, parameters, dummySums, resultPaths IF_ALIB_THREADS( , lock) );
1491
1492 Log_Info( "Scan Results: ", resultPaths.size() - firstResultPos )
1493 Log_Prune( int cntPaths= 0;
1494 for( auto& it : resultPaths ) {
1495 Log_Info( " Path {}: {} {} (Q={} D={}/F={}}",
1496 cntPaths++, it.Existed ? ' ' : '+',
1497 it.RealPath,
1498 it.Node->Quality(),
1499 it.Node->Quality() > FInfo::Qualities::STATS && it.Node->IsDirectory() ? it.Node.Value().Sums().CountDirectories() : 0,
1500 it.Node->Quality() > FInfo::Qualities::STATS && it.Node->IsDirectory() ? it.Node.Value().Sums().CountNonDirectories(): 0 )
1501 } )
1502
1503 return resultPaths.size() > 0 ? resultPaths.back().Node->Quality()
1505}
1506
1507#undef DBG_CHECKERRNO
1508
1509} // namespace [alib::files]
1510
1511# include "ALib.Lang.CIMethods.H"
The entry type which is embedded in each tree node.
Definition finfo.inl:15
@ DIRECTORY
Directory/folder.
Definition finfo.inl:26
@ CHARACTER
A character special file.
Definition finfo.inl:33
@ BLOCK
A block special file.
Definition finfo.inl:32
@ SOCKET
A socket file.
Definition finfo.inl:35
@ REGULAR
Regular file.
Definition finfo.inl:29
@ FIFO
A FIFO (also known as pipe) file.
Definition finfo.inl:34
uinteger size
The file size. In case of a directory, this is 0.
Definition finfo.inl:326
Qualities
Per-entry information about how a node was scanned.
Definition finfo.inl:123
@ RECURSIVE
Follow symlink target strings.
Definition finfo.inl:134
@ STATS
Only stats (size, date, owner, etc.) read.
Definition finfo.inl:125
@ NO_ACCESS_DIR
Scanner failure due to limited access rights.
Definition finfo.inl:139
@ MAX_DEPTH_REACHED
Scanner stopped, because maximum depth was reached.
Definition finfo.inl:129
@ RESOLVED
Read symlink target strings.
Definition finfo.inl:127
@ NO_ACCESS_SL
Scanner failure due to limited access rights.
Definition finfo.inl:137
@ UNKNOWN_ERROR
Unknown scanner failure.
Definition finfo.inl:148
@ BROKEN_LINK
A symbolic link targets a non-existent file or directory.
Definition finfo.inl:140
@ NO_ACCESS_SL_TARGET
Scanner failure due to limited access rights.
Definition finfo.inl:138
@ NO_ACCESS
Scanner failure due to limited access rights.
Definition finfo.inl:136
@ NOT_EXISTENT
Set if a given start path does not exist.
Definition finfo.inl:147
Permissions
Permission flags. Compatible with posix* definition.
Definition finfo.inl:89
@ NONE
no permission bits are set
Definition finfo.inl:90
void AllocateExtendedInfo(Cursor &node, const system::PathString &symLinkDest, const system::PathString &symLinkRealPath)
Definition ftree.inl:263
static ALIB_DLL void FixSums(Cursor directory)
Definition ftree.cpp:261
FTree & GetFTree() const
Definition ftree.inl:672
Cursor & AsCursor()
Definition ftree.inl:711
constexpr const TChar * Terminate() const
Definition tastring.inl:660
TAString & DeleteEnd(integer regionLength)
TAString & ShortenTo(integer newLength)
Definition tastring.inl:787
integer DetectLength(integer offset=0)
Definition tastring.inl:760
void SetLength(integer newLength)
Definition tastring.inl:739
constexpr integer Length() const
Definition string.inl:316
constexpr bool IsEmpty() const
Definition string.inl:365
TChar CharAtStart() const
Definition string.inl:433
integer IndexOf(TChar needle, integer startIdx=0) const
Definition string.inl:815
TChar CharAt(integer idx) const
Definition string.inl:415
constexpr bool IsNotEmpty() const
Definition string.inl:369
constexpr const TChar * Buffer() const
Definition string.inl:311
integer LastIndexOf(TChar needle, integer startIndex=MAX_LEN) const
Definition string.inl:925
TChar CharAtEnd() const
Definition string.inl:452
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
Definition string.inl:384
int IsAbsolute() const
Definition path.inl:284
ALIB_DLL void Release(ALIB_DBG_TAKE_CI)
Definition locks.cpp:334
ALIB_DLL void ReleaseShared(ALIB_DBG_TAKE_CI)
Definition locks.cpp:381
ALIB_DLL void AcquireShared(ALIB_DBG_TAKE_CI)
Definition locks.cpp:340
ALIB_DLL void Acquire(ALIB_DBG_TAKE_CI)
Definition locks.cpp:295
static DateTime FromEpochSeconds(time_t epochSeconds)
Definition datetime.inl:63
void Import(TTimePoint timePoint)
#define ALIB_MESSAGE(domain,...)
Definition alib.inl:1064
#define IF_ALIB_THREADS(...)
Definition alib.inl:401
#define A_CHAR(STR)
#define Log_IsActive(result,...)
#define ALIB_STRING_RESETTER(astring)
#define ALIB_WARNINGS_RESTORE
Definition alib.inl:719
#define ALIB_STRINGS_TO_NARROW( src, dest, bufSize)
#define ALIB_WARNING(domain,...)
Definition alib.inl:1063
#define Log_Prune(...)
#define ALIB_ASSERT_WARNING(cond, domain,...)
Definition alib.inl:1067
#define ALIB_ERROR(domain,...)
Definition alib.inl:1062
#define Log_Verbose(...)
#define ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
Definition alib.inl:647
#define Log_SetDomain(...)
#define ALIB_DBG(...)
Definition alib.inl:853
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1066
#define ALIB_CALLER_PRUNED
Definition alib.inl:1024
#define Log_Info(...)
#define ALIB_CHARACTERS_WIDE
Definition alib.inl:888
ALIB_DLL FInfo::Qualities ScanFiles(FTree &tree, ScanParameters &parameters, std::vector< ResultsPaths > &resultPaths, SharedLock *lock)
String DBG_FILES_SCAN_VERBOSE_LOG_FORMAT
strings::TCString< PathCharType > CPathString
The string-type used with this ALib Module.
Definition path.inl:36
strings::TString< PathCharType > PathString
The string-type used with this ALib Module.
Definition path.inl:33
strings::TSubstring< PathCharType > PathSubstring
The string-type used with this ALib Module.
Definition path.inl:39
constexpr PathCharType DIRECTORY_SEPARATOR
The standard path separator character. Defaults to '\' on Windows OS, '/' else.
Definition path.inl:63
threads::SharedLock SharedLock
Type alias in namespace alib.
files::File File
Type alias in namespace alib.
Definition ftree.inl:999
LocalString< 128 > String128
Type alias name for TLocalString<character,128>.
time::DateTime DateTime
Type alias in namespace alib.
Definition datetime.inl:185
NLocalString< 8 > NString8
Type alias name for TLocalString<nchar,8>.
monomem::TLocalAllocator< TCapacityInKB > LocalAllocator
Type alias in namespace alib.
strings::TSubstring< nchar > NSubstring
Type alias in namespace alib.
strings::TCString< character > CString
Type alias in namespace alib.
Definition cstring.inl:475
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
system::Path Path
Type alias in namespace alib.
Definition path.inl:376
strings::TCString< nchar > NCString
Type alias in namespace alib.
Definition cstring.inl:484
boxing::TBoxes< MonoAllocator > BoxesMA
Type alias in namespace alib.
Definition boxes.inl:193
files::FilesCamp FILES
The singleton instance of ALib Camp class FilesCamp.
Definition filescamp.cpp:47
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2189
system::SystemErrors SystemErrors
Type alias in namespace alib.
lang::uinteger uinteger
Type alias in namespace alib.
Definition integers.inl:152
virtual bool Includes(const File &file, const system::PathString &parentPath)=0
Recursively accumulated values for directories.
Definition finfo.inl:185
uint32_t QtyErrsAccess
Number of access errors in the folder and subfolders.
Definition finfo.inl:188
uint32_t QtyErrsBrokenLink
Number of broken symbolic links in the directory and its subfolders.
Definition finfo.inl:189
constexpr DirectorySums & Add(const FInfo &finfo) noexcept
Definition finfo.inl:237
@ DeleteNode
A file or directory entry was deleted.
Definition ftree.inl:98
@ CreateNode
A file or directory entry was created.
Definition ftree.inl:97
Input parameters to function ScanFiles.
Definition fscanner.inl:20
unsigned MaxDepth
The maximum recursion depth. Defaults to InfiniteRecursion.
Definition fscanner.inl:43
SPFileFilter DirectoryFilterPreRecursion
Definition fscanner.inl:99
static constexpr unsigned InfiniteRecursion
Denotes 'infinite' recursion if set to field MaxDepth.
Definition fscanner.inl:34
SymbolicLinks LinkTreatment
Denotes how symbolic links are treated.
Definition fscanner.inl:40
@ DONT_RESOLVE
Demands not to resolve symbolic links in any way.
Definition fscanner.inl:24
Path StartPath
The path to be scanned.
Definition fscanner.inl:37
SPFileFilter DirectoryFilterPostRecursion
Definition fscanner.inl:87