filebuf.hpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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. #include <boost/nowide/config.hpp>
  12. #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
  13. #include <boost/nowide/cstdio.hpp>
  14. #include <boost/nowide/stackstring.hpp>
  15. #include <cassert>
  16. #include <cstdio>
  17. #include <ios>
  18. #include <limits>
  19. #include <locale>
  20. #include <stdexcept>
  21. #include <streambuf>
  22. #else
  23. #include <fstream>
  24. #endif
  25. namespace boost {
  26. namespace nowide {
  27. namespace detail {
  28. /// Same as std::ftell but potentially with Large File Support
  29. BOOST_NOWIDE_DECL std::streampos ftell(FILE* file);
  30. /// Same as std::fseek but potentially with Large File Support
  31. BOOST_NOWIDE_DECL int fseek(FILE* file, std::streamoff offset, int origin);
  32. } // namespace detail
  33. #if !BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT && !defined(BOOST_NOWIDE_DOXYGEN)
  34. using std::basic_filebuf;
  35. using std::filebuf;
  36. #else // Windows
  37. ///
  38. /// \brief This forward declaration defines the basic_filebuf type.
  39. ///
  40. /// it is implemented and specialized for CharType = char, it
  41. /// implements std::filebuf over standard C I/O
  42. ///
  43. template<typename CharType, typename Traits = std::char_traits<CharType>>
  44. class basic_filebuf;
  45. ///
  46. /// \brief This is the implementation of std::filebuf
  47. ///
  48. /// it is implemented and specialized for CharType = char, it
  49. /// implements std::filebuf over standard C I/O
  50. ///
  51. template<>
  52. class basic_filebuf<char> : public std::basic_streambuf<char>
  53. {
  54. using Traits = std::char_traits<char>;
  55. public:
  56. #ifdef BOOST_MSVC
  57. #pragma warning(push)
  58. #pragma warning(disable : 4351) // new behavior : elements of array will be default initialized
  59. #endif
  60. ///
  61. /// Creates new filebuf
  62. ///
  63. basic_filebuf() :
  64. buffer_size_(BUFSIZ), buffer_(0), file_(0), owns_buffer_(false), last_char_(),
  65. mode_(std::ios_base::openmode(0))
  66. {
  67. setg(0, 0, 0);
  68. setp(0, 0);
  69. }
  70. #ifdef BOOST_MSVC
  71. #pragma warning(pop)
  72. #endif
  73. basic_filebuf(const basic_filebuf&) = delete;
  74. basic_filebuf& operator=(const basic_filebuf&) = delete;
  75. basic_filebuf(basic_filebuf&& other) noexcept : basic_filebuf()
  76. {
  77. swap(other);
  78. }
  79. basic_filebuf& operator=(basic_filebuf&& other) noexcept
  80. {
  81. swap(other);
  82. return *this;
  83. }
  84. void swap(basic_filebuf& rhs)
  85. {
  86. std::basic_streambuf<char>::swap(rhs);
  87. using std::swap;
  88. swap(buffer_size_, rhs.buffer_size_);
  89. swap(buffer_, rhs.buffer_);
  90. swap(file_, rhs.file_);
  91. swap(owns_buffer_, rhs.owns_buffer_);
  92. swap(last_char_[0], rhs.last_char_[0]);
  93. swap(mode_, rhs.mode_);
  94. // Fixup last_char references
  95. if(pbase() == rhs.last_char_)
  96. setp(last_char_, (pptr() == epptr()) ? last_char_ : last_char_ + 1);
  97. if(eback() == rhs.last_char_)
  98. setg(last_char_, (gptr() == rhs.last_char_) ? last_char_ : last_char_ + 1, last_char_ + 1);
  99. if(rhs.pbase() == last_char_)
  100. rhs.setp(rhs.last_char_, (rhs.pptr() == rhs.epptr()) ? rhs.last_char_ : rhs.last_char_ + 1);
  101. if(rhs.eback() == last_char_)
  102. {
  103. rhs.setg(rhs.last_char_,
  104. (rhs.gptr() == last_char_) ? rhs.last_char_ : rhs.last_char_ + 1,
  105. rhs.last_char_ + 1);
  106. }
  107. }
  108. virtual ~basic_filebuf()
  109. {
  110. close();
  111. }
  112. ///
  113. /// Same as std::filebuf::open but s is UTF-8 string
  114. ///
  115. basic_filebuf* open(const std::string& s, std::ios_base::openmode mode)
  116. {
  117. return open(s.c_str(), mode);
  118. }
  119. ///
  120. /// Same as std::filebuf::open but s is UTF-8 string
  121. ///
  122. basic_filebuf* open(const char* s, std::ios_base::openmode mode)
  123. {
  124. const wstackstring name(s);
  125. return open(name.get(), mode);
  126. }
  127. /// Opens the file with the given name, see std::filebuf::open
  128. basic_filebuf* open(const wchar_t* s, std::ios_base::openmode mode)
  129. {
  130. if(is_open())
  131. return NULL;
  132. validate_cvt(this->getloc());
  133. const bool ate = (mode & std::ios_base::ate) != 0;
  134. if(ate)
  135. mode &= ~std::ios_base::ate;
  136. const wchar_t* smode = get_mode(mode);
  137. if(!smode)
  138. return 0;
  139. file_ = detail::wfopen(s, smode);
  140. if(!file_)
  141. return 0;
  142. if(ate && detail::fseek(file_, 0, SEEK_END) != 0)
  143. {
  144. close();
  145. return 0;
  146. }
  147. mode_ = mode;
  148. return this;
  149. }
  150. ///
  151. /// Same as std::filebuf::close()
  152. ///
  153. basic_filebuf* close()
  154. {
  155. if(!is_open())
  156. return NULL;
  157. bool res = sync() == 0;
  158. if(std::fclose(file_) != 0)
  159. res = false;
  160. file_ = NULL;
  161. mode_ = std::ios_base::openmode(0);
  162. if(owns_buffer_)
  163. {
  164. delete[] buffer_;
  165. buffer_ = NULL;
  166. owns_buffer_ = false;
  167. }
  168. setg(0, 0, 0);
  169. setp(0, 0);
  170. return res ? this : NULL;
  171. }
  172. ///
  173. /// Same as std::filebuf::is_open()
  174. ///
  175. bool is_open() const
  176. {
  177. return file_ != NULL;
  178. }
  179. private:
  180. void make_buffer()
  181. {
  182. if(buffer_)
  183. return;
  184. if(buffer_size_ > 0)
  185. {
  186. buffer_ = new char[buffer_size_];
  187. owns_buffer_ = true;
  188. }
  189. }
  190. void validate_cvt(const std::locale& loc)
  191. {
  192. if(!std::use_facet<std::codecvt<char, char, std::mbstate_t>>(loc).always_noconv())
  193. throw std::runtime_error("Converting codecvts are not supported");
  194. }
  195. protected:
  196. std::streambuf* setbuf(char* s, std::streamsize n) override
  197. {
  198. assert(n >= 0);
  199. // Maximum compatibility: Discard all local buffers and use user-provided values
  200. // Users should call sync() before or better use it before any IO is done or any file is opened
  201. setg(NULL, NULL, NULL);
  202. setp(NULL, NULL);
  203. if(owns_buffer_)
  204. delete[] buffer_;
  205. buffer_ = s;
  206. buffer_size_ = (n >= 0) ? static_cast<size_t>(n) : 0;
  207. return this;
  208. }
  209. int overflow(int c = EOF) override
  210. {
  211. if(!(mode_ & std::ios_base::out))
  212. return EOF;
  213. if(!stop_reading())
  214. return EOF;
  215. size_t n = pptr() - pbase();
  216. if(n > 0)
  217. {
  218. if(std::fwrite(pbase(), 1, n, file_) != n)
  219. return EOF;
  220. setp(buffer_, buffer_ + buffer_size_);
  221. if(c != EOF)
  222. {
  223. *buffer_ = Traits::to_char_type(c);
  224. pbump(1);
  225. }
  226. } else if(c != EOF)
  227. {
  228. if(buffer_size_ > 0)
  229. {
  230. make_buffer();
  231. setp(buffer_, buffer_ + buffer_size_);
  232. *buffer_ = Traits::to_char_type(c);
  233. pbump(1);
  234. } else if(std::fputc(c, file_) == EOF)
  235. {
  236. return EOF;
  237. } else if(!pptr())
  238. {
  239. // Set to dummy value so we know we have written something
  240. setp(last_char_, last_char_);
  241. }
  242. }
  243. return Traits::not_eof(c);
  244. }
  245. int sync() override
  246. {
  247. if(!file_)
  248. return 0;
  249. bool result;
  250. if(pptr())
  251. {
  252. result = overflow() != EOF;
  253. // Only flush if anything was written, otherwise behavior of fflush is undefined
  254. if(std::fflush(file_) != 0)
  255. return result = false;
  256. } else
  257. result = stop_reading();
  258. return result ? 0 : -1;
  259. }
  260. int underflow() override
  261. {
  262. if(!(mode_ & std::ios_base::in))
  263. return EOF;
  264. if(!stop_writing())
  265. return EOF;
  266. // In text mode we cannot use a buffer size of more than 1 (i.e. single char only)
  267. // This is due to the need to seek back in case of a sync to "put back" unread chars.
  268. // However determining the number of chars to seek back is impossible in case there are newlines
  269. // as we cannot know if those were converted.
  270. if(buffer_size_ == 0 || !(mode_ & std::ios_base::binary))
  271. {
  272. const int c = std::fgetc(file_);
  273. if(c == EOF)
  274. return EOF;
  275. last_char_[0] = Traits::to_char_type(c);
  276. setg(last_char_, last_char_, last_char_ + 1);
  277. } else
  278. {
  279. make_buffer();
  280. const size_t n = std::fread(buffer_, 1, buffer_size_, file_);
  281. setg(buffer_, buffer_, buffer_ + n);
  282. if(n == 0)
  283. return EOF;
  284. }
  285. return Traits::to_int_type(*gptr());
  286. }
  287. int pbackfail(int c = EOF) override
  288. {
  289. if(!(mode_ & std::ios_base::in))
  290. return EOF;
  291. if(!stop_writing())
  292. return EOF;
  293. if(gptr() > eback())
  294. gbump(-1);
  295. else if(seekoff(-1, std::ios_base::cur) != std::streampos(std::streamoff(-1)))
  296. {
  297. if(underflow() == EOF)
  298. return EOF;
  299. } else
  300. return EOF;
  301. // Case 1: Caller just wanted space for 1 char
  302. if(c == EOF)
  303. return Traits::not_eof(c);
  304. // Case 2: Caller wants to put back different char
  305. // gptr now points to the (potentially newly read) previous char
  306. if(*gptr() != c)
  307. *gptr() = Traits::to_char_type(c);
  308. return Traits::not_eof(c);
  309. }
  310. std::streampos seekoff(std::streamoff off,
  311. std::ios_base::seekdir seekdir,
  312. std::ios_base::openmode = std::ios_base::in | std::ios_base::out) override
  313. {
  314. if(!file_)
  315. return EOF;
  316. // Switching between input<->output requires a seek
  317. // So do NOT optimize for seekoff(0, cur) as No-OP
  318. // On some implementations a seek also flushes, so do a full sync
  319. if(sync() != 0)
  320. return EOF;
  321. int whence;
  322. switch(seekdir)
  323. {
  324. case std::ios_base::beg: whence = SEEK_SET; break;
  325. case std::ios_base::cur: whence = SEEK_CUR; break;
  326. case std::ios_base::end: whence = SEEK_END; break;
  327. default: assert(false); return EOF;
  328. }
  329. if(detail::fseek(file_, off, whence) != 0)
  330. return EOF;
  331. return detail::ftell(file_);
  332. }
  333. std::streampos seekpos(std::streampos pos,
  334. std::ios_base::openmode m = std::ios_base::in | std::ios_base::out) override
  335. {
  336. // Standard mandates "as-if fsetpos", but assume the effect is the same as fseek
  337. return seekoff(pos, std::ios_base::beg, m);
  338. }
  339. void imbue(const std::locale& loc) override
  340. {
  341. validate_cvt(loc);
  342. }
  343. private:
  344. /// Stop reading adjusting the file pointer if necessary
  345. /// Postcondition: gptr() == NULL
  346. bool stop_reading()
  347. {
  348. if(!gptr())
  349. return true;
  350. const auto off = gptr() - egptr();
  351. setg(0, 0, 0);
  352. if(!off)
  353. return true;
  354. #if defined(__clang__)
  355. #pragma clang diagnostic push
  356. #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
  357. #endif
  358. // coverity[result_independent_of_operands]
  359. if(off > std::numeric_limits<std::streamoff>::max())
  360. return false;
  361. #if defined(__clang__)
  362. #pragma clang diagnostic pop
  363. #endif
  364. return detail::fseek(file_, static_cast<std::streamoff>(off), SEEK_CUR) == 0;
  365. }
  366. /// Stop writing. If any bytes are to be written, writes them to file
  367. /// Postcondition: pptr() == NULL
  368. bool stop_writing()
  369. {
  370. if(pptr())
  371. {
  372. const char* const base = pbase();
  373. const size_t n = pptr() - base;
  374. setp(0, 0);
  375. if(n && std::fwrite(base, 1, n, file_) != n)
  376. return false;
  377. }
  378. return true;
  379. }
  380. void reset(FILE* f = 0)
  381. {
  382. sync();
  383. if(file_)
  384. {
  385. fclose(file_);
  386. file_ = 0;
  387. }
  388. file_ = f;
  389. }
  390. static const wchar_t* get_mode(std::ios_base::openmode mode)
  391. {
  392. //
  393. // done according to n2914 table 106 27.9.1.4
  394. //
  395. // note can't use switch case as overload operator can't be used
  396. // in constant expression
  397. if(mode == (std::ios_base::out))
  398. return L"w";
  399. if(mode == (std::ios_base::out | std::ios_base::app))
  400. return L"a";
  401. if(mode == (std::ios_base::app))
  402. return L"a";
  403. if(mode == (std::ios_base::out | std::ios_base::trunc))
  404. return L"w";
  405. if(mode == (std::ios_base::in))
  406. return L"r";
  407. if(mode == (std::ios_base::in | std::ios_base::out))
  408. return L"r+";
  409. if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
  410. return L"w+";
  411. if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::app))
  412. return L"a+";
  413. if(mode == (std::ios_base::in | std::ios_base::app))
  414. return L"a+";
  415. if(mode == (std::ios_base::binary | std::ios_base::out))
  416. return L"wb";
  417. if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::app))
  418. return L"ab";
  419. if(mode == (std::ios_base::binary | std::ios_base::app))
  420. return L"ab";
  421. if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc))
  422. return L"wb";
  423. if(mode == (std::ios_base::binary | std::ios_base::in))
  424. return L"rb";
  425. if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out))
  426. return L"r+b";
  427. if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::trunc))
  428. return L"w+b";
  429. if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::app))
  430. return L"a+b";
  431. if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::app))
  432. return L"a+b";
  433. return 0;
  434. }
  435. size_t buffer_size_;
  436. char* buffer_;
  437. FILE* file_;
  438. bool owns_buffer_;
  439. char last_char_[1];
  440. std::ios::openmode mode_;
  441. };
  442. ///
  443. /// \brief Convenience typedef
  444. ///
  445. using filebuf = basic_filebuf<char>;
  446. #endif // windows
  447. } // namespace nowide
  448. } // namespace boost
  449. #endif