#!/bin/sh

#
# File: rmmod-socperf
#
# Description: script to unload socperf driver
#
# Version: 1.9
#
#  This file is provided under a dual BSD/GPLv2 license.  When using or
#  redistributing this file, you may do so under either license.
#
#  GPL LICENSE SUMMARY
#
#  Copyright(C) 2005-2018 Intel Corporation. All rights reserved.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of version 2 of the GNU General Public License as
#  published by the Free Software Foundation.
#
#  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, write to the Free Software
#  Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
#  The full GNU General Public License is included in this distribution
#  in the file called LICENSE.GPL.
#
#  BSD LICENSE
#
#  Copyright(C) 2005-2018 Intel Corporation. All rights reserved.
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in
#      the documentation and/or other materials provided with the
#      distribution.
#    * Neither the name of Intel Corporation nor the names of its
#      contributors may be used to endorse or promote products derived
#      from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

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

# base driver name and version
DRIVER_BASE=socperf
DRIVER_MAJOR=3
DRIVER_MINOR=0
# basic name of driver
DRIVER_NAME=${DRIVER_BASE}${DRIVER_MAJOR}

#error codes
SUCCESS=0
INVALID_DRIVER_NAME=1
DRIVER_UNLOAD_FAILED=245
UNABLE_TO_REMOVE_DRIVER=246
UNABLE_TO_REMOVE_ONE_DRIVER=247
OPTIONS_ERROR=254
COMMANDS_TO_CHECK_FAILED=255

# ------------------------------- 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
}

print_nnl_err()
{
    MSG="$*"
    if [ -w /dev/stderr ] ; then
        echo -n "$MSG" >> /dev/stderr
    else
        echo -n "$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

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

CUT="cut"
GREP="grep"
INSMOD="insmod"
LSMOD="lsmod"
PGREP="pgrep"
PKILL="pkill"
RM="rm"
RMMOD="rmmod"
SED="sed"
SU="su"
TR="tr"
UNAME="uname"
WHICH="which"
SORT="sort"

COMMANDS_TO_CHECK="${CUT} ${GREP} ${INSMOD} ${LSMOD} ${RM} ${RMMOD} ${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 ...
#

# busybox binary check
BUSYBOX_SHELL=` ${GREP} --help 2>&1 | ${GREP} BusyBox`

if [ -z "${BUSYBOX_SHELL}" ] ; then
    COMMANDS_TO_CHECK="${PGREP} ${PKILL} ${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` ;
    if [ -z "${CMD}" ] ; then
        OK="false"
        print_err "ERROR: unable to find command \"$c\" !"
    fi
done
if [ ${OK} != "true" ] ; then
    print_err "Please add the above commands to your PATH and re-run the script ... exiting."
    exit 255
fi

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

# function to show usage and exit
print_usage_and_exit()
{
    err=${1:-0}
    print_msg ""
    print_msg "Usage: $0"
    print_msg ""
    exit $err
}

# check for certain options
stop_pax_service=0
OPTIONS=""
while [ $# -gt 0 ] ; do
    case "$1" in
        -h | --help)
            print_usage_and_exit ${SUCCESS}
            ;;
        -r | -remove-driver)
            remove_driver=1
            if [ -n "$2" ] ; then
                ui_driver_name=$2
                shift
            fi
            ;;
        -R | --remove-all-drivers)
            remove_all_drivers=1
            ;;
        -s | --stop-pax-service)
            stop_pax_service=1
            OPTIONS="$OPTIONS -s"
            ;;
        *)
            print_err ""
            print_err "ERROR: unrecognized option \"$1\""
            print_usage_and_exit ${OPTIONS_ERROR}
            ;;
    esac
    shift
done

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

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

# set the directory of the rmmod-socperf script
SCRIPT_DIR=`dirname $0`
SOCPERF_SHELL=
SOCPERF_FORCE=-f

if [ -n "${BUSYBOX_SHELL}" ] ; then
    SOCPERF_SHELL=sh
    SOCPERF_FORCE=
fi

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

init_variables()
{
    if [ -n "${remove_all_drivers}" ] ; then
        DRIVERS_TO_CHECK=$( ${LSMOD} | ${CUT} -f1 -d' ' | ${GREP} -E '^socperf[0-9][_]*[0-9]*$' | ${TR} '\n' ' ' )
    elif [ -n "${remove_driver}" ] ; then
        if [ -z "${ui_driver_name}" ] ; then
            print_err ""
            print_err "ERROR: provide a valid name for the driver to be unloaded ... exiting ..."
            print_err ""
            exit ${INVALID_DRIVER_NAME}
        fi
        # remove double quotes if any
        ui_driver_name=$( echo ${ui_driver_name} | ${SED} 's/"//g' )
        DRIVERS_TO_CHECK=$( echo ${ui_driver_name} | ${TR} ',' ' ' )
    else
        DRIVERS_TO_CHECK="${DRIVER_NAME}"
    fi

    # remove duplicates if any
    if [ -n "${DRIVERS_TO_CHECK}" ] ; then
        DRIVERS_TO_CHECK=$( echo ${DRIVERS_TO_CHECK} | ${TR} ' ' '\n' | ${SORT} -u | ${TR} '\n' ' ' )
    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 socperf drivers were found loaded in the kernel."
        print_msg ""
    fi

    not_loaded_drivers=""
    for driver in ${DRIVERS_TO_CHECK} ; do
        DRIVER_LOADED=$( ${LSMOD} | ${CUT} -d ' ' -f 1 | ${GREP} ${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, and we're not stopping PAX service, then exit
    if [ $stop_pax_service -ne 1 -a -z "${DRIVERS_TO_CHECK}" ] ; then
        exit ${SUCCESS}
    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 ${SUCCESS}
            fi
        fi
    fi
}

shutdown_processes()
{
    SEP_PROCESSES="sep"
    SHUTDOWN_SUCCEEDED=1
    for i in ${SEP_PROCESSES} ; do
        if [ -z "${BUSYBOX_SHELL}" ] ; then
            PLIST=`${PGREP} -l -x $i`
            if [ -n "${PLIST}" ] ; then
                print_err "Shutting down the following $i process(es):"
                print_nnl_err "${PLIST} "
                ${PKILL} -x $i
                sleep 2
                ${PKILL} -x $i
                sleep 1
                PLIST=`${PGREP} -l -x $i`
                if [ -n "${PLIST}" ] ; then
                    print_err " -- shutdown FAILED"
                    SHUTDOWN_SUCCEEDED=0
                else
                    print_err ""
                fi
            fi
        else
            PLIST=`ps | ${GREP} -w $i | ${GREP} -v grep | ${CUT} -d ' ' -f 2`
            if [ -n "${PLIST}" ] ; then
                print_err "Shutting down the following $i process(es):"
                print_nnl_err "${PLIST} "
                kill ${PLIST}
                sleep 2
                PLIST=`ps | ${GREP} -w $i | ${GREP} -v grep`
                if [ -n "${PLIST}" ] ; then
                    print_err " -- shutdown FAILED"
                    SHUTDOWN_SUCCEEDED=0
                else
                    print_err ""
                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 ${DRIVER_UNLOAD_FAILED}
    fi
}

unload_socperf_driver()
{
    for driver in ${DRIVERS_TO_CHECK} ; do
        DRIVER_LOADED=$( ${LSMOD} | ${CUT} -d ' ' -f 1 | ${GREP} ${driver} )
        SOCPERF_DRIVER_DEPENDENCY=$( ${LSMOD} | ${GREP} ${driver} | ${GREP} -E 'sep|socwatch' | ${TR} ' ' ';' )
        SOCPERF_DRIVER_DEPENDENCY=${SOCPERF_DRIVER_DEPENDENCY##*;}

        # 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
            if [ -n "${SOCPERF_DRIVER_DEPENDENCY}" ] ; then
                print_err "Warning: unable to unload ${DRIVER_LOADED} driver since"
                print_err "  driver(s) ${SOCPERF_DRIVER_DEPENDENCY} is (are) using it."
                unable_to_remove_one=1
                continue
            fi
            print_nnl "Removing ${DRIVER_LOADED} driver from the kernel ... "
            sleep 2
            ${RMMOD} ${DRIVER_LOADED}
            RMMOD_RESULT=$?
            if [ ${RMMOD_RESULT} -ne 0 ] ; then
                print_err ""
                print_err "ERROR: unable to unload the driver from the kernel ..."
                print_err ""
                error_on_removing=1
                continue
            fi
            print_msg "done."

            # remove socperf devices that were created by insmod script
            if [ -e /dev/${DRIVER_LOADED} ] ; then
                print_nnl "Deleting /dev/${DRIVER_LOADED} devices ... "
                sleep 1
                ${RM} -r ${SOCPERF_FORCE} /dev/${DRIVER_LOADED}
                print_msg "done."
            fi

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

    done

    if [ -n "${error_on_removing}" ] ; then
          print_err ""
          print_err "Exiting with atleast one error ..."
          print_err ""
          exit ${UNABLE_TO_REMOVE_DRIVER}
    fi

    if [ -n "${unable_to_remove_one}" ] ; then
          exit ${UNABLE_TO_REMOVE_ONE_DRIVER}
    fi
}

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

# init_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 socperf driver is loaded, then attempt to remove it
unload_socperf_driver

exit ${SUCCESS}
