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  swap(other);
87  return *this;
88  }
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 
114  virtual ~basic_filebuf()
115  {
116  close();
117  }
118 
122  basic_filebuf* open(const std::string& s, std::ios_base::openmode mode)
123  {
124  return open(s.c_str(), mode);
125  }
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  }
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  }
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  }
180  bool is_open() const
181  {
182  return file_ != NULL;
183  }
184 
185  private:
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  }
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:
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 
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 
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 
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 
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 
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  }
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  }
351  void imbue(const std::locale& loc) override
352  {
353  validate_cvt(loc);
354  }
355 
356  private:
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 
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 
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 
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 
465 
466 #endif // windows
467 
468 } // namespace nowide
469 } // namespace boost
470 
471 #endif
basic_filebuf * close()
Definition: filebuf.hpp:160
bool is_open() const
Definition: filebuf.hpp:180
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:135
basic_filebuf * open(const char *s, std::ios_base::openmode mode)
Definition: filebuf.hpp:129
This is the implementation of std::filebuf.
Definition: filebuf.hpp:56
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:122
output_char * get()
Return the converted, NULL-terminated string or NULL if no string was converted.
Definition: stackstring.hpp:127