#!/bin/bash
################################################################################
# Copyright (c) 2013-2016 VMware, Inc. All rights reserved.
################################################################################
# Script which is called by archive_command
#
# Note that this script has been designed to work in an environment that
# has a dedicated partition layer like the one of the virtual appliance of
# PostgreSQL. Hence it should at least be used in an environment that uses
# different partitions mounted on the system for at least the following things:
# - PGDATA, where the data of the database is located.
# - pg_xlog, the local folder a PostgreSQL instance uses to store WAL files.
#   Note that those files should *never* be touched manually and should be
#   managed by the PostgreSQL instance itself.
# - WAL archive, where WAL is being stored using this script. Note that
#   this script will try to free enough space from the path where WAL
#   files are archived to give enough space for a single WAL file to be
#   archived. In normal builds, a single WAL file has a size of 16MB. If
#   this is not the case, then Postgres has been compiled with different
#   ./configure options than the default.
# If you do try to use this script in an environment designed differently,
# do it at your own risk.

THIS_DIR=`dirname $0`

# Ensure that global variables are loaded.
source /etc/profile

# Minimum check to ensure that environment variable able to redirect the
# rest of the settings is available. The rest of the sanity checks is done
# just after that.
if [ -z $VMWARE_POSTGRES_BASE ]; then
   echo "VMWARE_POSTGRES_BASE is not set."
   echo "Check your installation."
   exit 1
fi

# Sanity check for environment. Note that VMWARE_POSTGRES_BASE is used
# for consistency with the check done previously.
SANITY_FILE=$VMWARE_POSTGRES_BASE/scripts/vpostgres_sanity_checks
if [ -f $SANITY_FILE ]; then
   source $SANITY_FILE
else
   echo "Sanity check file for environment variables of VMware Postgres"
   echo "is not available. Check your installation."
   exit 1
fi

XLOG_FILE_PATH=$1
XLOG_FILE_NAME=$2

# Amount of free space necessary for at least one WAL file
ARCHIVE_FREESPACE_THRESHOLD=20480

# Error out immediately if one command fails
# Better to hold WAL files on server particularly if something is going
# awry at hardware level.
set -e

# Some log information for debugging
echo "Beginning archiving of $XLOG_FILE_NAME"

# Leave if file exists
if [ ! -e "$VMWARE_POSTGRES_ARCHIVE/$XLOG_FILE_NAME" ]
then
	echo "$XLOG_FILE_NAME not found yet in archives, continuing"
else
	echo "$XLOG_FILE_NAME found in archives... Leaving"
	exit 1
fi

# Look at the existing backups taken by pg_rman and determine what is the
# latest WAL file that can safely been removed. Only already validated
# backups can be used to purge old WAL files. This is determined by
# scanning the WAL archives of the latest backup available.

# Fetch timestamp of latest INCR|FULL|ARCH backup with OK status
LAST_BACKUP_TIME=`$VMWARE_POSTGRES_BIN/pg_rman show detail -B $VMWARE_POSTGRES_BACKUP -D $VMWARE_POSTGRES_DATA | grep -e FULL -e INCR -e ARCH | grep -e OK | head -n1 | cut -d ' ' -f 1-2`

# Now set pipe failure, pg_rman is rather unreliable with that.
set -o pipefail

# Calculate free space
free_space=`df -k $VMWARE_POSTGRES_ARCHIVE | tail -n 1 | tr -s ' ' | cut -d' ' -f 4`

# Leave if no backups found
if [ "$LAST_BACKUP_TIME" != "" ]
then
	# Now calculate the path of backup in VMWARE_POSTGRES_BACKUP. This is
	# hardcoded in pg_rman code so subject to change if format changes as
	# well in pg_rman.
	LAST_BACKUP_PATH=`date -d "$LAST_BACKUP_TIME" '+%Y%m%d/%H%M%S'`
	echo "Last backup time found: $LAST_BACKUP_TIME"
	LAST_BACKUP_PATH="$VMWARE_POSTGRES_BACKUP/$LAST_BACKUP_PATH/arclog"
	echo "Last backup path: $LAST_BACKUP_PATH"

	# Check the existence of last backup path, its presence is guaranteed
	# with pg_rman show but let's be sure and block anything that may lead
	# to backup corruption.
	if [ ! -d $LAST_BACKUP_PATH ]
	then
		echo "Backup detected by pg_rman but $LAST_BACKUP_PATH does not exist, leaving..."
		exit 1
	fi

	# Now determine what is the oldest WAL file that can safely be
	# removed.
	OLDEST_WAL_TO_DELETE=`ls -r $LAST_BACKUP_PATH | grep -E -x '[0-9A-Fa-f]{24}' | head -n1`

	# If this WAL file has already been deleted, process needs to do
	# nothing or it would finish scanning all the WALs in archives.
	if [ -f $VMWARE_POSTGRES_ARCHIVE/$OLDEST_WAL_TO_DELETE ]
	then
		echo "Newest WAL file of archives removable: $OLDEST_WAL_TO_DELETE"
		WAL_FILES_TO_DELETE=""

		# Now list the files that will be removed. We need to keep
		# OLDEST_WAL_TO_KEEP.
		for wal_file in `ls $VMWARE_POSTGRES_ARCHIVE | grep -E -x '[0-9A-Fa-f]{24}'`
		do
			WAL_FILES_TO_DELETE+=" $wal_file"
			if [ "$OLDEST_WAL_TO_DELETE" == $wal_file ]
			then
				break
			fi
		done

		# Finally remove them. As we detected one backup, we are sure
		# that this list contains at least one item.
		echo "Following WAL files are marked for removal in archives: $WAL_FILES_TO_DELETE"
		for wal_file in $WAL_FILES_TO_DELETE
		do
			rm -f $VMWARE_POSTGRES_ARCHIVE/$wal_file
			echo "Removal of $VMWARE_POSTGRES_ARCHIVE/$wal_file done"
		done
	else
		echo "Oldest unnecessary WAL file $OLDEST_WAL_TO_DELETE already deleted"
		echo "No need to remove any new WAL files."
	fi
else
	# No oldest backup found, then delete the oldest WAL file in partition
	# to make room for the new one if there is not enough space.
	if [ "$free_space" -lt $ARCHIVE_FREESPACE_THRESHOLD ]
	then
		WAL_FILE_TO_DELETE=`ls $VMWARE_POSTGRES_ARCHIVE | grep -E -x '[0-9A-Fa-f]{24}'`
		echo "No backups found, but just $free_space kB space left"
		echo "hence removing oldest WAL $WAL_FILE_TO_DELETE to make"
		echo "room for archiving of $XLOG_FILE_NAME"
		rm -f $VMWARE_POSTGRES_ARCHIVE/$WAL_FILE_TO_DELETE
		echo "Removal of $VMWARE_POSTGRES_ARCHIVE/$WAL_FILE_TO_DELETE done"
	fi
fi

# Even after all that if there is no space available just give up
# this creates contention on the server but user is responsible for
# its backups.
if [ "$free_space" -lt $ARCHIVE_FREESPACE_THRESHOLD ]
then
	echo "Not enough space available, cannot archive $XLOG_FILE_NAME"
	exit 1
fi

# Finally copy the new WAL file in the archive, ensuring that it is
# flushed to disk before leaving.
dd if="$XLOG_FILE_PATH" of="$VMWARE_POSTGRES_ARCHIVE/$XLOG_FILE_NAME" conv=fsync

# We're done here
echo "Finished archiving of $XLOG_FILE_NAME"
