Header And Logo

PostgreSQL
| The world's most advanced open source database.

pg_archivecleanup.c

Go to the documentation of this file.
00001 /*
00002  * contrib/pg_archivecleanup/pg_archivecleanup.c
00003  *
00004  * pg_archivecleanup.c
00005  *
00006  * Production-ready example of an archive_cleanup_command
00007  * used to clean an archive when using standby_mode = on in 9.0
00008  * or for standalone use for any version of PostgreSQL 8.0+.
00009  *
00010  * Original author:     Simon Riggs  [email protected]
00011  * Current maintainer:  Simon Riggs
00012  */
00013 #include "postgres_fe.h"
00014 
00015 #include <ctype.h>
00016 #include <dirent.h>
00017 #include <sys/stat.h>
00018 #include <fcntl.h>
00019 #include <signal.h>
00020 
00021 #ifndef WIN32
00022 #include <sys/time.h>
00023 #include <unistd.h>
00024 
00025 #ifdef HAVE_GETOPT_H
00026 #include <getopt.h>
00027 #endif
00028 #else                           /* WIN32 */
00029 extern int  getopt(int argc, char *const argv[], const char *optstring);
00030 #endif   /* ! WIN32 */
00031 
00032 extern char *optarg;
00033 extern int  optind;
00034 
00035 const char *progname;
00036 
00037 /* Options and defaults */
00038 bool        debug = false;      /* are we debugging? */
00039 bool        dryrun = false;     /* are we performing a dry-run operation? */
00040 char       *additional_ext = NULL;      /* Extension to remove from filenames */
00041 
00042 char       *archiveLocation;    /* where to find the archive? */
00043 char       *restartWALFileName; /* the file from which we can restart restore */
00044 char        WALFilePath[MAXPGPATH];     /* the file path including archive */
00045 char        exclusiveCleanupFileName[MAXPGPATH];        /* the oldest file we
00046                                                          * want to remain in
00047                                                          * archive */
00048 
00049 
00050 /* =====================================================================
00051  *
00052  *        Customizable section
00053  *
00054  * =====================================================================
00055  *
00056  *  Currently, this section assumes that the Archive is a locally
00057  *  accessible directory. If you want to make other assumptions,
00058  *  such as using a vendor-specific archive and access API, these
00059  *  routines are the ones you'll need to change. You're
00060  *  enouraged to submit any changes to [email protected]
00061  *  or personally to the current maintainer. Those changes may be
00062  *  folded in to later versions of this program.
00063  */
00064 
00065 #define XLOG_DATA_FNAME_LEN     24
00066 /* Reworked from access/xlog_internal.h */
00067 #define XLogFileName(fname, tli, log, seg)  \
00068     snprintf(fname, XLOG_DATA_FNAME_LEN + 1, "%08X%08X%08X", tli, log, seg)
00069 #define XLOG_BACKUP_FNAME_LEN   40
00070 
00071 /*
00072  *  Initialize allows customized commands into the archive cleanup program.
00073  *
00074  *  You may wish to add code to check for tape libraries, etc..
00075  */
00076 static void
00077 Initialize(void)
00078 {
00079     /*
00080      * This code assumes that archiveLocation is a directory, so we use stat
00081      * to test if it's accessible.
00082      */
00083     struct stat stat_buf;
00084 
00085     if (stat(archiveLocation, &stat_buf) != 0 ||
00086         !S_ISDIR(stat_buf.st_mode))
00087     {
00088         fprintf(stderr, "%s: archive location \"%s\" does not exist\n",
00089                 progname, archiveLocation);
00090         exit(2);
00091     }
00092 }
00093 
00094 static void
00095 TrimExtension(char *filename, char *extension)
00096 {
00097     int         flen;
00098     int         elen;
00099 
00100     if (extension == NULL)
00101         return;
00102 
00103     elen = strlen(extension);
00104     flen = strlen(filename);
00105 
00106     if (flen > elen && strcmp(filename + flen - elen, extension) == 0)
00107         filename[flen - elen] = '\0';
00108 }
00109 
00110 static void
00111 CleanupPriorWALFiles(void)
00112 {
00113     int         rc;
00114     DIR        *xldir;
00115     struct dirent *xlde;
00116     char        walfile[MAXPGPATH];
00117 
00118     if ((xldir = opendir(archiveLocation)) != NULL)
00119     {
00120         while ((xlde = readdir(xldir)) != NULL)
00121         {
00122             strncpy(walfile, xlde->d_name, MAXPGPATH);
00123             TrimExtension(walfile, additional_ext);
00124 
00125             /*
00126              * We ignore the timeline part of the XLOG segment identifiers in
00127              * deciding whether a segment is still needed.  This ensures that
00128              * we won't prematurely remove a segment from a parent timeline.
00129              * We could probably be a little more proactive about removing
00130              * segments of non-parent timelines, but that would be a whole lot
00131              * more complicated.
00132              *
00133              * We use the alphanumeric sorting property of the filenames to
00134              * decide which ones are earlier than the exclusiveCleanupFileName
00135              * file. Note that this means files are not removed in the order
00136              * they were originally written, in case this worries you.
00137              */
00138             if (strlen(walfile) == XLOG_DATA_FNAME_LEN &&
00139                 strspn(walfile, "0123456789ABCDEF") == XLOG_DATA_FNAME_LEN &&
00140                 strcmp(walfile + 8, exclusiveCleanupFileName + 8) < 0)
00141             {
00142                 /*
00143                  * Use the original file name again now, including any
00144                  * extension that might have been chopped off before testing
00145                  * the sequence.
00146                  */
00147                 snprintf(WALFilePath, MAXPGPATH, "%s/%s",
00148                          archiveLocation, xlde->d_name);
00149 
00150                 if (dryrun)
00151                 {
00152                     /*
00153                      * Prints the name of the file to be removed and skips the
00154                      * actual removal.  The regular printout is so that the
00155                      * user can pipe the output into some other program.
00156                      */
00157                     printf("%s\n", WALFilePath);
00158                     if (debug)
00159                         fprintf(stderr,
00160                                 "%s: file \"%s\" would be removed\n",
00161                                 progname, WALFilePath);
00162                     continue;
00163                 }
00164 
00165                 if (debug)
00166                     fprintf(stderr, "%s: removing file \"%s\"\n",
00167                             progname, WALFilePath);
00168 
00169                 rc = unlink(WALFilePath);
00170                 if (rc != 0)
00171                 {
00172                     fprintf(stderr, "%s: ERROR: could not remove file \"%s\": %s\n",
00173                             progname, WALFilePath, strerror(errno));
00174                     break;
00175                 }
00176             }
00177         }
00178         closedir(xldir);
00179     }
00180     else
00181         fprintf(stderr, "%s: could not open archive location \"%s\": %s\n",
00182                 progname, archiveLocation, strerror(errno));
00183 }
00184 
00185 /*
00186  * SetWALFileNameForCleanup()
00187  *
00188  *    Set the earliest WAL filename that we want to keep on the archive
00189  *    and decide whether we need_cleanup
00190  */
00191 static void
00192 SetWALFileNameForCleanup(void)
00193 {
00194     bool        fnameOK = false;
00195 
00196     TrimExtension(restartWALFileName, additional_ext);
00197 
00198     /*
00199      * If restartWALFileName is a WAL file name then just use it directly. If
00200      * restartWALFileName is a .backup filename, make sure we use the prefix
00201      * of the filename, otherwise we will remove wrong files since
00202      * 000000010000000000000010.00000020.backup is after
00203      * 000000010000000000000010.
00204      */
00205     if (strlen(restartWALFileName) == XLOG_DATA_FNAME_LEN &&
00206         strspn(restartWALFileName, "0123456789ABCDEF") == XLOG_DATA_FNAME_LEN)
00207     {
00208         strcpy(exclusiveCleanupFileName, restartWALFileName);
00209         fnameOK = true;
00210     }
00211     else if (strlen(restartWALFileName) == XLOG_BACKUP_FNAME_LEN)
00212     {
00213         int         args;
00214         uint32      tli = 1,
00215                     log = 0,
00216                     seg = 0,
00217                     offset = 0;
00218 
00219         args = sscanf(restartWALFileName, "%08X%08X%08X.%08X.backup", &tli, &log, &seg, &offset);
00220         if (args == 4)
00221         {
00222             fnameOK = true;
00223 
00224             /*
00225              * Use just the prefix of the filename, ignore everything after
00226              * first period
00227              */
00228             XLogFileName(exclusiveCleanupFileName, tli, log, seg);
00229         }
00230     }
00231 
00232     if (!fnameOK)
00233     {
00234         fprintf(stderr, "%s: invalid filename input\n", progname);
00235         fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
00236         exit(2);
00237     }
00238 }
00239 
00240 /* =====================================================================
00241  *        End of Customizable section
00242  * =====================================================================
00243  */
00244 
00245 static void
00246 usage(void)
00247 {
00248     printf("%s removes older WAL files from PostgreSQL archives.\n\n", progname);
00249     printf("Usage:\n");
00250     printf("  %s [OPTION]... ARCHIVELOCATION OLDESTKEPTWALFILE\n", progname);
00251     printf("\nOptions:\n");
00252     printf("  -d             generate debug output (verbose mode)\n");
00253     printf("  -n             dry run, show the names of the files that would be removed\n");
00254     printf("  -V, --version  output version information, then exit\n");
00255     printf("  -x EXT         clean up files if they have this extension\n");
00256     printf("  -?, --help     show this help, then exit\n");
00257     printf("\n"
00258            "For use as archive_cleanup_command in recovery.conf when standby_mode = on:\n"
00259            "  archive_cleanup_command = 'pg_archivecleanup [OPTION]... ARCHIVELOCATION %%r'\n"
00260            "e.g.\n"
00261            "  archive_cleanup_command = 'pg_archivecleanup /mnt/server/archiverdir %%r'\n");
00262     printf("\n"
00263            "Or for use as a standalone archive cleaner:\n"
00264            "e.g.\n"
00265            "  pg_archivecleanup /mnt/server/archiverdir 000000010000000000000010.00000020.backup\n");
00266     printf("\nReport bugs to <[email protected]>.\n");
00267 }
00268 
00269 /*------------ MAIN ----------------------------------------*/
00270 int
00271 main(int argc, char **argv)
00272 {
00273     int         c;
00274 
00275     progname = get_progname(argv[0]);
00276 
00277     if (argc > 1)
00278     {
00279         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
00280         {
00281             usage();
00282             exit(0);
00283         }
00284         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
00285         {
00286             puts("pg_archivecleanup (PostgreSQL) " PG_VERSION);
00287             exit(0);
00288         }
00289     }
00290 
00291     while ((c = getopt(argc, argv, "x:dn")) != -1)
00292     {
00293         switch (c)
00294         {
00295             case 'd':           /* Debug mode */
00296                 debug = true;
00297                 break;
00298             case 'n':           /* Dry-Run mode */
00299                 dryrun = true;
00300                 break;
00301             case 'x':
00302                 additional_ext = strdup(optarg);        /* Extension to remove from
00303                                                  * xlogfile names */
00304                 break;
00305             default:
00306                 fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
00307                 exit(2);
00308                 break;
00309         }
00310     }
00311 
00312     /*
00313      * We will go to the archiveLocation to check restartWALFileName.
00314      * restartWALFileName may not exist anymore, which would not be an error,
00315      * so we separate the archiveLocation and restartWALFileName so we can
00316      * check separately whether archiveLocation exists, if not that is an
00317      * error
00318      */
00319     if (optind < argc)
00320     {
00321         archiveLocation = argv[optind];
00322         optind++;
00323     }
00324     else
00325     {
00326         fprintf(stderr, "%s: must specify archive location\n", progname);
00327         fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
00328         exit(2);
00329     }
00330 
00331     if (optind < argc)
00332     {
00333         restartWALFileName = argv[optind];
00334         optind++;
00335     }
00336     else
00337     {
00338         fprintf(stderr, "%s: must specify restartfilename\n", progname);
00339         fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
00340         exit(2);
00341     }
00342 
00343     if (optind < argc)
00344     {
00345         fprintf(stderr, "%s: too many parameters\n", progname);
00346         fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
00347         exit(2);
00348     }
00349 
00350     /*
00351      * Check archive exists and other initialization if required.
00352      */
00353     Initialize();
00354 
00355     /*
00356      * Check filename is a valid name, then process to find cut-off
00357      */
00358     SetWALFileNameForCleanup();
00359 
00360     if (debug)
00361     {
00362         snprintf(WALFilePath, MAXPGPATH, "%s/%s",
00363                  archiveLocation, exclusiveCleanupFileName);
00364         fprintf(stderr, "%s: keep WAL file \"%s\" and later\n",
00365                 progname, WALFilePath);
00366     }
00367 
00368     /*
00369      * Remove WAL files older than cut-off
00370      */
00371     CleanupPriorWALFiles();
00372 
00373     exit(0);
00374 }