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