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
gzfilebuf()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
~gzfilebuf()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
setcompression(int comp_level,int comp_strategy)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*
open(const char * name,std::ios_base::openmode mode)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[7] = "\0\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*
attach(int fd,std::ios_base::openmode mode)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[7] = "\0\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*
close()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
open_mode(std::ios_base::openmode mode,char * c_mode) const131 gzfilebuf::open_mode(std::ios_base::openmode mode,
132                      char* c_mode) const
133 {
134   bool testb = (mode & std::ios_base::binary) == std::ios_base::binary;
135   bool testi = (mode & std::ios_base::in) == std::ios_base::in;
136   bool testo = (mode & std::ios_base::out) == std::ios_base::out;
137   bool testt = (mode & std::ios_base::trunc) == std::ios_base::trunc;
138   bool testa = (mode & std::ios_base::app) == 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     c_mode[1] = 'b';
164     //strcat(c_mode, "b");
165   return true;
166 }
167 
168 // Determine number of characters in internal get buffer
169 std::streamsize
showmanyc()170 gzfilebuf::showmanyc()
171 {
172   // Calls to underflow will fail if file not opened for reading
173   if (!this->is_open() || !(io_mode & std::ios_base::in))
174     return -1;
175   // Make sure get area is in use
176   if (this->gptr() && (this->gptr() < this->egptr()))
177     return std::streamsize(this->egptr() - this->gptr());
178   else
179     return 0;
180 }
181 
182 // Fill get area from gzipped file
183 gzfilebuf::int_type
underflow()184 gzfilebuf::underflow()
185 {
186   // If something is left in the get area by chance, return it
187   // (this shouldn't normally happen, as underflow is only supposed
188   // to be called when gptr >= egptr, but it serves as error check)
189   if (this->gptr() && (this->gptr() < this->egptr()))
190     return traits_type::to_int_type(*(this->gptr()));
191 
192   // If the file hasn't been opened for reading, produce error
193   if (!this->is_open() || !(io_mode & std::ios_base::in))
194     return traits_type::eof();
195 
196   // Attempt to fill internal buffer from gzipped file
197   // (buffer must be guaranteed to exist...)
198   int bytes_read = gzread(file, buffer, (unsigned int)buffer_size);
199   // Indicates error or EOF
200   if (bytes_read <= 0)
201   {
202     // Reset get area
203     this->setg(buffer, buffer, buffer);
204     return traits_type::eof();
205   }
206   // Make all bytes read from file available as get area
207   this->setg(buffer, buffer, buffer + bytes_read);
208 
209   // Return next character in get area
210   return traits_type::to_int_type(*(this->gptr()));
211 }
212 
213 // Write put area to gzipped file
214 gzfilebuf::int_type
overflow(int_type c)215 gzfilebuf::overflow(int_type c)
216 {
217   // Determine whether put area is in use
218   if (this->pbase())
219   {
220     // Double-check pointer range
221     if (this->pptr() > this->epptr() || this->pptr() < this->pbase())
222       return traits_type::eof();
223     // Add extra character to buffer if not EOF
224     if (!traits_type::eq_int_type(c, traits_type::eof()))
225     {
226       *(this->pptr()) = traits_type::to_char_type(c);
227       this->pbump(1);
228     }
229     // Number of characters to write to file
230     int bytes_to_write = int(this->pptr() - this->pbase());
231     // Overflow doesn't fail if nothing is to be written
232     if (bytes_to_write > 0)
233     {
234       // If the file hasn't been opened for writing, produce error
235       if (!this->is_open() || !(io_mode & std::ios_base::out))
236         return traits_type::eof();
237       // If gzipped file won't accept all bytes written to it, fail
238       if (gzwrite(file, this->pbase(), bytes_to_write) != bytes_to_write)
239         return traits_type::eof();
240       // Reset next pointer to point to pbase on success
241       this->pbump(-bytes_to_write);
242     }
243   }
244   // Write extra character to file if not EOF
245   else if (!traits_type::eq_int_type(c, traits_type::eof()))
246   {
247     // If the file hasn't been opened for writing, produce error
248     if (!this->is_open() || !(io_mode & std::ios_base::out))
249       return traits_type::eof();
250     // Impromptu char buffer (allows "unbuffered" output)
251     char_type last_char = traits_type::to_char_type(c);
252     // If gzipped file won't accept this character, fail
253     if (gzwrite(file, &last_char, 1) != 1)
254       return traits_type::eof();
255   }
256 
257   // If you got here, you have succeeded (even if c was EOF)
258   // The return value should therefore be non-EOF
259   if (traits_type::eq_int_type(c, traits_type::eof()))
260     return traits_type::not_eof(c);
261   else
262     return c;
263 }
264 
265 // Assign new buffer
266 std::streambuf*
setbuf(char_type * p,std::streamsize n)267 gzfilebuf::setbuf(char_type* p,
268                   std::streamsize n)
269 {
270   // First make sure stuff is sync'ed, for safety
271   if (this->sync() == -1)
272     return NULL;
273   // If buffering is turned off on purpose via setbuf(0,0), still allocate one...
274   // "Unbuffered" only really refers to put [27.8.1.4.10], while get needs at
275   // least a buffer of size 1 (very inefficient though, therefore make it bigger?)
276   // This follows from [27.5.2.4.3]/12 (gptr needs to point at something, it seems)
277   if (!p || !n)
278   {
279     // Replace existing buffer (if any) with small internal buffer
280     this->disable_buffer();
281     buffer = NULL;
282     buffer_size = 0;
283     own_buffer = true;
284     this->enable_buffer();
285   }
286   else
287   {
288     // Replace existing buffer (if any) with external buffer
289     this->disable_buffer();
290     buffer = p;
291     buffer_size = n;
292     own_buffer = false;
293     this->enable_buffer();
294   }
295   return this;
296 }
297 
298 // Write put area to gzipped file (i.e. ensures that put area is empty)
299 int
sync()300 gzfilebuf::sync()
301 {
302   return traits_type::eq_int_type(this->overflow(), traits_type::eof()) ? -1 : 0;
303 }
304 
305 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
306 
307 // Allocate internal buffer
308 void
enable_buffer()309 gzfilebuf::enable_buffer()
310 {
311   // If internal buffer required, allocate one
312   if (own_buffer && !buffer)
313   {
314     // Check for buffered vs. "unbuffered"
315     if (buffer_size > 0)
316     {
317       // Allocate internal buffer
318       buffer = new char_type[(unsigned int)buffer_size];
319       // Get area starts empty and will be expanded by underflow as need arises
320       this->setg(buffer, buffer, buffer);
321       // Setup entire internal buffer as put area.
322       // The one-past-end pointer actually points to the last element of the buffer,
323       // so that overflow(c) can safely add the extra character c to the sequence.
324       // These pointers remain in place for the duration of the buffer
325       this->setp(buffer, buffer + buffer_size - 1);
326     }
327     else
328     {
329       // Even in "unbuffered" case, (small?) get buffer is still required
330       buffer_size = SMALLBUFSIZE;
331       buffer = new char_type[(unsigned int)buffer_size];
332       this->setg(buffer, buffer, buffer);
333       // "Unbuffered" means no put buffer
334       this->setp(0, 0);
335     }
336   }
337   else
338   {
339     // If buffer already allocated, reset buffer pointers just to make sure no
340     // stale chars are lying around
341     this->setg(buffer, buffer, buffer);
342     this->setp(buffer, buffer + buffer_size - 1);
343   }
344 }
345 
346 // Destroy internal buffer
347 void
disable_buffer()348 gzfilebuf::disable_buffer()
349 {
350   // If internal buffer exists, deallocate it
351   if (own_buffer && buffer)
352   {
353     // Preserve unbuffered status by zeroing size
354     if (!this->pbase())
355       buffer_size = 0;
356     delete[] buffer;
357     buffer = NULL;
358     this->setg(0, 0, 0);
359     this->setp(0, 0);
360   }
361   else
362   {
363     // Reset buffer pointers to initial state if external buffer exists
364     this->setg(buffer, buffer, buffer);
365     if (buffer)
366       this->setp(buffer, buffer + buffer_size - 1);
367     else
368       this->setp(0, 0);
369   }
370 }
371 
372 /*****************************************************************************/
373 
374 // Default constructor initializes stream buffer
gzifstream()375 gzifstream::gzifstream()
376 : std::istream(NULL), sb()
377 { this->init(&sb); }
378 
379 // Initialize stream buffer and open file
gzifstream(const char * name,std::ios_base::openmode mode)380 gzifstream::gzifstream(const char* name,
381                        std::ios_base::openmode mode)
382 : std::istream(NULL), sb()
383 {
384   this->init(&sb);
385   this->open(name, mode);
386 }
387 
388 // Initialize stream buffer and attach to file
gzifstream(int fd,std::ios_base::openmode mode)389 gzifstream::gzifstream(int fd,
390                        std::ios_base::openmode mode)
391 : std::istream(NULL), sb()
392 {
393   this->init(&sb);
394   this->attach(fd, mode);
395 }
396 
397 // Open file and go into fail() state if unsuccessful
398 void
open(const char * name,std::ios_base::openmode mode)399 gzifstream::open(const char* name,
400                  std::ios_base::openmode mode)
401 {
402   if (!sb.open(name, mode | std::ios_base::in))
403     this->setstate(std::ios_base::failbit);
404   else
405     this->clear();
406 }
407 
408 // Attach to file and go into fail() state if unsuccessful
409 void
attach(int fd,std::ios_base::openmode mode)410 gzifstream::attach(int fd,
411                    std::ios_base::openmode mode)
412 {
413   if (!sb.attach(fd, mode | std::ios_base::in))
414     this->setstate(std::ios_base::failbit);
415   else
416     this->clear();
417 }
418 
419 // Close file
420 void
close()421 gzifstream::close()
422 {
423   if (!sb.close())
424     this->setstate(std::ios_base::failbit);
425 }
426 
427 /*****************************************************************************/
428 
429 // Default constructor initializes stream buffer
gzofstream()430 gzofstream::gzofstream()
431 : std::ostream(NULL), sb()
432 { this->init(&sb); }
433 
434 // Initialize stream buffer and open file
gzofstream(const char * name,std::ios_base::openmode mode)435 gzofstream::gzofstream(const char* name,
436                        std::ios_base::openmode mode)
437 : std::ostream(NULL), sb()
438 {
439   this->init(&sb);
440   this->open(name, mode);
441 }
442 
443 // Initialize stream buffer and attach to file
gzofstream(int fd,std::ios_base::openmode mode)444 gzofstream::gzofstream(int fd,
445                        std::ios_base::openmode mode)
446 : std::ostream(NULL), sb()
447 {
448   this->init(&sb);
449   this->attach(fd, mode);
450 }
451 
452 // Open file and go into fail() state if unsuccessful
453 void
open(const char * name,std::ios_base::openmode mode)454 gzofstream::open(const char* name,
455                  std::ios_base::openmode mode)
456 {
457   if (!sb.open(name, mode | std::ios_base::out))
458     this->setstate(std::ios_base::failbit);
459   else
460     this->clear();
461 }
462 
463 // Attach to file and go into fail() state if unsuccessful
464 void
attach(int fd,std::ios_base::openmode mode)465 gzofstream::attach(int fd,
466                    std::ios_base::openmode mode)
467 {
468   if (!sb.attach(fd, mode | std::ios_base::out))
469     this->setstate(std::ios_base::failbit);
470   else
471     this->clear();
472 }
473 
474 // Close file
475 void
close()476 gzofstream::close()
477 {
478   if (!sb.close())
479     this->setstate(std::ios_base::failbit);
480 }
481