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-2025, 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
943 tomas.vondra@postgre 55 :CBC 202 : DeflateCompressorInit(CompressorState *cs)
56 : : {
57 : : GzipCompressorState *gzipcs;
58 : : z_streamp zp;
59 : :
60 : 202 : gzipcs = (GzipCompressorState *) pg_malloc0(sizeof(GzipCompressorState));
61 : 202 : zp = gzipcs->zp = (z_streamp) pg_malloc(sizeof(z_stream));
62 : 202 : zp->zalloc = Z_NULL;
63 : 202 : zp->zfree = Z_NULL;
64 : 202 : 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 : 202 : gzipcs->outsize = DEFAULT_IO_BUFFER_SIZE;
72 : 202 : gzipcs->outbuf = pg_malloc(gzipcs->outsize + 1);
73 : :
74 : : /* -Z 0 uses the "None" compressor -- not zlib with no compression */
75 [ - + ]: 202 : Assert(cs->compression_spec.level != 0);
76 : :
77 [ - + ]: 202 : if (deflateInit(zp, cs->compression_spec.level) != Z_OK)
943 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 */
943 tomas.vondra@postgre 81 :CBC 202 : zp->next_out = gzipcs->outbuf;
82 : 202 : zp->avail_out = gzipcs->outsize;
83 : :
84 : : /* Keep track of gzipcs */
85 : 202 : cs->private_data = gzipcs;
86 : 202 : }
87 : :
88 : : static void
89 : 202 : DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs)
90 : : {
91 : 202 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
92 : : z_streamp zp;
93 : :
94 : 202 : zp = gzipcs->zp;
95 : 202 : zp->next_in = NULL;
96 : 202 : zp->avail_in = 0;
97 : :
98 : : /* Flush any remaining data from zlib buffer */
99 : 202 : DeflateCompressorCommon(AH, cs, true);
100 : :
101 [ - + ]: 202 : if (deflateEnd(zp) != Z_OK)
943 tomas.vondra@postgre 102 :UBC 0 : pg_fatal("could not close compression stream: %s", zp->msg);
103 : :
943 tomas.vondra@postgre 104 :CBC 202 : pg_free(gzipcs->outbuf);
105 : 202 : pg_free(gzipcs->zp);
106 : 202 : pg_free(gzipcs);
107 : 202 : cs->private_data = NULL;
108 : 202 : }
109 : :
110 : : static void
111 : 818 : DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, bool flush)
112 : : {
977 113 : 818 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
114 : 818 : z_streamp zp = gzipcs->zp;
115 : 818 : void *out = gzipcs->outbuf;
116 : 818 : int res = Z_OK;
117 : :
118 [ + + + + ]: 1434 : while (gzipcs->zp->avail_in != 0 || flush)
119 : : {
120 [ + + ]: 818 : res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH);
121 [ - + ]: 818 : if (res == Z_STREAM_ERROR)
977 tomas.vondra@postgre 122 :UBC 0 : pg_fatal("could not compress data: %s", zp->msg);
977 tomas.vondra@postgre 123 [ + + - + ]:CBC 818 : if ((flush && (zp->avail_out < gzipcs->outsize))
124 [ + - ]: 616 : || (zp->avail_out == 0)
125 [ - + ]: 616 : || (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 [ + - ]: 202 : 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 : 202 : size_t len = gzipcs->outsize - zp->avail_out;
140 : :
249 peter@eisentraut.org 141 : 202 : cs->writeF(AH, out, len);
142 : : }
977 tomas.vondra@postgre 143 : 202 : zp->next_out = out;
144 : 202 : zp->avail_out = gzipcs->outsize;
145 : : }
146 : :
147 [ + + ]: 818 : if (res == Z_STREAM_END)
148 : 202 : break;
149 : : }
150 : 818 : }
151 : :
152 : : static void
153 : 385 : EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs)
154 : : {
155 : : /* If deflation was initialized, finalize it */
943 156 [ + + ]: 385 : if (cs->private_data)
157 : 202 : DeflateCompressorEnd(AH, cs);
977 158 : 385 : }
159 : :
160 : : static void
161 : 616 : WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs,
162 : : const void *data, size_t dLen)
163 : : {
164 : 616 : GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
165 : :
459 peter@eisentraut.org 166 : 616 : gzipcs->zp->next_in = data;
977 tomas.vondra@postgre 167 : 616 : gzipcs->zp->avail_in = dLen;
943 168 : 616 : DeflateCompressorCommon(AH, cs, false);
977 169 : 616 : }
170 : :
171 : : static void
172 : 183 : ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs)
173 : : {
174 : : z_streamp zp;
175 : : char *out;
176 : 183 : int res = Z_OK;
177 : : size_t cnt;
178 : : char *buf;
179 : : size_t buflen;
180 : :
181 : 183 : zp = (z_streamp) pg_malloc(sizeof(z_stream));
182 : 183 : zp->zalloc = Z_NULL;
183 : 183 : zp->zfree = Z_NULL;
184 : 183 : zp->opaque = Z_NULL;
185 : :
949 186 : 183 : buflen = DEFAULT_IO_BUFFER_SIZE;
187 : 183 : buf = pg_malloc(buflen);
188 : :
189 : 183 : out = pg_malloc(DEFAULT_IO_BUFFER_SIZE + 1);
190 : :
977 191 [ - + ]: 183 : if (inflateInit(zp) != Z_OK)
977 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 */
977 tomas.vondra@postgre 196 [ + + ]:CBC 366 : while ((cnt = cs->readF(AH, &buf, &buflen)))
197 : : {
198 : 183 : zp->next_in = (void *) buf;
199 : 183 : zp->avail_in = cnt;
200 : :
201 [ + + ]: 368 : while (zp->avail_in > 0)
202 : : {
203 : 185 : zp->next_out = (void *) out;
949 204 : 185 : zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
205 : :
977 206 : 185 : res = inflate(zp, 0);
207 [ + + - + ]: 185 : if (res != Z_OK && res != Z_STREAM_END)
977 tomas.vondra@postgre 208 :UBC 0 : pg_fatal("could not uncompress data: %s", zp->msg);
209 : :
949 tomas.vondra@postgre 210 :CBC 185 : out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
211 : 185 : ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
212 : : }
213 : : }
214 : :
977 215 : 183 : zp->next_in = NULL;
216 : 183 : zp->avail_in = 0;
217 [ - + ]: 183 : while (res != Z_STREAM_END)
218 : : {
977 tomas.vondra@postgre 219 :UBC 0 : zp->next_out = (void *) out;
949 220 : 0 : zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
977 221 : 0 : res = inflate(zp, 0);
222 [ # # # # ]: 0 : if (res != Z_OK && res != Z_STREAM_END)
223 : 0 : pg_fatal("could not uncompress data: %s", zp->msg);
224 : :
949 225 : 0 : out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
226 : 0 : ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
227 : : }
228 : :
977 tomas.vondra@postgre 229 [ - + ]:CBC 183 : if (inflateEnd(zp) != Z_OK)
977 tomas.vondra@postgre 230 :UBC 0 : pg_fatal("could not close compression library: %s", zp->msg);
231 : :
977 tomas.vondra@postgre 232 :CBC 183 : free(buf);
233 : 183 : free(out);
234 : 183 : free(zp);
235 : 183 : }
236 : :
237 : : /* Public routines that support gzip compressed data I/O */
238 : : void
239 : 385 : InitCompressorGzip(CompressorState *cs,
240 : : const pg_compress_specification compression_spec)
241 : : {
242 : 385 : cs->readData = ReadDataFromArchiveGzip;
243 : 385 : cs->writeData = WriteDataToArchiveGzip;
244 : 385 : cs->end = EndCompressorGzip;
245 : :
246 : 385 : cs->compression_spec = compression_spec;
247 : :
248 : : /*
249 : : * If the caller has defined a write function, prepare the necessary
250 : : * state. Note that if the data is empty, End may be called immediately
251 : : * after Init, without ever calling Write.
252 : : */
943 253 [ + + ]: 385 : if (cs->writeF)
254 : 202 : DeflateCompressorInit(cs);
977 255 : 385 : }
256 : :
257 : :
258 : : /*----------------------
259 : : * Compress File API
260 : : *----------------------
261 : : */
262 : :
263 : : static size_t
59 dgustafsson@postgres 264 : 369 : Gzip_read(void *ptr, size_t size, CompressFileHandle *CFH)
265 : : {
977 tomas.vondra@postgre 266 : 369 : gzFile gzfp = (gzFile) CFH->private_data;
267 : : int gzret;
268 : :
269 : : /* Reading zero bytes must be a no-op */
14 tgl@sss.pgh.pa.us 270 [ + + ]: 369 : if (size == 0)
14 tgl@sss.pgh.pa.us 271 :GBC 16 : return 0;
272 : :
949 tomas.vondra@postgre 273 :CBC 353 : gzret = gzread(gzfp, ptr, size);
274 : :
275 : : /*
276 : : * gzread returns zero on EOF as well as some error conditions, and less
277 : : * than zero on other error conditions, so we need to inspect for EOF on
278 : : * zero.
279 : : */
59 dgustafsson@postgres 280 [ + + ]: 353 : if (gzret <= 0)
281 : : {
282 : : int errnum;
283 : : const char *errmsg;
284 : :
285 [ + - + - ]: 105 : if (gzret == 0 && gzeof(gzfp))
286 : 105 : return 0;
287 : :
59 dgustafsson@postgres 288 :UBC 0 : errmsg = gzerror(gzfp, &errnum);
289 : :
977 tomas.vondra@postgre 290 [ # # ]: 0 : pg_fatal("could not read from input file: %s",
291 : : errnum == Z_ERRNO ? strerror(errno) : errmsg);
292 : : }
293 : :
59 dgustafsson@postgres 294 :CBC 248 : return (size_t) gzret;
295 : : }
296 : :
297 : : static void
977 tomas.vondra@postgre 298 : 24451 : Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH)
299 : : {
300 : 24451 : gzFile gzfp = (gzFile) CFH->private_data;
301 : : int errnum;
302 : : const char *errmsg;
303 : :
59 dgustafsson@postgres 304 [ - + ]: 24451 : if (gzwrite(gzfp, ptr, size) != size)
305 : : {
59 dgustafsson@postgres 306 :UBC 0 : errmsg = gzerror(gzfp, &errnum);
307 [ # # ]: 0 : pg_fatal("could not write to file: %s",
308 : : errnum == Z_ERRNO ? strerror(errno) : errmsg);
309 : : }
977 tomas.vondra@postgre 310 :CBC 24451 : }
311 : :
312 : : static int
977 tomas.vondra@postgre 313 :GBC 1572 : Gzip_getc(CompressFileHandle *CFH)
314 : : {
315 : 1572 : gzFile gzfp = (gzFile) CFH->private_data;
316 : : int ret;
317 : :
318 : 1572 : errno = 0;
319 : 1572 : ret = gzgetc(gzfp);
320 [ - + ]: 1572 : if (ret == EOF)
321 : : {
977 tomas.vondra@postgre 322 [ # # ]:UBC 0 : if (!gzeof(gzfp))
594 michael@paquier.xyz 323 : 0 : pg_fatal("could not read from input file: %m");
324 : : else
977 tomas.vondra@postgre 325 : 0 : pg_fatal("could not read from input file: end of file");
326 : : }
327 : :
977 tomas.vondra@postgre 328 :GBC 1572 : return ret;
329 : : }
330 : :
331 : : static char *
977 tomas.vondra@postgre 332 :CBC 2 : Gzip_gets(char *ptr, int size, CompressFileHandle *CFH)
333 : : {
334 : 2 : gzFile gzfp = (gzFile) CFH->private_data;
335 : :
336 : 2 : return gzgets(gzfp, ptr, size);
337 : : }
338 : :
339 : : static bool
340 : 212 : Gzip_close(CompressFileHandle *CFH)
341 : : {
342 : 212 : gzFile gzfp = (gzFile) CFH->private_data;
343 : :
344 : 212 : CFH->private_data = NULL;
345 : :
949 346 : 212 : return gzclose(gzfp) == Z_OK;
347 : : }
348 : :
349 : : static bool
977 350 : 1 : Gzip_eof(CompressFileHandle *CFH)
351 : : {
352 : 1 : gzFile gzfp = (gzFile) CFH->private_data;
353 : :
949 354 : 1 : return gzeof(gzfp) == 1;
355 : : }
356 : :
357 : : static const char *
977 tomas.vondra@postgre 358 :UBC 0 : Gzip_get_error(CompressFileHandle *CFH)
359 : : {
360 : 0 : gzFile gzfp = (gzFile) CFH->private_data;
361 : : const char *errmsg;
362 : : int errnum;
363 : :
364 : 0 : errmsg = gzerror(gzfp, &errnum);
365 [ # # ]: 0 : if (errnum == Z_ERRNO)
366 : 0 : errmsg = strerror(errno);
367 : :
368 : 0 : return errmsg;
369 : : }
370 : :
371 : : static bool
977 tomas.vondra@postgre 372 :CBC 212 : Gzip_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH)
373 : : {
374 : : gzFile gzfp;
375 : : char mode_compression[32];
376 : :
377 [ + + ]: 212 : if (CFH->compression_spec.level != Z_DEFAULT_COMPRESSION)
378 : : {
379 : : /*
380 : : * user has specified a compression level, so tell zlib to use it
381 : : */
382 : 111 : snprintf(mode_compression, sizeof(mode_compression), "%s%d",
383 : : mode, CFH->compression_spec.level);
384 : : }
385 : : else
386 : 101 : strcpy(mode_compression, mode);
387 : :
388 [ - + ]: 212 : if (fd >= 0)
977 tomas.vondra@postgre 389 :UBC 0 : gzfp = gzdopen(dup(fd), mode_compression);
390 : : else
977 tomas.vondra@postgre 391 :CBC 212 : gzfp = gzopen(path, mode_compression);
392 : :
393 [ - + ]: 212 : if (gzfp == NULL)
949 tomas.vondra@postgre 394 :UBC 0 : return false;
395 : :
977 tomas.vondra@postgre 396 :CBC 212 : CFH->private_data = gzfp;
397 : :
949 398 : 212 : return true;
399 : : }
400 : :
401 : : static bool
977 402 : 104 : Gzip_open_write(const char *path, const char *mode, CompressFileHandle *CFH)
403 : : {
404 : : char *fname;
405 : : bool ret;
406 : : int save_errno;
407 : :
408 : 104 : fname = psprintf("%s.gz", path);
409 : 104 : ret = CFH->open_func(fname, -1, mode, CFH);
410 : :
411 : 104 : save_errno = errno;
412 : 104 : pg_free(fname);
413 : 104 : errno = save_errno;
414 : :
415 : 104 : return ret;
416 : : }
417 : :
418 : : void
419 : 212 : InitCompressFileHandleGzip(CompressFileHandle *CFH,
420 : : const pg_compress_specification compression_spec)
421 : : {
422 : 212 : CFH->open_func = Gzip_open;
423 : 212 : CFH->open_write_func = Gzip_open_write;
424 : 212 : CFH->read_func = Gzip_read;
425 : 212 : CFH->write_func = Gzip_write;
426 : 212 : CFH->gets_func = Gzip_gets;
427 : 212 : CFH->getc_func = Gzip_getc;
428 : 212 : CFH->close_func = Gzip_close;
429 : 212 : CFH->eof_func = Gzip_eof;
430 : 212 : CFH->get_error_func = Gzip_get_error;
431 : :
432 : 212 : CFH->compression_spec = compression_spec;
433 : :
434 : 212 : CFH->private_data = NULL;
435 : 212 : }
436 : : #else /* HAVE_LIBZ */
437 : : void
438 : : InitCompressorGzip(CompressorState *cs,
439 : : const pg_compress_specification compression_spec)
440 : : {
441 : : pg_fatal("this build does not support compression with %s", "gzip");
442 : : }
443 : :
444 : : void
445 : : InitCompressFileHandleGzip(CompressFileHandle *CFH,
446 : : const pg_compress_specification compression_spec)
447 : : {
448 : : pg_fatal("this build does not support compression with %s", "gzip");
449 : : }
450 : : #endif /* HAVE_LIBZ */
|