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  delete[] buffer_;
217  buffer_ = s;
218  buffer_size_ = (n >= 0) ? static_cast<size_t>(n) : 0;
219  return this;
220  }
221 
222  int overflow(int c = EOF) override
223  {
224  if(!(mode_ & (std::ios_base::out | std::ios_base::app)))
225  return EOF;
226 
227  if(!stop_reading())
228  return EOF;
229 
230  size_t n = pptr() - pbase();
231  if(n > 0)
232  {
233  if(std::fwrite(pbase(), 1, n, file_) != n)
234  return EOF;
235  setp(buffer_, buffer_ + buffer_size_);
236  if(c != EOF)
237  {
238  *buffer_ = Traits::to_char_type(c);
239  pbump(1);
240  }
241  } else if(c != EOF)
242  {
243  if(buffer_size_ > 0)
244  {
245  make_buffer();
246  setp(buffer_, buffer_ + buffer_size_);
247  *buffer_ = Traits::to_char_type(c);
248  pbump(1);
249  } else if(std::fputc(c, file_) == EOF)
250  {
251  return EOF;
252  } else if(!pptr())
253  {
254  // Set to dummy value so we know we have written something
255  setp(last_char_, last_char_);
256  }
257  }
258  return Traits::not_eof(c);
259  }
260 
261  int sync() override
262  {
263  if(!file_)
264  return 0;
265  bool result;
266  if(pptr())
267  {
268  result = overflow() != EOF;
269  // Only flush if anything was written, otherwise behavior of fflush is undefined
270  if(std::fflush(file_) != 0)
271  return result = false;
272  } else
273  result = stop_reading();
274  return result ? 0 : -1;
275  }
276 
277  int underflow() override
278  {
279  if(!(mode_ & std::ios_base::in))
280  return EOF;
281  if(!stop_writing())
282  return EOF;
283  // In text mode we cannot use a buffer size of more than 1 (i.e. single char only)
284  // This is due to the need to seek back in case of a sync to "put back" unread chars.
285  // However determining the number of chars to seek back is impossible in case there are newlines
286  // as we cannot know if those were converted.
287  if(buffer_size_ == 0 || !(mode_ & std::ios_base::binary))
288  {
289  const int c = std::fgetc(file_);
290  if(c == EOF)
291  return EOF;
292  last_char_[0] = Traits::to_char_type(c);
293  setg(last_char_, last_char_, last_char_ + 1);
294  } else
295  {
296  make_buffer();
297  const size_t n = std::fread(buffer_, 1, buffer_size_, file_);
298  setg(buffer_, buffer_, buffer_ + n);
299  if(n == 0)
300  return EOF;
301  }
302  return Traits::to_int_type(*gptr());
303  }
304 
305  int pbackfail(int c = EOF) override
306  {
307  if(!(mode_ & std::ios_base::in))
308  return EOF;
309  if(!stop_writing())
310  return EOF;
311  if(gptr() > eback())
312  gbump(-1);
313  else if(seekoff(-1, std::ios_base::cur) != std::streampos(std::streamoff(-1)))
314  {
315  if(underflow() == EOF)
316  return EOF;
317  } else
318  return EOF;
319 
320  // Case 1: Caller just wanted space for 1 char
321  if(c == EOF)
322  return Traits::not_eof(c);
323  // Case 2: Caller wants to put back different char
324  // gptr now points to the (potentially newly read) previous char
325  if(*gptr() != c)
326  *gptr() = Traits::to_char_type(c);
327  return Traits::not_eof(c);
328  }
329 
330  std::streampos seekoff(std::streamoff off,
331  std::ios_base::seekdir seekdir,
332  std::ios_base::openmode = std::ios_base::in | std::ios_base::out) override
333  {
334  if(!file_)
335  return EOF;
336  // Switching between input<->output requires a seek
337  // So do NOT optimize for seekoff(0, cur) as No-OP
338 
339  // On some implementations a seek also flushes, so do a full sync
340  if(sync() != 0)
341  return EOF;
342  int whence;
343  switch(seekdir)
344  {
345  case std::ios_base::beg: whence = SEEK_SET; break;
346  case std::ios_base::cur: whence = SEEK_CUR; break;
347  case std::ios_base::end: whence = SEEK_END; break;
348  default: assert(false); return EOF;
349  }
350  if(detail::fseek(file_, off, whence) != 0)
351  return EOF;
352  return detail::ftell(file_);
353  }
354  std::streampos seekpos(std::streampos pos,
355  std::ios_base::openmode m = std::ios_base::in | std::ios_base::out) override
356  {
357  // Standard mandates "as-if fsetpos", but assume the effect is the same as fseek
358  return seekoff(pos, std::ios_base::beg, m);
359  }
360  void imbue(const std::locale& loc) override
361  {
362  validate_cvt(loc);
363  }
364 
365  private:
368  bool stop_reading()
369  {
370  if(!gptr())
371  return true;
372  const auto off = gptr() - egptr();
373  setg(0, 0, 0);
374  if(!off)
375  return true;
376 #if defined(__clang__)
377 #pragma clang diagnostic push
378 #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
379 #endif
380  // coverity[result_independent_of_operands]
381  if(off > std::numeric_limits<std::streamoff>::max())
382  return false;
383 #if defined(__clang__)
384 #pragma clang diagnostic pop
385 #endif
386  return detail::fseek(file_, static_cast<std::streamoff>(off), SEEK_CUR) == 0;
387  }
388 
391  bool stop_writing()
392  {
393  if(pptr())
394  {
395  const char* const base = pbase();
396  const size_t n = pptr() - base;
397  setp(0, 0);
398  if(n && std::fwrite(base, 1, n, file_) != n)
399  return false;
400  }
401  return true;
402  }
403 
404  void reset(FILE* f = 0)
405  {
406  sync();
407  if(file_)
408  {
409  fclose(file_);
410  file_ = 0;
411  }
412  file_ = f;
413  }
414 
415  static const wchar_t* get_mode(std::ios_base::openmode mode)
416  {
417  //
418  // done according to n2914 table 106 27.9.1.4
419  //
420 
421  // note can't use switch case as overload operator can't be used
422  // in constant expression
423  if(mode == (std::ios_base::out))
424  return L"w";
425  if(mode == (std::ios_base::out | std::ios_base::app))
426  return L"a";
427  if(mode == (std::ios_base::app))
428  return L"a";
429  if(mode == (std::ios_base::out | std::ios_base::trunc))
430  return L"w";
431  if(mode == (std::ios_base::in))
432  return L"r";
433  if(mode == (std::ios_base::in | std::ios_base::out))
434  return L"r+";
435  if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
436  return L"w+";
437  if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::app))
438  return L"a+";
439  if(mode == (std::ios_base::in | std::ios_base::app))
440  return L"a+";
441  if(mode == (std::ios_base::binary | std::ios_base::out))
442  return L"wb";
443  if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::app))
444  return L"ab";
445  if(mode == (std::ios_base::binary | std::ios_base::app))
446  return L"ab";
447  if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc))
448  return L"wb";
449  if(mode == (std::ios_base::binary | std::ios_base::in))
450  return L"rb";
451  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out))
452  return L"r+b";
453  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
454  return L"w+b";
455  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::app))
456  return L"a+b";
457  if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::app))
458  return L"a+b";
459  return 0;
460  }
461 
462  size_t buffer_size_;
463  char* buffer_;
464  FILE* file_;
465  bool owns_buffer_;
466  char last_char_[1];
467  std::ios::openmode mode_;
468  };
469 
474 
476  template<typename CharType, typename Traits>
478  {
479  lhs.swap(rhs);
480  }
481 
482 #endif // windows
483 
484 } // namespace nowide
485 } // namespace boost
486 
487 #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:477
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