| /*------------------------------------------------------------------------- |
| * |
| * compress_gzip.c |
| * Routines for archivers to read or write a gzip compressed data stream. |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * src/bin/pg_dump/compress_gzip.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres_fe.h" |
| #include <unistd.h> |
| |
| #include "compress_gzip.h" |
| #include "pg_backup_utils.h" |
| |
| #ifdef HAVE_LIBZ |
| #include "zlib.h" |
| |
| /*---------------------- |
| * Compressor API |
| *---------------------- |
| */ |
| typedef struct GzipCompressorState |
| { |
| z_streamp zp; |
| |
| void *outbuf; |
| size_t outsize; |
| } GzipCompressorState; |
| |
| /* Private routines that support gzip compressed data I/O */ |
| static void DeflateCompressorInit(CompressorState *cs); |
| static void DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs); |
| static void DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, |
| bool flush); |
| static void EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs); |
| static void WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs, |
| const void *data, size_t dLen); |
| static void ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs); |
| |
| static void |
| DeflateCompressorInit(CompressorState *cs) |
| { |
| GzipCompressorState *gzipcs; |
| z_streamp zp; |
| |
| gzipcs = (GzipCompressorState *) pg_malloc0(sizeof(GzipCompressorState)); |
| zp = gzipcs->zp = (z_streamp) pg_malloc(sizeof(z_stream)); |
| zp->zalloc = Z_NULL; |
| zp->zfree = Z_NULL; |
| zp->opaque = Z_NULL; |
| |
| /* |
| * outsize is the buffer size we tell zlib it can output to. We actually |
| * allocate one extra byte because some routines want to append a trailing |
| * zero byte to the zlib output. |
| */ |
| gzipcs->outsize = DEFAULT_IO_BUFFER_SIZE; |
| gzipcs->outbuf = pg_malloc(gzipcs->outsize + 1); |
| |
| /* -Z 0 uses the "None" compressor -- not zlib with no compression */ |
| Assert(cs->compression_spec.level != 0); |
| |
| if (deflateInit(zp, cs->compression_spec.level) != Z_OK) |
| pg_fatal("could not initialize compression library: %s", zp->msg); |
| |
| /* Just be paranoid - maybe End is called after Start, with no Write */ |
| zp->next_out = gzipcs->outbuf; |
| zp->avail_out = gzipcs->outsize; |
| |
| /* Keep track of gzipcs */ |
| cs->private_data = gzipcs; |
| } |
| |
| static void |
| DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs) |
| { |
| GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; |
| z_streamp zp; |
| |
| zp = gzipcs->zp; |
| zp->next_in = NULL; |
| zp->avail_in = 0; |
| |
| /* Flush any remaining data from zlib buffer */ |
| DeflateCompressorCommon(AH, cs, true); |
| |
| if (deflateEnd(zp) != Z_OK) |
| pg_fatal("could not close compression stream: %s", zp->msg); |
| |
| pg_free(gzipcs->outbuf); |
| pg_free(gzipcs->zp); |
| pg_free(gzipcs); |
| cs->private_data = NULL; |
| } |
| |
| static void |
| DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, bool flush) |
| { |
| GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; |
| z_streamp zp = gzipcs->zp; |
| void *out = gzipcs->outbuf; |
| int res = Z_OK; |
| |
| while (gzipcs->zp->avail_in != 0 || flush) |
| { |
| res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH); |
| if (res == Z_STREAM_ERROR) |
| pg_fatal("could not compress data: %s", zp->msg); |
| if ((flush && (zp->avail_out < gzipcs->outsize)) |
| || (zp->avail_out == 0) |
| || (zp->avail_in != 0) |
| ) |
| { |
| /* |
| * Extra paranoia: avoid zero-length chunks, since a zero length |
| * chunk is the EOF marker in the custom format. This should never |
| * happen but ... |
| */ |
| if (zp->avail_out < gzipcs->outsize) |
| { |
| /* |
| * Any write function should do its own error checking but to |
| * make sure we do a check here as well ... |
| */ |
| size_t len = gzipcs->outsize - zp->avail_out; |
| |
| cs->writeF(AH, (char *) out, len); |
| } |
| zp->next_out = out; |
| zp->avail_out = gzipcs->outsize; |
| } |
| |
| if (res == Z_STREAM_END) |
| break; |
| } |
| } |
| |
| static void |
| EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs) |
| { |
| /* If deflation was initialized, finalize it */ |
| if (cs->private_data) |
| DeflateCompressorEnd(AH, cs); |
| } |
| |
| static void |
| WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs, |
| const void *data, size_t dLen) |
| { |
| GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; |
| |
| gzipcs->zp->next_in = (void *) unconstify(void *, data); |
| gzipcs->zp->avail_in = dLen; |
| DeflateCompressorCommon(AH, cs, false); |
| } |
| |
| static void |
| ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs) |
| { |
| z_streamp zp; |
| char *out; |
| int res = Z_OK; |
| size_t cnt; |
| char *buf; |
| size_t buflen; |
| |
| zp = (z_streamp) pg_malloc(sizeof(z_stream)); |
| zp->zalloc = Z_NULL; |
| zp->zfree = Z_NULL; |
| zp->opaque = Z_NULL; |
| |
| buflen = DEFAULT_IO_BUFFER_SIZE; |
| buf = pg_malloc(buflen); |
| |
| out = pg_malloc(DEFAULT_IO_BUFFER_SIZE + 1); |
| |
| if (inflateInit(zp) != Z_OK) |
| pg_fatal("could not initialize compression library: %s", |
| zp->msg); |
| |
| /* no minimal chunk size for zlib */ |
| while ((cnt = cs->readF(AH, &buf, &buflen))) |
| { |
| zp->next_in = (void *) buf; |
| zp->avail_in = cnt; |
| |
| while (zp->avail_in > 0) |
| { |
| zp->next_out = (void *) out; |
| zp->avail_out = DEFAULT_IO_BUFFER_SIZE; |
| |
| res = inflate(zp, 0); |
| if (res != Z_OK && res != Z_STREAM_END) |
| pg_fatal("could not uncompress data: %s", zp->msg); |
| |
| out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0'; |
| ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH); |
| } |
| } |
| |
| zp->next_in = NULL; |
| zp->avail_in = 0; |
| while (res != Z_STREAM_END) |
| { |
| zp->next_out = (void *) out; |
| zp->avail_out = DEFAULT_IO_BUFFER_SIZE; |
| res = inflate(zp, 0); |
| if (res != Z_OK && res != Z_STREAM_END) |
| pg_fatal("could not uncompress data: %s", zp->msg); |
| |
| out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0'; |
| ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH); |
| } |
| |
| if (inflateEnd(zp) != Z_OK) |
| pg_fatal("could not close compression library: %s", zp->msg); |
| |
| free(buf); |
| free(out); |
| free(zp); |
| } |
| |
| /* Public routines that support gzip compressed data I/O */ |
| void |
| InitCompressorGzip(CompressorState *cs, |
| const pg_compress_specification compression_spec) |
| { |
| cs->readData = ReadDataFromArchiveGzip; |
| cs->writeData = WriteDataToArchiveGzip; |
| cs->end = EndCompressorGzip; |
| |
| cs->compression_spec = compression_spec; |
| |
| /* |
| * If the caller has defined a write function, prepare the necessary |
| * state. Note that if the data is empty, End may be called immediately |
| * after Init, without ever calling Write. |
| */ |
| if (cs->writeF) |
| DeflateCompressorInit(cs); |
| } |
| |
| |
| /*---------------------- |
| * Compress File API |
| *---------------------- |
| */ |
| |
| static bool |
| Gzip_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| int gzret; |
| |
| gzret = gzread(gzfp, ptr, size); |
| if (gzret <= 0 && !gzeof(gzfp)) |
| { |
| int errnum; |
| const char *errmsg = gzerror(gzfp, &errnum); |
| |
| pg_fatal("could not read from input file: %s", |
| errnum == Z_ERRNO ? strerror(errno) : errmsg); |
| } |
| |
| if (rsize) |
| *rsize = (size_t) gzret; |
| |
| return true; |
| } |
| |
| static bool |
| Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| |
| return gzwrite(gzfp, ptr, size) > 0; |
| } |
| |
| static int |
| Gzip_getc(CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| int ret; |
| |
| errno = 0; |
| ret = gzgetc(gzfp); |
| if (ret == EOF) |
| { |
| if (!gzeof(gzfp)) |
| pg_fatal("could not read from input file: %s", strerror(errno)); |
| else |
| pg_fatal("could not read from input file: end of file"); |
| } |
| |
| return ret; |
| } |
| |
| static char * |
| Gzip_gets(char *ptr, int size, CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| |
| return gzgets(gzfp, ptr, size); |
| } |
| |
| static bool |
| Gzip_close(CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| |
| CFH->private_data = NULL; |
| |
| return gzclose(gzfp) == Z_OK; |
| } |
| |
| static bool |
| Gzip_eof(CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| |
| return gzeof(gzfp) == 1; |
| } |
| |
| static const char * |
| Gzip_get_error(CompressFileHandle *CFH) |
| { |
| gzFile gzfp = (gzFile) CFH->private_data; |
| const char *errmsg; |
| int errnum; |
| |
| errmsg = gzerror(gzfp, &errnum); |
| if (errnum == Z_ERRNO) |
| errmsg = strerror(errno); |
| |
| return errmsg; |
| } |
| |
| static bool |
| Gzip_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH) |
| { |
| gzFile gzfp; |
| char mode_compression[32]; |
| |
| if (CFH->compression_spec.level != Z_DEFAULT_COMPRESSION) |
| { |
| /* |
| * user has specified a compression level, so tell zlib to use it |
| */ |
| snprintf(mode_compression, sizeof(mode_compression), "%s%d", |
| mode, CFH->compression_spec.level); |
| } |
| else |
| strcpy(mode_compression, mode); |
| |
| if (fd >= 0) |
| gzfp = gzdopen(dup(fd), mode_compression); |
| else |
| gzfp = gzopen(path, mode_compression); |
| |
| if (gzfp == NULL) |
| return false; |
| |
| CFH->private_data = gzfp; |
| |
| return true; |
| } |
| |
| static bool |
| Gzip_open_write(const char *path, const char *mode, CompressFileHandle *CFH) |
| { |
| char *fname; |
| bool ret; |
| int save_errno; |
| |
| fname = psprintf("%s.gz", path); |
| ret = CFH->open_func(fname, -1, mode, CFH); |
| |
| save_errno = errno; |
| pg_free(fname); |
| errno = save_errno; |
| |
| return ret; |
| } |
| |
| void |
| InitCompressFileHandleGzip(CompressFileHandle *CFH, |
| const pg_compress_specification compression_spec) |
| { |
| CFH->open_func = Gzip_open; |
| CFH->open_write_func = Gzip_open_write; |
| CFH->read_func = Gzip_read; |
| CFH->write_func = Gzip_write; |
| CFH->gets_func = Gzip_gets; |
| CFH->getc_func = Gzip_getc; |
| CFH->close_func = Gzip_close; |
| CFH->eof_func = Gzip_eof; |
| CFH->get_error_func = Gzip_get_error; |
| |
| CFH->compression_spec = compression_spec; |
| |
| CFH->private_data = NULL; |
| } |
| #else /* HAVE_LIBZ */ |
| void |
| InitCompressorGzip(CompressorState *cs, |
| const pg_compress_specification compression_spec) |
| { |
| pg_fatal("this build does not support compression with %s", "gzip"); |
| } |
| |
| void |
| InitCompressFileHandleGzip(CompressFileHandle *CFH, |
| const pg_compress_specification compression_spec) |
| { |
| pg_fatal("this build does not support compression with %s", "gzip"); |
| } |
| #endif /* HAVE_LIBZ */ |