|           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
      55         404 : DeflateCompressorInit(CompressorState *cs)
      56             : {
      57             :     GzipCompressorState *gzipcs;
      58             :     z_streamp   zp;
      59             : 
      60         404 :     gzipcs = (GzipCompressorState *) pg_malloc0(sizeof(GzipCompressorState));
      61         404 :     zp = gzipcs->zp = (z_streamp) pg_malloc(sizeof(z_stream));
      62         404 :     zp->zalloc = Z_NULL;
      63         404 :     zp->zfree = Z_NULL;
      64         404 :     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         404 :     gzipcs->outsize = DEFAULT_IO_BUFFER_SIZE;
      72         404 :     gzipcs->outbuf = pg_malloc(gzipcs->outsize + 1);
      73             : 
      74             :     /* -Z 0 uses the "None" compressor -- not zlib with no compression */
      75             :     Assert(cs->compression_spec.level != 0);
      76             : 
      77         404 :     if (deflateInit(zp, cs->compression_spec.level) != Z_OK)
      78           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 */
      81         404 :     zp->next_out = gzipcs->outbuf;
      82         404 :     zp->avail_out = gzipcs->outsize;
      83             : 
      84             :     /* Keep track of gzipcs */
      85         404 :     cs->private_data = gzipcs;
      86         404 : }
      87             : 
      88             : static void
      89         404 : DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs)
      90             : {
      91         404 :     GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
      92             :     z_streamp   zp;
      93             : 
      94         404 :     zp = gzipcs->zp;
      95         404 :     zp->next_in = NULL;
      96         404 :     zp->avail_in = 0;
      97             : 
      98             :     /* Flush any remaining data from zlib buffer */
      99         404 :     DeflateCompressorCommon(AH, cs, true);
     100             : 
     101         404 :     if (deflateEnd(zp) != Z_OK)
     102           0 :         pg_fatal("could not close compression stream: %s", zp->msg);
     103             : 
     104         404 :     pg_free(gzipcs->outbuf);
     105         404 :     pg_free(gzipcs->zp);
     106         404 :     pg_free(gzipcs);
     107         404 :     cs->private_data = NULL;
     108         404 : }
     109             : 
     110             : static void
     111        1636 : DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, bool flush)
     112             : {
     113        1636 :     GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
     114        1636 :     z_streamp   zp = gzipcs->zp;
     115        1636 :     void       *out = gzipcs->outbuf;
     116        1636 :     int         res = Z_OK;
     117             : 
     118        2868 :     while (gzipcs->zp->avail_in != 0 || flush)
     119             :     {
     120        1636 :         res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH);
     121        1636 :         if (res == Z_STREAM_ERROR)
     122           0 :             pg_fatal("could not compress data: %s", zp->msg);
     123        1636 :         if ((flush && (zp->avail_out < gzipcs->outsize))
     124        1232 :             || (zp->avail_out == 0)
     125        1232 :             || (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         404 :             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         404 :                 size_t      len = gzipcs->outsize - zp->avail_out;
     140             : 
     141         404 :                 cs->writeF(AH, out, len);
     142             :             }
     143         404 :             zp->next_out = out;
     144         404 :             zp->avail_out = gzipcs->outsize;
     145             :         }
     146             : 
     147        1636 :         if (res == Z_STREAM_END)
     148         404 :             break;
     149             :     }
     150        1636 : }
     151             : 
     152             : static void
     153         770 : EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs)
     154             : {
     155             :     /* If deflation was initialized, finalize it */
     156         770 :     if (cs->private_data)
     157         404 :         DeflateCompressorEnd(AH, cs);
     158         770 : }
     159             : 
     160             : static void
     161        1232 : WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs,
     162             :                        const void *data, size_t dLen)
     163             : {
     164        1232 :     GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data;
     165             : 
     166        1232 :     gzipcs->zp->next_in = data;
     167        1232 :     gzipcs->zp->avail_in = dLen;
     168        1232 :     DeflateCompressorCommon(AH, cs, false);
     169        1232 : }
     170             : 
     171             : static void
     172         366 : ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs)
     173             : {
     174             :     z_streamp   zp;
     175             :     char       *out;
     176         366 :     int         res = Z_OK;
     177             :     size_t      cnt;
     178             :     char       *buf;
     179             :     size_t      buflen;
     180             : 
     181         366 :     zp = (z_streamp) pg_malloc(sizeof(z_stream));
     182         366 :     zp->zalloc = Z_NULL;
     183         366 :     zp->zfree = Z_NULL;
     184         366 :     zp->opaque = Z_NULL;
     185             : 
     186         366 :     buflen = DEFAULT_IO_BUFFER_SIZE;
     187         366 :     buf = pg_malloc(buflen);
     188             : 
     189         366 :     out = pg_malloc(DEFAULT_IO_BUFFER_SIZE + 1);
     190             : 
     191         366 :     if (inflateInit(zp) != Z_OK)
     192           0 :         pg_fatal("could not initialize compression library: %s",
     193             :                  zp->msg);
     194             : 
     195             :     /* no minimal chunk size for zlib */
     196         732 :     while ((cnt = cs->readF(AH, &buf, &buflen)))
     197             :     {
     198         366 :         zp->next_in = (void *) buf;
     199         366 :         zp->avail_in = cnt;
     200             : 
     201         736 :         while (zp->avail_in > 0)
     202             :         {
     203         370 :             zp->next_out = (void *) out;
     204         370 :             zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
     205             : 
     206         370 :             res = inflate(zp, 0);
     207         370 :             if (res != Z_OK && res != Z_STREAM_END)
     208           0 :                 pg_fatal("could not uncompress data: %s", zp->msg);
     209             : 
     210         370 :             out[DEFAULT_IO_BUFFER_SIZE - zp->avail_out] = '\0';
     211         370 :             ahwrite(out, 1, DEFAULT_IO_BUFFER_SIZE - zp->avail_out, AH);
     212             :         }
     213             :     }
     214             : 
     215         366 :     zp->next_in = NULL;
     216         366 :     zp->avail_in = 0;
     217         366 :     while (res != Z_STREAM_END)
     218             :     {
     219           0 :         zp->next_out = (void *) out;
     220           0 :         zp->avail_out = DEFAULT_IO_BUFFER_SIZE;
     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             : 
     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             : 
     229         366 :     if (inflateEnd(zp) != Z_OK)
     230           0 :         pg_fatal("could not close compression library: %s", zp->msg);
     231             : 
     232         366 :     free(buf);
     233         366 :     free(out);
     234         366 :     free(zp);
     235         366 : }
     236             : 
     237             : /* Public routines that support gzip compressed data I/O */
     238             : void
     239         770 : InitCompressorGzip(CompressorState *cs,
     240             :                    const pg_compress_specification compression_spec)
     241             : {
     242         770 :     cs->readData = ReadDataFromArchiveGzip;
     243         770 :     cs->writeData = WriteDataToArchiveGzip;
     244         770 :     cs->end = EndCompressorGzip;
     245             : 
     246         770 :     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             :      */
     253         770 :     if (cs->writeF)
     254         404 :         DeflateCompressorInit(cs);
     255         770 : }
     256             : 
     257             : 
     258             : /*----------------------
     259             :  * Compress File API
     260             :  *----------------------
     261             :  */
     262             : 
     263             : static size_t
     264         738 : Gzip_read(void *ptr, size_t size, CompressFileHandle *CFH)
     265             : {
     266         738 :     gzFile      gzfp = (gzFile) CFH->private_data;
     267             :     int         gzret;
     268             : 
     269             :     /* Reading zero bytes must be a no-op */
     270         738 :     if (size == 0)
     271          32 :         return 0;
     272             : 
     273         706 :     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             :      */
     280         706 :     if (gzret <= 0)
     281             :     {
     282             :         int         errnum;
     283             :         const char *errmsg;
     284             : 
     285         210 :         if (gzret == 0 && gzeof(gzfp))
     286         210 :             return 0;
     287             : 
     288           0 :         errmsg = gzerror(gzfp, &errnum);
     289             : 
     290           0 :         pg_fatal("could not read from input file: %s",
     291             :                  errnum == Z_ERRNO ? strerror(errno) : errmsg);
     292             :     }
     293             : 
     294         496 :     return (size_t) gzret;
     295             : }
     296             : 
     297             : static void
     298       48902 : Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH)
     299             : {
     300       48902 :     gzFile      gzfp = (gzFile) CFH->private_data;
     301             :     int         errnum;
     302             :     const char *errmsg;
     303             : 
     304       48902 :     if (gzwrite(gzfp, ptr, size) != size)
     305             :     {
     306           0 :         errmsg = gzerror(gzfp, &errnum);
     307           0 :         pg_fatal("could not write to file: %s",
     308             :                  errnum == Z_ERRNO ? strerror(errno) : errmsg);
     309             :     }
     310       48902 : }
     311             : 
     312             : static int
     313        3144 : Gzip_getc(CompressFileHandle *CFH)
     314             : {
     315        3144 :     gzFile      gzfp = (gzFile) CFH->private_data;
     316             :     int         ret;
     317             : 
     318        3144 :     errno = 0;
     319        3144 :     ret = gzgetc(gzfp);
     320        3144 :     if (ret == EOF)
     321             :     {
     322           0 :         if (!gzeof(gzfp))
     323           0 :             pg_fatal("could not read from input file: %m");
     324             :         else
     325           0 :             pg_fatal("could not read from input file: end of file");
     326             :     }
     327             : 
     328        3144 :     return ret;
     329             : }
     330             : 
     331             : static char *
     332           4 : Gzip_gets(char *ptr, int size, CompressFileHandle *CFH)
     333             : {
     334           4 :     gzFile      gzfp = (gzFile) CFH->private_data;
     335             : 
     336           4 :     return gzgets(gzfp, ptr, size);
     337             : }
     338             : 
     339             : static bool
     340         424 : Gzip_close(CompressFileHandle *CFH)
     341             : {
     342         424 :     gzFile      gzfp = (gzFile) CFH->private_data;
     343             : 
     344         424 :     CFH->private_data = NULL;
     345             : 
     346         424 :     return gzclose(gzfp) == Z_OK;
     347             : }
     348             : 
     349             : static bool
     350           2 : Gzip_eof(CompressFileHandle *CFH)
     351             : {
     352           2 :     gzFile      gzfp = (gzFile) CFH->private_data;
     353             : 
     354           2 :     return gzeof(gzfp) == 1;
     355             : }
     356             : 
     357             : static const char *
     358           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
     372         424 : Gzip_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH)
     373             : {
     374             :     gzFile      gzfp;
     375             :     char        mode_compression[32];
     376             : 
     377         424 :     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         222 :         snprintf(mode_compression, sizeof(mode_compression), "%s%d",
     383             :                  mode, CFH->compression_spec.level);
     384             :     }
     385             :     else
     386         202 :         strcpy(mode_compression, mode);
     387             : 
     388         424 :     if (fd >= 0)
     389           0 :         gzfp = gzdopen(dup(fd), mode_compression);
     390             :     else
     391         424 :         gzfp = gzopen(path, mode_compression);
     392             : 
     393         424 :     if (gzfp == NULL)
     394           0 :         return false;
     395             : 
     396         424 :     CFH->private_data = gzfp;
     397             : 
     398         424 :     return true;
     399             : }
     400             : 
     401             : static bool
     402         208 : Gzip_open_write(const char *path, const char *mode, CompressFileHandle *CFH)
     403             : {
     404             :     char       *fname;
     405             :     bool        ret;
     406             :     int         save_errno;
     407             : 
     408         208 :     fname = psprintf("%s.gz", path);
     409         208 :     ret = CFH->open_func(fname, -1, mode, CFH);
     410             : 
     411         208 :     save_errno = errno;
     412         208 :     pg_free(fname);
     413         208 :     errno = save_errno;
     414             : 
     415         208 :     return ret;
     416             : }
     417             : 
     418             : void
     419         424 : InitCompressFileHandleGzip(CompressFileHandle *CFH,
     420             :                            const pg_compress_specification compression_spec)
     421             : {
     422         424 :     CFH->open_func = Gzip_open;
     423         424 :     CFH->open_write_func = Gzip_open_write;
     424         424 :     CFH->read_func = Gzip_read;
     425         424 :     CFH->write_func = Gzip_write;
     426         424 :     CFH->gets_func = Gzip_gets;
     427         424 :     CFH->getc_func = Gzip_getc;
     428         424 :     CFH->close_func = Gzip_close;
     429         424 :     CFH->eof_func = Gzip_eof;
     430         424 :     CFH->get_error_func = Gzip_get_error;
     431             : 
     432         424 :     CFH->compression_spec = compression_spec;
     433             : 
     434         424 :     CFH->private_data = NULL;
     435         424 : }
     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 */
 |