LCOV - differential code coverage report
Current view: top level - src/bin/pg_archivecleanup - pg_archivecleanup.c (source / functions) Coverage Total Hit UNC UBC GIC GNC CBC DUB DCB
Current: Differential Code Coverage 16@8cea358b128 vs 17@8cea358b128 Lines: 97.0 % 133 129 4 31 98 4 23
Current Date: 2024-04-14 14:21:10 Functions: 100.0 % 6 6 3 3
Baseline: 16@8cea358b128 Branches: 84.9 % 73 62 6 5 1 22 39
Baseline Date: 2024-04-14 14:21:09 Line coverage date bins:
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed (60,120] days: 100.0 % 1 1 1
(240..) days: 97.0 % 132 128 4 30 98
Function coverage date bins:
(240..) days: 100.0 % 6 6 3 3
Branch coverage date bins:
(240..) days: 84.9 % 73 62 6 5 1 22 39

 Age         Owner                    Branch data    TLA  Line data    Source code
                                  1                 :                : /*
                                  2                 :                :  * pg_archivecleanup.c
                                  3                 :                :  *
                                  4                 :                :  * To be used as archive_cleanup_command to clean an archive when using
                                  5                 :                :  * standby mode.
                                  6                 :                :  *
                                  7                 :                :  * src/bin/pg_archivecleanup/pg_archivecleanup.c
                                  8                 :                :  */
                                  9                 :                : #include "postgres_fe.h"
                                 10                 :                : 
                                 11                 :                : #include <ctype.h>
                                 12                 :                : #include <dirent.h>
                                 13                 :                : #include <sys/stat.h>
                                 14                 :                : #include <fcntl.h>
                                 15                 :                : #include <signal.h>
                                 16                 :                : #include <sys/time.h>
                                 17                 :                : 
                                 18                 :                : #include "access/xlog_internal.h"
                                 19                 :                : #include "common/logging.h"
                                 20                 :                : #include "getopt_long.h"
                                 21                 :                : 
                                 22                 :                : const char *progname;
                                 23                 :                : 
                                 24                 :                : /* Options and defaults */
                                 25                 :                : bool        dryrun = false;     /* are we performing a dry-run operation? */
                                 26                 :                : bool        cleanBackupHistory = false; /* remove files including backup
                                 27                 :                :                                          * history files */
                                 28                 :                : char       *additional_ext = NULL;  /* Extension to remove from filenames */
                                 29                 :                : 
                                 30                 :                : char       *archiveLocation;    /* where to find the archive? */
                                 31                 :                : char       *restartWALFileName; /* the file from which we can restart restore */
                                 32                 :                : char        exclusiveCleanupFileName[MAXFNAMELEN];  /* the oldest file we want
                                 33                 :                :                                                      * to remain in archive */
                                 34                 :                : 
                                 35                 :                : 
                                 36                 :                : /* =====================================================================
                                 37                 :                :  *
                                 38                 :                :  *        Customizable section
                                 39                 :                :  *
                                 40                 :                :  * =====================================================================
                                 41                 :                :  *
                                 42                 :                :  *  Currently, this section assumes that the Archive is a locally
                                 43                 :                :  *  accessible directory. If you want to make other assumptions,
                                 44                 :                :  *  such as using a vendor-specific archive and access API, these
                                 45                 :                :  *  routines are the ones you'll need to change. You're
                                 46                 :                :  *  encouraged to submit any changes to pgsql-hackers@lists.postgresql.org
                                 47                 :                :  *  or personally to the current maintainer. Those changes may be
                                 48                 :                :  *  folded in to later versions of this program.
                                 49                 :                :  */
                                 50                 :                : 
                                 51                 :                : /*
                                 52                 :                :  *  Initialize allows customized commands into the archive cleanup program.
                                 53                 :                :  *
                                 54                 :                :  *  You may wish to add code to check for tape libraries, etc..
                                 55                 :                :  */
                                 56                 :                : static void
 5053 simon@2ndQuadrant.co       57                 :CBC           7 : Initialize(void)
                                 58                 :                : {
                                 59                 :                :     /*
                                 60                 :                :      * This code assumes that archiveLocation is a directory, so we use stat
                                 61                 :                :      * to test if it's accessible.
                                 62                 :                :      */
                                 63                 :                :     struct stat stat_buf;
                                 64                 :                : 
 5050 tgl@sss.pgh.pa.us          65         [ +  + ]:              7 :     if (stat(archiveLocation, &stat_buf) != 0 ||
                                 66         [ -  + ]:              6 :         !S_ISDIR(stat_buf.st_mode))
                                 67                 :                :     {
 1840 peter@eisentraut.org       68                 :              1 :         pg_log_error("archive location \"%s\" does not exist",
                                 69                 :                :                      archiveLocation);
 5053 simon@2ndQuadrant.co       70                 :              1 :         exit(2);
                                 71                 :                :     }
                                 72                 :              6 : }
                                 73                 :                : 
                                 74                 :                : static void
 4392 rhaas@postgresql.org       75                 :             41 : TrimExtension(char *filename, char *extension)
                                 76                 :                : {
                                 77                 :                :     int         flen;
                                 78                 :                :     int         elen;
                                 79                 :                : 
                                 80         [ +  + ]:             41 :     if (extension == NULL)
                                 81                 :             14 :         return;
                                 82                 :                : 
                                 83                 :             27 :     elen = strlen(extension);
                                 84                 :             27 :     flen = strlen(filename);
                                 85                 :                : 
                                 86   [ +  +  +  + ]:             27 :     if (flen > elen && strcmp(filename + flen - elen, extension) == 0)
                                 87                 :              3 :         filename[flen - elen] = '\0';
                                 88                 :                : }
                                 89                 :                : 
                                 90                 :                : static void
 5053 simon@2ndQuadrant.co       91                 :              5 : CleanupPriorWALFiles(void)
                                 92                 :                : {
                                 93                 :                :     int         rc;
                                 94                 :                :     DIR        *xldir;
                                 95                 :                :     struct dirent *xlde;
                                 96                 :                :     char        walfile[MAXPGPATH];
                                 97                 :                : 
  270 michael@paquier.xyz        98                 :GNC           5 :     xldir = opendir(archiveLocation);
                                 99         [ -  + ]:              5 :     if (xldir == NULL)
  270 michael@paquier.xyz       100                 :UNC           0 :         pg_fatal("could not open archive location \"%s\": %m",
                                101                 :                :                  archiveLocation);
                                102                 :                : 
  270 michael@paquier.xyz       103         [ +  + ]:GNC          40 :     while (errno = 0, (xlde = readdir(xldir)) != NULL)
                                104                 :                :     {
                                105                 :                :         char        WALFilePath[MAXPGPATH * 2]; /* the file path including
                                106                 :                :                                                  * archive */
                                107                 :                : 
                                108                 :                :         /*
                                109                 :                :          * Truncation is essentially harmless, because we skip files whose
                                110                 :                :          * format is different from WAL files and backup history files. (In
                                111                 :                :          * principle, one could use a 1000-character additional_ext and get
                                112                 :                :          * trouble.)
                                113                 :                :          */
                                114                 :             35 :         strlcpy(walfile, xlde->d_name, MAXPGPATH);
                                115                 :             35 :         TrimExtension(walfile, additional_ext);
                                116                 :                : 
                                117                 :                :         /*
                                118                 :                :          * Ignore anything does that not look like a WAL segment, a .partial
                                119                 :                :          * WAL segment or a backup history file (if requested).
                                120                 :                :          */
                                121   [ +  +  +  + ]:             35 :         if (!IsXLogFileName(walfile) && !IsPartialXLogFileName(walfile) &&
                                122   [ +  +  +  + ]:             18 :             !(cleanBackupHistory && IsBackupHistoryFileName(walfile)))
                                123                 :             27 :             continue;
                                124                 :                : 
                                125                 :                :         /*
                                126                 :                :          * We ignore the timeline part of the XLOG segment identifiers in
                                127                 :                :          * deciding whether a segment is still needed.  This ensures that we
                                128                 :                :          * won't prematurely remove a segment from a parent timeline. We could
                                129                 :                :          * probably be a little more proactive about removing segments of
                                130                 :                :          * non-parent timelines, but that would be a whole lot more
                                131                 :                :          * complicated.
                                132                 :                :          *
                                133                 :                :          * We use the alphanumeric sorting property of the filenames to decide
                                134                 :                :          * which ones are earlier than the exclusiveCleanupFileName file. Note
                                135                 :                :          * that this means files are not removed in the order they were
                                136                 :                :          * originally written, in case this worries you.
                                137                 :                :          */
                                138         [ +  + ]:             18 :         if (strcmp(walfile + 8, exclusiveCleanupFileName + 8) >= 0)
                                139                 :              9 :             continue;
                                140                 :                : 
                                141                 :                :         /*
                                142                 :                :          * Use the original file name again now, including any extension that
                                143                 :                :          * might have been chopped off before testing the sequence.
                                144                 :                :          */
                                145                 :              9 :         snprintf(WALFilePath, sizeof(WALFilePath), "%s/%s",
                                146                 :              9 :                  archiveLocation, xlde->d_name);
                                147                 :                : 
                                148         [ +  + ]:              9 :         if (dryrun)
                                149                 :                :         {
                                150                 :                :             /*
                                151                 :                :              * Prints the name of the file to be removed and skips the actual
                                152                 :                :              * removal.  The regular printout is so that the user can pipe the
                                153                 :                :              * output into some other program.
                                154                 :                :              */
                                155                 :              1 :             printf("%s\n", WALFilePath);
                                156         [ +  - ]:              1 :             pg_log_debug("file \"%s\" would be removed", WALFilePath);
                                157                 :              1 :             continue;
                                158                 :                :         }
                                159                 :                : 
                                160         [ -  + ]:              8 :         pg_log_debug("removing file \"%s\"", WALFilePath);
                                161                 :                : 
                                162                 :              8 :         rc = unlink(WALFilePath);
                                163         [ -  + ]:              8 :         if (rc != 0)
  270 michael@paquier.xyz       164                 :UNC           0 :             pg_fatal("could not remove file \"%s\": %m",
                                165                 :                :                      WALFilePath);
                                166                 :                :     }
                                167                 :                : 
  270 michael@paquier.xyz       168         [ -  + ]:GNC           5 :     if (errno)
  270 michael@paquier.xyz       169                 :UNC           0 :         pg_fatal("could not read archive location \"%s\": %m",
                                170                 :                :                  archiveLocation);
  270 michael@paquier.xyz       171         [ -  + ]:GNC           5 :     if (closedir(xldir))
  270 michael@paquier.xyz       172                 :UNC           0 :         pg_fatal("could not close archive location \"%s\": %m",
                                173                 :                :                  archiveLocation);
 5053 simon@2ndQuadrant.co      174                 :CBC           5 : }
                                175                 :                : 
                                176                 :                : /*
                                177                 :                :  * SetWALFileNameForCleanup()
                                178                 :                :  *
                                179                 :                :  *    Set the earliest WAL filename that we want to keep on the archive
                                180                 :                :  *    and decide whether we need cleanup
                                181                 :                :  */
                                182                 :                : static void
                                183                 :              6 : SetWALFileNameForCleanup(void)
                                184                 :                : {
 5031 bruce@momjian.us          185                 :              6 :     bool        fnameOK = false;
                                186                 :                : 
 4392 rhaas@postgresql.org      187                 :              6 :     TrimExtension(restartWALFileName, additional_ext);
                                188                 :                : 
                                189                 :                :     /*
                                190                 :                :      * If restartWALFileName is a WAL file name then just use it directly. If
                                191                 :                :      * restartWALFileName is a .partial or .backup filename, make sure we use
                                192                 :                :      * the prefix of the filename, otherwise we will remove wrong files since
                                193                 :                :      * 000000010000000000000010.partial and
                                194                 :                :      * 000000010000000000000010.00000020.backup are after
                                195                 :                :      * 000000010000000000000010.
                                196                 :                :      */
 3209 fujii@postgresql.org      197         [ +  + ]:              6 :     if (IsXLogFileName(restartWALFileName))
                                198                 :                :     {
 5053 simon@2ndQuadrant.co      199                 :              3 :         strcpy(exclusiveCleanupFileName, restartWALFileName);
                                200                 :              3 :         fnameOK = true;
                                201                 :                :     }
 3208 fujii@postgresql.org      202         [ +  + ]:              3 :     else if (IsPartialXLogFileName(restartWALFileName))
                                203                 :                :     {
                                204                 :                :         int         args;
                                205                 :              1 :         uint32      tli = 1,
                                206                 :              1 :                     log = 0,
                                207                 :              1 :                     seg = 0;
                                208                 :                : 
                                209                 :              1 :         args = sscanf(restartWALFileName, "%08X%08X%08X.partial",
                                210                 :                :                       &tli, &log, &seg);
                                211         [ +  - ]:              1 :         if (args == 3)
                                212                 :                :         {
                                213                 :              1 :             fnameOK = true;
                                214                 :                : 
                                215                 :                :             /*
                                216                 :                :              * Use just the prefix of the filename, ignore everything after
                                217                 :                :              * first period
                                218                 :                :              */
                                219                 :              1 :             XLogFileNameById(exclusiveCleanupFileName, tli, log, seg);
                                220                 :                :         }
                                221                 :                :     }
 3209                           222         [ +  + ]:              2 :     else if (IsBackupHistoryFileName(restartWALFileName))
                                223                 :                :     {
                                224                 :                :         int         args;
 5053 simon@2ndQuadrant.co      225                 :              1 :         uint32      tli = 1,
                                226                 :              1 :                     log = 0,
                                227                 :              1 :                     seg = 0,
                                228                 :              1 :                     offset = 0;
                                229                 :                : 
                                230                 :              1 :         args = sscanf(restartWALFileName, "%08X%08X%08X.%08X.backup", &tli, &log, &seg, &offset);
                                231         [ +  - ]:              1 :         if (args == 4)
                                232                 :                :         {
                                233                 :              1 :             fnameOK = true;
                                234                 :                : 
                                235                 :                :             /*
                                236                 :                :              * Use just the prefix of the filename, ignore everything after
                                237                 :                :              * first period
                                238                 :                :              */
 3209 fujii@postgresql.org      239                 :              1 :             XLogFileNameById(exclusiveCleanupFileName, tli, log, seg);
                                240                 :                :         }
                                241                 :                :     }
                                242                 :                : 
 5053 simon@2ndQuadrant.co      243         [ +  + ]:              6 :     if (!fnameOK)
                                244                 :                :     {
 1840 peter@eisentraut.org      245                 :              1 :         pg_log_error("invalid file name argument");
  737 tgl@sss.pgh.pa.us         246                 :              1 :         pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 5053 simon@2ndQuadrant.co      247                 :              1 :         exit(2);
                                248                 :                :     }
                                249                 :              5 : }
                                250                 :                : 
                                251                 :                : /* =====================================================================
                                252                 :                :  *        End of Customizable section
                                253                 :                :  * =====================================================================
                                254                 :                :  */
                                255                 :                : 
                                256                 :                : static void
                                257                 :              1 : usage(void)
                                258                 :                : {
 2739 peter_e@gmx.net           259                 :              1 :     printf(_("%s removes older WAL files from PostgreSQL archives.\n\n"), progname);
                                260                 :              1 :     printf(_("Usage:\n"));
                                261                 :              1 :     printf(_("  %s [OPTION]... ARCHIVELOCATION OLDESTKEPTWALFILE\n"), progname);
                                262                 :              1 :     printf(_("\nOptions:\n"));
  270 michael@paquier.xyz       263                 :GNC           1 :     printf(_("  -b, --clean-backup-history  clean up files including backup history files\n"));
  289                           264                 :              1 :     printf(_("  -d, --debug                 generate debug output (verbose mode)\n"));
                                265                 :              1 :     printf(_("  -n, --dry-run               dry run, show the names of the files that would be\n"
                                266                 :                :              "                              removed\n"));
                                267                 :              1 :     printf(_("  -V, --version               output version information, then exit\n"));
  102                           268                 :              1 :     printf(_("  -x, --strip-extension=EXT   strip this extension before identifying files for\n"
                                269                 :                :              "                              clean up\n"));
  289                           270                 :              1 :     printf(_("  -?, --help                  show this help, then exit\n"));
 2739 peter_e@gmx.net           271                 :CBC           1 :     printf(_("\n"
                                272                 :                :              "For use as archive_cleanup_command in postgresql.conf:\n"
                                273                 :                :              "  archive_cleanup_command = 'pg_archivecleanup [OPTION]... ARCHIVELOCATION %%r'\n"
                                274                 :                :              "e.g.\n"
                                275                 :                :              "  archive_cleanup_command = 'pg_archivecleanup /mnt/server/archiverdir %%r'\n"));
                                276                 :              1 :     printf(_("\n"
                                277                 :                :              "Or for use as a standalone archive cleaner:\n"
                                278                 :                :              "e.g.\n"
                                279                 :                :              "  pg_archivecleanup /mnt/server/archiverdir 000000010000000000000010.00000020.backup\n"));
 1507 peter@eisentraut.org      280                 :              1 :     printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
                                281                 :              1 :     printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
 5053 simon@2ndQuadrant.co      282                 :              1 : }
                                283                 :                : 
                                284                 :                : /*------------ MAIN ----------------------------------------*/
                                285                 :                : int
                                286                 :             13 : main(int argc, char **argv)
                                287                 :                : {
                                288                 :                :     static struct option long_options[] = {
                                289                 :                :         {"clean-backup-history", no_argument, NULL, 'b'},
                                290                 :                :         {"debug", no_argument, NULL, 'd'},
                                291                 :                :         {"dry-run", no_argument, NULL, 'n'},
                                292                 :                :         {"strip-extension", required_argument, NULL, 'x'},
                                293                 :                :         {NULL, 0, NULL, 0}
                                294                 :                :     };
                                295                 :                :     int         c;
                                296                 :                : 
 1840 peter@eisentraut.org      297                 :             13 :     pg_logging_init(argv[0]);
 2739 peter_e@gmx.net           298                 :             13 :     set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_archivecleanup"));
 5053 simon@2ndQuadrant.co      299                 :             13 :     progname = get_progname(argv[0]);
                                300                 :                : 
                                301         [ +  + ]:             13 :     if (argc > 1)
                                302                 :                :     {
                                303   [ +  +  -  + ]:             12 :         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
                                304                 :                :         {
                                305                 :              1 :             usage();
                                306                 :              1 :             exit(0);
                                307                 :                :         }
                                308   [ +  +  -  + ]:             11 :         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
                                309                 :                :         {
                                310                 :              1 :             puts("pg_archivecleanup (PostgreSQL) " PG_VERSION);
                                311                 :              1 :             exit(0);
                                312                 :                :         }
                                313                 :                :     }
                                314                 :                : 
  270 michael@paquier.xyz       315         [ +  + ]:GNC          17 :     while ((c = getopt_long(argc, argv, "bdnx:", long_options, NULL)) != -1)
                                316                 :                :     {
 5053 simon@2ndQuadrant.co      317   [ +  +  +  +  :CBC           7 :         switch (c)
                                                 + ]
                                318                 :                :         {
  270 michael@paquier.xyz       319                 :GNC           1 :             case 'b':           /* Remove backup history files as well */
                                320                 :              1 :                 cleanBackupHistory = true;
                                321                 :              1 :                 break;
 5053 simon@2ndQuadrant.co      322                 :CBC           1 :             case 'd':           /* Debug mode */
 1305 tgl@sss.pgh.pa.us         323                 :              1 :                 pg_logging_increase_verbosity();
 5053 simon@2ndQuadrant.co      324                 :              1 :                 break;
 4456 alvherre@alvh.no-ip.      325                 :              1 :             case 'n':           /* Dry-Run mode */
                                326                 :              1 :                 dryrun = true;
                                327                 :              1 :                 break;
 4392 rhaas@postgresql.org      328                 :              3 :             case 'x':
 2489 tgl@sss.pgh.pa.us         329                 :              3 :                 additional_ext = pg_strdup(optarg); /* Extension to remove
                                330                 :                :                                                      * from xlogfile names */
 4392 rhaas@postgresql.org      331                 :              3 :                 break;
 5053 simon@2ndQuadrant.co      332                 :              1 :             default:
                                333                 :                :                 /* getopt already emitted a complaint */
  737 tgl@sss.pgh.pa.us         334                 :              1 :                 pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 5053 simon@2ndQuadrant.co      335                 :              1 :                 exit(2);
                                336                 :                :         }
                                337                 :                :     }
                                338                 :                : 
                                339                 :                :     /*
                                340                 :                :      * We will go to the archiveLocation to check restartWALFileName.
                                341                 :                :      * restartWALFileName may not exist anymore, which would not be an error,
                                342                 :                :      * so we separate the archiveLocation and restartWALFileName so we can
                                343                 :                :      * check separately whether archiveLocation exists, if not that is an
                                344                 :                :      * error
                                345                 :                :      */
                                346         [ +  + ]:             10 :     if (optind < argc)
                                347                 :                :     {
                                348                 :              9 :         archiveLocation = argv[optind];
                                349                 :              9 :         optind++;
                                350                 :                :     }
                                351                 :                :     else
                                352                 :                :     {
 1840 peter@eisentraut.org      353                 :              1 :         pg_log_error("must specify archive location");
  737 tgl@sss.pgh.pa.us         354                 :              1 :         pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 5053 simon@2ndQuadrant.co      355                 :              1 :         exit(2);
                                356                 :                :     }
                                357                 :                : 
                                358         [ +  + ]:              9 :     if (optind < argc)
                                359                 :                :     {
                                360                 :              8 :         restartWALFileName = argv[optind];
                                361                 :              8 :         optind++;
                                362                 :                :     }
                                363                 :                :     else
                                364                 :                :     {
 1840 peter@eisentraut.org      365                 :              1 :         pg_log_error("must specify oldest kept WAL file");
  737 tgl@sss.pgh.pa.us         366                 :              1 :         pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 5053 simon@2ndQuadrant.co      367                 :              1 :         exit(2);
                                368                 :                :     }
                                369                 :                : 
                                370         [ +  + ]:              8 :     if (optind < argc)
                                371                 :                :     {
 1840 peter@eisentraut.org      372                 :              1 :         pg_log_error("too many command-line arguments");
  737 tgl@sss.pgh.pa.us         373                 :              1 :         pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 5053 simon@2ndQuadrant.co      374                 :              1 :         exit(2);
                                375                 :                :     }
                                376                 :                : 
                                377                 :                :     /*
                                378                 :                :      * Check archive exists and other initialization if required.
                                379                 :                :      */
                                380                 :              7 :     Initialize();
                                381                 :                : 
                                382                 :                :     /*
                                383                 :                :      * Check filename is a valid name, then process to find cut-off
                                384                 :                :      */
                                385                 :              6 :     SetWALFileNameForCleanup();
                                386                 :                : 
 1840 peter@eisentraut.org      387         [ +  + ]:              5 :     pg_log_debug("keeping WAL file \"%s/%s\" and later",
                                388                 :                :                  archiveLocation, exclusiveCleanupFileName);
                                389                 :                : 
                                390                 :                :     /*
                                391                 :                :      * Remove WAL files older than cut-off
                                392                 :                :      */
 5053 simon@2ndQuadrant.co      393                 :              5 :     CleanupPriorWALFiles();
                                394                 :                : 
                                395                 :              5 :     exit(0);
                                396                 :                : }
        

Generated by: LCOV version 2.1-beta2-3-g6141622