|
| 1 | +/* |
| 2 | + * $PostgreSQL: pgsql/contrib/pg_archivecleanup/pg_archivecleanup.c,v 1.1 2010/06/14 16:19:24 sriggs Exp $ |
| 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 | + |
| 21 | +#ifdefWIN32 |
| 22 | +intgetopt(intargc,char*constargv[],constchar*optstring); |
| 23 | +#else |
| 24 | +#include<sys/time.h> |
| 25 | +#include<unistd.h> |
| 26 | + |
| 27 | +#ifdefHAVE_GETOPT_H |
| 28 | +#include<getopt.h> |
| 29 | +#endif |
| 30 | +#endif/* ! WIN32 */ |
| 31 | + |
| 32 | +externchar*optarg; |
| 33 | +externintoptind; |
| 34 | + |
| 35 | +constchar*progname; |
| 36 | + |
| 37 | +/* Options and defaults */ |
| 38 | +booldebug= false;/* are we debugging? */ |
| 39 | + |
| 40 | +char*archiveLocation;/* where to find the archive? */ |
| 41 | +char*restartWALFileName;/* the file from which we can restart restore */ |
| 42 | +charWALFilePath[MAXPGPATH];/* the file path including archive */ |
| 43 | +charexclusiveCleanupFileName[MAXPGPATH];/* the oldest file we want to |
| 44 | + * remain in archive */ |
| 45 | + |
| 46 | +structstatstat_buf; |
| 47 | + |
| 48 | +/* ===================================================================== |
| 49 | + * |
| 50 | + * Customizable section |
| 51 | + * |
| 52 | + * ===================================================================== |
| 53 | + * |
| 54 | + *Currently, this section assumes that the Archive is a locally |
| 55 | + *accessible directory. If you want to make other assumptions, |
| 56 | + *such as using a vendor-specific archive and access API, these |
| 57 | + *routines are the ones you'll need to change. You're |
| 58 | + *enouraged to submit any changes to pgsql-hackers@postgresql.org |
| 59 | + *or personally to the current maintainer. Those changes may be |
| 60 | + *folded in to later versions of this program. |
| 61 | + */ |
| 62 | + |
| 63 | +#defineXLOG_DATA_FNAME_LEN24 |
| 64 | +/* Reworked from access/xlog_internal.h */ |
| 65 | +#defineXLogFileName(fname,tli,log,seg)\ |
| 66 | +snprintf(fname, XLOG_DATA_FNAME_LEN + 1, "%08X%08X%08X", tli, log, seg) |
| 67 | +#defineXLOG_BACKUP_FNAME_LEN40 |
| 68 | + |
| 69 | +/* |
| 70 | + *Initialize allows customized commands into the archive cleanup program. |
| 71 | + * |
| 72 | + * You may wish to add code to check for tape libraries, etc.. |
| 73 | + */ |
| 74 | +staticvoid |
| 75 | +Initialize(void) |
| 76 | +{ |
| 77 | +/* |
| 78 | + * This code assumes that archiveLocation is a directory, so we use |
| 79 | + * stat to test if it's accessible. |
| 80 | + */ |
| 81 | +if (stat(archiveLocation,&stat_buf)!=0) |
| 82 | +{ |
| 83 | +fprintf(stderr,"%s: archiveLocation \"%s\" does not exist\n",progname,archiveLocation); |
| 84 | +fflush(stderr); |
| 85 | +exit(2); |
| 86 | +} |
| 87 | +} |
| 88 | + |
| 89 | +staticvoid |
| 90 | +CleanupPriorWALFiles(void) |
| 91 | +{ |
| 92 | +intrc; |
| 93 | +DIR*xldir; |
| 94 | +structdirent*xlde; |
| 95 | + |
| 96 | +if ((xldir=opendir(archiveLocation))!=NULL) |
| 97 | +{ |
| 98 | +while ((xlde=readdir(xldir))!=NULL) |
| 99 | +{ |
| 100 | +/* |
| 101 | + * We ignore the timeline part of the XLOG segment identifiers |
| 102 | + * in deciding whether a segment is still needed. This |
| 103 | + * ensures that we won't prematurely remove a segment from a |
| 104 | + * parent timeline. We could probably be a little more |
| 105 | + * proactive about removing segments of non-parent timelines, |
| 106 | + * but that would be a whole lot more complicated. |
| 107 | + * |
| 108 | + * We use the alphanumeric sorting property of the filenames |
| 109 | + * to decide which ones are earlier than the |
| 110 | + * exclusiveCleanupFileName file. Note that this means files |
| 111 | + * are not removed in the order they were originally written, |
| 112 | + * in case this worries you. |
| 113 | + */ |
| 114 | +if (strlen(xlde->d_name)==XLOG_DATA_FNAME_LEN&& |
| 115 | +strspn(xlde->d_name,"0123456789ABCDEF")==XLOG_DATA_FNAME_LEN&& |
| 116 | +strcmp(xlde->d_name+8,exclusiveCleanupFileName+8)<0) |
| 117 | +{ |
| 118 | +#ifdefWIN32 |
| 119 | +snprintf(WALFilePath,MAXPGPATH,"%s\\%s",archiveLocation,xlde->d_name); |
| 120 | +#else |
| 121 | +snprintf(WALFilePath,MAXPGPATH,"%s/%s",archiveLocation,xlde->d_name); |
| 122 | +#endif |
| 123 | + |
| 124 | +if (debug) |
| 125 | +fprintf(stderr,"\n%s: removing \"%s\"",progname,WALFilePath); |
| 126 | + |
| 127 | +rc=unlink(WALFilePath); |
| 128 | +if (rc!=0) |
| 129 | +{ |
| 130 | +fprintf(stderr,"\n%s: ERROR failed to remove \"%s\": %s", |
| 131 | +progname,WALFilePath,strerror(errno)); |
| 132 | +break; |
| 133 | +} |
| 134 | +} |
| 135 | +} |
| 136 | +if (debug) |
| 137 | +fprintf(stderr,"\n"); |
| 138 | +} |
| 139 | +else |
| 140 | +fprintf(stderr,"%s: archiveLocation \"%s\" open error\n",progname,archiveLocation); |
| 141 | + |
| 142 | +closedir(xldir); |
| 143 | +fflush(stderr); |
| 144 | +} |
| 145 | + |
| 146 | +/* |
| 147 | + * SetWALFileNameForCleanup() |
| 148 | + * |
| 149 | + * Set the earliest WAL filename that we want to keep on the archive |
| 150 | + * and decide whether we need_cleanup |
| 151 | + */ |
| 152 | +staticvoid |
| 153 | +SetWALFileNameForCleanup(void) |
| 154 | +{ |
| 155 | +boolfnameOK= false; |
| 156 | + |
| 157 | +/* |
| 158 | + * If restartWALFileName is a WAL file name then just use it directly. |
| 159 | + * If restartWALFileName is a .backup filename, make sure we use |
| 160 | + * the prefix of the filename, otherwise we will remove wrong files |
| 161 | + * since 000000010000000000000010.00000020.backup is after |
| 162 | + * 000000010000000000000010. |
| 163 | + */ |
| 164 | +if (strlen(restartWALFileName)==XLOG_DATA_FNAME_LEN&& |
| 165 | +strspn(restartWALFileName,"0123456789ABCDEF")==XLOG_DATA_FNAME_LEN) |
| 166 | +{ |
| 167 | +strcpy(exclusiveCleanupFileName,restartWALFileName); |
| 168 | +fnameOK= true; |
| 169 | +} |
| 170 | +elseif (strlen(restartWALFileName)==XLOG_BACKUP_FNAME_LEN) |
| 171 | +{ |
| 172 | +intargs; |
| 173 | +uint32tli=1, |
| 174 | +log=0, |
| 175 | +seg=0, |
| 176 | +offset=0; |
| 177 | +args=sscanf(restartWALFileName,"%08X%08X%08X.%08X.backup",&tli,&log,&seg,&offset); |
| 178 | +if (args==4) |
| 179 | +{ |
| 180 | +fnameOK= true; |
| 181 | +/* |
| 182 | + * Use just the prefix of the filename, ignore everything after first period |
| 183 | + */ |
| 184 | +XLogFileName(exclusiveCleanupFileName,tli,log,seg); |
| 185 | +} |
| 186 | +} |
| 187 | + |
| 188 | +if (!fnameOK) |
| 189 | +{ |
| 190 | +fprintf(stderr,"%s: invalid filename input\n",progname); |
| 191 | +fprintf(stderr,"Try \"%s --help\" for more information.\n",progname); |
| 192 | +exit(2); |
| 193 | +} |
| 194 | +} |
| 195 | + |
| 196 | +/* ===================================================================== |
| 197 | + * End of Customizable section |
| 198 | + * ===================================================================== |
| 199 | + */ |
| 200 | + |
| 201 | +staticvoid |
| 202 | +usage(void) |
| 203 | +{ |
| 204 | +printf("%s removes older WAL files from PostgreSQL archives.\n\n",progname); |
| 205 | +printf("Usage:\n"); |
| 206 | +printf(" %s [OPTION]... ARCHIVELOCATION OLDESTKEPTWALFILE\n",progname); |
| 207 | +printf("\n" |
| 208 | +"for use as an archive_cleanup_command in the recovery.conf when standby_mode = on:\n" |
| 209 | +" archive_cleanup_command = 'pg_archivecleanup [OPTION]... ARCHIVELOCATION %%r'\n" |
| 210 | +"e.g.\n" |
| 211 | +" archive_cleanup_command = 'pg_archivecleanup /mnt/server/archiverdir %%r'\n"); |
| 212 | +printf("\n" |
| 213 | +"or for use as a standalone archive cleaner:\n" |
| 214 | +"e.g.\n" |
| 215 | +" pg_archivecleanup /mnt/server/archiverdir 000000010000000000000010.00000020.backup\n"); |
| 216 | +printf("\nOptions:\n"); |
| 217 | +printf(" -d generates debug output (verbose mode)\n"); |
| 218 | +printf(" --help show this help, then exit\n"); |
| 219 | +printf(" --version output version information, then exit\n"); |
| 220 | +printf("\nReport bugs to <pgsql-bugs@postgresql.org>.\n"); |
| 221 | +} |
| 222 | + |
| 223 | +/*------------ MAIN ----------------------------------------*/ |
| 224 | +int |
| 225 | +main(intargc,char**argv) |
| 226 | +{ |
| 227 | +intc; |
| 228 | + |
| 229 | +progname=get_progname(argv[0]); |
| 230 | + |
| 231 | +if (argc>1) |
| 232 | +{ |
| 233 | +if (strcmp(argv[1],"--help")==0||strcmp(argv[1],"-?")==0) |
| 234 | +{ |
| 235 | +usage(); |
| 236 | +exit(0); |
| 237 | +} |
| 238 | +if (strcmp(argv[1],"--version")==0||strcmp(argv[1],"-V")==0) |
| 239 | +{ |
| 240 | +puts("pg_archivecleanup (PostgreSQL) "PG_VERSION); |
| 241 | +exit(0); |
| 242 | +} |
| 243 | +} |
| 244 | + |
| 245 | +while ((c=getopt(argc,argv,"d"))!=-1) |
| 246 | +{ |
| 247 | +switch (c) |
| 248 | +{ |
| 249 | +case'd':/* Debug mode */ |
| 250 | +debug= true; |
| 251 | +break; |
| 252 | +default: |
| 253 | +fprintf(stderr,"Try \"%s --help\" for more information.\n",progname); |
| 254 | +exit(2); |
| 255 | +break; |
| 256 | +} |
| 257 | +} |
| 258 | + |
| 259 | +/* |
| 260 | + * We will go to the archiveLocation to check restartWALFileName. |
| 261 | + * restartWALFileName may not exist anymore, which would not be an error, so |
| 262 | + * we separate the archiveLocation and restartWALFileName so we can check |
| 263 | + * separately whether archiveLocation exists, if not that is an error |
| 264 | + */ |
| 265 | +if (optind<argc) |
| 266 | +{ |
| 267 | +archiveLocation=argv[optind]; |
| 268 | +optind++; |
| 269 | +} |
| 270 | +else |
| 271 | +{ |
| 272 | +fprintf(stderr,"%s: must specify archive location\n",progname); |
| 273 | +fprintf(stderr,"Try \"%s --help\" for more information.\n",progname); |
| 274 | +exit(2); |
| 275 | +} |
| 276 | + |
| 277 | +if (optind<argc) |
| 278 | +{ |
| 279 | +restartWALFileName=argv[optind]; |
| 280 | +optind++; |
| 281 | +} |
| 282 | +else |
| 283 | +{ |
| 284 | +fprintf(stderr,"%s: must specify restartfilename\n",progname); |
| 285 | +fprintf(stderr,"Try \"%s --help\" for more information.\n",progname); |
| 286 | +exit(2); |
| 287 | +} |
| 288 | + |
| 289 | +if (optind<argc) |
| 290 | +{ |
| 291 | +fprintf(stderr,"%s: too many parameters\n",progname); |
| 292 | +fprintf(stderr,"Try \"%s --help\" for more information.\n",progname); |
| 293 | +exit(2); |
| 294 | +} |
| 295 | + |
| 296 | +/* |
| 297 | + * Check archive exists and other initialization if required. |
| 298 | + */ |
| 299 | +Initialize(); |
| 300 | + |
| 301 | +/* |
| 302 | + * Check filename is a valid name, then process to find cut-off |
| 303 | + */ |
| 304 | +SetWALFileNameForCleanup(); |
| 305 | + |
| 306 | +if (debug) |
| 307 | +{ |
| 308 | +fprintf(stderr,"%s: keep WAL file %s and later",progname,exclusiveCleanupFileName); |
| 309 | +fflush(stderr); |
| 310 | +} |
| 311 | + |
| 312 | +/* |
| 313 | + * Remove WAL files older than cut-off |
| 314 | + */ |
| 315 | +CleanupPriorWALFiles(); |
| 316 | + |
| 317 | +exit(0); |
| 318 | +} |