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
21gzfilebuf::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
30gzfilebuf::~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
42int
43gzfilebuf::setcompression(int comp_level,
44                          int comp_strategy)
45{
46  return gzsetparams(file, comp_level, comp_strategy);
47}
48
49// Open gzipped file
50gzfilebuf*
51gzfilebuf::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
78gzfilebuf*
79gzfilebuf::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
106gzfilebuf*
107gzfilebuf::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
130bool
131gzfilebuf::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
168std::streamsize
169gzfilebuf::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
182gzfilebuf::int_type
183gzfilebuf::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
213gzfilebuf::int_type
214gzfilebuf::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
265std::streambuf*
266gzfilebuf::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)
298int
299gzfilebuf::sync()
300{
301  return traits_type::eq_int_type(this->overflow(), traits_type::eof()) ? -1 : 0;
302}
303
304/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
305
306// Allocate internal buffer
307void
308gzfilebuf::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
346void
347gzfilebuf::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
374gzifstream::gzifstream()
375: std::istream(NULL), sb()
376{ this->init(&sb); }
377
378// Initialize stream buffer and open file
379gzifstream::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
388gzifstream::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
397void
398gzifstream::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
408void
409gzifstream::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
419void
420gzifstream::close()
421{
422  if (!sb.close())
423    this->setstate(std::ios_base::failbit);
424}
425
426/*****************************************************************************/
427
428// Default constructor initializes stream buffer
429gzofstream::gzofstream()
430: std::ostream(NULL), sb()
431{ this->init(&sb); }
432
433// Initialize stream buffer and open file
434gzofstream::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
443gzofstream::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
452void
453gzofstream::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
463void
464gzofstream::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
474void
475gzofstream::close()
476{
477  if (!sb.close())
478    this->setstate(std::ios_base::failbit);
479}
480