#!/bin/sh
#
#
# Copyright (C) 2019 Intel Corporation
#
# This software and the related documents are Intel copyrighted materials, and your use of them
# is governed by the express license under which they were provided to you ("License"). Unless
# the License provides otherwise, you may not use, modify, copy, publish, distribute, disclose
# or transmit this software or the related documents without Intel's prior written permission.
#
# This software and the related documents are provided as is, with no express or implied
# warranties, other than those that are expressly stated in the License.
#

#
# File: kldunload-sep
#
# Description: script to unload SEP/PAX driver
#
# Version: 1.0
#

# ------------------------------ CONSTANTS -----------------------------------

# basic name of driver
SEP_DRIVER_NAME=sep
PAX_DRIVER_NAME=pax

# ------------------------------- OUTPUT -------------------------------------

print_msg()
{
    MSG="$*"
    echo "$MSG"
}

print_nnl()
{
    MSG="$*"
    echo -n "$MSG"
}

print_err()
{
    MSG="$*"
    if [ -w /dev/stderr ] ; then
        echo "$MSG" >> /dev/stderr
    else
        echo "$MSG"
    fi
}

# set the path to include "standard" locations so commands below can be found
PATH="/sbin:/usr/sbin:/bin:/usr/bin/:/usr/local/sbin:/usr/local/bin:/usr/local/gnu/bin:"${PATH}":."
export PATH

# --------------------------------- FUNCTIONS --------------------------------

# function to show usage and exit
print_usage_and_exit()
{
    err=${1:-0}
    print_msg ""
    print_msg "Usage: $0 [ options ]"
    print_msg ""
    print_msg " where \"options\" are the following:"
    print_msg ""
    print_msg "    -r | -remove-driver"
    print_msg "      removes the driver specified by the user."
    print_msg "      e.g.: ./kldunload-sep -r <driver1>[,<driver2>,...]"
    print_msg ""
    print_msg "    -R | --remove-all-drivers"
    print_msg "      removes all the loaded old version SEP drivers along with the current version."
    print_msg ""
    exit $err
}

init_variables()
{
    if [ -n "${remove_driver}" ] ; then
        if [ -z "${ui_driver_name}" ] ; then
            print_err ""
            print_err "Error: provide a valid name for the driver to be removed ... exiting ..."
            print_err
            exit 1
        fi
        DRIVERS_TO_CHECK=${ui_driver_name}
    else
        DRIVERS_TO_CHECK=${SEP_DRIVER_NAME}" "${PAX_DRIVER_NAME}
    fi
}

remove_driver_from_list()
{
    local driver_to_remove=$1
    shift
    local driver_list="$*"
    local driver=""
    local temp=""
    for driver in ${driver_list} ; do
        if [ "${driver}" != "${driver_to_remove}" ] ; then
            if [ -z "${temp}" ] ; then
                temp="${driver}"
                continue
            fi
            temp="${temp} ${driver}"
        fi
    done
    driver_list=${temp}

    echo ${driver_list}
}


check_any_driver_presence()
{
    if [ -z "${DRIVERS_TO_CHECK}" ] ; then
        print_msg ""
        print_msg "Warning:  no sep drivers were found loaded in the kernel."
        print_msg ""
    fi

    not_loaded_drivers=""
    for driver in ${DRIVERS_TO_CHECK} ; do
        DRIVER_LOADED=$( ${KLDSTAT} -n ${driver} )
        if [ -z "${DRIVER_LOADED}" ] ; then
            not_loaded_drivers="${not_loaded_drivers} ${driver}"
            # remove unloaded driver from array
            DRIVERS_TO_CHECK=$( remove_driver_from_list ${driver} ${DRIVERS_TO_CHECK} )
        fi
    done

    # if any of the given drivers are not loaded, print out the names
    if [ -n "${not_loaded_drivers}" ] ; then
        print_msg ""
        print_msg "Warning:  the following driver(s) were not found loaded in the kernel: ${not_loaded_drivers}."
        print_msg ""
    fi

    # if no driver is loaded, then exit
    if [ -z "${DRIVERS_TO_CHECK}" ] ; then
        exit 0
    fi
}

check_user_type()
{
    if [ -z "${BUSYBOX_SHELL}" ] ; then
        if [ "${USER}x" != "rootx" ] ; then
            if [ ! -w /dev ] ; then
                print_msg "NOTE:  super-user or \"root\" privileges are required in order to continue."
                print_nnl "Please enter \"root\" "
                exec ${SU} -c "/bin/sh ${SCRIPT} ${OPTIONS}"
                print_msg ""
                exit 0
            fi
        fi
    fi
}

shutdown_processes()
{
    SEP_PROCESSES="sep emon"
    SHUTDOWN_SUCCEEDED=1
    for i in ${SEP_PROCESSES} ; do
        if [ -z "${BUSYBOX_SHELL}" ] ; then
            PLIST=`${PGREP} -l -x $i`
            if [ -n "${PLIST}" ] ; then
                print_msg "Shutting down the following $i process(es):"
                print_nnl "${PLIST} "
                ${KILL} $i
                sleep 2
                PLIST=`${PGREP} -l -x $i`
                if [ -n "${PLIST}" ] ; then
                    print_msg " -- shutdown FAILED"
                    SHUTDOWN_SUCCEEDED=0
                else
                    print_msg ""
                fi
            fi
        else
            PLIST=`ps | ${GREP} -w "\s$i\s" | ${GREP} -v grep | ${CUT} -d ' ' -f 2`
            if [ -n "${PLIST}" ] ; then
                print_msg "Shutting down the following $i process(es):"
                print_nnl "${PLIST} "
                kill ${PLIST}
                sleep 2
                PLIST=`ps | ${GREP} -w "\s$i\s" | ${GREP} -v grep`
                if [ -n "${PLIST}" ] ; then
                    print_msg " -- shutdown FAILED"
                    SHUTDOWN_SUCCEEDED=0
                else
                    print_msg ""
                fi
            fi
        fi
    done

    # if any driver processes are still running, exit with error
    if [ ${SHUTDOWN_SUCCEEDED} -eq 0 ] ; then
        print_err ""
        print_err "ERROR:  The above process(es) must be shutdown before unloading the driver."
        print_err ""
        exit 245
    fi
}

unload_driver()
{
    for driver in ${DRIVERS_TO_CHECK} ; do
        DRIVER_LOADED=$( ${KLDSTAT} -n ${driver} )

        # this check is reduntant now since we are removing unloaded drivers at
        # check_any_driver_presence.
        # keeping this check as an extra measure though
        if [ -n "${DRIVER_LOADED}" ] ; then
            print_nnl "Removing ${driver} driver from the kernel ... "
            sleep 2
            ${KLDUNLOAD} ${driver}
            sleep 1
            KLDUNLOAD_RESULT=$?
            if [ ${KLDUNLOAD_RESULT} -ne 0 ] ; then
                print_err ""
                print_err "Error:  unable to remove ${DRIVER_LOADED} driver from the kernel ..."
                print_err ""
                error_on_removing=1
                continue
            else
                atleast_one_removed=1
            fi
            print_msg "done."

            # show which SEP driver was unloaded
            print_msg "The ${driver} driver has been successfully unloaded."
        fi

    done

    # exit if all driver unload failed
    if [ -z "${atleast_one_removed}" ] ; then
        if [ -n "${DRIVERS_TO_CHECK}" ] ; then
            exit 246
        fi
    fi
}

# ------------------------------ PARSING ------------------------------------

# check for certain options
remove_driver=
remove_all_drivers=0
OPTIONS=""
while [ $# -gt 0 ] ; do
    case "$1" in
        -h | --help)
            print_usage_and_exit 0
            ;;
        -r | -remove-driver)
            remove_driver=1
            if [ -n "$2" ] ; then
                ui_driver_name=$2
                OPTIONS="$OPTIONS -r $2"
                shift
            else
                print_err "ERROR: Driver name to unload was not specified"
                print_usage_and_exit 253
            fi
            ;;
        -R | --remove-all-drivers)
            remove_all_drivers=1
            OPTIONS="$OPTIONS -R"
            ;;
        *)
            print_err ""
            print_err "ERROR: unrecognized option \"$1\""
            print_usage_and_exit 254
            ;;
    esac
    shift
done

# ------------------------------ COMMANDS ------------------------------------

CUT="cut"
GREP="grep"
KLDSTAT="kldstat"
PGREP="pgrep"
KILL="killall"
RM="rm"
KLDUNLOAD="kldunload"
SED="sed"
SU="su"
TR="tr"
UNAME="uname"
WHICH="which"
SORT="sort"

COMMANDS_TO_CHECK="${CUT} ${GREP} ${KLDSTAT} ${RM} ${KLDUNLOAD} ${SED} ${TR} ${UNAME} ${SORT}"

#
# Note: Busybox has a restricted shell environment, and
#       conventional system utilities may not be present;
#       so need to account for this ...
# Note: Restricted environment mode can be forced by the option -re
#       in case user may not know about the Busybox
#

# busybox binary check
if [ -z "${BUSYBOX_SHELL}" ]; then
    # if not forced by command line option -re then check it
    BUSYBOX_SHELL=` ${GREP} --help 2>&1 | ${GREP} BusyBox`
fi

if [ -z "${BUSYBOX_SHELL}" ] ; then
    COMMANDS_TO_CHECK="${GREP} ${KILL} ${SU} ${COMMANDS_TO_CHECK}"
fi

# if any of the COMMANDS_TO_CHECK are not executable, then exit script
OK="true"
for c in ${COMMANDS_TO_CHECK} ; do
    CMD=`${WHICH} $c 2>&1` ;
    ret_val=$?
    if [ ${ret_val} -ne 0 ] ; then
        OK="false"
        print_err "ERROR: unable to find command \"$c\" !"
    fi
done
if [ ${OK} != "true" ] ; then
    print_err "If you are using BusyBox, please re-run this script with the '-re' flag added"
    print_err "Otherwise, please add the above commands to your PATH and re-run the script ... exiting."
    exit 255
fi

# ------------------------------ VARIABLES -----------------------------------

SCRIPT=$0
PLATFORM=`${UNAME} -m`
KERNEL_VERSION=`${UNAME} -r`

# set the directory of the rmmod-sep script
SCRIPT_DIR=`dirname $0`
SEP_SHELL=
SEP_FORCE=-f

if [ -n "${BUSYBOX_SHELL}" ] ; then
   SEP_SHELL=sh
   SEP_FORCE=
fi

VARLOG_DIR="/var/log"

# --------------------------------- MAIN -------------------------------------

# initialize variables
init_variables

# check for a loaded driver (should be only one)
check_any_driver_presence

# check if USER is root
check_user_type

# shutdown any currently running SEP processes
shutdown_processes

# if SEP driver is loaded, then attempt to remove it
unload_driver

# if atleast one SEP driver fails to be removed return error
if [ -n "${error_on_removing}" ] ; then
    exit 246
fi

exit 0
