Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
FileInputStream.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <fcntl.h>
7#include <sys/stat.h>
8#include <sys/types.h>
9
10#if qStroika_Foundation_Common_Platform_POSIX
11#include <poll.h>
12#include <unistd.h>
13#elif qStroika_Foundation_Common_Platform_Windows
14#include <io.h>
15#endif
16
21#include "Stroika/Foundation/Execution/Activity.h"
22#include "Stroika/Foundation/Execution/Common.h"
23#include "Stroika/Foundation/Execution/Exceptions.h"
24#include "Stroika/Foundation/Execution/Throw.h"
25#if qStroika_Foundation_Common_Platform_Windows
26#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
27#endif
29
30#include "Exception.h"
31
32#include "FileInputStream.h"
33
34using std::byte;
35
36using namespace Stroika::Foundation;
38using namespace Stroika::Foundation::Debug;
39using namespace Stroika::Foundation::Execution;
40using namespace Stroika::Foundation::IO;
42using namespace Stroika::Foundation::IO::FileSystem::FileInputStream;
43using namespace Stroika::Foundation::Streams;
44
46
47// Comment this in to turn on aggressive noisy DbgTrace in this module
48//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
49
50namespace {
51 class Rep_ : public InputStream::IRep<byte>, public Memory::UseBlockAllocationIfAppropriate<Rep_> {
52 public:
53 Rep_ () = delete;
54 Rep_ (const Rep_&) = delete;
55 Rep_ (const filesystem::path& fileName, SeekableFlag seekable)
56 : fFD_{-1}
57 , fSeekable_{seekable}
58 , fFileName_{fileName}
59 {
60 auto activity = LazyEvalActivity{[&] () -> String { return "opening {} for read access"_f(fFileName_); }};
61 DeclareActivity currentActivity{&activity};
62#if qStroika_Foundation_Common_Platform_Windows
63 errno_t e = ::_wsopen_s (&fFD_, fileName.c_str (), (O_RDONLY | O_BINARY), _SH_DENYNO, 0);
64 if (e != 0) {
66 }
67 if (fFD_ == -1) {
69 }
70#else
71 FileSystem::Exception::ThrowPOSIXErrNoIfNegative (fFD_ = ::open (fileName.generic_string ().c_str (), O_RDONLY), fileName);
72#endif
73#if USE_NOISY_TRACE_IN_THIS_MODULE_
74 DbgTrace ("opened fd: {}"_f, fFD_);
75#endif
76 }
77 Rep_ (FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy, SeekableFlag seekable)
78 : fFD_{fd}
79 , fSeekable_{seekable}
80 , fAdoptFDPolicy_{adoptFDPolicy}
81 {
82#if USE_NOISY_TRACE_IN_THIS_MODULE_
83 DbgTrace ("attached fd: {}"_f, fFD_);
84#endif
85 }
86 ~Rep_ ()
87 {
88#if USE_NOISY_TRACE_IN_THIS_MODULE_
89 Debug::TraceContextBumper ctx{"FileInputStream::Rep_::~Rep_"};
90 if (fAdoptFDPolicy_ == AdoptFDPolicy::eCloseOnDestruction and IsOpenRead ()) {
91 DbgTrace (L"closing {}"_f, fFD_);
92 }
93#endif
94 if (fAdoptFDPolicy_ == AdoptFDPolicy::eCloseOnDestruction and IsOpenRead ()) {
95#if qStroika_Foundation_Common_Platform_Windows
96 ::_close (fFD_);
97#else
98 ::close (fFD_);
99#endif
100 }
101 }
102 nonvirtual Rep_& operator= (const Rep_&) = delete;
103
104 virtual bool IsSeekable () const override
105 {
106 return fSeekable_ == eSeekable;
107 }
108 virtual void CloseRead () override
109 {
110 if (IsOpenRead ()) {
111 if (fAdoptFDPolicy_ == AdoptFDPolicy::eCloseOnDestruction) {
112#if qStroika_Foundation_Common_Platform_Windows
113 ::_close (fFD_);
114#else
115 ::close (fFD_);
116#endif
117 }
118 fFD_ = -1;
119 }
120 Ensure (not IsOpenRead ());
121 }
122 virtual bool IsOpenRead () const override
123 {
124 return fFD_ >= 0;
125 }
126 virtual optional<size_t> AvailableToRead () override
127 {
128#if qStroika_Foundation_Common_Platform_POSIX
129 pollfd pollData{fFD_, POLLIN, 0};
130 int pollResult = Execution::Handle_ErrNoResultInterruption ([&] () { return ::poll (&pollData, 1, 0); });
131 Assert (pollResult >= 0);
132 if (pollResult == 0) {
133 return nullopt; // if no data available, return nullopt
134 }
135 else {
136 // we don't know how much is available, but at least one byte. If not actually reading, just return 1
137 return 1;
138 }
139#endif
140#if qStroika_Foundation_Common_Platform_Windows
142 return nullopt;
143#endif
144 }
145 virtual optional<SeekOffsetType> RemainingLength () override
146 {
147 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
148 if (fSeekable_ == eSeekable) {
149 auto saved = GetReadOffset ();
150 auto eof = SeekRead (Whence::eFromEnd, 0);
151 SeekRead (eFromStart, saved);
152 Ensure (eof >= saved);
153 return eof - saved;
154 }
155 else {
156 return nullopt;
157 }
158 }
159 virtual optional<span<byte>> Read (span<byte> intoBuffer, NoDataAvailableHandling blockFlag) override
160 {
161 Require (not intoBuffer.empty ());
162 size_t nRequested = intoBuffer.size ();
163#if USE_NOISY_TRACE_IN_THIS_MODULE_
164 Debug::TraceContextBumper ctx{L"FileInputStream::Rep_::Read", L"nRequested: %llu", static_cast<unsigned long long> (nRequested)};
165#endif
166 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
167 auto readingFromFileActivity = LazyEvalActivity{[&] () -> String { return "reading from {}"_f(fFileName_); }};
168 DeclareActivity currentActivity{&readingFromFileActivity};
169
170 if (blockFlag == NoDataAvailableHandling::eDontBlock) {
171#if qStroika_Foundation_Common_Platform_POSIX
172 pollfd pollData{fFD_, POLLIN, 0};
173 int pollResult = Execution::Handle_ErrNoResultInterruption ([&] () { return ::poll (&pollData, 1, 0); });
174 Assert (pollResult >= 0);
175 if (pollResult == 0) {
176 return nullopt; // if no data available, return nullopt
177 }
178 else {
179 // if there is data available, safe to perform normal read = fall-thru
180 }
181#endif
182#if qStroika_Foundation_Common_Platform_Windows
183 /*
184 * For now, assume all FILE reads are already non-blocking. Not sure about this.
185 *
186 * COULD use intptr_t _get_osfhandle (int fd);
187 * to use Windows APIs, but those all seem to require the file to be opened a special way to do async reads.
188 *
189 * Tried:
190 * int oldFileFlags = ::fcntl (fFD_, F_GETFL, 0);
191 * if (fcntl (fFD_, F_SETFL, oldFileFlags | O_NONBLOCK))
192 * ;
193 * [[maybe_unused]] auto&& cleanup = Execution::Finally ([this]() noexcept {
194 * fcntl (fFD_, F_SETFL, oldFileFlags);
195 * });
196 *
197 * but windows doesn't appear to support fcntl()
198 */
199#endif
200 }
201
202 /*
203 * Standard blocking read
204 */
205#if qStroika_Foundation_Common_Platform_Windows
206 return intoBuffer.subspan (0, static_cast<size_t> (ThrowPOSIXErrNoIfNegative (
207 ::_read (fFD_, intoBuffer.data (), Math::PinToMaxForType<unsigned int> (nRequested)))));
208#else
209 return intoBuffer.subspan (0, static_cast<size_t> (ThrowPOSIXErrNoIfNegative (::read (fFD_, intoBuffer.data (), nRequested))));
210#endif
211 }
212 virtual Streams::SeekOffsetType GetReadOffset () const override
213 {
214 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
215#if qStroika_Foundation_Common_Platform_Windows
216 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, 0, SEEK_CUR)));
217#elif qStroika_Foundation_Common_Platform_Linux
218 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, 0, SEEK_CUR)));
219#else
220 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, 0, SEEK_CUR)));
221#endif
222 }
223 virtual Streams::SeekOffsetType SeekRead (Streams::Whence whence, Streams::SignedSeekOffsetType offset) override
224 {
225 using namespace Streams;
226#if USE_NOISY_TRACE_IN_THIS_MODULE_
227 Debug::TraceContextBumper ctx{"FileInputStream::Rep_::SeekRead", "whence: {}, offset: {}", whence, offset};
228#endif
229 static const auto kException_ = range_error{"seek"};
230 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
231 switch (whence) {
232 case eFromStart: {
233 if (offset < 0) [[unlikely]] {
234 Execution::Throw (kException_);
235 }
236#if qStroika_Foundation_Common_Platform_Windows
237 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, offset, SEEK_SET)));
238#elif qStroika_Foundation_Common_Platform_Linux
239 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, offset, SEEK_SET)));
240#else
241 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, offset, SEEK_SET)));
242#endif
243 } break;
244 case eFromCurrent: {
245#if qStroika_Foundation_Common_Platform_Windows
246 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, offset, SEEK_CUR)));
247#elif qStroika_Foundation_Common_Platform_Linux
248 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, offset, SEEK_CUR)));
249#else
250 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, offset, SEEK_CUR)));
251#endif
252 } break;
253 case eFromEnd: {
254#if qStroika_Foundation_Common_Platform_Windows
255 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, offset, SEEK_END)));
256#elif qStroika_Foundation_Common_Platform_Linux
257 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, offset, SEEK_END)));
258#else
259 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, offset, SEEK_END)));
260#endif
261 } break;
262 }
264 return 0;
265 }
266
267 private:
268 int fFD_;
269 SeekableFlag fSeekable_;
270 AdoptFDPolicy fAdoptFDPolicy_{AdoptFDPolicy::eCloseOnDestruction};
271 optional<filesystem::path> fFileName_;
272 [[no_unique_address]] AssertExternallySynchronizedMutex fThisAssertExternallySynchronized_;
273 };
274}
275
276/*
277 ********************************************************************************
278 **************************** FileSystem::FileInputStream ***********************
279 ********************************************************************************
280 */
281auto FileInputStream::New (const filesystem::path& fileName, SeekableFlag seekable) -> Ptr
282{
283 return Ptr{Memory::MakeSharedPtr<Rep_> (fileName, seekable)};
284}
285
286auto FileInputStream::New (FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy, SeekableFlag seekable) -> Ptr
287{
288 return Ptr{Memory::MakeSharedPtr<Rep_> (fd, adoptFDPolicy, seekable)};
289}
290
291auto FileInputStream::New (Execution::InternallySynchronized internallySynchronized, const filesystem::path& fileName, SeekableFlag seekable) -> Ptr
292{
293 switch (internallySynchronized) {
294 case Execution::eInternallySynchronized:
295 return Streams::InternallySynchronizedInputStream::New<Rep_> ({}, fileName, seekable);
296 case Execution::eNotKnownInternallySynchronized:
297 return New (fileName, seekable);
298 default:
300 return Ptr{};
301 }
302}
303
304auto FileInputStream::New (Execution::InternallySynchronized internallySynchronized, FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy,
305 SeekableFlag seekable) -> Ptr
306{
307 switch (internallySynchronized) {
308 case Execution::eInternallySynchronized:
309 return Streams::InternallySynchronizedInputStream::New<Rep_> ({}, fd, adoptFDPolicy, seekable);
310 case Execution::eNotKnownInternallySynchronized:
311 return New (fd, adoptFDPolicy, seekable);
312 default:
314 return New (fd, adoptFDPolicy, seekable);
315 }
316}
317
318InputStream::Ptr<byte> FileInputStream::New (const filesystem::path& fileName, SeekableFlag seekable, BufferFlag bufferFlag)
319{
320#if USE_NOISY_TRACE_IN_THIS_MODULE_
321 Debug::TraceContextBumper ctx{"FileInputStream::New", "fileName: {}, seekable: {}, bufferFlag: {}", fileName, seekable, bufferFlag};
322#endif
323 InputStream::Ptr<byte> in = FileInputStream::New (fileName, seekable);
324 switch (bufferFlag) {
325 case eBuffered:
326 return Streams::BufferedInputStream::New<byte> (in);
327 case eUnbuffered:
328 return in;
329 default:
331 return in;
332 }
333}
334
335InputStream::Ptr<byte> FileInputStream::New (FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy, SeekableFlag seekable, BufferFlag bufferFlag)
336{
337#if USE_NOISY_TRACE_IN_THIS_MODULE_
338 Debug::TraceContextBumper ctx{"FileInputStream::New", "fd: {}, seekable: {}, bufferFlag: {}"_f, fd, seekable, bufferFlag};
339#endif
340 InputStream::Ptr<byte> in = FileInputStream::New (fd, adoptFDPolicy, seekable);
341 switch (bufferFlag) {
342 case eBuffered:
343 return Streams::BufferedInputStream::New<byte> (in);
344 case eUnbuffered:
345 return in;
346 default:
348 return in;
349 }
350}
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotReached()
Definition Assertions.h:385
#define AssertNotReached()
Definition Assertions.h:355
conditional_t< qStroika_Foundation_Memory_PreferBlockAllocation and andTrueCheck, BlockAllocationUseHelper< T >, Common::Empty > UseBlockAllocationIfAppropriate
Use this to enable block allocation for a particular class. Beware of subclassing.
NoDataAvailableHandling
If eDontBlock passed to most Stream APIs, then when the code would do a blocking read,...
Definition Stream.h:90
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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 INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode, const path &p1={}, const path &p2={})
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...
static void ThrowSystemErrNo(int sysErr, const path &p1={}, const path &p2={})
treats errNo as a platform-defined error number, and throws a FileSystem::Exception (subclass of @std...
virtual bool IsSeekable() const =0
virtual SeekOffsetType GetReadOffset() const =0
virtual optional< span< ElementType > > Read(span< ElementType > intoBuffer, NoDataAvailableHandling blockFlag)=0
virtual optional< SeekOffsetType > RemainingLength()
returns nullopt if not known (typical, and the default) - but sometimes it is known,...
virtual optional< size_t > AvailableToRead()
returns nullopt if nothing known available, zero if known EOF, and any other number of elements (typi...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
A Streams::Ptr<ELEMENT_TYPE> is a smart-pointer to a stream of elements of type T.
Definition Stream.h:170
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Handle_ErrNoResultInterruption(CALL call) -> decltype(call())
Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from th...
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)