LCOV - code coverage report
Current view: top level - contrib/basic_archive - basic_archive.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 29 79 36.7 %
Date: 2024-11-21 08:14:44 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * basic_archive.c
       4             :  *
       5             :  * This file demonstrates a basic archive library implementation that is
       6             :  * roughly equivalent to the following shell command:
       7             :  *
       8             :  *      test ! -f /path/to/dest && cp /path/to/src /path/to/dest
       9             :  *
      10             :  * One notable difference between this module and the shell command above
      11             :  * is that this module first copies the file to a temporary destination,
      12             :  * syncs it to disk, and then durably moves it to the final destination.
      13             :  *
      14             :  * Another notable difference is that if /path/to/dest already exists
      15             :  * but has contents identical to /path/to/src, archiving will succeed,
      16             :  * whereas the command shown above would fail. This prevents problems if
      17             :  * a file is successfully archived and then the system crashes before
      18             :  * a durable record of the success has been made.
      19             :  *
      20             :  * Copyright (c) 2022-2024, PostgreSQL Global Development Group
      21             :  *
      22             :  * IDENTIFICATION
      23             :  *    contrib/basic_archive/basic_archive.c
      24             :  *
      25             :  *-------------------------------------------------------------------------
      26             :  */
      27             : #include "postgres.h"
      28             : 
      29             : #include <sys/stat.h>
      30             : #include <sys/time.h>
      31             : #include <unistd.h>
      32             : 
      33             : #include "archive/archive_module.h"
      34             : #include "common/int.h"
      35             : #include "miscadmin.h"
      36             : #include "storage/copydir.h"
      37             : #include "storage/fd.h"
      38             : #include "utils/guc.h"
      39             : 
      40           2 : PG_MODULE_MAGIC;
      41             : 
      42             : static char *archive_directory = NULL;
      43             : 
      44             : static bool basic_archive_configured(ArchiveModuleState *state);
      45             : static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
      46             : static bool check_archive_directory(char **newval, void **extra, GucSource source);
      47             : static bool compare_files(const char *file1, const char *file2);
      48             : 
      49             : static const ArchiveModuleCallbacks basic_archive_callbacks = {
      50             :     .startup_cb = NULL,
      51             :     .check_configured_cb = basic_archive_configured,
      52             :     .archive_file_cb = basic_archive_file,
      53             :     .shutdown_cb = NULL
      54             : };
      55             : 
      56             : /*
      57             :  * _PG_init
      58             :  *
      59             :  * Defines the module's GUC.
      60             :  */
      61             : void
      62           2 : _PG_init(void)
      63             : {
      64           2 :     DefineCustomStringVariable("basic_archive.archive_directory",
      65             :                                gettext_noop("Archive file destination directory."),
      66             :                                NULL,
      67             :                                &archive_directory,
      68             :                                "",
      69             :                                PGC_SIGHUP,
      70             :                                0,
      71             :                                check_archive_directory, NULL, NULL);
      72             : 
      73           2 :     MarkGUCPrefixReserved("basic_archive");
      74           2 : }
      75             : 
      76             : /*
      77             :  * _PG_archive_module_init
      78             :  *
      79             :  * Returns the module's archiving callbacks.
      80             :  */
      81             : const ArchiveModuleCallbacks *
      82           2 : _PG_archive_module_init(void)
      83             : {
      84           2 :     return &basic_archive_callbacks;
      85             : }
      86             : 
      87             : /*
      88             :  * check_archive_directory
      89             :  *
      90             :  * Checks that the provided archive directory exists.
      91             :  */
      92             : static bool
      93           4 : check_archive_directory(char **newval, void **extra, GucSource source)
      94             : {
      95             :     struct stat st;
      96             : 
      97             :     /*
      98             :      * The default value is an empty string, so we have to accept that value.
      99             :      * Our check_configured callback also checks for this and prevents
     100             :      * archiving from proceeding if it is still empty.
     101             :      */
     102           4 :     if (*newval == NULL || *newval[0] == '\0')
     103           2 :         return true;
     104             : 
     105             :     /*
     106             :      * Make sure the file paths won't be too long.  The docs indicate that the
     107             :      * file names to be archived can be up to 64 characters long.
     108             :      */
     109           2 :     if (strlen(*newval) + 64 + 2 >= MAXPGPATH)
     110             :     {
     111           0 :         GUC_check_errdetail("Archive directory too long.");
     112           0 :         return false;
     113             :     }
     114             : 
     115             :     /*
     116             :      * Do a basic sanity check that the specified archive directory exists. It
     117             :      * could be removed at some point in the future, so we still need to be
     118             :      * prepared for it not to exist in the actual archiving logic.
     119             :      */
     120           2 :     if (stat(*newval, &st) != 0 || !S_ISDIR(st.st_mode))
     121             :     {
     122           0 :         GUC_check_errdetail("Specified archive directory does not exist.");
     123           0 :         return false;
     124             :     }
     125             : 
     126           2 :     return true;
     127             : }
     128             : 
     129             : /*
     130             :  * basic_archive_configured
     131             :  *
     132             :  * Checks that archive_directory is not blank.
     133             :  */
     134             : static bool
     135           4 : basic_archive_configured(ArchiveModuleState *state)
     136             : {
     137           4 :     if (archive_directory != NULL && archive_directory[0] != '\0')
     138           4 :         return true;
     139             : 
     140           0 :     arch_module_check_errdetail("%s is not set.",
     141             :                                 "basic_archive.archive_directory");
     142           0 :     return false;
     143             : }
     144             : 
     145             : /*
     146             :  * basic_archive_file
     147             :  *
     148             :  * Archives one file.
     149             :  */
     150             : static bool
     151           4 : basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
     152             : {
     153             :     char        destination[MAXPGPATH];
     154             :     char        temp[MAXPGPATH + 256];
     155             :     struct stat st;
     156             :     struct timeval tv;
     157             :     uint64      epoch;          /* milliseconds */
     158             : 
     159           4 :     ereport(DEBUG3,
     160             :             (errmsg("archiving \"%s\" via basic_archive", file)));
     161             : 
     162           4 :     snprintf(destination, MAXPGPATH, "%s/%s", archive_directory, file);
     163             : 
     164             :     /*
     165             :      * First, check if the file has already been archived.  If it already
     166             :      * exists and has the same contents as the file we're trying to archive,
     167             :      * we can return success (after ensuring the file is persisted to disk).
     168             :      * This scenario is possible if the server crashed after archiving the
     169             :      * file but before renaming its .ready file to .done.
     170             :      *
     171             :      * If the archive file already exists but has different contents,
     172             :      * something might be wrong, so we just fail.
     173             :      */
     174           4 :     if (stat(destination, &st) == 0)
     175             :     {
     176           0 :         if (compare_files(path, destination))
     177             :         {
     178           0 :             ereport(DEBUG3,
     179             :                     (errmsg("archive file \"%s\" already exists with identical contents",
     180             :                             destination)));
     181             : 
     182           0 :             fsync_fname(destination, false);
     183           0 :             fsync_fname(archive_directory, true);
     184             : 
     185           0 :             return true;
     186             :         }
     187             : 
     188           0 :         ereport(ERROR,
     189             :                 (errmsg("archive file \"%s\" already exists", destination)));
     190             :     }
     191           4 :     else if (errno != ENOENT)
     192           0 :         ereport(ERROR,
     193             :                 (errcode_for_file_access(),
     194             :                  errmsg("could not stat file \"%s\": %m", destination)));
     195             : 
     196             :     /*
     197             :      * Pick a sufficiently unique name for the temporary file so that a
     198             :      * collision is unlikely.  This helps avoid problems in case a temporary
     199             :      * file was left around after a crash or another server happens to be
     200             :      * archiving to the same directory.
     201             :      */
     202           4 :     gettimeofday(&tv, NULL);
     203           8 :     if (pg_mul_u64_overflow((uint64) 1000, (uint64) tv.tv_sec, &epoch) ||
     204           4 :         pg_add_u64_overflow(epoch, (uint64) (tv.tv_usec / 1000), &epoch))
     205           0 :         elog(ERROR, "could not generate temporary file name for archiving");
     206             : 
     207           4 :     snprintf(temp, sizeof(temp), "%s/%s.%s.%d." UINT64_FORMAT,
     208             :              archive_directory, "archtemp", file, MyProcPid, epoch);
     209             : 
     210             :     /*
     211             :      * Copy the file to its temporary destination.  Note that this will fail
     212             :      * if temp already exists.
     213             :      */
     214           4 :     copy_file(path, temp);
     215             : 
     216             :     /*
     217             :      * Sync the temporary file to disk and move it to its final destination.
     218             :      * Note that this will overwrite any existing file, but this is only
     219             :      * possible if someone else created the file since the stat() above.
     220             :      */
     221           4 :     (void) durable_rename(temp, destination, ERROR);
     222             : 
     223           4 :     ereport(DEBUG1,
     224             :             (errmsg("archived \"%s\" via basic_archive", file)));
     225             : 
     226           4 :     return true;
     227             : }
     228             : 
     229             : /*
     230             :  * compare_files
     231             :  *
     232             :  * Returns whether the contents of the files are the same.
     233             :  */
     234             : static bool
     235           0 : compare_files(const char *file1, const char *file2)
     236             : {
     237             : #define CMP_BUF_SIZE (4096)
     238             :     char        buf1[CMP_BUF_SIZE];
     239             :     char        buf2[CMP_BUF_SIZE];
     240             :     int         fd1;
     241             :     int         fd2;
     242           0 :     bool        ret = true;
     243             : 
     244           0 :     fd1 = OpenTransientFile(file1, O_RDONLY | PG_BINARY);
     245           0 :     if (fd1 < 0)
     246           0 :         ereport(ERROR,
     247             :                 (errcode_for_file_access(),
     248             :                  errmsg("could not open file \"%s\": %m", file1)));
     249             : 
     250           0 :     fd2 = OpenTransientFile(file2, O_RDONLY | PG_BINARY);
     251           0 :     if (fd2 < 0)
     252           0 :         ereport(ERROR,
     253             :                 (errcode_for_file_access(),
     254             :                  errmsg("could not open file \"%s\": %m", file2)));
     255             : 
     256             :     for (;;)
     257           0 :     {
     258           0 :         int         nbytes = 0;
     259           0 :         int         buf1_len = 0;
     260           0 :         int         buf2_len = 0;
     261             : 
     262           0 :         while (buf1_len < CMP_BUF_SIZE)
     263             :         {
     264           0 :             nbytes = read(fd1, buf1 + buf1_len, CMP_BUF_SIZE - buf1_len);
     265           0 :             if (nbytes < 0)
     266           0 :                 ereport(ERROR,
     267             :                         (errcode_for_file_access(),
     268             :                          errmsg("could not read file \"%s\": %m", file1)));
     269           0 :             else if (nbytes == 0)
     270           0 :                 break;
     271             : 
     272           0 :             buf1_len += nbytes;
     273             :         }
     274             : 
     275           0 :         while (buf2_len < CMP_BUF_SIZE)
     276             :         {
     277           0 :             nbytes = read(fd2, buf2 + buf2_len, CMP_BUF_SIZE - buf2_len);
     278           0 :             if (nbytes < 0)
     279           0 :                 ereport(ERROR,
     280             :                         (errcode_for_file_access(),
     281             :                          errmsg("could not read file \"%s\": %m", file2)));
     282           0 :             else if (nbytes == 0)
     283           0 :                 break;
     284             : 
     285           0 :             buf2_len += nbytes;
     286             :         }
     287             : 
     288           0 :         if (buf1_len != buf2_len || memcmp(buf1, buf2, buf1_len) != 0)
     289             :         {
     290           0 :             ret = false;
     291           0 :             break;
     292             :         }
     293           0 :         else if (buf1_len == 0)
     294           0 :             break;
     295             :     }
     296             : 
     297           0 :     if (CloseTransientFile(fd1) != 0)
     298           0 :         ereport(ERROR,
     299             :                 (errcode_for_file_access(),
     300             :                  errmsg("could not close file \"%s\": %m", file1)));
     301             : 
     302           0 :     if (CloseTransientFile(fd2) != 0)
     303           0 :         ereport(ERROR,
     304             :                 (errcode_for_file_access(),
     305             :                  errmsg("could not close file \"%s\": %m", file2)));
     306             : 
     307           0 :     return ret;
     308             : }

Generated by: LCOV version 1.14