• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //  Copyright (c) 2012 Artyom Beilis (Tonkikh)
3 //  Copyright (c) 2019-2020 Alexander Grund
4 //
5 //  Distributed under the Boost Software License, Version 1.0. (See
6 //  accompanying file LICENSE or copy at
7 //  http://www.boost.org/LICENSE_1_0.txt)
8 //
9 #ifndef BOOST_NOWIDE_FILEBUF_HPP_INCLUDED
10 #define BOOST_NOWIDE_FILEBUF_HPP_INCLUDED
11 
12 #include <boost/nowide/config.hpp>
13 #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
14 #include <boost/nowide/cstdio.hpp>
15 #include <boost/nowide/stackstring.hpp>
16 #include <cassert>
17 #include <cstdio>
18 #include <ios>
19 #include <limits>
20 #include <locale>
21 #include <stdexcept>
22 #include <streambuf>
23 #else
24 #include <fstream>
25 #endif
26 
27 namespace boost {
28 namespace nowide {
29     namespace detail {
30         /// Same as std::ftell but potentially with Large File Support
31         BOOST_NOWIDE_DECL std::streampos ftell(FILE* file);
32         /// Same as std::fseek but potentially with Large File Support
33         BOOST_NOWIDE_DECL int fseek(FILE* file, std::streamoff offset, int origin);
34     } // namespace detail
35 
36 #if !BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT && !defined(BOOST_NOWIDE_DOXYGEN)
37     using std::basic_filebuf;
38     using std::filebuf;
39 #else // Windows
40     ///
41     /// \brief This forward declaration defines the basic_filebuf type.
42     ///
43     /// it is implemented and specialized for CharType = char, it
44     /// implements std::filebuf over standard C I/O
45     ///
46     template<typename CharType, typename Traits = std::char_traits<CharType> >
47     class basic_filebuf;
48 
49     ///
50     /// \brief This is the implementation of std::filebuf
51     ///
52     /// it is implemented and specialized for CharType = char, it
53     /// implements std::filebuf over standard C I/O
54     ///
55     template<>
56     class basic_filebuf<char> : public std::basic_streambuf<char>
57     {
58         typedef std::char_traits<char> Traits;
59 
60     public:
61 #ifdef BOOST_MSVC
62 #pragma warning(push)
63 #pragma warning(disable : 4351) // new behavior : elements of array will be default initialized
64 #endif
65         ///
66         /// Creates new filebuf
67         ///
basic_filebuf()68         basic_filebuf() :
69             buffer_size_(BUFSIZ), buffer_(0), file_(0), owns_buffer_(false), last_char_(),
70             mode_(std::ios_base::openmode(0))
71         {
72             setg(0, 0, 0);
73             setp(0, 0);
74         }
75 #ifdef BOOST_MSVC
76 #pragma warning(pop)
77 #endif
78         basic_filebuf(const basic_filebuf&) = delete;
79         basic_filebuf& operator=(const basic_filebuf&) = delete;
basic_filebuf(basic_filebuf && other)80         basic_filebuf(basic_filebuf&& other) noexcept : basic_filebuf()
81         {
82             swap(other);
83         }
operator =(basic_filebuf && other)84         basic_filebuf& operator=(basic_filebuf&& other) noexcept
85         {
86             swap(other);
87             return *this;
88         }
swap(basic_filebuf & rhs)89         void swap(basic_filebuf& rhs)
90         {
91             std::basic_streambuf<char>::swap(rhs);
92             using std::swap;
93             swap(buffer_size_, rhs.buffer_size_);
94             swap(buffer_, rhs.buffer_);
95             swap(file_, rhs.file_);
96             swap(owns_buffer_, rhs.owns_buffer_);
97             swap(last_char_[0], rhs.last_char_[0]);
98             swap(mode_, rhs.mode_);
99             // Fixup last_char references
100             if(epptr() == rhs.last_char_)
101                 setp(last_char_, last_char_);
102             if(egptr() == rhs.last_char_)
103                 rhs.setg(last_char_, gptr() == rhs.last_char_ ? last_char_ : last_char_ + 1, last_char_ + 1);
104             if(rhs.epptr() == last_char_)
105                 setp(rhs.last_char_, rhs.last_char_);
106             if(rhs.egptr() == rhs.last_char_)
107             {
108                 rhs.setg(rhs.last_char_,
109                          rhs.gptr() == last_char_ ? rhs.last_char_ : rhs.last_char_ + 1,
110                          rhs.last_char_ + 1);
111             }
112         }
113 
~basic_filebuf()114         virtual ~basic_filebuf()
115         {
116             close();
117         }
118 
119         ///
120         /// Same as std::filebuf::open but s is UTF-8 string
121         ///
open(const std::string & s,std::ios_base::openmode mode)122         basic_filebuf* open(const std::string& s, std::ios_base::openmode mode)
123         {
124             return open(s.c_str(), mode);
125         }
126         ///
127         /// Same as std::filebuf::open but s is UTF-8 string
128         ///
open(const char * s,std::ios_base::openmode mode)129         basic_filebuf* open(const char* s, std::ios_base::openmode mode)
130         {
131             const wstackstring name(s);
132             return open(name.get(), mode);
133         }
134         /// Opens the file with the given name, see std::filebuf::open
open(const wchar_t * s,std::ios_base::openmode mode)135         basic_filebuf* open(const wchar_t* s, std::ios_base::openmode mode)
136         {
137             if(is_open())
138                 return NULL;
139             validate_cvt(this->getloc());
140             const bool ate = (mode & std::ios_base::ate) != 0;
141             if(ate)
142                 mode &= ~std::ios_base::ate;
143             const wchar_t* smode = get_mode(mode);
144             if(!smode)
145                 return 0;
146             file_ = detail::wfopen(s, smode);
147             if(!file_)
148                 return 0;
149             if(ate && detail::fseek(file_, 0, SEEK_END) != 0)
150             {
151                 close();
152                 return 0;
153             }
154             mode_ = mode;
155             return this;
156         }
157         ///
158         /// Same as std::filebuf::close()
159         ///
close()160         basic_filebuf* close()
161         {
162             if(!is_open())
163                 return NULL;
164             bool res = sync() == 0;
165             if(std::fclose(file_) != 0)
166                 res = false;
167             file_ = NULL;
168             mode_ = std::ios_base::openmode(0);
169             if(owns_buffer_)
170             {
171                 delete[] buffer_;
172                 buffer_ = NULL;
173                 owns_buffer_ = false;
174             }
175             return res ? this : NULL;
176         }
177         ///
178         /// Same as std::filebuf::is_open()
179         ///
is_open() const180         bool is_open() const
181         {
182             return file_ != NULL;
183         }
184 
185     private:
make_buffer()186         void make_buffer()
187         {
188             if(buffer_)
189                 return;
190             if(buffer_size_ > 0)
191             {
192                 buffer_ = new char[buffer_size_];
193                 owns_buffer_ = true;
194             }
195         }
validate_cvt(const std::locale & loc)196         void validate_cvt(const std::locale& loc)
197         {
198             if(!std::use_facet<std::codecvt<char, char, std::mbstate_t> >(loc).always_noconv())
199                 throw std::runtime_error("Converting codecvts are not supported");
200         }
201 
202     protected:
setbuf(char * s,std::streamsize n)203         std::streambuf* setbuf(char* s, std::streamsize n) override
204         {
205             assert(n >= 0);
206             // Maximum compatibility: Discard all local buffers and use user-provided values
207             // Users should call sync() before or better use it before any IO is done or any file is opened
208             setg(NULL, NULL, NULL);
209             setp(NULL, NULL);
210             if(owns_buffer_)
211                 delete[] buffer_;
212             buffer_ = s;
213             buffer_size_ = (n >= 0) ? static_cast<size_t>(n) : 0;
214             return this;
215         }
216 
overflow(int c=EOF)217         int overflow(int c = EOF) override
218         {
219             if(!(mode_ & std::ios_base::out))
220                 return EOF;
221 
222             if(!stop_reading())
223                 return EOF;
224 
225             size_t n = pptr() - pbase();
226             if(n > 0)
227             {
228                 if(std::fwrite(pbase(), 1, n, file_) != n)
229                     return -1;
230                 setp(buffer_, buffer_ + buffer_size_);
231                 if(c != EOF)
232                 {
233                     *buffer_ = Traits::to_char_type(c);
234                     pbump(1);
235                 }
236             } else if(c != EOF)
237             {
238                 if(buffer_size_ > 0)
239                 {
240                     make_buffer();
241                     setp(buffer_, buffer_ + buffer_size_);
242                     *buffer_ = Traits::to_char_type(c);
243                     pbump(1);
244                 } else if(std::fputc(c, file_) == EOF)
245                 {
246                     return EOF;
247                 } else if(!pptr())
248                 {
249                     // Set to dummy value so we know we have written something
250                     setp(last_char_, last_char_);
251                 }
252             }
253             return Traits::not_eof(c);
254         }
255 
sync()256         int sync() override
257         {
258             if(!file_)
259                 return 0;
260             bool result;
261             if(pptr())
262             {
263                 result = overflow() != EOF;
264                 // Only flush if anything was written, otherwise behavior of fflush is undefined
265                 if(std::fflush(file_) != 0)
266                     return result = false;
267             } else
268                 result = stop_reading();
269             return result ? 0 : -1;
270         }
271 
underflow()272         int underflow() override
273         {
274             if(!(mode_ & std::ios_base::in))
275                 return EOF;
276             if(!stop_writing())
277                 return EOF;
278             if(buffer_size_ == 0)
279             {
280                 const int c = std::fgetc(file_);
281                 if(c == EOF)
282                     return EOF;
283                 last_char_[0] = Traits::to_char_type(c);
284                 setg(last_char_, last_char_, last_char_ + 1);
285             } else
286             {
287                 make_buffer();
288                 const size_t n = std::fread(buffer_, 1, buffer_size_, file_);
289                 setg(buffer_, buffer_, buffer_ + n);
290                 if(n == 0)
291                     return EOF;
292             }
293             return Traits::to_int_type(*gptr());
294         }
295 
pbackfail(int c=EOF)296         int pbackfail(int c = EOF) override
297         {
298             if(!(mode_ & std::ios_base::in))
299                 return EOF;
300             if(!stop_writing())
301                 return EOF;
302             if(gptr() > eback())
303                 gbump(-1);
304             else if(seekoff(-1, std::ios_base::cur) != std::streampos(std::streamoff(-1)))
305             {
306                 if(underflow() == EOF)
307                     return EOF;
308             } else
309                 return EOF;
310 
311             // Case 1: Caller just wanted space for 1 char
312             if(c == EOF)
313                 return Traits::not_eof(c);
314             // Case 2: Caller wants to put back different char
315             // gptr now points to the (potentially newly read) previous char
316             if(*gptr() != c)
317                 *gptr() = Traits::to_char_type(c);
318             return Traits::not_eof(c);
319         }
320 
seekoff(std::streamoff off,std::ios_base::seekdir seekdir,std::ios_base::openmode=std::ios_base::in|std::ios_base::out)321         std::streampos seekoff(std::streamoff off,
322                                std::ios_base::seekdir seekdir,
323                                std::ios_base::openmode = std::ios_base::in | std::ios_base::out) override
324         {
325             if(!file_)
326                 return EOF;
327             // Switching between input<->output requires a seek
328             // So do NOT optimize for seekoff(0, cur) as No-OP
329 
330             // On some implementations a seek also flushes, so do a full sync
331             if(sync() != 0)
332                 return EOF;
333             int whence;
334             switch(seekdir)
335             {
336             case std::ios_base::beg: whence = SEEK_SET; break;
337             case std::ios_base::cur: whence = SEEK_CUR; break;
338             case std::ios_base::end: whence = SEEK_END; break;
339             default: assert(false); return EOF;
340             }
341             if(detail::fseek(file_, off, whence) != 0)
342                 return EOF;
343             return detail::ftell(file_);
344         }
seekpos(std::streampos pos,std::ios_base::openmode m=std::ios_base::in|std::ios_base::out)345         std::streampos seekpos(std::streampos pos,
346                                std::ios_base::openmode m = std::ios_base::in | std::ios_base::out) override
347         {
348             // Standard mandates "as-if fsetpos", but assume the effect is the same as fseek
349             return seekoff(pos, std::ios_base::beg, m);
350         }
imbue(const std::locale & loc)351         void imbue(const std::locale& loc) override
352         {
353             validate_cvt(loc);
354         }
355 
356     private:
357         /// Stop reading adjusting the file pointer if necessary
358         /// Postcondition: gptr() == NULL
stop_reading()359         bool stop_reading()
360         {
361             if(!gptr())
362                 return true;
363             const auto off = gptr() - egptr();
364             setg(0, 0, 0);
365             if(!off)
366                 return true;
367 #if defined(__clang__)
368 #pragma clang diagnostic push
369 #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
370 #endif
371             // coverity[result_independent_of_operands]
372             if(off > std::numeric_limits<std::streamoff>::max())
373                 return false;
374 #if defined(__clang__)
375 #pragma clang diagnostic pop
376 #endif
377             return detail::fseek(file_, static_cast<std::streamoff>(off), SEEK_CUR) == 0;
378         }
379 
380         /// Stop writing. If any bytes are to be written, writes them to file
381         /// Postcondition: pptr() == NULL
stop_writing()382         bool stop_writing()
383         {
384             if(pptr())
385             {
386                 const char* const base = pbase();
387                 const size_t n = pptr() - base;
388                 setp(0, 0);
389                 if(n && std::fwrite(base, 1, n, file_) != n)
390                     return false;
391             }
392             return true;
393         }
394 
reset(FILE * f=0)395         void reset(FILE* f = 0)
396         {
397             sync();
398             if(file_)
399             {
400                 fclose(file_);
401                 file_ = 0;
402             }
403             file_ = f;
404         }
405 
get_mode(std::ios_base::openmode mode)406         static const wchar_t* get_mode(std::ios_base::openmode mode)
407         {
408             //
409             // done according to n2914 table 106 27.9.1.4
410             //
411 
412             // note can't use switch case as overload operator can't be used
413             // in constant expression
414             if(mode == (std::ios_base::out))
415                 return L"w";
416             if(mode == (std::ios_base::out | std::ios_base::app))
417                 return L"a";
418             if(mode == (std::ios_base::app))
419                 return L"a";
420             if(mode == (std::ios_base::out | std::ios_base::trunc))
421                 return L"w";
422             if(mode == (std::ios_base::in))
423                 return L"r";
424             if(mode == (std::ios_base::in | std::ios_base::out))
425                 return L"r+";
426             if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
427                 return L"w+";
428             if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::app))
429                 return L"a+";
430             if(mode == (std::ios_base::in | std::ios_base::app))
431                 return L"a+";
432             if(mode == (std::ios_base::binary | std::ios_base::out))
433                 return L"wb";
434             if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::app))
435                 return L"ab";
436             if(mode == (std::ios_base::binary | std::ios_base::app))
437                 return L"ab";
438             if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc))
439                 return L"wb";
440             if(mode == (std::ios_base::binary | std::ios_base::in))
441                 return L"rb";
442             if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out))
443                 return L"r+b";
444             if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
445                 return L"w+b";
446             if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::app))
447                 return L"a+b";
448             if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::app))
449                 return L"a+b";
450             return 0;
451         }
452 
453         size_t buffer_size_;
454         char* buffer_;
455         FILE* file_;
456         bool owns_buffer_;
457         char last_char_[1];
458         std::ios::openmode mode_;
459     };
460 
461     ///
462     /// \brief Convenience typedef
463     ///
464     typedef basic_filebuf<char> filebuf;
465 
466 #endif // windows
467 
468 } // namespace nowide
469 } // namespace boost
470 
471 #endif
472