Age Owner Branch data 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-2026, 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 : : * We don't use the gzgetc() macro, because zlib's configuration logic is not
25 : : * robust enough to guarantee that the macro will have the same ideas about
26 : : * struct field layout as the library itself does; see for example
27 : : * https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59711
28 : : * Instead, #undef the macro and fall back to the underlying function.
29 : : */
30 : : #undef gzgetc
31 : :
32 : : /*----------------------
33 : : * Compressor API
34 : : *----------------------
35 : : */
36 : : typedef struct GzipCompressorState
37 : : {
38 : : z_streamp zp;
39 : :
40 : : void *outbuf;
41 : : size_t outsize;
42 : : } GzipCompressorState;
43 : :
44 : : /* Private routines that support gzip compressed data I/O */
45 : : static void DeflateCompressorInit(CompressorState *cs);
46 : : static void DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs);
47 : : static void DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs,
48 : : bool flush);
49 : : static void EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs);
50 : : static void WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs,
51 : : const void *data, size_t dLen);
52 : : static void ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs);
53 : :
54 : : static void
1293 tomas.vondra@postgre 55 :CBC 269 : DeflateCompressorInit(CompressorState *cs)
56 : : {
57 : : GzipCompressorState *gzipcs;
58 : : z_streamp zp;
59 : :
241 michael@paquier.xyz 60 : 269 : gzipcs = pg_malloc0_object(GzipCompressorState);
61 : 269 : zp = gzipcs->zp = pg_malloc_object(z_stream);
1293 tomas.vondra@postgre 62 : 269 : zp->zalloc = Z_NULL;
63 : 269 : zp->zfree = Z_NULL;
64 : 269 : zp->opaque = Z_NULL;
65 : :
66 : : /*
67 : : * outsize is the buffer size we tell zlib it can output to. We actually
68 : : * allocate one extra byte because some routines want to append a trailing
69 : : * zero byte to the zlib output.
70 : : */
71 : 269 : gzipcs->outsize = DEFAULT_IO_BUFFER_SIZE;
72 : 269 : gzipcs->outbuf = pg_malloc(gzipcs->outsize + 1);
73 : :
74 : : /* -Z 0 uses the "None" compressor -- not zlib with no compression */
75 [ - + ]: 269 : Assert(cs->compression_spec.level != 0);
76 : :
77 [ - + ]: 269 : if (deflateInit(zp, cs->compression_spec.level) != Z_OK)
1293 tomas.vondra@postgre 78 :UBC 0 : pg_fatal("could not initialize compression library: %s", zp->msg);
79 : :
80 : : /* Just be paranoid - maybe End is called after Start, with no Write */
1293 tomas.vondra@postgre 81 :CBC 269 : zp->next_out = gzipcs->outbuf;
82 : 269 : zp->avail_out = gzipcs->outsize;
83 : :
84 : : /* Keep track of gzipcs */
85 : 269 : cs->private_data = gzipcs;
86 : 269 : }
87 : :
88 : : static void
89 : 269 : DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs)
90 : : {
91 : 269 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
92 : : z_streamp zp;
93 : :
94 : 269 : zp = gzipcs->zp;
95 : 269 : zp->next_in = NULL;
96 : 269 : zp->avail_in = 0;
97 : :
98 : : /* Flush any remaining data from zlib buffer */
99 : 269 : DeflateCompressorCommon(AH, cs, true);
100 : :
101 [ - + ]: 269 : if (deflateEnd(zp) != Z_OK)
1293 tomas.vondra@postgre 102 :UBC 0 : pg_fatal("could not close compression stream: %s", zp->msg);
103 : :
1293 tomas.vondra@postgre 104 :CBC 269 : pg_free(gzipcs->outbuf);
105 : 269 : pg_free(gzipcs->zp);
106 : 269 : pg_free(gzipcs);
107 : 269 : cs->private_data = NULL;
108 : 269 : }
109 : :
110 : : static void
111 : 1695 : DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, bool flush)
112 : : {
1327 113 : 1695 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
114 : 1695 : z_streamp zp = gzipcs->zp;
115 : 1695 : void *out = gzipcs->outbuf;
116 : 1695 : int res = Z_OK;
117 : :
118 [ + + + + ]: 3121 : while (gzipcs->zp->avail_in != 0 || flush)
119 : : {
120 [ + + ]: 1695 : res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH);
121 [ - + ]: 1695 : if (res == Z_STREAM_ERROR)
1327 tomas.vondra@postgre 122 :UBC 0 : pg_fatal("could not compress data: %s", zp->msg);
1327 tomas.vondra@postgre 123 [ + + - + ]:CBC 1695 : if ((flush && (zp->avail_out < gzipcs->outsize))
124 [ + - ]: 1426 : || (zp->avail_out == 0)
125 [ - + ]: 1426 : || (zp->avail_in != 0)
126 : : )
127 : : {
128 : : /*
129 : : * Extra paranoia: avoid zero-length chunks, since a zero length
130 : : * chunk is the EOF marker in the custom format. This should never
131 : : * happen but ...
132 : : */
133 [ + - ]: 269 : if (zp->avail_out < gzipcs->outsize)
134 : : {
135 : : /*
136 : : * Any write function should do its own error checking but to
137 : : * make sure we do a check here as well ...
138 : : */
139 : 269 : size_t len = gzipcs->outsize - zp->avail_out;
140 : :
599 peter@eisentraut.org 141 : 269 : cs->writeF(AH, out, len);
142 : : }
1327 tomas.vondra@postgre 143 : 269 : zp->next_out = out;
144 : 269 : zp->avail_out = gzipcs->outsize;
145 : : }
146 : :
147 [ + + ]: 1695 : if (res == Z_STREAM_END)
148 : 269 : break;
149 : : }
150 : 1695 : }
151 : :
152 : : static void
153 : 439 : EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs)
154 : : {
155 : : /* If deflation was initialized, finalize it */
1293 156 [ + + ]: 439 : if (cs->private_data)
157 : 269 : DeflateCompressorEnd(AH, cs);
1327 158 : 439 : }
159 : :
160 : : static void
161 : 1426 : WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs,
162 : : const void *data, size_t dLen)
163 : : {
164 : 1426 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
165 : :
809 peter@eisentraut.org 166 : 1426 : gzipcs->zp->next_in = data;
1327 tomas.vondra@postgre 167 : 1426 : gzipcs->zp->avail_in = dLen;
1293 168 : 1426 : DeflateCompressorCommon(AH, cs, false);
1327 169 : 1426 : }
170 : :
171 : : static void
172 : 170 : ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs)
173 : : {
174 : : z_streamp zp;
175 : : char *out;
176 : 170 : int res = Z_OK;
177 : : size_t cnt;
178 : : char *buf;
179 : : size_t buflen;
180 : :
241 michael@paquier.xyz 181 : 170 : zp = pg_malloc_object(z_stream);
1327 tomas.vondra@postgre 182 : 170 : zp->zalloc = Z_NULL;
183 : 170 : zp->zfree = Z_NULL;
184 : 170 : zp->opaque = Z_NULL;
185 : :
1299 186 : 170 : buflen = DEFAULT_IO_BUFFER_SIZE;
187 : 170 : buf = pg_malloc(buflen);
188 : :
189 : 170 : out = pg_malloc(DEFAULT_IO_BUFFER_SIZE + 1);
190 : :
1327 191 [ - + ]: 170 : if (inflateInit(zp) != Z_OK)
1327 tomas.vondra@postgre 192 :UBC 0 : pg_fatal("could not initialize compression library: %s",
193 : : zp->msg);
194 : :
195 : : /* no minimal chunk size for zlib */
1327 tomas.vondra@postgre 196 [ + + ]:CBC 340 : while ((cnt = cs->readF(AH, &buf, &buflen)))
197 : : {
198 : 170 : zp->next_in = (void *) buf;
199 : 170 : zp->avail_in = cnt;
200 : :
201 [ + + ]: 342 : while (zp->avail_in > 0)
202 : : {
203 : 172 : zp->next_out = (void *) out;
1299 204 : 172 : zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
205 : :
1327 206 : 172 : res = inflate(zp, 0);
207 [ + + - + ]: 172 : if (res != Z_OK && res != Z_STREAM_END)
3 dgustafsson@postgres 208 [ # # ]:UBC 0 : pg_fatal("could not uncompress data: %s",
209 : : zp->msg ? zp->msg : "unknown error");
210 : :
1299 tomas.vondra@postgre 211 :CBC 172 : out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
212 : 172 : ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
213 : : }
214 : : }
215 : :
1327 216 : 170 : zp->next_in = NULL;
217 : 170 : zp->avail_in = 0;
218 [ - + ]: 170 : while (res != Z_STREAM_END)
219 : : {
1327 tomas.vondra@postgre 220 :UBC 0 : zp->next_out = (void *) out;
1299 221 : 0 : zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
1327 222 : 0 : res = inflate(zp, 0);
223 [ # # # # ]: 0 : if (res != Z_OK && res != Z_STREAM_END)
3 dgustafsson@postgres 224 [ # # ]: 0 : pg_fatal("could not uncompress data: %s",
225 : : zp->msg ? zp->msg : "unknown error");
226 : :
1299 tomas.vondra@postgre 227 : 0 : out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
228 : 0 : ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
229 : : }
230 : :
1327 tomas.vondra@postgre 231 [ - + ]:CBC 170 : if (inflateEnd(zp) != Z_OK)
1327 tomas.vondra@postgre 232 :UBC 0 : pg_fatal("could not close compression library: %s", zp->msg);
233 : :
103 peter@eisentraut.org 234 :GNC 170 : pg_free(buf);
235 : 170 : pg_free(out);
236 : 170 : pg_free(zp);
1327 tomas.vondra@postgre 237 :CBC 170 : }
238 : :
239 : : /* Public routines that support gzip compressed data I/O */
240 : : void
241 : 439 : InitCompressorGzip(CompressorState *cs,
242 : : const pg_compress_specification compression_spec)
243 : : {
244 : 439 : cs->readData = ReadDataFromArchiveGzip;
245 : 439 : cs->writeData = WriteDataToArchiveGzip;
246 : 439 : cs->end = EndCompressorGzip;
247 : :
248 : 439 : cs->compression_spec = compression_spec;
249 : :
250 : : /*
251 : : * If the caller has defined a write function, prepare the necessary
252 : : * state. Note that if the data is empty, End may be called immediately
253 : : * after Init, without ever calling Write.
254 : : */
1293 255 [ + + ]: 439 : if (cs->writeF)
256 : 269 : DeflateCompressorInit(cs);
1327 257 : 439 : }
258 : :
259 : :
260 : : /*----------------------
261 : : * Compress File API
262 : : *----------------------
263 : : */
264 : :
265 : : static size_t
409 dgustafsson@postgres 266 : 385 : Gzip_read(void *ptr, size_t size, CompressFileHandle *CFH)
267 : : {
1327 tomas.vondra@postgre 268 : 385 : gzFile gzfp = (gzFile) CFH->private_data;
269 : : int gzret;
270 : :
271 : : /* Reading zero bytes must be a no-op */
364 tgl@sss.pgh.pa.us 272 [ + + ]: 385 : if (size == 0)
273 : 16 : return 0;
274 : :
1299 tomas.vondra@postgre 275 : 369 : gzret = gzread(gzfp, ptr, size);
276 : :
277 : : /*
278 : : * gzread returns zero on EOF as well as some error conditions, and less
279 : : * than zero on other error conditions, so we need to inspect for EOF on
280 : : * zero.
281 : : */
409 dgustafsson@postgres 282 [ + + ]: 369 : if (gzret <= 0)
283 : : {
284 : : int errnum;
285 : : const char *errmsg;
286 : :
287 [ + - + - ]: 113 : if (gzret == 0 && gzeof(gzfp))
288 : 113 : return 0;
289 : :
409 dgustafsson@postgres 290 :UBC 0 : errmsg = gzerror(gzfp, &errnum);
291 : :
1327 tomas.vondra@postgre 292 [ # # ]: 0 : pg_fatal("could not read from input file: %s",
293 : : errnum == Z_ERRNO ? strerror(errno) : errmsg);
294 : : }
295 : :
409 dgustafsson@postgres 296 :CBC 256 : return (size_t) gzret;
297 : : }
298 : :
299 : : static void
1327 tomas.vondra@postgre 300 : 24659 : Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH)
301 : : {
302 : 24659 : gzFile gzfp = (gzFile) CFH->private_data;
303 : : int errnum;
304 : : const char *errmsg;
305 : :
409 dgustafsson@postgres 306 [ - + ]: 24659 : if (gzwrite(gzfp, ptr, size) != size)
307 : : {
409 dgustafsson@postgres 308 :UBC 0 : errmsg = gzerror(gzfp, &errnum);
309 [ # # ]: 0 : pg_fatal("could not write to file: %s",
310 : : errnum == Z_ERRNO ? strerror(errno) : errmsg);
311 : : }
1327 tomas.vondra@postgre 312 :CBC 24659 : }
313 : :
314 : : static int
315 : 1572 : Gzip_getc(CompressFileHandle *CFH)
316 : : {
317 : 1572 : gzFile gzfp = (gzFile) CFH->private_data;
318 : : int ret;
319 : :
320 : 1572 : errno = 0;
321 : 1572 : ret = gzgetc(gzfp);
322 [ - + ]: 1572 : if (ret == EOF)
323 : : {
1327 tomas.vondra@postgre 324 [ # # ]:UBC 0 : if (!gzeof(gzfp))
944 michael@paquier.xyz 325 : 0 : pg_fatal("could not read from input file: %m");
326 : : else
1327 tomas.vondra@postgre 327 : 0 : pg_fatal("could not read from input file: end of file");
328 : : }
329 : :
1327 tomas.vondra@postgre 330 :CBC 1572 : return ret;
331 : : }
332 : :
333 : : static char *
334 : 2 : Gzip_gets(char *ptr, int size, CompressFileHandle *CFH)
335 : : {
336 : 2 : gzFile gzfp = (gzFile) CFH->private_data;
337 : :
338 : 2 : return gzgets(gzfp, ptr, size);
339 : : }
340 : :
341 : : static bool
342 : 228 : Gzip_close(CompressFileHandle *CFH)
343 : : {
344 : 228 : gzFile gzfp = (gzFile) CFH->private_data;
345 : :
346 : 228 : CFH->private_data = NULL;
347 : :
1299 348 : 228 : return gzclose(gzfp) == Z_OK;
349 : : }
350 : :
351 : : static bool
1327 352 : 1 : Gzip_eof(CompressFileHandle *CFH)
353 : : {
354 : 1 : gzFile gzfp = (gzFile) CFH->private_data;
355 : :
1299 356 : 1 : return gzeof(gzfp) == 1;
357 : : }
358 : :
359 : : static const char *
1327 tomas.vondra@postgre 360 :UBC 0 : Gzip_get_error(CompressFileHandle *CFH)
361 : : {
362 : 0 : gzFile gzfp = (gzFile) CFH->private_data;
363 : : const char *errmsg;
364 : : int errnum;
365 : :
366 : 0 : errmsg = gzerror(gzfp, &errnum);
367 [ # # ]: 0 : if (errnum == Z_ERRNO)
368 : 0 : errmsg = strerror(errno);
369 : :
370 : 0 : return errmsg;
371 : : }
372 : :
373 : : static bool
1327 tomas.vondra@postgre 374 :CBC 228 : Gzip_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH)
375 : : {
376 : : gzFile gzfp;
377 : : char mode_compression[32];
378 : :
379 [ + + ]: 228 : if (CFH->compression_spec.level != Z_DEFAULT_COMPRESSION)
380 : : {
381 : : /*
382 : : * user has specified a compression level, so tell zlib to use it
383 : : */
384 : 119 : snprintf(mode_compression, sizeof(mode_compression), "%s%d",
385 : : mode, CFH->compression_spec.level);
386 : : }
387 : : else
388 : 109 : strcpy(mode_compression, mode);
389 : :
390 [ - + ]: 228 : if (fd >= 0)
391 : : {
207 tgl@sss.pgh.pa.us 392 :UBC 0 : int dup_fd = dup(fd);
393 : :
394 [ # # ]: 0 : if (dup_fd < 0)
395 : 0 : return false;
396 : 0 : gzfp = gzdopen(dup_fd, mode_compression);
397 [ # # ]: 0 : if (gzfp == NULL)
398 : : {
399 : 0 : close(dup_fd);
400 : 0 : return false;
401 : : }
402 : : }
403 : : else
404 : : {
1327 tomas.vondra@postgre 405 :CBC 228 : gzfp = gzopen(path, mode_compression);
207 tgl@sss.pgh.pa.us 406 [ - + ]: 228 : if (gzfp == NULL)
207 tgl@sss.pgh.pa.us 407 :UBC 0 : return false;
408 : : }
409 : :
1327 tomas.vondra@postgre 410 :CBC 228 : CFH->private_data = gzfp;
411 : :
1299 412 : 228 : return true;
413 : : }
414 : :
415 : : static bool
1327 416 : 112 : Gzip_open_write(const char *path, const char *mode, CompressFileHandle *CFH)
417 : : {
418 : : char *fname;
419 : : bool ret;
420 : : int save_errno;
421 : :
422 : 112 : fname = psprintf("%s.gz", path);
423 : 112 : ret = CFH->open_func(fname, -1, mode, CFH);
424 : :
425 : 112 : save_errno = errno;
103 peter@eisentraut.org 426 :GNC 112 : pfree(fname);
1327 tomas.vondra@postgre 427 :CBC 112 : errno = save_errno;
428 : :
429 : 112 : return ret;
430 : : }
431 : :
432 : : void
433 : 228 : InitCompressFileHandleGzip(CompressFileHandle *CFH,
434 : : const pg_compress_specification compression_spec)
435 : : {
436 : 228 : CFH->open_func = Gzip_open;
437 : 228 : CFH->open_write_func = Gzip_open_write;
438 : 228 : CFH->read_func = Gzip_read;
439 : 228 : CFH->write_func = Gzip_write;
440 : 228 : CFH->gets_func = Gzip_gets;
441 : 228 : CFH->getc_func = Gzip_getc;
442 : 228 : CFH->close_func = Gzip_close;
443 : 228 : CFH->eof_func = Gzip_eof;
444 : 228 : CFH->get_error_func = Gzip_get_error;
445 : :
446 : 228 : CFH->compression_spec = compression_spec;
447 : :
448 : 228 : CFH->private_data = NULL;
449 : 228 : }
450 : : #else /* HAVE_LIBZ */
451 : : void
452 : : InitCompressorGzip(CompressorState *cs,
453 : : const pg_compress_specification compression_spec)
454 : : {
455 : : pg_fatal("this build does not support compression with %s", "gzip");
456 : : }
457 : :
458 : : void
459 : : InitCompressFileHandleGzip(CompressFileHandle *CFH,
460 : : const pg_compress_specification compression_spec)
461 : : {
462 : : pg_fatal("this build does not support compression with %s", "gzip");
463 : : }
464 : : #endif /* HAVE_LIBZ */
|