pg_archivecleanup.tar

application/x-tar

Filename: pg_archivecleanup.tar
Type: application/x-tar
Part: 0
Message: Re: Command to prune archive at restartpoints
pg_archivecleanup/0000755000175000017500000000000011404242634014113 5ustar  sriggssriggspg_archivecleanup/Makefile0000644000175000017500000000065111404175112015551 0ustar  sriggssriggs# $PostgreSQL$

PGFILEDESC = "pg_archivecleanup - cleans archive when used with streaming replication"
PGAPPICON=win32

PROGRAM = pg_archivecleanup
OBJS	= pg_archivecleanup.o

ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/pg_archivecleanup
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
pg_archivecleanup/pg_archivecleanup.c0000644000175000017500000001564111404206413017740 0ustar  sriggssriggs/*
 * $PostgreSQL$
 *
 * pg_archivecleanup.c
 *
 * Production-ready example of an archive_cleanup_command
 * used to clean an archive when using standby_mode = on
 *
 * Original author:		Simon Riggs  simon@2ndquadrant.com
 * Current maintainer:	Simon Riggs
 */
#include "postgres_fe.h"

#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#ifdef WIN32
int			getopt(int argc, char *const argv[], const char *optstring);
#else
#include <sys/time.h>
#include <unistd.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#endif   /* ! WIN32 */

extern char *optarg;
extern int	optind;

const char *progname;

/* Options and defaults */
bool		debug = false;		/* are we debugging? */

char	   *archiveLocation;	/* where to find the archive? */
char	   *restartWALFileName; /* the file from which we can restart restore */
char		WALFilePath[MAXPGPATH];		/* the file path including archive */
char		exclusiveCleanupFileName[MAXPGPATH];		/* the file we need to
														 * get from archive */

struct stat stat_buf;

/* =====================================================================
 *
 *		  Customizable section
 *
 * =====================================================================
 *
 *	Currently, this section assumes that the Archive is a locally
 *	accessible directory. If you want to make other assumptions,
 *	such as using a vendor-specific archive and access API, these
 *	routines are the ones you'll need to change. You're
 *	enouraged to submit any changes to pgsql-hackers@postgresql.org
 *	or personally to the current maintainer. Those changes may be
 *	folded in to later versions of this program.
 */

#define XLOG_DATA_FNAME_LEN		24
/* Reworked from access/xlog_internal.h */
#define XLogFileName(fname, tli, log, seg)	\
	snprintf(fname, XLOG_DATA_FNAME_LEN + 1, "%08X%08X%08X", tli, log, seg)

/*
 *	Initialize allows customized commands into the archive cleanup program.
 */
static void
Initialize(void)
{
	/*
	 * This code assumes that archiveLocation is a directory You may wish to
	 * add code to check for tape libraries, etc.. So, since it is a
	 * directory, we use stat to test if it's accessible
	 */
	if (stat(archiveLocation, &stat_buf) != 0)
	{
		fprintf(stderr, "%s: archiveLocation \"%s\" does not exist\n", progname, archiveLocation);
		fflush(stderr);
		exit(2);
	}
}

static void
CleanupPriorWALFiles(void)
{
	int			rc;
	DIR		   *xldir;
	struct dirent *xlde;

	if ((xldir = opendir(archiveLocation)) != NULL)
	{
		while ((xlde = readdir(xldir)) != NULL)
		{
			/*
			 * We ignore the timeline part of the XLOG segment identifiers
			 * in deciding whether a segment is still needed.  This
			 * ensures that we won't prematurely remove a segment from a
			 * parent timeline. We could probably be a little more
			 * proactive about removing segments of non-parent timelines,
			 * but that would be a whole lot more complicated.
			 *
			 * We use the alphanumeric sorting property of the filenames
			 * to decide which ones are earlier than the
			 * exclusiveCleanupFileName file. Note that this means files
			 * are not removed in the order they were originally written,
			 * in case this worries you.
			 */
			if (strlen(xlde->d_name) == XLOG_DATA_FNAME_LEN &&
				strspn(xlde->d_name, "0123456789ABCDEF") == XLOG_DATA_FNAME_LEN &&
			  strcmp(xlde->d_name + 8, exclusiveCleanupFileName + 8) < 0)
			{
#ifdef WIN32
				snprintf(WALFilePath, MAXPGPATH, "%s\\%s", archiveLocation, xlde->d_name);
#else
				snprintf(WALFilePath, MAXPGPATH, "%s/%s", archiveLocation, xlde->d_name);
#endif

				if (debug)
					fprintf(stderr, "\nremoving \"%s\"", WALFilePath);

				rc = unlink(WALFilePath);
				if (rc != 0)
				{
					fprintf(stderr, "\n%s: ERROR failed to remove \"%s\": %s",
							progname, WALFilePath, strerror(errno));
					break;
				}
			}
		}
		if (debug)
			fprintf(stderr, "\n");
	}
	else
		fprintf(stderr, "%s: archiveLocation \"%s\" open error\n", progname, archiveLocation);

	closedir(xldir);
	fflush(stderr);
}

/*
 * SetWALFileNameForCleanup()
 *
 *	  Set the earliest WAL filename that we want to keep on the archive
 *	  and decide whether we need_cleanup
 */
static void
SetWALFileNameForCleanup(void)
{
	strcpy(exclusiveCleanupFileName, restartWALFileName);
}

/* =====================================================================
 *		  End of Customizable section
 * =====================================================================
 */

static void
usage(void)
{
	printf("%s allows PostgreSQL archives to be cleaned when standby_mode=on.\n\n", progname);
	printf("Usage:\n");
	printf("  %s [OPTION]... ARCHIVELOCATION RESTARTWALFILE\n", progname);
	printf("\n"
		"with main intended use as a archive_cleanup_command in the recovery.conf:\n"
		   "  archive_cleanup_command = 'pg_archivecleanup [OPTION]... ARCHIVELOCATION %%r'\n"
		   "e.g.\n"
		   "  archive_cleanup_command = 'pg_archivecleanup /mnt/server/archiverdir %%r'\n");
	printf("\nOptions:\n");
	printf("  -d                 generate lots of debugging output (testing only)\n");
	printf("  --help             show this help, then exit\n");
	printf("  --version          output version information, then exit\n");
	printf("\nReport bugs to <pgsql-bugs@postgresql.org>.\n");
}

/*------------ MAIN ----------------------------------------*/
int
main(int argc, char **argv)
{
	int			c;

	progname = get_progname(argv[0]);

	if (argc > 1)
	{
		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
		{
			usage();
			exit(0);
		}
		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
		{
			puts("pg_archivecleanup (PostgreSQL) " PG_VERSION);
			exit(0);
		}
	}

	while ((c = getopt(argc, argv, "dk:")) != -1)
	{
		switch (c)
		{
			case 'd':			/* Debug mode */
				debug = true;
				break;
			default:
				fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
				exit(2);
				break;
		}
	}

	/*
	 * We will go to the archiveLocation to get nextWALFileName.
	 * restartWALFileName may not exist anymore, which would not be an error, so
	 * we separate the archiveLocation and nextWALFileName so we can check
	 * separately whether archiveLocation exists, if not that is an error
	 */
	if (optind < argc)
	{
		archiveLocation = argv[optind];
		optind++;
	}
	else
	{
		fprintf(stderr, "%s: must specify archive location\n", progname);
		fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
		exit(2);
	}

	if (optind < argc)
	{
		restartWALFileName = argv[optind];
		optind++;
	}
	else
	{
		fprintf(stderr, "%s: must specify restartfilename\n", progname);
		fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
		exit(2);
	}

	if (optind < argc)
	{
		fprintf(stderr, "%s: too many parameters\n", progname);
		fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
		exit(2);
	}

	Initialize();

	SetWALFileNameForCleanup();

	if (debug)
	{
		fprintf(stderr, "Keep archive history	: %s and later", exclusiveCleanupFileName);
		fflush(stderr);
	}

	CleanupPriorWALFiles();

	exit(0);
}