Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * compress_gzip.c
4 : * Routines for archivers to read or write a gzip compressed data stream.
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : * IDENTIFICATION
10 : * src/bin/pg_dump/compress_gzip.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres_fe.h"
15 : #include <unistd.h>
16 :
17 : #include "compress_gzip.h"
18 : #include "pg_backup_utils.h"
19 :
20 : #ifdef HAVE_LIBZ
21 : #include "zlib.h"
22 :
23 : /*----------------------
24 : * Compressor API
25 : *----------------------
26 : */
27 : typedef struct GzipCompressorState
28 : {
29 : z_streamp zp;
30 :
31 : void *outbuf;
32 : size_t outsize;
33 : } GzipCompressorState;
34 :
35 : /* Private routines that support gzip compressed data I/O */
36 : static void DeflateCompressorInit(CompressorState *cs);
37 : static void DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs);
38 : static void DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs,
39 : bool flush);
40 : static void EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs);
41 : static void WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs,
42 : const void *data, size_t dLen);
43 : static void ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs);
44 :
45 : static void
11 tomas.vondra 46 GNC 55 : DeflateCompressorInit(CompressorState *cs)
47 : {
48 : GzipCompressorState *gzipcs;
49 : z_streamp zp;
50 :
51 55 : gzipcs = (GzipCompressorState *) pg_malloc0(sizeof(GzipCompressorState));
52 55 : zp = gzipcs->zp = (z_streamp) pg_malloc(sizeof(z_stream));
53 55 : zp->zalloc = Z_NULL;
54 55 : zp->zfree = Z_NULL;
55 55 : zp->opaque = Z_NULL;
56 :
57 : /*
58 : * outsize is the buffer size we tell zlib it can output to. We actually
59 : * allocate one extra byte because some routines want to append a trailing
60 : * zero byte to the zlib output.
61 : */
62 55 : gzipcs->outsize = DEFAULT_IO_BUFFER_SIZE;
63 55 : gzipcs->outbuf = pg_malloc(gzipcs->outsize + 1);
64 :
65 : /* -Z 0 uses the "None" compressor -- not zlib with no compression */
66 55 : Assert(cs->compression_spec.level != 0);
67 :
68 55 : if (deflateInit(zp, cs->compression_spec.level) != Z_OK)
11 tomas.vondra 69 UNC 0 : pg_fatal("could not initialize compression library: %s", zp->msg);
70 :
71 : /* Just be paranoid - maybe End is called after Start, with no Write */
11 tomas.vondra 72 GNC 55 : zp->next_out = gzipcs->outbuf;
73 55 : zp->avail_out = gzipcs->outsize;
74 :
75 : /* Keep track of gzipcs */
76 55 : cs->private_data = gzipcs;
77 55 : }
78 :
79 : static void
80 55 : DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs)
81 : {
82 55 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
83 : z_streamp zp;
84 :
85 55 : zp = gzipcs->zp;
86 55 : zp->next_in = NULL;
87 55 : zp->avail_in = 0;
88 :
89 : /* Flush any remaining data from zlib buffer */
90 55 : DeflateCompressorCommon(AH, cs, true);
91 :
92 55 : if (deflateEnd(zp) != Z_OK)
11 tomas.vondra 93 UNC 0 : pg_fatal("could not close compression stream: %s", zp->msg);
94 :
11 tomas.vondra 95 GNC 55 : pg_free(gzipcs->outbuf);
96 55 : pg_free(gzipcs->zp);
97 55 : pg_free(gzipcs);
98 55 : cs->private_data = NULL;
99 55 : }
100 :
101 : static void
102 166 : DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, bool flush)
103 : {
45 104 166 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
105 166 : z_streamp zp = gzipcs->zp;
106 166 : void *out = gzipcs->outbuf;
107 166 : int res = Z_OK;
108 :
109 277 : while (gzipcs->zp->avail_in != 0 || flush)
110 : {
111 166 : res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH);
112 166 : if (res == Z_STREAM_ERROR)
45 tomas.vondra 113 UNC 0 : pg_fatal("could not compress data: %s", zp->msg);
45 tomas.vondra 114 GNC 166 : if ((flush && (zp->avail_out < gzipcs->outsize))
115 111 : || (zp->avail_out == 0)
116 111 : || (zp->avail_in != 0)
117 : )
118 : {
119 : /*
120 : * Extra paranoia: avoid zero-length chunks, since a zero length
121 : * chunk is the EOF marker in the custom format. This should never
122 : * happen but ...
123 : */
124 55 : if (zp->avail_out < gzipcs->outsize)
125 : {
126 : /*
127 : * Any write function should do its own error checking but to
128 : * make sure we do a check here as well ...
129 : */
130 55 : size_t len = gzipcs->outsize - zp->avail_out;
131 :
132 55 : cs->writeF(AH, (char *) out, len);
133 : }
134 55 : zp->next_out = out;
135 55 : zp->avail_out = gzipcs->outsize;
136 : }
137 :
138 166 : if (res == Z_STREAM_END)
139 55 : break;
140 : }
141 166 : }
142 :
143 : static void
144 110 : EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs)
145 : {
146 : /* If deflation was initialized, finalize it */
11 147 110 : if (cs->private_data)
148 55 : DeflateCompressorEnd(AH, cs);
45 149 110 : }
150 :
151 : static void
152 111 : WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs,
153 : const void *data, size_t dLen)
154 : {
155 111 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
156 :
157 111 : gzipcs->zp->next_in = (void *) unconstify(void *, data);
158 111 : gzipcs->zp->avail_in = dLen;
11 159 111 : DeflateCompressorCommon(AH, cs, false);
45 160 111 : }
161 :
162 : static void
163 55 : ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs)
164 : {
165 : z_streamp zp;
166 : char *out;
167 55 : int res = Z_OK;
168 : size_t cnt;
169 : char *buf;
170 : size_t buflen;
171 :
172 55 : zp = (z_streamp) pg_malloc(sizeof(z_stream));
173 55 : zp->zalloc = Z_NULL;
174 55 : zp->zfree = Z_NULL;
175 55 : zp->opaque = Z_NULL;
176 :
17 177 55 : buflen = DEFAULT_IO_BUFFER_SIZE;
178 55 : buf = pg_malloc(buflen);
179 :
180 55 : out = pg_malloc(DEFAULT_IO_BUFFER_SIZE + 1);
181 :
45 182 55 : if (inflateInit(zp) != Z_OK)
45 tomas.vondra 183 UNC 0 : pg_fatal("could not initialize compression library: %s",
184 : zp->msg);
185 :
186 : /* no minimal chunk size for zlib */
45 tomas.vondra 187 GNC 110 : while ((cnt = cs->readF(AH, &buf, &buflen)))
188 : {
189 55 : zp->next_in = (void *) buf;
190 55 : zp->avail_in = cnt;
191 :
192 110 : while (zp->avail_in > 0)
193 : {
194 55 : zp->next_out = (void *) out;
17 195 55 : zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
196 :
45 197 55 : res = inflate(zp, 0);
198 55 : if (res != Z_OK && res != Z_STREAM_END)
45 tomas.vondra 199 UNC 0 : pg_fatal("could not uncompress data: %s", zp->msg);
200 :
17 tomas.vondra 201 GNC 55 : out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
202 55 : ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
203 : }
204 : }
205 :
45 206 55 : zp->next_in = NULL;
207 55 : zp->avail_in = 0;
208 55 : while (res != Z_STREAM_END)
209 : {
45 tomas.vondra 210 UNC 0 : zp->next_out = (void *) out;
17 211 0 : zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
45 212 0 : res = inflate(zp, 0);
213 0 : if (res != Z_OK && res != Z_STREAM_END)
214 0 : pg_fatal("could not uncompress data: %s", zp->msg);
215 :
17 216 0 : out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
217 0 : ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
218 : }
219 :
45 tomas.vondra 220 GNC 55 : if (inflateEnd(zp) != Z_OK)
45 tomas.vondra 221 UNC 0 : pg_fatal("could not close compression library: %s", zp->msg);
222 :
45 tomas.vondra 223 GNC 55 : free(buf);
224 55 : free(out);
225 55 : free(zp);
226 55 : }
227 :
228 : /* Public routines that support gzip compressed data I/O */
229 : void
230 110 : InitCompressorGzip(CompressorState *cs,
231 : const pg_compress_specification compression_spec)
232 : {
233 110 : cs->readData = ReadDataFromArchiveGzip;
234 110 : cs->writeData = WriteDataToArchiveGzip;
235 110 : cs->end = EndCompressorGzip;
236 :
237 110 : cs->compression_spec = compression_spec;
238 :
239 : /*
240 : * If the caller has defined a write function, prepare the necessary
241 : * state. Note that if the data is empty, End may be called immediately
242 : * after Init, without ever calling Write.
243 : */
11 244 110 : if (cs->writeF)
245 55 : DeflateCompressorInit(cs);
45 246 110 : }
247 :
248 :
249 : /*----------------------
250 : * Compress File API
251 : *----------------------
252 : */
253 :
254 : static bool
17 255 236 : Gzip_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH)
256 : {
45 257 236 : gzFile gzfp = (gzFile) CFH->private_data;
258 : int gzret;
259 :
17 260 236 : gzret = gzread(gzfp, ptr, size);
261 236 : if (gzret <= 0 && !gzeof(gzfp))
262 : {
263 : int errnum;
45 tomas.vondra 264 UNC 0 : const char *errmsg = gzerror(gzfp, &errnum);
265 :
266 0 : pg_fatal("could not read from input file: %s",
267 : errnum == Z_ERRNO ? strerror(errno) : errmsg);
268 : }
269 :
17 tomas.vondra 270 GNC 236 : if (rsize)
271 236 : *rsize = (size_t) gzret;
272 :
273 236 : return true;
274 : }
275 :
276 : static bool
45 277 25632 : Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH)
278 : {
279 25632 : gzFile gzfp = (gzFile) CFH->private_data;
280 :
17 281 25632 : return gzwrite(gzfp, ptr, size) > 0;
282 : }
283 :
284 : static int
45 tomas.vondra 285 UNC 0 : Gzip_getc(CompressFileHandle *CFH)
286 : {
287 0 : gzFile gzfp = (gzFile) CFH->private_data;
288 : int ret;
289 :
290 0 : errno = 0;
291 0 : ret = gzgetc(gzfp);
292 0 : if (ret == EOF)
293 : {
294 0 : if (!gzeof(gzfp))
295 0 : pg_fatal("could not read from input file: %s", strerror(errno));
296 : else
297 0 : pg_fatal("could not read from input file: end of file");
298 : }
299 :
300 0 : return ret;
301 : }
302 :
303 : static char *
45 tomas.vondra 304 GNC 3 : Gzip_gets(char *ptr, int size, CompressFileHandle *CFH)
305 : {
306 3 : gzFile gzfp = (gzFile) CFH->private_data;
307 :
308 3 : return gzgets(gzfp, ptr, size);
309 : }
310 :
311 : static bool
312 209 : Gzip_close(CompressFileHandle *CFH)
313 : {
314 209 : gzFile gzfp = (gzFile) CFH->private_data;
315 :
316 209 : CFH->private_data = NULL;
317 :
17 318 209 : return gzclose(gzfp) == Z_OK;
319 : }
320 :
321 : static bool
45 322 1 : Gzip_eof(CompressFileHandle *CFH)
323 : {
324 1 : gzFile gzfp = (gzFile) CFH->private_data;
325 :
17 326 1 : return gzeof(gzfp) == 1;
327 : }
328 :
329 : static const char *
45 tomas.vondra 330 UNC 0 : Gzip_get_error(CompressFileHandle *CFH)
331 : {
332 0 : gzFile gzfp = (gzFile) CFH->private_data;
333 : const char *errmsg;
334 : int errnum;
335 :
336 0 : errmsg = gzerror(gzfp, &errnum);
337 0 : if (errnum == Z_ERRNO)
338 0 : errmsg = strerror(errno);
339 :
340 0 : return errmsg;
341 : }
342 :
343 : static bool
45 tomas.vondra 344 GNC 209 : Gzip_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH)
345 : {
346 : gzFile gzfp;
347 : char mode_compression[32];
348 :
349 209 : if (CFH->compression_spec.level != Z_DEFAULT_COMPRESSION)
350 : {
351 : /*
352 : * user has specified a compression level, so tell zlib to use it
353 : */
354 132 : snprintf(mode_compression, sizeof(mode_compression), "%s%d",
355 : mode, CFH->compression_spec.level);
356 : }
357 : else
358 77 : strcpy(mode_compression, mode);
359 :
360 209 : if (fd >= 0)
45 tomas.vondra 361 UNC 0 : gzfp = gzdopen(dup(fd), mode_compression);
362 : else
45 tomas.vondra 363 GNC 209 : gzfp = gzopen(path, mode_compression);
364 :
365 209 : if (gzfp == NULL)
17 tomas.vondra 366 UNC 0 : return false;
367 :
45 tomas.vondra 368 GNC 209 : CFH->private_data = gzfp;
369 :
17 370 209 : return true;
371 : }
372 :
373 : static bool
45 374 103 : Gzip_open_write(const char *path, const char *mode, CompressFileHandle *CFH)
375 : {
376 : char *fname;
377 : bool ret;
378 : int save_errno;
379 :
380 103 : fname = psprintf("%s.gz", path);
381 103 : ret = CFH->open_func(fname, -1, mode, CFH);
382 :
383 103 : save_errno = errno;
384 103 : pg_free(fname);
385 103 : errno = save_errno;
386 :
387 103 : return ret;
388 : }
389 :
390 : void
391 209 : InitCompressFileHandleGzip(CompressFileHandle *CFH,
392 : const pg_compress_specification compression_spec)
393 : {
394 209 : CFH->open_func = Gzip_open;
395 209 : CFH->open_write_func = Gzip_open_write;
396 209 : CFH->read_func = Gzip_read;
397 209 : CFH->write_func = Gzip_write;
398 209 : CFH->gets_func = Gzip_gets;
399 209 : CFH->getc_func = Gzip_getc;
400 209 : CFH->close_func = Gzip_close;
401 209 : CFH->eof_func = Gzip_eof;
402 209 : CFH->get_error_func = Gzip_get_error;
403 :
404 209 : CFH->compression_spec = compression_spec;
405 :
406 209 : CFH->private_data = NULL;
407 209 : }
408 : #else /* HAVE_LIBZ */
409 : void
410 : InitCompressorGzip(CompressorState *cs,
411 : const pg_compress_specification compression_spec)
412 : {
413 : pg_fatal("this build does not support compression with %s", "gzip");
414 : }
415 :
416 : void
417 : InitCompressFileHandleGzip(CompressFileHandle *CFH,
418 : const pg_compress_specification compression_spec)
419 : {
420 : pg_fatal("this build does not support compression with %s", "gzip");
421 : }
422 : #endif /* HAVE_LIBZ */
|