/*-------------------------------------------------------------------------
 *
 * pg_arch.c
 *	  A utility to archive xlogs to an archive directory
 *	  Uses the XLogArchive API to decide when to archive particular xlogs
 *
 * Copyright (c) Simon Riggs <simon@2ndQuadrant.com>, 2004;
 * licence: BSD
 *
 * Portions Copyright (c) 2004, PostgreSQL Global Development Group

 * Usage Notes:
 *	  Will use PGDATA if no DATADIR is supplied
 *
 * 	  During archive, the name and filedates of the xlog are NOT changed!
 *					
 * 	  If you have multiple PostgreSQL instances on one machine, then you 
 *	  SHOULD NOT use pg_arch to copy all xlogs from all instances to the 
 * 	  same archive directory - the xlogs will be indistinguishable and
 *	  recovery will be impossible. Create an archive directory for each instance.
 *
 * Program overview:
 *	  1. Initialises, then enters main loop
 *	  2. Main Loop: 
 *		XLogArchiveXLogs( ) to check for files to be archived
 *			If there is a file
 *				Copy file to <ArchiveDest>
 *				XLogArchiveComplete( )
 *			Else
 * 				Wait for <CheckTimer>
 *			Loop again forever
 *	  3. Handle any signals that get sent
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <locale.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "access/xlog.h"

/*
#include "xarchive.h"
*/

#define _(x) gettext((x))

/******************** stuff copied from xlog.c ********************/

/* Increment an xlogid/segment pair */
#define NextLogSeg(logId, logSeg)	\
	do { \
		if ((logSeg) >= XLogSegsPerFile-1) \
		{ \
			(logId)++; \
			(logSeg) = 0; \
		} \
		else \
			(logSeg)++; \
	} while (0)

#define XLogFileName(path, log, seg)	\
			snprintf(path, MAXPGPATH, "%s/%08X%08X",	\
					 XLogDir, log, seg)

/******************** end of stuff copied from xlog.c ********************/

static char *progname;
extern char *optarg;

static char *DataDir;
static char *ArchiveDestDir;

static char XLogDir[MAXPGPATH];
static char XLogArchiveDir[MAXPGPATH];		/* XLogArchive API */

	uint32		nextrlogId = -1;
	uint32		nextrlogSeg;

/* Function prototypes */
static bool XLogArchiveXLogs(char *xlog, char *rlogdir);
static bool XLogArchiveComplete(char *xlog, char *rlogdir);

static bool CopyXLogtoArchive(char *xlog);

static void usage(void);

/*
 * XLogArchiveXLogs
 *
 * Return name of the oldest xlog file that has not yet been archived,
 * setting notification that file archiving is now in progress. 
 * It is important that we return the oldest, so that we archive xlogs
 * in order that they were written, for two reasons: 
 * 1) to maintain the sequential chain of xlogs required for recovery 
 * 2) because the oldest ones will sooner become candidates for 
 * recycling by checkpoint backend.
 */
static bool 
XLogArchiveXLogs(char *xlog, char *rlogdir)
{
/* implementation:
 * if first call, open XLogArchive directory and read through list of 
 * rlogs that have the .full suffix, looking for earliest file. 
 * Decode xlog part of rlog filename back to 
 * log/seg values, then increment, so we can predict next rlog.
 * If not first call, use remembered next rlog value in call to stat,
 * to see if that file is available yet. If not, return empty handed.
 * If so, set rlog file to .busy, increment rlog value again and then 
 * return name of available file to allow copy to archive to begin.
 */

	char		rlogfull[MAXPGPATH];
	char		rlogbusy[MAXPGPATH];
	char		newxlog[32];
	char		nextlogstr[8];

	DIR		*rldir;
	struct dirent 	*rlde;
	char		*endptr;
	int 		rc;
	struct stat	statbuf;
	bool		firstfile;

	if (nextrlogId == -1) {
		rldir = opendir(rlogdir);
		if (rldir == NULL)
			fprintf(stderr, _("%s could not open rlog directory\n"), progname);	

		printf(_("\n%s firstcall: scanning rlogdir...\n"), progname);
		firstfile = true;
		while ((rlde = readdir(rldir)) != NULL)
		{
/*
			printf(_("\n%s found... %s\n"), progname, rlde->d_name);
			if (strlen(rlde->d_name) == 21)
				printf(_("\n%s namelen=21\n"), progname);
			if (strspn(rlde->d_name, "0123456789ABCDEF") == 16)
				printf(_("\n%s composed of hex chars\n"), progname);
			if (strcmp(rlde->d_name + 16, ".full") == 0)
				printf(_("\n%s name+16=full\n"), progname);
 */
			if (strlen(rlde->d_name) == 21 &&
				strspn(rlde->d_name, "0123456789ABCDEF") == 16 &&
				strcmp(rlde->d_name + 16, ".full") == 0)
			{
/*
				printf(_("\n%s identify... %s\n"), progname, rlde->d_name);
 */
			    if (firstfile) {
				strcpy(newxlog, rlde->d_name);
				firstfile = false;
			    } else {
				/* strip off the suffix to get xlog name */
				if (strcmp(rlde->d_name, newxlog) <= 0) 
					strcpy(newxlog, rlde->d_name);
			    }
			}
		}
		printf(_("%s closing rlogdir...\n"), progname);
		rc = closedir(rldir);
		if (rc < 0)
			fprintf(stderr, _("%s could not close rlog directory %i\n"), progname,rc);	

		if (firstfile) {
			printf(_("%s no .full rlogs found...\n"), progname);
			return false;
		}
		printf(_("%s found...%s\n"), progname,  newxlog);

		/* decode xlog back to LogId and SegId, so we can increment */
		sprintf(nextlogstr,"00000000");
		memcpy(nextlogstr, newxlog, 8);
		nextrlogId = strtoul(nextlogstr, &endptr, 16);
		if (endptr == nextlogstr || *endptr != '\0')
		{
			fprintf(stderr, _("%s decode xlog logID error\n"), progname);
			exit(1);
		}
		memcpy(nextlogstr, newxlog+8, 8);
		nextrlogSeg = strtoul(nextlogstr, &endptr, 16);
		if (endptr == nextlogstr || *endptr != '\0')
		{
			fprintf(stderr, _("%s decode xlog logSeg error\n"), progname);
			exit(1);
		}
		memcpy(xlog, newxlog, 16);

		/* set the rlog to .busy until XLogArchiveComplete is called */
		snprintf(rlogfull, MAXPGPATH, "%s/%s.full", rlogdir, xlog);
		snprintf(rlogbusy, MAXPGPATH, "%s/%s.busy", rlogdir, xlog);
		rc = rename (rlogfull, rlogbusy);
		if (rc < 0) {
			fprintf(stderr, 
			   _("%s XLogArchiveXLogs could not rename %s to %s\n"), 
			   progname, rlogfull, rlogbusy);
			return false;
		}

	} 
	else {
		snprintf(nextlogstr, 32, "%08X%08X", 
				nextrlogId, nextrlogSeg);
		snprintf(rlogfull, MAXPGPATH, "%s/%s.full", 
				rlogdir, nextlogstr);
		rc = stat (rlogfull, &statbuf);
		/* if .full file is not there...that's OK...we wait until it is */
		if (rc < 0) {

			/* Good error checking required here, otherwise we might loop
			   forever, slowly! */
			printf(_("%s %s not found yet...\n"), progname,nextlogstr);

			return false;
		}

		/* set the xlog that will be archived next */
		sprintf(xlog, "%08X%08X", nextrlogId, nextrlogSeg);

		/* set the rlog to .busy until XLogArchiveComplete is called */
		snprintf(rlogbusy, MAXPGPATH, "%s/%s.busy", rlogdir, xlog);
		rc = rename (rlogfull, rlogbusy);
		if (rc < 0) {
			fprintf(stderr, 
			   _("%s XLogArchiveComplete could not rename %s to %s\n"), 
			   progname, rlogfull, rlogbusy);
			return false;
		}
	}
	/* increment onto the next rlog */
	NextLogSeg(nextrlogId, nextrlogSeg);
	
	/* we have an xlog to archive...*/
	return true;
}

/* 
 * XLogArchiveComplete
 *
 * Write notification that an xlog has now been successfully archived
 */
static bool 
XLogArchiveComplete(char *xlog, char *rlogdir)
{

/* implementation:
 * stat the notification file as xlog filename with .busy suffix
 * Rename the notification file to a suffix of .done
 */
	char		rlogbusy[MAXPGPATH];
	char		rlogdone[MAXPGPATH];

	int 		rc;
	struct stat	statbuf;

	snprintf(rlogbusy, MAXPGPATH, "%s/%s.busy", rlogdir, xlog);
	rc = stat (rlogbusy, &statbuf);
	if (rc < 0) {
		fprintf(stderr, 
		   _("%s XLogArchiveComplete could not locate %s\n"), progname, rlogbusy);
		return false;
	}

/*
	archive_time_sec = time() - statbuf->st_mtime;
	printf("%s archive elapsed time = %n", archive_time_sec);
 */

	snprintf(rlogdone, MAXPGPATH, "%s/%s.done", rlogdir, xlog);
	rc = rename (rlogbusy, rlogdone);
	if (rc < 0) {
		fprintf(stderr, _("%s XLogArchiveComplete could not rename %s to %s\n"),
			progname, rlogbusy, rlogdone);
		return false;
	}

	return true;
}

/*
 * CopyXLogtoArchive
 *
 * Copy transaction log from the pg_xlog directory of a PostgreSQL instance identified
 * by the DATADIR parameter through to an archive destination, ARCHIVEDESTDIR
 *
 * Should ignore signals during this section, to allow archive to complete
 */
static bool
CopyXLogtoArchive(char *xlog)
{

/* Implementation:
 * We open the archive file using O_SYNC to make sure no mistakes
 * writing data in buffers equal to the blocksize, so we will
 * always have at least a partially consistent set of data to recover from
 * ...then we check filesize of written file to ensure we did it right
 */
	char 	xlogpath[MAXPGPATH];
	char 	archpath[MAXPGPATH];

	int 	n, xlogfd, archfd;
	char 	buf[BLCKSZ];
	int rc;
	struct stat	statbuf;

	snprintf(xlogpath, MAXPGPATH, "%s/%s", XLogDir, xlog);
	printf(_("%s xlogpath= %s\n"), progname, xlogpath);	
	rc = stat(xlogpath, &statbuf);
	if (rc < 0 ) {
		fprintf(stderr, _("%s xlog does not exist\n"), progname);
		return false;
	}

	snprintf(xlogpath, MAXPGPATH, "%s/%s", XLogDir, xlog);
	xlogfd = open(xlogpath, 
			O_RDONLY);
		if (errno != ENOENT)
		{	
			if (xlogfd == EACCES)
				fprintf(stderr, _("%s EACCES\n"), progname);	
			if (xlogfd < 0)
				return false;
		}
	fprintf(stderr, _("%s xlog file opened\n"), progname);	

	snprintf(archpath, MAXPGPATH, "%s/%s", ArchiveDestDir, xlog);
	archfd = open(archpath, O_RDWR | O_CREAT | O_EXCL | O_SYNC | PG_BINARY,
			  	S_IRUSR | S_IWUSR | 
				S_IRGRP | S_IWGRP);
	if (archfd < 0) {
		if (errno == EEXIST) {
			fprintf(stderr, _("%s archive file %s already exists in %s\n"), progname, xlog, ArchiveDestDir);
			exit(1);
		}
		return false;
	}
	fprintf(stderr, _("%s archive file opened\n"), progname);	

	while ( (n = read( xlogfd, buf, BLCKSZ)) > 0)
		if ( write( archfd, buf, n) != n)
			return false;
		if (n < 0)
			return false;

	fprintf(stderr, _("%s archive written...\n"), progname);	
	archfd = close(archfd);
	if (archfd < 0)
		return false;

	/* Should stat the archpath, to check filesize == XLogFileSize */

	/* Reset the file date/time on the xlog, to maintain the original
	 * timing of the xlog final write by PostgreSQL
	 */

	xlogfd = close(xlogfd);
	if (xlogfd < 0)
		return false;

	return true;
}

int
main(int argc, char *argv[])
{
    	char	xlog[16];

/* Options read in from command line, or defaults */
/* option t */
	int		ArchiveCheckLoopTime = 3;
/* option n */
	bool		noarchive = false;
/* option s */
	bool		noloop = false;

	int	c;
	char	*endptr;

	setlocale(LC_ALL, "");
#ifdef ENABLE_NLS
	bindtextdomain("pg_arch", LOCALEDIR);
	textdomain("pg_arch");
#endif

	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_arch (PostgreSQL) " PG_VERSION);
			exit(0);
		}
	}

	while ((c = getopt(argc, argv, "snt:")) != -1)
	{
		switch (c)
		{
			case 'n':
				noarchive = true;
				break;
	
			case 's':
				noloop = true;
				break;

			case 't':
				ArchiveCheckLoopTime = strtoul(optarg, &endptr, 0);
				if (endptr == optarg || *endptr != '\0')
				{
				  fprintf(stderr, 
					_("%s invalid argument for option -t\n"), 
					progname);
				  fprintf(stderr, 
					_("Try \"%s --help\" for more information.\n"),
					 progname);
				  exit(1);
				}
				if (ArchiveCheckLoopTime < 1)
				{
				  fprintf(stderr, 
					_("%s wait time (-t) must be > 0\n"), 
					progname);
				  exit(1);
				}
				if (ArchiveCheckLoopTime > 999)
				{
				  fprintf(stderr, 
					_("%s wait time (-t) must be < 1000\n"), 
					progname);
				  exit(1);
				}
				break;

			default:
				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
				exit(1);
		}
	}

	if (optind == argc)
	{
		fprintf(stderr, _("%s no archive directory specified\n"), progname);
		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
		exit(1);
	}
	ArchiveDestDir = argv[optind++];

	if (optind == argc)
		DataDir = getenv("PGDATA");
	else
		DataDir = argv[optind];
	if (DataDir == NULL)
	{
		fprintf(stderr, _("%s no data directory specified\n"), progname);
		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
		exit(1);
	}


	snprintf(XLogDir, MAXPGPATH, "%s/pg_xlog", DataDir);

	fprintf(stderr, _("%s Archiving transaction logs from %s to %s\n"), 
		progname, XLogDir, ArchiveDestDir);

	snprintf(XLogArchiveDir, MAXPGPATH, "%s/pg_rlog", DataDir);

/* File/Directory Permissions required:
 * pg_arch should run as either:
 *  i) database-owning userid i.e. postgres
 * ii) another user in the same group as database-owning userid
 *
 * Permissions required are:
 *  XLogDir		r
 *  XLogArchiveDir	rw
 *  ArchiveDestDir	w
 *
 * Security options are:
 *  i) add XLogArchiveDir under DataDir
 * 	allow access to ArchiveDestDir
 * ii) 	chmod 760 DataDir
 *     	chmod 760 XLogArchiveDir
 *	chmod 740 XLogDir
 *
 * Let's test our access rights to these directories now. At this stage
 * all of these directories may be empty, or not, without error.
 */

/* check directory XLogDir */

/* check directory ArchiveDestDir 
 * Directory must NOT have World read rights - security hole
 */

	/*
	 * XLogArchive environment creation & connection to PostgreSQL
	 *
	 * Currently, there isn't any. If there was, it would go here
	 *
	 */

/* Main Loop */
    do
    {
	if (XLogArchiveXLogs(xlog, XLogArchiveDir)) {
		printf(_("%s archive starting for transaction log %s\n"), progname, xlog);
		if (noarchive || (!noarchive && CopyXLogtoArchive(xlog))) {
			if (XLogArchiveComplete(xlog, XLogArchiveDir))
				fprintf(stderr, 
				  _("%s archive complete for transaction log %s \n\n"), 
				  progname, xlog);
			else {
				fprintf(stderr,
				  _("%s XLogArchiveComplete error\n"), progname);
				exit(1);
			}
		} else {
			fprintf(stderr,
			  _("%s archive copy error\n"), progname);
			exit(1);
		}
		/* if we have copied one file, we do not wait:
		   immediately loop back round and check to see if another is there.
		   If we're too quick....then we wait
		*/
		
	} else
	{
		printf(_("%s sleeping...\n"), progname);
		sleep(ArchiveCheckLoopTime);
		printf(_("%s .....awake\n"), progname);
	}
    } while (!noloop);

	printf(_("%s ending\n"), progname);
	/*
	 * XLogArchive disconnection from PostgreSQL & environment tear-down 
	 *
	 * Currently, there isn't any. If there was, it would go here
	 *
	 */
	return 0;
}

static void
usage(void)
{
	printf(
	 _("%s copies PostgreSQL transaction log files to an archive directory.\n\n"),
	 progname);
	printf(_("Usage:\n  %s [OPTIONS]... ARCHIVEDESTDIR [DATADIR]\n\n"), progname);
	printf(_("Options:\n"));
	printf(_("  -t              wait time (secs) between checks for xlogs to archive\n"));
	printf(_("  -n              no archival, just show xlog file names (for testing)\n"));
	printf(_("  -s              single execution - archive all full xlogs then stop\n"));
	printf(_("  --help          show this help, then exit\n"));
	printf(_("  --version       output version information, then exit\n"));
	printf(_("\nIf no data directory is specified, the environment variable PGDATA\n"));
	printf(_("is used. An archive destination must be specified. Default wait=30 secs.\n"));
	printf(_("\nReport bugs to <pgsql-bugs@postgresql.org>.\n"));
}
