cannam@128: /* cannam@128: * A C++ I/O streams interface to the zlib gz* functions cannam@128: * cannam@128: * by Ludwig Schwardt cannam@128: * original version by Kevin Ruland cannam@128: * cannam@128: * This version is standard-compliant and compatible with gcc 3.x. cannam@128: */ cannam@128: cannam@128: #include "zfstream.h" cannam@128: #include // for strcpy, strcat, strlen (mode strings) cannam@128: #include // for BUFSIZ cannam@128: cannam@128: // Internal buffer sizes (default and "unbuffered" versions) cannam@128: #define BIGBUFSIZE BUFSIZ cannam@128: #define SMALLBUFSIZE 1 cannam@128: cannam@128: /*****************************************************************************/ cannam@128: cannam@128: // Default constructor cannam@128: gzfilebuf::gzfilebuf() cannam@128: : file(NULL), io_mode(std::ios_base::openmode(0)), own_fd(false), cannam@128: buffer(NULL), buffer_size(BIGBUFSIZE), own_buffer(true) cannam@128: { cannam@128: // No buffers to start with cannam@128: this->disable_buffer(); cannam@128: } cannam@128: cannam@128: // Destructor cannam@128: gzfilebuf::~gzfilebuf() cannam@128: { cannam@128: // Sync output buffer and close only if responsible for file cannam@128: // (i.e. attached streams should be left open at this stage) cannam@128: this->sync(); cannam@128: if (own_fd) cannam@128: this->close(); cannam@128: // Make sure internal buffer is deallocated cannam@128: this->disable_buffer(); cannam@128: } cannam@128: cannam@128: // Set compression level and strategy cannam@128: int cannam@128: gzfilebuf::setcompression(int comp_level, cannam@128: int comp_strategy) cannam@128: { cannam@128: return gzsetparams(file, comp_level, comp_strategy); cannam@128: } cannam@128: cannam@128: // Open gzipped file cannam@128: gzfilebuf* cannam@128: gzfilebuf::open(const char *name, cannam@128: std::ios_base::openmode mode) cannam@128: { cannam@128: // Fail if file already open cannam@128: if (this->is_open()) cannam@128: return NULL; cannam@128: // Don't support simultaneous read/write access (yet) cannam@128: if ((mode & std::ios_base::in) && (mode & std::ios_base::out)) cannam@128: return NULL; cannam@128: cannam@128: // Build mode string for gzopen and check it [27.8.1.3.2] cannam@128: char char_mode[6] = "\0\0\0\0\0"; cannam@128: if (!this->open_mode(mode, char_mode)) cannam@128: return NULL; cannam@128: cannam@128: // Attempt to open file cannam@128: if ((file = gzopen(name, char_mode)) == NULL) cannam@128: return NULL; cannam@128: cannam@128: // On success, allocate internal buffer and set flags cannam@128: this->enable_buffer(); cannam@128: io_mode = mode; cannam@128: own_fd = true; cannam@128: return this; cannam@128: } cannam@128: cannam@128: // Attach to gzipped file cannam@128: gzfilebuf* cannam@128: gzfilebuf::attach(int fd, cannam@128: std::ios_base::openmode mode) cannam@128: { cannam@128: // Fail if file already open cannam@128: if (this->is_open()) cannam@128: return NULL; cannam@128: // Don't support simultaneous read/write access (yet) cannam@128: if ((mode & std::ios_base::in) && (mode & std::ios_base::out)) cannam@128: return NULL; cannam@128: cannam@128: // Build mode string for gzdopen and check it [27.8.1.3.2] cannam@128: char char_mode[6] = "\0\0\0\0\0"; cannam@128: if (!this->open_mode(mode, char_mode)) cannam@128: return NULL; cannam@128: cannam@128: // Attempt to attach to file cannam@128: if ((file = gzdopen(fd, char_mode)) == NULL) cannam@128: return NULL; cannam@128: cannam@128: // On success, allocate internal buffer and set flags cannam@128: this->enable_buffer(); cannam@128: io_mode = mode; cannam@128: own_fd = false; cannam@128: return this; cannam@128: } cannam@128: cannam@128: // Close gzipped file cannam@128: gzfilebuf* cannam@128: gzfilebuf::close() cannam@128: { cannam@128: // Fail immediately if no file is open cannam@128: if (!this->is_open()) cannam@128: return NULL; cannam@128: // Assume success cannam@128: gzfilebuf* retval = this; cannam@128: // Attempt to sync and close gzipped file cannam@128: if (this->sync() == -1) cannam@128: retval = NULL; cannam@128: if (gzclose(file) < 0) cannam@128: retval = NULL; cannam@128: // File is now gone anyway (postcondition [27.8.1.3.8]) cannam@128: file = NULL; cannam@128: own_fd = false; cannam@128: // Destroy internal buffer if it exists cannam@128: this->disable_buffer(); cannam@128: return retval; cannam@128: } cannam@128: cannam@128: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ cannam@128: cannam@128: // Convert int open mode to mode string cannam@128: bool cannam@128: gzfilebuf::open_mode(std::ios_base::openmode mode, cannam@128: char* c_mode) const cannam@128: { cannam@128: bool testb = mode & std::ios_base::binary; cannam@128: bool testi = mode & std::ios_base::in; cannam@128: bool testo = mode & std::ios_base::out; cannam@128: bool testt = mode & std::ios_base::trunc; cannam@128: bool testa = mode & std::ios_base::app; cannam@128: cannam@128: // Check for valid flag combinations - see [27.8.1.3.2] (Table 92) cannam@128: // Original zfstream hardcoded the compression level to maximum here... cannam@128: // Double the time for less than 1% size improvement seems cannam@128: // excessive though - keeping it at the default level cannam@128: // To change back, just append "9" to the next three mode strings cannam@128: if (!testi && testo && !testt && !testa) cannam@128: strcpy(c_mode, "w"); cannam@128: if (!testi && testo && !testt && testa) cannam@128: strcpy(c_mode, "a"); cannam@128: if (!testi && testo && testt && !testa) cannam@128: strcpy(c_mode, "w"); cannam@128: if (testi && !testo && !testt && !testa) cannam@128: strcpy(c_mode, "r"); cannam@128: // No read/write mode yet cannam@128: // if (testi && testo && !testt && !testa) cannam@128: // strcpy(c_mode, "r+"); cannam@128: // if (testi && testo && testt && !testa) cannam@128: // strcpy(c_mode, "w+"); cannam@128: cannam@128: // Mode string should be empty for invalid combination of flags cannam@128: if (strlen(c_mode) == 0) cannam@128: return false; cannam@128: if (testb) cannam@128: strcat(c_mode, "b"); cannam@128: return true; cannam@128: } cannam@128: cannam@128: // Determine number of characters in internal get buffer cannam@128: std::streamsize cannam@128: gzfilebuf::showmanyc() cannam@128: { cannam@128: // Calls to underflow will fail if file not opened for reading cannam@128: if (!this->is_open() || !(io_mode & std::ios_base::in)) cannam@128: return -1; cannam@128: // Make sure get area is in use cannam@128: if (this->gptr() && (this->gptr() < this->egptr())) cannam@128: return std::streamsize(this->egptr() - this->gptr()); cannam@128: else cannam@128: return 0; cannam@128: } cannam@128: cannam@128: // Fill get area from gzipped file cannam@128: gzfilebuf::int_type cannam@128: gzfilebuf::underflow() cannam@128: { cannam@128: // If something is left in the get area by chance, return it cannam@128: // (this shouldn't normally happen, as underflow is only supposed cannam@128: // to be called when gptr >= egptr, but it serves as error check) cannam@128: if (this->gptr() && (this->gptr() < this->egptr())) cannam@128: return traits_type::to_int_type(*(this->gptr())); cannam@128: cannam@128: // If the file hasn't been opened for reading, produce error cannam@128: if (!this->is_open() || !(io_mode & std::ios_base::in)) cannam@128: return traits_type::eof(); cannam@128: cannam@128: // Attempt to fill internal buffer from gzipped file cannam@128: // (buffer must be guaranteed to exist...) cannam@128: int bytes_read = gzread(file, buffer, buffer_size); cannam@128: // Indicates error or EOF cannam@128: if (bytes_read <= 0) cannam@128: { cannam@128: // Reset get area cannam@128: this->setg(buffer, buffer, buffer); cannam@128: return traits_type::eof(); cannam@128: } cannam@128: // Make all bytes read from file available as get area cannam@128: this->setg(buffer, buffer, buffer + bytes_read); cannam@128: cannam@128: // Return next character in get area cannam@128: return traits_type::to_int_type(*(this->gptr())); cannam@128: } cannam@128: cannam@128: // Write put area to gzipped file cannam@128: gzfilebuf::int_type cannam@128: gzfilebuf::overflow(int_type c) cannam@128: { cannam@128: // Determine whether put area is in use cannam@128: if (this->pbase()) cannam@128: { cannam@128: // Double-check pointer range cannam@128: if (this->pptr() > this->epptr() || this->pptr() < this->pbase()) cannam@128: return traits_type::eof(); cannam@128: // Add extra character to buffer if not EOF cannam@128: if (!traits_type::eq_int_type(c, traits_type::eof())) cannam@128: { cannam@128: *(this->pptr()) = traits_type::to_char_type(c); cannam@128: this->pbump(1); cannam@128: } cannam@128: // Number of characters to write to file cannam@128: int bytes_to_write = this->pptr() - this->pbase(); cannam@128: // Overflow doesn't fail if nothing is to be written cannam@128: if (bytes_to_write > 0) cannam@128: { cannam@128: // If the file hasn't been opened for writing, produce error cannam@128: if (!this->is_open() || !(io_mode & std::ios_base::out)) cannam@128: return traits_type::eof(); cannam@128: // If gzipped file won't accept all bytes written to it, fail cannam@128: if (gzwrite(file, this->pbase(), bytes_to_write) != bytes_to_write) cannam@128: return traits_type::eof(); cannam@128: // Reset next pointer to point to pbase on success cannam@128: this->pbump(-bytes_to_write); cannam@128: } cannam@128: } cannam@128: // Write extra character to file if not EOF cannam@128: else if (!traits_type::eq_int_type(c, traits_type::eof())) cannam@128: { cannam@128: // If the file hasn't been opened for writing, produce error cannam@128: if (!this->is_open() || !(io_mode & std::ios_base::out)) cannam@128: return traits_type::eof(); cannam@128: // Impromptu char buffer (allows "unbuffered" output) cannam@128: char_type last_char = traits_type::to_char_type(c); cannam@128: // If gzipped file won't accept this character, fail cannam@128: if (gzwrite(file, &last_char, 1) != 1) cannam@128: return traits_type::eof(); cannam@128: } cannam@128: cannam@128: // If you got here, you have succeeded (even if c was EOF) cannam@128: // The return value should therefore be non-EOF cannam@128: if (traits_type::eq_int_type(c, traits_type::eof())) cannam@128: return traits_type::not_eof(c); cannam@128: else cannam@128: return c; cannam@128: } cannam@128: cannam@128: // Assign new buffer cannam@128: std::streambuf* cannam@128: gzfilebuf::setbuf(char_type* p, cannam@128: std::streamsize n) cannam@128: { cannam@128: // First make sure stuff is sync'ed, for safety cannam@128: if (this->sync() == -1) cannam@128: return NULL; cannam@128: // If buffering is turned off on purpose via setbuf(0,0), still allocate one... cannam@128: // "Unbuffered" only really refers to put [27.8.1.4.10], while get needs at cannam@128: // least a buffer of size 1 (very inefficient though, therefore make it bigger?) cannam@128: // This follows from [27.5.2.4.3]/12 (gptr needs to point at something, it seems) cannam@128: if (!p || !n) cannam@128: { cannam@128: // Replace existing buffer (if any) with small internal buffer cannam@128: this->disable_buffer(); cannam@128: buffer = NULL; cannam@128: buffer_size = 0; cannam@128: own_buffer = true; cannam@128: this->enable_buffer(); cannam@128: } cannam@128: else cannam@128: { cannam@128: // Replace existing buffer (if any) with external buffer cannam@128: this->disable_buffer(); cannam@128: buffer = p; cannam@128: buffer_size = n; cannam@128: own_buffer = false; cannam@128: this->enable_buffer(); cannam@128: } cannam@128: return this; cannam@128: } cannam@128: cannam@128: // Write put area to gzipped file (i.e. ensures that put area is empty) cannam@128: int cannam@128: gzfilebuf::sync() cannam@128: { cannam@128: return traits_type::eq_int_type(this->overflow(), traits_type::eof()) ? -1 : 0; cannam@128: } cannam@128: cannam@128: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ cannam@128: cannam@128: // Allocate internal buffer cannam@128: void cannam@128: gzfilebuf::enable_buffer() cannam@128: { cannam@128: // If internal buffer required, allocate one cannam@128: if (own_buffer && !buffer) cannam@128: { cannam@128: // Check for buffered vs. "unbuffered" cannam@128: if (buffer_size > 0) cannam@128: { cannam@128: // Allocate internal buffer cannam@128: buffer = new char_type[buffer_size]; cannam@128: // Get area starts empty and will be expanded by underflow as need arises cannam@128: this->setg(buffer, buffer, buffer); cannam@128: // Setup entire internal buffer as put area. cannam@128: // The one-past-end pointer actually points to the last element of the buffer, cannam@128: // so that overflow(c) can safely add the extra character c to the sequence. cannam@128: // These pointers remain in place for the duration of the buffer cannam@128: this->setp(buffer, buffer + buffer_size - 1); cannam@128: } cannam@128: else cannam@128: { cannam@128: // Even in "unbuffered" case, (small?) get buffer is still required cannam@128: buffer_size = SMALLBUFSIZE; cannam@128: buffer = new char_type[buffer_size]; cannam@128: this->setg(buffer, buffer, buffer); cannam@128: // "Unbuffered" means no put buffer cannam@128: this->setp(0, 0); cannam@128: } cannam@128: } cannam@128: else cannam@128: { cannam@128: // If buffer already allocated, reset buffer pointers just to make sure no cannam@128: // stale chars are lying around cannam@128: this->setg(buffer, buffer, buffer); cannam@128: this->setp(buffer, buffer + buffer_size - 1); cannam@128: } cannam@128: } cannam@128: cannam@128: // Destroy internal buffer cannam@128: void cannam@128: gzfilebuf::disable_buffer() cannam@128: { cannam@128: // If internal buffer exists, deallocate it cannam@128: if (own_buffer && buffer) cannam@128: { cannam@128: // Preserve unbuffered status by zeroing size cannam@128: if (!this->pbase()) cannam@128: buffer_size = 0; cannam@128: delete[] buffer; cannam@128: buffer = NULL; cannam@128: this->setg(0, 0, 0); cannam@128: this->setp(0, 0); cannam@128: } cannam@128: else cannam@128: { cannam@128: // Reset buffer pointers to initial state if external buffer exists cannam@128: this->setg(buffer, buffer, buffer); cannam@128: if (buffer) cannam@128: this->setp(buffer, buffer + buffer_size - 1); cannam@128: else cannam@128: this->setp(0, 0); cannam@128: } cannam@128: } cannam@128: cannam@128: /*****************************************************************************/ cannam@128: cannam@128: // Default constructor initializes stream buffer cannam@128: gzifstream::gzifstream() cannam@128: : std::istream(NULL), sb() cannam@128: { this->init(&sb); } cannam@128: cannam@128: // Initialize stream buffer and open file cannam@128: gzifstream::gzifstream(const char* name, cannam@128: std::ios_base::openmode mode) cannam@128: : std::istream(NULL), sb() cannam@128: { cannam@128: this->init(&sb); cannam@128: this->open(name, mode); cannam@128: } cannam@128: cannam@128: // Initialize stream buffer and attach to file cannam@128: gzifstream::gzifstream(int fd, cannam@128: std::ios_base::openmode mode) cannam@128: : std::istream(NULL), sb() cannam@128: { cannam@128: this->init(&sb); cannam@128: this->attach(fd, mode); cannam@128: } cannam@128: cannam@128: // Open file and go into fail() state if unsuccessful cannam@128: void cannam@128: gzifstream::open(const char* name, cannam@128: std::ios_base::openmode mode) cannam@128: { cannam@128: if (!sb.open(name, mode | std::ios_base::in)) cannam@128: this->setstate(std::ios_base::failbit); cannam@128: else cannam@128: this->clear(); cannam@128: } cannam@128: cannam@128: // Attach to file and go into fail() state if unsuccessful cannam@128: void cannam@128: gzifstream::attach(int fd, cannam@128: std::ios_base::openmode mode) cannam@128: { cannam@128: if (!sb.attach(fd, mode | std::ios_base::in)) cannam@128: this->setstate(std::ios_base::failbit); cannam@128: else cannam@128: this->clear(); cannam@128: } cannam@128: cannam@128: // Close file cannam@128: void cannam@128: gzifstream::close() cannam@128: { cannam@128: if (!sb.close()) cannam@128: this->setstate(std::ios_base::failbit); cannam@128: } cannam@128: cannam@128: /*****************************************************************************/ cannam@128: cannam@128: // Default constructor initializes stream buffer cannam@128: gzofstream::gzofstream() cannam@128: : std::ostream(NULL), sb() cannam@128: { this->init(&sb); } cannam@128: cannam@128: // Initialize stream buffer and open file cannam@128: gzofstream::gzofstream(const char* name, cannam@128: std::ios_base::openmode mode) cannam@128: : std::ostream(NULL), sb() cannam@128: { cannam@128: this->init(&sb); cannam@128: this->open(name, mode); cannam@128: } cannam@128: cannam@128: // Initialize stream buffer and attach to file cannam@128: gzofstream::gzofstream(int fd, cannam@128: std::ios_base::openmode mode) cannam@128: : std::ostream(NULL), sb() cannam@128: { cannam@128: this->init(&sb); cannam@128: this->attach(fd, mode); cannam@128: } cannam@128: cannam@128: // Open file and go into fail() state if unsuccessful cannam@128: void cannam@128: gzofstream::open(const char* name, cannam@128: std::ios_base::openmode mode) cannam@128: { cannam@128: if (!sb.open(name, mode | std::ios_base::out)) cannam@128: this->setstate(std::ios_base::failbit); cannam@128: else cannam@128: this->clear(); cannam@128: } cannam@128: cannam@128: // Attach to file and go into fail() state if unsuccessful cannam@128: void cannam@128: gzofstream::attach(int fd, cannam@128: std::ios_base::openmode mode) cannam@128: { cannam@128: if (!sb.attach(fd, mode | std::ios_base::out)) cannam@128: this->setstate(std::ios_base::failbit); cannam@128: else cannam@128: this->clear(); cannam@128: } cannam@128: cannam@128: // Close file cannam@128: void cannam@128: gzofstream::close() cannam@128: { cannam@128: if (!sb.close()) cannam@128: this->setstate(std::ios_base::failbit); cannam@128: }