Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
DirectoryIterator.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_Foundation_Common_Platform_Windows
7#include <windows.h>
8#elif qStroika_Foundation_Common_Platform_POSIX
9#include <dirent.h>
10#endif
11
17#include "Stroika/Foundation/Execution/Exceptions.h"
18#if qStroika_Foundation_Common_Platform_Windows
19#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
20#endif
23
24#include "DirectoryIterator.h"
25
26// Comment this in to turn on aggressive noisy DbgTrace in this module
27// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
28
29using namespace Stroika::Foundation;
31using namespace Stroika::Foundation::IO;
33using namespace Stroika::Foundation::Traversal;
34
36
37// from https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html -
38// To distinguish between an end-of-directory condition or an error, you must set errno to zero before calling readdir.
39
40class DirectoryIterator::Rep_ : public Iterator<filesystem::path>::IRep {
41private:
42 IteratorReturnType fIteratorReturnType_;
43 String fDirName_;
44 filesystem::path fReportPrefix_;
45#if qStroika_Foundation_Common_Platform_POSIX
46 DIR* fDirIt_{nullptr};
47 dirent* fCur_{nullptr};
48#elif qStroika_Foundation_Common_Platform_Windows
49 HANDLE fHandle_{INVALID_HANDLE_VALUE}; // after constructor - fHandle_ == INVALID_HANDLE_VALUE means iterator ATEND
50 WIN32_FIND_DATA fFindFileData_{};
51#endif
52 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fThisAssertExternallySynchronized_;
53
54public:
55 Rep_ (const String& dir, IteratorReturnType iteratorReturns)
56 : fIteratorReturnType_{iteratorReturns}
57 , fDirName_{dir}
58 , fReportPrefix_{mkReportPrefix_ (dir.As<filesystem::path> (), iteratorReturns)}
59#if qStroika_Foundation_Common_Platform_POSIX
60 , fDirIt_{::opendir (dir.AsSDKString ().c_str ())}
61#endif
62 {
63#if USE_NOISY_TRACE_IN_THIS_MODULE_
64 Debug::TraceContextBumper ctx{"DirectoryIterator::Rep_::CTOR", "'{}'"_f, dir};
65#endif
66#if qStroika_Foundation_Common_Platform_POSIX
67 if (fDirIt_ == nullptr) {
69 }
70 else {
71 errno = 0;
72 if ((fCur_ = ::readdir (fDirIt_)) == nullptr) {
73 // readdir if errno==0 just means EOF
74 FileSystem::Exception::ThrowPOSIXErrNo (errno, path (dir.As<wstring> ()));
75 }
76 }
77 if (fCur_ != nullptr and fCur_->d_name[0] == '.' and
78 (CString::Equals (fCur_->d_name, SDKSTR (".")) or CString::Equals (fCur_->d_name, SDKSTR ("..")))) {
79 optional<filesystem::path> tmphack;
80 More (&tmphack, true);
81 }
82#elif qStroika_Foundation_Common_Platform_Windows
83 fHandle_ = ::FindFirstFile ((dir + L"\\*").AsSDKString ().c_str (), &fFindFileData_);
84 while (fHandle_ != INVALID_HANDLE_VALUE and
85 (CString::Equals (fFindFileData_.cFileName, SDKSTR (".")) or CString::Equals (fFindFileData_.cFileName, SDKSTR ("..")))) {
86 optional<filesystem::path> tmphack;
87 More (&tmphack, true);
88 }
89#endif
90 }
91#if qStroika_Foundation_Common_Platform_POSIX
92 Rep_ (const String& dirName, const optional<ino_t>& curInode, IteratorReturnType iteratorReturns)
93 : fIteratorReturnType_{iteratorReturns}
94 , fDirName_{dirName}
95 , fReportPrefix_{mkReportPrefix_ (dirName.As<filesystem::path> (), iteratorReturns)}
96 , fDirIt_{::opendir (dirName.AsSDKString ().c_str ())}
97 {
98 if (fDirIt_ == nullptr) {
100 }
101#if USE_NOISY_TRACE_IN_THIS_MODULE_
102 Debug::TraceContextBumper ctx{"DirectoryIterator::Rep_::CTOR", "curInode={}"_f, curInode};
103#endif
104 if (curInode) {
105 do {
106 fCur_ = ::readdir (fDirIt_);
107 } while (fCur_ != nullptr and fCur_->d_ino != *curInode);
108 }
109 }
110#elif qStroika_Foundation_Common_Platform_Windows
111 // missing name implies Iterator::IsAtEnd ()
112 Rep_ (const String& dir, const optional<String>& name, IteratorReturnType iteratorReturns)
113 : fIteratorReturnType_{iteratorReturns}
114 , fDirName_{dir}
115 , fReportPrefix_{mkReportPrefix_ (dir.As<filesystem::path> (), iteratorReturns)}
116 {
117#if USE_NOISY_TRACE_IN_THIS_MODULE_
118 Debug::TraceContextBumper ctx{L"DirectoryIterator::Rep_::CTOR", "'{}',name={}"_f, dir, name};
119#endif
120 if (name) {
121 fHandle_ = ::FindFirstFile ((dir + L"\\*").AsSDKString ().c_str (), &fFindFileData_);
122 while (fHandle_ != INVALID_HANDLE_VALUE and String::FromSDKString (fFindFileData_.cFileName) != name) {
123 optional<filesystem::path> tmphack;
124 More (&tmphack, true);
125 }
126 }
127 }
128#endif
129 virtual ~Rep_ ()
130 {
131#if qStroika_Foundation_Common_Platform_POSIX
132 if (fDirIt_ != nullptr) {
133 ::closedir (fDirIt_);
134 }
135#elif qStroika_Foundation_Common_Platform_Windows
136 if (fHandle_ != INVALID_HANDLE_VALUE) {
137 ::FindClose (fHandle_);
138 }
139#endif
140 }
141 virtual void More (optional<filesystem::path>* result, bool advance) override
142 {
143 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
144 RequireNotNull (result);
145 *result = nullopt;
146#if qStroika_Foundation_Common_Platform_POSIX
147 if (advance) {
148 Again:
149 RequireNotNull (fCur_);
150 RequireNotNull (fDirIt_);
151 errno = 0;
152 fCur_ = ::readdir (fDirIt_);
153 if (fCur_ == nullptr) {
154 // errno can be zero here at end of directory
155 if (errno != EBADF and errno != 0) {
157 }
158 }
159 if (fCur_ != nullptr and fCur_->d_name[0] == '.' and
160 (CString::Equals (fCur_->d_name, SDKSTR (".")) or CString::Equals (fCur_->d_name, SDKSTR ("..")))) {
161 goto Again;
162 }
163 }
164 if (fCur_ != nullptr) {
165 *result = fReportPrefix_ / fCur_->d_name;
166 }
167#elif qStroika_Foundation_Common_Platform_Windows
168 if (advance) {
169 Again:
170 Require (fHandle_ != INVALID_HANDLE_VALUE);
171 (void)::memset (&fFindFileData_, 0, sizeof (fFindFileData_));
172 if (::FindNextFile (fHandle_, &fFindFileData_) == 0) {
173 ::FindClose (fHandle_);
174 fHandle_ = INVALID_HANDLE_VALUE;
175 }
176 if (fHandle_ != INVALID_HANDLE_VALUE and
177 (CString::Equals (fFindFileData_.cFileName, SDKSTR (".")) or CString::Equals (fFindFileData_.cFileName, SDKSTR ("..")))) {
178 goto Again;
179 }
180 }
181 if (fHandle_ != INVALID_HANDLE_VALUE) {
182 *result = fReportPrefix_ / fFindFileData_.cFileName;
183 }
184#endif
185 }
186 virtual bool Equals (const Iterator<filesystem::path>::IRep* rhs) const override
187 {
188 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
189 RequireNotNull (rhs);
190 RequireMember (rhs, Rep_);
191 const Rep_& rrhs = *Debug::UncheckedDynamicCast<const Rep_*> (rhs);
192#if qStroika_Foundation_Common_Platform_POSIX
193 return fDirName_ == rrhs.fDirName_ and fIteratorReturnType_ == rrhs.fIteratorReturnType_ and
194 ((fCur_ == rrhs.fCur_ and fCur_ == nullptr) or (rrhs.fCur_ != nullptr and fCur_->d_ino == rrhs.fCur_->d_ino));
195#elif qStroika_Foundation_Common_Platform_Windows
196 return fHandle_ == rrhs.fHandle_;
197#endif
198 }
199 virtual unique_ptr<IRep> Clone () const override
200 {
201#if USE_NOISY_TRACE_IN_THIS_MODULE_
202 Debug::TraceContextBumper ctx{"Entering DirectoryIterator::Rep_::Clone"};
203#endif
204 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
205#if qStroika_Foundation_Common_Platform_POSIX
206 AssertNotNull (fDirIt_);
207 /*
208 * must find telldir() returns the location of the NEXT read. We must pass along the value of telldir as
209 * of the PREVIOUS read. Essentially (seek CUR_OFF, -1);
210 *
211 * But - this API doesn't support that.
212 *
213 * This leaves us with several weak alternatives:
214 * o save the telldir, and seek to the start of the dir, and repeatedly seek until
215 * we get the same telldir, and then return the previous stored value.
216 *
217 * o substract 1 from the value of telldir()
218 *
219 * o Before each readdir() - do a telldir() to capture its value.
220 *
221 * o When cloning - compute offset in file#s by rewinding and seeking til we find the same value by
222 * name or some such, and then use that same process to re-seek in the cloned dirEnt.
223 *
224 * The 'subtract 1' approach, though cheap and simple, appears not supported by the telldir documentation,
225 * and looking at values in the current linux/gcc, seems unpromising.
226 *
227 * Between the 'telldir' before each read, and re-seek approaches, the former adds overhead even when not cloning
228 * iterators, and the later only when you use the feature. Moreover, the seek-to-start and keep looking
229 * for telldir() approach probably works out efficiently, as the most likely time to Clone () an iterator is
230 * when it is 'at start' anyhow (DirectoryIterable). So we'll start with that...
231 * -- LGP 2014-07-10
232 *
233 * This above didn't work on macos, so use the (actually simpler) approach of just opening the dir again, and scanning til we
234 * find the same inode. Not perfect (in case that is deleted) - but not sure there is a guaranteed way then.
235 */
236 return make_unique<Rep_> (fDirName_, fCur_ == nullptr ? optional<ino_t>{} : fCur_->d_ino, fIteratorReturnType_);
237#elif qStroika_Foundation_Common_Platform_Windows
238 return make_unique<Rep_> (fDirName_, fHandle_ == INVALID_HANDLE_VALUE ? optional<String>{} : String::FromSDKString (fFindFileData_.cFileName),
239 fIteratorReturnType_);
240#endif
241 }
242
243private:
244 static filesystem::path mkReportPrefix_ (const filesystem::path& dirName, IteratorReturnType iteratorReturns)
245 {
246 switch (iteratorReturns) {
247 case IteratorReturnType::eFilenameOnly:
248 return filesystem::path{};
249 case IteratorReturnType::eDirPlusFilename:
250 return dirName;
251 case IteratorReturnType::eFullPathName:
252 return filesystem::absolute (dirName);
253 default:
255 return filesystem::path{};
256 }
257 }
258};
259
260/*
261 ********************************************************************************
262 ******************** IO::FileSystem::DirectoryIterator *************************
263 ********************************************************************************
264 */
265DirectoryIterator::DirectoryIterator (const filesystem::path& directoryName, IteratorReturnType iteratorReturns)
266 : Iterator<filesystem::path>{make_unique<Rep_> (String{directoryName}, iteratorReturns)}
267{
268}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireMember(p, c)
Definition Assertions.h:326
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
bool Equals(const T *lhs, const T *rhs)
strcmp or wsccmp() as appropriate == 0
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
static String FromSDKString(const SDKChar *from)
Definition String.inl:447
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
static void ThrowPOSIXErrNo(errno_t errNo, const path &p1={}, const path &p2={})
treats errNo as a POSIX errno value, and throws a FileSystem::Exception (subclass of @std::filesystem...
Implementation detail for iterator implementors.
Definition Iterator.h:599
An Iterator<T> is a copyable object which allows traversing the contents of some container....
Definition Iterator.h:225
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...