#!/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.

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 "      -n        fix the change of DEFAULT 'now' to DEFAULT now() 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.pgdbak ]
	then
		mv $PGDATA/pg_options.pgdbak $PGDATA/pg_options
	fi
	if [ -d "$preserve" ]
	then
		there=`pwd`
		cd $preserve
		if [ -f pg_options.pgdbak ]
		then
		     mv pg_options.pgdbak pg_options
		fi
		cd $there
	fi
}

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="/var/lib/postgres/dumpall/${installed}"
	else
		LD_LIBRARY_PATH="/var/lib/postgres/dumpall/${installed}:$LD_LIBRARY_PATH"
	fi
	export LD_LIBRARY_PATH
}

recoverconfig () {
# put the config files back where they were
    rm -f $PGDATA/pg_hba.conf
    ln -s /etc/postgresql/pg_hba.conf $PGDATA/pg_hba.conf
    rm -f $PGDATA/postgresql.conf
    ln -s /etc/postgresql/postgresql.conf $PGDATA/postgresql.conf
    if [ -d "$preserve" ]
    then
	there=`pwd`
        cd $preserve
	if [ -f postgresql.conf.pgdbak ]
	then
	    rm -f postgresql.conf
	    mv postgresql.conf.pgdbak postgresql.conf
	fi
	if [ -f pg_hba.conf.pgdbak ]
	then
	    rm -f pg_hba.conf
	    mv pg_hba.conf.pgdbak pg_hba.conf
	fi
	cd $there
    fi
    restore_pg_options
}

## Start of execution ##

SHELL=/bin/sh
trap recoverconfig 0

if [ -r /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=/var/lib/postgres/dumpall/default_encoding
export PGLIB PGDATA LANG PATH
current=%PG_VERSION%          # Current PostgreSQL database version

if [ -f ${PGDATA}/PG_VERSION ]; then
    installed=`cat ${PGDATA}/PG_VERSION`
fi

# 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

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:Ffnilp: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'";;
		n)
			dump_options="-N";;
		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 [ -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

# Make sure either the ${PGDATA} directory or the dump target exists
dumped=
if [ ! -d $PGDATA/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
else
	# source the LC_* settings from the old database
	export $(/usr/lib/postgresql/bin/pg_controldata $PGDATA |
		grep '^LC_' |
		sed -e 's/:[ 	]*/=/')
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
	must_dump=

	if [ -z "${installed}" ]
	then
		echo "There is no existing installation of Postgresql" >&2
		if [ -d ${PGDATA}/base ]
		then
			echo However, there is a base 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="$dump_options -z"
			fi
		        # make sure the old database has its own copy of
		        # pg_hba.conf and postgresql.conf
		        if [ -L $PGDATA/pg_hba.conf ]
		        then
			    rm $PGDATA/pg_hba.conf
			    cp /etc/postgresql/pg_hba.conf $PGDATA/pg_hba.conf
		        fi
		        if [ -L $PGDATA/postgresql.conf ]
		        then
			    rm $PGDATA/postgresql.conf
			    cp /etc/postgresql/postgresql.conf  $PGDATA/postgresql.conf
		        fi
			cat $PGDATA/pg_hba.conf >$PGDATA/pg_hba.conf.pgdbak
			cat $PGDATA/postgresql.conf >$PGDATA/postgresql.conf.pgdbak
			# now make sure we have full access
			if [ $icomp -le 107102 ]
			then
			    echo "local all trust" >$PGDATA/pg_hba.conf
			else
			    echo "local all all trust" >$PGDATA/pg_hba.conf
			fi
		fi
	fi

	if [ -n "${must_dump}" -a -z "${exclude_dump}" ]
	then
		# Look for existing postgresql binaries, for the previous release
		export BINDIR=/var/lib/postgres/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=`pidof /usr/lib/postgresql/bin/postmaster` && kill $pm_pid || true

		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.pgdbak
		fi
		rm -f $PGDATA/pg_options
		echo 'all = -1' > $PGDATA/pg_options

		# There are new postgresql.conf options at 7.3 that 7.2 and
		# earlier cannot cope with
		if [ -L $PGDATA/postgresql.conf -a -f /var/lib/postgres/dumpall/$installed/postgresql.conf ]
		then
			rm $PGDATA/postgresql.conf
			ln -s /var/lib/postgres/dumpall/$installed/postgresql.conf $PGDATA/postgresql.conf
		fi
		# Just to be sure, we will replace it with an empty one
		mv $PGDATA/postgresql.conf $PGDATA/postgresql.conf.pgdbak
		touch $PGDATA/postgresql.conf
		# ... but we may need the locale data
		grep -i '^ *(LC_CTYPE|LC_COLLATE)' $PGDATA/postgresql.conf.pgdbak |
			sed -e 's/lc_ctype/LC_CTYPE/' -e 's/lc_collate/LC_COLLATE/' -e 's/ *= */=/' -e "s/'//g" >$PGDATA/postgresql.conf || true
                . $PGDATA/postgresql.conf
		export LC_COLLATE LC_CTYPE

		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
		fi

		new_pm_pid=`pidof /usr/lib/postgresql/bin/postmaster` || {
			echo "Failed to start the postmaster" 2>&1
			exit 7
                }
		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
			echo "Dumping the database to ${target}" >&2
		fi

		echo pg_dumpall "$dump_options"
		for PG_DUMPALL in /usr/lib/postgresql/bin/pg_dumpall /var/lib/postgres/dumpall/${installed}/pg_dumpall
		do
			if [ -n "$RETRYING" ]
			then
				echo Retrying with $PG_DUMPALL
				export PATH=/var/lib/postgres/dumpall/${installed}:$PATH
				fixup_libpath
			else
				echo Dumping with new pg_dumpall >&2
				export LD_LIBRARY_PATH=/usr/lib/postgresql/lib
			fi
			export PGPORT=5431
			(
				echo "-- postgresql-dump on `date` from version ${installed}"
				if $PG_DUMPALL $dump_options 
				then
					echo "-- postgresql-dump completed on `date`"
				else
					echo "-- dump failed"
					echo "Dump failed" >&2
					RETRYING=true
				fi
			) | eval $writeout
			[ -z "$RETRYING" ] && break || true
		done

		if [ -n "${verbose}" ]
		then
			echo "Finding the default encoding" >&2
		fi
		(
			fixup_libpath
			default_encoding=`${BINDIR}/psql $PORT -q -t -d template1 -c "select pg_encoding_to_char(encoding) from pg_database where datname = 'template1'"`
			echo $default_encoding >$ENCODINGFILE
		)
		if [ -n "${verbose}" ]
		then
			echo "Killing the postmaster" >&2
		fi
		kill $new_pm_pid
		while kill -0 $new_pm_pid
		do
			sleep 1
		done
	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

	
	(echo This is the ASCII output of the dump for you to check:
	 echo

	 eval ${readin} | eval ${sed_command}) | ${PAGER:-/usr/bin/pager}
	
	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
			echo Directory ${preserve} already exists || exit 8
		fi

		# Use cp rather than mv in case we are being asked
		# to move between filesystems.
		if ! cp -a $PGDATA ${preserve}
		then
			echo Could not copy ${PGDATA}/\* to ${preserve} >&2
			exit 9
		fi
		# ensure the saved directory is not readable except to its
		# owner - who will be postgres or root
		chmod 700 ${preserve}
	fi
	if [ -n "${verbose}" -a -n "${destroy}" ]
	then
		echo "Deleting ${PGDATA}" >&2
	fi

	rm -rf ${PGDATA}/*
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

	PGDATA=${POSTGRES_DATA:=/var/lib/postgres/data}
	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
	# copy any authentication files from old PGDATA to new one; this covers
	# the lists of users and databases introduced at release 7.3
	for f in $(grep '^[[space]]*[^#].*[[space]]*@' $preserve/*)
	do
		for word in $f
		do
			if [ `echo $word | cut -c1` = @ ]
			then
				file=`echo $word | cut -c2-9999`
				[ -r "$file" ] && cp $preserve/$file $PGDATA || true
			fi
		done
	done
fi

if [ -n "${load}" ]
then
	if [ -n "${verbose}" ]
	then
		echo "Checking that the postmaster is running" >&2
	fi
        if ! pidof /usr/lib/postgresql/bin/postmaster > /dev/null
	then
		. /etc/postgresql/postmaster.conf
		PGDATA=${POSTGRES_DATA:=/var/lib/postgres/data}
		# make sure we have full access
		rm $PGDATA/pg_hba.conf
		echo "local all all trust" > $PGDATA/pg_hba.conf
		# start up the postmaster
		PGLIB=/usr/lib/postgresql
		PGPORT=5431
		PORT="-p $PGPORT"
		export PGLIB PGDATA PGPORT LANG
		POSTMASTER=${PGLIB}/bin/postmaster
		POSTGRES=${PGLIB}/bin/postgres
		OPTIONS=

		if [ -n "${verbose}" ]
		then
			echo "Preparing to start the postmaster" >&2
		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/postgresql/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...
	report_error=true
	DUMP_LOC=`dirname $target`
	eval $readin | eval ${sed_command} |
		psql -p $PGPORT -e template1 > $DUMP_LOC/reload.trace 2>&1 &&
		report_error= ||
		echo Reload failed >&2
	if grep -q '^ERROR' $DUMP_LOC/reload.trace
	then
		echo There were errors on reloading the data.  Review the trace in >&2
		echo $DUMP_LOC/reload.trace to see what happened and >&2
		echo what action is needed to correct the situation. >&2
		echo >&2
		echo The reload command was: >&2
		echo $readin \| ${sed_command} \| psql -p $PGPORT -e template1 >&2
		report_error=true
	fi

	# stop the postmaster
	[ -r /var/run/postgresql/.s.PGSQL.$PGPORT.lock ] &&
	    kill -INT `head -1 /var/run/postgresql/.s.PGSQL.$PGPORT.lock` || true
fi

if [ -n "$report_error" ]
then
	exit 1
fi

exit 0
