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