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

Generated by: LCOV version 2.0-1