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