ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
fscanner.cpp
1// #################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2024 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
8
11#include "alib/alox.hpp"
12
13#if !defined(ALIB_DOX)
14
15//#define ALIB_TESTSTDFS
16
17namespace alib::files { namespace {
18
19 // forward declaration of startScan()
20 bool startScan( FTree& tree,
21 String realPath,
22 ScanParameters& params,
23 FInfo::DirectorySums& parentSums,
24 std::vector<ResultsPaths>& resultPaths);
25
26 // scan parameters used with startScan to evaluate directory entries
27 ScanParameters paramsPathOnly( nullptr, ScanParameters::SymbolicLinks::DONT_RESOLVE, 0, true, true );
28}}
29
30//--------------------------------------------------------------------------------------------------
31//--- scanFilePosix
32//--------------------------------------------------------------------------------------------------
33#if ( ( defined(__GLIBCXX__) && !defined(__MINGW32__) ) \
34 || defined(__APPLE__) \
35 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
36# include <unistd.h>
37# include <dirent.h>
38# include <sys/stat.h>
39# if !defined(__APPLE__)
40# include <sys/sysmacros.h>
41# else
42# include <sys/types.h>
43# endif
44# include <pwd.h>
45# include <fcntl.h>
46# include <pwd.h>
47# include <grp.h>
48
49#if ALIB_DEBUG
50# define DBG_CHECKERRNO \
51 ALIB_ASSERT_WARNING(errno == 0, "CAMP/FILES", "Errno set ({}){!Q}.", \
52 errno, SystemErrors(errno) ) \
53 errno= 0;
54# define DBG_CHECKERRNO_WITH_PATH \
55 ALIB_ASSERT_WARNING(errno == 0, "CAMP/FILES", "Errno set ({}){!Q}. Current path: {}", \
56 errno, SystemErrors(errno), actPath ) \
57 errno= 0;
58#else
59# define DBG_CHECKERRNO
60# define DBG_CHECKERRNO_WITH_PATH
61#endif
62
63namespace alib::files { namespace {
64void scanFilePosix( DIR* pxDir,
65 FTree::Cursor& node,
66 const CString& nameOrFullPath, // if full path, this has the same buffer as actPath!
67 unsigned int depth,
68 ScanParameters& params,
69 decltype(stat::st_dev) currentDevice,
70 FInfo::DirectorySums& parentSums ,
71 PathString& actPath,
72 std::vector<ResultsPaths>& resultPaths)
73{
74 ALIB_ASSERT_ERROR( actPath.CharAtStart()== DirectorySeparator
75 && ( actPath.Length()==1
76 || actPath.CharAtEnd() != DirectorySeparator )
77 && actPath.IndexOf(String8(DirectorySeparator).Append(DirectorySeparator)) < 0 ,
78 "CAMP/FILES","Given path not absolute or ending with '{}': {}", DirectorySeparator, actPath )
79 ALIB_DBG( PathString dbgActFile;
80 if( actPath.Buffer() == nameOrFullPath.Buffer() )
81 dbgActFile << nameOrFullPath;
82 else
83 {
84 dbgActFile << actPath;
85 if(dbgActFile.Length()>1)
86 dbgActFile << DirectorySeparator;
87 dbgActFile << nameOrFullPath;
88 } )
89 //ALIB_MESSAGE( "CAMP/FILES","[{}] {}/{} {}", &params != &paramsPathOnly ? '>':'P', depth,
90 // params.MaxDepth < std::numeric_limits<unsigned int>::max()
91 // ? String128(params.MaxDepth)
92 // : String("M"),
93 // dbgActFile )
94 ALIB_ASSERT_WARNING(errno == 0, "CAMP/FILES", "Errno set ({}){!Q} with current file: {}",
95 errno, SystemErrors(errno), dbgActFile )
96 ALIB_DBG( errno= 0;)
97
98 auto& value = node.Value();
99 auto oldQuality= value.Quality();
100
101 // ------------------------------ get stats? ------------------------------
102 if( value.Quality() == FInfo::Qualities::NONE
103 || ( value.Quality() == FInfo::Qualities::STATS
104 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE ) )
105 {
106 value.SetQuality(FInfo::Qualities::STATS);
107 PathString symLinkDest;
108 PathString symLinkDestReal;
109
110 // read base stats
111 struct stat stats;
112 int statResult= pxDir ? fstatat(dirfd(pxDir), nameOrFullPath, &stats, AT_SYMLINK_NOFOLLOW
113 #if !defined(__APPLE__)
114 | AT_NO_AUTOMOUNT
115 #endif
116 )
117 : lstat ( nameOrFullPath, &stats );
118
119 if( statResult )
120 {
121 ALIB_ASSERT_WARNING( errno != ENOENT, "CAMP/FILES", "File does not exist (anymore) while stating {!Q}",
122 dbgActFile )
123 ALIB_ASSERT_WARNING( errno == ENOENT, "CAMP/FILES", "Unknown error ({}) {!Q} while stating file {!Q}",
124 errno, SystemErrors(errno), dbgActFile )
125 ALIB_DBG( errno= 0;)
126 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
127 goto APPLY_FILTER;
128 }
129 DBG_CHECKERRNO_WITH_PATH
130
131 // check filesystem type (artificial fs & mount point)
132 if( currentDevice == 0)
133 currentDevice= stats.st_dev;
134 else if( currentDevice != stats.st_dev )
135 {
136 value.SetCrossingFS();
137 currentDevice= stats.st_dev;
138 }
139
140 if( major(stats.st_dev) == 0 // artificial?
141 && minor(stats.st_dev) != 35 ) // tmpfs included, not considered artificial!
142 value.SetArtificialFS();
143
144
145 //------------ is symlink? ------------
146 bool origFileIsSymlink= (stats.st_mode & S_IFMT) == S_IFLNK;
147 if( origFileIsSymlink
148 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE )
149 {
150 value.SetQuality( FInfo::Qualities::RESOLVED );
151
152 // 1. Read plain symlink target (only to be attached to the entry)
153 ssize_t cntChars= pxDir ? readlinkat( dirfd(pxDir), nameOrFullPath, symLinkDest.VBuffer(), PATH_MAX)
154 : readlink ( nameOrFullPath, symLinkDest.VBuffer(), PATH_MAX);
155
156 if (cntChars == -1) switch(errno)
157 {
158 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL); ALIB_DBG(errno= 0;)
159 goto ABORT_SYMLINK;
160
161 case ENOENT: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL);
162 ALIB_ASSERT_ERROR(major(stats.st_dev) == 0, "CAMP/FILES",
163 "Posix raised ({}) {!Q} on reading a symbolic link which is not located on an "
164 "artificial filesystem (like /proc). File:{!Q}",
165 errno, SystemErrors(errno), dbgActFile ) ALIB_DBG(errno= 0;)
166 goto ABORT_SYMLINK;
167
168 default: value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
169 ALIB_ERROR("CAMP/FILES", "Posix raised ({}) {!Q} on reading symbolic link {!Q}",
170 errno, SystemErrors(errno), dbgActFile ) ALIB_DBG(errno= 0;)
171 goto ABORT_SYMLINK;
172 }
173 symLinkDest.SetLength(cntChars);
174
175
176 // 2. Read symlink's real target path (fully and recursively translated)
177 ALIB_STRING_RESETTER(actPath);
178 if( pxDir )
179 actPath << DirectorySeparator << nameOrFullPath;
180 errno= 0;
181 *symLinkDestReal.VBuffer()= '\0';
182 if(! realpath(actPath.Terminate(), symLinkDestReal.VBuffer() ) ) switch (errno)
183 { // The named file does not exist.
184 case ENOENT: if( *symLinkDestReal.VBuffer() != '\0')
185 symLinkDestReal.DetectLength();
186 value.SetQuality(FInfo::Qualities::BROKEN_LINK); ALIB_DBG(errno= 0;)
187 goto ABORT_SYMLINK;
188 case ELOOP: value.SetQuality(FInfo::Qualities::CIRCULAR_LINK); ALIB_DBG(errno= 0;)
189 goto ABORT_SYMLINK;
190 // this might happen with strange system files
191 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_SL_TARGET); ALIB_DBG(errno= 0;)
192 goto ABORT_SYMLINK;
193 default: ALIB_ERROR("CAMP/FILES", "Posix raised ({}) {!Q} on resolving symbolic link {!Q}",
194 errno, SystemErrors(errno), dbgActFile ) ALIB_DBG(errno= 0;)
195 goto ABORT_SYMLINK;
196 }
197 symLinkDestReal.DetectLength();
198
199 ALIB_DBG( if( errno == EINVAL) errno= 0;) // this happens, even though realpath() above returned 'OK'
200 DBG_CHECKERRNO_WITH_PATH
201 ALIB_ASSERT_ERROR( symLinkDestReal.CharAtStart() == DirectorySeparator,
202 "CAMP/FILES", "Real path is not absolute: ", symLinkDestReal )
203
204 // 3. get resolved status
205 DBG_CHECKERRNO_WITH_PATH
206 statResult= stat(symLinkDestReal.Terminate(), &stats );
207 DBG_CHECKERRNO_WITH_PATH
208
209 if(statResult == -1 )
210 {
212 if(errno) switch( SystemErrors(errno) )
213 { case SystemErrors::enoent: value.SetQuality(FInfo::Qualities::BROKEN_LINK);
214 ALIB_DBG(errno= 0;)
215 goto APPLY_FILTER;
216 default: ALIB_WARNING("CAMP/FILES",
217 "Unhandled error code invoking 'stat()' on resolved symbolic link: {} ({!Q})\n"
218 " Symbolic link target: {!Q}", errno, SystemErrors(errno), dbgActFile )
219 ALIB_DBG(errno= 0;)
220 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
221 goto APPLY_FILTER;
222 }
224 }
225
226 // check for target artificial fs
227 if( major(stats.st_dev) == 0 // artificial?
228 && minor(stats.st_dev) != 35 ) // tempfs included!
229 value.SetTargetArtificialFS();
230
231 } // if is symlink && resolve symlinks
232
233 ABORT_SYMLINK:
234 DBG_CHECKERRNO_WITH_PATH
235
236 // 1. type
237 {
239 auto posixType= stats.st_mode & S_IFMT;
240 if( origFileIsSymlink )
241 {
242 type= posixType == S_IFDIR ? FInfo::Types::SYMBOLIC_LINK_DIR
244 }
245 else switch(stats.st_mode & S_IFMT )
246 {
247 case S_IFLNK : type= FInfo::Types::SYMBOLIC_LINK; ALIB_ERROR( "CAMP/FILES", "Impossible")
248 break;
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}",
256 dbgActFile ) break;
257 }
258 value.SetType( type );
259 }
260
261 // 2. perms
262 value.SetPerms( FInfo::Permissions(stats.st_mode & int32_t(FInfo::Permissions::MASK)) );
263
264 // 3. timestamps
265 {
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
270 #else
271 # define st_mtime_name st_mtimespec
272 # define st_ctime_name st_ctimespec
273 # define st_atime_name st_atimespec
274 #endif
275 DateTime dt;
276 dt.Import(
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} ) } );
281 value.SetMTime(dt);
282
283 dt.Import(
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} ) } );
288 value.SetCTime(dt);
289
290 dt.Import(
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} ) } );
295 value.SetATime(dt);
296 #undef st_mtime_name
297 #undef st_ctime_name
298 #undef st_atime_name
299 }
300 // 4. size
301 value.SetSize( uinteger(stats.st_size ) );
302
303 // 5. uid/gid
304 value.SetOwner( stats.st_uid );
305 value.SetGroup( stats.st_gid );
306
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 );
311
312 } // if scan stats (quality was just path)
313
314 DBG_CHECKERRNO_WITH_PATH
315
316 // Count broken link.
317 if(value.Quality() == FInfo::Qualities::BROKEN_LINK)
318 {
319 ++parentSums.QtyErrsBrokenLink;
320 goto APPLY_FILTER;
321 }
322
323 // ------------------------------ recursion with directories? ------------------------------
324 if( !value.IsDirectory()
325 || value.Quality() >= FInfo::Qualities::RECURSIVE )
326 goto APPLY_FILTER;
327
328 // stop recursion due to artificial fs?
329 if( value.IsArtificialFS() && !params.IncludeArtificialFS )
330 {
331 value.SetQuality( FInfo::Qualities::NO_AFS );
332 goto APPLY_FILTER;
333 }
334
335 // stop recursion due to crossing filesystem?
336 if( value.IsCrossingFS() && !params.CrossFileSystems )
337 {
338 value.SetQuality( FInfo::Qualities::NOT_CROSSING_FS );
339 goto APPLY_FILTER;
340 }
341
342 // stop recursion due to max depth?
343 if( depth >= params.MaxDepth )
344 {
345 value.SetQuality( FInfo::Qualities::MAX_DEPTH_REACHED );
346 ++parentSums.QtyStopsOnMaxDepth;
347 goto APPLY_FILTER;
348 }
349
350 // stop recursion due to filter
351 if( depth > 0
352 && params.DirectoryFilterPreRecursion
353 && !params.DirectoryFilterPreRecursion->Includes( node, actPath ) )
354 goto APPLY_FILTER;
355
356 // mark as recursively scanned
357 value.SetQuality( FInfo::Qualities::RECURSIVE );
358
359 // SYMLINK RECURSION
360 if ( value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR )
361 {
362 if( params.LinkTreatment != ScanParameters::SymbolicLinks::RECURSIVE
363 || value.IsArtificialFS() ) // never recurse with symlinks RESIDING on artificial fs!
364 {
365 value.SetQuality( FInfo::Qualities::NOT_FOLLOWED );
366 goto APPLY_FILTER;
367 }
368
369 if( value.TargetIsArtificialFS() && !params.IncludeArtificialFS )
370 {
371 value.SetQuality( FInfo::Qualities::NO_AFS );
372 goto APPLY_FILTER;
373 }
374
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;
381
382 goto APPLY_FILTER;
383 }
384
385 // DIRECTORY RECURSION
386 {ALIB_STRING_RESETTER( actPath );
387 if( pxDir == nullptr )
388 {
389 ALIB_ASSERT_ERROR( actPath.Buffer() == nameOrFullPath.Buffer(), "CAMP/FILES", "Internal error" )
390 actPath.SetLength(nameOrFullPath.Length());
391 }
392 else
393 {
394 if( actPath.Length() > 1 ) actPath << DirectorySeparator;
395 actPath << nameOrFullPath;
396 }
397
398 errno= 0;
399 int fd= pxDir ? openat( dirfd(pxDir), nameOrFullPath, O_RDONLY | O_DIRECTORY )
400 : open ( actPath , O_RDONLY | O_DIRECTORY );
401
402 if (fd != -1) // success?
403 {
404 DBG_CHECKERRNO_WITH_PATH
405 FInfo::DirectorySums subSums;
406 DIR* childDir = fdopendir(fd);
407 for(;;)
408 {
409 errno= 0;
410 dirent* pxEntry = readdir(childDir);
411 if( pxEntry == nullptr )
412 {
413 switch(errno)
414 {
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.
419 case 0: break;
420 case EACCES: value.SetQuality(FInfo::Qualities::NO_ACCESS_DIR);
421 break;
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 )
427 break;
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 )
431 break;
432 }
433 errno= 0;
434 break;
435 }
436
437 // skip "." and ".."
438 if( pxEntry->d_name[0] == '.'
439 && ( pxEntry->d_name[1] == '\0'
440 || ( pxEntry->d_name[1] == '.'
441 && pxEntry->d_name[2] == '\0' ) ) )
442 continue;
443
444 // recursive call
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);
450 } // dir entry loop
451 closedir(childDir);
452 DBG_CHECKERRNO_WITH_PATH
453
454 // previously scanned in lower quality?
455 if( oldQuality != FInfo::Qualities::NONE )
456 {
457 FTree::FixSums( node );
458 parentSums+= value.Sums();
459 }
460 else
461 {
462 value.SetSums(subSums);
463 parentSums+= subSums;
464 }
465 ALIB_DBG( errno= 0;)
466 goto APPLY_FILTER;
467 } // success opening director
468
469 // error with recursion
470 ALIB_ASSERT_ERROR(errno != ENOTDIR, "CAMP/FILES",
471 "Internal error opening directory. This must never happen")
472
473
474 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
475 switch (SystemErrors(errno))
476 {
477 case SystemErrors::eacces:
478 ++parentSums.QtyErrsAccess;
479 value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR );
480 errno= 0;
481 break;
482
483 default:
484 ALIB_ERROR("CAMP/FILES", "Unknown error {}({!Q}) while opening directory {!Q}",
485 errno, SystemErrors(errno), actPath)
486 value.SetQuality( FInfo::Qualities::UNKNOWN_ERROR );
487 break;
488 }
489 }
490
491 // ------------------------------------ Apply Filter ------------------------------------------
492 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 )
496 {
497 if ( value.IsDirectory() )
498 {
499 if( depth > 0
500 && ( ( params.DirectoryFilterPostRecursion
501 && !params.DirectoryFilterPostRecursion->Includes(node, actPath ) )
502 || ( params.RemoveEmptyDirectories
503 && value.Sums().Count() == 0 )
504 ) )
505 {
506 parentSums-= value.Sums();
507 if( params.RemoveEmptyDirectories )
508 {
509 node.Delete();
510 return;
511 }
512 else
513 // do not return here. Still count the type below
514 node.DeleteChildren();
515 }
516
517 }
518 else
519 {
520 if ( params.FileFilter
521 && !params.FileFilter->Includes(node, actPath ) )
522 {
523 node.Delete();
524 return;
525 }
526 }
527 }
528
529 // cnt file type
530 parentSums.Add(value);
531
532 ALIB_WARNINGS_RESTORE
533 DBG_CHECKERRNO_WITH_PATH
534} // scanFilePosix()
535}} // namespace [alib::files::anonymous]
536#undef DBG_CHECKERRNO_WITH_PATH
537
538
539//--------------------------------------------------------------------------------------------------
540//--- UNKNOWN platform, using C++17 filesystem (not all functionality given)
541//--------------------------------------------------------------------------------------------------
542#else
543# pragma message ("Unknown Platform. Using std::filesystem for scanning. In file: " __FILE__ )
544
545//#endif
546//#if !defined(TOOD_REMOVE_THIS_WITH_THE_ENDIF_ABOVE)
547
548#include "alib/compatibility/std_strings.hpp"
549#include <filesystem>
550namespace fs = std::filesystem;
551using namespace alib;
552
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__)
555 namespace
556 {
557 template <typename TP>
558 std::time_t to_time_t(TP tp)
559 {
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);
563 }
564 }
565#endif
566#if ALIB_DEBUG
567# define DBG_CHECKERRNO \
568 ALIB_ASSERT_WARNING(errno == 0, "CAMP/FILES", "Errno set ({}){!Q}.", \
569 errno, SystemErrors(errno) ) \
570 errno= 0;
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() ) \
574 errno= 0;
575#else
576# define DBG_CHECKERRNO
577# define DBG_CHECKERRNO_WITH_PATH
578#endif
579
580namespace alib::files { namespace {
581
582void scanFileStdFS( const fs::path& path,
583 FTree::Cursor& node,
584 unsigned int depth,
585 ScanParameters& params,
586 FInfo::DirectorySums& parentSums,
587 std::vector<ResultsPaths>& resultPaths )
588{
589#if defined(__MINGW32__)
590 PathString pathAsCString(path.c_str());
591 pathAsCString.Terminate();
592#else
593 CString pathAsCString(path.c_str());
594#endif
595 Substring parentPath= pathAsCString.Substring(0, pathAsCString.LastIndexOf(DirectorySeparator));
596
597 #if !defined(_WIN32)
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 )
603 #else
604 ALIB_ASSERT_ERROR( ( ( pathAsCString.CharAt(1)== ':'
605 && pathAsCString.CharAt(2)== DirectorySeparator
606 && ( pathAsCString.Length()==3
607 || pathAsCString.CharAtEnd() != DirectorySeparator) )
608
609 || ( pathAsCString.CharAt(0)== DirectorySeparator
610 && pathAsCString.CharAt(1)== DirectorySeparator
611 && ( pathAsCString.Length()==2
612 || pathAsCString.CharAtEnd() != DirectorySeparator) )
613 )
614 && pathAsCString.IndexOf(String8(DirectorySeparator).Append(DirectorySeparator), 2)<0,
615 "CAMP/FILES","Given path not absolute or ending with '{}': {}", DirectorySeparator, pathAsCString )
616 #endif
617
618
619 ALIB_MESSAGE( "CAMP/FILES","[{}] {}/{} {}", &params != &paramsPathOnly ? '>':'P', depth,
620 params.MaxDepth < (std::numeric_limits<unsigned int>::max)()
621 ? String128(params.MaxDepth)
622 : String(A_CHAR("M")),
623 pathAsCString )
624
625 std::error_code errorCode;
626 auto& value = node.Value();
627 auto oldQuality= value.Quality();
628
629 // ------------------------------ get stats? ------------------------------
630 if( value.Quality() == FInfo::Qualities::NONE
631 || ( value.Quality() == FInfo::Qualities::STATS
632 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE ) )
633 {
634 value.SetQuality( FInfo::Qualities::STATS );
635 PathString symLinkDest;
636 PathString symLinkDestReal;
637
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
641 if(errorCode)
642 {
643 ALIB_ERROR("CAMP/FILES",
644 "Unhandled error code invoking 'directory_entry::symlink_status()': {} ({!Q})\n"
645 " With file: {!Q}",
646 errorCode.value(), errorCode.message(), pathAsCString )
647 ALIB_DBG( errno= 0;)
648 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
649 goto APPLY_FILTER;
650 }
651 ALIB_WARNINGS_RESTORE
652 ALIB_DBG(errno= 0;)
653
654 //------------ is symlink? ------------
655 bool origFileIsSymlink= (stats.type() == fs::file_type::symlink);
656 if( origFileIsSymlink
657 && params.LinkTreatment != ScanParameters::SymbolicLinks::DONT_RESOLVE )
658 {
659 value.SetQuality( FInfo::Qualities::RESOLVED );
660
661 // 1. Read plain symlink target (only to be attached to the entry)
662 fs::path resolved= fs::read_symlink(path, errorCode);
663 if(errorCode)
664 {
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);
669 ALIB_DBG(errno= 0;)
670 goto ABORT_SYMLINK;
671 default: ALIB_ERROR("CAMP/FILES", "Unhandled error code invoking 'fs::read_symlink()': {} ({!Q})\n"
672 " with file: ", errorCode.value(), errorCode.message(), pathAsCString )
673 ALIB_DBG( errno= 0;)
674 value.SetQuality(FInfo::Qualities::UNKNOWN_ERROR);
675 goto APPLY_FILTER;
676 }
677 ALIB_WARNINGS_RESTORE
678 }
679 DBG_CHECKERRNO_WITH_PATH
680 symLinkDest << resolved.c_str();
681
682 // 2. Read symlink's real target path (fully and recursively translated)
683 fs::path realPath;
684 if( resolved.is_absolute() )
685 realPath= fs::canonical(resolved, errorCode);
686 else
687 {
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()))),
694 errorCode);
695 symLinkDestReal.Reset();
696 }
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;)
699
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 )
708 goto ABORT_SYMLINK;
709 }
710 ALIB_WARNINGS_RESTORE
711 DBG_CHECKERRNO_WITH_PATH
712 symLinkDestReal << realPath.c_str();
713
714 // 3. get resolved status
715 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
716 auto newStatus= fs::status(path, errorCode);
717 if(!errorCode)
718 {
719 // this happens with strange /proc files...
720 if(newStatus.type() != fs::file_type::unknown)
721 stats= newStatus;
722 }
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 )
730 goto ABORT_SYMLINK;
731 }
732 ALIB_WARNINGS_RESTORE
733
734 // check for target artificial fs
735 // Not available with std::filesystem version
736 }
737
738 ABORT_SYMLINK:
739 DBG_CHECKERRNO_WITH_PATH
740
741 // 1. type
742 {
743 auto type= FInfo::Types::UNKNOWN_OR_ERROR;
744 if( origFileIsSymlink )
745 {
746 type= is_directory(stats) ? FInfo::Types::SYMBOLIC_LINK_DIR
747 : FInfo::Types::SYMBOLIC_LINK;
748 }
749 else switch( stats.type() )
750 {
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;
758
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;
771 }
772 value.SetType( type );
773 }
774
775 // 2. perms
776 value.SetPerms( FInfo::Permissions(int32_t(stats.permissions())) );
777
778 // 3. timestamps
779 // attn: This method always follows symbolic link and uses the target's time
780 // This seems to be a confirmed behaviour:
781 // https://stackoverflow.com/questions/50778660/boost-filesystem-how-to-get-last-write-time-for-symlink-without-resolving
782 auto fsTime= std::filesystem::file_time_type(std::filesystem::file_time_type::clock::now());
783 if ( value.Quality() <= FInfo::Qualities::RESOLVED ) // no error
784 {
785 fsTime= fs::last_write_time( path, errorCode );
787 if(errorCode) switch( SystemErrors(errorCode.value()) )
788 { // This happens if with symbolic links that point to nowhere.
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);
793 break;
794
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;)
798 break;
799 }
800 ALIB_WARNINGS_RESTORE
801 }
802
803
804 #if ALIB_CPP_STANDARD == 17 || defined(__APPLE__) || defined(__ANDROID_NDK__)
805 value.SetMTime( DateTime::FromEpochSeconds( to_time_t( fsTime ) ) );
806 #else
807 value.SetMTime( DateTime::FromEpochSeconds( std::chrono::system_clock::to_time_t(
808 std::chrono::clock_cast<std::chrono::system_clock>(fsTime) ) ) );
809 #endif
810
811
812 // 4. size
813 errorCode.clear();
814 value.SetSize( symLinkDest.Length() > 0 ? uinteger(symLinkDest.Length())
815 : value.Quality() <= FInfo::Qualities::RESOLVED ? uinteger(fs::file_size(path, errorCode))
816 : 0 );
817 if( value.Size() == uinteger(-1))
818 {
819 value.SetSize(0);
820 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
821 switch( SystemErrors(errorCode.value()) )
822 {
823 // target is a directory (no error)
824 case SystemErrors::eisdir:
825 break;
826
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" )
831 break;
832
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 )
837 ALIB_DBG( errno= 0;)
838 break;
839 }
840 ALIB_WARNINGS_RESTORE
841 }
842
843 // 5. uid/gid
844 value.SetOwner( FInfo::UnknownID );
845 value.SetGroup( FInfo::UnknownID );
846
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 );
851
852 } // if scan stats (quality was just path)
853
854 DBG_CHECKERRNO_WITH_PATH
855
856 // Count broken link.
857 if(value.Quality() == FInfo::Qualities::BROKEN_LINK)
858 {
859 ++parentSums.QtyErrsBrokenLink;
860 goto APPLY_FILTER;
861 }
862
863 // ------------------------------ recursion with directories? ------------------------------
864 if( !value.IsDirectory()
865 || value.Quality() >= FInfo::Qualities::RECURSIVE )
866 goto APPLY_FILTER;
867
868
869 // stop recursion due to artificial fs?
870 // Not supported with std::filesystem!
871
872 // stop recursion due to crossing filesystem?
873 if( value.IsCrossingFS() && !params.CrossFileSystems )
874 {
875 value.SetQuality( FInfo::Qualities::NOT_CROSSING_FS );
876 goto APPLY_FILTER;
877 }
878
879 // stop recursion due to max depth?
880 if( depth >= params.MaxDepth )
881 {
882 value.SetQuality( FInfo::Qualities::MAX_DEPTH_REACHED );
883 ++parentSums.QtyStopsOnMaxDepth;
884 goto APPLY_FILTER;
885 }
886
887 // stop recursion due to filter
888 if( depth > 0
889 && params.DirectoryFilterPreRecursion
890 && !params.DirectoryFilterPreRecursion->Includes( node, parentPath ) )
891 goto APPLY_FILTER;
892
893 // mark as recursively scanned
894 value.SetQuality( FInfo::Qualities::RECURSIVE );
895
896 // SYMLINK RECURSION
897 if ( value.Type() == FInfo::Types::SYMBOLIC_LINK_DIR )
898 {
899 if( params.LinkTreatment != ScanParameters::SymbolicLinks::RECURSIVE
900 || value.IsArtificialFS() ) // never recurse with symlinks RESIDING on artificial fs!
901 {
902 value.SetQuality( FInfo::Qualities::NOT_FOLLOWED );
903 goto APPLY_FILTER;
904 }
905 else
906 {
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;
913 goto APPLY_FILTER;
914 }
915 }
916
917 // DIRECTORY RECURSION
918 {
919 fs::directory_iterator dit= fs::directory_iterator(path, errorCode);
920 if(!errorCode) // success?
921 {
922 FInfo::DirectorySums subSums;
923 for( const fs::directory_entry& childDir : dit )
924 {
925 // recursive call
926 #if defined(__MINGW32__)
927 PathString mingwBuf( childDir.path().c_str());
928 Substring childName(mingwBuf);
929 #else
930 Substring childName(CString(childDir.path().c_str()));
931 #endif
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 );
936 }
937
938 // previously scanned in lower quality?
939 if( oldQuality != FInfo::Qualities::NONE )
940 {
941 FTree::FixSums( node );
942 parentSums+= value.Sums();
943 }
944 else
945 {
946 value.SetSums(subSums);
947 parentSums+= subSums;
948 }
949 ALIB_DBG( errno= 0;)
950 goto APPLY_FILTER;
951 }
952 }
953
954 // error with recursion
955 ALIB_ASSERT_ERROR(errorCode.value() != ENOTDIR, "CAMP/FILES",
956 "Internal error opening directory. This must never happen")
957
958 ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
959 if(errorCode) switch (SystemErrors(errorCode.value()))
960 {
961 case SystemErrors::einval: // happens with /proc
962 case SystemErrors::eacces: ++parentSums.QtyErrsAccess;
963 value.SetQuality( FInfo::Qualities::NO_ACCESS_DIR );
964 ALIB_DBG( errno= 0;)
965 goto APPLY_FILTER;
966
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)
970 ALIB_DBG( errno= 0;)
971 goto APPLY_FILTER;
972 }
973 ALIB_WARNINGS_RESTORE
974 ALIB_DBG( errno= 0;)
975
976 // ------------------------------------ Apply Filter ------------------------------------------
977 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 )
981 {
982 if ( value.IsDirectory() )
983 {
984 if( depth > 0
985 && ( ( params.DirectoryFilterPostRecursion
986 && !params.DirectoryFilterPostRecursion->Includes(node, parentPath ) )
987 || ( params.RemoveEmptyDirectories
988 && value.Sums().Count() == 0 )
989 ) )
990 {
991 parentSums-= value.Sums();
992 if( params.RemoveEmptyDirectories )
993 {
994 node.Delete();
995 return;
996 }
997 else
998 node.DeleteChildren(); // do not return here. Still count the directories
999 }
1000
1001 }
1002 else
1003 {
1004 if ( params.FileFilter
1005 && !params.FileFilter->Includes(node, parentPath ) )
1006 {
1007 node.Delete();
1008 return;
1009 }
1010 }
1011 }
1012
1013 // cnt file type
1014 parentSums.Add(value);
1015
1016} // scanFileStdFS
1017
1018
1019}} // namespace [alib::files::anonymous]
1020
1021#undef DBG_CHECKERRNO_WITH_PATH
1022#endif // std::fs version
1023
1024//--------------------------------------------------------------------------------------------------
1025//--- ALL Platforms
1026//--------------------------------------------------------------------------------------------------
1027namespace alib::files {
1028
1029namespace {
1030
1031// Creates start path nodes and invokes scanFileXXX
1032bool startScan( FTree& tree,
1033 String realPath,
1034 ScanParameters& params,
1035 FInfo::DirectorySums& parentSums,
1036 std::vector<ResultsPaths>& resultPaths )
1037{
1038#if !defined(_WIN32)
1039 ALIB_ASSERT_ERROR( realPath.CharAtStart() == DirectorySeparator,
1040 "CAMP/FILES", "Real path is not absolute: ", realPath )
1041
1042 PathString path(DirectorySeparator);
1043 FTree::Cursor node = tree.Root();
1044
1045 // travel any existing portion of the path
1046 Substring pathRemainder= node.GoToTraversedPath( realPath );
1047 path << realPath.Substring(1, realPath.Length() - pathRemainder.Length() - 1);
1048#else
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 )
1054
1055 PathString path;
1056 Substring pathRemainder;
1057 FTree::Cursor node = tree.Root();
1058 if(realPath.CharAt(1) == ':')
1059 {
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 );
1064 }
1065 else
1066 {
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 );
1074 }
1075
1076#endif
1077
1078 DBG_CHECKERRNO
1079
1080
1081 // existed already?
1082 if( pathRemainder.IsEmpty() )
1083 {
1084 // For directories, call scan just for the case of having 'higher' scan parameters
1085 if( node.Value().IsDirectory())
1086 {
1087
1088 #if ( (defined(__GLIBCXX__) && !defined(__MINGW32__)) \
1089 || defined(__APPLE__) \
1090 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
1091
1092 path.Terminate();
1093 CString fullPathChildName(path);
1094 path.SetLength(path.LastIndexOf(DirectorySeparator) );
1095 scanFilePosix( nullptr, node, fullPathChildName, 0, params, 0, parentSums, path, resultPaths );
1096 #else
1097 scanFileStdFS( fs::path(std::basic_string_view<character>(path.Buffer(), size_t(path.Length()))),
1098 node, 0, params, parentSums, resultPaths );
1099 #endif
1100
1101 //resultPaths.emplace_back(ResultsPaths(realPath, node, true));
1102 }
1103
1104 return true;
1105 }
1106
1107 // did not exist already
1108 if( path.Length() > 1 )
1109 path.DeleteEnd<false>(1);
1110
1111 Tokenizer tknzr( pathRemainder, DirectorySeparator );
1112 while(tknzr.HasNext())
1113 {
1114 String name;
1115 if( path.Length() != 1 )
1116 {
1117 name= tknzr.Next();
1118 node= node.CreateChild(name);
1119 }
1120
1121
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.
1125
1126 auto detectNodeDeletion= node.Depth();
1127
1128 #if ( (defined(__GLIBCXX__) && !defined(__MINGW32__)) \
1129 || defined(__APPLE__) \
1130 || defined(__ANDROID_NDK__) ) && !defined(ALIB_TESTSTDFS)
1131
1132 if( path.IsEmpty() ) path << DirectorySeparator;
1133 CString fullPathChildName;
1134 {
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();
1139 path.Terminate();
1140 fullPathChildName= path;
1141 }
1142
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; }
1148 #else
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();
1154 #endif
1155
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));
1159
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);
1163 }
1164
1165 return false;
1166}
1167
1168} // namespace alib::files[::anonymous]
1169
1170#endif // !defined(ALIB_DOX)
1171
1172// --------------------------------------------------------------------------------------------------
1173//--- ScanFiles()
1174//--------------------------------------------------------------------------------------------------
1175enum FInfo::Qualities ScanFiles( FTree& tree,
1176 ScanParameters& parameters,
1177 std::vector<ResultsPaths>& resultPaths )
1178
1179{
1180 // todo: add some log statements, respectively replace current commented messages
1181 ALIB_DBG( if( alib::FILES.IsBootstrapped())
1182 {
1183 Log_SetDomain( "ALIB/FILES", Scope::Path)
1184 Log_SetDomain( "SCAN" , Scope::Filename)
1185 } )
1186
1187
1188 // get real path
1189 PathString path(parameters.StartPath);
1190 PathString realPath;
1191 realPath.Terminate();
1192
1193
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)
1198 {
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;
1205 }
1206 realPath.DetectLength();
1207 #else
1208 {
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()))),
1212 errorCode);
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;)
1215
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;
1225 }
1226 ALIB_WARNINGS_RESTORE
1227 realPath << fsRealPath.c_str();
1228 }
1229
1230 #endif
1231
1232 path.Reset(DirectorySeparator);
1233
1234 ALIB_DBG( errno=0; )
1235
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();
1240}
1241
1242#undef DBG_CHECKERRNO
1243
1244
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(...)
Definition alib.hpp:981
#define ALIB_STRING_RESETTER(astring)
Definition astring.hpp:2126
#define ALIB_WARNINGS_RESTORE
Definition alib.hpp:715
#define ALIB_ERROR(...)
Definition alib.hpp:980
#define ALIB_WARNINGS_ALLOW_SPARSE_ENUM_SWITCH
Definition alib.hpp:669
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:984
#define ALIB_ASSERT_WARNING(cond,...)
Definition alib.hpp:985
#define ALIB_DBG(...)
Definition alib.hpp:457
alib::strings::TLocalString< character, 512 > PathString
Definition finfo.hpp:36
Definition alib.cpp:57
files::Files FILES
Definition filescamp.cpp:30
files::ScanParameters ScanParameters
Type alias in namespace alib.
Definition fscanner.hpp:254
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.
Definition finfo.hpp:551
strings::TString< character > String
Type alias in namespace alib.
files::FTree FTree
Type alias in namespace alib.
Definition ftree.hpp:284
lang::system::SystemErrors SystemErrors
Type alias in namespace alib.
@ DONT_RESOLVE
Demands not to resolve symbolic links in any way.