Age Owner 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
4682 simon 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 :
4679 tgl 63 6 : if (stat(archiveLocation, &stat_buf) != 0 ||
64 5 : !S_ISDIR(stat_buf.st_mode))
65 : {
1469 peter 66 1 : pg_log_error("archive location \"%s\" does not exist",
67 : archiveLocation);
4682 simon 68 1 : exit(2);
69 : }
70 5 : }
71 :
72 : static void
4021 rhaas 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
4682 simon 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 : {
3306 bruce 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 : */
3156 noah 105 28 : strlcpy(walfile, xlde->d_name, MAXPGPATH);
4021 rhaas 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 : */
2837 fujii 121 28 : if ((IsXLogFileName(walfile) || IsPartialXLogFileName(walfile)) &&
4021 rhaas 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 : */
2189 peter_e 132 7 : snprintf(WALFilePath, sizeof(WALFilePath), "%s/%s",
4612 tgl 133 7 : archiveLocation, xlde->d_name);
134 :
4085 alvherre 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);
1469 peter 143 1 : pg_log_debug("file \"%s\" would be removed", WALFilePath);
4085 alvherre 144 1 : continue;
145 : }
146 :
1469 peter 147 6 : pg_log_debug("removing file \"%s\"", WALFilePath);
148 :
4682 simon 149 6 : rc = unlink(WALFilePath);
150 6 : if (rc != 0)
366 tgl 151 UBC 0 : pg_fatal("could not remove file \"%s\": %m",
152 : WALFilePath);
153 : }
154 : }
155 :
3306 bruce 156 CBC 4 : if (errno)
366 tgl 157 UBC 0 : pg_fatal("could not read archive location \"%s\": %m",
158 : archiveLocation);
3306 bruce 159 CBC 4 : if (closedir(xldir))
366 tgl 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);
4682 simon 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 : {
4660 bruce 177 5 : bool fnameOK = false;
178 :
4021 rhaas 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 : */
2838 fujii 189 5 : if (IsXLogFileName(restartWALFileName))
190 : {
4682 simon 191 2 : strcpy(exclusiveCleanupFileName, restartWALFileName);
192 2 : fnameOK = true;
193 : }
2837 fujii 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 : }
2838 214 2 : else if (IsBackupHistoryFileName(restartWALFileName))
215 : {
216 : int args;
4682 simon 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 : */
2838 fujii 231 1 : XLogFileNameById(exclusiveCleanupFileName, tli, log, seg);
232 : }
233 : }
234 :
4682 simon 235 5 : if (!fnameOK)
236 : {
1469 peter 237 1 : pg_log_error("invalid file name argument");
366 tgl 238 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4682 simon 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 : {
2368 peter_e 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"));
1136 peter 269 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
270 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
4682 simon 271 1 : }
272 :
273 : /*------------ MAIN ----------------------------------------*/
274 : int
275 12 : main(int argc, char **argv)
276 : {
277 : int c;
278 :
1469 peter 279 12 : pg_logging_init(argv[0]);
2368 peter_e 280 12 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_archivecleanup"));
4682 simon 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 :
118 peter 297 GNC 15 : while ((c = getopt(argc, argv, "dnx:")) != -1)
298 : {
4682 simon 299 CBC 6 : switch (c)
300 : {
301 1 : case 'd': /* Debug mode */
934 tgl 302 1 : pg_logging_increase_verbosity();
4682 simon 303 1 : break;
4085 alvherre 304 1 : case 'n': /* Dry-Run mode */
305 1 : dryrun = true;
306 1 : break;
4021 rhaas 307 3 : case 'x':
2118 tgl 308 3 : additional_ext = pg_strdup(optarg); /* Extension to remove
309 : * from xlogfile names */
4021 rhaas 310 3 : break;
4682 simon 311 1 : default:
312 : /* getopt already emitted a complaint */
366 tgl 313 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4682 simon 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 : {
1469 peter 332 1 : pg_log_error("must specify archive location");
366 tgl 333 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4682 simon 334 1 : exit(2);
335 : }
336 :
337 8 : if (optind < argc)
338 : {
339 7 : restartWALFileName = argv[optind];
340 7 : optind++;
341 : }
342 : else
343 : {
1469 peter 344 1 : pg_log_error("must specify oldest kept WAL file");
366 tgl 345 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4682 simon 346 1 : exit(2);
347 : }
348 :
349 7 : if (optind < argc)
350 : {
1469 peter 351 1 : pg_log_error("too many command-line arguments");
366 tgl 352 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4682 simon 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 :
1469 peter 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 : */
4682 simon 372 4 : CleanupPriorWALFiles();
373 :
374 4 : exit(0);
375 : }
|