LCOV - code coverage report
Current view: top level - src/bin/pg_basebackup - walmethods.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 325 520 62.5 %
Date: 2024-04-25 01:13:18 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-2024, 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         770 : dir_get_file_name(WalWriteMethod *wwmethod,
     103             :                   const char *pathname, const char *temp_suffix)
     104             : {
     105         770 :     char       *filename = pg_malloc0(MAXPGPATH * sizeof(char));
     106             : 
     107        1540 :     snprintf(filename, MAXPGPATH, "%s%s%s",
     108             :              pathname,
     109         770 :              wwmethod->compression_algorithm == PG_COMPRESSION_GZIP ? ".gz" :
     110         754 :              wwmethod->compression_algorithm == PG_COMPRESSION_LZ4 ? ".lz4" : "",
     111             :              temp_suffix ? temp_suffix : "");
     112             : 
     113         770 :     return filename;
     114             : }
     115             : 
     116             : static Walfile *
     117         258 : dir_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
     118             :                    const char *temp_suffix, size_t pad_to_size)
     119             : {
     120         258 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     121             :     char        tmppath[MAXPGPATH];
     122             :     char       *filename;
     123             :     int         fd;
     124             :     DirectoryMethodFile *f;
     125             : #ifdef HAVE_LIBZ
     126         258 :     gzFile      gzfp = NULL;
     127             : #endif
     128             : #ifdef USE_LZ4
     129         258 :     LZ4F_compressionContext_t ctx = NULL;
     130         258 :     size_t      lz4bufsize = 0;
     131         258 :     void       *lz4buf = NULL;
     132             : #endif
     133             : 
     134         258 :     clear_error(wwmethod);
     135             : 
     136         258 :     filename = dir_get_file_name(wwmethod, pathname, temp_suffix);
     137         258 :     snprintf(tmppath, sizeof(tmppath), "%s/%s",
     138             :              dir_data->basedir, filename);
     139         258 :     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         258 :     fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
     148         258 :     if (fd < 0)
     149             :     {
     150           0 :         wwmethod->lasterrno = errno;
     151           0 :         return NULL;
     152             :     }
     153             : 
     154             : #ifdef HAVE_LIBZ
     155         258 :     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         258 :     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         258 :     if (pad_to_size && wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     222             :     {
     223             :         ssize_t     rc;
     224             : 
     225         232 :         rc = pg_pwrite_zeros(fd, pad_to_size, 0);
     226             : 
     227         232 :         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         232 :         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         258 :     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         258 :     f = pg_malloc0(sizeof(DirectoryMethodFile));
     279             : #ifdef HAVE_LIBZ
     280         258 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     281           4 :         f->gzfp = gzfp;
     282             : #endif
     283             : #ifdef USE_LZ4
     284         258 :     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         258 :     f->base.wwmethod = wwmethod;
     293         258 :     f->base.currpos = 0;
     294         258 :     f->base.pathname = pg_strdup(pathname);
     295         258 :     f->fd = fd;
     296         258 :     f->fullpath = pg_strdup(tmppath);
     297         258 :     if (temp_suffix)
     298          28 :         f->temp_suffix = pg_strdup(temp_suffix);
     299             : 
     300         258 :     return &f->base;
     301             : }
     302             : 
     303             : static ssize_t
     304        6268 : dir_write(Walfile *f, const void *buf, size_t count)
     305             : {
     306             :     ssize_t     r;
     307        6268 :     DirectoryMethodFile *df = (DirectoryMethodFile *) f;
     308             : 
     309             :     Assert(f != NULL);
     310        6268 :     clear_error(f->wwmethod);
     311             : 
     312             : #ifdef HAVE_LIBZ
     313        6268 :     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        6250 :     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        6232 :         errno = 0;
     372        6232 :         r = write(df->fd, buf, count);
     373        6232 :         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        6268 :     if (r > 0)
     380        6268 :         df->base.currpos += r;
     381        6268 :     return r;
     382             : }
     383             : 
     384             : static int
     385         258 : dir_close(Walfile *f, WalCloseMethod method)
     386             : {
     387             :     int         r;
     388         258 :     DirectoryMethodFile *df = (DirectoryMethodFile *) f;
     389         258 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) f->wwmethod;
     390             :     char        tmppath[MAXPGPATH];
     391             :     char        tmppath2[MAXPGPATH];
     392             : 
     393             :     Assert(f != NULL);
     394         258 :     clear_error(f->wwmethod);
     395             : 
     396             : #ifdef HAVE_LIBZ
     397         258 :     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         254 :     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         250 :         r = close(df->fd);
     432             : 
     433         258 :     if (r == 0)
     434             :     {
     435             :         /* Build path to the current version of the file */
     436         258 :         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         242 :         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         242 :             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         258 :     if (r != 0)
     497           0 :         f->wwmethod->lasterrno = errno;
     498             : 
     499             : #ifdef USE_LZ4
     500         258 :     pg_free(df->lz4buf);
     501             :     /* supports free on NULL */
     502         258 :     LZ4F_freeCompressionContext(df->ctx);
     503             : #endif
     504             : 
     505         258 :     pg_free(df->base.pathname);
     506         258 :     pg_free(df->fullpath);
     507         258 :     pg_free(df->temp_suffix);
     508         258 :     pg_free(df);
     509             : 
     510         258 :     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         236 : dir_existsfile(WalWriteMethod *wwmethod, const char *pathname)
     585             : {
     586         236 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     587             :     char        tmppath[MAXPGPATH];
     588             :     int         fd;
     589             : 
     590         236 :     clear_error(wwmethod);
     591             : 
     592         236 :     snprintf(tmppath, sizeof(tmppath), "%s/%s",
     593             :              dir_data->basedir, pathname);
     594             : 
     595         236 :     fd = open(tmppath, O_RDONLY | PG_BINARY, 0);
     596         236 :     if (fd < 0)
     597             : 
     598             :         /*
     599             :          * Skip setting dir_data->lasterrno here because we are only checking
     600             :          * for existence.
     601             :          */
     602         236 :         return false;
     603           0 :     close(fd);
     604           0 :     return true;
     605             : }
     606             : 
     607             : static bool
     608         226 : dir_finish(WalWriteMethod *wwmethod)
     609             : {
     610         226 :     clear_error(wwmethod);
     611             : 
     612         226 :     if (wwmethod->sync)
     613             :     {
     614           8 :         DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     615             : 
     616             :         /*
     617             :          * Files are fsynced when they are closed, but we need to fsync the
     618             :          * directory entry here as well.
     619             :          */
     620           8 :         if (fsync_fname(dir_data->basedir, true) != 0)
     621             :         {
     622           0 :             wwmethod->lasterrno = errno;
     623           0 :             return false;
     624             :         }
     625             :     }
     626         226 :     return true;
     627             : }
     628             : 
     629             : static void
     630         226 : dir_free(WalWriteMethod *wwmethod)
     631             : {
     632         226 :     DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
     633             : 
     634         226 :     pg_free(dir_data->basedir);
     635         226 :     pg_free(wwmethod);
     636         226 : }
     637             : 
     638             : 
     639             : WalWriteMethod *
     640         232 : CreateWalDirectoryMethod(const char *basedir,
     641             :                          pg_compress_algorithm compression_algorithm,
     642             :                          int compression_level, bool sync)
     643             : {
     644             :     DirectoryMethodData *wwmethod;
     645             : 
     646         232 :     wwmethod = pg_malloc0(sizeof(DirectoryMethodData));
     647         232 :     *((const WalWriteMethodOps **) &wwmethod->base.ops) =
     648             :         &WalDirectoryMethodOps;
     649         232 :     wwmethod->base.compression_algorithm = compression_algorithm;
     650         232 :     wwmethod->base.compression_level = compression_level;
     651         232 :     wwmethod->base.sync = sync;
     652         232 :     clear_error(&wwmethod->base);
     653         232 :     wwmethod->basedir = pg_strdup(basedir);
     654             : 
     655         232 :     return &wwmethod->base;
     656             : }
     657             : 
     658             : 
     659             : /*-------------------------------------------------------------------------
     660             :  * WalTarMethod - write wal to a tar file containing pg_wal contents
     661             :  *-------------------------------------------------------------------------
     662             :  */
     663             : 
     664             : static Walfile *tar_open_for_write(WalWriteMethod *wwmethod,
     665             :                                    const char *pathname,
     666             :                                    const char *temp_suffix,
     667             :                                    size_t pad_to_size);
     668             : static int  tar_close(Walfile *f, WalCloseMethod method);
     669             : static bool tar_existsfile(WalWriteMethod *wwmethod, const char *pathname);
     670             : static ssize_t tar_get_file_size(WalWriteMethod *wwmethod,
     671             :                                  const char *pathname);
     672             : static char *tar_get_file_name(WalWriteMethod *wwmethod,
     673             :                                const char *pathname, const char *temp_suffix);
     674             : static ssize_t tar_write(Walfile *f, const void *buf, size_t count);
     675             : static int  tar_sync(Walfile *f);
     676             : static bool tar_finish(WalWriteMethod *wwmethod);
     677             : static void tar_free(WalWriteMethod *wwmethod);
     678             : 
     679             : const WalWriteMethodOps WalTarMethodOps = {
     680             :     .open_for_write = tar_open_for_write,
     681             :     .close = tar_close,
     682             :     .existsfile = tar_existsfile,
     683             :     .get_file_size = tar_get_file_size,
     684             :     .get_file_name = tar_get_file_name,
     685             :     .write = tar_write,
     686             :     .sync = tar_sync,
     687             :     .finish = tar_finish,
     688             :     .free = tar_free
     689             : };
     690             : 
     691             : typedef struct TarMethodFile
     692             : {
     693             :     Walfile     base;
     694             :     off_t       ofs_start;      /* Where does the *header* for this file start */
     695             :     char        header[TAR_BLOCK_SIZE];
     696             :     size_t      pad_to_size;
     697             : } TarMethodFile;
     698             : 
     699             : typedef struct TarMethodData
     700             : {
     701             :     WalWriteMethod base;
     702             :     char       *tarfilename;
     703             :     int         fd;
     704             :     TarMethodFile *currentfile;
     705             : #ifdef HAVE_LIBZ
     706             :     z_streamp   zp;
     707             :     void       *zlibOut;
     708             : #endif
     709             : } TarMethodData;
     710             : 
     711             : #ifdef HAVE_LIBZ
     712             : static bool
     713       11514 : tar_write_compressed_data(TarMethodData *tar_data, const void *buf, size_t count,
     714             :                           bool flush)
     715             : {
     716       11514 :     tar_data->zp->next_in = buf;
     717       11514 :     tar_data->zp->avail_in = count;
     718             : 
     719       23056 :     while (tar_data->zp->avail_in || flush)
     720             :     {
     721             :         int         r;
     722             : 
     723       11566 :         r = deflate(tar_data->zp, flush ? Z_FINISH : Z_NO_FLUSH);
     724       11566 :         if (r == Z_STREAM_ERROR)
     725             :         {
     726           0 :             tar_data->base.lasterrstring = _("could not compress data");
     727           0 :             return false;
     728             :         }
     729             : 
     730       11566 :         if (tar_data->zp->avail_out < ZLIB_OUT_SIZE)
     731             :         {
     732         124 :             size_t      len = ZLIB_OUT_SIZE - tar_data->zp->avail_out;
     733             : 
     734         124 :             errno = 0;
     735         124 :             if (write(tar_data->fd, tar_data->zlibOut, len) != len)
     736             :             {
     737             :                 /* If write didn't set errno, assume problem is no disk space */
     738           0 :                 tar_data->base.lasterrno = errno ? errno : ENOSPC;
     739           0 :                 return false;
     740             :             }
     741             : 
     742         124 :             tar_data->zp->next_out = tar_data->zlibOut;
     743         124 :             tar_data->zp->avail_out = ZLIB_OUT_SIZE;
     744             :         }
     745             : 
     746       11566 :         if (r == Z_STREAM_END)
     747          24 :             break;
     748             :     }
     749             : 
     750       11514 :     if (flush)
     751             :     {
     752             :         /* Reset the stream for writing */
     753          24 :         if (deflateReset(tar_data->zp) != Z_OK)
     754             :         {
     755           0 :             tar_data->base.lasterrstring = _("could not reset compression stream");
     756           0 :             return false;
     757             :         }
     758             :     }
     759             : 
     760       11514 :     return true;
     761             : }
     762             : #endif
     763             : 
     764             : static ssize_t
     765       28382 : tar_write(Walfile *f, const void *buf, size_t count)
     766             : {
     767       28382 :     TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
     768             :     ssize_t     r;
     769             : 
     770             :     Assert(f != NULL);
     771       28382 :     clear_error(f->wwmethod);
     772             : 
     773             :     /* Tarfile will always be positioned at the end */
     774       28382 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     775             :     {
     776       16898 :         errno = 0;
     777       16898 :         r = write(tar_data->fd, buf, count);
     778       16898 :         if (r != count)
     779             :         {
     780             :             /* If write didn't set errno, assume problem is no disk space */
     781           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
     782           0 :             return -1;
     783             :         }
     784       16898 :         f->currpos += r;
     785       16898 :         return r;
     786             :     }
     787             : #ifdef HAVE_LIBZ
     788       11484 :     else if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     789             :     {
     790       11484 :         if (!tar_write_compressed_data(tar_data, buf, count, false))
     791           0 :             return -1;
     792       11484 :         f->currpos += count;
     793       11484 :         return count;
     794             :     }
     795             : #endif
     796             :     else
     797             :     {
     798             :         /* Can't happen - compression enabled with no method set */
     799           0 :         f->wwmethod->lasterrno = ENOSYS;
     800           0 :         return -1;
     801             :     }
     802             : }
     803             : 
     804             : static bool
     805          14 : tar_write_padding_data(TarMethodFile *f, size_t bytes)
     806             : {
     807             :     PGAlignedXLogBlock zerobuf;
     808          14 :     size_t      bytesleft = bytes;
     809             : 
     810          14 :     memset(zerobuf.data, 0, XLOG_BLCKSZ);
     811       27822 :     while (bytesleft)
     812             :     {
     813       27808 :         size_t      bytestowrite = Min(bytesleft, XLOG_BLCKSZ);
     814       27808 :         ssize_t     r = tar_write(&f->base, zerobuf.data, bytestowrite);
     815             : 
     816       27808 :         if (r < 0)
     817           0 :             return false;
     818       27808 :         bytesleft -= r;
     819             :     }
     820             : 
     821          14 :     return true;
     822             : }
     823             : 
     824             : static char *
     825          44 : tar_get_file_name(WalWriteMethod *wwmethod, const char *pathname,
     826             :                   const char *temp_suffix)
     827             : {
     828          44 :     char       *filename = pg_malloc0(MAXPGPATH * sizeof(char));
     829             : 
     830          44 :     snprintf(filename, MAXPGPATH, "%s%s",
     831             :              pathname, temp_suffix ? temp_suffix : "");
     832             : 
     833          44 :     return filename;
     834             : }
     835             : 
     836             : static Walfile *
     837          16 : tar_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
     838             :                    const char *temp_suffix, size_t pad_to_size)
     839             : {
     840          16 :     TarMethodData *tar_data = (TarMethodData *) wwmethod;
     841             :     char       *tmppath;
     842             : 
     843          16 :     clear_error(wwmethod);
     844             : 
     845          16 :     if (tar_data->fd < 0)
     846             :     {
     847             :         /*
     848             :          * We open the tar file only when we first try to write to it.
     849             :          */
     850          14 :         tar_data->fd = open(tar_data->tarfilename,
     851             :                             O_WRONLY | O_CREAT | PG_BINARY,
     852             :                             pg_file_create_mode);
     853          14 :         if (tar_data->fd < 0)
     854             :         {
     855           0 :             wwmethod->lasterrno = errno;
     856           0 :             return NULL;
     857             :         }
     858             : 
     859             : #ifdef HAVE_LIBZ
     860          14 :         if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     861             :         {
     862           6 :             tar_data->zp = (z_streamp) pg_malloc(sizeof(z_stream));
     863           6 :             tar_data->zp->zalloc = Z_NULL;
     864           6 :             tar_data->zp->zfree = Z_NULL;
     865           6 :             tar_data->zp->opaque = Z_NULL;
     866           6 :             tar_data->zp->next_out = tar_data->zlibOut;
     867           6 :             tar_data->zp->avail_out = ZLIB_OUT_SIZE;
     868             : 
     869             :             /*
     870             :              * Initialize deflation library. Adding the magic value 16 to the
     871             :              * default 15 for the windowBits parameter makes the output be
     872             :              * gzip instead of zlib.
     873             :              */
     874           6 :             if (deflateInit2(tar_data->zp, wwmethod->compression_level,
     875             :                              Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
     876             :             {
     877           0 :                 pg_free(tar_data->zp);
     878           0 :                 tar_data->zp = NULL;
     879           0 :                 wwmethod->lasterrstring =
     880           0 :                     _("could not initialize compression library");
     881           0 :                 return NULL;
     882             :             }
     883             :         }
     884             : #endif
     885             : 
     886             :         /* There's no tar header itself, the file starts with regular files */
     887             :     }
     888             : 
     889          16 :     if (tar_data->currentfile != NULL)
     890             :     {
     891           0 :         wwmethod->lasterrstring =
     892           0 :             _("implementation error: tar files can't have more than one open file");
     893           0 :         return NULL;
     894             :     }
     895             : 
     896          16 :     tar_data->currentfile = pg_malloc0(sizeof(TarMethodFile));
     897          16 :     tar_data->currentfile->base.wwmethod = wwmethod;
     898             : 
     899          16 :     tmppath = tar_get_file_name(wwmethod, pathname, temp_suffix);
     900             : 
     901             :     /* Create a header with size set to 0 - we will fill out the size on close */
     902          16 :     if (tarCreateHeader(tar_data->currentfile->header, tmppath, NULL, 0, S_IRUSR | S_IWUSR, 0, 0, time(NULL)) != TAR_OK)
     903             :     {
     904           0 :         pg_free(tar_data->currentfile);
     905           0 :         pg_free(tmppath);
     906           0 :         tar_data->currentfile = NULL;
     907           0 :         wwmethod->lasterrstring = _("could not create tar header");
     908           0 :         return NULL;
     909             :     }
     910             : 
     911          16 :     pg_free(tmppath);
     912             : 
     913             : #ifdef HAVE_LIBZ
     914          16 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     915             :     {
     916             :         /* Flush existing data */
     917           6 :         if (!tar_write_compressed_data(tar_data, NULL, 0, true))
     918           0 :             return NULL;
     919             : 
     920             :         /* Turn off compression for header */
     921           6 :         if (deflateParams(tar_data->zp, 0, Z_DEFAULT_STRATEGY) != Z_OK)
     922             :         {
     923           0 :             wwmethod->lasterrstring =
     924           0 :                 _("could not change compression parameters");
     925           0 :             return NULL;
     926             :         }
     927             :     }
     928             : #endif
     929             : 
     930          16 :     tar_data->currentfile->ofs_start = lseek(tar_data->fd, 0, SEEK_CUR);
     931          16 :     if (tar_data->currentfile->ofs_start == -1)
     932             :     {
     933           0 :         wwmethod->lasterrno = errno;
     934           0 :         pg_free(tar_data->currentfile);
     935           0 :         tar_data->currentfile = NULL;
     936           0 :         return NULL;
     937             :     }
     938          16 :     tar_data->currentfile->base.currpos = 0;
     939             : 
     940          16 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     941             :     {
     942          10 :         errno = 0;
     943          10 :         if (write(tar_data->fd, tar_data->currentfile->header,
     944             :                   TAR_BLOCK_SIZE) != TAR_BLOCK_SIZE)
     945             :         {
     946             :             /* If write didn't set errno, assume problem is no disk space */
     947           0 :             wwmethod->lasterrno = errno ? errno : ENOSPC;
     948           0 :             pg_free(tar_data->currentfile);
     949           0 :             tar_data->currentfile = NULL;
     950           0 :             return NULL;
     951             :         }
     952             :     }
     953             : #ifdef HAVE_LIBZ
     954           6 :     else if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
     955             :     {
     956             :         /* Write header through the zlib APIs but with no compression */
     957           6 :         if (!tar_write_compressed_data(tar_data, tar_data->currentfile->header,
     958             :                                        TAR_BLOCK_SIZE, true))
     959           0 :             return NULL;
     960             : 
     961             :         /* Re-enable compression for the rest of the file */
     962           6 :         if (deflateParams(tar_data->zp, wwmethod->compression_level,
     963             :                           Z_DEFAULT_STRATEGY) != Z_OK)
     964             :         {
     965           0 :             wwmethod->lasterrstring = _("could not change compression parameters");
     966           0 :             return NULL;
     967             :         }
     968             :     }
     969             : #endif
     970             :     else
     971             :     {
     972             :         /* not reachable */
     973             :         Assert(false);
     974             :     }
     975             : 
     976          16 :     tar_data->currentfile->base.pathname = pg_strdup(pathname);
     977             : 
     978             :     /*
     979             :      * Uncompressed files are padded on creation, but for compression we can't
     980             :      * do that
     981             :      */
     982          16 :     if (pad_to_size)
     983             :     {
     984          14 :         tar_data->currentfile->pad_to_size = pad_to_size;
     985          14 :         if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
     986             :         {
     987             :             /* Uncompressed, so pad now */
     988           8 :             if (!tar_write_padding_data(tar_data->currentfile, pad_to_size))
     989           0 :                 return NULL;
     990             :             /* Seek back to start */
     991          16 :             if (lseek(tar_data->fd,
     992           8 :                       tar_data->currentfile->ofs_start + TAR_BLOCK_SIZE,
     993           8 :                       SEEK_SET) != tar_data->currentfile->ofs_start + TAR_BLOCK_SIZE)
     994             :             {
     995           0 :                 wwmethod->lasterrno = errno;
     996           0 :                 return NULL;
     997             :             }
     998             : 
     999           8 :             tar_data->currentfile->base.currpos = 0;
    1000             :         }
    1001             :     }
    1002             : 
    1003          16 :     return &tar_data->currentfile->base;
    1004             : }
    1005             : 
    1006             : static ssize_t
    1007           0 : tar_get_file_size(WalWriteMethod *wwmethod, const char *pathname)
    1008             : {
    1009           0 :     clear_error(wwmethod);
    1010             : 
    1011             :     /* Currently not used, so not supported */
    1012           0 :     wwmethod->lasterrno = ENOSYS;
    1013           0 :     return -1;
    1014             : }
    1015             : 
    1016             : static int
    1017          16 : tar_sync(Walfile *f)
    1018             : {
    1019          16 :     TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
    1020             :     int         r;
    1021             : 
    1022             :     Assert(f != NULL);
    1023          16 :     clear_error(f->wwmethod);
    1024             : 
    1025          16 :     if (!f->wwmethod->sync)
    1026          16 :         return 0;
    1027             : 
    1028             :     /*
    1029             :      * Always sync the whole tarfile, because that's all we can do. This makes
    1030             :      * no sense on compressed files, so just ignore those.
    1031             :      */
    1032           0 :     if (f->wwmethod->compression_algorithm != PG_COMPRESSION_NONE)
    1033           0 :         return 0;
    1034             : 
    1035           0 :     r = fsync(tar_data->fd);
    1036           0 :     if (r < 0)
    1037           0 :         f->wwmethod->lasterrno = errno;
    1038           0 :     return r;
    1039             : }
    1040             : 
    1041             : static int
    1042          16 : tar_close(Walfile *f, WalCloseMethod method)
    1043             : {
    1044             :     ssize_t     filesize;
    1045             :     int         padding;
    1046          16 :     TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
    1047          16 :     TarMethodFile *tf = (TarMethodFile *) f;
    1048             : 
    1049             :     Assert(f != NULL);
    1050          16 :     clear_error(f->wwmethod);
    1051             : 
    1052          16 :     if (method == CLOSE_UNLINK)
    1053             :     {
    1054           0 :         if (f->wwmethod->compression_algorithm != PG_COMPRESSION_NONE)
    1055             :         {
    1056           0 :             f->wwmethod->lasterrstring = _("unlink not supported with compression");
    1057           0 :             return -1;
    1058             :         }
    1059             : 
    1060             :         /*
    1061             :          * Unlink the file that we just wrote to the tar. We do this by
    1062             :          * truncating it to the start of the header. This is safe as we only
    1063             :          * allow writing of the very last file.
    1064             :          */
    1065           0 :         if (ftruncate(tar_data->fd, tf->ofs_start) != 0)
    1066             :         {
    1067           0 :             f->wwmethod->lasterrno = errno;
    1068           0 :             return -1;
    1069             :         }
    1070             : 
    1071           0 :         pg_free(tf->base.pathname);
    1072           0 :         pg_free(tf);
    1073           0 :         tar_data->currentfile = NULL;
    1074             : 
    1075           0 :         return 0;
    1076             :     }
    1077             : 
    1078             :     /*
    1079             :      * Pad the file itself with zeroes if necessary. Note that this is
    1080             :      * different from the tar format padding -- this is the padding we asked
    1081             :      * for when the file was opened.
    1082             :      */
    1083          16 :     if (tf->pad_to_size)
    1084             :     {
    1085          14 :         if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1086             :         {
    1087             :             /*
    1088             :              * A compressed tarfile is padded on close since we cannot know
    1089             :              * the size of the compressed output until the end.
    1090             :              */
    1091           6 :             size_t      sizeleft = tf->pad_to_size - tf->base.currpos;
    1092             : 
    1093           6 :             if (sizeleft)
    1094             :             {
    1095           6 :                 if (!tar_write_padding_data(tf, sizeleft))
    1096           0 :                     return -1;
    1097             :             }
    1098             :         }
    1099             :         else
    1100             :         {
    1101             :             /*
    1102             :              * An uncompressed tarfile was padded on creation, so just adjust
    1103             :              * the current position as if we seeked to the end.
    1104             :              */
    1105           8 :             tf->base.currpos = tf->pad_to_size;
    1106             :         }
    1107             :     }
    1108             : 
    1109             :     /*
    1110             :      * Get the size of the file, and pad out to a multiple of the tar block
    1111             :      * size.
    1112             :      */
    1113          16 :     filesize = f->currpos;
    1114          16 :     padding = tarPaddingBytesRequired(filesize);
    1115          16 :     if (padding)
    1116             :     {
    1117           0 :         char        zerobuf[TAR_BLOCK_SIZE] = {0};
    1118             : 
    1119           0 :         if (tar_write(f, zerobuf, padding) != padding)
    1120           0 :             return -1;
    1121             :     }
    1122             : 
    1123             : 
    1124             : #ifdef HAVE_LIBZ
    1125          16 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1126             :     {
    1127             :         /* Flush the current buffer */
    1128           6 :         if (!tar_write_compressed_data(tar_data, NULL, 0, true))
    1129           0 :             return -1;
    1130             :     }
    1131             : #endif
    1132             : 
    1133             :     /*
    1134             :      * Now go back and update the header with the correct filesize and
    1135             :      * possibly also renaming the file. We overwrite the entire current header
    1136             :      * when done, including the checksum.
    1137             :      */
    1138          16 :     print_tar_number(&(tf->header[TAR_OFFSET_SIZE]), 12, filesize);
    1139             : 
    1140          16 :     if (method == CLOSE_NORMAL)
    1141             : 
    1142             :         /*
    1143             :          * We overwrite it with what it was before if we have no tempname,
    1144             :          * since we're going to write the buffer anyway.
    1145             :          */
    1146          16 :         strlcpy(&(tf->header[TAR_OFFSET_NAME]), tf->base.pathname, 100);
    1147             : 
    1148          16 :     print_tar_number(&(tf->header[TAR_OFFSET_CHECKSUM]), 8,
    1149          16 :                      tarChecksum(((TarMethodFile *) f)->header));
    1150          16 :     if (lseek(tar_data->fd, tf->ofs_start, SEEK_SET) != ((TarMethodFile *) f)->ofs_start)
    1151             :     {
    1152           0 :         f->wwmethod->lasterrno = errno;
    1153           0 :         return -1;
    1154             :     }
    1155          16 :     if (f->wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
    1156             :     {
    1157          10 :         errno = 0;
    1158          10 :         if (write(tar_data->fd, tf->header, TAR_BLOCK_SIZE) != TAR_BLOCK_SIZE)
    1159             :         {
    1160             :             /* If write didn't set errno, assume problem is no disk space */
    1161           0 :             f->wwmethod->lasterrno = errno ? errno : ENOSPC;
    1162           0 :             return -1;
    1163             :         }
    1164             :     }
    1165             : #ifdef HAVE_LIBZ
    1166           6 :     else if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1167             :     {
    1168             :         /* Turn off compression */
    1169           6 :         if (deflateParams(tar_data->zp, 0, Z_DEFAULT_STRATEGY) != Z_OK)
    1170             :         {
    1171           0 :             f->wwmethod->lasterrstring = _("could not change compression parameters");
    1172           0 :             return -1;
    1173             :         }
    1174             : 
    1175             :         /* Overwrite the header, assuming the size will be the same */
    1176           6 :         if (!tar_write_compressed_data(tar_data, tar_data->currentfile->header,
    1177             :                                        TAR_BLOCK_SIZE, true))
    1178           0 :             return -1;
    1179             : 
    1180             :         /* Turn compression back on */
    1181           6 :         if (deflateParams(tar_data->zp, f->wwmethod->compression_level,
    1182             :                           Z_DEFAULT_STRATEGY) != Z_OK)
    1183             :         {
    1184           0 :             f->wwmethod->lasterrstring = _("could not change compression parameters");
    1185           0 :             return -1;
    1186             :         }
    1187             :     }
    1188             : #endif
    1189             :     else
    1190             :     {
    1191             :         /* not reachable */
    1192             :         Assert(false);
    1193             :     }
    1194             : 
    1195             :     /* Move file pointer back down to end, so we can write the next file */
    1196          16 :     if (lseek(tar_data->fd, 0, SEEK_END) < 0)
    1197             :     {
    1198           0 :         f->wwmethod->lasterrno = errno;
    1199           0 :         return -1;
    1200             :     }
    1201             : 
    1202             :     /* Always fsync on close, so the padding gets fsynced */
    1203          16 :     if (tar_sync(f) < 0)
    1204             :     {
    1205             :         /* XXX this seems pretty bogus; why is only this case fatal? */
    1206           0 :         pg_fatal("could not fsync file \"%s\": %s",
    1207             :                  tf->base.pathname, GetLastWalMethodError(f->wwmethod));
    1208             :     }
    1209             : 
    1210             :     /* Clean up and done */
    1211          16 :     pg_free(tf->base.pathname);
    1212          16 :     pg_free(tf);
    1213          16 :     tar_data->currentfile = NULL;
    1214             : 
    1215          16 :     return 0;
    1216             : }
    1217             : 
    1218             : static bool
    1219           8 : tar_existsfile(WalWriteMethod *wwmethod, const char *pathname)
    1220             : {
    1221           8 :     clear_error(wwmethod);
    1222             :     /* We only deal with new tarfiles, so nothing externally created exists */
    1223           8 :     return false;
    1224             : }
    1225             : 
    1226             : static bool
    1227          14 : tar_finish(WalWriteMethod *wwmethod)
    1228             : {
    1229          14 :     TarMethodData *tar_data = (TarMethodData *) wwmethod;
    1230          14 :     char        zerobuf[1024] = {0};
    1231             : 
    1232          14 :     clear_error(wwmethod);
    1233             : 
    1234          14 :     if (tar_data->currentfile)
    1235             :     {
    1236           0 :         if (tar_close(&tar_data->currentfile->base, CLOSE_NORMAL) != 0)
    1237           0 :             return false;
    1238             :     }
    1239             : 
    1240             :     /* A tarfile always ends with two empty blocks */
    1241          14 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
    1242             :     {
    1243           8 :         errno = 0;
    1244           8 :         if (write(tar_data->fd, zerobuf, sizeof(zerobuf)) != sizeof(zerobuf))
    1245             :         {
    1246             :             /* If write didn't set errno, assume problem is no disk space */
    1247           0 :             wwmethod->lasterrno = errno ? errno : ENOSPC;
    1248           0 :             return false;
    1249             :         }
    1250             :     }
    1251             : #ifdef HAVE_LIBZ
    1252           6 :     else if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1253             :     {
    1254           6 :         if (!tar_write_compressed_data(tar_data, zerobuf, sizeof(zerobuf),
    1255             :                                        false))
    1256           0 :             return false;
    1257             : 
    1258             :         /* Also flush all data to make sure the gzip stream is finished */
    1259           6 :         tar_data->zp->next_in = NULL;
    1260           6 :         tar_data->zp->avail_in = 0;
    1261             :         while (true)
    1262           0 :         {
    1263             :             int         r;
    1264             : 
    1265           6 :             r = deflate(tar_data->zp, Z_FINISH);
    1266             : 
    1267           6 :             if (r == Z_STREAM_ERROR)
    1268             :             {
    1269           0 :                 wwmethod->lasterrstring = _("could not compress data");
    1270           0 :                 return false;
    1271             :             }
    1272           6 :             if (tar_data->zp->avail_out < ZLIB_OUT_SIZE)
    1273             :             {
    1274           6 :                 size_t      len = ZLIB_OUT_SIZE - tar_data->zp->avail_out;
    1275             : 
    1276           6 :                 errno = 0;
    1277           6 :                 if (write(tar_data->fd, tar_data->zlibOut, len) != len)
    1278             :                 {
    1279             :                     /*
    1280             :                      * If write didn't set errno, assume problem is no disk
    1281             :                      * space.
    1282             :                      */
    1283           0 :                     wwmethod->lasterrno = errno ? errno : ENOSPC;
    1284           0 :                     return false;
    1285             :                 }
    1286             :             }
    1287           6 :             if (r == Z_STREAM_END)
    1288           6 :                 break;
    1289             :         }
    1290             : 
    1291           6 :         if (deflateEnd(tar_data->zp) != Z_OK)
    1292             :         {
    1293           0 :             wwmethod->lasterrstring = _("could not close compression stream");
    1294           0 :             return false;
    1295             :         }
    1296             :     }
    1297             : #endif
    1298             :     else
    1299             :     {
    1300             :         /* not reachable */
    1301             :         Assert(false);
    1302             :     }
    1303             : 
    1304             :     /* sync the empty blocks as well, since they're after the last file */
    1305          14 :     if (wwmethod->sync)
    1306             :     {
    1307           0 :         if (fsync(tar_data->fd) != 0)
    1308             :         {
    1309           0 :             wwmethod->lasterrno = errno;
    1310           0 :             return false;
    1311             :         }
    1312             :     }
    1313             : 
    1314          14 :     if (close(tar_data->fd) != 0)
    1315             :     {
    1316           0 :         wwmethod->lasterrno = errno;
    1317           0 :         return false;
    1318             :     }
    1319             : 
    1320          14 :     tar_data->fd = -1;
    1321             : 
    1322          14 :     if (wwmethod->sync)
    1323             :     {
    1324           0 :         if (fsync_fname(tar_data->tarfilename, false) != 0 ||
    1325           0 :             fsync_parent_path(tar_data->tarfilename) != 0)
    1326             :         {
    1327           0 :             wwmethod->lasterrno = errno;
    1328           0 :             return false;
    1329             :         }
    1330             :     }
    1331             : 
    1332          14 :     return true;
    1333             : }
    1334             : 
    1335             : static void
    1336          14 : tar_free(WalWriteMethod *wwmethod)
    1337             : {
    1338          14 :     TarMethodData *tar_data = (TarMethodData *) wwmethod;
    1339             : 
    1340          14 :     pg_free(tar_data->tarfilename);
    1341             : #ifdef HAVE_LIBZ
    1342          14 :     if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
    1343           6 :         pg_free(tar_data->zlibOut);
    1344             : #endif
    1345          14 :     pg_free(wwmethod);
    1346          14 : }
    1347             : 
    1348             : /*
    1349             :  * The argument compression_algorithm is currently ignored. It is in place for
    1350             :  * symmetry with CreateWalDirectoryMethod which uses it for distinguishing
    1351             :  * between the different compression methods. CreateWalTarMethod and its family
    1352             :  * of functions handle only zlib compression.
    1353             :  */
    1354             : WalWriteMethod *
    1355          14 : CreateWalTarMethod(const char *tarbase,
    1356             :                    pg_compress_algorithm compression_algorithm,
    1357             :                    int compression_level, bool sync)
    1358             : {
    1359             :     TarMethodData *wwmethod;
    1360          14 :     const char *suffix = (compression_algorithm == PG_COMPRESSION_GZIP) ?
    1361          14 :         ".tar.gz" : ".tar";
    1362             : 
    1363          14 :     wwmethod = pg_malloc0(sizeof(TarMethodData));
    1364          14 :     *((const WalWriteMethodOps **) &wwmethod->base.ops) =
    1365             :         &WalTarMethodOps;
    1366          14 :     wwmethod->base.compression_algorithm = compression_algorithm;
    1367          14 :     wwmethod->base.compression_level = compression_level;
    1368          14 :     wwmethod->base.sync = sync;
    1369          14 :     clear_error(&wwmethod->base);
    1370             : 
    1371          14 :     wwmethod->tarfilename = pg_malloc0(strlen(tarbase) + strlen(suffix) + 1);
    1372          14 :     sprintf(wwmethod->tarfilename, "%s%s", tarbase, suffix);
    1373          14 :     wwmethod->fd = -1;
    1374             : #ifdef HAVE_LIBZ
    1375          14 :     if (compression_algorithm == PG_COMPRESSION_GZIP)
    1376           6 :         wwmethod->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
    1377             : #endif
    1378             : 
    1379          14 :     return &wwmethod->base;
    1380             : }
    1381             : 
    1382             : const char *
    1383           0 : GetLastWalMethodError(WalWriteMethod *wwmethod)
    1384             : {
    1385           0 :     if (wwmethod->lasterrstring)
    1386           0 :         return wwmethod->lasterrstring;
    1387           0 :     return strerror(wwmethod->lasterrno);
    1388             : }

Generated by: LCOV version 1.14