Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
DirectoryIterator.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. 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
35using Execution::ThrowPOSIXErrNo;
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
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 More ();
80 }
81#elif qStroika_Foundation_Common_Platform_Windows
82 fHandle_ = ::FindFirstFile ((dir + L"\\*").AsSDKString ().c_str (), &fFindFileData_);
83 while (fHandle_ != INVALID_HANDLE_VALUE and
84 (CString::Equals (fFindFileData_.cFileName, SDKSTR (".")) or CString::Equals (fFindFileData_.cFileName, SDKSTR ("..")))) {
85 More ();
86 }
87#endif
88 }
89#if qStroika_Foundation_Common_Platform_POSIX
90 Rep_ (const String& dirName, const optional<ino_t>& curInode, IteratorReturnType iteratorReturns)
91 : fIteratorReturnType_{iteratorReturns}
92 , fDirName_{dirName}
93 , fReportPrefix_{mkReportPrefix_ (dirName.As<filesystem::path> (), iteratorReturns)}
94 , fDirIt_{::opendir (dirName.AsSDKString ().c_str ())}
95 {
96 if (fDirIt_ == nullptr) {
98 }
99#if USE_NOISY_TRACE_IN_THIS_MODULE_
100 Debug::TraceContextBumper ctx{"DirectoryIterator::Rep_::CTOR", "curInode={}"_f, curInode};
101#endif
102 if (curInode) {
103 do {
104 fCur_ = ::readdir (fDirIt_);
105 } while (fCur_ != nullptr and fCur_->d_ino != *curInode);
106 }
107 }
108#elif qStroika_Foundation_Common_Platform_Windows
109 // missing name implies Iterator::IsAtEnd ()
110 Rep_ (const String& dir, const optional<String>& name, IteratorReturnType iteratorReturns)
111 : fIteratorReturnType_{iteratorReturns}
112 , fDirName_{dir}
113 , fReportPrefix_{mkReportPrefix_ (dir.As<filesystem::path> (), iteratorReturns)}
114 {
115#if USE_NOISY_TRACE_IN_THIS_MODULE_
116 Debug::TraceContextBumper ctx{L"DirectoryIterator::Rep_::CTOR", "'{}',name={}"_f, dir, name};
117#endif
118 if (name) {
119 fHandle_ = ::FindFirstFile ((dir + L"\\*").AsSDKString ().c_str (), &fFindFileData_);
120 while (fHandle_ != INVALID_HANDLE_VALUE and String::FromSDKString (fFindFileData_.cFileName) != name) {
121 More ();
122 }
123 }
124 }
125#endif
126 virtual ~Rep_ ()
127 {
128#if qStroika_Foundation_Common_Platform_POSIX
129 if (fDirIt_ != nullptr) {
130 ::closedir (fDirIt_);
131 }
132#elif qStroika_Foundation_Common_Platform_Windows
133 if (fHandle_ != INVALID_HANDLE_VALUE) {
134 ::FindClose (fHandle_);
135 }
136#endif
137 }
138 virtual bool AtEnd () const override
139 {
140#if qStroika_Foundation_Common_Platform_POSIX
141 return fCur_ == nullptr;
142#elif qStroika_Foundation_Common_Platform_Windows
143 return fHandle_ == INVALID_HANDLE_VALUE;
144#else
146#endif
147 }
148 virtual optional<filesystem::path> Current () const override
149 {
150#if qStroika_Foundation_Common_Platform_POSIX
151 if (fCur_ == nullptr) {
152 return nullopt;
153 }
154 return fReportPrefix_ / fCur_->d_name;
155#elif qStroika_Foundation_Common_Platform_Windows
156 if (fHandle_ == INVALID_HANDLE_VALUE) {
157 return nullopt;
158 }
159 return fReportPrefix_ / fFindFileData_.cFileName;
160#else
162#endif
163 }
164 virtual optional<filesystem::path> More () override
165 {
166 Debug::AssertExternallySynchronizedChecker::WriteContext declareContext{fThisAssertExternallySynchronized_};
167#if qStroika_Foundation_Common_Platform_POSIX
168 Again:
169 RequireNotNull (fCur_);
170 RequireNotNull (fDirIt_);
171 errno = 0;
172 fCur_ = ::readdir (fDirIt_);
173 if (fCur_ == nullptr) {
174 // errno can be zero here at end of directory
175 if (errno != EBADF and errno != 0) {
177 }
178 }
179 if (fCur_ != nullptr and fCur_->d_name[0] == '.' and
180 (CString::Equals (fCur_->d_name, SDKSTR (".")) or CString::Equals (fCur_->d_name, SDKSTR ("..")))) {
181 goto Again;
182 }
183 if (fCur_ != nullptr) {
184 return fReportPrefix_ / fCur_->d_name;
185 }
186#elif qStroika_Foundation_Common_Platform_Windows
187 Again:
188 Require (fHandle_ != INVALID_HANDLE_VALUE);
189 (void)::memset (&fFindFileData_, 0, sizeof (fFindFileData_));
190 if (::FindNextFile (fHandle_, &fFindFileData_) == 0) {
191 ::FindClose (fHandle_);
192 fHandle_ = INVALID_HANDLE_VALUE;
193 }
194 if (fHandle_ != INVALID_HANDLE_VALUE and
195 (CString::Equals (fFindFileData_.cFileName, SDKSTR (".")) or CString::Equals (fFindFileData_.cFileName, SDKSTR ("..")))) {
196 goto Again;
197 }
198 if (fHandle_ != INVALID_HANDLE_VALUE) {
199 return fReportPrefix_ / fFindFileData_.cFileName;
200 }
201#endif
202 return nullopt;
203 }
204 virtual bool Equals (const Iterator<filesystem::path>::IRep* rhs) const override
205 {
206 Debug::AssertExternallySynchronizedChecker::ReadContext declareContext{fThisAssertExternallySynchronized_};
207 RequireNotNull (rhs);
208 RequireMember (rhs, Rep_);
209 const Rep_& rrhs = *Debug::UncheckedDynamicCast<const Rep_*> (rhs);
210#if qStroika_Foundation_Common_Platform_POSIX
211 return fDirName_ == rrhs.fDirName_ and fIteratorReturnType_ == rrhs.fIteratorReturnType_ and
212 ((fCur_ == rrhs.fCur_ and fCur_ == nullptr) or (rrhs.fCur_ != nullptr and fCur_->d_ino == rrhs.fCur_->d_ino));
213#elif qStroika_Foundation_Common_Platform_Windows
214 return fHandle_ == rrhs.fHandle_;
215#endif
216 }
217 virtual unique_ptr<IRep> Clone () const override
218 {
219#if USE_NOISY_TRACE_IN_THIS_MODULE_
220 Debug::TraceContextBumper ctx{"Entering DirectoryIterator::Rep_::Clone"};
221#endif
222 Debug::AssertExternallySynchronizedChecker::ReadContext declareContext{fThisAssertExternallySynchronized_};
223#if qStroika_Foundation_Common_Platform_POSIX
224 AssertNotNull (fDirIt_);
225 /*
226 * must find telldir() returns the location of the NEXT read. We must pass along the value of telldir as
227 * of the PREVIOUS read. Essentially (seek CUR_OFF, -1);
228 *
229 * But - this API doesn't support that.
230 *
231 * This leaves us with several weak alternatives:
232 * o save the telldir, and seek to the start of the dir, and repeatedly seek until
233 * we get the same telldir, and then return the previous stored value.
234 *
235 * o substract 1 from the value of telldir()
236 *
237 * o Before each readdir() - do a telldir() to capture its value.
238 *
239 * o When cloning - compute offset in file#s by rewinding and seeking til we find the same value by
240 * name or some such, and then use that same process to re-seek in the cloned dirEnt.
241 *
242 * The 'subtract 1' approach, though cheap and simple, appears not supported by the telldir documentation,
243 * and looking at values in the current linux/gcc, seems unpromising.
244 *
245 * Between the 'telldir' before each read, and re-seek approaches, the former adds overhead even when not cloning
246 * iterators, and the later only when you use the feature. Moreover, the seek-to-start and keep looking
247 * for telldir() approach probably works out efficiently, as the most likely time to Clone () an iterator is
248 * when it is 'at start' anyhow (DirectoryIterable). So we'll start with that...
249 * -- LGP 2014-07-10
250 *
251 * This above didn't work on macos, so use the (actually simpler) approach of just opening the dir again, and scanning til we
252 * find the same inode. Not perfect (in case that is deleted) - but not sure there is a guaranteed way then.
253 */
254 return make_unique<Rep_> (fDirName_, fCur_ == nullptr ? optional<ino_t>{} : fCur_->d_ino, fIteratorReturnType_);
255#elif qStroika_Foundation_Common_Platform_Windows
256 return make_unique<Rep_> (fDirName_, fHandle_ == INVALID_HANDLE_VALUE ? optional<String>{} : String::FromSDKString (fFindFileData_.cFileName),
257 fIteratorReturnType_);
258#endif
259 }
260
261private:
262 static filesystem::path mkReportPrefix_ (const filesystem::path& dirName, IteratorReturnType iteratorReturns)
263 {
264 switch (iteratorReturns) {
265 case IteratorReturnType::eFilenameOnly:
266 return filesystem::path{};
267 case IteratorReturnType::eDirPlusFilename:
268 return dirName;
269 case IteratorReturnType::eFullPathName:
270 return filesystem::absolute (dirName);
271 default:
273 return filesystem::path{};
274 }
275 }
276};
277
278/*
279 ********************************************************************************
280 ******************** IO::FileSystem::DirectoryIterator *************************
281 ********************************************************************************
282 */
283DirectoryIterator::DirectoryIterator (const filesystem::path& directoryName, IteratorReturnType iteratorReturns)
284 : Iterator<filesystem::path>{make_unique<Rep_> (String{directoryName}, iteratorReturns)}
285{
286}
#define AssertNotNull(p)
Definition Assertions.h:334
#define AssertNotImplemented()
Definition Assertions.h:402
#define RequireMember(p, c)
Definition Assertions.h:327
#define RequireNotNull(p)
Definition Assertions.h:348
#define AssertNotReached()
Definition Assertions.h:356
bool Equals(const T *lhs, const T *rhs)
strcmp or wsccmp() as appropriate == 0
#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Definition StdCompat.h:443
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:449
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
unique_lock< AssertExternallySynchronizedChecker > WriteContext
Instantiate AssertExternallySynchronizedChecker::WriteContext to designate an area of code where prot...
shared_lock< const AssertExternallySynchronizedChecker > ReadContext
Instantiate AssertExternallySynchronizedChecker::ReadContext to designate an area of code where prote...
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:616
An Iterator<T> is a copyable object which allows traversing the contents of some container.
Definition Iterator.h:253
nonvirtual const T & Current() const
Returns the value of the current item visited by the Iterator<T>, and is illegal to call if AtEnd()
Definition Iterator.inl:135
nonvirtual bool AtEnd() const
AtEnd () means there is nothing left in this iterator (a synonym for (it == container....
Definition Iterator.inl:143
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...