#!/bin/bash
# *******************************************************************
# **  @file eisman_uninstall.sh - uninstall package
# **
# **  Creation:     2013-01-13 dv
# **  Last update:  $Id: eisman_uninstall.sh 44252 2016-12-30 23:14:30Z dv $
# **
# **  Copyright (c) 2013-2017 the eisfair team, team(at)eisfair(dot)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.
# *******************************************************************

. /var/install/include/eislib
. /var/install/include/eismanlib


# *******************************************************************
# **  show help on arguments and possible options
# **  @param  n/a
# **  @return n/a
# *******************************************************************

show_help()
{
cat <<EOF

Uninstall an eisfair package

usage:
    eisman uninstall [OPTIONS] PACKAGE

options:
    --help          show this help screen
    --force         uninstall even if requirements deny it
    --auto          non interactive mode
    --info          do not uninstall, only show packages
    --output=<file> redirect result into a file

EOF
}


# *******************************************************************
# **  remove package from database of installed files
# **  @param  $1  package name
# **  @return n/a
# *******************************************************************

remove_from_database()
{
    local package="${1}"

    # grep all packages other then "$package" into temporary file
    grep -v -e "^\[.*\]\[${package}\]\[.*\]\[.*\]\[.*\]" \
       "${EISMAN_PATH}/installed.db" > "${EISMAN_PATH}/installed.db.tmp"

    if [ "$?" -eq 0 ]
    then
        # remove database file
        rm -f "${EISMAN_PATH}/installed.db"

        # move temporary file to new database
        mv "${EISMAN_PATH}/installed.db.tmp" "${EISMAN_PATH}/installed.db"
    else
        echo "error: unable to delete package from database!" >&2
        exit 1
    fi
}


# *******************************************************************
# **  search installed package database for packages requireing the
# **  package that has to be uninstalled.
# **  @param  $1  package
# **  @return (on stdout) data found in packages.db meeting the
# **          search criteria
# *******************************************************************

find_backward_requirements()
{
    local package="${1}"

    result=$(grep -E "^\[.*\]\[.*\]\[.*\]\[.*\]\[.*\]require    ${package}([ ]|$)" \
        "${EISMAN_PATH}/installed.db")

    sed -ne 's#^\[.*\]\[\(.*\)\]\[\(.*\)\]\[.*\]\[.*\].*#- \1 (\2)#p' <<< "${result}"
}


# *******************************************************************
# **  return short description of the package
# **  @param  $1  package
# **  @return (on stdout) data found in packages.db meeting the
# **          search criteria
# *******************************************************************

get_package_description()
{
    local package="${1}"
    local fields="name|short|version|date"

    # perform query
    query_package_db      \
        ""                \
        ""                \
        "${package}"      \
        ""                \
        ""                \
        ""                \
        "${fields}"       \
        "${EISMAN_PATH}/installed.db"
}


# *******************************************************************
# **  pass result to logger
# **  @param  $1  package
# **  @param  $2  version
# **  @param  $3  message to be logged
# **  @param  $4  is it an update? (yes/no)
# **  @return n/a
# *******************************************************************

log_result()
{
    local package="${1}"
    local version="${2}"
    local message="${3}"
    local update="${4}"
    local date=$(sed -ne 's#^date  \(.*\)#\1#p' <<< "${description}" | sed 's# ##g')


    if [ "${update}" = "no" ]
    then
        /usr/bin/logger -t 'eis-deinstall' -p 'local5.info' \
            "package ${package} (${version}|${date}) ${message}"
    else
        /usr/bin/logger -t 'eis-update' -p 'local5.info' \
            "package ${package} (${version}|${date}) ${message}"
    fi
}


# *******************************************************************
# **  check if package is installed
# **  @param  $1  package
# **  @return 0 if installed, 1 otherwise
# *******************************************************************

is_package_installed()
{
    # check if package is installed
    local package=${1}
    local description=$(get_package_description "${package}")
    if [ -z "${description}" ]
    then
        return 1
    fi
    return 0
}


# *******************************************************************
# **  uninstall a package using the filelist supplied by the package
# **  @param  $1  package
# **  @param  $2  version
# **  @return 0 if successful
# *******************************************************************

uninstall_by_filelist()
{
    local package=${1}
    local version=${2}

    filelist=/etc/filelist.d/${package}-files.txt

    # process file list info and remove files
    while read id perm user group packname file
    do
        case ${id} in
        b|u|s)
            # remove program files
            if [ "${file}" != "tmp/install.sh" \
              -a "${file}" != "tmp/preinstall.sh" ]
            then
                rm -f /${file}
            fi
            ;;
        f)
            # remove directories
            rmdir --ignore-fail-on-non-empty /${file} 2>/dev/null
            ;;
        esac
    done < ${filelist}

    # make sure the file list is removed too
    rm -f ${filelist}

    # only if this isn't an update, remove package record
    # from installed packages database
    if [ "${update}" = "no" ]
    then
        # remove database record and update database
        remove_from_database "${package}"
    fi

    # remove general kernel entry from database
    # if this entry still exists. Only the specialized
    # kernel package names are allowed
    if [ "${package:0:10}" = "eiskernel-" ]
    then
        remove_from_database "eiskernel"
    fi

    # print result
    if [ "${update}" = "no" ] && [ "${quiet}" = "no" ]
    then
        mecho --info -n "Successfully uninstalled: "
        mecho        -n "${package} (${version})"
        mecho --info    "!"
    fi

    # log result and exit with success code 0
    # this procedure will never fail.. hopefully ;-)
    log_result "${package}" "${version}" \
               "without error" "${update}"
    return 0
}


# *******************************************************************
# **  uninstall a package using the uninstall script supplied by
# **  the package
# **  @param  $1  package
# **  @param  $2  version
# **  @return 0 if successful, 1 otherwise
# *******************************************************************

uninstall_by_package_script()
{
    local package=${1}
    local version=${2}

    # modify file permissions
    chmod 0750 /var/install/deinstall/${package}

    # convert the file to unix :-(
    /usr/local/bin/dtou /var/install/deinstall/${package}

    # execute deinstallation and verify result
    /var/install/deinstall/${package} ${argument}
    rtc=${?}

    if [ ${rtc} -ne 0 ]
    then
        # log result and exit with error code 1
        mecho --warn -n "Failed to uninstall: "
        mecho           "${package} (${version})!"

        log_result "${package}" "${version}" \
                   "with error(s) (${rtc}/0)" "${update}"
        return 1
    fi

    # make sure uninstall script is removed too
    rm -f /var/install/packages/${package}

    # only if this isn't an update, remove package record
    # from installed packages database
    if [ "${update}" = "no" ]
    then
        # remove database record and update database
        remove_from_database "${package}"
    fi

    # remove general kernel entry from database
    # if this entry still exists. Only the specialized
    # kernel package names are allowed
    if [ "${package:0:10}" = "eiskernel-" ]
    then
        remove_from_database "eiskernel"
    fi

    # print result
    if [ "${update}" = "no" ] && [ "${quiet}" = "no" ]
    then
        mecho --info -n 'Successfully uninstalled: '
        mecho        -n "${package} (${version})"
        mecho --info    "!"
    fi

    # log result and exit with success code 0
    # this procedure will never fail.. hopefully ;-)
    log_result "${package}" "${version}" \
               "without error" "${update}"
}


# *******************************************************************
# **  uninstall a package by checking for proper uninstall method
# **  @param  $1  package
# **  @return 0 if successful, 1 otherwise
# *******************************************************************

uninstall_package()
{
    local package=${1}
    local version

    # check if package is currently installed and get package info
    description=$(get_package_description "${package}")
    if [ -z "${description}" -a "${force}" = "no" ]
    then
        echo "error: package \"${package}\" not installed!" >&2
        return 1
    fi

    # retreive package version
    version=$(sed -ne 's#^version  \(.*\)#\1#p' <<< "${description}")

    # prepare argument for update
    argument=""
    if [ "${update}" = "yes" ]
    then
        argument="--update"
    fi

    # check if update is possible
    if [ -f /var/install/deinstall/${package}  \
      -o -f /etc/filelist.d/${package}-files.txt ]
    then

        # check if uninstall script is present
        if [ -f /var/install/deinstall/${package} ]
        then
            uninstall_by_package_script ${package} ${version}
            return $?
        else
            uninstall_by_filelist ${package} ${version}
            return $?
        fi
    else
        if [ "${force}" = "yes" ] || [ "${update}" = "yes" ]
        then
            # log result and exit with success code 0
            # this procedure will never fail.. hopefully ;-)
            log_result "${package}" "${version}" \
                    "missing files, uninstall forced!" "${update}"

            # show warning message only if we are not updating
            if [ "${update}" = "no" ]
            then
                mecho --warn -n "Missing files, uninstall forced: "
                mecho           "${package} (${version})!"
            fi

            # only if this isn't an update, remove package record
            # from installed packages database
            if [ "${update}" = "no" ]
            then
                # remove database record and update database
                remove_from_database "${package}"
            fi

            return 0
        else
            # missing either file list or deinstall script
            echo "Unable to deinstall package ${package}!"
        fi
    fi

    # exit with error
    return 1
}


# *******************************************************************
# **  main program
# *******************************************************************

main()
{
    local outfile=""

    force='no'
    auto='no'
    update='no'
    quiet='no'
    info='no'

    while [ "${1:0:2}" = "--" ]
    do
        case ${1} in
        --help)
            show_help
            exit 0
            ;;
        --force)
            force='yes'
            shift
            ;;
        --auto)
            shift
            auto='yes'        # non interactive installation
            ;;
        --info)
            shift
            info='yes'        # not a real uninstallation only show what would be done
            ;;
        --quiet)
            shift
            quiet='yes'       # no success messages
            ;;
        --update)
            shift
            update='yes'
            ;;
        --output)
            shift
            outfile="${1}"
            shift
            ;;
        --output=*)
            outfile="${1:9}"
            shift
            ;;
        *)
            echo "error: unknown option \"${1}\"!" >&2
            echo
            show_help
            exit 1
            ;;
        esac
    done

    # read package parameter
    package="${1}"
    if [ -z "${package}" ]
    then
        echo "error: missing package parameter!" >&2
        echo
        show_help
        exit 1
    fi


    # creation of package list

    # if package is not installed, exit if force is not set
    if ! is_package_installed "${package}"
    then
        if [ "${quiet}" = "no" ]
        then
            echo "info: package \"${package}\" not installed!"
        fi

        if [ "${force}" = "no" ]
        then
            exit 0
        fi
    fi

    # start creation of package list
    packagelist[0]="${package}"
    num_packages=1

    if [ "${update}" = "no" ]
    then
        # if we are uninstalling a library, remove -dev package too
        if is_package_installed "${package}-dev"
        then
            packagelist[${num_packages}]="${package}-dev"
            num_packages=$((${num_packages} + 1))
        fi

        # if we are uninstalling a library, remove -dev-static package too
        if is_package_installed "${package}-dev-static"
        then
            packagelist[${num_packages}]="${package}-dev-static"
            num_packages=$((${num_packages} + 1))
        fi

        # if we only uninstall a lib-dev package, the lib-dev-static
        # package must be uninstalled to
        if is_package_installed "${package}-static"
        then
            packagelist[${num_packages}]="${package}-static"
            num_packages=$((${num_packages} + 1))
        fi
    fi


    # check if the package may be uninstalled
    if [ "${force}" = "no" -a "${update}" = "no" ]
    then
        index=0
        while [ "${index}" -lt "${num_packages}" ]
        do
            requirements=$(find_backward_requirements "${packagelist[${index}]}")

            for requirement in ${requirements}
            do
                if [ "${requirement}" != '-' ] && [ "${requirement:0:1}" != '(' ]
                then
                    index2=0
                    while [ "${index2}" -lt "${num_packages}" ]
                    do
                        if [ "${packagelist[${index2}]}" = "${requirement}" ]
                        then
                            break
                        fi
                        index2=$((${index2} + 1))
                    done

                    if [ "${index2}" -ge "${num_packages}" ]
                    then
                        echo "error: uninstallation aborted due to pending requirements!" >&2

                        mecho --info "List of pending requirements:"
                        ${EISMAN_BIN_PATH}/eisman_usage.sh "${package}"

                        exit 1
                    fi
                fi
            done

            index=$((${index} + 1))
        done
    fi


    # ask user to continue
    if [ "${update}" = "no" ] && [ "${auto}" = "no" ]
    then
        # check if package is currently installed and get package info
        description=$(get_package_description "${package}")

        if [ -z "${outfile}" ]
        then
            # show info
            mecho --info 'The following package will be uninstalled:'
            echo "${description}"
            echo

            if [ "${num_packages}" -gt 1 ]
            then
                mecho --info 'This will also trigger uninstallation of:'
                index=1
                while [ "${index}" -lt "${num_packages}" ]
                do
                    echo "- ${packagelist[${index}]}"
                    index=$((${index} + 1))
                done
                echo
            fi
        else
            {
                # show info
                mecho --info 'The following package will be uninstalled:'
                echo "${description}"
                echo

                if [ "${num_packages}" -gt 1 ]
                then
                    mecho --info 'This will also trigger uninstallation of:'
                    index=1
                    while [ "${index}" -lt "${num_packages}" ]
                    do
                        echo "- ${packagelist[${index}]}"
                        index=$((${index} + 1))
                    done
                    echo
                fi
            } > "${outfile}"
        fi

        if [ "${info}" = "no" ]
        then
            res=$(/var/install/bin/ask 'Continue' 'no')

            # if ask break, ask returned 255
            if [ $? = 255 ]
            then
                res='no'
            fi

            if [ "${res}" != "yes" ]
            then
                echo 'Uninstall aborted by user!' >&2
                exit 1
            fi
        fi
    fi

    if [ "${info}" = "no" ]
    then
        index=0
        result=0
        while [ "${index}" -lt "${num_packages}" ]
        do
            if ! uninstall_package "${packagelist[${index}]}"
            then
                mecho --warn "Package ${packagename} NOT removed."
                result=1
            fi
            index=$((${index} + 1))
        done
    fi

    exit $result
}

main "${@}"
