#!/bin/bash
#include <genscript.warning>
set -e
#
# Script postgresql-dump
#
# This script is used to dump and destroy a database when PostgreSQL is updated
# to an incompatible database format.

check_access() {
 if grep -Eq '^[ 	]*local[ 	]+all[ 	]+trust' /etc/postgresql/pg_hba.conf
 then
    # we have unrestricted local access
	 :
 else
    echo "This script needs unrestricted access to be given in /etc/postgresq/pg_hba.conf
without having to give a password.  Use 'local all trust' as the first line
of the file. (If you see this message during automatic upgrading, it is a
bug, because the installation scripts should have arranged for that access
level.)"
    exit 1
 fi
 if grep -Eq '^[ 	]*local[ 	].*[ 	]peer[ 	]' /etc/postgresql/pg_hba.conf
 then
 	echo "/etc/postgresql/pg_hba.conf still includes lines that request 'peer'
authentication.  This is no longer valid; all occurrences of 'peer' must be
changed to 'ident'.

Please correct /etc/postgresql/pg_hba.conf before trying again."
	exit 1
 fi
}

syntax () {
	help
	exit 1
}

help () {
	echo Usage:
	echo "`basename $0` [options] -t target"
	echo "`basename $0` -h"
	echo "       where target is a file or tape device."
	echo "      -b blksz  Use dd with a blocksize of <blksz> to write the dump"
	echo "      -c        list the ASCII dump to screen for the user to check"
	echo "      -d        destroy the old database framework"
	echo "      -e encoding  Force the new framework to use this encoding as default"
	echo "      -f        fix the change of 'current' to 'old' in rules"
	echo "      -F        force a reload even if versions are up-to-date"
	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 "      -v        verbose output"
	echo "      -X        do not dump the old database; cancels -c"
	echo "      -x        do not dump the old database; forces -c"
}

restore_pg_options() {
	rm -f $PGDATA/pg_options
	if [ -f $PGDATA/pg_options.bak ]
	then
		mv $PGDATA/pg_options.bak $PGDATA/pg_options
	fi
}

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

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

checkpath () {
	dp=`dirname $1`
	dir=`(cd $dp; pwd)`   # cope with $PGDATA/../ and so on
	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
}

fixup_libpath () {
	if [ -z "$LD_LIBRARY_PATH" ];
	then
		LD_LIBRARY_PATH="/usr/lib/postgresql/dumpall/${installed}"
	else
		LD_LIBRARY_PATH="/usr/lib/postgresql/dumpall/${installed}:$LD_LIBRARY_PATH"
	fi
	export LD_LIBRARY_PATH
}

SHELL=/bin/sh

check_access

if [ ! -w /dev/tty ]
then
	echo "No write access to /dev/tty for user `id -nu`" >/dev/stdout
	exit 1
fi

current=%PG_VERSION%          # Current PostgreSQL database version
check=
destroy=
create=
load=
sed_command=cat
preserve=
verbose=
exclude_dump=
no_check=
force_reload=
blksz=
set +e
unset PGHOST
set -e

# Read command line
while [ $OPTIND -le $# ]
do
	getopts b:cde:Ffilp:t:vXx arg
	case "$arg"
		in
		b)
		   blksz=$OPTARG;;
		c)
			check=TRUE;;
		d)
			destroy=TRUE;;
		e)
			encoding=$OPTARG;;
		F)
			force_reload=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;;
		v)
			verbose=TRUE;;
		X)
			no_check=TRUE
			exclude_dump=TRUE;;
		x)
			exclude_dump=TRUE
			check=TRUE;;
		*)
			syntax;;
	esac
done

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

if [ -n "${no_check}" ]
then
	check=
fi

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

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

if [ -f /etc/postgresql/postmaster.conf ]
then
	. /etc/postgresql/postmaster.conf
fi

PGDATA=${POSTGRES_DATA:=/var/lib/postgres/data}
PGLIB=/usr/lib/postgresql
PATH=/usr/lib/postgresql/bin:/bin:/usr/bin
ENCODINGFILE=/usr/lib/postgresql/dumpall/default_encoding
export PGLIB PGDATA LANG PATH

# 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/postgres/data}/base ]
then
	if [ ! -f ${target} ]
	then
		echo The PostgreSQL data directory, ${PGDATA}, and the dump file are missing. >&2
		echo >&2
		echo Please check the settings in /etc/postgresql/postmaster.conf >&2
		exit 3
	else
		dumped=TRUE
		destroy=""
		preserve=""
	fi
fi
if [ -n "${preserve}" ]
then
	checkpath ${preserve}
fi

if [ -n "$blksz" ]
then
	writeout="dd of=$target bs=$blksiz"
	readin="dd if=$target bs=$blksiz"
	header="$readin count=1 | head -1"
else
	writeout="cat >$target"
	readin="cat $target"
	header="head -1 $target"
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} -a -z "${force_reload}" ]
		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 [ $icomp -le 106104 ]
			then
				dump_options=-z
			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/postgresql/dumpall/${installed}

		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='-o -d0'
		
		export LANG PGDATESTYLE

		# Check for old-style postmaster.conf
		if [ "${PGDATESTYLE}" = American -o "${PGDATESTYLE}" = European ]
		then
			if [ "${PGDATESTYLE}" = American ]
			then
				PGDATESTYLE=US
			else
				PGDATESTYLE=EURO
			fi
		elif [ -z "${PGDATESTYLE}" ]
		then
			PGDATESTYLE=EURO
		fi

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

		if [ -f $PGDATA/pg_options -o -L $PGDATA/pg_options ]
		then
			mv $PGDATA/pg_options $PGDATA/pg_options.bak
		fi
		rm -f $PGDATA/pg_options
		echo 'all = -1' > $PGDATA/pg_options

		echo "${POSTMASTER} -D ${PGDATA} ${AUTH} ${PORT} ${OPTIONS}"
		${POSTMASTER} -D ${PGDATA} ${AUTH} ${PORT} ${OPTIONS} &
		sleep 5

		# some older versions of postmaster put the socket in /tmp
		# rather than /var/run/postgresql
		if [ -S /tmp/.s.PGSQL.5431 ]
		then
			# make a symlink to cope, and arrange to destroy it
			# when this script exits
			ln -sf /tmp/.s.PGSQL.5431 /var/run/postgresql/.s.PGSQL.5431
			trap "rm /var/run/postgresql/.s.PGSQL.5431" EXIT
		fi

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

		if [ -n "${verbose}" ]
		then
			echo "Dumping the database to ${target}" >&2
		fi
		if dpkg --compare-versions $installed ge 7.1
		then
			PG_DUMPALL=/usr/lib/postgresql/bin/pg_dumpall
		else
			PG_DUMPALL=/usr/lib/postgresql/dumpall/${installed}/pg_dumpall
		fi

		echo $PG_DUMPALL "$dump_options"
		(
			if dpkg --compare-versions $installed lt 7.1
			then
				export PATH=/usr/lib/postgresql/dumpall/${installed}:$PATH
				fixup_libpath
			fi
			export PGPORT=5431
			echo "-- postgresql-dump on `date` from version ${installed}"
			$PG_DUMPALL $dump_options
			echo "-- postgresql-dump completed on `date`"
		) | eval $writeout

		if [ -n "${verbose}" ]
		then
			echo "Finding the default encoding" >&2
		fi
		(
			fixup_libpath
			/usr/lib/postgresql/bin/pg_encoding `${BINDIR}/psql $PORT -q -t -d template1 -c "select encoding from pg_database where datname = 'template1'"` >$ENCODINGFILE
		)
		if [ -n "${verbose}" ]
		then
			echo "Killing the postmaster" >&2
		fi
		kill $new_pm_pid
		restore_pg_options

	fi
fi

if [ -f "$target" ]
then
	dumptype=`eval $header | cut -f3 -d\ `
	if [ "$dumptype" = upgrade ]
	then
# this section was for use only with pg_upgrade
		#unset destroy	# We must not destroy the database, because the data
				## is not in the dump
		echo "You are attempting to upgrade with an 'upgrade only' dumpfile.
This is no longer supported, because pg_upgrade has been dropped."
		exit 14
	#else
		#dumptype=full
	fi
fi

if [ -n "${check}" ]
then
	if echo ${target} | grep -q '^/dev/'
	then
		mt -f ${target} rewind
	else
		if [ ! -f ${target} ]
		then
			echo ASCII dump file ${target} does not exist.
			exit 13
		fi
	fi

	# Redirect stdout from where it was when postgresql-dump started.  This is
	# because when called from postinst it is piped through tee and this upsets
	# the operation of the pager which we need now

	3>&1
	1>/dev/tty
	
	(echo This is the ASCII output of the dump for you to check:
	 echo

	 eval ${readin} | eval ${sed_command}) | ${PAGER:-more}

	# restore stdout
	1>&3
	
	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 echo ${target} | grep -q '^/dev/'
then
	mt -f ${target} rewind
fi

if [ -n "${destroy}" ]
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

	rm -rf ${PGDATA}/* ||
		(echo Could not delete ${PGDATA}/\* >&2
		exit 10)
fi

set +e
unset POSTGRES
unset POSTMASTER
unset PGLIB
unset PGDATA
unset PORT
unset BINDIR
. /etc/postgresql/postgresql.env
set -e

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

	failed=
	ENCODING=`cat $ENCODINGFILE 2>/dev/null`
	# override with the command line option, if given
	if [ -n "$encoding" ]
	then
	   ENCODING=$encoding
	fi
	if [ -n "$ENCODING" ]
	then
		INITOPTS=" --encoding $ENCODING"
	fi
	USER=postgres initdb $INITOPTS || 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/postgresql/postmaster.conf
		PGDATA=${POSTGRES_DATA:=/var/lib/postgres/data}
		PGLIB=/usr/lib/postgresql
		export PGLIB PGDATA PGPORT LANG
		POSTMASTER=${PGLIB}/bin/postmaster
		POSTGRES=${PGLIB}/bin/postgres
		OPTIONS=

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

		# American or European date format
		if [ -z "${PGDATESTYLE}" ]
		then
		   export PGDATESTYLE=ISO,European
		fi


		# We certainly don't want to get started if the executable is missing.
		if [ ! -x ${POSTMASTER} ]
		then
		    echo No postmaster executable for postgresql
		    exit 3
		fi

		LOGFILE=${POSTGRES_LOG:-/var/log/postgres.log}
		# Ready to go: stand clear...
		echo Starting PostgreSQL postmaster
        	echo ${POSTMASTER} -i -b ${POSTGRES} ${BUFFERS} \
		    -D ${PGDATA} ${DEBUGLEVEL} ${PORT} ${OPTIONS:+-o \'${OPTIONS}\'} \>$LOGFILE 2\>\&1 \&
        	nohup ${POSTMASTER} -i -b ${POSTGRES} ${BUFFERS} \
		    -D ${PGDATA} ${DEBUGLEVEL} ${PORT} ${OPTIONS:+-o "${OPTIONS}"} >$LOGFILE 2>&1 &
	fi

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

	# Give the postmaster a chance to start up
	sleep 5
	echo Reloading databases...
	eval $readin | eval ${sed_command} |
		psql -e template1 &&
		echo Reload succeeded ||
		echo Reload failed
fi

exit 0
