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