1 /*
   2  * A C++ I/O streams interface to the zlib gz* functions
   3  *
   4  * by Ludwig Schwardt <schwardt@sun.ac.za>
   5  * original version by Kevin Ruland <kevin@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 }