Home | History | Annotate | Download | only in iostream3
      1 /*
      2  * A C++ I/O streams interface to the zlib gz* functions
      3  *
      4  * by Ludwig Schwardt <schwardt (at) sun.ac.za>
      5  * original version by Kevin Ruland <kevin (at) rodin.wustl.edu>
      6  *
      7  * This version is standard-compliant and compatible with gcc 3.x.
      8  */
      9 
     10 #include "zfstream.h"
     11 #include <cstring>          // for strcpy, strcat, strlen (mode strings)
     12 #include <cstdio>           // for BUFSIZ
     13 
     14 // Internal buffer sizes (default and "unbuffered" versions)
     15 #define BIGBUFSIZE BUFSIZ
     16 #define SMALLBUFSIZE 1
     17 
     18 /*****************************************************************************/
     19 
     20 // Default constructor
     21 gzfilebuf::gzfilebuf()
     22 : file(NULL), io_mode(std::ios_base::openmode(0)), own_fd(false),
     23   buffer(NULL), buffer_size(BIGBUFSIZE), own_buffer(true)
     24 {
     25   // No buffers to start with
     26   this->disable_buffer();
     27 }
     28 
     29 // Destructor
     30 gzfilebuf::~gzfilebuf()
     31 {
     32   // Sync output buffer and close only if responsible for file
     33   // (i.e. attached streams should be left open at this stage)
     34   this->sync();
     35   if (own_fd)
     36     this->close();
     37   // Make sure internal buffer is deallocated
     38   this->disable_buffer();
     39 }
     40 
     41 // Set compression level and strategy
     42 int
     43 gzfilebuf::setcompression(int comp_level,
     44                           int comp_strategy)
     45 {
     46   return gzsetparams(file, comp_level, comp_strategy);
     47 }
     48 
     49 // Open gzipped file
     50 gzfilebuf*
     51 gzfilebuf::open(const char *name,
     52                 std::ios_base::openmode mode)
     53 {
     54   // Fail if file already open
     55   if (this->is_open())
     56     return NULL;
     57   // Don't support simultaneous read/write access (yet)
     58   if ((mode & std::ios_base::in) && (mode & std::ios_base::out))
     59     return NULL;
     60 
     61   // Build mode string for gzopen and check it [27.8.1.3.2]
     62   char char_mode[6] = "\0\0\0\0\0";
     63   if (!this->open_mode(mode, char_mode))
     64     return NULL;
     65 
     66   // Attempt to open file
     67   if ((file = gzopen(name, char_mode)) == NULL)
     68     return NULL;
     69 
     70   // On success, allocate internal buffer and set flags
     71   this->enable_buffer();
     72   io_mode = mode;
     73   own_fd = true;
     74   return this;
     75 }
     76 
     77 // Attach to gzipped file
     78 gzfilebuf*
     79 gzfilebuf::attach(int fd,
     80                   std::ios_base::openmode mode)
     81 {
     82   // Fail if file already open
     83   if (this->is_open())
     84     return NULL;
     85   // Don't support simultaneous read/write access (yet)
     86   if ((mode & std::ios_base::in) && (mode & std::ios_base::out))
     87     return NULL;
     88 
     89   // Build mode string for gzdopen and check it [27.8.1.3.2]
     90   char char_mode[6] = "\0\0\0\0\0";
     91   if (!this->open_mode(mode, char_mode))
     92     return NULL;
     93 
     94   // Attempt to attach to file
     95   if ((file = gzdopen(fd, char_mode)) == NULL)
     96     return NULL;
     97 
     98   // On success, allocate internal buffer and set flags
     99   this->enable_buffer();
    100   io_mode = mode;
    101   own_fd = false;
    102   return this;
    103 }
    104 
    105 // Close gzipped file
    106 gzfilebuf*
    107 gzfilebuf::close()
    108 {
    109   // Fail immediately if no file is open
    110   if (!this->is_open())
    111     return NULL;
    112   // Assume success
    113   gzfilebuf* retval = this;
    114   // Attempt to sync and close gzipped file
    115   if (this->sync() == -1)
    116     retval = NULL;
    117   if (gzclose(file) < 0)
    118     retval = NULL;
    119   // File is now gone anyway (postcondition [27.8.1.3.8])
    120   file = NULL;
    121   own_fd = false;
    122   // Destroy internal buffer if it exists
    123   this->disable_buffer();
    124   return retval;
    125 }
    126 
    127 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    128 
    129 // Convert int open mode to mode string
    130 bool
    131 gzfilebuf::open_mode(std::ios_base::openmode mode,
    132                      char* c_mode) const
    133 {
    134   bool testb = mode & std::ios_base::binary;
    135   bool testi = mode & std::ios_base::in;
    136   bool testo = mode & std::ios_base::out;
    137   bool testt = mode & std::ios_base::trunc;
    138   bool testa = mode & std::ios_base::app;
    139 
    140   // Check for valid flag combinations - see [27.8.1.3.2] (Table 92)
    141   // Original zfstream hardcoded the compression level to maximum here...
    142   // Double the time for less than 1% size improvement seems
    143   // excessive though - keeping it at the default level
    144   // To change back, just append "9" to the next three mode strings
    145   if (!testi && testo && !testt && !testa)
    146     strcpy(c_mode, "w");
    147   if (!testi && testo && !testt && testa)
    148     strcpy(c_mode, "a");
    149   if (!testi && testo && testt && !testa)
    150     strcpy(c_mode, "w");
    151   if (testi && !testo && !testt && !testa)
    152     strcpy(c_mode, "r");
    153   // No read/write mode yet
    154 //  if (testi && testo && !testt && !testa)
    155 //    strcpy(c_mode, "r+");
    156 //  if (testi && testo && testt && !testa)
    157 //    strcpy(c_mode, "w+");
    158 
    159   // Mode string should be empty for invalid combination of flags
    160   if (strlen(c_mode) == 0)
    161     return false;
    162   if (testb)
    163     strcat(c_mode, "b");
    164   return true;
    165 }
    166 
    167 // Determine number of characters in internal get buffer
    168 std::streamsize
    169 gzfilebuf::showmanyc()
    170 {
    171   // Calls to underflow will fail if file not opened for reading
    172   if (!this->is_open() || !(io_mode & std::ios_base::in))
    173     return -1;
    174   // Make sure get area is in use
    175   if (this->gptr() && (this->gptr() < this->egptr()))
    176     return std::streamsize(this->egptr() - this->gptr());
    177   else
    178     return 0;
    179 }
    180 
    181 // Fill get area from gzipped file
    182 gzfilebuf::int_type
    183 gzfilebuf::underflow()
    184 {
    185   // If something is left in the get area by chance, return it
    186   // (this shouldn't normally happen, as underflow is only supposed
    187   // to be called when gptr >= egptr, but it serves as error check)
    188   if (this->gptr() && (this->gptr() < this->egptr()))
    189     return traits_type::to_int_type(*(this->gptr()));
    190 
    191   // If the file hasn't been opened for reading, produce error
    192   if (!this->is_open() || !(io_mode & std::ios_base::in))
    193     return traits_type::eof();
    194 
    195   // Attempt to fill internal buffer from gzipped file
    196   // (buffer must be guaranteed to exist...)
    197   int bytes_read = gzread(file, buffer, buffer_size);
    198   // Indicates error or EOF
    199   if (bytes_read <= 0)
    200   {
    201     // Reset get area
    202     this->setg(buffer, buffer, buffer);
    203     return traits_type::eof();
    204   }
    205   // Make all bytes read from file available as get area
    206   this->setg(buffer, buffer, buffer + bytes_read);
    207 
    208   // Return next character in get area
    209   return traits_type::to_int_type(*(this->gptr()));
    210 }
    211 
    212 // Write put area to gzipped file
    213 gzfilebuf::int_type
    214 gzfilebuf::overflow(int_type c)
    215 {
    216   // Determine whether put area is in use
    217   if (this->pbase())
    218   {
    219     // Double-check pointer range
    220     if (this->pptr() > this->epptr() || this->pptr() < this->pbase())
    221       return traits_type::eof();
    222     // Add extra character to buffer if not EOF
    223     if (!traits_type::eq_int_type(c, traits_type::eof()))
    224     {
    225       *(this->pptr()) = traits_type::to_char_type(c);
    226       this->pbump(1);
    227     }
    228     // Number of characters to write to file
    229     int bytes_to_write = this->pptr() - this->pbase();
    230     // Overflow doesn't fail if nothing is to be written
    231     if (bytes_to_write > 0)
    232     {
    233       // If the file hasn't been opened for writing, produce error
    234       if (!this->is_open() || !(io_mode & std::ios_base::out))
    235         return traits_type::eof();
    236       // If gzipped file won't accept all bytes written to it, fail
    237       if (gzwrite(file, this->pbase(), bytes_to_write) != bytes_to_write)
    238         return traits_type::eof();
    239       // Reset next pointer to point to pbase on success
    240       this->pbump(-bytes_to_write);
    241     }
    242   }
    243   // Write extra character to file if not EOF
    244   else if (!traits_type::eq_int_type(c, traits_type::eof()))
    245   {
    246     // If the file hasn't been opened for writing, produce error
    247     if (!this->is_open() || !(io_mode & std::ios_base::out))
    248       return traits_type::eof();
    249     // Impromptu char buffer (allows "unbuffered" output)
    250     char_type last_char = traits_type::to_char_type(c);
    251     // If gzipped file won't accept this character, fail
    252     if (gzwrite(file, &last_char, 1) != 1)
    253       return traits_type::eof();
    254   }
    255 
    256   // If you got here, you have succeeded (even if c was EOF)
    257   // The return value should therefore be non-EOF
    258   if (traits_type::eq_int_type(c, traits_type::eof()))
    259     return traits_type::not_eof(c);
    260   else
    261     return c;
    262 }
    263 
    264 // Assign new buffer
    265 std::streambuf*
    266 gzfilebuf::setbuf(char_type* p,
    267                   std::streamsize n)
    268 {
    269   // First make sure stuff is sync'ed, for safety
    270   if (this->sync() == -1)
    271     return NULL;
    272   // If buffering is turned off on purpose via setbuf(0,0), still allocate one...
    273   // "Unbuffered" only really refers to put [27.8.1.4.10], while get needs at
    274   // least a buffer of size 1 (very inefficient though, therefore make it bigger?)
    275   // This follows from [27.5.2.4.3]/12 (gptr needs to point at something, it seems)
    276   if (!p || !n)
    277   {
    278     // Replace existing buffer (if any) with small internal buffer
    279     this->disable_buffer();
    280     buffer = NULL;
    281     buffer_size = 0;
    282     own_buffer = true;
    283     this->enable_buffer();
    284   }
    285   else
    286   {
    287     // Replace existing buffer (if any) with external buffer
    288     this->disable_buffer();
    289     buffer = p;
    290     buffer_size = n;
    291     own_buffer = false;
    292     this->enable_buffer();
    293   }
    294   return this;
    295 }
    296 
    297 // Write put area to gzipped file (i.e. ensures that put area is empty)
    298 int
    299 gzfilebuf::sync()
    300 {
    301   return traits_type::eq_int_type(this->overflow(), traits_type::eof()) ? -1 : 0;
    302 }
    303 
    304 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    305 
    306 // Allocate internal buffer
    307 void
    308 gzfilebuf::enable_buffer()
    309 {
    310   // If internal buffer required, allocate one
    311   if (own_buffer && !buffer)
    312   {
    313     // Check for buffered vs. "unbuffered"
    314     if (buffer_size > 0)
    315     {
    316       // Allocate internal buffer
    317       buffer = new char_type[buffer_size];
    318       // Get area starts empty and will be expanded by underflow as need arises
    319       this->setg(buffer, buffer, buffer);
    320       // Setup entire internal buffer as put area.
    321       // The one-past-end pointer actually points to the last element of the buffer,
    322       // so that overflow(c) can safely add the extra character c to the sequence.
    323       // These pointers remain in place for the duration of the buffer
    324       this->setp(buffer, buffer + buffer_size - 1);
    325     }
    326     else
    327     {
    328       // Even in "unbuffered" case, (small?) get buffer is still required
    329       buffer_size = SMALLBUFSIZE;
    330       buffer = new char_type[buffer_size];
    331       this->setg(buffer, buffer, buffer);
    332       // "Unbuffered" means no put buffer
    333       this->setp(0, 0);
    334     }
    335   }
    336   else
    337   {
    338     // If buffer already allocated, reset buffer pointers just to make sure no
    339     // stale chars are lying around
    340     this->setg(buffer, buffer, buffer);
    341     this->setp(buffer, buffer + buffer_size - 1);
    342   }
    343 }
    344 
    345 // Destroy internal buffer
    346 void
    347 gzfilebuf::disable_buffer()
    348 {
    349   // If internal buffer exists, deallocate it
    350   if (own_buffer && buffer)
    351   {
    352     // Preserve unbuffered status by zeroing size
    353     if (!this->pbase())
    354       buffer_size = 0;
    355     delete[] buffer;
    356     buffer = NULL;
    357     this->setg(0, 0, 0);
    358     this->setp(0, 0);
    359   }
    360   else
    361   {
    362     // Reset buffer pointers to initial state if external buffer exists
    363     this->setg(buffer, buffer, buffer);
    364     if (buffer)
    365       this->setp(buffer, buffer + buffer_size - 1);
    366     else
    367       this->setp(0, 0);
    368   }
    369 }
    370 
    371 /*****************************************************************************/
    372 
    373 // Default constructor initializes stream buffer
    374 gzifstream::gzifstream()
    375 : std::istream(NULL), sb()
    376 { this->init(&sb); }
    377 
    378 // Initialize stream buffer and open file
    379 gzifstream::gzifstream(const char* name,
    380                        std::ios_base::openmode mode)
    381 : std::istream(NULL), sb()
    382 {
    383   this->init(&sb);
    384   this->open(name, mode);
    385 }
    386 
    387 // Initialize stream buffer and attach to file
    388 gzifstream::gzifstream(int fd,
    389                        std::ios_base::openmode mode)
    390 : std::istream(NULL), sb()
    391 {
    392   this->init(&sb);
    393   this->attach(fd, mode);
    394 }
    395 
    396 // Open file and go into fail() state if unsuccessful
    397 void
    398 gzifstream::open(const char* name,
    399                  std::ios_base::openmode mode)
    400 {
    401   if (!sb.open(name, mode | std::ios_base::in))
    402     this->setstate(std::ios_base::failbit);
    403   else
    404     this->clear();
    405 }
    406 
    407 // Attach to file and go into fail() state if unsuccessful
    408 void
    409 gzifstream::attach(int fd,
    410                    std::ios_base::openmode mode)
    411 {
    412   if (!sb.attach(fd, mode | std::ios_base::in))
    413     this->setstate(std::ios_base::failbit);
    414   else
    415     this->clear();
    416 }
    417 
    418 // Close file
    419 void
    420 gzifstream::close()
    421 {
    422   if (!sb.close())
    423     this->setstate(std::ios_base::failbit);
    424 }
    425 
    426 /*****************************************************************************/
    427 
    428 // Default constructor initializes stream buffer
    429 gzofstream::gzofstream()
    430 : std::ostream(NULL), sb()
    431 { this->init(&sb); }
    432 
    433 // Initialize stream buffer and open file
    434 gzofstream::gzofstream(const char* name,
    435                        std::ios_base::openmode mode)
    436 : std::ostream(NULL), sb()
    437 {
    438   this->init(&sb);
    439   this->open(name, mode);
    440 }
    441 
    442 // Initialize stream buffer and attach to file
    443 gzofstream::gzofstream(int fd,
    444                        std::ios_base::openmode mode)
    445 : std::ostream(NULL), sb()
    446 {
    447   this->init(&sb);
    448   this->attach(fd, mode);
    449 }
    450 
    451 // Open file and go into fail() state if unsuccessful
    452 void
    453 gzofstream::open(const char* name,
    454                  std::ios_base::openmode mode)
    455 {
    456   if (!sb.open(name, mode | std::ios_base::out))
    457     this->setstate(std::ios_base::failbit);
    458   else
    459     this->clear();
    460 }
    461 
    462 // Attach to file and go into fail() state if unsuccessful
    463 void
    464 gzofstream::attach(int fd,
    465                    std::ios_base::openmode mode)
    466 {
    467   if (!sb.attach(fd, mode | std::ios_base::out))
    468     this->setstate(std::ios_base::failbit);
    469   else
    470     this->clear();
    471 }
    472 
    473 // Close file
    474 void
    475 gzofstream::close()
    476 {
    477   if (!sb.close())
    478     this->setstate(std::ios_base::failbit);
    479 }
    480