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