]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
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 | |
20effc67 | 6 | // accompanying file LICENSE or copy at |
f67539c2 TL |
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> | |
20effc67 | 18 | #include <ios> |
f67539c2 TL |
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 { | |
20effc67 TL |
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 | ||
f67539c2 TL |
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 | /// | |
20effc67 | 46 | template<typename CharType, typename Traits = std::char_traits<CharType>> |
f67539c2 TL |
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 | { | |
20effc67 | 58 | using Traits = std::char_traits<char>; |
f67539c2 TL |
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 | /// | |
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 | |
f67539c2 TL |
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 | { | |
1e59de90 | 86 | close(); |
f67539c2 TL |
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_); | |
1e59de90 | 100 | |
f67539c2 | 101 | // Fixup last_char references |
1e59de90 TL |
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_) | |
f67539c2 TL |
110 | { |
111 | rhs.setg(rhs.last_char_, | |
1e59de90 | 112 | (rhs.gptr() == last_char_) ? rhs.last_char_ : rhs.last_char_ + 1, |
f67539c2 TL |
113 | rhs.last_char_ + 1); |
114 | } | |
115 | } | |
20effc67 | 116 | |
f67539c2 TL |
117 | virtual ~basic_filebuf() |
118 | { | |
119 | close(); | |
120 | } | |
121 | ||
122 | /// | |
123 | /// Same as std::filebuf::open but s is UTF-8 string | |
124 | /// | |
125 | basic_filebuf* open(const std::string& s, std::ios_base::openmode mode) | |
126 | { | |
127 | return open(s.c_str(), mode); | |
128 | } | |
129 | /// | |
130 | /// Same as std::filebuf::open but s is UTF-8 string | |
131 | /// | |
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 | } | |
137 | /// Opens the file with the given name, see std::filebuf::open | |
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; | |
20effc67 | 152 | if(ate && detail::fseek(file_, 0, SEEK_END) != 0) |
f67539c2 TL |
153 | { |
154 | close(); | |
155 | return 0; | |
156 | } | |
157 | mode_ = mode; | |
158 | return this; | |
159 | } | |
160 | /// | |
161 | /// Same as std::filebuf::close() | |
162 | /// | |
163 | basic_filebuf* close() | |
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 | } | |
1e59de90 TL |
178 | setg(0, 0, 0); |
179 | setp(0, 0); | |
f67539c2 TL |
180 | return res ? this : NULL; |
181 | } | |
182 | /// | |
183 | /// Same as std::filebuf::is_open() | |
184 | /// | |
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 | { | |
20effc67 | 203 | if(!std::use_facet<std::codecvt<char, char, std::mbstate_t>>(loc).always_noconv()) |
f67539c2 TL |
204 | throw std::runtime_error("Converting codecvts are not supported"); |
205 | } | |
206 | ||
207 | protected: | |
20effc67 | 208 | std::streambuf* setbuf(char* s, std::streamsize n) override |
f67539c2 TL |
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_) | |
1e59de90 | 216 | { |
f67539c2 | 217 | delete[] buffer_; |
1e59de90 TL |
218 | owns_buffer_ = false; |
219 | } | |
f67539c2 TL |
220 | buffer_ = s; |
221 | buffer_size_ = (n >= 0) ? static_cast<size_t>(n) : 0; | |
222 | return this; | |
223 | } | |
224 | ||
20effc67 | 225 | int overflow(int c = EOF) override |
f67539c2 | 226 | { |
1e59de90 | 227 | if(!(mode_ & (std::ios_base::out | std::ios_base::app))) |
f67539c2 TL |
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) | |
1e59de90 | 237 | return EOF; |
f67539c2 TL |
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 | ||
20effc67 | 264 | int sync() override |
f67539c2 TL |
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 | ||
20effc67 | 280 | int underflow() override |
f67539c2 TL |
281 | { |
282 | if(!(mode_ & std::ios_base::in)) | |
283 | return EOF; | |
284 | if(!stop_writing()) | |
285 | return EOF; | |
1e59de90 TL |
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)) | |
f67539c2 TL |
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 | ||
20effc67 | 308 | int pbackfail(int c = EOF) override |
f67539c2 TL |
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 | ||
20effc67 TL |
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 | |
f67539c2 TL |
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 | } | |
20effc67 | 353 | if(detail::fseek(file_, off, whence) != 0) |
f67539c2 | 354 | return EOF; |
20effc67 | 355 | return detail::ftell(file_); |
f67539c2 | 356 | } |
20effc67 TL |
357 | std::streampos seekpos(std::streampos pos, |
358 | std::ios_base::openmode m = std::ios_base::in | std::ios_base::out) override | |
f67539c2 TL |
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 | } | |
20effc67 | 363 | void imbue(const std::locale& loc) override |
f67539c2 TL |
364 | { |
365 | validate_cvt(loc); | |
366 | } | |
367 | ||
368 | private: | |
369 | /// Stop reading adjusting the file pointer if necessary | |
370 | /// Postcondition: gptr() == NULL | |
371 | bool stop_reading() | |
372 | { | |
20effc67 TL |
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; | |
f67539c2 TL |
390 | } |
391 | ||
392 | /// Stop writing. If any bytes are to be written, writes them to file | |
393 | /// Postcondition: pptr() == NULL | |
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 | ||
473 | /// | |
474 | /// \brief Convenience typedef | |
475 | /// | |
20effc67 | 476 | using filebuf = basic_filebuf<char>; |
f67539c2 | 477 | |
1e59de90 TL |
478 | /// Swap the basic_filebuf instances |
479 | template<typename CharType, typename Traits> | |
480 | void swap(basic_filebuf<CharType, Traits>& lhs, basic_filebuf<CharType, Traits>& rhs) | |
481 | { | |
482 | lhs.swap(rhs); | |
483 | } | |
484 | ||
f67539c2 TL |
485 | #endif // windows |
486 | ||
487 | } // namespace nowide | |
488 | } // namespace boost | |
489 | ||
490 | #endif |