#!/bin/sh
#
# Copyright (c) 2014-2016 Samsung Electronics Co., Ltd All Rights Reserved
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#
# @file        migration/cynara-db-migration
# @author      Pawel Wieczorek <p.wieczorek2@samsung.com>
# @brief       Migration tool for Cynara's database
#

set -e

##### Constants (these must not be modified by shell)

STATE_PATH='/var/cynara'
DB_DIR='db'
INDEX_NAME='buckets'
DEFAULT_BUCKET_NAME='_'
CHECKSUM_NAME='checksum'
GUARD_NAME='guard'
DENY_POLICY=';0x0;'

# Return values for comparison
LESS=-1
EQUAL=0
GREATER=1

# Cynara version which introduced checksums
CHS_INTRO_VERSION='0.6.0'
CHS_MD5_VERSION='0.10.0'

##### Variables, with default values (optional)

CYNARA_USER='cynara'
CYNARA_GROUP='cynara'

SMACK_LABEL='System'

OLD_VERSION=
NEW_VERSION=

##### Functions

version_compare() {
    local MAJOR1=
    local MAJOR2=
    local MINOR1=
    local MINOR2=
    local PATCH1=
    local PATCH2=

    # Parse
    i=1
    while [ 2 -ge $i ] ; do
        eval "ARG=\$$i"
        eval "MAJOR$i=${ARG%%.*}"
        eval "TMP=${ARG#*.}"
        eval "MINOR$i=${TMP%%.*}"
        eval "PATCH$i=${ARG##*.}"
        i=$(($i+1))
    done

    # Compare
    if [ $MAJOR1 -lt $MAJOR2 ] ; then
        echo "$LESS"
    elif [ $MAJOR1 -eq $MAJOR2 ] ; then
        if [ $MINOR1 -lt $MINOR2 ] ; then
            echo "$LESS"
        elif [ $MINOR1 -eq $MINOR2 ] ; then
            if [ $PATCH1 -lt $PATCH2 ] ; then
                echo "$LESS"
            elif [ $PATCH1 -eq $PATCH2 ] ; then
                echo "$EQUAL"
            else
                echo "$GREATER"
            fi
        else
            echo "$GREATER"
        fi
    else
        echo "$GREATER"
    fi
}

parse_opts() {
    while getopts ":f:t:p:u:g:l:" opt; do
        case $opt in
            f )
                OLD_VERSION="${OPTARG}"
                ;;
            t )
                NEW_VERSION="${OPTARG}"
                ;;
            p )
                STATE_PATH="${OPTARG}"
                ;;
            u )
                CYNARA_USER="${OPTARG}"
                ;;
            g )
                CYNARA_GROUP="${OPTARG}"
                ;;
            l )
                SMACK_LABEL="${OPTARG}"
                ;;
            \? )
                echo "Invalid option: -$OPTARG" >&2
                failure
                ;;
            : )
                echo "Option -$OPTARG requires an argument." >&2
                failure
                ;;
        esac
    done
}

generate_checksums() {
    # Output file
    CHECKSUMS="${STATE_PATH}/${DB_DIR}/${CHECKSUM_NAME}"
    GUARD="${STATE_PATH}/${DB_DIR}/${GUARD_NAME}"
    WILDCARD="*"

    # Check if checksums should be generated from backup
    if [ -e $GUARD ] ; then
        WILDCARD="${WILDCARD}~"

        # Database will be read from backup at next load
        CHECKSUMS="${CHECKSUMS}~"
    fi

    # Mimic opening in truncate mode
    echo -n "" > "${CHECKSUMS}"

    # Actual checksums generation
    for FILE in $(find ${STATE_PATH}/${DB_DIR}/${WILDCARD} -type f ! -name "${CHECKSUM_NAME}*" \
                                                                   ! -name "${GUARD_NAME}"); do
        CHECKSUM=""
        if [ 1 -eq $(version_compare ${CHS_MD5_VERSION} ${NEW_VERSION}) ] ; then
            CHECKSUM="$(/usr/sbin/cynara-db-chsgen ${FILE})"
        else
            CHECKSUM="$(/usr/sbin/cynara-db-chsgen ${FILE} -a md5)"
        fi

        if [ 0 -eq $? ] ; then
            echo "${CHECKSUM}" >> ${CHECKSUMS}
        else
            exit_failure
        fi
    done

    # Set proper permissions for newly created checksum file
    chown -R ${CYNARA_USER}:${CYNARA_GROUP} ${CHECKSUMS}

    # Set proper SMACK label for newly created checksum file
    chsmack -a ${SMACK_LABEL} ${CHECKSUMS}
}

minimal_db() {
    # Create Cynara's database directory
    mkdir -p ${STATE_PATH}/${DB_DIR}

    # Create contents of minimal database: first index file, then default bucket
    echo ${DENY_POLICY} > ${STATE_PATH}/${DB_DIR}/${INDEX_NAME}
    touch ${STATE_PATH}/${DB_DIR}/${DEFAULT_BUCKET_NAME}

    # Set proper permissions for newly created database
    chown -R ${CYNARA_USER}:${CYNARA_GROUP} ${STATE_PATH}/${DB_DIR}

    # Set proper SMACK labels for newly created database
    chsmack -a ${SMACK_LABEL} ${STATE_PATH}/${DB_DIR}
    chsmack -a ${SMACK_LABEL} ${STATE_PATH}/${DB_DIR}/*
}

upgrade_db() {
    # Add or update checksum file if necessary
    if [ 0 -ge $(version_compare ${CHS_INTRO_VERSION} ${NEW_VERSION}) ] ; then
        generate_checksums
    fi
}

downgrade_db() {
    # Remove checksum file if necessary
    if [ 0 -lt $(version_compare ${CHS_INTRO_VERSION} ${NEW_VERSION}) ] ; then
        rm "${STATE_PATH}/${DB_DIR}/${CHECKSUM_NAME}" > /dev/null 2>&1
    fi
}

install_db() {
    if [ -z ${NEW_VERSION} ] ; then
        failure
    fi

    minimal_db
    upgrade_db
}

migrate_db() {
    if [ -z ${OLD_VERSION} -o -z ${NEW_VERSION} ]; then
        failure
    fi

    # Create minimal database if there was none:
    if [ ! -d "${STATE_PATH}/${DB_DIR}" ]; then
        install_db
        exit_success
    fi

#quick fix - always generate or remove checksums if needed
    upgrade_db
    downgrade_db

#    case $(version_compare ${OLD_VERSION} ${NEW_VERSION}) in
#        -1 )
#            upgrade_db
#            ;;
#        0 )
#            :
#            # Same version given twice; take no action
#            ;;
#        1 )
#            downgrade_db
#            ;;
#        * )
#            failure
#    esac
}

remove_db() {
    if [ -z ${OLD_VERSION} ]; then
        failure
    fi

    rm -rf "${STATE_PATH}/${DB_DIR}"
}

usage() {
    cat << EOF
Usage: $0 COMMAND OPTIONS

This script installs, migrates to another version or removes Cynara's policies database

Commands:
  upgrade (up)    migrate data to different version of database
  install (in)    create minimal database
  uninstall (rm)  remove database entirely

Options:
  -f from   Set old version of database (mandatory for upgrade and uninstall)
  -t to     Set new version of database (mandatory for upgrade and install)
  -p path   Set path for storing database (default: /var/cynara)
  -u user   Set database owner (default: cynara)
  -g group  Set database group (default: cynara)
  -l label  Set SMACK label for database (default: System)
  -h        Show this help message
EOF
}

failure() {
    usage
    exit_failure
}

exit_failure() {
    exit 1
}

exit_success() {
    exit 0
}

##### Main

if [ 0 -eq $# ]; then
    failure
fi

case $1 in
    "up" | "upgrade" )
        shift $OPTIND
        parse_opts "$@"
        migrate_db
        ;;
    "in" | "install" )
        shift $OPTIND
        parse_opts "$@"
        install_db
        ;;
    "rm" | "uninstall" )
        shift $OPTIND
        parse_opts "$@"
        remove_db
        ;;
    "-h" | "--help" )
        usage
        ;;
    * )
        failure
esac
