LCOV - code coverage report
Current view: top level - src/foreign/zstr - zstr.hpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 76.2 % 168 128
Test Date: 2024-12-21 15:45:41 Functions: 69.6 % 23 16

            Line data    Source code
       1              : //---------------------------------------------------------
       2              : // Copyright 2015 Ontario Institute for Cancer Research
       3              : // Written by Matei David (matei@cs.toronto.edu)
       4              : //---------------------------------------------------------
       5              : 
       6              : // Reference:
       7              : // http://stackoverflow.com/questions/14086417/how-to-write-custom-input-stream-in-c
       8              : 
       9              : #pragma once
      10              : 
      11              : #include <cassert>
      12              : #include <fstream>
      13              : #include <sstream>
      14              : #include <zlib.h>
      15              : #include <memory>
      16              : #include <iostream>
      17              : #include "strict_fstream.hpp"
      18              : 
      19              : #if defined(__GNUC__) && !defined(__clang__)
      20              : #if (__GNUC__ > 5) || (__GNUC__ == 5 && __GNUC_MINOR__>0)
      21              : #define CAN_MOVE_IOSTREAM
      22              : #endif
      23              : #else
      24              : #define CAN_MOVE_IOSTREAM
      25              : #endif
      26              : 
      27              : namespace zstr
      28              : {
      29              : 
      30              : static const std::size_t default_buff_size = static_cast<std::size_t>(1 << 20);
      31              : 
      32              : /// Exception class thrown by failed zlib operations.
      33              : class Exception
      34              :     : public std::ios_base::failure
      35              : {
      36              : public:
      37            0 :     static std::string error_to_message(z_stream * zstrm_p, int ret)
      38              :     {
      39            0 :         std::string msg = "zlib: ";
      40            0 :         switch (ret)
      41              :         {
      42              :         case Z_STREAM_ERROR:
      43              :             msg += "Z_STREAM_ERROR: ";
      44              :             break;
      45              :         case Z_DATA_ERROR:
      46              :             msg += "Z_DATA_ERROR: ";
      47              :             break;
      48              :         case Z_MEM_ERROR:
      49              :             msg += "Z_MEM_ERROR: ";
      50              :             break;
      51              :         case Z_VERSION_ERROR:
      52              :             msg += "Z_VERSION_ERROR: ";
      53              :             break;
      54              :         case Z_BUF_ERROR:
      55              :             msg += "Z_BUF_ERROR: ";
      56              :             break;
      57            0 :         default:
      58            0 :             std::ostringstream oss;
      59            0 :             oss << ret;
      60            0 :             msg += "[" + oss.str() + "]: ";
      61              :             break;
      62              :         }
      63            0 :         if (zstrm_p->msg) {
      64              :             msg += zstrm_p->msg;
      65              :         }
      66              :         msg += " ("
      67            0 :                 "next_in: " +
      68            0 :                 std::to_string(uintptr_t(zstrm_p->next_in)) +
      69            0 :                 ", avail_in: " +
      70            0 :                 std::to_string(uintptr_t(zstrm_p->avail_in)) +
      71            0 :                 ", next_out: " +
      72            0 :                 std::to_string(uintptr_t(zstrm_p->next_out)) +
      73            0 :                 ", avail_out: " +
      74            0 :                 std::to_string(uintptr_t(zstrm_p->avail_out)) +
      75              :                 ")";
      76            0 :         return msg;
      77              :     }
      78              : 
      79            0 :     Exception(z_stream * zstrm_p, int ret)
      80            0 :         : std::ios_base::failure(error_to_message(zstrm_p, ret))
      81              :     {
      82            0 :     }
      83              : }; // class Exception
      84              : 
      85              : namespace detail
      86              : {
      87              : 
      88              : class z_stream_wrapper
      89              :     : public z_stream
      90              : {
      91              : public:
      92          427 :     z_stream_wrapper(bool _is_input, int _level, int _window_bits)
      93          427 :         : is_input(_is_input)
      94              :     {
      95          427 :         this->zalloc = nullptr;//Z_NULL
      96          427 :         this->zfree = nullptr;//Z_NULL
      97          427 :         this->opaque = nullptr;//Z_NULL
      98              :         int ret;
      99          427 :         if (is_input)
     100              :         {
     101          394 :             this->avail_in = 0;
     102          394 :             this->next_in = nullptr;//Z_NULL
     103          788 :             ret = inflateInit2(this, _window_bits ? _window_bits : 15+32);
     104              :         }
     105              :         else
     106              :         {
     107           66 :             ret = deflateInit2(this, _level, Z_DEFLATED, _window_bits ? _window_bits : 15+16, 8, Z_DEFAULT_STRATEGY);
     108              :         }
     109          427 :         if (ret != Z_OK) throw Exception(this, ret);
     110          427 :     }
     111          427 :     ~z_stream_wrapper()
     112              :     {
     113          427 :         if (is_input)
     114              :         {
     115          394 :             inflateEnd(this);
     116              :         }
     117              :         else
     118              :         {
     119           33 :             deflateEnd(this);
     120              :         }
     121          427 :     }
     122              : private:
     123              :     bool is_input;
     124              : }; // class z_stream_wrapper
     125              : 
     126              : } // namespace detail
     127              : 
     128              : class istreambuf
     129              :     : public std::streambuf
     130              : {
     131              : public:
     132       205268 :     istreambuf(std::streambuf * _sbuf_p,
     133              :                std::size_t _buff_size = default_buff_size, bool _auto_detect = true, int _window_bits = 0)
     134       205268 :         : sbuf_p(_sbuf_p),
     135              :           in_buff(),
     136       205268 :           in_buff_start(nullptr),
     137       205268 :           in_buff_end(nullptr),
     138              :           out_buff(),
     139              :           zstrm_p(nullptr),
     140       205268 :           buff_size(_buff_size),
     141       205268 :           auto_detect(_auto_detect),
     142       205268 :           auto_detect_run(false),
     143       205268 :           is_text(false),
     144       205268 :           window_bits(_window_bits)
     145              :     {
     146              :         assert(sbuf_p);
     147       205268 :         in_buff = std::unique_ptr<char[]>(new char[buff_size]);
     148       205268 :         in_buff_start = in_buff.get();
     149       205268 :         in_buff_end = in_buff.get();
     150       205268 :         out_buff = std::unique_ptr<char[]>(new char[buff_size]);
     151              :         setg(out_buff.get(), out_buff.get(), out_buff.get());
     152       205268 :     }
     153              : 
     154              :     istreambuf(const istreambuf &) = delete;
     155              :     istreambuf & operator = (const istreambuf &) = delete;
     156              : 
     157            0 :     pos_type seekoff(off_type off, std::ios_base::seekdir dir,
     158              :                      std::ios_base::openmode which) override
     159              :     {
     160            0 :         if (off != 0 || dir != std::ios_base::cur) {
     161              :             return std::streambuf::seekoff(off, dir, which);
     162              :         }
     163              : 
     164            0 :         if (!zstrm_p) {
     165            0 :             return 0;
     166              :         }
     167              : 
     168            0 :         return static_cast<long int>(zstrm_p->total_out - static_cast<uLong>(in_avail()));
     169              :     }
     170              : 
     171       410986 :     std::streambuf::int_type underflow() override
     172              :     {
     173       410986 :         if (this->gptr() == this->egptr())
     174              :         {
     175              :             // pointers for free region in output buffer
     176              :             char * out_buff_free_start = out_buff.get();
     177              :             int tries = 0;
     178              :             do
     179              :             {
     180       410986 :                 if (++tries > 1000) {
     181            0 :                     throw std::ios_base::failure("Failed to fill buffer after 1000 tries");
     182              :                 }
     183              : 
     184              :                 // read more input if none available
     185       410986 :                 if (in_buff_start == in_buff_end)
     186              :                 {
     187              :                     // empty input buffer: refill from the start
     188       410610 :                     in_buff_start = in_buff.get();
     189       410610 :                     std::streamsize sz = sbuf_p->sgetn(in_buff.get(), static_cast<std::streamsize>(buff_size));
     190       410610 :                     in_buff_end = in_buff_start + sz;
     191       410610 :                     if (in_buff_end == in_buff_start) break; // end of input
     192              :                 }
     193              :                 // auto detect if the stream contains text or deflate data
     194       205730 :                 if (auto_detect && ! auto_detect_run)
     195              :                 {
     196       205254 :                     auto_detect_run = true;
     197       205254 :                     unsigned char b0 = *reinterpret_cast< unsigned char * >(in_buff_start);
     198       205254 :                     unsigned char b1 = *reinterpret_cast< unsigned char * >(in_buff_start + 1);
     199              :                     // Ref:
     200              :                     // http://en.wikipedia.org/wiki/Gzip
     201              :                     // http://stackoverflow.com/questions/9050260/what-does-a-zlib-header-look-like
     202       410508 :                     is_text = ! (in_buff_start + 2 <= in_buff_end
     203       205254 :                                  && ((b0 == 0x1F && b1 == 0x8B)         // gzip header
     204       205152 :                                      || (b0 == 0x78 && (b1 == 0x01      // zlib header
     205            0 :                                                         || b1 == 0x9C
     206            0 :                                                         || b1 == 0xDA))));
     207              :                 }
     208       205730 :                 if (is_text)
     209              :                 {
     210              :                     // simply swap in_buff and out_buff, and adjust pointers
     211              :                     assert(in_buff_start == in_buff.get());
     212              :                     std::swap(in_buff, out_buff);
     213       205243 :                     out_buff_free_start = in_buff_end;
     214       205243 :                     in_buff_start = in_buff.get();
     215       205243 :                     in_buff_end = in_buff.get();
     216              :                 }
     217              :                 else
     218              :                 {
     219              :                     // run inflate() on input
     220          881 :                     if (! zstrm_p) zstrm_p = std::unique_ptr<detail::z_stream_wrapper>(new detail::z_stream_wrapper(true, Z_DEFAULT_COMPRESSION, window_bits));
     221          487 :                     zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(in_buff_start);
     222          487 :                     zstrm_p->avail_in = uint32_t(in_buff_end - in_buff_start);
     223          487 :                     zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff_free_start);
     224          487 :                     zstrm_p->avail_out = uint32_t((out_buff.get() + buff_size) - out_buff_free_start);
     225          487 :                     int ret = inflate(zstrm_p.get(), Z_NO_FLUSH);
     226              :                     // process return code
     227          487 :                     if (ret != Z_OK && ret != Z_STREAM_END) throw Exception(zstrm_p.get(), ret);
     228              :                     // update in&out pointers following inflate()
     229          487 :                     in_buff_start = reinterpret_cast< decltype(in_buff_start) >(zstrm_p->next_in);
     230          487 :                     in_buff_end = in_buff_start + zstrm_p->avail_in;
     231          487 :                     out_buff_free_start = reinterpret_cast< decltype(out_buff_free_start) >(zstrm_p->next_out);
     232              :                     assert(out_buff_free_start + zstrm_p->avail_out == out_buff.get() + buff_size);
     233              : 
     234          487 :                     if (ret == Z_STREAM_END) {
     235              :                         // if stream ended, deallocate inflator
     236              :                         zstrm_p.reset();
     237              :                     }
     238              :                 }
     239       205730 :             } while (out_buff_free_start == out_buff.get());
     240              :             // 2 exit conditions:
     241              :             // - end of input: there might or might not be output available
     242              :             // - out_buff_free_start != out_buff: output available
     243              :             this->setg(out_buff.get(), out_buff.get(), out_buff_free_start);
     244              :         }
     245              :         return this->gptr() == this->egptr()
     246       410986 :             ? traits_type::eof()
     247       410986 :             : traits_type::to_int_type(*this->gptr());
     248              :     }
     249              : private:
     250              :     std::streambuf * sbuf_p;
     251              :     std::unique_ptr<char[]> in_buff;
     252              :     char * in_buff_start;
     253              :     char * in_buff_end;
     254              :     std::unique_ptr<char[]> out_buff;
     255              :     std::unique_ptr<detail::z_stream_wrapper> zstrm_p;
     256              :     std::size_t buff_size;
     257              :     bool auto_detect;
     258              :     bool auto_detect_run;
     259              :     bool is_text;
     260              :     int window_bits;
     261              : 
     262              : }; // class istreambuf
     263              : 
     264              : class ostreambuf
     265              :     : public std::streambuf
     266              : {
     267              : public:
     268           33 :     ostreambuf(std::streambuf * _sbuf_p,
     269              :                std::size_t _buff_size = default_buff_size, int _level = Z_DEFAULT_COMPRESSION, int _window_bits = 0)
     270           33 :         : sbuf_p(_sbuf_p),
     271              :           in_buff(),
     272              :           out_buff(),
     273           33 :           zstrm_p(new detail::z_stream_wrapper(false, _level, _window_bits)),
     274           33 :           buff_size(_buff_size)
     275              :     {
     276              :         assert(sbuf_p);
     277           33 :         in_buff = std::unique_ptr<char[]>(new char[buff_size]);
     278           33 :         out_buff = std::unique_ptr<char[]>(new char[buff_size]);
     279           33 :         setp(in_buff.get(), in_buff.get() + buff_size);
     280           33 :     }
     281              : 
     282              :     ostreambuf(const ostreambuf &) = delete;
     283              :     ostreambuf & operator = (const ostreambuf &) = delete;
     284              : 
     285          175 :     int deflate_loop(int flush)
     286              :     {
     287              :         while (true)
     288              :         {
     289          250 :             zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff.get());
     290          250 :             zstrm_p->avail_out = uint32_t(buff_size);
     291          250 :             int ret = deflate(zstrm_p.get(), flush);
     292          250 :             if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
     293            0 :                 failed = true;
     294            0 :                 throw Exception(zstrm_p.get(), ret);
     295              :             }
     296          250 :             std::streamsize sz = sbuf_p->sputn(out_buff.get(), reinterpret_cast< decltype(out_buff.get()) >(zstrm_p->next_out) - out_buff.get());
     297          250 :             if (sz != reinterpret_cast< decltype(out_buff.get()) >(zstrm_p->next_out) - out_buff.get())
     298              :             {
     299              :                 // there was an error in the sink stream
     300              :                 return -1;
     301              :             }
     302          217 :             if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || sz == 0)
     303              :             {
     304              :                 break;
     305              :             }
     306              :         }
     307              :         return 0;
     308              :     }
     309              : 
     310           66 :     virtual ~ostreambuf()
     311           33 :     {
     312              :         // flush the zlib stream
     313              :         //
     314              :         // NOTE: Errors here (sync() return value not 0) are ignored, because we
     315              :         // cannot throw in a destructor. This mirrors the behaviour of
     316              :         // std::basic_filebuf::~basic_filebuf(). To see an exception on error,
     317              :         // close the ofstream with an explicit call to close(), and do not rely
     318              :         // on the implicit call in the destructor.
     319              :         //
     320           33 :         if (!failed) try {
     321           33 :             sync();
     322            0 :         } catch (...) {}
     323           99 :     }
     324          109 :     std::streambuf::int_type overflow(std::streambuf::int_type c = traits_type::eof()) override
     325              :     {
     326          109 :         zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(pbase());
     327          109 :         zstrm_p->avail_in = uint32_t(pptr() - pbase());
     328          185 :         while (zstrm_p->avail_in > 0)
     329              :         {
     330           76 :             int r = deflate_loop(Z_NO_FLUSH);
     331           76 :             if (r != 0)
     332              :             {
     333              :                 setp(nullptr, nullptr);
     334            0 :                 return traits_type::eof();
     335              :             }
     336              :         }
     337          109 :         setp(in_buff.get(), in_buff.get() + buff_size);
     338          109 :         return traits_type::eq_int_type(c, traits_type::eof()) ? traits_type::eof() : sputc(char_type(c));
     339              :     }
     340           99 :     int sync() override
     341              :     {
     342              :         // first, call overflow to clear in_buff
     343           99 :         overflow();
     344           99 :         if (! pptr()) return -1;
     345              :         // then, call deflate asking to finish the zlib stream
     346           99 :         zstrm_p->next_in = nullptr;
     347           99 :         zstrm_p->avail_in = 0;
     348           99 :         if (deflate_loop(Z_FINISH) != 0) return -1;
     349           66 :         deflateReset(zstrm_p.get());
     350           66 :         return 0;
     351              :     }
     352              : private:
     353              :     std::streambuf * sbuf_p = nullptr;
     354              :     std::unique_ptr<char[]> in_buff;
     355              :     std::unique_ptr<char[]> out_buff;
     356              :     std::unique_ptr<detail::z_stream_wrapper> zstrm_p;
     357              :     std::size_t buff_size;
     358              :     bool failed = false;
     359              : 
     360              : }; // class ostreambuf
     361              : 
     362              : class istream
     363              :     : public std::istream
     364              : {
     365              : public:
     366              :     istream(std::istream & is,
     367              :             std::size_t _buff_size = default_buff_size, bool _auto_detect = true, int _window_bits = 0)
     368              :         : std::istream(new istreambuf(is.rdbuf(), _buff_size, _auto_detect, _window_bits))
     369              :     {
     370              :         exceptions(std::ios_base::badbit);
     371              :     }
     372              :     explicit istream(std::streambuf * sbuf_p)
     373              :         : std::istream(new istreambuf(sbuf_p))
     374              :     {
     375              :         exceptions(std::ios_base::badbit);
     376              :     }
     377            0 :     virtual ~istream()
     378            0 :     {
     379            0 :         delete rdbuf();
     380            0 :     }
     381              : }; // class istream
     382              : 
     383              : class ostream
     384              :     : public std::ostream
     385              : {
     386              : public:
     387              :     ostream(std::ostream & os,
     388              :             std::size_t _buff_size = default_buff_size, int _level = Z_DEFAULT_COMPRESSION, int _window_bits = 0)
     389              :         : std::ostream(new ostreambuf(os.rdbuf(), _buff_size, _level, _window_bits))
     390              :     {
     391              :         exceptions(std::ios_base::badbit);
     392              :     }
     393              :     explicit ostream(std::streambuf * sbuf_p)
     394              :         : std::ostream(new ostreambuf(sbuf_p))
     395              :     {
     396              :         exceptions(std::ios_base::badbit);
     397              :     }
     398            0 :     virtual ~ostream()
     399            0 :     {
     400            0 :         delete rdbuf();
     401            0 :     }
     402              : }; // class ostream
     403              : 
     404              : namespace detail
     405              : {
     406              : 
     407              : template < typename FStream_Type >
     408       202347 : struct strict_fstream_holder
     409              : {
     410       205302 :     strict_fstream_holder(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in)
     411       205302 :         : _fs(filename, mode)
     412       205301 :     {}
     413              :     strict_fstream_holder() = default;
     414              :     FStream_Type _fs {};
     415              : }; // class strict_fstream_holder
     416              : 
     417              : } // namespace detail
     418              : 
     419              : class ifstream
     420              :     : private detail::strict_fstream_holder< strict_fstream::ifstream >,
     421              :       public std::istream
     422              : {
     423              : public:
     424       205268 :     explicit ifstream(const std::string filename, std::ios_base::openmode mode = std::ios_base::in, size_t buff_size = default_buff_size)
     425       205268 :         : detail::strict_fstream_holder< strict_fstream::ifstream >(filename, mode),
     426       410536 :           std::istream(new istreambuf(_fs.rdbuf(), buff_size))
     427              :     {
     428              :         exceptions(std::ios_base::badbit);
     429       205268 :     }
     430              :     explicit ifstream(): detail::strict_fstream_holder< strict_fstream::ifstream >(), std::istream(new istreambuf(_fs.rdbuf())){}
     431              :     void close() {
     432       202314 :         _fs.close();
     433       202314 :     }
     434              :     #ifdef CAN_MOVE_IOSTREAM
     435              :     void open(const std::string filename, std::ios_base::openmode mode = std::ios_base::in) {
     436              :         _fs.open(filename, mode);
     437              :         std::istream::operator=(std::istream(new istreambuf(_fs.rdbuf())));
     438              :     }
     439              :     #endif
     440              :     bool is_open() const {
     441              :         return _fs.is_open();
     442              :     }
     443       244590 :     virtual ~ifstream()
     444       202314 :     {
     445       202314 :         if (_fs.is_open()) close();
     446       202314 :         if (rdbuf()) delete rdbuf();
     447       244590 :     }
     448              : 
     449              :     /// Return the position within the compressed file (wrapped filestream)
     450              :     std::streampos compressed_tellg()
     451              :     {
     452              :         return _fs.tellg();
     453              :     }
     454              : }; // class ifstream
     455              : 
     456              : class ofstream
     457              :     : private detail::strict_fstream_holder< strict_fstream::ofstream >,
     458              :       public std::ostream
     459              : {
     460              : public:
     461           34 :     explicit ofstream(const std::string filename, std::ios_base::openmode mode = std::ios_base::out,
     462              :                       int level = Z_DEFAULT_COMPRESSION, size_t buff_size = default_buff_size)
     463           34 :         : detail::strict_fstream_holder< strict_fstream::ofstream >(filename, mode | std::ios_base::binary),
     464           66 :           std::ostream(new ostreambuf(_fs.rdbuf(), buff_size, level))
     465              :     {
     466              :         exceptions(std::ios_base::badbit);
     467           33 :     }
     468              :     explicit ofstream(): detail::strict_fstream_holder< strict_fstream::ofstream >(), std::ostream(new ostreambuf(_fs.rdbuf())){}
     469              :     void close() {
     470           33 :         std::ostream::flush();
     471           33 :         _fs.close();
     472           33 :     }
     473              :     #ifdef CAN_MOVE_IOSTREAM
     474              :     void open(const std::string filename, std::ios_base::openmode mode = std::ios_base::out, int level = Z_DEFAULT_COMPRESSION) {
     475              :         flush();
     476              :         _fs.open(filename, mode | std::ios_base::binary);
     477              :         std::ostream::operator=(std::ostream(new ostreambuf(_fs.rdbuf(), default_buff_size, level)));
     478              :     }
     479              :     #endif
     480              :     bool is_open() const {
     481              :         return _fs.is_open();
     482              :     }
     483              :     ofstream& flush() {
     484              :         std::ostream::flush();
     485              :         _fs.flush();
     486              :         return *this;
     487              :     }
     488           66 :     virtual ~ofstream()
     489           33 :     {
     490           33 :         if (_fs.is_open()) close();
     491           33 :         if (rdbuf()) delete rdbuf();
     492           66 :     }
     493              : 
     494              :     // Return the position within the compressed file (wrapped filestream)
     495              :     std::streampos compressed_tellp()
     496              :     {
     497              :         return _fs.tellp();
     498              :     }
     499              : }; // class ofstream
     500              : 
     501              : } // namespace zstr
        

Generated by: LCOV version 2.0-1