Search code examples
c++gzipstreambuf

Problems with writing a custom streambuf for gzipped streams


I am implementing my own streambuf class for writing compressed output files. Here what it is look like.

template <class T>
class gzstreambufbase : public std::streambuf
{
protected:
    static const int bufferSize = 8192;

public:
    gzstreambufbase();
    ~gzstreambufbase();

    bool close();
    bool is_open();

protected:
    virtual T* open(const std::string& name, std::ios::openmode mode) = 0;
    virtual int sync();

    // flush the characters in the buffer
    int flush_buffer();

protected:
    gzFile             filePtr_;
    std::ios::openmode mode_;
    bool               opened_;
    char               buffer_[bufferSize];
    std::string        fileName_;
};

Then I am deriving from this base new igzstreambuf and ogzstreambuf classes for input and output streambufs correspondingly. Basically, the implementation was done by following the example from Nicolai M. Josuttis [C++ Standard Library, The: A Tutorial and Reference] book.

Lets look at the ogzstream's implementation only.

ogzstreambuf::ogzstreambuf()
{
    // initialize data buffer
    // one character less to let the bufferSizeth
    // character cause a call of overflow()
    setp( buffer_, buffer_ + (bufferSize - 1));
}

ogzstreambuf*
ogzstreambuf::open(const std::string& name, std::ios::openmode mode)
{
    if (is_open())
        return (ogzstreambuf*)0;

    mode_ = mode;
    fileName_ = name;

    filePtr_ = gzopen(fileName_.c_str(), "wb");
    CUSTOM_CHECK(0 != filePtr_, ("GZIP_IO_ERROR", strerror(errno)));
    opened_ = 1;

    return this;
}

std::streampos
ogzstreambuf::seekpos(std::streampos offset, std::ios_base::openmode which)
{
    return seekImpl(offset, std::ios_base::beg, which);
}

std::streampos
ogzstreambuf::seekoff(std::streamoff offset, std::ios_base::seekdir way, std::ios_base::openmode which)
{
    return seekImpl(offset, way, which);
}

std::streampos
ogzstreambuf::seekImpl(std::streamoff offset, std::ios_base::seekdir way, std::ios_base::openmode which)
{
    assert(!fileName_.empty(), "");
    assert(LONG_MAX != offset, "");
    assert(std::ios_base::out == which, "");
    assert( way != std::ios_base::end,
                 "zlib doesn't support the value SEEK_END in gzseek()." );

    if (!flush_buffer())
        return std::streampos(EOF);

    const long newPos = gzseek(filePtr_, offset,
                              (way == std::ios_base::beg ? SEEK_SET : SEEK_CUR));

    CUSTOM_CHECK((long) offset == newPos, ("GZIP_IO_ERROR", strerror(errno)));
    setp(buffer_, buffer_ + (bufferSize - 1));

    return offset;
}

So, the problem is that the call of tellp() on my own implemented ogzstream object (which holds an instance of ogzstreambuf internally) returns -1(EOF) value, since:

Internally, if member fail returns true, the function returns -1. Otherwise, it returns rdbuf()->pubseekoff(0,cur,out);

Quoted from cpp. And finally flush_buffer() returns 0 because pptr() - pbase(); is equal to 0:

template <class T>
int gzstreambufbase<T>::flush_buffer()
{
    // Separate the writing of the buffer from overflow() and
    // sync() operation.
    int w = pptr() - pbase();
    if ( gzwrite( filePtr_, pbase(), w) != w)
        return EOF;

    pbump( -w); // reset put pointer acccordingly
    return w;
}

As the result, pubseekoff() returns EOF and tellp() fails. I want to understand what I've missed in implementation and what should I do to improve this realization.


Solution

  • After proper debugging, I myself finally found the issue. There was just needed to check EOF == flush_buffer() instead of !flush_buffer()...