#!/bin/bash

#Preupgrade Assistant performs system upgradability assessment
#and gathers information required for successful operating system upgrade.
#Copyright (C) 2013 Red Hat Inc.
#Pavel Raiskup <praiskup@redhat.com>
#
#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 3 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.
#
#You should have received a copy of the GNU General Public License
#along with this program.  If not, see <http://www.gnu.org/licenses/>.
. /usr/share/preupgrade/common.sh
check_rpm_to "perl" ""
#END GENERATED SECTION

# NOTEs:
# * #1007802    - There is no easy solution for the bug #1007802.  The most
#       logical is to warn users in KB article about that and to allow users to
#       specify additional initdb options to postgresql-setup (bug #1052063).
#       Not a good material for PA.

# allow better case matching
shopt -s extglob

# this is for 'common.sh'
COMPONENT=postgresql
PG_NEW_VERSION=9.2
PG_OLD_VERSION=8.1

is_pkg_installed postgresql-server && is_dist_native postgresql-server || {
    is_pkg_installed postgresql84-server && is_dist_native postgresql84-server || {
        exit_not_applicable
    }
    COMPONENT=postgresql84
    PG_OLD_VERSION=8.4
}

# settings
HOME_DIR=/var/lib/pgsql
DATA_DIR=$HOME_DIR/data
PG_VERSION_FILE=$DATA_DIR/PG_VERSION
POSTGRESQL_CONF_FILE=$DATA_DIR/postgresql.conf
PG_HBA_CONF_FILE=$DATA_DIR/pg_hba.conf
INITFILE=/etc/rc.d/init.d/postgresql

# FIXME: KB article for RHEL5!
README_DIST_FILE=`rpm -ql $COMPONENT | grep README | grep dist`
KB_ARTICLE_UPGRADE="https://access.redhat.com/site/articles/541873"

# particular results
SOLUTION_TEXT=
HOME_DIR_OK=no
DATA_DIR_OK=no
DATA_DIR_INITIALIZED=no
DIFFERENT_USAGE_OK=no
STARTED_OK=no
OPTIONS_OK=no
PLUGINS_OK=no

SKIP_TESTING=no
PLAN_STOP_SERVER=no

SERVICE_BIN=/sbin/service
CHKCONFIG_BIN=/sbin/chkconfig

UPSTREAM_DOC_PGDUMP=http://www.postgresql.org/docs/9.2/static/upgrading.html

# FIXME: remove once $PATH is correctly propagated
export PATH="$PATH:/usr/bin:/usr/sbin:/sbin:/bin"

LIBDIR=`rpm --eval '%{_libdir}'`
PLUGINDIR=$LIBDIR/pgsql

#############################################################################
#                            FUNCTIONS                                      #
#############################################################################
# run command under postgres user (requires running under root)
run_as_postgres() {
    param="$@"
    su - postgres -c "$param"
}

# <LIB-CANDIDATES>

_FIRST_APPEND=1
append_to_solution()
{
    if test ! $_FIRST_APPEND -eq 1; then
        echo >> $SOLUTION_FILE
    fi
    _FIRST_APPEND=0
    cat >> $SOLUTION_FILE
}


# remove the version & relase part from NVR
# -----------------------------------------
# STREAM | remove_ver_rel > OUTPUT
remove_ver_rel() {
    sed 's|-[^-]*-[^-]*$||'
}


# find symlinks/hard links to filename in specified directory
# -----------------------------------------------------------
# find_links FILE WHERE [WHERE ...]
find_links()
{
    res=1
    file=$1 ; shift
    while test "$#" -gt 0; do
        dir=$1 ; shift
        find "$dir" -lname "$file"
        find -L "$dir" -samefile "$file" | grep -v "^$file$"
        test $? -eq 0 && res=0
    done
    return $res
}

# </LIB-CANDIDATES>

check_home_dir() {
    $FUNC_ENTRY

    postgres_home=`run_as_postgres pwd`
    if test "$postgres_home" != "$HOME_DIR"; then
        log_high_risk "wrong PostgreSQL home directory '$postgres_home'"
        SKIP_TESTING=yes
        return $RESULT_FAIL
    fi

    HOME_DIR_OK=yes
    log_info "$STR_OK PostgreSQL home directory is '$postgres_home'"
    return $RESULT_PASS
}

# Check that $PGDATA points to the expected path and that the directory exists.
check_data_dir() {
    $FUNC_ENTRY

    pgdata_dir=`run_as_postgres 'echo $PGDATA'`
    if test "$pgdata_dir" != "$DATA_DIR"; then
        log_high_risk "PGDATA directory is now $pgdata_dir instead of $DATA_DIR"
        SKIP_TESTING=yes
        return $RESULT_FAIL
    fi

    if test ! -d "$pgdata_dir"; then
        log_high_risk "PGDATA directory $pgdata_dir does not exist"
        SKIP_TESTING=yes
        return $RESULT_FAIL
    fi

    DATA_DIR_OK=yes
    log_info "$STR_OK PGDATA points to the correct path '$DATA_DIR'"
    return $RESULT_PASS
}

check_is_initialized() {
    $FUNC_ENTRY

    if test ! -f "$PG_VERSION_FILE"; then
        append_to_solution <<EOF
* Be careful. It seems that you have installed the PostgreSQL server but you
  have not initialized the data directory. That means that either you have never
  used the PostgreSQL server or you are using the PostgreSQL server from a
  ${COMPONENT}-server package in a different way (which would need a manual
  interaction).
EOF
        log_high_risk \
            "$PG_VERSION_FILE does not exist, are you using PostgreSQL server?"
        SKIP_TESTING=yes
        return $RESULT_FAIL
    fi

    DATA_DIR_INITIALIZED=yes
    log_info "$PG_VERSION_FILE is on place, the database seems to be initialized."
    return $RESULT_PASS
}

# Check that the /etc/rc.d/initd./postgresql is the only controller for
# postgresql server.

check_different_usage() {
    $FUNC_ENTRY

    # check that the init file exists
    if test ! -f "$INITFILE"; then
        log_high_risk "The $INITFILE initfile not found"
        SKIP_TESTING=yes
        return $RESULT_FAIL
    fi

    # we support runing multiple instances of PostgreSQL server at the same
    # time (symlinking the service file can be symptom of this usage) but we are
    # unable to deal with this configuration automatically.  Rather set high
    # risk.
    links=`find_links "$INITFILE" /etc/rc.d/init.d`
    if test $? -eq 0; then
        log_high_risk "There seems to be non-default PostgreSQL init.d usage" \
                      "via $INITFILE symbolic links or hardlinks."
        append_to_solution <<EOF
* We support running multiple instances of PostgreSQL server at the same time
  (achieved usually by symlinking the init file).  This situation was detected
  on your system.  Unfortunately, we are unable to handle such cases
  automatically in the Preupgrade Assistant. Look at upstream pg_dumpall
  documentation and go through the steps with respect to your special
  configuration. The links are:
`echo "$links" | sed 's|^|    |'`
EOF
        #SKIP_TESTING=yes   # It seems that skip is not ideal here
        return $RESULT_FAIL
    fi

    append_to_solution <<EOF
* Even if the postgresql-server is probably configured correctly, we are unable
  to say for 100% that the server is not used in some specific way on your
  machine, so look at your system and check that the
  $INITFILE initfile is the only trigger that the server is started by.
EOF
    #log_slight_risk "We can't tell for 100% that the system will be in-place upgradable"
    DIFFERENT_USAGE_OK=yes
    return $RESULT_PASS
}

start_server() {
    $FUNC_ENTRY
    STARTED_OK=yes

    $SERVICE_BIN postgresql status &>/dev/null
    if test $? -eq 0; then
        log_info "PostgreSQL is already running"
        return $RESULT_PASS
    fi
    # try to start..
    $SERVICE_BIN postgresql start &>/dev/null
    res=$?
    if test "$res" -eq 0; then
        log_info "Successfully started PostgreSQL"
        PLAN_STOP_SERVER=yes
        return $RESULT_PASS
    fi

    log_error "Cannot start the PostgreSQL server - res: $res"
    STARTED_OK=no
    return $RESULT_ERROR
}

# start and plan stopping the server if it is not running.
# TODO: this is not 100% needed in RHEL5->RHEL7 migration usecase
check_started() {
    $FUNC_ENTRY
    start_server
    return $?
}

# warn if it is not enabled - it is quite weird and it may be pretty easily more
# comfortable to uninstall the server completely
check_enabled() {
    $FUNC_ENTRY

    # FIXME: is this correct way?
    $CHKCONFIG_BIN --list postgresql | grep "on" >/dev/null
    if test "$?" -eq 0; then
        log_info "PostgreSQL is enabled at least in one runlevel"
        return $RESULT_PASS
    fi

    log_slight_risk "PostgreSQL is not enabled after system startup"
    append_to_solution <<EOF
* Note that PostgreSQL is not enabled at system startup. This is not "really"
  risky but it is at least worth observing. If that is on a server machine, the
  PostgreSQL seems to be unused. In this situation the sysadmin can uninstall the
  ${COMPONENT}-server package).
EOF
    return $RESULT_FAIL
}

#NOTE: if we want  to do some automatic actions using postgresql in future,
#      we should append check_permissions to be sure we can do that without
#      required passwd

filter_comments() {
    grep -v -e '^[[:space:]]*$' -e '^[[:space:]]*#' | sed 's|[[:space:]]*#.*||'
}

# The upgrade process does not rely on configuration options from
# postgresql.conf.  I didn't know that when I was writing this function.  But
# the suggestion for user (to be prepared for incompatibilities) is still nice
# to have.
check_options() {
    $FUNC_ENTRY

    OPTIONS_OK=yes
    # incompatible options in configuration files
    filtered_conf="`cat $POSTGRESQL_CONF_FILE | filter_comments`"

    # starting from RHEL-7.0, unix_socket_directory is not supported - we rather
    # use unix_socket_directories.  That feature is backported from PostgreSQL
    # 9.3 to 9.2 in RHEL7.
    echo "$filtered_conf" | grep unix_socket_directory >/dev/null
    if test $? -eq 0; then
        append_to_solution <<EOF
* The 'unix_socket_directory' option is not supported Red Hat Enterprise Linux 7.  Instead of this we
  use the 'unix_socket_directories' option (which is able to specify multiple
  directories separated by commas). See the documentation of that option at
  [link:http://www.postgresql.org/docs/9.2/static/runtime-config-connection.html]
  Do not remove this option from the configuration file. You will
  need to fix that option in Red Hat Enterprise Linux 7.
EOF
        log_medium_risk "The 'unix_socket_directory' option is not supported."
        OPTIONS_OK=no
    fi

    obsolete_options_used=
    for i in add_missing_from \
             australian_timezones \
             bgwriter_all_maxpages \
             bgwriter_all_percent \
             bgwriter_lru_percent \
             custom_variable_classes \
             explain_pretty_print \
             krb_server_hostname \
             max_fsm_pages \
             max_fsm_relations \
             preload_libraries \
             redirect_stderr \
             regex_flavor \
             silent_mode \
             stats_block_level \
             stats_command_string \
             stats_reset_on_server_start\
             stats_row_level \
             stats_start_collector ; do
        echo "$filtered_conf" | grep $i >/dev/null
        if test $? -eq 0; then
            log_medium_risk "The '$i' option was removed in PostgreSQL 9.2"
            OPTIONS_OK=no
            obsolete_options_used="${obsolete_options_used} '$i'"
        fi
    done

    obsolete_options_used=`echo $obsolete_options_used | sed 's|^ ||' | sed 's| |, |'`

    test x"$OPTIONS_OK" = xno && append_to_solution <<EOF
* The options $obsolete_options_used specified in your postgresql.conf are not
  supported in PostgreSQL 9.2 in Red Hat Enterprise Linux 7 anymore.  Remove their usage
  later.
EOF

    test $OPTIONS_OK == yes && \
        log_info "No options problem in 'postgresql.conf' found."
    test $OPTIONS_OK == fail &&
        return $RESULT_FAIL
    return $RESULT_PASS
}

check_plugins() {
    # prepare user he will need to recompile plugins in RHEL7 also
    $FUNC_ENTRY

    unowned_reported=no
    unknown_pkg_reported=no

    PLUGINS_OK=yes
    provides="`rpm -q --whatprovides $PLUGINDIR/* | sort | uniq | remove_ver_rel`"
    pkg=
    while IFS= read pkg || [ -n "$pkg" ]; do
        case $pkg in
        postgresql-@(contrib|devel|docs|plperl|plpython|pltcl|server|test))
            # nothing really happens, those packages are also in RHEL7 and
            # should be updated appropriately
            continue
            ;;

        postgresql84-@(contrib|devel|docs|plperl|plpython|pltcl|server|test))
            #TODO: should be solved automatically due to our static lists
            continue
            ;;

        # FIXME: Can I assume that we have C locale?
        *"not owned by any package")
            set $pkg
            log_slight_risk "The file in the plug-in directory '$2' is not owned by any package"
            test $unowned_reported = no && unowned_reported=yes && \
            append_to_solution <<EOF
* An unowned file was detected under the $PLUGINDIR directory. It could
  mean that you have installed a third party PostgreSQL plug-in. To make the
  database upgrade smoothly, you should be prepared to provide this plug-in in
  Red Hat Enterprise Linux 7 before you start the upgrade process.
EOF
            PLUGINS_OK=no
            ;;

        *)
            log_medium_risk "unknown package '$pkg'"
            test $unknown_pkg_reported = no && unknown_pkg_reported=yes && \
            append_to_solution <<EOF
* A third party PostgreSQL plug-in package was detected installed on your
  system. You should be prepared to provide this package also in Red Hat
  Enterprise Linux 7 to make the database upgrade smoothly.
EOF
            PLUGINS_OK=no
            ;;
        esac
    done <<<"$provides"

    if test $PLUGINS_OK = no; then
        return $RESULT_FAIL
    fi

    log_info "No problem with plug-ins detected"
    return $RESULT_PASS
}

#############################################################################
#                            MAIN                                           #
#############################################################################

# run all checkers
res=$RESULT_PASS
for i in home_dir data_dir different_usage is_initialized enabled started \
    options plugins;
do
    check_$i
    tmp_res=$?
    if test $tmp_res -ne $RESULT_PASS; then
        if test $res -ne $RESULT_ERROR; then
            res=$tmp_res
        fi
    fi
    test "$SKIP_TESTING" = "yes" && break
done

if test "$PLAN_STOP_SERVER" = yes; then
    log_info "Stopping the server."
    $SERVICE_BIN postgresql stop &>/dev/null
    if test $? -ne 0; then
        # this shouldn't really happen
        res=$RESULT_FAIL
    fi
fi

[ "$SKIP_TESTING" == "yes" ] && log_high_risk \
  "The check script has not done all the tests because of a very unusual configuration of your system."

log_high_risk "For the correct migration, follow the instructions in the report and dump all data from your database first."
append_to_solution <<EOF
* When migrating from Red Hat Enterprise Linux 5 to Red Hat Enterprise Linux 7,
  full data directory backup ($DATA_DIR) done by 'pg_dumpall' utility *must* be
  done on administrator's responsibility and the resulting SQL file copied to the
  migrated machine.
  See upstream HOWTO for pg_dumpall: [link:$UPSTREAM_DOC_PGDUMP]

* All the PostgreSQL configuration files must be backed up and manually copied to the
  migrated machine. By default those configuration files are installed in
  '$HOME_DIR/data'.

* Then you will have to initiate a new database cluster by the \`pg_restore\`
  command in Red Hat Enterprise Linux 7. Note that this process does not keep
  PostgreSQL configuration files (the whole configuration will be generated from
  scratch). The old configuration files must be restored from your backups.

  For more information about migrating process, see the section "The dump & restore way"
  at [link:$KB_ARTICLE_UPGRADE] and in $README_DIST_FILE.
EOF

exit $res

# vim: ts=4:sw=4:expandtab:tw=80
