#!/bin/sh
set -e
#
# Script postgresql-dump
#
# This script is used to dump and destroy a database when PostgreSQL is updated
# to an incompatible database format.

# Original version by Oliver Elphick for the Debian Project.

# Modified for RPM use Sep 18 1999 Lamar Owen <lamar.owen@wgcr.org> Version 1.0

# Version 1.1 for RPMs for 7.0beta1 -- changed PGDATA location for new version.

function syntax() {
	help
	exit 1
}

function help() {
	echo Usage:
	echo `basename $0` [-c] [-d] [-f] [-i] [-l] [-p directory] -t target [-v] [-x]
	echo `basename $0` -h
	echo "       where target is a file or tape device."
	echo "      -c        list the ASCII dump to screen for the user to check"
	echo "      -d        destroy the old database framework"
	echo "      -f        fix the change of 'current' to 'old' in rules"
	echo "      -h        print this help message"
	echo "      -i        create a new database framework with initdb"
	echo "      -l        load the ASCII dump into the new database"
	echo "      -p dir    preserve the old database framework under directory <dir>"
	echo "      -t target create the ASCII dump on file or device <target>"
	echo "      -u        dump only the database structure; use pg_upgrade to fill this"
	echo "                from the existing database"
	echo "      -v        verbose output"
	echo "      -x        do not dump the old database; forces -c"
}

function add_option() {
	if [ -n "$OPTIONS" ]
	then
		OPTIONS=$OPTIONS\ $1
	else
		OPTIONS=$1
	fi
	shift

	while [ -n "$1" ]
	do
		OPTIONS=$OPTIONS\ $1
		shift
	done
}

function checkpath() {
	dir=$1
	while [ -n "`echo $dir | awk -F/ '{print $2}'`" ]
	do
		if [ $dir = $PGDATA ]
		then
			echo $1 is or is under PGDATA. It will be destroyed when PGDATA is cleared. >&2
			echo Specify a different path.>&2
			exit 11
		fi
		dir=`dirname $dir`
	done
}

SHELL=/bin/sh
current=7.0          # Current PostgreSQL database version
check=
destroy=
create=
load=
sed_command=cat
preserve=
upgrade=
verbose=
exclude_dump=
MT=/bin/mt

# Read command line
while [ $OPTIND -le $# ]
do
	getopts cdfilp:t:uvx arg
	case "$arg"
		in
		c)
			check=TRUE;;
		d)
			destroy=TRUE;;
		f)
			sed_command="sed -e '/^CREATE RULE /s/current\./old./g'";;
		h)
			help
			exit 0;;
		i)
			create=TRUE;;
		l)
			load=TRUE;;
		p)
			preserve=$OPTARG;;
		t)
			target=$OPTARG;;
		u)
			upgrade=upgrade;;
		v)
			verbose=TRUE;;
		x)
			exclude_dump=TRUE
			check=TRUE;;
		*)
			syntax;;
	esac
done

if [ -z "${exclude_dump}" ]
then
	check=TRUE
fi

if [ -z "${destroy}" ]
then
	preserve=
fi

if [ -z "${target}" ]
then
	echo No target file or device specified >&2
	syntax
else
	checkpath ${target}
fi


PGDATA=${POSTGRES_DATA:=/var/lib/pgsql/data}
PGLIB=/usr/lib/pgsql
export PGLIB PGDATA LANG

# Since this script has to dump the entire database, it must be run by the
# PostgreSQL superuser
if [ `id | cut -d\( -f2 | cut -d\) -f1` != postgres ]
then
	echo $0: this script must be run by the user postgres. >&2
	exit 2
fi

# Make sure either the ${PGDATA} directory or the dump target exists
dumped=
if [ ! -d ${PGDATA:=/var/lib/pgsql}/base ]
then
	if [ ! -f ${target} ]
	then
		echo The PostgreSQL data directory, ${PGDATA}, and the dump file are missing. >&2
		exit 3
	else
		dumped=TRUE
		destroy=""
		preserve=""
	fi
fi
if [ -n "${preserve}" ]
then
	checkpath ${preserve}
fi


load_only=
if [ -z "${dumped}" ]
then
	# Find out what the current database format version is
	installed=`cat ${PGDATA}/PG_VERSION`
	must_dump=

	if [ -z "${installed}" ]
	then
		echo "There is no existing installation of Postgresql" >&2
		if [ -d ${PGDATA}/template1 ]
		then
			echo However, there is a template1 directory in ${PGDATA}, which >&2
			echo is unexpected. Please analyse and repair the directory by >&2
			echo hand. This script is unable to handle it in its present >&2
			echo state. >&2
			echo >&2
			exit 4
		fi
	else
		# check the current and installed version numbers
		majorc=`echo ${current} | cut -d. -f1`
		minorc=`echo ${current} | cut -d. -f2`
		majori=`echo ${installed} | cut -d. -f1`
		minori=`echo ${installed} | cut -d. -f2`
		ccomp=`expr 100 + $majorc``expr 100 + $minorc`
		icomp=`expr 100 + $majori``expr 100 + $minori`

		if [ ${ccomp} -eq ${icomp} ]
		then
			echo The PostgreSQL database format has not changed\; there is >&2
			echo no need to do a dump and restore with this script. >&2
			load_only=TRUE
		else
			must_dump=TRUE
			if [ ${ccomp} -ge 106105 -a ${icomp} -le 106104 -a -n "$upgrade" ]
			then
				echo "It is not possible to use the -u option to upgrade from 6.4 or lower"
				echo "to 6.5 or higher, because the internal structure of tables changed"
				echo "at 6.5."
				exit 1
			fi
			if [ $icomp -le 106104 ]
			then
				dump_options=-z
			fi
			if [ -n "$upgrade" ]
			then
				dump_options=$dump_options' -s'
			fi
		fi
	fi

	if [ -n "${must_dump}" -a -z "${exclude_dump}" ]
	then
		# Look for existing postgresql binaries, for the previous release
		export BINDIR=/usr/lib/pgsql/backup

		if [ ! -d ${BINDIR} ]
		then
			echo There are no binaries for dumping database format ${installed} >&2
			exit 5
		fi

		if [ -n "${verbose}" ]
		then
			echo "Stopping and restarting the postmaster" >&2
		fi
		pm_pid=`ps ax | grep -s postmaster | grep -v grep | awk '{print $1}'`
		if [ -n "$pm_pid" ]
		then
			kill $pm_pid
		fi

		OPTIONS=
		
		# American or European date format
		if [ "${PGDATESTYLE}" != American ]
		then
		    OPTIONS="-o -e"
		fi

		POSTMASTER=${BINDIR}/postmaster
		POSTGRES=${BINDIR}/postgres
		PORT="-p 5431"    # change the port to stop normal users connecting

		${POSTMASTER} -S -D ${PGDATA} ${AUTH} ${PORT} ${OPTIONS}

		new_pm_pid=`ps ax | grep -s postmaster | grep -v grep | awk '{print $1}'`
		if [ A${new_pm_pid} = A ]
		then
			echo "Failed to start the postmaster" 2>&1
			exit 7
		fi
		if [ A${new_pm_pid} = A${pm_pid} ]
		then
			echo "Failed to stop the running postmaster" 2>&1
			exit 6
		fi

		if [ -n "${verbose}" ]
		then
			if [ -n "$upgrade" ]
			then
				echo "Dumping the database structure to ${target}" >&2
			else
				echo "Dumping the database to ${target}" >&2
			fi
		fi
		echo "-- postgresql-dump ${upgrade} on `date` from version ${installed}" >$target
		/usr/share/pgsql/backup/pg_dumpall_new $dump_options >>$target
		echo "-- postgresql-dump ${upgrade} completed on `date`" >>$target

		if [ -n "${verbose}" ]
		then
			echo "Killing the postmaster" >&2
		fi
		kill $new_pm_pid

	fi
fi

if [ -f "$target" ]
then
	dumptype=`head -1 $target | cut -f3 -d\ `
	if [ "$dumptype" = upgrade ]
	then
		unset destroy	# We must not destroy the database, because the data
				# is not in the dump
	else
		dumptype=full
	fi
fi

if [ -n "${check}" ]
then
	if echo ${target} | grep -q '^/dev/'
	then
		[ ! -x "$MT" ] || "$MT" -f ${target} rewind
	else
		if [ ! -f ${target} ]
		then
			echo ASCII dump file ${target} does not exist.
			exit 13
		fi
	fi
	(echo This is the ASCII output of the dump for you to check:
	 echo
	 cat ${target} | eval ${sed_command}) | ${PAGER:-more}
	if [ -n "${destroy}" ]
	then
		echo -n On the basis of this dump, is it OK to delete the old database? [y/n]\ 
		read x
		case $x
			in
			y|Y|Yes|yes|YES)
				echo Destroying old database...
				;;
			*)
				exit 11;;
		esac
	else
		echo -n Is this dump suitable for loading into the new database? [y/n]\ 
		read x
		case $x
			in
			y|Y|Yes|yes|YES)
				;;
			*)
				exit 11;;
		esac
	fi
fi

if [ -n "${destroy}" -o -n "$upgrade" ]
then
	if [ -n "${preserve}" ]
	then
		# Copy the database tree to ${preserve} -- just in case...
		if [ -n "${verbose}" ]
		then
			echo "Copying ${PGDATA} to ${preserve}" >&2
		fi

		if [ ! -d ${preserve} ]
		then
			mkdir ${preserve} || exit 8
		fi

		# Use cp rather than mv in case we are being asked
		# to move between filesystems.
		cp -a $PGDATA/* ${preserve} ||
			(echo Could not copy ${PGDATA}/\* to ${preserve} >&2; exit 9)
	fi
	if [ -n "${verbose}" -a -n "${destroy}" ]
	then
		echo "Deleting ${PGDATA}" >&2
	fi

	if [ -n "$upgrade" ]
	then
		old_database=${PGDATA}.old
		if [ ! -d $old_database ]
		then
			if [ -n "${verbose}" ]
			then
				echo "Moving ${PGDATA} to $old_database" >&2
			fi
			mv ${PGDATA} $old_database
			mkdir $PGDATA
			chmod 700 $PGDATA
		fi
	else
		rm -rf ${PGDATA}/* ||
			(echo Could not delete ${PGDATA}/\* >&2
			exit 10)
	fi
fi

set +e
unset POSTGRES
unset POSTMASTER
unset PGLIB
unset PGDATA
unset PORT
unset BINDIR
PGLIB=/usr/lib/pgsql
PGDATA=/var/lib/pgsql
PORT=5432
 
set -e

if [ -n "${create}" ]
then
	if [ -n "${verbose}" ]
	then
		echo "Creating the new database structure" >&2
	fi

	failed=
	USER=postgres initdb || failed=TRUE
	if [ -n "$failed" ]
	then
		echo initdb failed
		exit 2
	fi
fi

if [ -n "${load}" ]
then
	if [ -n "${verbose}" ]
	then
		echo "Checking that the postmaster is running" >&2
	fi
	if [ -z "`ps ax | grep postmaster | grep -v grep`" ]
	then
		# start up the postmaster
		. /etc/rc.d/init.d/postgresql start
		PGDATA=${POSTGRES_DATA:=/var/lib/pgsql}
		PGLIB=/usr/lib/pgsql
		export PGLIB PGDATA PGPORT LANG
	fi

	if [ -n "${verbose}" ]
	then
		if [ -n "$upgrade" ]
		then
			echo "Loading the database structure from ${target}" >&2
		else
			echo "Loading the database from ${target}" >&2
		fi
	fi

	# Give the postmaster a chance to start up
	sleep 5
	echo Reloading databases...
	if [ -n "$upgrade" ]
	then
		cd ${PGDATA}/..
		echo Current directory now `pwd`
		rm -f ${target}.fixed
		eval ${sed_command} < ${target} > ${target}.fixed
		pg_upgrade -f $target.fixed `basename $old_database` 
		rm ${target}.fixed
	else
		eval ${sed_command} < $target |
			psql -e template1 >/tmp/db_reload.log 2>&1 &&
			(echo Reload succeeded; rm /tmp/db_reload.log) ||
			echo Reload failed - output is in /tmp/db_reload.log
	fi
fi

exit 0
