LCOV - differential code coverage report
Current view: top level - src/bin/pg_archivecleanup - pg_archivecleanup.c (source / functions) Coverage Total Hit UBC GNC CBC DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 96.8 % 125 121 4 1 120 1
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 6 6 1 5
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a