/*------------------------------------------------------------------------- * * compress_gzip.c * Routines for archivers to read or write a gzip compressed data stream. * * Portions Copyright (c) 1996-2024, 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 #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: %m"); 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 */