Boost.Nowide
filebuf.hpp
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 {
31  BOOST_NOWIDE_DECL std::streampos ftell(FILE* file);
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  template<typename CharType, typename Traits = std::char_traits<CharType>>
48 
55  template<>
56  class basic_filebuf<char> : public std::basic_streambuf<char>
57  {
58  using Traits = std::char_traits<char>;
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  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;
80  basic_filebuf(basic_filebuf&& other) noexcept : basic_filebuf()
81  {
82  swap(other);
83  }
84  basic_filebuf& operator=(basic_filebuf&& other) noexcept
85  {
86  close();
87  swap(other);
88  return *this;
89  }
90  void swap(basic_filebuf& rhs)
91  {
92  std::basic_streambuf<char>::swap(rhs);
93  using std::swap;
94  swap(buffer_size_, rhs.buffer_size_);
95  swap(buffer_, rhs.buffer_);
96  swap(file_, rhs.file_);
97  swap(owns_buffer_, rhs.owns_buffer_);
98  swap(last_char_[0], rhs.last_char_[0]);
99  swap(mode_, rhs.mode_);
100 
101  // Fixup last_char references
102  if(pbase() == rhs.last_char_)
103  setp(last_char_, (pptr() == epptr()) ? last_char_ : last_char_ + 1);
104  if(eback() == rhs.last_char_)
105  setg(last_char_, (gptr() == rhs.last_char_) ? last_char_ : last_char_ + 1, last_char_ + 1);
106 
107  if(rhs.pbase() == last_char_)
108  rhs.setp(rhs.last_char_, (rhs.pptr() == rhs.epptr()) ? rhs.last_char_ : rhs.last_char_ + 1);
109  if(rhs.eback() == last_char_)
110  {
111  rhs.setg(rhs.last_char_,
112  (rhs.gptr() == last_char_) ? rhs.last_char_ : rhs.last_char_ + 1,
113  rhs.last_char_ + 1);
114  }
115  }
116 
117  virtual ~basic_filebuf()
118  {
119  close();
120  }
121 
125  basic_filebuf* open(const std::string& s, std::ios_base::openmode mode)
126  {
127  return open(s.c_str(), mode);
128  }
132  basic_filebuf* open(const char* s, std::ios_base::openmode mode)
133  {
134  const wstackstring name(s);
135  return open(name.get(), mode);
136  }
138  basic_filebuf* open(const wchar_t* s, std::ios_base::openmode mode)
139  {
140  if(is_open())
141  return NULL;
142  validate_cvt(this->getloc());
143  const bool ate = (mode & std::ios_base::ate) != 0;
144  if(ate)
145  mode &= ~std::ios_base::ate;
146  const wchar_t* smode = get_mode(mode);
147  if(!smode)
148  return 0;
149  file_ = detail::wfopen(s, smode);
150  if(!file_)
151  return 0;
152  if(ate && detail::fseek(file_, 0, SEEK_END) != 0)
153  {
154  close();
155  return 0;
156  }
157  mode_ = mode;
158  return this;
159  }
164  {
165  if(!is_open())
166  return NULL;
167  bool res = sync() == 0;
168  if(std::fclose(file_) != 0)
169  res = false;
170  file_ = NULL;
171  mode_ = std::ios_base::openmode(0);
172  if(owns_buffer_)
173  {
174  delete[] buffer_;
175  buffer_ = NULL;
176  owns_buffer_ = false;
177  }
178  setg(0, 0, 0);
179  setp(0, 0);
180  return res ? this : NULL;
181  }
185  bool is_open() const
186  {
187  return file_ != NULL;
188  }
189 
190  private:
191  void make_buffer()
192  {
193  if(buffer_)
194  return;
195  if(buffer_size_ > 0)
196  {
197  buffer_ = new char[buffer_size_];
198  owns_buffer_ = true;
199  }
200  }
201  void validate_cvt(const std::locale& loc)
202  {
203  if(!std::use_facet<std::codecvt<char, char, std::mbstate_t>>(loc).always_noconv())
204  throw std::runtime_error("Converting codecvts are not supported");
205  }
206 
207  protected:
208  std::streambuf* setbuf(char* s, std::streamsize n) override
209  {
210  assert(n >= 0);
211  // Maximum compatibility: Discard all local buffers and use user-provided values
212  // Users should call sync() before or better use it before any IO is done or any file is opened
213  setg(NULL, NULL, NULL);
214  setp(NULL, NULL);
215  if(owns_buffer_)
216  {
217  delete[] buffer_;
218  owns_buffer_ = false;
219  }
220  buffer_ = s;
221  buffer_size_ = (n >= 0) ? static_cast<size_t>(n) : 0;
222  return this;
223  }
224 
225  int overflow(int c = EOF) override
226  {
227  if(!(mode_ & (std::ios_base::out | std::ios_base::app)))
228  return EOF;
229 
230  if(!stop_reading())
231  return EOF;
232 
233  size_t n = pptr() - pbase();
234  if(n > 0)
235  {
236  if(std::fwrite(pbase(), 1, n, file_) != n)
237  return EOF;
238  setp(buffer_, buffer_ + buffer_size_);
239  if(c != EOF)
240  {
241  *buffer_ = Traits::to_char_type(c);
242  pbump(1);
243  }
244  } else if(c != EOF)
245  {
246  if(buffer_size_ > 0)
247  {
248  make_buffer();
249  setp(buffer_, buffer_ + buffer_size_);
250  *buffer_ = Traits::to_char_type(c);
251  pbump(1);
252  } else if(std::fputc(c, file_) == EOF)
253  {
254  return EOF;
255  } else if(!pptr())
256  {
257  // Set to dummy value so we know we have written something
258  setp(last_char_, last_char_);
259  }
260  }
261  return Traits::not_eof(c);
262  }
263 
264  int sync() override
265  {
266  if(!file_)
267  return 0;
268  bool result;
269  if(pptr())
270  {
271  result = overflow() != EOF;
272  // Only flush if anything was written, otherwise behavior of fflush is undefined
273  if(std::fflush(file_) != 0)
274  return result = false;
275  } else
276  result = stop_reading();
277  return result ? 0 : -1;
278  }
279 
280  int underflow() override
281  {
282  if(!(mode_ & std::ios_base::in))
283  return EOF;
284  if(!stop_writing())
285  return EOF;
286  // In text mode we cannot use a buffer size of more than 1 (i.e. single char only)
287  // This is due to the need to seek back in case of a sync to "put back" unread chars.
288  // However determining the number of chars to seek back is impossible in case there are newlines
289  // as we cannot know if those were converted.
290  if(buffer_size_ == 0 || !(mode_ & std::ios_base::binary))
291  {
292  const int c = std::fgetc(file_);
293  if(c == EOF)
294  return EOF;
295  last_char_[0] = Traits::to_char_type(c);
296  setg(last_char_, last_char_, last_char_ + 1);
297  } else
298  {
299  make_buffer();
300  const size_t n = std::fread(buffer_, 1, buffer_size_, file_);
301  setg(buffer_, buffer_, buffer_ + n);
302  if(n == 0)
303  return EOF;
304  }
305  return Traits::to_int_type(*gptr());
306  }
307 
308  int pbackfail(int c = EOF) override
309  {
310  if(!(mode_ & std::ios_base::in))
311  return EOF;
312  if(!stop_writing())
313  return EOF;
314  if(gptr() > eback())
315  gbump(-1);
316  else if(seekoff(-1, std::ios_base::cur) != std::streampos(std::streamoff(-1)))
317  {
318  if(underflow() == EOF)
319  return EOF;
320  } else
321  return EOF;
322 
323  // Case 1: Caller just wanted space for 1 char
324  if(c == EOF)
325  return Traits::not_eof(c);
326  // Case 2: Caller wants to put back different char
327  // gptr now points to the (potentially newly read) previous char
328  if(*gptr() != c)
329  *gptr() = Traits::to_char_type(c);
330  return Traits::not_eof(c);
331  }
332 
333  std::streampos seekoff(std::streamoff off,
334  std::ios_base::seekdir seekdir,
335  std::ios_base::openmode = std::ios_base::in | std::ios_base::out) override
336  {
337  if(!file_)
338  return EOF;
339  // Switching between input<->output requires a seek
340  // So do NOT optimize for seekoff(0, cur) as No-OP
341 
342  // On some implementations a seek also flushes, so do a full sync
343  if(sync() != 0)
344  return EOF;
345  int whence;
346  switch(seekdir)
347  {
348  case std::ios_base::beg: whence = SEEK_SET; break;
349  case std::ios_base::cur: whence = SEEK_CUR; break;
350  case std::ios_base::end: whence = SEEK_END; break;
351  default: assert(false); return EOF;
352  }
353  if(detail::fseek(file_, off, whence) != 0)
354  return EOF;
355  return detail::ftell(file_);
356  }
357  std::streampos seekpos(std::streampos pos,
358  std::ios_base::openmode m = std::ios_base::in | std::ios_base::out) override
359  {
360  // Standard mandates "as-if fsetpos", but assume the effect is the same as fseek
361  return seekoff(pos, std::ios_base::beg, m);
362  }
363  void imbue(const std::locale& loc) override
364  {
365  validate_cvt(loc);
366  }
367 
368  private:
371  bool stop_reading()
372  {
373  if(!gptr())
374  return true;
375  const auto off = gptr() - egptr();
376  setg(0, 0, 0);
377  if(!off)
378  return true;
379 #if defined(__clang__)
380 #pragma clang diagnostic push
381 #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
382 #endif
383  // coverity[result_independent_of_operands]
384  if(off > std::numeric_limits<std::streamoff>::max())
385  return false;
386 #if defined(__clang__)
387 #pragma clang diagnostic pop
388 #endif
389  return detail::fseek(file_, static_cast<std::streamoff>(off), SEEK_CUR) == 0;
390  }
391 
394  bool stop_writing()
395  {
396  if(pptr())
397  {
398  const char* const base = pbase();
399  const size_t n = pptr() - base;
400  setp(0, 0);
401  if(n && std::fwrite(base, 1, n, file_) != n)
402  return false;
403  }
404  return true;
405  }
406 
407  void reset(FILE* f = 0)
408  {
409  sync();
410  if(file_)
411  {
412  fclose(file_);
413  file_ = 0;
414  }
415  file_ = f;
416  }
417 
418  static const wchar_t* get_mode(std::ios_base::openmode mode)
419  {
420  //
421  // done according to n2914 table 106 27.9.1.4
422  //
423 
424  // note can't use switch case as overload operator can't be used
425  // in constant expression
426  if(mode == (std::ios_base::out))
427  return L"w";
428  if(mode == (std::ios_base::out | std::ios_base::app))
429  return L"a";
430  if(mode == (std::ios_base::app))
431  return L"a";
432  if(mode == (std::ios_base::out | std::ios_base::trunc))
433  return L"w";
434  if(mode == (std::ios_base::in))
435  return L"r";
436  if(mode == (std::ios_base::in | std::ios_base::out))
437  return L"r+";
438  if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
439  return L"w+";
440  if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::app))
441  return L"a+";
442  if(mode == (std::ios_base::in | std::ios_base::app))
443  return L"a+";
444  if(mode == (std::ios_base::binary | std::ios_base::out))
445  return L"wb";
446  if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::app))
447  return L"ab";
448  if(mode == (std::ios_base::binary | std::ios_base::app))
449  return L"ab";
450  if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc))
451  return L"wb";
452  if(mode == (std::ios_base::binary | std::ios_base::in))
453  return L"rb";
454  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out))
455  return L"r+b";
456  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
457  return L"w+b";
458  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::app))
459  return L"a+b";
460  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::app))
461  return L"a+b";
462  return 0;
463  }
464 
465  size_t buffer_size_;
466  char* buffer_;
467  FILE* file_;
468  bool owns_buffer_;
469  char last_char_[1];
470  std::ios::openmode mode_;
471  };
472 
477 
479  template<typename CharType, typename Traits>
481  {
482  lhs.swap(rhs);
483  }
484 
485 #endif // windows
486 
487 } // namespace nowide
488 } // namespace boost
489 
490 #endif
basic_filebuf * close()
Definition: filebuf.hpp:163
bool is_open() const
Definition: filebuf.hpp:185
This forward declaration defines the basic_filebuf type.
Definition: filebuf.hpp:47
basic_filebuf * open(const wchar_t *s, std::ios_base::openmode mode)
Opens the file with the given name, see std::filebuf::open.
Definition: filebuf.hpp:138
basic_filebuf * open(const char *s, std::ios_base::openmode mode)
Definition: filebuf.hpp:132
This is the implementation of std::filebuf.
Definition: filebuf.hpp:56
void swap(basic_filebuf< CharType, Traits > &lhs, basic_filebuf< CharType, Traits > &rhs)
Swap the basic_filebuf instances.
Definition: filebuf.hpp:480
A class that allows to create a temporary wide or narrow UTF strings from wide or narrow UTF source.
Definition: stackstring.hpp:32
basic_filebuf * open(const std::string &s, std::ios_base::openmode mode)
Definition: filebuf.hpp:125
output_char * get()
Return the converted, NULL-terminated string or NULL if no string was converted.
Definition: stackstring.hpp:127