LCOV - code coverage report
Current view: top level - src/bin/pg_basebackup - walmethods.c (source / functions) Hit Total Coverage
Test: PostgreSQL 16beta1 Lines: 324 519 62.4 %
Date: 2023-05-30 23:12:14 Functions: 19 23 82.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * walmethods.c - implementations of different ways to write received wal
       4             :  *
       5             :  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
       6             :  *
       7             :  * IDENTIFICATION
       8             :  *        src/bin/pg_basebackup/walmethods.c
       9             :  *-------------------------------------------------------------------------
      10             :  */
      11             : 
      12             : #include "postgres_fe.h"
      13             : 
      14             : #include <sys/stat.h>
      15             : #include <time.h>
      16             : #include <unistd.h>
      17             : 
      18             : #ifdef USE_LZ4
      19             : #include <lz4frame.h>
      20             : #endif
      21             : #ifdef HAVE_LIBZ
      22             : #include <zlib.h>
      23             : #endif
      24             : 
      25             : #include "common/file_perm.h"
      26             : #include "common/file_utils.h"
      27             : #include "common/logging.h"
      28             : #include "pgtar.h"
      29             : #include "receivelog.h"
      30             : #include "streamutil.h"
      31             : 
      32             : /* Size of zlib buffer for .tar.gz */
      33             : #define ZLIB_OUT_SIZE 4096
      34             : 
      35             : /* Size of LZ4 input chunk for .lz4 */
      36             : #define LZ4_IN_SIZE  4096
      37             : 
      38             : /*-------------------------------------------------------------------------
      39             :  * WalDirectoryMethod - write wal to a directory looking like pg_wal
      40             :  *-------------------------------------------------------------------------
      41             :  */
      42             : 
      43             : static Walfile *dir_open_for_write(WalWriteMethod *wwmethod,
      44             :                                    const char *pathname,
      45             :                                    const char *temp_suffix,
      46             :                                    size_t pad_to_size);
      47             : static int  dir_close(Walfile *f, WalCloseMethod method);
      48             : static bool dir_existsfile(WalWriteMethod *wwmethod, const char *pathname);
      49             : static ssize_t dir_get_file_size(WalWriteMethod *wwmethod,
      50             :                                  const char *pathname);
      51             : static char *dir_get_file_name(WalWriteMethod *wwmethod,
      52             :                                const char *pathname, const char *temp_suffix);
      53             : static ssize_t dir_write(Walfile *f, const void *buf, size_t count);
      54             : static int  dir_sync(Walfile *f);
      55             : static bool dir_finish(WalWriteMethod *wwmethod);
      56             : static void dir_free(WalWriteMethod *wwmethod);
      57             : 
      58             : const WalWriteMethodOps WalDirectoryMethodOps = {
      59             :     .open_for_write = dir_open_for_write,
      60             :     .close = dir_close,
      61             :     .existsfile = dir_existsfile,
      62             :     .get_file_size = dir_get_file_size,
      63             :     .get_file_name = dir_get_file_name,
      64             :     .write = dir_write,
      65             :     .sync = dir_sync,
      66             :     .finish = dir_finish,
      67             :     .free = dir_free
      68             : };
      69             : 
      70             : /*
      71             :  * Global static data for this method
      72             :  */
      73             : typedef struct DirectoryMethodData
      74             : {
      75             :     WalWriteMethod base;
      76             :     char       *basedir;
      77             : } DirectoryMethodData;
      78             : 
      79             : /*
      80             :  * Local file handle
      81             :  */
      82             : typedef struct DirectoryMethodFile
      83             : {
      84             :     Walfile     base;
      85             :     int         fd;
      86             :     char       *fullpath;
      87             :     char       *temp_suffix;
      88             : #ifdef HAVE_LIBZ
      89             :     gzFile      gzfp;
      90             : #endif
      91             : #ifdef USE_LZ4
      92             :     LZ4F_compressionContext_t ctx;
      93             :     size_t      lz4bufsize;
      94             :     void       *lz4buf;
      95             : #endif
      96             : } DirectoryMethodFile;
      97             : 
      98             : #define clear_error(wwmethod) \
      99             :     ((wwmethod)->lasterrstring = NULL, (wwmethod)->lasterrno = 0)
     100             : 
     101             : static char *
     102         632 : dir_get_file_name(WalWriteMethod *wwmethod,
     103             :                   const char *pathname, const char *temp_suffix)
     104             : {
     105         632 :     char       *filename = pg_malloc0(MAXPGPATH * sizeof(char));
     106             : 
     107        1264 :     snprintf(filename, MAXPGPATH, "%s%s%s",
     108             :              pathname,
     109         632 :              wwmethod->compression_algorithm == PG_COMPRESSION_GZIP ? ".gz" :
     110         616 :              wwmethod->compression_algorithm == PG_COMPRESSION_LZ4 ? ".lz4" : "",
     111             :              temp_suffix ? temp_suffix : "");
     112             : 
     113         632 :     return filename;
     114             : }
     115             : 
     116             : static Walfile *
     117         212 : dir_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
     118             :                    const char *temp_suffix, size_t pad_to_size)
     119             : {
     120         212 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     121             :     char        tmppath[MAXPGPATH];
     122             :     char       *filename;
     123             :     int         fd;
     124             :     DirectoryMethodFile *f;
     125             : #ifdef HAVE_LIBZ
     126         212 :     gzFile      gzfp = NULL;
     127             : #endif
     128             : #ifdef USE_LZ4
     129         212 :     LZ4F_compressionContext_t ctx = NULL;
     130         212 :     size_t      lz4bufsize = 0;
     131         212 :     void       *lz4buf = NULL;
     132             : #endif
     133             : 
     134         212 :     clear_error(wwmethod);
     135             : 
     136         212 :     filename = dir_get_file_name(wwmethod, pathname, temp_suffix);
     137         212 :     snprintf(tmppath, sizeof(tmppath), "%s/%s",
     138             :              dir_data->basedir, filename);
     139         212 :     pg_free(filename);
     140             : 
     141             :     /*
     142             :      * Open a file for non-compressed as well as compressed files. Tracking
     143             :      * the file descriptor is important for dir_sync() method as gzflush()
     144             :      * does not do any system calls to fsync() to make changes permanent on
     145             :      * disk.
     146             :      */
     147         212 :     fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
     148         212 :     if (fd < 0)
     149             :     {
     150           0 :         wwmethod->lasterrno = errno;
     151           0 :         return NULL;
     152             :     }
     153             : 
     154             : #ifdef HAVE_LIBZ
     155         212 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     156             :     {
     157           4 :         gzfp = gzdopen(fd, "wb");
     158           4 :         if (gzfp == NULL)
     159             :         {
     160           0 :             wwmethod->lasterrno = errno;
     161           0 :             close(fd);
     162           0 :             return NULL;
     163             :         }
     164             : 
     165           4 :         if (gzsetparams(gzfp, wwmethod->compression_level,
     166             :                         Z_DEFAULT_STRATEGY) != Z_OK)
     167             :         {
     168           0 :             wwmethod->lasterrno = errno;
     169           0 :             gzclose(gzfp);
     170           0 :             return NULL;
     171             :         }
     172             :     }
     173             : #endif
     174             : #ifdef USE_LZ4
     175         212 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
     176             :     {
     177             :         size_t      ctx_out;
     178             :         size_t      header_size;
     179             :         LZ4F_preferences_t prefs;
     180             : 
     181           4 :         ctx_out = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
     182           4 :         if (LZ4F_isError(ctx_out))
     183             :         {
     184           0 :             wwmethod->lasterrstring = LZ4F_getErrorName(ctx_out);
     185           0 :             close(fd);
     186           0 :             return NULL;
     187             :         }
     188             : 
     189           4 :         lz4bufsize = LZ4F_compressBound(LZ4_IN_SIZE, NULL);
     190           4 :         lz4buf = pg_malloc0(lz4bufsize);
     191             : 
     192             :         /* assign the compression level, default is 0 */
     193           4 :         memset(&prefs, 0, sizeof(prefs));
     194           4 :         prefs.compressionLevel = wwmethod->compression_level;
     195             : 
     196             :         /* add the header */
     197           4 :         header_size = LZ4F_compressBegin(ctx, lz4buf, lz4bufsize, &prefs);
     198           4 :         if (LZ4F_isError(header_size))
     199             :         {
     200           0 :             wwmethod->lasterrstring = LZ4F_getErrorName(header_size);
     201           0 :             (void) LZ4F_freeCompressionContext(ctx);
     202           0 :             pg_free(lz4buf);
     203           0 :             close(fd);
     204           0 :             return NULL;
     205             :         }
     206             : 
     207           4 :         errno = 0;
     208           4 :         if (write(fd, lz4buf, header_size) != header_size)
     209             :         {
     210             :             /* If write didn't set errno, assume problem is no disk space */
     211           0 :             wwmethod->lasterrno = errno ? errno : ENOSPC;
     212           0 :             (void) LZ4F_freeCompressionContext(ctx);
     213           0 :             pg_free(lz4buf);
     214           0 :             close(fd);
     215           0 :             return NULL;
     216             :         }
     217             :     }
     218             : #endif
     219             : 
     220             :     /* Do pre-padding on non-compressed files */
     221         212 :     if (pad_to_size && wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     222             :     {
     223             :         ssize_t     rc;
     224             : 
     225         186 :         rc = pg_pwrite_zeros(fd, pad_to_size, 0);
     226             : 
     227         186 :         if (rc < 0)
     228             :         {
     229           0 :             wwmethod->lasterrno = errno;
     230           0 :             close(fd);
     231           0 :             return NULL;
     232             :         }
     233             : 
     234             :         /*
     235             :          * pg_pwrite() (called via pg_pwrite_zeros()) may have moved the file
     236             :          * position, so reset it (see win32pwrite.c).
     237             :          */
     238         186 :         if (lseek(fd, 0, SEEK_SET) != 0)
     239             :         {
     240           0 :             wwmethod->lasterrno = errno;
     241           0 :             close(fd);
     242           0 :             return NULL;
     243             :         }
     244             :     }
     245             : 
     246             :     /*
     247             :      * fsync WAL file and containing directory, to ensure the file is
     248             :      * persistently created and zeroed (if padded). That's particularly
     249             :      * important when using synchronous mode, where the file is modified and
     250             :      * fsynced in-place, without a directory fsync.
     251             :      */
     252         212 :     if (wwmethod->sync)
     253             :     {
     254          28 :         if (fsync_fname(tmppath, false) != 0 ||
     255          14 :             fsync_parent_path(tmppath) != 0)
     256             :         {
     257           0 :             wwmethod->lasterrno = errno;
     258             : #ifdef HAVE_LIBZ
     259           0 :             if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     260           0 :                 gzclose(gzfp);
     261             :             else
     262             : #endif
     263             : #ifdef USE_LZ4
     264           0 :             if (wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
     265             :             {
     266           0 :                 (void) LZ4F_compressEnd(ctx, lz4buf, lz4bufsize, NULL);
     267           0 :                 (void) LZ4F_freeCompressionContext(ctx);
     268           0 :                 pg_free(lz4buf);
     269           0 :                 close(fd);
     270             :             }
     271             :             else
     272             : #endif
     273           0 :                 close(fd);
     274           0 :             return NULL;
     275             :         }
     276             :     }
     277             : 
     278         212 :     f = pg_malloc0(sizeof(DirectoryMethodFile));
     279             : #ifdef HAVE_LIBZ
     280         212 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     281           4 :         f->gzfp = gzfp;
     282             : #endif
     283             : #ifdef USE_LZ4
     284         212 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
     285             :     {
     286           4 :         f->ctx = ctx;
     287           4 :         f->lz4buf = lz4buf;
     288           4 :         f->lz4bufsize = lz4bufsize;
     289             :     }
     290             : #endif
     291             : 
     292         212 :     f->base.wwmethod = wwmethod;
     293         212 :     f->base.currpos = 0;
     294         212 :     f->base.pathname = pg_strdup(pathname);
     295         212 :     f->fd = fd;
     296         212 :     f->fullpath = pg_strdup(tmppath);
     297         212 :     if (temp_suffix)
     298          28 :         f->temp_suffix = pg_strdup(temp_suffix);
     299             : 
     300         212 :     return &f->base;
     301             : }
     302             : 
     303             : static ssize_t
     304        3556 : dir_write(Walfile *f, const void *buf, size_t count)
     305             : {
     306             :     ssize_t     r;
     307        3556 :     DirectoryMethodFile *df = (DirectoryMethodFile *) f;
     308             : 
     309             :     Assert(f != NULL);
     310        3556 :     clear_error(f->wwmethod);
     311             : 
     312             : #ifdef HAVE_LIBZ
     313        3556 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     314             :     {
     315          18 :         errno = 0;
     316          18 :         r = (ssize_t) gzwrite(df->gzfp, buf, count);
     317          18 :         if (r != count)
     318             :         {
     319             :             /* If write didn't set errno, assume problem is no disk space */
     320           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     321             :         }
     322             :     }
     323             :     else
     324             : #endif
     325             : #ifdef USE_LZ4
     326        3538 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
     327             :     {
     328             :         size_t      chunk;
     329             :         size_t      remaining;
     330          18 :         const void *inbuf = buf;
     331             : 
     332          18 :         remaining = count;
     333         532 :         while (remaining > 0)
     334             :         {
     335             :             size_t      compressed;
     336             : 
     337         514 :             if (remaining > LZ4_IN_SIZE)
     338         496 :                 chunk = LZ4_IN_SIZE;
     339             :             else
     340          18 :                 chunk = remaining;
     341             : 
     342         514 :             remaining -= chunk;
     343         514 :             compressed = LZ4F_compressUpdate(df->ctx,
     344             :                                              df->lz4buf, df->lz4bufsize,
     345             :                                              inbuf, chunk,
     346             :                                              NULL);
     347             : 
     348         514 :             if (LZ4F_isError(compressed))
     349             :             {
     350           0 :                 f->wwmethod->lasterrstring = LZ4F_getErrorName(compressed);
     351           0 :                 return -1;
     352             :             }
     353             : 
     354         514 :             errno = 0;
     355         514 :             if (write(df->fd, df->lz4buf, compressed) != compressed)
     356             :             {
     357             :                 /* If write didn't set errno, assume problem is no disk space */
     358           0 :                 f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     359           0 :                 return -1;
     360             :             }
     361             : 
     362         514 :             inbuf = ((char *) inbuf) + chunk;
     363             :         }
     364             : 
     365             :         /* Our caller keeps track of the uncompressed size. */
     366          18 :         r = (ssize_t) count;
     367             :     }
     368             :     else
     369             : #endif
     370             :     {
     371        3520 :         errno = 0;
     372        3520 :         r = write(df->fd, buf, count);
     373        3520 :         if (r != count)
     374             :         {
     375             :             /* If write didn't set errno, assume problem is no disk space */
     376           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     377             :         }
     378             :     }
     379        3556 :     if (r > 0)
     380        3556 :         df->base.currpos += r;
     381        3556 :     return r;
     382             : }
     383             : 
     384             : static int
     385         212 : dir_close(Walfile *f, WalCloseMethod method)
     386             : {
     387             :     int         r;
     388         212 :     DirectoryMethodFile *df = (DirectoryMethodFile *) f;
     389         212 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) f->wwmethod;
     390             :     char        tmppath[MAXPGPATH];
     391             :     char        tmppath2[MAXPGPATH];
     392             : 
     393             :     Assert(f != NULL);
     394         212 :     clear_error(f->wwmethod);
     395             : 
     396             : #ifdef HAVE_LIBZ
     397         212 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     398             :     {
     399           4 :         errno = 0;              /* in case gzclose() doesn't set it */
     400           4 :         r = gzclose(df->gzfp);
     401             :     }
     402             :     else
     403             : #endif
     404             : #ifdef USE_LZ4
     405         208 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
     406             :     {
     407             :         size_t      compressed;
     408             : 
     409           4 :         compressed = LZ4F_compressEnd(df->ctx,
     410             :                                       df->lz4buf, df->lz4bufsize,
     411             :                                       NULL);
     412             : 
     413           4 :         if (LZ4F_isError(compressed))
     414             :         {
     415           0 :             f->wwmethod->lasterrstring = LZ4F_getErrorName(compressed);
     416           0 :             return -1;
     417             :         }
     418             : 
     419           4 :         errno = 0;
     420           4 :         if (write(df->fd, df->lz4buf, compressed) != compressed)
     421             :         {
     422             :             /* If write didn't set errno, assume problem is no disk space */
     423           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     424           0 :             return -1;
     425             :         }
     426             : 
     427           4 :         r = close(df->fd);
     428             :     }
     429             :     else
     430             : #endif
     431         204 :         r = close(df->fd);
     432             : 
     433         212 :     if (r == 0)
     434             :     {
     435             :         /* Build path to the current version of the file */
     436         212 :         if (method == CLOSE_NORMAL && df->temp_suffix)
     437          16 :         {
     438             :             char       *filename;
     439             :             char       *filename2;
     440             : 
     441             :             /*
     442             :              * If we have a temp prefix, normal operation is to rename the
     443             :              * file.
     444             :              */
     445          16 :             filename = dir_get_file_name(f->wwmethod, df->base.pathname,
     446          16 :                                          df->temp_suffix);
     447          16 :             snprintf(tmppath, sizeof(tmppath), "%s/%s",
     448             :                      dir_data->basedir, filename);
     449          16 :             pg_free(filename);
     450             : 
     451             :             /* permanent name, so no need for the prefix */
     452          16 :             filename2 = dir_get_file_name(f->wwmethod, df->base.pathname, NULL);
     453          16 :             snprintf(tmppath2, sizeof(tmppath2), "%s/%s",
     454             :                      dir_data->basedir, filename2);
     455          16 :             pg_free(filename2);
     456          16 :             if (f->wwmethod->sync)
     457           6 :                 r = durable_rename(tmppath, tmppath2);
     458             :             else
     459             :             {
     460          10 :                 if (rename(tmppath, tmppath2) != 0)
     461             :                 {
     462           0 :                     pg_log_error("could not rename file \"%s\" to \"%s\": %m",
     463             :                                  tmppath, tmppath2);
     464           0 :                     r = -1;
     465             :                 }
     466             :             }
     467             :         }
     468         196 :         else if (method == CLOSE_UNLINK)
     469             :         {
     470             :             char       *filename;
     471             : 
     472             :             /* Unlink the file once it's closed */
     473           0 :             filename = dir_get_file_name(f->wwmethod, df->base.pathname,
     474           0 :                                          df->temp_suffix);
     475           0 :             snprintf(tmppath, sizeof(tmppath), "%s/%s",
     476             :                      dir_data->basedir, filename);
     477           0 :             pg_free(filename);
     478           0 :             r = unlink(tmppath);
     479             :         }
     480             :         else
     481             :         {
     482             :             /*
     483             :              * Else either CLOSE_NORMAL and no temp suffix, or
     484             :              * CLOSE_NO_RENAME. In this case, fsync the file and containing
     485             :              * directory if sync mode is requested.
     486             :              */
     487         196 :             if (f->wwmethod->sync)
     488             :             {
     489           8 :                 r = fsync_fname(df->fullpath, false);
     490           8 :                 if (r == 0)
     491           8 :                     r = fsync_parent_path(df->fullpath);
     492             :             }
     493             :         }
     494             :     }
     495             : 
     496         212 :     if (r != 0)
     497           0 :         f->wwmethod->lasterrno = errno;
     498             : 
     499             : #ifdef USE_LZ4
     500         212 :     pg_free(df->lz4buf);
     501             :     /* supports free on NULL */
     502         212 :     LZ4F_freeCompressionContext(df->ctx);
     503             : #endif
     504             : 
     505         212 :     pg_free(df->base.pathname);
     506         212 :     pg_free(df->fullpath);
     507         212 :     pg_free(df->temp_suffix);
     508         212 :     pg_free(df);
     509             : 
     510         212 :     return r;
     511             : }
     512             : 
     513             : static int
     514           0 : dir_sync(Walfile *f)
     515             : {
     516             :     int         r;
     517             : 
     518             :     Assert(f != NULL);
     519           0 :     clear_error(f->wwmethod);
     520             : 
     521           0 :     if (!f->wwmethod->sync)
     522           0 :         return 0;
     523             : 
     524             : #ifdef HAVE_LIBZ
     525           0 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     526             :     {
     527           0 :         if (gzflush(((DirectoryMethodFile *) f)->gzfp, Z_SYNC_FLUSH) != Z_OK)
     528             :         {
     529           0 :             f->wwmethod->lasterrno = errno;
     530           0 :             return -1;
     531             :         }
     532             :     }
     533             : #endif
     534             : #ifdef USE_LZ4
     535           0 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
     536             :     {
     537           0 :         DirectoryMethodFile *df = (DirectoryMethodFile *) f;
     538             :         size_t      compressed;
     539             : 
     540             :         /* Flush any internal buffers */
     541           0 :         compressed = LZ4F_flush(df->ctx, df->lz4buf, df->lz4bufsize, NULL);
     542           0 :         if (LZ4F_isError(compressed))
     543             :         {
     544           0 :             f->wwmethod->lasterrstring = LZ4F_getErrorName(compressed);
     545           0 :             return -1;
     546             :         }
     547             : 
     548           0 :         errno = 0;
     549           0 :         if (write(df->fd, df->lz4buf, compressed) != compressed)
     550             :         {
     551             :             /* If write didn't set errno, assume problem is no disk space */
     552           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     553           0 :             return -1;
     554             :         }
     555             :     }
     556             : #endif
     557             : 
     558           0 :     r = fsync(((DirectoryMethodFile *) f)->fd);
     559           0 :     if (r < 0)
     560           0 :         f->wwmethod->lasterrno = errno;
     561           0 :     return r;
     562             : }
     563             : 
     564             : static ssize_t
     565           0 : dir_get_file_size(WalWriteMethod *wwmethod, const char *pathname)
     566             : {
     567           0 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     568             :     struct stat statbuf;
     569             :     char        tmppath[MAXPGPATH];
     570             : 
     571           0 :     snprintf(tmppath, sizeof(tmppath), "%s/%s",
     572             :              dir_data->basedir, pathname);
     573             : 
     574           0 :     if (stat(tmppath, &statbuf) != 0)
     575             :     {
     576           0 :         wwmethod->lasterrno = errno;
     577           0 :         return -1;
     578             :     }
     579             : 
     580           0 :     return statbuf.st_size;
     581             : }
     582             : 
     583             : static bool
     584         190 : dir_existsfile(WalWriteMethod *wwmethod, const char *pathname)
     585             : {
     586         190 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     587             :     char        tmppath[MAXPGPATH];
     588             :     int         fd;
     589             : 
     590         190 :     clear_error(wwmethod);
     591             : 
     592         190 :     snprintf(tmppath, sizeof(tmppath), "%s/%s",
     593             :              dir_data->basedir, pathname);
     594             : 
     595         190 :     fd = open(tmppath, O_RDONLY | PG_BINARY, 0);
     596         190 :     if (fd < 0)
     597         190 :         return false;
     598           0 :     close(fd);
     599           0 :     return true;
     600             : }
     601             : 
     602             : static bool
     603         180 : dir_finish(WalWriteMethod *wwmethod)
     604             : {
     605         180 :     clear_error(wwmethod);
     606             : 
     607         180 :     if (wwmethod->sync)
     608             :     {
     609           8 :         DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     610             : 
     611             :         /*
     612             :          * Files are fsynced when they are closed, but we need to fsync the
     613             :          * directory entry here as well.
     614             :          */
     615           8 :         if (fsync_fname(dir_data->basedir, true) != 0)
     616             :         {
     617           0 :             wwmethod->lasterrno = errno;
     618           0 :             return false;
     619             :         }
     620             :     }
     621         180 :     return true;
     622             : }
     623             : 
     624             : static void
     625         180 : dir_free(WalWriteMethod *wwmethod)
     626             : {
     627         180 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     628             : 
     629         180 :     pg_free(dir_data->basedir);
     630         180 :     pg_free(wwmethod);
     631         180 : }
     632             : 
     633             : 
     634             : WalWriteMethod *
     635         186 : CreateWalDirectoryMethod(const char *basedir,
     636             :                          pg_compress_algorithm compression_algorithm,
     637             :                          int compression_level, bool sync)
     638             : {
     639             :     DirectoryMethodData *wwmethod;
     640             : 
     641         186 :     wwmethod = pg_malloc0(sizeof(DirectoryMethodData));
     642         186 :     *((const WalWriteMethodOps **) &wwmethod->base.ops) =
     643             :         &WalDirectoryMethodOps;
     644         186 :     wwmethod->base.compression_algorithm = compression_algorithm;
     645         186 :     wwmethod->base.compression_level = compression_level;
     646         186 :     wwmethod->base.sync = sync;
     647         186 :     clear_error(&wwmethod->base);
     648         186 :     wwmethod->basedir = pg_strdup(basedir);
     649             : 
     650         186 :     return &wwmethod->base;
     651             : }
     652             : 
     653             : 
     654             : /*-------------------------------------------------------------------------
     655             :  * WalTarMethod - write wal to a tar file containing pg_wal contents
     656             :  *-------------------------------------------------------------------------
     657             :  */
     658             : 
     659             : static Walfile *tar_open_for_write(WalWriteMethod *wwmethod,
     660             :                                    const char *pathname,
     661             :                                    const char *temp_suffix,
     662             :                                    size_t pad_to_size);
     663             : static int  tar_close(Walfile *f, WalCloseMethod method);
     664             : static bool tar_existsfile(WalWriteMethod *wwmethod, const char *pathname);
     665             : static ssize_t tar_get_file_size(WalWriteMethod *wwmethod,
     666             :                                  const char *pathname);
     667             : static char *tar_get_file_name(WalWriteMethod *wwmethod,
     668             :                                const char *pathname, const char *temp_suffix);
     669             : static ssize_t tar_write(Walfile *f, const void *buf, size_t count);
     670             : static int  tar_sync(Walfile *f);
     671             : static bool tar_finish(WalWriteMethod *wwmethod);
     672             : static void tar_free(WalWriteMethod *wwmethod);
     673             : 
     674             : const WalWriteMethodOps WalTarMethodOps = {
     675             :     .open_for_write = tar_open_for_write,
     676             :     .close = tar_close,
     677             :     .existsfile = tar_existsfile,
     678             :     .get_file_size = tar_get_file_size,
     679             :     .get_file_name = tar_get_file_name,
     680             :     .write = tar_write,
     681             :     .sync = tar_sync,
     682             :     .finish = tar_finish,
     683             :     .free = tar_free
     684             : };
     685             : 
     686             : typedef struct TarMethodFile
     687             : {
     688             :     Walfile     base;
     689             :     off_t       ofs_start;      /* Where does the *header* for this file start */
     690             :     char        header[TAR_BLOCK_SIZE];
     691             :     size_t      pad_to_size;
     692             : } TarMethodFile;
     693             : 
     694             : typedef struct TarMethodData
     695             : {
     696             :     WalWriteMethod base;
     697             :     char       *tarfilename;
     698             :     int         fd;
     699             :     TarMethodFile *currentfile;
     700             : #ifdef HAVE_LIBZ
     701             :     z_streamp   zp;
     702             :     void       *zlibOut;
     703             : #endif
     704             : } TarMethodData;
     705             : 
     706             : #ifdef HAVE_LIBZ
     707             : static bool
     708       11814 : tar_write_compressed_data(TarMethodData *tar_data, void *buf, size_t count,
     709             :                           bool flush)
     710             : {
     711       11814 :     tar_data->zp->next_in = buf;
     712       11814 :     tar_data->zp->avail_in = count;
     713             : 
     714       23656 :     while (tar_data->zp->avail_in || flush)
     715             :     {
     716             :         int         r;
     717             : 
     718       11866 :         r = deflate(tar_data->zp, flush ? Z_FINISH : Z_NO_FLUSH);
     719       11866 :         if (r == Z_STREAM_ERROR)
     720             :         {
     721           0 :             tar_data->base.lasterrstring = _("could not compress data");
     722           0 :             return false;
     723             :         }
     724             : 
     725       11866 :         if (tar_data->zp->avail_out < ZLIB_OUT_SIZE)
     726             :         {
     727         124 :             size_t      len = ZLIB_OUT_SIZE - tar_data->zp->avail_out;
     728             : 
     729         124 :             errno = 0;
     730         124 :             if (write(tar_data->fd, tar_data->zlibOut, len) != len)
     731             :             {
     732             :                 /* If write didn't set errno, assume problem is no disk space */
     733           0 :                 tar_data->base.lasterrno = errno ? errno : ENOSPC;
     734           0 :                 return false;
     735             :             }
     736             : 
     737         124 :             tar_data->zp->next_out = tar_data->zlibOut;
     738         124 :             tar_data->zp->avail_out = ZLIB_OUT_SIZE;
     739             :         }
     740             : 
     741       11866 :         if (r == Z_STREAM_END)
     742          24 :             break;
     743             :     }
     744             : 
     745       11814 :     if (flush)
     746             :     {
     747             :         /* Reset the stream for writing */
     748          24 :         if (deflateReset(tar_data->zp) != Z_OK)
     749             :         {
     750           0 :             tar_data->base.lasterrstring = _("could not reset compression stream");
     751           0 :             return false;
     752             :         }
     753             :     }
     754             : 
     755       11814 :     return true;
     756             : }
     757             : #endif
     758             : 
     759             : static ssize_t
     760       29014 : tar_write(Walfile *f, const void *buf, size_t count)
     761             : {
     762       29014 :     TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
     763             :     ssize_t     r;
     764             : 
     765             :     Assert(f != NULL);
     766       29014 :     clear_error(f->wwmethod);
     767             : 
     768             :     /* Tarfile will always be positioned at the end */
     769       29014 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     770             :     {
     771       17230 :         errno = 0;
     772       17230 :         r = write(tar_data->fd, buf, count);
     773       17230 :         if (r != count)
     774             :         {
     775             :             /* If write didn't set errno, assume problem is no disk space */
     776           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     777           0 :             return -1;
     778             :         }
     779       17230 :         f->currpos += r;
     780       17230 :         return r;
     781             :     }
     782             : #ifdef HAVE_LIBZ
     783       11784 :     else if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     784             :     {
     785       11784 :         if (!tar_write_compressed_data(tar_data, unconstify(void *, buf),
     786             :                                        count, false))
     787           0 :             return -1;
     788       11784 :         f->currpos += count;
     789       11784 :         return count;
     790             :     }
     791             : #endif
     792             :     else
     793             :     {
     794             :         /* Can't happen - compression enabled with no method set */
     795           0 :         f->wwmethod->lasterrno = ENOSYS;
     796           0 :         return -1;
     797             :     }
     798             : }
     799             : 
     800             : static bool
     801          14 : tar_write_padding_data(TarMethodFile *f, size_t bytes)
     802             : {
     803             :     PGAlignedXLogBlock zerobuf;
     804          14 :     size_t      bytesleft = bytes;
     805             : 
     806          14 :     memset(zerobuf.data, 0, XLOG_BLCKSZ);
     807       28142 :     while (bytesleft)
     808             :     {
     809       28128 :         size_t      bytestowrite = Min(bytesleft, XLOG_BLCKSZ);
     810       28128 :         ssize_t     r = tar_write(&f->base, zerobuf.data, bytestowrite);
     811             : 
     812       28128 :         if (r < 0)
     813           0 :             return false;
     814       28128 :         bytesleft -= r;
     815             :     }
     816             : 
     817          14 :     return true;
     818             : }
     819             : 
     820             : static char *
     821          46 : tar_get_file_name(WalWriteMethod *wwmethod, const char *pathname,
     822             :                   const char *temp_suffix)
     823             : {
     824          46 :     char       *filename = pg_malloc0(MAXPGPATH * sizeof(char));
     825             : 
     826          46 :     snprintf(filename, MAXPGPATH, "%s%s",
     827             :              pathname, temp_suffix ? temp_suffix : "");
     828             : 
     829          46 :     return filename;
     830             : }
     831             : 
     832             : static Walfile *
     833          18 : tar_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
     834             :                    const char *temp_suffix, size_t pad_to_size)
     835             : {
     836          18 :     TarMethodData *tar_data = (TarMethodData *) wwmethod;
     837             :     char       *tmppath;
     838             : 
     839          18 :     clear_error(wwmethod);
     840             : 
     841          18 :     if (tar_data->fd < 0)
     842             :     {
     843             :         /*
     844             :          * We open the tar file only when we first try to write to it.
     845             :          */
     846          14 :         tar_data->fd = open(tar_data->tarfilename,
     847             :                             O_WRONLY | O_CREAT | PG_BINARY,
     848             :                             pg_file_create_mode);
     849          14 :         if (tar_data->fd < 0)
     850             :         {
     851           0 :             wwmethod->lasterrno = errno;
     852           0 :             return NULL;
     853             :         }
     854             : 
     855             : #ifdef HAVE_LIBZ
     856          14 :         if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     857             :         {
     858           6 :             tar_data->zp = (z_streamp) pg_malloc(sizeof(z_stream));
     859           6 :             tar_data->zp->zalloc = Z_NULL;
     860           6 :             tar_data->zp->zfree = Z_NULL;
     861           6 :             tar_data->zp->opaque = Z_NULL;
     862           6 :             tar_data->zp->next_out = tar_data->zlibOut;
     863           6 :             tar_data->zp->avail_out = ZLIB_OUT_SIZE;
     864             : 
     865             :             /*
     866             :              * Initialize deflation library. Adding the magic value 16 to the
     867             :              * default 15 for the windowBits parameter makes the output be
     868             :              * gzip instead of zlib.
     869             :              */
     870           6 :             if (deflateInit2(tar_data->zp, wwmethod->compression_level,
     871             :                              Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
     872             :             {
     873           0 :                 pg_free(tar_data->zp);
     874           0 :                 tar_data->zp = NULL;
     875           0 :                 wwmethod->lasterrstring =
     876           0 :                     _("could not initialize compression library");
     877           0 :                 return NULL;
     878             :             }
     879             :         }
     880             : #endif
     881             : 
     882             :         /* There's no tar header itself, the file starts with regular files */
     883             :     }
     884             : 
     885          18 :     if (tar_data->currentfile != NULL)
     886             :     {
     887           0 :         wwmethod->lasterrstring =
     888           0 :             _("implementation error: tar files can't have more than one open file");
     889           0 :         return NULL;
     890             :     }
     891             : 
     892          18 :     tar_data->currentfile = pg_malloc0(sizeof(TarMethodFile));
     893          18 :     tar_data->currentfile->base.wwmethod = wwmethod;
     894             : 
     895          18 :     tmppath = tar_get_file_name(wwmethod, pathname, temp_suffix);
     896             : 
     897             :     /* Create a header with size set to 0 - we will fill out the size on close */
     898          18 :     if (tarCreateHeader(tar_data->currentfile->header, tmppath, NULL, 0, S_IRUSR | S_IWUSR, 0, 0, time(NULL)) != TAR_OK)
     899             :     {
     900           0 :         pg_free(tar_data->currentfile);
     901           0 :         pg_free(tmppath);
     902           0 :         tar_data->currentfile = NULL;
     903           0 :         wwmethod->lasterrstring = _("could not create tar header");
     904           0 :         return NULL;
     905             :     }
     906             : 
     907          18 :     pg_free(tmppath);
     908             : 
     909             : #ifdef HAVE_LIBZ
     910          18 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     911             :     {
     912             :         /* Flush existing data */
     913           6 :         if (!tar_write_compressed_data(tar_data, NULL, 0, true))
     914           0 :             return NULL;
     915             : 
     916             :         /* Turn off compression for header */
     917           6 :         if (deflateParams(tar_data->zp, 0, Z_DEFAULT_STRATEGY) != Z_OK)
     918             :         {
     919           0 :             wwmethod->lasterrstring =
     920           0 :                 _("could not change compression parameters");
     921           0 :             return NULL;
     922             :         }
     923             :     }
     924             : #endif
     925             : 
     926          18 :     tar_data->currentfile->ofs_start = lseek(tar_data->fd, 0, SEEK_CUR);
     927          18 :     if (tar_data->currentfile->ofs_start == -1)
     928             :     {
     929           0 :         wwmethod->lasterrno = errno;
     930           0 :         pg_free(tar_data->currentfile);
     931           0 :         tar_data->currentfile = NULL;
     932           0 :         return NULL;
     933             :     }
     934          18 :     tar_data->currentfile->base.currpos = 0;
     935             : 
     936          18 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     937             :     {
     938          12 :         errno = 0;
     939          12 :         if (write(tar_data->fd, tar_data->currentfile->header,
     940             :                   TAR_BLOCK_SIZE) != TAR_BLOCK_SIZE)
     941             :         {
     942             :             /* If write didn't set errno, assume problem is no disk space */
     943           0 :             wwmethod->lasterrno = errno ? errno : ENOSPC;
     944           0 :             pg_free(tar_data->currentfile);
     945           0 :             tar_data->currentfile = NULL;
     946           0 :             return NULL;
     947             :         }
     948             :     }
     949             : #ifdef HAVE_LIBZ
     950           6 :     else if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     951             :     {
     952             :         /* Write header through the zlib APIs but with no compression */
     953           6 :         if (!tar_write_compressed_data(tar_data, tar_data->currentfile->header,
     954             :                                        TAR_BLOCK_SIZE, true))
     955           0 :             return NULL;
     956             : 
     957             :         /* Re-enable compression for the rest of the file */
     958           6 :         if (deflateParams(tar_data->zp, wwmethod->compression_level,
     959             :                           Z_DEFAULT_STRATEGY) != Z_OK)
     960             :         {
     961           0 :             wwmethod->lasterrstring = _("could not change compression parameters");
     962           0 :             return NULL;
     963             :         }
     964             :     }
     965             : #endif
     966             :     else
     967             :     {
     968             :         /* not reachable */
     969             :         Assert(false);
     970             :     }
     971             : 
     972          18 :     tar_data->currentfile->base.pathname = pg_strdup(pathname);
     973             : 
     974             :     /*
     975             :      * Uncompressed files are padded on creation, but for compression we can't
     976             :      * do that
     977             :      */
     978          18 :     if (pad_to_size)
     979             :     {
     980          14 :         tar_data->currentfile->pad_to_size = pad_to_size;
     981          14 :         if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     982             :         {
     983             :             /* Uncompressed, so pad now */
     984           8 :             if (!tar_write_padding_data(tar_data->currentfile, pad_to_size))
     985           0 :                 return NULL;
     986             :             /* Seek back to start */
     987          16 :             if (lseek(tar_data->fd,
     988           8 :                       tar_data->currentfile->ofs_start + TAR_BLOCK_SIZE,
     989           8 :                       SEEK_SET) != tar_data->currentfile->ofs_start + TAR_BLOCK_SIZE)
     990             :             {
     991           0 :                 wwmethod->lasterrno = errno;
     992           0 :                 return NULL;
     993             :             }
     994             : 
     995           8 :             tar_data->currentfile->base.currpos = 0;
     996             :         }
     997             :     }
     998             : 
     999          18 :     return &tar_data->currentfile->base;
    1000             : }
    1001             : 
    1002             : static ssize_t
    1003           0 : tar_get_file_size(WalWriteMethod *wwmethod, const char *pathname)
    1004             : {
    1005           0 :     clear_error(wwmethod);
    1006             : 
    1007             :     /* Currently not used, so not supported */
    1008           0 :     wwmethod->lasterrno = ENOSYS;
    1009           0 :     return -1;
    1010             : }
    1011             : 
    1012             : static int
    1013          18 : tar_sync(Walfile *f)
    1014             : {
    1015          18 :     TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
    1016             :     int         r;
    1017             : 
    1018             :     Assert(f != NULL);
    1019          18 :     clear_error(f->wwmethod);
    1020             : 
    1021          18 :     if (!f->wwmethod->sync)
    1022          18 :         return 0;
    1023             : 
    1024             :     /*
    1025             :      * Always sync the whole tarfile, because that's all we can do. This makes
    1026             :      * no sense on compressed files, so just ignore those.
    1027             :      */
    1028           0 :     if (f->wwmethod->compression_algorithm != PG_COMPRESSION_NONE)
    1029           0 :         return 0;
    1030             : 
    1031           0 :     r = fsync(tar_data->fd);
    1032           0 :     if (r < 0)
    1033           0 :         f->wwmethod->lasterrno = errno;
    1034           0 :     return r;
    1035             : }
    1036             : 
    1037             : static int
    1038          18 : tar_close(Walfile *f, WalCloseMethod method)
    1039             : {
    1040             :     ssize_t     filesize;
    1041             :     int         padding;
    1042          18 :     TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
    1043          18 :     TarMethodFile *tf = (TarMethodFile *) f;
    1044             : 
    1045             :     Assert(f != NULL);
    1046          18 :     clear_error(f->wwmethod);
    1047             : 
    1048          18 :     if (method == CLOSE_UNLINK)
    1049             :     {
    1050           0 :         if (f->wwmethod->compression_algorithm != PG_COMPRESSION_NONE)
    1051             :         {
    1052           0 :             f->wwmethod->lasterrstring = _("unlink not supported with compression");
    1053           0 :             return -1;
    1054             :         }
    1055             : 
    1056             :         /*
    1057             :          * Unlink the file that we just wrote to the tar. We do this by
    1058             :          * truncating it to the start of the header. This is safe as we only
    1059             :          * allow writing of the very last file.
    1060             :          */
    1061           0 :         if (ftruncate(tar_data->fd, tf->ofs_start) != 0)
    1062             :         {
    1063           0 :             f->wwmethod->lasterrno = errno;
    1064           0 :             return -1;
    1065             :         }
    1066             : 
    1067           0 :         pg_free(tf->base.pathname);
    1068           0 :         pg_free(tf);
    1069           0 :         tar_data->currentfile = NULL;
    1070             : 
    1071           0 :         return 0;
    1072             :     }
    1073             : 
    1074             :     /*
    1075             :      * Pad the file itself with zeroes if necessary. Note that this is
    1076             :      * different from the tar format padding -- this is the padding we asked
    1077             :      * for when the file was opened.
    1078             :      */
    1079          18 :     if (tf->pad_to_size)
    1080             :     {
    1081          14 :         if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1082             :         {
    1083             :             /*
    1084             :              * A compressed tarfile is padded on close since we cannot know
    1085             :              * the size of the compressed output until the end.
    1086             :              */
    1087           6 :             size_t      sizeleft = tf->pad_to_size - tf->base.currpos;
    1088             : 
    1089           6 :             if (sizeleft)
    1090             :             {
    1091           6 :                 if (!tar_write_padding_data(tf, sizeleft))
    1092           0 :                     return -1;
    1093             :             }
    1094             :         }
    1095             :         else
    1096             :         {
    1097             :             /*
    1098             :              * An uncompressed tarfile was padded on creation, so just adjust
    1099             :              * the current position as if we seeked to the end.
    1100             :              */
    1101           8 :             tf->base.currpos = tf->pad_to_size;
    1102             :         }
    1103             :     }
    1104             : 
    1105             :     /*
    1106             :      * Get the size of the file, and pad out to a multiple of the tar block
    1107             :      * size.
    1108             :      */
    1109          18 :     filesize = f->currpos;
    1110          18 :     padding = tarPaddingBytesRequired(filesize);
    1111          18 :     if (padding)
    1112             :     {
    1113           0 :         char        zerobuf[TAR_BLOCK_SIZE] = {0};
    1114             : 
    1115           0 :         if (tar_write(f, zerobuf, padding) != padding)
    1116           0 :             return -1;
    1117             :     }
    1118             : 
    1119             : 
    1120             : #ifdef HAVE_LIBZ
    1121          18 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1122             :     {
    1123             :         /* Flush the current buffer */
    1124           6 :         if (!tar_write_compressed_data(tar_data, NULL, 0, true))
    1125           0 :             return -1;
    1126             :     }
    1127             : #endif
    1128             : 
    1129             :     /*
    1130             :      * Now go back and update the header with the correct filesize and
    1131             :      * possibly also renaming the file. We overwrite the entire current header
    1132             :      * when done, including the checksum.
    1133             :      */
    1134          18 :     print_tar_number(&(tf->header[124]), 12, filesize);
    1135             : 
    1136          18 :     if (method == CLOSE_NORMAL)
    1137             : 
    1138             :         /*
    1139             :          * We overwrite it with what it was before if we have no tempname,
    1140             :          * since we're going to write the buffer anyway.
    1141             :          */
    1142          18 :         strlcpy(&(tf->header[0]), tf->base.pathname, 100);
    1143             : 
    1144          18 :     print_tar_number(&(tf->header[148]), 8, tarChecksum(((TarMethodFile *) f)->header));
    1145          18 :     if (lseek(tar_data->fd, tf->ofs_start, SEEK_SET) != ((TarMethodFile *) f)->ofs_start)
    1146             :     {
    1147           0 :         f->wwmethod->lasterrno = errno;
    1148           0 :         return -1;
    1149             :     }
    1150          18 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
    1151             :     {
    1152          12 :         errno = 0;
    1153          12 :         if (write(tar_data->fd, tf->header, TAR_BLOCK_SIZE) != TAR_BLOCK_SIZE)
    1154             :         {
    1155             :             /* If write didn't set errno, assume problem is no disk space */
    1156           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
    1157           0 :             return -1;
    1158             :         }
    1159             :     }
    1160             : #ifdef HAVE_LIBZ
    1161           6 :     else if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1162             :     {
    1163             :         /* Turn off compression */
    1164           6 :         if (deflateParams(tar_data->zp, 0, Z_DEFAULT_STRATEGY) != Z_OK)
    1165             :         {
    1166           0 :             f->wwmethod->lasterrstring = _("could not change compression parameters");
    1167           0 :             return -1;
    1168             :         }
    1169             : 
    1170             :         /* Overwrite the header, assuming the size will be the same */
    1171           6 :         if (!tar_write_compressed_data(tar_data, tar_data->currentfile->header,
    1172             :                                        TAR_BLOCK_SIZE, true))
    1173           0 :             return -1;
    1174             : 
    1175             :         /* Turn compression back on */
    1176           6 :         if (deflateParams(tar_data->zp, f->wwmethod->compression_level,
    1177             :                           Z_DEFAULT_STRATEGY) != Z_OK)
    1178             :         {
    1179           0 :             f->wwmethod->lasterrstring = _("could not change compression parameters");
    1180           0 :             return -1;
    1181             :         }
    1182             :     }
    1183             : #endif
    1184             :     else
    1185             :     {
    1186             :         /* not reachable */
    1187             :         Assert(false);
    1188             :     }
    1189             : 
    1190             :     /* Move file pointer back down to end, so we can write the next file */
    1191          18 :     if (lseek(tar_data->fd, 0, SEEK_END) < 0)
    1192             :     {
    1193           0 :         f->wwmethod->lasterrno = errno;
    1194           0 :         return -1;
    1195             :     }
    1196             : 
    1197             :     /* Always fsync on close, so the padding gets fsynced */
    1198          18 :     if (tar_sync(f) < 0)
    1199             :     {
    1200             :         /* XXX this seems pretty bogus; why is only this case fatal? */
    1201           0 :         pg_fatal("could not fsync file \"%s\": %s",
    1202             :                  tf->base.pathname, GetLastWalMethodError(f->wwmethod));
    1203             :     }
    1204             : 
    1205             :     /* Clean up and done */
    1206          18 :     pg_free(tf->base.pathname);
    1207          18 :     pg_free(tf);
    1208          18 :     tar_data->currentfile = NULL;
    1209             : 
    1210          18 :     return 0;
    1211             : }
    1212             : 
    1213             : static bool
    1214           8 : tar_existsfile(WalWriteMethod *wwmethod, const char *pathname)
    1215             : {
    1216           8 :     clear_error(wwmethod);
    1217             :     /* We only deal with new tarfiles, so nothing externally created exists */
    1218           8 :     return false;
    1219             : }
    1220             : 
    1221             : static bool
    1222          14 : tar_finish(WalWriteMethod *wwmethod)
    1223             : {
    1224          14 :     TarMethodData *tar_data = (TarMethodData *) wwmethod;
    1225          14 :     char        zerobuf[1024] = {0};
    1226             : 
    1227          14 :     clear_error(wwmethod);
    1228             : 
    1229          14 :     if (tar_data->currentfile)
    1230             :     {
    1231           0 :         if (tar_close(&tar_data->currentfile->base, CLOSE_NORMAL) != 0)
    1232           0 :             return false;
    1233             :     }
    1234             : 
    1235             :     /* A tarfile always ends with two empty blocks */
    1236          14 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
    1237             :     {
    1238           8 :         errno = 0;
    1239           8 :         if (write(tar_data->fd, zerobuf, sizeof(zerobuf)) != sizeof(zerobuf))
    1240             :         {
    1241             :             /* If write didn't set errno, assume problem is no disk space */
    1242           0 :             wwmethod->lasterrno = errno ? errno : ENOSPC;
    1243           0 :             return false;
    1244             :         }
    1245             :     }
    1246             : #ifdef HAVE_LIBZ
    1247           6 :     else if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1248             :     {
    1249           6 :         if (!tar_write_compressed_data(tar_data, zerobuf, sizeof(zerobuf),
    1250             :                                        false))
    1251           0 :             return false;
    1252             : 
    1253             :         /* Also flush all data to make sure the gzip stream is finished */
    1254           6 :         tar_data->zp->next_in = NULL;
    1255           6 :         tar_data->zp->avail_in = 0;
    1256             :         while (true)
    1257           0 :         {
    1258             :             int         r;
    1259             : 
    1260           6 :             r = deflate(tar_data->zp, Z_FINISH);
    1261             : 
    1262           6 :             if (r == Z_STREAM_ERROR)
    1263             :             {
    1264           0 :                 wwmethod->lasterrstring = _("could not compress data");
    1265           0 :                 return false;
    1266             :             }
    1267           6 :             if (tar_data->zp->avail_out < ZLIB_OUT_SIZE)
    1268             :             {
    1269           6 :                 size_t      len = ZLIB_OUT_SIZE - tar_data->zp->avail_out;
    1270             : 
    1271           6 :                 errno = 0;
    1272           6 :                 if (write(tar_data->fd, tar_data->zlibOut, len) != len)
    1273             :                 {
    1274             :                     /*
    1275             :                      * If write didn't set errno, assume problem is no disk
    1276             :                      * space.
    1277             :                      */
    1278           0 :                     wwmethod->lasterrno = errno ? errno : ENOSPC;
    1279           0 :                     return false;
    1280             :                 }
    1281             :             }
    1282           6 :             if (r == Z_STREAM_END)
    1283           6 :                 break;
    1284             :         }
    1285             : 
    1286           6 :         if (deflateEnd(tar_data->zp) != Z_OK)
    1287             :         {
    1288           0 :             wwmethod->lasterrstring = _("could not close compression stream");
    1289           0 :             return false;
    1290             :         }
    1291             :     }
    1292             : #endif
    1293             :     else
    1294             :     {
    1295             :         /* not reachable */
    1296             :         Assert(false);
    1297             :     }
    1298             : 
    1299             :     /* sync the empty blocks as well, since they're after the last file */
    1300          14 :     if (wwmethod->sync)
    1301             :     {
    1302           0 :         if (fsync(tar_data->fd) != 0)
    1303             :         {
    1304           0 :             wwmethod->lasterrno = errno;
    1305           0 :             return false;
    1306             :         }
    1307             :     }
    1308             : 
    1309          14 :     if (close(tar_data->fd) != 0)
    1310             :     {
    1311           0 :         wwmethod->lasterrno = errno;
    1312           0 :         return false;
    1313             :     }
    1314             : 
    1315          14 :     tar_data->fd = -1;
    1316             : 
    1317          14 :     if (wwmethod->sync)
    1318             :     {
    1319           0 :         if (fsync_fname(tar_data->tarfilename, false) != 0 ||
    1320           0 :             fsync_parent_path(tar_data->tarfilename) != 0)
    1321             :         {
    1322           0 :             wwmethod->lasterrno = errno;
    1323           0 :             return false;
    1324             :         }
    1325             :     }
    1326             : 
    1327          14 :     return true;
    1328             : }
    1329             : 
    1330             : static void
    1331          14 : tar_free(WalWriteMethod *wwmethod)
    1332             : {
    1333          14 :     TarMethodData *tar_data = (TarMethodData *) wwmethod;
    1334             : 
    1335          14 :     pg_free(tar_data->tarfilename);
    1336             : #ifdef HAVE_LIBZ
    1337          14 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1338           6 :         pg_free(tar_data->zlibOut);
    1339             : #endif
    1340          14 :     pg_free(wwmethod);
    1341          14 : }
    1342             : 
    1343             : /*
    1344             :  * The argument compression_algorithm is currently ignored. It is in place for
    1345             :  * symmetry with CreateWalDirectoryMethod which uses it for distinguishing
    1346             :  * between the different compression methods. CreateWalTarMethod and its family
    1347             :  * of functions handle only zlib compression.
    1348             :  */
    1349             : WalWriteMethod *
    1350          14 : CreateWalTarMethod(const char *tarbase,
    1351             :                    pg_compress_algorithm compression_algorithm,
    1352             :                    int compression_level, bool sync)
    1353             : {
    1354             :     TarMethodData *wwmethod;
    1355          14 :     const char *suffix = (compression_algorithm == PG_COMPRESSION_GZIP) ?
    1356          14 :         ".tar.gz" : ".tar";
    1357             : 
    1358          14 :     wwmethod = pg_malloc0(sizeof(TarMethodData));
    1359          14 :     *((const WalWriteMethodOps **) &wwmethod->base.ops) =
    1360             :         &WalTarMethodOps;
    1361          14 :     wwmethod->base.compression_algorithm = compression_algorithm;
    1362          14 :     wwmethod->base.compression_level = compression_level;
    1363          14 :     wwmethod->base.sync = sync;
    1364          14 :     clear_error(&wwmethod->base);
    1365             : 
    1366          14 :     wwmethod->tarfilename = pg_malloc0(strlen(tarbase) + strlen(suffix) + 1);
    1367          14 :     sprintf(wwmethod->tarfilename, "%s%s", tarbase, suffix);
    1368          14 :     wwmethod->fd = -1;
    1369             : #ifdef HAVE_LIBZ
    1370          14 :     if (compression_algorithm == PG_COMPRESSION_GZIP)
    1371           6 :         wwmethod->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
    1372             : #endif
    1373             : 
    1374          14 :     return &wwmethod->base;
    1375             : }
    1376             : 
    1377             : const char *
    1378           0 : GetLastWalMethodError(WalWriteMethod *wwmethod)
    1379             : {
    1380           0 :     if (wwmethod->lasterrstring)
    1381           0 :         return wwmethod->lasterrstring;
    1382           0 :     return strerror(wwmethod->lasterrno);
    1383             : }

Generated by: LCOV version 1.14