LCOV - code coverage report
Current view: top level - src - deflateoutputstreambuf.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 83 83 100.0 %
Date: 2019-04-24 14:10:30 Functions: 11 13 84.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   Zipios -- a small C++ library that provides easy access to .zip files.
       3             : 
       4             :   Copyright (C) 2000-2007  Thomas Sondergaard
       5             :   Copyright (C) 2015-2019  Made to Order Software Corporation
       6             : 
       7             :   This library is free software; you can redistribute it and/or
       8             :   modify it under the terms of the GNU Lesser General Public
       9             :   License as published by the Free Software Foundation; either
      10             :   version 2.1 of the License, or (at your option) any later version.
      11             : 
      12             :   This library is distributed in the hope that it will be useful,
      13             :   but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15             :   Lesser General Public License for more details.
      16             : 
      17             :   You should have received a copy of the GNU Lesser General Public
      18             :   License along with this library; if not, write to the Free Software
      19             :   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      20             : */
      21             : 
      22             : /** \file
      23             :  * \brief Implementation of zipios::DeflateOutputStreambuf.
      24             :  *
      25             :  * This is the counterpart of the zipios::InflateInputStreambuf.
      26             :  */
      27             : 
      28             : #include "deflateoutputstreambuf.hpp"
      29             : 
      30             : #include "zipios/zipiosexceptions.hpp"
      31             : 
      32             : #include "zipios_common.hpp"
      33             : 
      34             : 
      35             : namespace zipios
      36             : {
      37             : 
      38             : /** \class DeflateOutputStreambuf
      39             :  * \brief A class to handle stream deflate on the fly.
      40             :  *
      41             :  * DeflateOutputStreambuf is an output stream filter, that deflates
      42             :  * the data that is written to it before it passes it on to the
      43             :  * output stream it is attached to. Deflation/Inflation is a
      44             :  * compression/decompression method used in gzip and zip. The zlib
      45             :  * library is used to perform the actual deflation, this class only
      46             :  * wraps the functionality in an output stream filter.
      47             :  */
      48             : 
      49             : 
      50             : /** \brief Initialize a DeflateOutputStreambuf object.
      51             :  *
      52             :  * This function initializes the DeflateOutputStreambuf object to make it
      53             :  * ready for compressing data using the zlib library.
      54             :  *
      55             :  * \param[in,out] outbuf  The streambuf to use for output.
      56             :  */
      57         243 : DeflateOutputStreambuf::DeflateOutputStreambuf(std::streambuf *outbuf)
      58             :     : FilterOutputStreambuf(outbuf)
      59             :     //, m_overflown_bytes(0) -- auto-init
      60             :     , m_invec(getBufferSize())
      61             :     //, m_zs() -- auto-init
      62             :     //, m_zs_initialized(false) -- auto-init
      63         243 :     , m_outvec(getBufferSize())
      64             :     //, m_crc32(0) -- auto-init
      65             : {
      66             :     // NOTICE: It is important that this constructor and the methods it
      67             :     //         calls does not do anything with the output streambuf m_outbuf.
      68             :     //         The reason is that this class can be subclassed, and the
      69             :     //         subclass should get a chance to write to the buffer first.
      70             : 
      71             :     // zlib init: (this is done in the class declaration)
      72             :     //m_zs.zalloc = Z_NULL;
      73             :     //m_zs.zfree  = Z_NULL;
      74             :     //m_zs.opaque = Z_NULL;
      75         243 : }
      76             : 
      77             : 
      78             : /** \brief Clean up any resources used by this object.
      79             :  *
      80             :  * The destructor makes sure that the zlib library is done with all
      81             :  * the input and output data by calling various flush functions. It
      82             :  * then makes sure that the remaining data from zlib is printed in
      83             :  * the output file.
      84             :  *
      85             :  * This is similar to calling closeStream() explicitly.
      86             :  */
      87         486 : DeflateOutputStreambuf::~DeflateOutputStreambuf()
      88             : {
      89         243 :     closeStream();
      90         243 : }
      91             : 
      92             : 
      93             : /** \brief Initialize the zlib library.
      94             :  *
      95             :  * This method is called in the constructor, so it must not write
      96             :  * anything to the output streambuf m_outbuf (see notice in
      97             :  * constructor.)
      98             :  *
      99             :  * It will initialize the output stream as required to accept data
     100             :  * to be compressed using the zlib library. The compression level
     101             :  * is expected to come from the FileEntry which is about to be
     102             :  * saved in the file.
     103             :  *
     104             :  * \return true if the initialization succeeded, false otherwise.
     105             :  */
     106       93731 : bool DeflateOutputStreambuf::init(FileEntry::CompressionLevel compression_level)
     107             : {
     108       93731 :     if(m_zs_initialized)
     109             :     {
     110             :         // This is excluded from the coverage since if we reach this
     111             :         // line there is an internal error that needs to be fixed.
     112             :         throw std::logic_error("DeflateOutputStreambuf::init(): initialization function called when the class is already initialized. This is not supported."); // LCOV_EXCL_LINE
     113             :     }
     114       93731 :     m_zs_initialized = true;
     115             : 
     116       93731 :     int const default_mem_level(8);
     117             : 
     118       93731 :     int zlevel(Z_NO_COMPRESSION);
     119       93731 :     switch(compression_level)
     120             :     {
     121             :     case FileEntry::COMPRESSION_LEVEL_DEFAULT:
     122         903 :         zlevel = Z_DEFAULT_COMPRESSION;
     123         903 :         break;
     124             : 
     125             :     case FileEntry::COMPRESSION_LEVEL_SMALLEST:
     126         903 :         zlevel = Z_BEST_COMPRESSION;
     127         903 :         break;
     128             : 
     129             :     case FileEntry::COMPRESSION_LEVEL_FASTEST:
     130         903 :         zlevel = Z_BEST_SPEED;
     131         903 :         break;
     132             : 
     133             :     case FileEntry::COMPRESSION_LEVEL_NONE:
     134             :         throw std::logic_error("the compression level NONE is not supported in DeflateOutputStreambuf::init()"); // LCOV_EXCL_LINE
     135             : 
     136             :     default:
     137       91022 :         if(compression_level < FileEntry::COMPRESSION_LEVEL_MINIMUM
     138       91022 :         || compression_level > FileEntry::COMPRESSION_LEVEL_MAXIMUM)
     139             :         {
     140             :             // This is excluded from the coverage since if we reach this
     141             :             // line there is an internal error that needs to be fixed.
     142             :             throw std::logic_error("the compression level must be defined between -3 and 100, see the zipios/fileentry.hpp for a list of valid levels."); // LCOV_EXCL_LINE
     143             :         }
     144             :         // The zlevel is calculated linearly from the user specified value
     145             :         // of 1 to 100
     146             :         //
     147             :         // The calculation goes as follow:
     148             :         //
     149             :         //    x = user specified value - 1    (0 to 99)
     150             :         //    x = x * 8                       (0 to 792)
     151             :         //    x = x + 11 / 2                  (5 to 797, i.e. +5 with integers)
     152             :         //    x = x / 99                      (0 to 8)
     153             :         //    x = x + 1                       (1 to 9)
     154             :         //
     155       91022 :         zlevel = ((compression_level - 1) * 8 + 11 / 2) / 99 + 1;
     156       91022 :         break;
     157             : 
     158             :     }
     159             : 
     160             :     // m_zs.next_in and avail_in must be set according to
     161             :     // zlib.h (inline doc).
     162       93731 :     m_zs.next_in  = reinterpret_cast<unsigned char *>(&m_invec[0]);
     163       93731 :     m_zs.avail_in = 0;
     164             : 
     165       93731 :     m_zs.next_out  = reinterpret_cast<unsigned char *>(&m_outvec[0]);
     166       93731 :     m_zs.avail_out = getBufferSize();
     167             : 
     168             :     //
     169             :     // windowBits is passed -MAX_WBITS to tell that no zlib
     170             :     // header should be written.
     171             :     //
     172       93731 :     int const err = deflateInit2(&m_zs, zlevel, Z_DEFLATED, -MAX_WBITS, default_mem_level, Z_DEFAULT_STRATEGY);
     173       93731 :     if(err != Z_OK)
     174             :     {
     175             :         // Not too sure how we could generate an error here, the deflateInit2()
     176             :         // would fail if (1) there is not enough memory and (2) if a parameter
     177             :         // is out of wack which neither can be generated from the outside
     178             :         // (well... not easily)
     179             :         std::ostringstream msgs; // LCOV_EXCL_LINE
     180             :         msgs << "DeflateOutputStreambuf::init(): error while initializing zlib, " << zError(err) << std::endl; // LCOV_EXCL_LINE
     181             :         throw IOException(msgs.str()); // LCOV_EXCL_LINE
     182             :     }
     183             : 
     184             :     // streambuf init:
     185       93731 :     setp(&m_invec[0], &m_invec[0] + getBufferSize());
     186             : 
     187       93731 :     m_crc32 = crc32(0, Z_NULL, 0);
     188             : 
     189       93731 :     return err == Z_OK;
     190             : }
     191             : 
     192             : 
     193             : /** \brief Closing the stream.
     194             :  *
     195             :  * This function is expected to be called once the stream is getting
     196             :  * closed (the buffer is destroyed.)
     197             :  *
     198             :  * It ensures that the zlib library last few bytes get flushed and
     199             :  * then mark the class as closed.
     200             :  *
     201             :  * Note that this function can be called to close the current zlib
     202             :  * library stream and start a new one. It is actually called from
     203             :  * the putNextEntry() function (via the closeEntry() function.)
     204             :  */
     205       93974 : void DeflateOutputStreambuf::closeStream()
     206             : {
     207       93974 :     if(m_zs_initialized)
     208             :     {
     209       93731 :         m_zs_initialized = false;
     210             : 
     211             :         // flush any remaining data
     212       93731 :         endDeflation();
     213             : 
     214       93731 :         int const err(deflateEnd(&m_zs));
     215       93731 :         if(err != Z_OK) // when we close a directory, we get the Z_DATA_ERROR!
     216             :         {
     217             :             // There are not too many cases which break the deflateEnd()
     218             :             // function call...
     219             :             std::ostringstream msgs; // LCOV_EXCL_LINE
     220             :             msgs << "DeflateOutputStreambuf::closeStream(): deflateEnd failed: " << zError(err) << std::endl; // LCOV_EXCL_LINE
     221             :             throw IOException(msgs.str()); // LCOV_EXCL_LINE
     222             :         }
     223             :     }
     224       93974 : }
     225             : 
     226             : 
     227             : /** \brief Get the CRC32 of the file.
     228             :  *
     229             :  * This function returns the CRC32 for the current file.
     230             :  *
     231             :  * The returned value is the CRC for the data that has been compressed
     232             :  * already (due to calls to overflow()). As DeflateOutputStreambuf may
     233             :  * buffer an arbitrary amount of bytes until closeStream() has been
     234             :  * invoked, the returned value is not very useful before closeStream()
     235             :  * has been called.
     236             :  *
     237             :  * \return The CRC32 of the last file that was passed through.
     238             :  */
     239      171466 : uint32_t DeflateOutputStreambuf::getCrc32() const
     240             : {
     241      171466 :     return m_crc32;
     242             : }
     243             : 
     244             : 
     245             : /** \brief Retrieve the size of the file deflated.
     246             :  *
     247             :  * This function returns the number of bytes written to the
     248             :  * streambuf object and that were processed from the input
     249             :  * buffer by the compressor. After closeStream() has been
     250             :  * called this number is the total number of bytes written
     251             :  * to the stream. In other words, the size of the uncompressed
     252             :  * data.
     253             :  *
     254             :  * \return The uncompressed size of the file that got written here.
     255             :  */
     256      171466 : size_t DeflateOutputStreambuf::getSize() const
     257             : {
     258      171466 :     return m_overflown_bytes;
     259             : }
     260             : 
     261             : 
     262             : /** \brief Handle an overflow.
     263             :  *
     264             :  * This function is called by the streambuf implementation whenever
     265             :  * "too many bytes" are in the output buffer, ready to be compressed.
     266             :  *
     267             :  * \exception IOException
     268             :  * This exception is raised whenever the overflow() function calls
     269             :  * a zlib library function which returns an error.
     270             :  *
     271             :  * \param[in] c  The character (byte) that overflowed the buffer.
     272             :  *
     273             :  * \return Always zero (0).
     274             :  */
     275      660277 : int DeflateOutputStreambuf::overflow(int c)
     276             : {
     277      660277 :     int err(Z_OK);
     278             : 
     279      660277 :     m_zs.avail_in = pptr() - pbase();
     280      660277 :     m_zs.next_in = reinterpret_cast<unsigned char *>(&m_invec[0]);
     281             : 
     282      660277 :     if(m_zs.avail_in > 0)
     283             :     {
     284      660277 :         m_crc32 = crc32(m_crc32, m_zs.next_in, m_zs.avail_in); // update crc32
     285             : 
     286      660277 :         m_zs.next_out = reinterpret_cast<unsigned char *>(&m_outvec[0]);
     287      660277 :         m_zs.avail_out = getBufferSize();
     288             : 
     289             :         // Deflate until m_invec is empty.
     290     3016451 :         while((m_zs.avail_in > 0 || m_zs.avail_out == 0) && err == Z_OK)
     291             :         {
     292     1178087 :             if(m_zs.avail_out == 0)
     293             :             {
     294      517810 :                 flushOutvec();
     295             :             }
     296             : 
     297     1178087 :             err = deflate(&m_zs, Z_NO_FLUSH);
     298             :         }
     299             :     }
     300             : 
     301             :     // somehow we need this flush here or it fails
     302      660277 :     flushOutvec();
     303             : 
     304             :     // Update 'put' pointers
     305      660277 :     setp(&m_invec[0], &m_invec[0] + getBufferSize());
     306             : 
     307      660277 :     if(err != Z_OK && err != Z_STREAM_END)
     308             :     {
     309             :         // Throw an exception to make istream set badbit
     310             :         //
     311             :         // This is marked as not cover-able because the calls that
     312             :         // access this function only happen in an internal loop and
     313             :         // even if we were to write a direct test, I do not see how
     314             :         // we could end up with an error here
     315             :         OutputStringStream msgs; // LCOV_EXCL_LINE
     316             :         msgs << "Deflation failed:" << zError(err); // LCOV_EXCL_LINE
     317             :         throw IOException(msgs.str()); // LCOV_EXCL_LINE
     318             :     }
     319             : 
     320      660277 :     if(c != EOF)
     321             :     {
     322      566546 :         *pptr() = c;
     323      566546 :         pbump(1);
     324             :     }
     325             : 
     326      660277 :     return 0;
     327             : }
     328             : 
     329             : 
     330             : /** \brief Synchronize the buffer.
     331             :  *
     332             :  * The sync() function is expected to clear the input buffer so that
     333             :  * any new data read from the input (i.e. a file) are re-read from
     334             :  * disk. However, a call to sync() could break the filtering
     335             :  * functionality so we do not implement it at all.
     336             :  *
     337             :  * This means you are stuck with the existing buffer. But to make
     338             :  * sure the system understands that, we always returns -1.
     339             :  */
     340             : int DeflateOutputStreambuf::sync() // LCOV_EXCL_LINE
     341             : {
     342             :     return -1; // LCOV_EXCL_LINE
     343             : }
     344             : 
     345             : 
     346             : /** \brief Flush the cached output data.
     347             :  *
     348             :  * This function flushes m_outvec and updates the output pointer
     349             :  * and size m_zs.next_out and m_zs.avail_out.
     350             :  */
     351     1320120 : void DeflateOutputStreambuf::flushOutvec()
     352             : {
     353             :     /** \TODO
     354             :      * We need to redesign the class to allow for STORED files to
     355             :      * flow through without the need to have this crap of bytes to
     356             :      * skip...
     357             :      */
     358     1320120 :     size_t deflated_bytes(getBufferSize() - m_zs.avail_out);
     359     1320120 :     if(deflated_bytes > 0)
     360             :     {
     361      918748 :         size_t const bc(m_outbuf->sputn(&m_outvec[0], deflated_bytes));
     362      918748 :         if(deflated_bytes != bc)
     363             :         {
     364             :             // Without implementing our own stream in our test, this
     365             :             // cannot really be reached because it is all happening
     366             :             // inside the same loop in ZipFile::saveCollectionToArchive()
     367             :             throw IOException("DeflateOutputStreambuf::flushOutvec(): write to buffer failed."); // LCOV_EXCL_LINE
     368             :         }
     369             :     }
     370             : 
     371     1320120 :     m_zs.next_out = reinterpret_cast<unsigned char *>(&m_outvec[0]);
     372     1320120 :     m_zs.avail_out = getBufferSize();
     373     1320120 : }
     374             : 
     375             : 
     376             : /** \brief End deflation of current file.
     377             :  *
     378             :  * This function flushes the remaining data in the zlib buffers,
     379             :  * after which the only possible operations are deflateEnd() or
     380             :  * deflateReset().
     381             :  */
     382       93731 : void DeflateOutputStreambuf::endDeflation()
     383             : {
     384       93731 :     overflow();
     385             : 
     386       93731 :     m_zs.next_out = reinterpret_cast<unsigned char *>(&m_outvec[0]);
     387       93731 :     m_zs.avail_out = getBufferSize();
     388             : 
     389             :     // Deflate until _invec is empty.
     390       93731 :     int err(Z_OK);
     391             : 
     392             :     // make sure to NOT call deflate() if nothing was written to the
     393             :     // deflate output stream, otherwise we get a "spurious" (as far
     394             :     // Zip archives are concerned) 0x03 0x00 marker from the zlib
     395             :     // library
     396             :     //
     397       93731 :     if(m_overflown_bytes > 0)
     398             :     {
     399      377797 :         while(err == Z_OK)
     400             :         {
     401      142033 :             if(m_zs.avail_out == 0)
     402             :             {
     403       48302 :                 flushOutvec();
     404             :             }
     405             : 
     406      142033 :             err = deflate(&m_zs, Z_FINISH);
     407             :         }
     408             :     }
     409             :     else
     410             :     {
     411             :         // this is not expected to happen, but it can
     412             :         err = Z_STREAM_END; // LCOV_EXCL_LINE
     413             :     }
     414             : 
     415       93731 :     flushOutvec();
     416             : 
     417       93731 :     if(err != Z_STREAM_END)
     418             :     {
     419             :         // This is marked as not cover-able because the calls that
     420             :         // access this function only happen in an internal loop and
     421             :         // even if we were to write a direct test, I do not see how
     422             :         // we could end up with an error here
     423             :         std::ostringstream msgs; // LCOV_EXCL_LINE
     424             :         msgs << "DeflateOutputStreambuf::endDeflation(): deflate() failed: " // LCOV_EXCL_LINE
     425             :              << zError(err) << std::endl; // LCOV_EXCL_LINE
     426             :         throw IOException(msgs.str()); // LCOV_EXCL_LINE
     427             :     }
     428       93731 : }
     429             : 
     430             : 
     431           3 : } // namespace
     432             : 
     433             : // Local Variables:
     434             : // mode: cpp
     435             : // indent-tabs-mode: nil
     436             : // c-basic-offset: 4
     437             : // tab-width: 4
     438             : // End:
     439             : 
     440             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.12