#!/bin/bash
# -*- mode: sh; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
# vi: set shiftwidth=4 tabstop=8 softtabstop=4 expandtab:
# :indentSize=4:tabSize=8:noTabs=true:
# vim: filetype=sh

# ---- error handling
set -o errexit;
set -o posix;
set -o pipefail;
set -o errtrace;
set -o nounset;

unexpected_error() {
    local errstat=$?
    echo "${g_prog:-$(basename $0)}: Error! Encountered unexpected error at 'line $(caller)', bailing out..." 1>&2

    if $g_set_dbstate_onexit; then
        set_db_exit_state;
    fi

    # Properly shut down logging and exit
    logfile_shutdown
    exit $errstat;
}
trap unexpected_error ERR;

# ---- error functions

merror() {
    echo "$g_prog: Error! ""$@" 1>&2;
    if $g_set_dbstate_onexit; then
        set_db_exit_state;
    fi

    # Properly shut down logging and exit
    logfile_shutdown
    exit 1;
}
minterror() {
    echo "$g_prog: Internal Error! ""$@" 1>&2;
    if $g_set_dbstate_onexit; then
        set_db_exit_state;
    fi

    # Properly shut down logging and exit
    logfile_shutdown
    exit 1;
}
mwarn() {
    echo "$g_prog: Warning! ""$@" 1>&2;
}

# ---- usage

usage() {
    local exitstat=2;
    if [[ ! -z "${2:-}" ]] ; then
        if [[ ! $2 =~ [[:digit:]]+ ]] ; then
            minterror "usage(): exitstat ($2) must be numeric."
        fi
        exitstat=$2;
    fi

    # Only redirect to stderr on non-zero exit status
    if [[ $exitstat -ne 0 ]] ; then
        exec 1>&2;
    fi

    if [[ ! -z "${1:-}" ]] ; then
        echo "$g_prog: Error! $1"
    fi

    echo "Usage: $g_prog [--help] --checklocale-fix-and-backup [more-opts...]"
    echo "       $g_prog [--help] --checklocale-and-fix [more-opts...]"
    echo "       $g_prog [--help] [--checklocale] \\"
    echo "                        [--fix|--testfix] \\"
    echo "                        [--fix-forcelocale-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [--fix-dumprestore-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [--fix-drop-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [--fix-nofix-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [--ignore-unrecognnized-dbnames] \\"
    echo "                        [--dumprestore-restorefile file] \\"
    echo "                        [--no-drop-tmpdb] \\"
    echo "                        [--wso2am-optional] \\"
    echo "                        [--allow-empty-smrtlinkdb] \\"
    echo "                        [--allow-empty-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [--preupgrade-backup] \\"
    echo "                        [--preupgrade-backup-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [more-opts...]"
    echo "       $g_prog [--help] --preupgrade-backup \\"
    echo "                        [--preupgrade-backup-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [more-opts...]"
    echo "       $g_prog [--help] --postupgrade-backup \\"
    echo "                        [--postupgrade-backup-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [more-opts...]"
    echo "       $g_prog [--help] --backup \\"
    echo "                        [--backup-dbnames \"dbname1 dbname2...\"] \\"
    echo "                        [more-opts...]"
    echo "       $g_prog [--help] --backup-dbname dbname \\"
    echo "                        --backup-file file \\"
    echo "                        [more-opts...]"
    echo "       $g_prog [--help] --restore-dbname|--restore dbname \\"
    echo "                        --restore-file file \\"
    echo "                        [more-opts...]"
    echo "       $g_prog [--help] [--start-db|--start]"
    echo "       $g_prog [--help] [--start-db-debug|--start-debug]"
    echo "       $g_prog [--help] [--stop-db|--stop]"
    echo "       $g_prog [--help] [--stop-db-debug|--stop-debug]"
    echo "       $g_prog [--help] [--status-db|--status]"
    echo "       $g_prog [--help] [--status-db-debug|--status-debug]"
    echo ""
    echo "       where 'more-opts' are:"
    echo "           --noexec"
    echo "         for database start/stop control:"
    echo "           --no-db-control"
    echo "           --onstart-start-db            (DEFAULT)"
    echo "           --onstart-no-start-db"
    echo "           --onstart-restart-db"
    echo "           --onstart-no-restart-db       (DEFAULT)"
    echo "           --onexit-restorestate-db      (DEFAULT)"
    echo "           --onexit-no-restorestate-db"
    echo "           --onexit-stop-db"
    echo "           --onexit-no-stop-db           (DEFAULT)"
    echo "         for database access and authentication:"
    echo "           --host"
    echo "           --port"
    echo "           --username"
    echo "           --password"
    echo "           --sldbname"
    echo "           --pgdatadir"
    echo "           --pgdatastoredir"
    echo "           --use-password"
    echo ""
    echo "        --checklocale                  -- check locales for all databases"
    echo "        --checklocale-and-fix          -- same as '--checklocale --fix'"
    echo "        --checklocale-fix-and-backup   -- same as '--checklocale"
    echo "                                          --fix --preupgrade-backup'"
    echo ""
    echo "        --fix                          -- fix locale issues"
    echo "        --testfix                      -- fix locale issues (test mode)"
    echo "                                          (check for errors in dump/restore,"
    echo "                                          but will not modify database)"
    echo "        --fix-forcelocale-dbnames      -- dbnames to fix with 'forcelocale'"
    echo "        --fix-dumprestore-dbnames      -- dbnames to fix with 'dumprestore'"
    echo "        --fix-drop-dbnames             -- dbnames to drop (tmp databases)"
    echo "        --fix-nofix-dbnames            -- dbnames to leave untouched"
    echo "        --ignore-unrecognized-dbnames  -- ignore errors for unrecognized"
    echo "                                          databases"
    echo "        --dumprestore-restorefile      -- file for dumprestore fix"
    echo "        --no-drop-tmpdb                -- do not drop temporary databases"
    echo "        --wso2am-optional              -- avoid error if 'wso2am' missing"
    echo "        --allow-empty-smrtlinkdb       -- avoid error if 'smrtlinkdb' missing"
    echo "        --allow-empty-dbnames          -- list of dbnames allowed to be empty"

    echo ""
    echo "        --preupgrade-backup            -- backup all databases, tagged as "
    echo "                                          'preupgrade'"
    echo "        --preupgrade-backup-dbnames    -- space-separated list of dbnames to"
    echo "                                          use for --preupgrade-backuip"
    echo "        --postupgrade-backup           -- backup all databases, tagged as "
    echo "                                          'postupgrade'"
    echo "        --postupgrade-backup-dbnames   -- space-separated list of dbnames to"
    echo "                                          use for --postupgrade-backuip"
    echo "        --backup                       -- backup all databases"
    echo "        --backup-dbnames|--backup-dbname -- space-separated list of dabases"
    echo "                                            to backup"
    echo "        --backup-file                  -- file to back up to, must specify"
    echo "                                          --backup-dbname with only one dbname"
    echo "        --restore-dbname|--restore     -- restore backup to specified dbname "
    echo "        --restore-file                 -- backup dump file to restore"
    echo ""
    echo "        --start-db|--start             -- start db server (using dbctrl)"
    echo "        --start-db-debug|--start-debug -- start db server, debug (pg_ctl)"
    echo "        --stop-db|--stop               -- stop db server (using dbctrl)"
    echo "        --stop-db-debug|--stop-debug   -- stop db server, debug (pg_ctl)"
    echo "        --status-db|--status           -- db server status (using dbctrl)"
    echo "        --status-db-debug|--status-debug -- db server status, debug (pg_ctl)"
    echo ""
    echo "        --no-db-control                -- do not start/stop/status db server"
    echo "        --onstart-start-db             -- start db server, if not running"
    echo "                                          (DEFAULT)"
    echo "        --onstart-no-start-db          -- do not start db server, if not running"
    echo "        --onstart-restart-db           -- restart db server, if running"
    echo "        --onstart-no-restart-db        -- do not restart db server, if running"
    echo "                                          (DEFAULT)"
    echo "        --onexit-restorestate-db       -- restore db server state on exit"
    echo "                                          (DEFAULT)"
    echo "        --onexit-no-restorestate-db    -- do not restore db server state on exit"
    echo "        --onexit-stop-db               -- stop db server on exit"
    echo "        --onexit-no-stop-db            -- do not stop db server on exit"
    echo "                                          (DEFAULT)"
    echo ""
    echo "        --host                         -- override database host"
    echo "        --port                         -- override database port"
    echo "        --username                     -- override database username"
    echo "        --password                     -- override database password"
    echo "        --sldbname                     -- override smrtlinkdb database name"
    echo "        --pgdatadir                    -- override the pdgatadir"
    echo "        --pgdatastoredir               -- override path to pdgatadir/dbstore"
    echo "        --use-password                 -- force using database password"
    echo ""
    echo "        --noexec|-n                    -- no execute mode (dry-run)"
    echo "        -h|--help                      -- print this usage";
    echo "";

    if [[ $exitstat -ne 0 ]] ; then
        # Print error again, useful for long usages messages
        if [[ ! -z "${1:-}" ]] ; then
            echo ""
            echo "$g_prog: Error! $1"
        fi
    fi

    # bash only:
    if [[ $exitstat -ne 0 ]] ; then
        echo "  at: $(caller)";
    fi
    exit $exitstat;
}

# ---- argument parsing

achk() {
    if [[ $1 -eq 0 ]] ; then usage "Missing argument to $2 option"; fi
}

parseargs() {
    opt_checklocale=false;
    opt_checklocale_and_fix=false;
    opt_checklocale_fix_and_backup=false;

    opt_fix=false;
    opt_testfix=false;
    opt_fix_forcelocale_dbnames="";
    opt_fix_dumprestore_dbnames="";
    opt_fix_drop_dbnames="";
    opt_fix_nofix_dbnames="";
    opt_ignore_unrecognized_dbnames=false;
    opt_dumprestore_restorefile="";
    opt_no_drop_tmpdb=false;
    opt_wso2am_optional=false;
    opt_allow_empty_smrtlinkdb=false;
    opt_allow_empty_dbnames=""

    opt_preupgrade_backup=false;
    opt_preupgrade_backup_dbnames=""
    opt_postupgrade_backup=false;
    opt_postupgrade_backup_dbnames=""
    opt_backup=false;
    opt_backup_dbnames=""
    opt_backup_file=""
    opt_restore_dbname="";
    opt_restore_file="";

    opt_noexec=false;

    opt_start_db=false;
    opt_start_db_debug=false;
    opt_stop_db=false;
    opt_stop_db_debug=false;
    opt_status_db=false;
    opt_status_db_debug=false;

    opt_no_db_control=false;
    opt_onstart_no_start_db=false;
    opt_onstart_restart_db=false;
    opt_onexit_no_restorestate_db=false;
    opt_onexit_stop_db=false;

    opt_host=""
    opt_port=""
    opt_username=""
    opt_password=""
    opt_sldbname=""
    opt_pgdatadir=""
    opt_pgdatastoredir=""
    opt_use_password=false;

    while [[ $# != 0 ]]; do
        opt="$1"; shift;
        case "$opt" in
            # Flag with no argument example:
            #   --flag|--fla|--fl|--f)
            #     opt_flag=true;;
            # Option with argument example:
            #   --arg|--ar|--a)
            #     achk $# $opt; opt_somearg=$1; shift;;
            --checklocale) opt_checklocale=true;;
            --checklocale-and-fix) opt_checklocale_and_fix=true;;
            --checklocale-fix-and-backup) opt_checklocale_fix_and_backup=true;;

            --fix) opt_fix=true;;
            --testfix) opt_testfix=true;;
            --fix-forcelocale-dbnames) achk $# $opt; opt_fix_forcelocale_dbnames=$1; shift;;
            --fix-dumprestore-dbnames) achk $# $opt; opt_fix_dumprestore_dbnames=$1; shift;;
            --fix-drop-dbnames) achk $# $opt; opt_fix_drop_dbnames=$1; shift;;
            --fix-nofix-dbnames) achk $# $opt; opt_fix_nofix_dbnames=$1; shift;;
            --ignore-unrecognized-dbnames) opt_ignore_unrecognized_dbnames=true;;
            --dumprestore-restorefile) achk $# $opt; opt_dumprestore_restorefile=$1; shift;;
            --no-drop-tmpdb) opt_no_drop_tmpdb=true;;
            --wso2am-optional) opt_wso2am_optional=true;;
            --allow-empty-smrtlinkdb) opt_allow_empty_smrtlinkdb=true;;
            --allow-empty-dbnames) achk $# $opt; opt_allow_empty_dbnames=$1; shift;;

            --preupgrade-backup) opt_preupgrade_backup=true;;
            --preupgrade-backup-dbnames)
                opt_preupgrade_backup=true;
                achk $# $opt; opt_preupgrade_backup_dbnames=$1; shift;;
            --postupgrade-backup) opt_postupgrade_backup=true;;
            --postupgrade-backup-dbnames)
                opt_postupgrade_backup=true;
                achk $# $opt; opt_postupgrade_backup_dbnames=$1; shift;;
            --backup) opt_backup=true;;
            --backup-dbnames|--backup-dbname)
                opt_backup=true;
                achk $# $opt; opt_backup_dbnames=$1; shift;;
            --backup-file) achk $# $opt; opt_backup_file=$1; shift;;
            --restore-dbname|--restore) achk $# $opt; opt_restore_dbname=$1; shift;;
            --restore-file) achk $# $opt; opt_restore_file=$1; shift;;

            --noexec|-n) opt_noexec=true;;

            --start-db|--start) opt_start_db=true;;
            --start-db-debug|--start-debug) opt_start_db_debug=true;;
            --stop-db|--stop) opt_stop_db=true;;
            --stop-db-debug|--stop-debug) opt_stop_db_debug=true;;
            --status-db|--status) opt_status_db=true;;
            --status-db-debug|--status-debug) opt_status_db_debug=true;;

            --db-control) opt_no_db_control=false;;
            --no-db-control) opt_no_db_control=true;;
            --onstart-start-db) opt_onstart_no_start_db=false;;
            --onstart-no-start-db) opt_onstart_no_start_db=true;;
            --onstart-restart-db) opt_onstart_restart_db=true;;
            --onstart-no-restart-db) opt_onstart_restart_db=false;;
            --onexit-restorestate-db) opt_onexit_no_restorestate_db=false;;
            --onexit-no-restorestate-db) opt_onexit_no_restorestate_db=true;;
            --onexit-stop-db) opt_onexit_stop_db=true;;
            --onexit-no-stop-db) opt_onexit_stop_db=false;;

            --host) achk $# $opt; opt_host=$1; shift;;
            --port) achk $# $opt; opt_port=$1; shift;;
            --username) achk $# $opt; opt_username=$1; shift;;
            --password) achk $# $opt; opt_password=$1; shift;;
            --sldbname) achk $# $opt; opt_sldbname=$1; shift;;
            --pgdatadir) achk $# $opt; opt_pgdatadir=$1; shift;;
            --pgdatastoredir) achk $# $opt; opt_pgdatastoredir=$1; shift;;
            --use-password) opt_use_password=true;;

            -h|-help|--help|--hel|--he|--h) usage "" 0;;
            -*) usage "Unrecognized option: $opt";;
            *)
                local argstr=${1+"$@"};
                usage "Unexpected extraneous arguments detected: $opt $argstr"
                ;;
        esac
    done


    if $opt_checklocale_and_fix; then
        opt_checklocale=true;
        opt_fix=true;
    fi
    if $opt_checklocale_fix_and_backup; then
        opt_checklocale=true;
        opt_fix=true;
        opt_preupgrade_backup=true;
    fi

    if $opt_fix && $opt_testfix; then
        usage "The --fix and --testfix options may not be specified toegether."
    fi

    if $opt_preupgrade_backup; then
        if $opt_postupgrade_backup; then
            usage "The --preupgrade-backup and --postupgrade-backup options may not be specified toegether."
        elif $opt_backup; then
            usage "The --preupgrade-backup and --backup options may not be specified toegether."
        fi
    fi

    if $opt_postupgrade_backup; then
        if $opt_checklocale; then
            usage "The --postupgrade-backup and --checklocale options may not be specified toegether."
        elif $opt_fix; then
            usage "The --postupgrade-backup and --fix options may not be specified toegether."
        elif $opt_testfix; then
            usage "The --postupgrade-backup and --testfix options may not be specified toegether."
        elif $opt_preupgrade_backup; then
            usage "The --postupgrade-backup and --preupgrade-backup options may not be specified toegether."
        elif $opt_backup; then
            usage "The --postupgrade-backup and --backup options may not be specified toegether."
        fi
    fi

    if $opt_backup; then
        if $opt_checklocale; then
            usage "The --backup and --checklocale options may not be specified toegether."
        elif $opt_fix; then
            usage "The --backup and --fix options may not be specified toegether."
        elif $opt_testfix; then
            usage "The --backup and --testfix options may not be specified toegether."
        elif $opt_preupgrade_backup; then
            usage "The --backup and --preupgrade-backup options may not be specified toegether."
        elif $opt_postupgrade_backup; then
            usage "The --backup and --postupgrade-ackup options may not be specified toegether."
        fi
    fi

    if [[ ! -z "$opt_backup_file" ]] ; then
        if [[ -z "$opt_backup_dbnames" ]] ; then
            usage "Must specify one --backup-dbname with the --backup-file option"
        elif [[ $opt_backup_dbnames =~ [[:space:]] ]] ; then
            usage "Must specify only one --backup-dbname with the --backup-file option"
        fi
    fi

    opt_restore=false;
    if [[ ! -z "$opt_restore_dbname" ]] && [[ ! -z "$opt_restore_file" ]]; then
        opt_restore=true;
    elif [[ ! -z "$opt_restore_dbname" ]] || [[ ! -z "$opt_restore_dbname" ]] ; then
        usage "The --restore-dbname and --restore-file options must be specified together."
    fi

    if [[ ! -z "$opt_restore_dbname" ]] ; then
        if [[ $opt_restore_dbname =~ [[:space:]] ]] ; then
            usage "Must specify only one dbname with the --restore-dbname option"
        fi
    fi

    local conflict_str="${opt_checklocale}${opt_fix}${opt_testfix}${opt_fix_forcelocale_dbnames}${opt_fix_dumprestore_dbnames}${opt_fix_drop_dbnames}${opt_fix_nofix_dbnames}${opt_ignore_unrecognized_dbnames}${opt_dumprestore_restorefile}${opt_no_drop_tmpdb}${opt_wso2am_optional}${opt_preupgrade_backup}${opt_postupgrade_backup}${opt_backup}${opt_restore}${opt_start_db}${opt_start_db_debug}${opt_stop_db}${opt_stop_db_debug}${opt_status_db}${opt_status_db_debug}${opt_no_db_control}${opt_onstart_no_start_db}${opt_onstart_restart_db}${opt_onexit_no_restorestate_db}${opt_onexit_stop_db}"
    conflict_str=${conflict_str//false/}

    if [[ x"$conflict_str" != x"true" ]] ; then
        if $opt_start_db; then
            usage "Cannot specify '--start' or '--start-db' option with other options"
        elif $opt_start_db_debug; then
            usage "Cannot specify '--start-debug' or '--start-db-debug' option with other options"
        elif $opt_stop_db; then
            usage "Cannot specify '--stop' or '--stop-db' option with other options"
        elif $opt_stop_db_debug; then
            usage "Cannot specify '--stop-debug' or '--stop-db-debug' option with other options"
        elif $opt_status_db; then
            usage "Cannot specify '--status' or '--status-db' option with other options"
        elif $opt_status_db_debug; then
            usage "Cannot specify '--status-debug' or '--status-db-debug' option with other options"
        fi
    fi

    opt_run_standalone_startstopstatus=false;
    if  $opt_start_db  || $opt_start_db_debug ||
        $opt_stop_db   || $opt_stop_db_debug  ||
        $opt_status_db || $opt_status_db_debug  ; then
        opt_run_standalone_startstopstatus=true;
    fi

    if  ! $opt_checklocale &&
        ! $opt_fix &&
        ! $opt_testfix &&
        ! $opt_preupgrade_backup &&
        ! $opt_postupgrade_backup &&
        ! $opt_backup &&
        ! $opt_restore; then

        # Run --checklocale by default, if nothing else specified
        opt_checklocale=true;
    fi

}

# ---- globals

set_preglobals() {
    # Force the path to only what we need (/sbin needed for ifconfig)
    PATH_ORIG=$PATH
    PATH="/usr/bin:/bin"

    g_prog=$(basename $0);
    g_progdir=$(dirname $0);
    g_progdir_abs=$(readlink -f "$g_progdir");
}

set_globals() {
    g_start_epoch=$(date +"%s")
    g_start_datestr=$(date -d "@$g_start_epoch")

    # Use $g_progdir_abs here since something like this won't work for things
    # like 'cd', although it will work for executing exes with bash (because
    # of the admin->current symbolic link)
    #    smrtroot/admin/bin/../../path/to/pythondir
    # "cd smrtroot/admin/bin/../../path/to/pythondir" will give an error, but
    # executing "smrtroot/admin/bin/../../path/to/pythondir/python3" will work.
    g_topdir="$g_progdir_abs/../.."

    g_versionnum_short=$(cat "$g_topdir/etc/versionstr.txt" | sed -e 's/\.[^\.]*$//' -e 's/^[^_]*_//')

    g_userdata_dir="$g_topdir/../../userdata"
    g_log_dir="$g_userdata_dir/log"
    g_logfile="$g_log_dir/installer/dbhelper_${g_versionnum_short}.log"

    g_db_upgrade_backupdir="$g_userdata_dir/db_datadir/backups/upgrade"
    g_db_manual_backupdir="$g_userdata_dir/db_datadir/backups/manual"
    g_serverconfig_file="$g_userdata_dir/generated/config/smrtlink-system-config.json"

    g_dbctrl_exe="$g_progdir/dbctrl"

    g_pg_ctl_exe="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/postgresql/postgresql_9.6.1/binwrap/pg_ctl"
    g_pg_dump_exe="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/postgresql/postgresql_9.6.1/binwrap/pg_dump"
    g_createdb_exe="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/postgresql/postgresql_9.6.1/binwrap/createdb"

    # Do not use this version of psql directly, use the $progdir/psql version
    # instead (as it doesn't issue a libtinfo.so library warning for using the
    # 'more' pager on ubuntu-16):
    #   g_psql_exe="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/postgresql/postgresql_9.6.1/binwrap/psql"
    g_psql_exe="$g_progdir/psql"

    g_python3_exe="$g_topdir/bundles/smrttools/current/private/thirdparty/python3/python3_3.7.3/binwrap/python3"

    g_exp_encoding="UTF8"
    g_exp_collate="en_US.UTF-8"
    g_exp_ctype="en_US.UTF-8"

    g_noexec=false;
    # Set this to get commands dumped to the logfile
    g_verbose_level=1;

    # Turn off setting db state on exit for now (turned on after we do the
    # initial setup of the database)
    g_set_dbstate_onexit=false;

    g_restoredb_tmp_dbname="restoredb_tmp"

}

# -- logging subroutines

redir_init() {
    g_npipe=$(mktemp -u -t "$g_prog.tmp.XXXXXXXXXX")
    trap "rm -f $g_npipe" EXIT
    mkfifo "$g_npipe"
    # Alternatively:
    #  mknod $g_npipe p
}
redir_on() {
    tee -a <$g_npipe "$g_logfile" &
    exec 6>&1
    exec 7>&2
    exec 1>$g_npipe 2>&1
}
redir_off() {
    exec 1>&6 6>&-
    exec 2>&7 7>&-
}

logonly () {
    # echo ${1+"$@"}
    echo ${1+"$@"} >> "$g_logfile"
}

# Initialize the logfile.  After running this, all output to stdout and
# stderr will go to the log file as well.  To only output to the logfile,
# use the logonly() function.
logfile_setup() {
    # Create the log dir if it does not exist
    mkdir -p "$(dirname "$g_logfile")"

    # Always append to the log file, so do not remove it here
    # #  rm -f "$g_logfile"

    redir_init;
    redir_on;

    local arg;
    local -a startargs=()
    for arg in "${g_origargs[@]+${g_origargs[@]}}" ; do
        if [[ $arg =~ [[:space:]] ]] ; then
            startargs+=( "\"$arg\"" )
        else
            startargs+=( "$arg" )
        fi
    done

    # Initial logging
    logonly ""
    logonly "====++ Start ${g_prog}: $g_start_epoch: $g_start_datestr"
    logonly ""
    logonly "$g_prog cmdline: $0 ${startargs[@]+${startargs[@]}}"
    logonly ""
}

logfile_shutdown() {
    local end_epoch;
    local end_epochstr;

    end_epoch=$(date +"%s")
    end_datestr=$(date -d "@$end_epoch")

    redir_off;
    logonly ""
    logonly "  Duration ${g_prog}:   $(( $end_epoch - $g_start_epoch )) seconds"
    logonly ""
    logonly "====-- End ${g_prog}:   $end_epoch: $end_datestr"
}

# ---- subroutines

runcmd() {
    local -a envs;
    local -a args;

    # Separate the leading environment variable settings from the command args
    local envdone=false;
    for i in ${1+"$@"}; do
        if ! $envdone && [[ $i =~ ^([[:alpha:]_][[:alnum:]_]*)=([\"\'].*) ]]; then
            envs+=( "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" )
        elif ! $envdone && [[ $i =~ ^([[:alpha:]_][[:alnum:]_]*)=(.*) ]]; then
            envs+=( "${BASH_REMATCH[1]}='${BASH_REMATCH[2]}'" )
        else
            envdone=true;
            # NOTE: with "set -u nounset" set, we have to use += or do
            #       something like this (since "${args[@]}" and "${#args[@]}"
            #       will give errors if the array is empty).  Another option
            #       is:
            #           args=( "${args[@]+${args[@]}}" "$i" );
            args+=( "$i" );
        fi
    done

    # Determine the command in a format that can be printed out and
    # copy-and-pasted directly into a shell and can be run with an "eval".
    # NOTE: Another option is to use:
    #          cmd=$(printf '%q ' ${1+"$@"})
    #       But it will use backslashes instead of quotes, which is fine for
    #       executing, but it doesn't look as natural as with quotes.  Plus
    #       using quotes make it look like the trace output under "set -x"
    #       or "set -o xtrace"
    local cmd
    cmd=$( PS4=; set -x; ( true "${args[@]}" ) 2>&1 );
    cmd=${cmd#true };
    [[ ! -z "${envs[@]+set}" ]] && cmd="${envs[@]} $cmd"

    # Print out the command if neceassry.  The output should be
    # copy-and-pastable directly into a shell.
    if [[ $g_verbose_level -ge 1 ]] || $g_noexec; then
        $g_noexec && echo -n "noexec: "
        echo "$cmd"
    fi

    # Run the command
    if ! $g_noexec; then
        eval "$cmd"
    fi
}

runcmd_log() {
    local stat=0;
    if $g_noexec; then
        runcmd ${1+"$@"} || stat=$?
    else
        runcmd ${1+"$@"} >> "$g_logfile" || stat=$?
    fi
    return $stat;
}

set_database_opts() {
    g_pg_opts=();

    # host
    local host=$opt_host;
    if [[ -z "$opt_host" ]] ; then
        host=$("$g_python3_exe" -c "import json; d = json.load(open('$g_serverconfig_file')); print(d['smrtflow']['db']['properties']['serverName'])")
    fi
    g_host=$host;
    g_pg_opts+=( "--host" "$host" );

    # port
    local port=$opt_port;
    if [[ -z "$opt_port" ]] ; then
        port=$("$g_python3_exe" -c "import json; d = json.load(open('$g_serverconfig_file')); print(d['smrtflow']['db']['properties']['portNumber'])")
    fi
    g_port=$port;
    g_pg_opts+=( "--port" "$port" );

    # username
    local username=$opt_username;
    if [[ -z "$opt_username" ]] ; then
        username=$("$g_python3_exe" -c "import json; d = json.load(open('$g_serverconfig_file')); print(d['smrtflow']['db']['properties']['user'])")
    fi
    g_pg_opts+=( "--username" "$username" );

    # password
    if ! $opt_use_password; then
        g_pg_opts+=( "--no-password" );
    else
        local password=$opt_password;
        if [[ -z "$opt_password" ]] ; then
            password=$("$g_python3_exe" -c "import json; d = json.load(open('$g_serverconfig_file')); print(d['smrtflow']['db']['properties']['password'])")
        fi
        g_pg_opts+=( "--password" "$password" );
    fi

    # dbname
    local sldbname=$opt_sldbname;
    if [[ -z "$opt_sldbname" ]] ; then
        sldbname=$("$g_python3_exe" -c "import json; d = json.load(open('$g_serverconfig_file')); print(d['smrtflow']['db']['properties']['databaseName'])")
    fi
    g_sldbname=$sldbname

    # pgdatadir
    local pgdatadir=$opt_pgdatadir;
    if [[ -z "$opt_pgdatadir" ]] ; then
        pgdatadir=$("$g_python3_exe" -c "import json; d = json.load(open('$g_serverconfig_file')); print(d['pacBioSystem']['pgDataDir'])")
    fi
    g_pgdatadir=$pgdatadir

    local pgdatastoredir=$opt_pgdatastoredir;
    if [[ -z "$opt_pgdatastoredir" ]] ; then
        pgdatastoredir="$pgdatadir/dbstore"
    fi
    g_pgdatastoredir=$pgdatastoredir;

    g_allow_empty_dbnames=$opt_allow_empty_dbnames;
    if $opt_allow_empty_smrtlinkdb; then
        g_allow_empty_dbnames+=" $g_sldbname"
    fi
    # Allow an empty cromwell database for an 8.0.0->8.0.0 upgrade withou
    # cromwell services getting fired up.  This should be ok, in general,
    # since we assum the cromwell db will be transient (no permanent data
    # in the database).
    g_allow_empty_dbnames+=" cromwell"
}

psql_cmd() {
    local -a cmdline=( "$g_psql_exe" "${g_pg_opts[@]+${g_pg_opts[@]}}" -X -v "ON_ERROR_STOP=1" ${1+"$@"} );

    runcmd_log "${cmdline[@]}"
}

# same as psql_cmd() with noexec and verbose turned off (needed when capturing
# the output of the psql command).  Also do not dump output into log file.
# "_capout" means we intend to capture the output (no verbose, noexec, no
# logging should be done).
psql_cmd_nve() {
    local verbose_level_save=$g_verbose_level;
    local noexec_save=$g_noexec;

    g_verbose_level=0;
    g_noexec=false;

    local stat=0
    # Run psql cmd directly here (instead of psql_cmd()), to avoid capturing
    # output in logfile (we need it to go to stdout instead)
    runcmd "$g_psql_exe" "${g_pg_opts[@]+${g_pg_opts[@]}}" -X -v "ON_ERROR_STOP=1" ${1+"$@"} || stat=$?

    g_verbose_level=$verbose_level_save;
    g_noexec=$noexec_save;

    return $stat
}

pg_ctl_cmd() {
    local -a cmdline=( "$g_pg_ctl_exe" ${1+"$@"} );

    runcmd_log "${cmdline[@]}"
}

dbctrl_cmd() {
    local cmd=$1; shift;
    local -a cmdline=( "$g_dbctrl_exe" "$cmd" -c "$g_serverconfig_file" ${1+"$@"} )

    runcmd_log "${cmdline[@]}"
}

pg_dump_cmd() {
    local -a cmdline=( "$g_pg_dump_exe" "${g_pg_opts[@]+${g_pg_opts[@]}}" ${1+"$@"} )
    runcmd_log "${cmdline[@]}"
}

start_db() {
    local debug=false;
    [[ x"${1:-}" == x"debug" ]] && debug=true;

    # set debug if using a non-default pgdatastoredir (dbctrl does not honor
    # anything but the default dbdatastoredir setting)
    [[ ! -z "$opt_pgdatastoredir" ]] && debug=true;

    local stat=0;
    echo "  Starting database server..."

    if $debug; then
        pg_ctl_cmd start -w --pgdata "$g_pgdatastoredir" -o "-p $g_port -h $g_host" || stat=$?
    else
        dbctrl_cmd start || stat=$?
    fi

    if [[ $stat -ne 0 ]] ; then
        merror "Could not start database server"
    fi

    return $stat;
}

stop_db() {
    local debug=false;
    [[ x"${1:-}" == x"debug" ]] && debug=true;

    # set debug if using a non-default pgdatastoredir (dbctrl does not honor
    # anything but the default dbdatastoredir setting)
    [[ ! -z "$opt_pgdatastoredir" ]] && debug=true;

    local stat=0;

    echo "  Stopping database server..."

    if $debug; then
        pg_ctl_cmd stop -w --pgdata "$g_pgdatastoredir" -o "-p $g_port -h $g_host" || stat=$?
    else
        dbctrl_cmd stop || stat=$?
    fi

    if [[ $stat -ne 0 ]] ; then
        merror "Could not stop database server"
    fi

    return $stat;
}

status_db() {
    local debug=false;
    [[ x"${1:-}" == x"debug" ]] && debug=true;

    # set debug if using a non-default pgdatastoredir (dbctrl does not honor
    # anything but the default dbdatastoredir setting)
    [[ ! -z "$opt_pgdatastoredir" ]] && debug=true;

    local stat=0;

    if $debug; then
        pg_ctl_cmd status --silent --pgdata "$g_pgdatastoredir" -o "-p $g_port" || stat=$?
    else
        dbctrl_cmd status || stat=$?
    fi

    if [[ $stat -eq 0 ]] ; then
        echo "  Database is running."
    elif [[ $stat -eq 3 ]] ; then
        echo "  Database is not running."
    else
        merror "Unexpected exit code ($stat) in checking database status"
    fi

    return $stat;
}

run_standalone_startstopstatus() {
    local stat=0;

    # We are guaranteed only one of these is active
    if $opt_start_db; then
        start_db || stat=$?
    elif $opt_start_db_debug; then
        start_db "debug" || stat=$?
    elif $opt_stop_db; then
        stop_db || stat=$?
    elif $opt_stop_db_debug; then
        stop_db "debug" || stat=$?
    elif $opt_status_db; then
        status_db || stat=$?
    elif $opt_status_db_debug; then
        status_db "debug" || stat=$?
    fi

    return $stat;
}


set_db_enter_state() {
    g_db_initialstate_running=false;

    local db_running=false;
    if status_db > /dev/null ; then
        db_running=true;
    fi
    if $opt_no_db_control; then
        if ! $db_running; then
            merror "Database is not running and --no-db-control was specified.  Database must already be running.  Bailing out..."
        fi
        # Ignore the rest of the checks in the --no-db-control case
        g_db_initialstate_running=true;
        return 0;
    fi
    if $db_running; then
        # Database is running
        g_db_initialstate_running=true;
        if $opt_onstart_restart_db; then
            # We are required to stop and restart the database
            # Stop the database
            stop_db;
            # Start the database
            start_db;
        else
            # Leave the database running and use it directly
            :
        fi
    else
        # Database is not running
        g_db_initialstate_running=false;
        if $opt_onstart_no_start_db; then
            # We are not allowed to start the databsae
            merror "Database is not running and --onstart-no-start-db was specified.  Database must already be running.  Bailing out..."
        else
            # Start the database
            start_db;
        fi
    fi
    # At this point the database is guaranteed to be running

    # Set the flag to turn on setting the database state on exit (no matter how
    # we exit the program.
    g_set_dbstate_onexit=true;
}

set_db_exit_state() {
    if $opt_no_db_control; then
        # Never modify the state of the server, leave it running
        return 0;
    fi

    # if --onexit-stop-db is set, always stop the server, (i.e. ignore the
    # --onexit-restorestate-db setting)
    if $opt_onexit_stop_db; then
        stop_db;
    elif ! $opt_onexit_no_restorestate_db; then
        if ! $g_db_initialstate_running; then
            # Database not running when we started, stop it to restore state
            stop_db;
        else
            # Leave database running
            :
        fi
    else
        # We are not restoring the initial database state, and we are not
        # stopping the dataabase explicitly, so leave it running.
        :
    fi

}

get_dbinfo() {
    local dbname=$1; shift;

    local ret=""
    local dbinfo;
    for dbinfo in "${g_dbinfo[@]+${g_dbinfo[@]}}"; do
        if [[ $dbinfo =~ ^${dbname}: ]]; then
            ret=${dbinfo#*:}
            break;
        fi
    done

    echo "${ret//:/ }";
}

is_forcelocale_db() {
    local dbname=$1; shift;

    if [[ ,$g_forcelocale_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}

is_dumprestore_db() {
    local dbname=$1; shift;

    if [[ ,$g_dumprestore_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}
is_drop_db() {
    local dbname=$1; shift;

    if [[ ,$g_drop_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}
is_nofix_db() {
    local dbname=$1; shift;

    if [[ ,$g_nofix_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}
is_unrecognized_db() {
    local dbname=$1; shift;

    if [[ ,$g_unrecognized_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}
is_exist_db() {
    local dbname=$1; shift;

    if [[ ,$g_exist_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}
is_nobackup_db() {
    local dbname=$1; shift;

    if [[ ,$g_nobackup_dbstr, =~ ,${dbname}, ]] ; then
        return 0;
    fi
    return 1;
}

get_dbinfo_state() {
    g_forcelocale_dbs=(
        "template0"
        "template1"
        "postgres"
    )
    g_dumprestore_dbs=(
        "$g_sldbname"
    )
    g_nofix_dbs=(
        "wso2am"
        "cromwell"
    )
    g_drop_dbs=(
        "$g_restoredb_tmp_dbname"
    )
    g_nobackup_dbs=(
        "cromwell"
    )

    g_ignore_unrecognized_dbnames=$opt_ignore_unrecognized_dbnames

    if  [[ ! -z "$opt_fix_forcelocale_dbnames" ]] ||
        [[ ! -z "$opt_fix_dumprestore_dbnames" ]] ||
        [[ ! -z "$opt_fix_nofix_dbnames" ]]       ||
        [[ ! -z "$opt_fix_drop_dbnames" ]] ; then
        g_ignore_unrecognized_dbnames=true;

        g_forcelocale_dbs=( $opt_fix_forcelocale_dbnames );
        g_dumprestore_dbs=( $opt_fix_dumprestore_dbnames );
        g_nofix_dbs=( $opt_fix_nofix_dbnames );
        g_drop_dbs=( $opt_fix_drop_dbnames );
    fi

    g_forcelocale_dbstr="${g_forcelocale_dbs[@]+${g_forcelocale_dbs[@]}}"
    g_forcelocale_dbstr="${g_forcelocale_dbstr// /,}"
    g_dumprestore_dbstr="${g_dumprestore_dbs[@]+${g_dumprestore_dbs[@]}}"
    g_dumprestore_dbstr="${g_dumprestore_dbstr// /,}"
    g_nofix_dbstr="${g_nofix_dbs[@]+${g_nofix_dbs[@]}}"
    g_nofix_dbstr="${g_nofix_dbstr// /,}"
    g_drop_dbstr="${g_drop_dbs[@]+${g_drop_dbs[@]}}"
    g_drop_dbstr="${g_drop_dbstr// /,}"
    g_nobackup_dbstr="${g_nobackup_dbs[@]+${g_nobackup_dbs[@]}}"
    g_nobackup_dbstr="${g_nobackup_dbstr// /,}"

    g_unrecognized_dbs=();

    g_dbinfo=();

    local dblocaleinfo;
    local dblocaleinfos;
    dblocaleinfos=$(psql_cmd_nve postgres -c "select d.datname,pg_catalog.pg_encoding_to_char(d.encoding),d.datcollate,d.datctype from pg_catalog.pg_database d;" -t -A -F ":" | sed -e 's/[[:space:]]//g');

    g_exist_dbstr=""

    local dbname;
    for dblocaleinfo in $dblocaleinfos; do

        dbname=${dblocaleinfo%%:*}
        g_exist_dbstr+=",$dbname"
        if  is_forcelocale_db "$dbname" ||
            is_dumprestore_db "$dbname" ||
            is_nofix_db "$dbname"       ||
            is_drop_db "$dbname" ; then
            :
        else
            g_unrecognized_dbs+=( "$dbname" );
        fi

        # Compute the number of schemas and tables in each database (if
        # connections are allowed to the database)
        local connectallowed;
        local nschemas="-"
        local ntables="-"
        connectallowed=$(psql_cmd_nve postgres -t -A -c "select datallowconn from pg_database where datname = '$dbname';");
        if [[ x"$connectallowed" = x"t" ]] ; then
            nschemas=$(psql_cmd_nve "$dbname" -t -A -c 'select count(schema_name) from information_schema.schemata where schema_name !~ '\''^pg_toast_?'\'' and schema_name !~ '\''^pg_temp_'\'' and schema_name <> '\''pg_catalog'\'' and schema_name <> '\''information_schema'\'' ;')
            ntables=$(psql_cmd_nve "$dbname" -t -A -c 'SELECT count(c.relname) FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('\''r'\'','\'''\'') AND n.nspname <> '\''pg_catalog'\'' AND n.nspname <> '\''information_schema'\'' AND n.nspname !~ '\''^pg_toast'\'' AND pg_catalog.pg_table_is_visible(c.oid);')
        fi

        g_dbinfo+=( "$dblocaleinfo:$nschemas:$ntables" );
    done
    g_exist_dbstr=${g_exist_dbstr#,}

    g_unrecognized_dbstr="${g_unrecognized_dbs[@]+${g_unrecognized_dbs[@]}}"
    g_unrecognized_dbstr="${g_unrecognized_dbstr// /,}"

    g_all_dbs=(
        "${g_forcelocale_dbs[@]+${g_forcelocale_dbs[@]}}"
        "${g_dumprestore_dbs[@]+${g_dumprestore_dbs[@]}}"
        "${g_nofix_dbs[@]+${g_nofix_dbs[@]}}"
        "${g_drop_dbs[@]+${g_drop_dbs[@]}}"
        "${g_unrecognized_dbs[@]+${g_unrecognized_dbs[@]}}"
    )
}

checklocale() {
    local encoding;
    local collate;
    local ctype;
    local nschemas;
    local ntables;

    local warns=false;
    local errs=false;
    for dbname in "${g_all_dbs[@]+${g_all_dbs[@]}}" ; do
        local dbinfo
        dbinfo="$(get_dbinfo "$dbname")"
        read encoding collate ctype nschemas ntables <<< "$dbinfo"

        local dbinfostr="$encoding,$collate,$ctype,$nschemas,$ntables"

        local errstr;

        if ! is_exist_db "$dbname"; then
            # Databsae is missing
            if $opt_wso2am_optional && [[ x"$dbname" == x"wso2am" ]] ; then
                # Ignore missing wso2am database (presumably only needed
                # for the 5.0.1 -> 5.1.0 upgrade case)
                continue;
            elif is_drop_db "$dbname"; then
                continue;
            fi

            dbinfostr=""
            errstr="[ERROR] (missing db)"
            errs=true;
        elif is_drop_db "$dbname"; then
            errstr="[WARN]  (temporary db)"
            warns=true;
        elif is_unrecognized_db "$dbname"; then
            if $g_ignore_unrecognized_dbnames; then
                errstr="[OK]    (ignored)"
            else
                errstr="[ERROR] (unrecognized db)"
                errs=true;
            fi
        else
            if  ( is_nofix_db "$dbname" || is_dumprestore_db "$dbname" ) &&
                [[ ! \ ${g_allow_empty_dbnames//, }\  =~ \ ${dbname}\  ]] &&
                [[ x"$ntables" == x"0" ]] ; then
                errstr="[ERROR] (empty db)"
                errs=true;
            elif [[ x"$encoding" != x"$g_exp_encoding" ]] ||
                [[ x"$collate" != x"$g_exp_collate" ]] ||
                [[ x"$ctype" != x"$g_exp_ctype" ]] ; then
                if is_nofix_db "$dbname" ; then
                    errstr="[ERROR] (bad locale)"
                    errs=true;
                else
                    errstr="[WARN]  (fixable locale)"
                    warns=true;
                fi
            else
                errstr="[OK]"
            fi
        fi

        printf " %15s: %-35s $errstr\n" "$dbname" "$dbinfostr"
    done
    echo ""

    if $errs; then
        return 2;
    elif $warns; then
        return 1;
    fi
    return 0
}

fix_forcelocale_db() {
    local dbname=$1; shift;
    local encoding=$1; shift;
    local collate=$1; shift;
    local ctype=$1; shift;

    if [[ x"$encoding" != x"$g_exp_encoding" ]] ; then
        g_fixes_applied=true;
        if $opt_testfix; then
            echo "  [testfix mode] Not setting encoding to '$g_exp_encoding' on '$dbname' database..."
        else
            echo "  Setting encoding to '$g_exp_encoding' on '$dbname' database..."
            psql_cmd postgres -c "update pg_database set encoding = pg_char_to_encoding('$g_exp_encoding') where datname = '$dbname';"
        fi
    fi

    if [[ x"$collate" != x"$g_exp_collate" ]] ; then
        g_fixes_applied=true;
        if $opt_testfix; then
            echo "  [testfix mode] Not setting collate  to '$g_exp_collate' on '$dbname' database..."
        else
            echo "  Setting collate  to '$g_exp_collate' on '$dbname' database..."
            psql_cmd postgres -c "update pg_database set datcollate = '$g_exp_collate' where datname = '$dbname';"
        fi
    fi

    if [[ x"$ctype" != x"$g_exp_ctype" ]] ; then
        g_fixes_applied=true;
        if $opt_testfix; then
            echo "  [testfix mode] Not setting ctype    to '$g_exp_ctype' on '$dbname' database..."
        else
            echo "  Setting ctype    to '$g_exp_ctype' on '$dbname' database..."
            psql_cmd postgres -c "update pg_database set datctype = '$g_exp_ctype' where datname = '$dbname';"
        fi
    fi
}

fix_dumprestore_db() {
    local dbname=$1; shift;
    local encoding=$1; shift;
    local collate=$1; shift;
    local ctype=$1; shift;

    if  [[ x"$encoding" == x"$g_exp_encoding" ]] &&
        [[ x"$collate" == x"$g_exp_collate" ]] &&
        [[ x"$ctype" == x"$g_exp_ctype" ]] ; then
        return 0;
    fi

    g_fixes_applied=true;

    local backupfile;

    if [[ -z "$opt_dumprestore_restorefile" ]] ; then

        backupfile=$(get_backupfile "$dbname" "prelocalefix")

        # echo "  Backing up '$dbname' database to '$(basename "$backupfile")' ..."
        echo "  Backing up '$dbname' database..."
        local starttime;
        starttime=$(date +"%s")
        backup_db "$dbname" "$backupfile"
        local endtime;
        endtime=$(date +"%s")
        echo "  Backup complete ($(($endtime-$starttime)) sec)."
    else
        backupfile=$opt_dumprestore_restorefile
    fi

    echo "  Creating temporary database with new locale..."
    # Avoid the "NOTICE: database 'xxx' does not exist, skipping" in DROP
    PGOPTIONS='--client-min-messages=warning' \
        psql_cmd postgres -c "DROP DATABASE IF EXISTS ${g_restoredb_tmp_dbname};"
    psql_cmd postgres -c "CREATE DATABASE ${g_restoredb_tmp_dbname} WITH ENCODING '$g_exp_encoding' LC_COLLATE='$g_exp_collate' LC_CTYPE='$g_exp_ctype' TEMPLATE='template0';"

    echo "  Restoring to temporary database with new locale..."
    local starttime;
    starttime=$(date +"%s")
    # --quiet --echo-hidden
    psql_cmd  --single-transaction -f "$backupfile" "${g_restoredb_tmp_dbname}"
    local endtime;
    endtime=$(date +"%s")
    echo "  Restore complete ($(($endtime-$starttime)) sec)."


    if ! $opt_testfix; then
        echo "  Dropping '$dbname' database..."
        # Avoid the "NOTICE: database 'xxx' does not exist, skipping" in DROP
        PGOPTIONS='--client-min-messages=warning' \
            psql_cmd postgres -c "DROP DATABASE IF EXISTS ${dbname};"
        echo "  Renaming temporary database to '$dbname'..."
        psql_cmd postgres -c "ALTER DATABASE ${g_restoredb_tmp_dbname} RENAME TO ${dbname}"
    fi
}

fix_drop_db() {
    local dbname=$1; shift;

    g_fixes_applied=true;

    if $opt_testfix; then
        echo "  [testfix mode] Not dropping '$dbname' database..."
    else
        echo "  Dropping '$dbname' database..."
        # Avoid the "NOTICE: database 'xxx' does not exist, skipping" in DROP
        PGOPTIONS='--client-min-messages=warning' \
            psql_cmd postgres -c "DROP DATABASE IF EXISTS ${dbname};"
    fi
}

fixlocales() {
    local encoding;
    local collate;
    local ctype;
    local nschemas;
    local ntables;

    echo "  Fixing database encodings and locales..."
    echo ""

    local -a fixdbs;
    fixdbs=(
        "${g_forcelocale_dbs[@]+${g_forcelocale_dbs[@]}}"
        "${g_dumprestore_dbs[@]+${g_dumprestore_dbs[@]}}"
        "${g_drop_dbs[@]+${g_drop_dbs[@]}}"
    )

    g_fixes_applied=false;

    for dbname in "${fixdbs[@]+${fixdbs[@]}}" ; do
        local dbinfo
        dbinfo="$(get_dbinfo "$dbname")"
        read encoding collate ctype nschemas ntables <<< "$dbinfo"

        if ! is_exist_db "$dbname"; then
            # Database is missing, skip it
            continue
        fi
        if is_forcelocale_db "$dbname"; then
            fix_forcelocale_db "$dbname" "$encoding" "$collate" "$ctype"
        fi
        if is_dumprestore_db "$dbname"; then
            fix_dumprestore_db "$dbname" "$encoding" "$collate" "$ctype"
        fi
        if ! $opt_no_drop_tmpdb; then
            if is_drop_db "$dbname"; then
                fix_drop_db "$dbname"
            fi
        fi
    done

    if $g_fixes_applied; then
        # Fixes were applied, print out the checklocale information again
        # after the fixes.  Should be clean if all the fixes worked.

        get_dbinfo_state;

        echo ""
        echo "  Rechecking database encodings and locales..."
        echo ""

        local stat=0;
        checklocale || stat=$?
        if [[ $stat -ne 0 ]] ; then
            echo "       Database fixes still required."
        else
            echo "       All databases ok."
        fi
        echo ""
    fi

    return 0
}

get_backupversion() {
    local desc=$1; shift;
    local ret;

    if [[ x"$desc" == x"postupgrade" ]] ; then
        ret=$g_versionnum_short;
    else
        ret=$(readlink "$g_topdir/../../current" | sed -e 's/\.[^\.]*$//' -e 's/^[^_]*_//')
    fi

    echo "$ret"
}

get_backupfile() {
    local dbname=$1; shift;
    local desc=$1; shift;

    local backupdate;
    backupdate=$(date "+%Y%m%d-%H%M%S")
    local backupversion;
    backupversion=$(get_backupversion "$desc");


    local backupfilename="${dbname}_${backupversion}_${desc:+${desc}_}${backupdate}.sql";
    local backuppath;
    if [[ $desc =~ upgrade ]] ; then
        backuppath="$g_db_upgrade_backupdir/$backupfilename"
    else
        backuppath="$g_db_manual_backupdir/$backupfilename"
    fi

    echo "$backuppath"
}

backup_db() {
    local dbname=$1; shift;
    local backupfile=$1; shift;

    backup_dirname=$(dirname "$backupfile");
    backup_basename=$(basename "$backupfile");

    # Make sure the backup directory exists
    runcmd_log mkdir -p "$backup_dirname"
    # Dump the database
    pg_dump_cmd -f "$backupfile" "$dbname"

    # Check to make sure that we didn't dump an empty database.  This should
    # not be ppssible if the database is populated and the backup mechanism
    # is working properly.  But in testing the locale migration we had a
    # couple of instances of empty databases being generated (running commands
    # manually).  This cause of empty backups is either that the database
    # being dumped is actually empty or the backup mechanism did not work
    # properly.  This check should tell us if it is the latter.   The former
    # is being checked in the initial checklocale(), by ensuring the database
    # has more than one table (unless disabled on the command line, presumably
    # in the case where services were not started after an initial install).
    #
    # Die with an error if the database has tables, but the backup doesn't.
    local encoding collate ctype nschemas ntables;
    local dbinfo
    dbinfo="$(get_dbinfo "$dbname")"
    read encoding collate ctype nschemas ntables <<< "$dbinfo"

    if ! $g_noexec ; then
        if [[ x"$ntables" != x"0" ]] ; then
            if !  grep -l --quiet '^CREATE TABLE ' "$backupfile" ; then
                merror "Empty backup created for '$dbname' (expected $ntables tables, found 0): $backupfile"
            fi
        fi
    fi

    local latest_link="$backup_dirname/latest_${dbname}.sql"
    if [[ -e "$latest_link" ]] && [[ ! -h "$latest_link" ]] ; then
        merror "Backup 'latest' link ($latest_link) exists but is not a symbolic link"
    fi
    if [[ -h "$latest_link" ]] ; then
        if [[ ! -f "$latest_link" ]] ; then
            merror "Backup 'latest' link ($latest_link) exists but does not point to a regular file"
        fi

        local latest_size;
        local backupfile_size;

        latest_size=$(stat -L --format "%s" "$latest_link")
        backupfile_size=$(stat -L --format "%s" "$backupfile")
        if [[ $latest_size -eq $backupfile_size ]] ; then
            if cmp --quiet "$latest_link" "$backupfile" ; then
                # Files are the same, remove the current backup file and
                # create a symbolic link to the latest file
                runcmd_log rm -f "$backupfile"
                runcmd_log ln -s "$(readlink "$latest_link")" "$backupfile"
            fi
        fi
    fi

    # Update the latest link
    if [[ -h "$latest_link" ]] ; then
        runcmd_log rm -f "$latest_link"
    fi
    runcmd_log ln -s "$backup_basename" "$latest_link"
}


preupgrade_backup_dbs() {
    local backupdbs=""

    if [[ ! -z "$opt_preupgrade_backup_dbnames" ]] ; then
        backupdbs=$opt_preupgrade_backup_dbnames;
    else
        local dbname
        for dbname in "${g_dumprestore_dbs[@]+${g_dumprestore_dbs[@]}}" "${g_nofix_dbs[@]+${g_nofix_dbs[@]}}" ; do
            if is_exist_db "$dbname"  && ! is_nobackup_db "$dbname" ; then
                backupdbs+=" $dbname"
            fi
        done
    fi

    for dbname in $backupdbs; do
        backupfile=$(get_backupfile "$dbname" "preupgrade")

        # echo "  Backing up '$dbname' database to '$(basename "$backupfile")' ..."
        echo "  Backing up '$dbname' database..."
        local starttime;
        starttime=$(date +"%s")
        backup_db "$dbname" "$backupfile"
        local endtime;
        endtime=$(date +"%s")
        echo "  Backup complete ($(($endtime-$starttime)) sec)."
    done
}

postupgrade_backup_dbs() {
    local backupdbs=""

    if [[ ! -z "$opt_postupgrade_backup_dbnames" ]] ; then
        backupdbs=$opt_postupgrade_backup_dbnames;
    else
        local dbname
        for dbname in "${g_dumprestore_dbs[@]+${g_dumprestore_dbs[@]}}" "${g_nofix_dbs[@]+${g_nofix_dbs[@]}}" ; do
            if is_exist_db "$dbname"  && ! is_nobackup_db "$dbname" ; then
                backupdbs+=" $dbname"
            fi
        done
    fi

    for dbname in $backupdbs; do
        backupfile=$(get_backupfile "$dbname" "postupgrade")

        # echo "  Backing up '$dbname' database to '$(basename "$backupfile")' ..."
        echo "  Backing up '$dbname' database..."
        local starttime;
        starttime=$(date +"%s")
        backup_db "$dbname" "$backupfile"
        local endtime;
        endtime=$(date +"%s")
        echo "  Backup complete ($(($endtime-$starttime)) sec)."
    done
}

backup_dbs() {
    local backupdbs=""

    if [[ ! -z "$opt_backup_dbnames" ]] ; then
        backupdbs=$opt_backup_dbnames;
    else
        local dbname
        for dbname in "${g_dumprestore_dbs[@]+${g_dumprestore_dbs[@]}}" "${g_nofix_dbs[@]+${g_nofix_dbs[@]}}" ; do
            if is_exist_db "$dbname"; then
                backupdbs+=" $dbname"
            fi
        done
    fi

    if [[ ! -z "$opt_backup_file" ]] ; then
        if [[ -z "$opt_backup_dbnames" ]] ; then
            minterror "backup_dbs(): No --backup-dbname specified."
        elif [[ $opt_backup_dbnames =~ [[:space:]] ]]; then
            minterror "backup_dbs(): More then one --backup-dbname specified with --backup-file option."
        fi
        dbname=$opt_backup_dbnames
        backupfile=$opt_backup_file

        echo "  Backing up '$dbname' database to '$backupfile' ..."
        local starttime;
        starttime=$(date +"%s")
        backup_db "$dbname" "$backupfile"
        local endtime;
        endtime=$(date +"%s")
        echo "  Backup complete ($(($endtime-$starttime)) sec)."
    else
        for dbname in $backupdbs; do
            backupfile=$(get_backupfile "$dbname" "backup")

            # echo "  Backing up '$dbname' database to '$(basename "$backupfile")' ..."
            echo "  Backing up '$dbname' database..."
            local starttime;
            starttime=$(date +"%s")
            backup_db "$dbname" "$backupfile"
            local endtime;
            endtime=$(date +"%s")
            echo "  Backup complete ($(($endtime-$starttime)) sec)."
        done
    fi
}

restore_db() {
    echo "  Creating temporary restore database..."
    # Avoid the "NOTICE: database 'xxx' does not exist, skipping" in DROP
    PGOPTIONS='--client-min-messages=warning' \
        psql_cmd postgres -c "DROP DATABASE IF EXISTS $g_restoredb_tmp_dbname;"
    psql_cmd postgres -c "CREATE DATABASE ${g_restoredb_tmp_dbname} WITH ENCODING '$g_exp_encoding' LC_COLLATE='$g_exp_collate' LC_CTYPE='$g_exp_ctype' TEMPLATE='template0';"

    echo "  Restoring to temporary restore database..."
    # --quiet --echo-hidden
    psql_cmd  --single-transaction -f "$opt_restore_file" "$g_restoredb_tmp_dbname"

    local dbname=$opt_restore_dbname

    echo "  Dropping '$dbname' database..."
    # Avoid the "NOTICE: database 'xxx' does not exist, skipping" in DROP
    PGOPTIONS='--client-min-messages=warning' \
        psql_cmd postgres -c "DROP DATABASE IF EXISTS ${dbname};"
    echo "  Renaming temporary restore database to '$dbname'..."
    psql_cmd postgres -c "ALTER DATABASE $g_restoredb_tmp_dbname RENAME TO ${dbname}"
}


# ---- main

# Save off original cmdline args, use as "${g_origargs[@]}" (with doublequotes)
g_origargs=( ${1+"$@"} )

set_preglobals ${1+"$@"};
parseargs ${1+"$@"};
set_globals;

# Set umask, turn off group/other access for any new database related file/dirs
umask 0077

# After logfile_setup, all stdout/stderr will also go to logfile.
# Use logonly() to only write to logfile.
logfile_setup;

set_database_opts;

# Run any standalong start/stop/status commands and exit
if $opt_run_standalone_startstopstatus; then
    stat=0;
    run_standalone_startstopstatus || stat=$?

    # Properly shut down logging and exit
    logfile_shutdown
    exit $stat;
fi

set_db_enter_state;

g_noexec=$opt_noexec;


get_dbinfo_state;

if $opt_checklocale; then
    echo "  Checking database encodings and locales..."
    echo ""

    stat=0;
    checklocale || stat=$?

    if [[ $stat -eq 2 ]] ; then
        merror "Errors detected in checking database encodings and locales."
    elif [[ $stat -eq 1 ]] ; then
        if ! $opt_fix && ! $opt_testfix; then
            merror "Unexpected warnings detected in checking database encodings and locales."
        fi
        echo "      Database fixes required."
    else
        echo "      All databases ok."
    fi
    echo ""
fi


if $opt_fix || $opt_testfix; then
    fixlocales
fi

if $opt_preupgrade_backup; then
    preupgrade_backup_dbs
fi

if $opt_postupgrade_backup; then
    postupgrade_backup_dbs
fi

if $opt_backup; then
    backup_dbs
fi

if $opt_restore; then
    restore_db
fi

g_noexec=false;

set_db_exit_state;

# Properly shut down logging and exit
logfile_shutdown
exit 0;
