Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
FileOutputStream.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 <unistd.h>
12#elif qStroika_Foundation_Common_Platform_Windows
13#include <io.h>
14#endif
15
19#include "Stroika/Foundation/Execution/Activity.h"
20#include "Stroika/Foundation/Execution/Common.h"
21#include "Stroika/Foundation/Execution/Exceptions.h"
22#include "Stroika/Foundation/Execution/Throw.h"
23#if qStroika_Foundation_Common_Platform_Windows
24#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
25#endif
26#include "Stroika/Foundation/Streams/InternallySynchronizedOutputStream.h"
27
28#include "Exception.h"
29
30#include "FileOutputStream.h"
31
32using std::byte;
33
34using namespace Stroika::Foundation;
36using namespace Stroika::Foundation::Debug;
37using namespace Stroika::Foundation::Execution;
38using namespace Stroika::Foundation::IO;
40using namespace Stroika::Foundation::IO::FileSystem::FileOutputStream;
41
44using Execution::ThrowSystemErrNo;
45
46#if qStroika_Foundation_Common_Platform_Windows
48#endif
49
50namespace {
51 class Rep_ : public Streams::OutputStream::IRep<byte> /*, public Memory::UseBlockAllocationIfAppropriate<Rep_>*/ {
52 public:
53 Rep_ () = delete;
54 Rep_ (const Rep_&) = delete;
55 Rep_ (const filesystem::path& fileName, AppendFlag appendFlag, FlushFlag flushFlag)
56 : fFD_{-1}
57 , fFlushFlag{flushFlag}
58 , fFileName_{fileName}
59 {
60 auto activity = LazyEvalActivity ([&] () -> String { return "opening {} for write access"_f(fFileName_); });
61 DeclareActivity currentActivity{&activity};
62#if qStroika_Foundation_Common_Platform_Windows
63 int appendFlag2Or = appendFlag == eStartFromStart ? _O_TRUNC : _O_APPEND;
64 errno_t e = ::_wsopen_s (&fFD_, fileName.generic_wstring ().c_str (), _O_WRONLY | _O_CREAT | _O_BINARY | appendFlag2Or,
65 _SH_DENYNO, _S_IREAD | _S_IWRITE);
66 if (e != 0) {
68 }
69 if (fFD_ == -1) {
71 }
72#else
73 int appendFlag2Or = appendFlag == eStartFromStart ? O_TRUNC : O_APPEND;
74 const mode_t kCreateMode_ = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
76 fFD_ = ::open (fileName.generic_string ().c_str (), O_WRONLY | O_CREAT | appendFlag2Or, kCreateMode_), fileName);
77#endif
78 }
79 Rep_ (FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy, SeekableFlag seekableFlag, FlushFlag flushFlag)
80 : fFD_{fd}
81 , fFlushFlag{flushFlag}
82 , fAdoptFDPolicy_{adoptFDPolicy}
83 , fSeekable_{seekableFlag == SeekableFlag::eSeekable}
84 {
85 }
86 ~Rep_ ()
87 {
88 IgnoreExceptionsForCall (Flush ()); // for fFlushFlag == FlushFlag::eToDisk
89 if (fAdoptFDPolicy_ == AdoptFDPolicy::eCloseOnDestruction and IsOpenWrite ()) {
90#if qStroika_Foundation_Common_Platform_Windows
91 ::_close (fFD_);
92#else
93 ::close (fFD_);
94#endif
95 }
96 }
97 nonvirtual Rep_& operator= (const Rep_&) = delete;
98 virtual bool IsSeekable () const override
99 {
100 return fSeekable_;
101 }
102 virtual void CloseWrite () override
103 {
104 if (IsOpenWrite ()) {
105 if (fAdoptFDPolicy_ == AdoptFDPolicy::eCloseOnDestruction) {
106#if qStroika_Foundation_Common_Platform_Windows
107 ::_close (fFD_);
108#else
109 ::close (fFD_);
110#endif
111 }
112 fFD_ = -1;
113 }
114 Ensure (not IsOpenWrite ());
115 }
116 virtual bool IsOpenWrite () const override
117 {
118 return fFD_ >= 0;
119 }
120 virtual void Write (span<const byte> elts) override
121 {
122 Require (not elts.empty ());
123 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
124 auto activity = LazyEvalActivity ([&] () -> String { return "writing to {}"_f(fFileName_); });
125 DeclareActivity currentActivity{&activity};
126 const byte* i = elts.data ();
127 const byte* end = elts.data () + elts.size ();
128 while (i < end) {
129#if qStroika_Foundation_Common_Platform_Windows
130 int n = ThrowPOSIXErrNoIfNegative (_write (fFD_, i, Math::PinToMaxForType<unsigned int> (end - i)));
131#else
132 int n = ThrowPOSIXErrNoIfNegative (write (fFD_, i, end - i));
133#endif
134 Assert (n <= (end - i));
135 i += n;
136 }
137 }
138 virtual void Flush () override
139 {
140 // normally nothing todo - write 'writes thru' (except if fFlushFlag)
141 if (fFlushFlag == FlushFlag::eToDisk) {
142 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
143 auto activity = LazyEvalActivity{[&] () -> String { return "flushing data to {}"_f(fFileName_); }};
144 DeclareActivity currentActivity{&activity};
145#if qStroika_Foundation_Common_Platform_POSIX
146 ThrowPOSIXErrNoIfNegative (::fsync (fFD_));
147#elif qStroika_Foundation_Common_Platform_Windows
148 ThrowIfZeroGetLastError (::FlushFileBuffers (reinterpret_cast<HANDLE> (::_get_osfhandle (fFD_))));
149#else
151#endif
152 }
153 }
154 virtual Streams::SeekOffsetType GetWriteOffset () const override
155 {
156 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
157#if qStroika_Foundation_Common_Platform_Linux
158 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, 0, SEEK_CUR)));
159#elif qStroika_Foundation_Common_Platform_Windows
160 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, 0, SEEK_CUR)));
161#else
162 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, 0, SEEK_CUR)));
163#endif
164 }
165 virtual Streams::SeekOffsetType SeekWrite (Streams::Whence whence, Streams::SignedSeekOffsetType offset) override
166 {
167 Require (fSeekable_);
168 using namespace Streams;
169 static const auto kException_ = range_error{"seek"};
170 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
171 switch (whence) {
172 case eFromStart: {
173 if (offset < 0) [[unlikely]] {
174 Execution::Throw (kException_);
175 }
176#if qStroika_Foundation_Common_Platform_Linux
177 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, offset, SEEK_SET)));
178#elif qStroika_Foundation_Common_Platform_Windows
179 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, offset, SEEK_SET)));
180#else
181 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, offset, SEEK_SET)));
182#endif
183 } break;
184 case eFromCurrent: {
185#if qStroika_Foundation_Common_Platform_Linux
186 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, offset, SEEK_CUR)));
187#elif qStroika_Foundation_Common_Platform_Windows
188 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, offset, SEEK_CUR)));
189#else
190 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, offset, SEEK_CUR)));
191#endif
192 } break;
193 case eFromEnd: {
194#if qStroika_Foundation_Common_Platform_Linux
195 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek64 (fFD_, offset, SEEK_END)));
196#elif qStroika_Foundation_Common_Platform_Windows
197 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::_lseeki64 (fFD_, offset, SEEK_END)));
198#else
199 return static_cast<Streams::SeekOffsetType> (ThrowPOSIXErrNoIfNegative (::lseek (fFD_, offset, SEEK_END)));
200#endif
201 } break;
202 }
204 return 0;
205 }
206
207 private:
208 int fFD_;
209 FlushFlag fFlushFlag;
210 AdoptFDPolicy fAdoptFDPolicy_{AdoptFDPolicy::eCloseOnDestruction};
211 bool fSeekable_{true};
212 optional<filesystem::path> fFileName_;
213 [[no_unique_address]] AssertExternallySynchronizedMutex fThisAssertExternallySynchronized_;
214 };
215}
216
217/*
218 ********************************************************************************
219 ************************* FileSystem::FileOutputStream *************************
220 ********************************************************************************
221 */
222auto FileOutputStream::New (const filesystem::path& fileName, FlushFlag flushFlag) -> Ptr
223{
224 return Ptr{Memory::MakeSharedPtr<Rep_> (fileName, AppendFlag::eDEFAULT, flushFlag)};
225}
226
227auto FileOutputStream::New (const filesystem::path& fileName, AppendFlag appendFlag, FlushFlag flushFlag) -> Ptr
228{
229 return Ptr{Memory::MakeSharedPtr<Rep_> (fileName, appendFlag, flushFlag)};
230}
231
232auto FileOutputStream::New (FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy, SeekableFlag seekableFlag, FlushFlag flushFlag) -> Ptr
233{
234 return Ptr{Memory::MakeSharedPtr<Rep_> (fd, adoptFDPolicy, seekableFlag, flushFlag)};
235}
236
237auto FileOutputStream::New (Execution::InternallySynchronized internallySynchronized, const filesystem::path& fileName, FlushFlag flushFlag) -> Ptr
238{
239 switch (internallySynchronized) {
240 case Execution::eInternallySynchronized:
241 return Streams::InternallySynchronizedOutputStream::New<Rep_> ({}, fileName, AppendFlag::eDEFAULT, flushFlag);
242 case Execution::eNotKnownInternallySynchronized:
243 return New (fileName, AppendFlag::eDEFAULT, flushFlag);
244 default:
246 return Ptr{};
247 }
248}
249
250auto FileOutputStream::New (Execution::InternallySynchronized internallySynchronized, const filesystem::path& fileName,
251 AppendFlag appendFlag, FlushFlag flushFlag) -> Ptr
252{
253 switch (internallySynchronized) {
254 case Execution::eInternallySynchronized:
255 return Streams::InternallySynchronizedOutputStream::New<Rep_> ({}, fileName, appendFlag, flushFlag);
256 case Execution::eNotKnownInternallySynchronized:
257 return New (fileName, appendFlag, flushFlag);
258 default:
260 return Ptr{};
261 }
262}
263
264auto FileOutputStream::New (Execution::InternallySynchronized internallySynchronized, FileDescriptorType fd, AdoptFDPolicy adoptFDPolicy,
265 SeekableFlag seekableFlag, FlushFlag flushFlag) -> Ptr
266{
267 switch (internallySynchronized) {
268 case Execution::eInternallySynchronized:
269 return Streams::InternallySynchronizedOutputStream::New<Rep_> ({}, fd, adoptFDPolicy, seekableFlag, flushFlag);
270 case Execution::eNotKnownInternallySynchronized:
271 return New (fd, adoptFDPolicy, seekableFlag, flushFlag);
272 default:
274 return Ptr{};
275 }
276}
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotReached()
Definition Assertions.h:385
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
Abstract interface for output stream object. Don't call directly (use Ptr usually) - but use directly...
virtual void Write(span< const ELEMENT_TYPE > elts)=0
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)