#!/bin/bash

# Create a throw-away PostgreSQL environment for running regression tests. This
# happens in a temp dir/on unshared tmpfses, so does not interfere with
# existing clusters.
#
# (C) 2005-2012 Martin Pitt <mpitt@debian.org>
# (C) 2012-2014 Christoph Berg <myon@debian.org>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.

set -e # no -u here as that breaks PGCONF_OPTS[@]

case ${LD_PRELOAD:-} in
    *fakeroot*) NONROOT=1 ;;
esac
if [ -z "${NONROOT:-}" ] && [ "$(id -u)" != 0 ]; then
    NONROOT=1
fi

# root operation: re-exec ourselves through unshare
if [ -z "${NONROOT:-}" ] && [ -z "${UNSHARED:-}" ]; then
    UNSHARED=1 exec unshare -uimn -- "$0" "$@"
fi
# unshared program starts here

help ()
{
    echo "pg_virtualenv: Create throw-away PostgreSQL environment for regression tests"
    echo "Syntax: $0 [options] [command]"
    echo "    -a                use all installed server versions"
    echo "    -v 'version ...'  list of PostgreSQL versions to run [default: latest]"
    echo "    -c 'options'      extra options to pass to pg_createcluster"
    echo "    -i 'initdb opts'  extra initdb options to pass to pg_createcluster"
    echo "    -o 'guc=value'    postgresql.conf options to pass to pg_createcluster"
    echo "    -s                open a shell when command fails"
    exit ${1:-0}
}

# option parsing
PGBINROOT="/usr/lib/postgresql/"
#redhat# PGBINROOT="/usr/pgsql-"
PG_VERSIONS=""
PGCONF_OPTS=()
while getopts "ac:i:ho:sv:" opt ; do
    case $opt in
	a) for d in $PGBINROOT*/bin/pg_ctl; do
		# prepend version so latest ends up first (i.e. on port 5432)
                dir=${d%%/bin/pg_ctl}
		PG_VERSIONS="${dir#$PGBINROOT} ${PG_VERSIONS:-}"
	   done ;;
	c) CREATE_OPTS="$OPTARG" ;;
	i) INITDB_OPTS="$OPTARG" ;;
	h) help ;;
	o) PGCONF_OPTS+=("--pgoption" "$OPTARG") ;;
	s) run_shell=1 ;;
	v) PG_VERSIONS="$OPTARG" ;;
	*) help 1 ;;
    esac
done
if [ -z "$PG_VERSIONS" ]; then
    # use latest version
    d=$(ls $PGBINROOT*/bin/pg_ctl | tail -1)
    dir=${d%%/bin/pg_ctl}
    PG_VERSIONS="${dir#$PGBINROOT}"
fi
# shift away args
shift $(($OPTIND - 1))
# if no command is given, open a shell
[ "${1:-}" ] || set -- ${SHELL:-/bin/sh}

# non-root operation: create a temp dir where we store everything
if [ "${NONROOT:-}" ]; then
    WORKDIR=$(mktemp -d -t pg_virtualenv.XXXXXX)
    export PG_CLUSTER_CONF_ROOT="$WORKDIR/postgresql"
    export PGUSER="${USER:-${LOGNAME:-$(id -un)}}"
    PGSYSCONFDIR="$WORKDIR/postgresql-common" # no export yet so pg_createcluster uses the original createcluster.conf
    mkdir "$PGSYSCONFDIR" "$WORKDIR/log"
    PWFILE="$PGSYSCONFDIR/pwfile"
    LOGDIR="$WORKDIR/log"

    cleanup () {
	set +e
	for v in $PG_VERSIONS; do
	    echo "Stopping cluster $v/regress..."
	    pg_ctlcluster --mode immediate $v regress stop
	done
	rm -rf $WORKDIR
    }
    trap cleanup 0 HUP INT QUIT ILL ABRT PIPE TERM

# root operation: keep everything in the standard locations
else
    # let everything happen in overlay tmpfses to avoid interfering with already
    # existing clusters; this also speeds up testing
    mount --make-rprivate / 2> /dev/null || : # reset / to private mounts (systemd changes this to shared)
    created_dirs=""
    for d in /etc/postgresql /var/lib/postgresql /var/log/postgresql /var/run/postgresql; do
	if ! [ -d $d ]; then
	    created_dirs="$created_dirs $d"
	    mkdir -p $d
	fi
	mount -n -t tmpfs -o mode=755 tmpfs $d
    done
    : ${PGSYSCONFDIR:=/etc/postgresql-common}
    pg_service="$PGSYSCONFDIR/pg_service.conf"

    export PGUSER="postgres"
    PWFILE=$(mktemp -t pgpassword.XXXXXX)
    chown postgres:postgres "$PWFILE"

    # clean up created clusters and directories after us
    cleanup () {
	set +e
	for v in $PG_VERSIONS; do
	    echo "Stopping cluster $v/regress..."
	    pg_ctlcluster --mode immediate $v regress stop
	done
	if [ "$created_dirs" ]; then
	    umount $created_dirs
	    rmdir --ignore-fail-on-non-empty -p $created_dirs
	fi
	rm -f $PWFILE $pg_service
	if [ -f $pg_service.pg_virtualenv-save ]; then
	    mv -f $pg_service.pg_virtualenv-save $pg_service
	fi
    }
    trap cleanup 0 HUP INT QUIT ILL ABRT PIPE TERM

    chown root:postgres /var/log/postgresql
    chmod 1775 /var/log/postgresql
    chown postgres:postgres /var/run/postgresql
    chmod 2775 /var/run/postgresql
    if [ -f $pg_service ]; then
	mv -f $pg_service $pg_service.pg_virtualenv-save
    fi

    # start localhost interface
    if [ -x /bin/ip ]; then
        ip link set dev lo up || true
    else
        ifconfig lo up || true
    fi
fi

# create postgres environments
if [ -x /usr/bin/pwgen ]; then
    export PGPASSWORD=$(pwgen 20 1)
else
    export PGPASSWORD=$(dd if=/dev/urandom bs=1k count=1 2>/dev/null | md5sum - | awk '{ print $1 }')
fi
echo "$PGPASSWORD" > "$PWFILE"

for v in $PG_VERSIONS; do
    # create temporary cluster
    # we chdir to / so programs don't throw "could not change directory to ..."
    (
	cd /
        case $v in
            8*|9.0|9.1|9.2) : ;;
            *) NOSYNC="--nosync" ;;
        esac
	pg_createcluster \
	    ${NONROOT:+-d "$WORKDIR/data/$v/regress"} \
	    ${NONROOT:+-l "$WORKDIR/log/postgresql-$v-regress.log"} \
	    ${CREATE_OPTS:-} "${PGCONF_OPTS[@]}" $v regress -- \
	    --username="$PGUSER" --pwfile="$PWFILE" $NOSYNC ${INITDB_OPTS:-}
	# in fakeroot, the username will likely default to "postgres" otherwise
	pg_ctlcluster -o '-F' $v regress start
    )
    port=$(pg_conftool -s $v regress show port)

    # record cluster information in service file
    cat >> $PGSYSCONFDIR/pg_service.conf <<EOF
[$v]
host=localhost
port=$port
dbname=postgres
user=$PGUSER
password=$PGPASSWORD

EOF
done

export PGSYSCONFDIR
export PGHOST="localhost"
export PGDATABASE="postgres"
case $PG_VERSIONS in
    *\ *) ;; # multiple versions: do not set PGPORT because that breaks --cluster
    *)
	export PGPORT="$port" # will always be 5432 in root mode, but could differ otherwise
	export PG_CONFIG="$PGBINROOT$PG_VERSIONS/bin/pg_config"
	;;
esac

# run program
"$@" || EXIT="$?"
if [ ${EXIT:-0} -gt 0 ]; then
    for log in ${LOGDIR:-/var/log/postgresql}/*.log; do
	echo "$log:"
	tail -100 $log
    done

    if [ "${run_shell:-}" ]; then
	echo "pg_virtualenv: command exited with status $EXIT, dropping you into a shell"
	${SHELL:-/bin/sh}
    fi
fi

exit ${EXIT:-0}
