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