GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
gzfstream.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2005-2024 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 /*
27 
28  This file is adapted from the zlib 1.2.2 contrib/iostream3 code,
29  written by
30 
31  Ludwig Schwardt <schwardt@sun.ac.za>
32  original version by Kevin Ruland <kevin@rodin.wustl.edu>
33 
34 */
35 
36 #if defined (HAVE_CONFIG_H)
37 # include "config.h"
38 #endif
39 
40 #include <iomanip>
41 #include <istream>
42 #include <ostream>
43 
44 #include "gzfstream.h"
45 
46 #if defined (HAVE_ZLIB)
47 
48 // For strcpy, strcat, strlen (mode strings).
49 #include <cstring>
50 // For BUFSIZ.
51 #include <cstdio>
52 
53 // Internal buffer sizes (default and "unbuffered" versions)
54 #define STASHED_CHARACTERS 16
55 #define BIGBUFSIZE (256 * 1024 + STASHED_CHARACTERS)
56 #define SMALLBUFSIZE 1
57 
58 // Default constructor
60  : m_file(nullptr), m_io_mode(std::ios_base::openmode(0)), m_own_fd(false),
61  m_buffer(nullptr), m_buffer_size(BIGBUFSIZE), m_own_buffer(true)
62 {
63  // No buffers to start with
64  this->disable_buffer ();
65 }
66 
67 // Destructor
69 {
70  // Sync output buffer and close only if responsible for file
71  // (i.e., attached streams should be left open at this stage)
72  this->sync ();
73  if (m_own_fd)
74  this->close ();
75  // Make sure internal buffer is deallocated
76  this->disable_buffer ();
77 }
78 
79 // Set compression level and strategy
80 int
81 gzfilebuf::setcompression (int comp_level, int comp_strategy)
82 {
83  return gzsetparams (m_file, comp_level, comp_strategy);
84 }
85 
86 // Open gzipped file
87 gzfilebuf *
88 gzfilebuf::open (const char *name, std::ios_base::openmode mode)
89 {
90  // Fail if file already open
91  if (this->is_open ())
92  return nullptr;
93  // Don't support simultaneous read/write access (yet)
94  if ((mode & std::ios_base::in) && (mode & std::ios_base::out))
95  return nullptr;
96 
97  // Build mode string for gzopen and check it [27.8.1.3.2]
98  char char_mode[6] = "\0\0\0\0\0";
99  if (! this->open_mode (mode, char_mode))
100  return nullptr;
101 
102  // Attempt to open file
103  if ((m_file = gzopen (name, char_mode)) == nullptr)
104  return nullptr;
105 
106  // On success, allocate internal buffer and set flags
107  this->enable_buffer ();
108  m_io_mode = mode;
109  m_own_fd = true;
110  return this;
111 }
112 
113 // Attach to gzipped file
114 gzfilebuf *
115 gzfilebuf::attach (int fd, std::ios_base::openmode mode)
116 {
117  // Fail if file already open
118  if (this->is_open ())
119  return nullptr;
120  // Don't support simultaneous read/write access (yet)
121  if ((mode & std::ios_base::in) && (mode & std::ios_base::out))
122  return nullptr;
123 
124  // Build mode string for gzdopen and check it [27.8.1.3.2]
125  char char_mode[6] = "\0\0\0\0\0";
126  if (! this->open_mode (mode, char_mode))
127  return nullptr;
128 
129  // Attempt to attach to file
130  if ((m_file = gzdopen (fd, char_mode)) == nullptr)
131  return nullptr;
132 
133  // On success, allocate internal buffer and set flags
134  this->enable_buffer ();
135  m_io_mode = mode;
136  m_own_fd = false;
137  return this;
138 }
139 
140 // Close gzipped file
141 gzfilebuf *
143 {
144  // Fail immediately if no file is open
145  if (! this->is_open ())
146  return nullptr;
147  // Assume success
148  gzfilebuf *retval = this;
149  // Attempt to sync and close gzipped file
150  if (this->sync () == -1)
151  retval = nullptr;
152  if (gzclose (m_file) < 0)
153  retval = nullptr;
154  // File is now gone anyway (postcondition [27.8.1.3.8])
155  m_file = nullptr;
156  m_own_fd = false;
157  // Destroy internal buffer if it exists
158  this->disable_buffer ();
159  return retval;
160 }
161 
162 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
163 
164 // Convert int open mode to mode string
165 bool
166 gzfilebuf::open_mode (std::ios_base::openmode mode, char *c_mode) const
167 {
168  // FIXME: do we need testb?
169  // bool testb = mode & std::ios_base::binary;
170  bool testi = mode & std::ios_base::in;
171  bool testo = mode & std::ios_base::out;
172  bool testt = mode & std::ios_base::trunc;
173  bool testa = mode & std::ios_base::app;
174 
175  // Check for valid flag combinations - see [27.8.1.3.2] (Table 92)
176  // Original zfstream hardcoded the compression level to maximum here...
177  // Double the time for less than 1% size improvement seems
178  // excessive though - keeping it at the default level
179  // To change back, just append "9" to the next three mode strings
180  if (! testi && testo && ! testt && ! testa)
181  strcpy (c_mode, "w");
182  if (! testi && testo && ! testt && testa)
183  strcpy (c_mode, "a");
184  if (! testi && testo && testt && ! testa)
185  strcpy (c_mode, "w");
186  if (testi && ! testo && ! testt && ! testa)
187  strcpy (c_mode, "r");
188  // No read/write mode yet
189  // if (testi && testo && ! testt && ! testa)
190  // strcpy(c_mode, "r+");
191  // if (testi && testo && testt && ! testa)
192  // strcpy(c_mode, "w+");
193 
194  // Mode string should be empty for invalid combination of flags
195  if (strlen (c_mode) == 0)
196  return false;
197 
198  strcat (c_mode, "b");
199 
200  return true;
201 }
202 
203 // Determine number of characters in internal get buffer
204 std::streamsize
206 {
207  // Calls to underflow will fail if file not opened for reading
208  if (! this->is_open () || ! (m_io_mode & std::ios_base::in))
209  return -1;
210  // Make sure get area is in use
211  if (this->gptr () && (this->gptr () < this->egptr ()))
212  return std::streamsize (this->egptr () - this->gptr ());
213  else
214  return 0;
215 }
216 
217 // Puts back a character to the stream in two cases. Firstly, when there
218 // is no putback position available, and secondly when the character putback
219 // differs from the one in the file. We can only support the first case
220 // with gzipped files.
221 gzfilebuf::int_type
222 gzfilebuf::pbackfail (gzfilebuf::int_type c)
223 {
224  if (this->is_open ())
225  {
226  if (gzseek (m_file, this->gptr () - this->egptr () - 1, SEEK_CUR) < 0)
227  return traits_type::eof ();
228 
229  // Invalidates contents of the buffer
230  enable_buffer ();
231 
232  // Attempt to fill internal buffer from gzipped file
233  // (buffer must be guaranteed to exist...)
234  int bytes_read = gzread (m_file, m_buffer, m_buffer_size);
235  // Indicates error or EOF
236  if (bytes_read <= 0)
237  {
238  // Reset get area
239  this->setg (m_buffer, m_buffer, m_buffer);
240  return traits_type::eof ();
241  }
242 
243  // Make all bytes read from file available as get area
244  this->setg (m_buffer, m_buffer, m_buffer + bytes_read);
245 
246  // If next character in get area differs from putback character
247  // flag a failure
248  gzfilebuf::int_type ret = traits_type::to_int_type (*(this->gptr ()));
249  if (ret != c)
250  return traits_type::eof ();
251  else
252  return ret;
253  }
254  else
255  return traits_type::eof ();
256 }
257 
258 // Fill get area from gzipped file
259 gzfilebuf::int_type
261 {
262  // If something is left in the get area by chance, return it
263  // (this shouldn't normally happen, as underflow is only supposed
264  // to be called when gptr >= egptr, but it serves as error check)
265  if (this->gptr () && (this->gptr () < this->egptr ()))
266  return traits_type::to_int_type (*(this->gptr ()));
267 
268  // If the file hasn't been opened for reading, produce error
269  if (! this->is_open () || ! (m_io_mode & std::ios_base::in))
270  return traits_type::eof ();
271 
272  // Copy the final characters to the front of the buffer
273  int stash = 0;
274  if (this->eback () && m_buffer && m_buffer_size > STASHED_CHARACTERS)
275  {
276  char_type *ptr1 = m_buffer;
277  char_type *ptr2 = this->egptr () - STASHED_CHARACTERS + 1;
278  if (ptr2 > this->eback ())
279  while (stash++ <= STASHED_CHARACTERS)
280  *ptr1++ = *ptr2++;
281  }
282 
283  // Attempt to fill internal buffer from gzipped file
284  // (buffer must be guaranteed to exist...)
285  int bytes_read = gzread (m_file, m_buffer + stash, m_buffer_size - stash);
286 
287  // Indicates error or EOF
288  if (bytes_read <= 0)
289  {
290  // Reset get area
291  this->setg (m_buffer, m_buffer, m_buffer);
292  return traits_type::eof ();
293  }
294  // Make all bytes read from file plus the stash available as get area
295  this->setg (m_buffer, m_buffer + stash, m_buffer + bytes_read + stash);
296 
297  // Return next character in get area
298  return traits_type::to_int_type (*(this->gptr ()));
299 }
300 
301 // Write put area to gzipped file
302 gzfilebuf::int_type
304 {
305  // Determine whether put area is in use
306  if (this->pbase ())
307  {
308  // Double-check pointer range
309  if (this->pptr () > this->epptr () || this->pptr () < this->pbase ())
310  return traits_type::eof ();
311  // Add extra character to buffer if not EOF
312  if (! traits_type::eq_int_type (c, traits_type::eof ()))
313  {
314  *(this->pptr ()) = traits_type::to_char_type (c);
315  this->pbump (1);
316  }
317  // Number of characters to write to file
318  int bytes_to_write = this->pptr () - this->pbase ();
319  // Overflow doesn't fail if nothing is to be written
320  if (bytes_to_write > 0)
321  {
322  // If the file hasn't been opened for writing, produce error
323  if (! this->is_open () || ! (m_io_mode & std::ios_base::out))
324  return traits_type::eof ();
325  // If gzipped file won't accept all bytes written to it, fail
326  if (gzwrite (m_file, this->pbase (), bytes_to_write)
327  != bytes_to_write)
328  return traits_type::eof ();
329  // Reset next pointer to point to pbase on success
330  this->pbump (-bytes_to_write);
331  }
332  }
333  // Write extra character to file if not EOF
334  else if (! traits_type::eq_int_type (c, traits_type::eof ()))
335  {
336  // If the file hasn't been opened for writing, produce error
337  if (! this->is_open () || ! (m_io_mode & std::ios_base::out))
338  return traits_type::eof ();
339  // Impromptu char buffer (allows "unbuffered" output)
340  char_type last_char = traits_type::to_char_type (c);
341  // If gzipped file won't accept this character, fail
342  if (gzwrite (m_file, &last_char, 1) != 1)
343  return traits_type::eof ();
344  }
345 
346  // If you got here, you have succeeded (even if c was EOF)
347  // The return value should therefore be non-EOF
348  if (traits_type::eq_int_type (c, traits_type::eof ()))
349  return traits_type::not_eof (c);
350  else
351  return c;
352 }
353 
354 // Assign new buffer
355 std::streambuf *
356 gzfilebuf::setbuf (char_type *p, std::streamsize n)
357 {
358  // First make sure stuff is sync'ed, for safety
359  if (this->sync () == -1)
360  return nullptr;
361  // If buffering is turned off on purpose via setbuf(0,0), still allocate one.
362  // "Unbuffered" only really refers to put [27.8.1.4.10], while get needs at
363  // least a buffer of size 1 (very inefficient though, therefore make it
364  // bigger?). This follows from [27.5.2.4.3]/12 (gptr needs to point at
365  // something, it seems).
366  if (! p || ! n)
367  {
368  // Replace existing buffer (if any) with small internal buffer
369  this->disable_buffer ();
370  m_buffer = nullptr;
371  m_buffer_size = 0;
372  m_own_buffer = true;
373  this->enable_buffer ();
374  }
375  else
376  {
377  // Replace existing buffer (if any) with external buffer
378  this->disable_buffer ();
379  m_buffer = p;
380  m_buffer_size = n;
381  m_own_buffer = false;
382  this->enable_buffer ();
383  }
384  return this;
385 }
386 
387 // Write put area to gzipped file (i.e., ensures that put area is empty)
388 int
390 {
391  return traits_type::eq_int_type (this->overflow (),
392  traits_type::eof ()) ? -1 : 0;
393 }
394 
395 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
396 
397 // Allocate internal buffer
398 void
399 gzfilebuf::enable_buffer ()
400 {
401  // If internal buffer required, allocate one
402  if (m_own_buffer && ! m_buffer)
403  {
404  // Check for buffered vs. "unbuffered"
405  if (m_buffer_size > 0)
406  {
407  // Allocate internal buffer
408  m_buffer = new char_type [m_buffer_size];
409  // Get area starts empty and will be expanded by underflow as needed
410  this->setg (m_buffer, m_buffer, m_buffer);
411  // Setup entire internal buffer as put area.
412  // The one-past-end pointer actually points to the last element of
413  // the buffer, so that overflow(c) can safely add the extra character
414  // c to the sequence. These pointers remain in place for the
415  // duration of the buffer
416  this->setp (m_buffer, m_buffer + m_buffer_size - 1);
417  }
418  else
419  {
420  // Even in "unbuffered" case, (small?) get buffer is still required
421  m_buffer_size = SMALLBUFSIZE;
422  m_buffer = new char_type [m_buffer_size];
423  this->setg (m_buffer, m_buffer, m_buffer);
424  // "Unbuffered" means no put buffer
425  this->setp (nullptr, nullptr);
426  }
427  }
428  else
429  {
430  // If buffer already allocated, reset buffer pointers just to make sure no
431  // stale chars are lying around
432  this->setg (m_buffer, m_buffer, m_buffer);
433  this->setp (m_buffer, m_buffer + m_buffer_size - 1);
434  }
435 }
436 
437 // Destroy internal buffer
438 void
439 gzfilebuf::disable_buffer ()
440 {
441  // If internal buffer exists, deallocate it
442  if (m_own_buffer && m_buffer)
443  {
444  // Preserve unbuffered status by zeroing size
445  if (! this->pbase ())
446  m_buffer_size = 0;
447  delete [] m_buffer;
448  m_buffer = nullptr;
449  this->setg (nullptr, nullptr, nullptr);
450  this->setp (nullptr, nullptr);
451  }
452  else
453  {
454  // Reset buffer pointers to initial state if external buffer exists
455  this->setg (m_buffer, m_buffer, m_buffer);
456  if (m_buffer)
457  this->setp (m_buffer, m_buffer + m_buffer_size - 1);
458  else
459  this->setp (nullptr, nullptr);
460  }
461 }
462 
463 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
464 
465 // Seek functions
466 gzfilebuf::pos_type
467 gzfilebuf::seekoff (off_type off, std::ios_base::seekdir way,
468  std::ios_base::openmode)
469 {
470  pos_type ret = pos_type (off_type (-1));
471 
472  if (this->is_open ())
473  {
474  off_type computed_off = off;
475 
476  if ((m_io_mode & std::ios_base::in) && way == std::ios_base::cur)
477  computed_off += this->gptr () - this->egptr ();
478 
479  // Handle tellg/tellp as a special case up front, no need to seek
480  // or invalidate get/put buffers
481  if (off == 0 && way == std::ios_base::cur)
482  return pos_type (gztell (m_file) + computed_off);
483 
484  if (way == std::ios_base::beg)
485  ret = pos_type (gzseek (m_file, computed_off, SEEK_SET));
486  else if (way == std::ios_base::cur)
487  ret = pos_type (gzseek (m_file, computed_off, SEEK_CUR));
488  else
489  // Can't seek from end of a gzipped file, so this will give -1
490  ret = pos_type (gzseek (m_file, computed_off, SEEK_END));
491 
492  if (m_io_mode & std::ios_base::in)
493  // Invalidates contents of the buffer
494  enable_buffer ();
495  else
496  // flush contents of buffer to file
497  overflow ();
498  }
499 
500  return ret;
501 }
502 
503 gzfilebuf::pos_type
504 gzfilebuf::seekpos (pos_type sp, std::ios_base::openmode)
505 {
506  pos_type ret = pos_type (off_type (-1));
507 
508  if (this->is_open ())
509  {
510  ret = pos_type (gzseek (m_file, sp, SEEK_SET));
511 
512  if (m_io_mode & std::ios_base::in)
513  // Invalidates contents of the buffer
514  enable_buffer ();
515  else
516  // flush contents of buffer to file
517  overflow ();
518  }
519 
520  return ret;
521 }
522 
523 // Default constructor initializes stream buffer
525  : std::istream (nullptr), m_sb ()
526 { this->init (&m_sb); }
527 
528 // Initialize stream buffer and open file
529 gzifstream::gzifstream (const char *name, std::ios_base::openmode mode)
530  : std::istream (nullptr), m_sb ()
531 {
532  this->init (&m_sb);
533  this->open (name, mode);
534 }
535 
536 // Initialize stream buffer and attach to file
537 gzifstream::gzifstream (int fd, std::ios_base::openmode mode)
538  : std::istream (nullptr), m_sb ()
539 {
540  this->init (&m_sb);
541  this->attach (fd, mode);
542 }
543 
544 // Open file and go into fail() state if unsuccessful
545 void
546 gzifstream::open (const char *name, std::ios_base::openmode mode)
547 {
548  if (! m_sb.open (name, mode | std::ios_base::in))
549  this->setstate (std::ios_base::failbit);
550  else
551  this->clear ();
552 }
553 
554 // Attach to file and go into fail() state if unsuccessful
555 void
556 gzifstream::attach (int fd, std::ios_base::openmode mode)
557 {
558  if (! m_sb.attach (fd, mode | std::ios_base::in))
559  this->setstate (std::ios_base::failbit);
560  else
561  this->clear ();
562 }
563 
564 // Close file
565 void
567 {
568  if (! m_sb.close ())
569  this->setstate (std::ios_base::failbit);
570 }
571 
572 // Default constructor initializes stream buffer
574  : std::ostream (nullptr), m_sb ()
575 { this->init (&m_sb); }
576 
577 // Initialize stream buffer and open file
578 gzofstream::gzofstream (const char *name, std::ios_base::openmode mode)
579  : std::ostream (nullptr), m_sb ()
580 {
581  this->init (&m_sb);
582  this->open (name, mode);
583 }
584 
585 // Initialize stream buffer and attach to file
586 gzofstream::gzofstream (int fd, std::ios_base::openmode mode)
587  : std::ostream (nullptr), m_sb ()
588 {
589  this->init (&m_sb);
590  this->attach (fd, mode);
591 }
592 
593 // Open file and go into fail() state if unsuccessful
594 void
595 gzofstream::open (const char *name, std::ios_base::openmode mode)
596 {
597  if (! m_sb.open (name, mode | std::ios_base::out))
598  this->setstate (std::ios_base::failbit);
599  else
600  this->clear ();
601 }
602 
603 // Attach to file and go into fail() state if unsuccessful
604 void
605 gzofstream::attach (int fd, std::ios_base::openmode mode)
606 {
607  if (! m_sb.attach (fd, mode | std::ios_base::out))
608  this->setstate (std::ios_base::failbit);
609  else
610  this->clear ();
611 }
612 
613 // Close file
614 void
616 {
617  if (! m_sb.close ())
618  this->setstate (std::ios_base::failbit);
619 }
620 
621 #endif
#define SEEK_SET
#define SEEK_CUR
#define SEEK_END
Gzipped file stream buffer class.
Definition: gzfstream.h:56
gzfilebuf * close()
Close gzipped file.
Definition: gzfstream.cc:142
bool is_open() const
Check if file is open.
Definition: gzfstream.h:86
virtual int_type underflow()
Fill get area from gzipped file.
Definition: gzfstream.cc:260
virtual int_type overflow(int_type c=traits_type::eof())
Write put area to gzipped file.
Definition: gzfstream.cc:303
virtual ~gzfilebuf()
Definition: gzfstream.cc:68
virtual pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode mode=std::ios_base::in|std::ios_base::out)
Alters the stream positions.
Definition: gzfstream.cc:467
virtual int_type pbackfail(int_type c=traits_type::eof())
Definition: gzfstream.cc:222
virtual std::streambuf * setbuf(char_type *p, std::streamsize n)
Installs external stream buffer.
Definition: gzfstream.cc:356
gzfilebuf * open(const char *name, std::ios_base::openmode mode)
Open gzipped file.
Definition: gzfstream.cc:88
virtual pos_type seekpos(pos_type sp, std::ios_base::openmode mode=std::ios_base::in|std::ios_base::out)
Alters the stream positions.
Definition: gzfstream.cc:504
bool open_mode(std::ios_base::openmode mode, char *c_mode) const
Convert ios open mode int to mode string used by zlib.
Definition: gzfstream.cc:166
int setcompression(int comp_level, int comp_strategy=Z_DEFAULT_STRATEGY)
Set compression level and strategy on the fly.
Definition: gzfstream.cc:81
virtual int sync()
Flush stream buffer to file.
Definition: gzfstream.cc:389
gzfilebuf * attach(int fd, std::ios_base::openmode mode)
Attach to already open gzipped file.
Definition: gzfstream.cc:115
virtual std::streamsize showmanyc()
Number of characters available in stream buffer.
Definition: gzfstream.cc:205
void close()
Close gzipped file.
Definition: gzfstream.cc:566
void attach(int fd, std::ios_base::openmode mode=std::ios_base::in)
Attach to already open gzipped file.
Definition: gzfstream.cc:556
void open(const char *name, std::ios_base::openmode mode=std::ios_base::in)
Open gzipped file.
Definition: gzfstream.cc:546
void close()
Close gzipped file.
Definition: gzfstream.cc:615
void attach(int fd, std::ios_base::openmode mode=std::ios_base::out)
Attach to already open gzipped file.
Definition: gzfstream.cc:605
void open(const char *name, std::ios_base::openmode mode=std::ios_base::out)
Open gzipped file.
Definition: gzfstream.cc:595
#define SMALLBUFSIZE
Definition: gzfstream.cc:56
#define STASHED_CHARACTERS
Definition: gzfstream.cc:54
#define BIGBUFSIZE
Definition: gzfstream.cc:55
std::complex< T > trunc(const std::complex< T > &x)
Definition: lo-mappers.h:111
octave_idx_type n
Definition: mx-inlines.cc:761
T::size_type strlen(const typename T::value_type *str)
Definition: oct-string.cc:88