#!/bin/bash
# Copyright (C) 2019. Huawei Technologies Co., Ltd. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 and
# only version 2 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.

SHELL_DIR=$(dirname $0)
SHELL_DIR=$(cd $SHELL_DIR; pwd)
G_PRIVATE_MODULE=
G_HOTPATCH=
G_HOTPATCH_ID=
G_HOTPATCH_TAR=
G_DIFFEXT=
G_JOBS="$(getconf _NPROCESSORS_ONLN)"
G_PATCH_SRC=
if [ -z "$CUSTOM_BUILD_DIR" ];then
    G_KERNEL_SRC=/opt/patch_workspace/kernel-source
    G_KERNEL_CONFIG=/opt/patch_workspace/.config
    G_HOTPATCH_DIR=/opt/patch_workspace/hotpatch
    G_VMLINUX=/opt/patch_workspace/vmlinux
else
    G_KERNEL_SRC="$CUSTOM_BUILD_DIR"/kernel-source
    G_KERNEL_CONFIG="$CUSTOM_BUILD_DIR"/.config
    G_HOTPATCH_DIR="$CUSTOM_BUILD_DIR"/hotpatch
    G_VMLINUX="$CUSTOM_BUILD_DIR"/vmlinux
fi
G_PREFIX=klp
G_TMP_DIR=
G_PATCHFILE=
G_MODULE_SRC=
G_MODULE_MAKEFILE=
G_KPLICE_PWD_FIELD="KPLICE_PWD"
G_PRIVATE_MODULE_SRC_FIELD="PRIVATE_MODULE_SRC"
G_KPATCH_FLAGS=/opt/patch_workspace/.flags
G_DEBUG_INFO=
G_EXT_FLAGS=
USERMODBUILDDIR=

#########################################################
#    Description: usage
#    Input:  
#    Return: 0-success
#########################################################
function fn_usage()
{
    cat << EOF
Usage:
    make_hotpatch [*OPTIONS*] -d patch_diffext -i patch_id -m module_src

Options:
    -d,--diffext 
            make hotpatch using the modified source files with
            names ending in *patch_diffext*. The patch will be determined by
            comparing all of the files in the build directory tree
            whose names end with the extra extension *patch_diffext* against the
            corresponding files without the extra extension.
    -j,--jobs
            Specifies the number of jobs to run simultaneously while
            performing builds.
    -i,--id
            Specifies the unique value that will be used as the identifier
            of the hotpatch. 
    -m,--modulesrc
            Specifies the module src path for make user module hotpatch.
    -f,--makefile
            Specifies the module makefile for make user module hotpatch.
    --extra_flags
            Specifies extra flags file for make user module hotpatch.
    --no_stack_check
            make hotpatch without stack check when activating or deactivating this patch.
    --debug_info
            Save debug info when hotpatch building.Default is closed.
    -p,--patch
            Specifies the patch file
    --kallsyms
            Specifies module kallsyms in running system(cat /proc/kallsyms|grep mod)
    -h,--help
            help info
EOF

return 0
}


#########################################################
#    Description: fn_do_clean, remove tmp file
#    Input: 
#        $1: init flag
#    Return: 0-success
#########################################################
function fn_do_clean()
{
    local ret=0 
    G_EXT_FLAGS=
    unset NO_STACK_CHECK
    unset KALLSYMS
    rm -rf $G_TMP_DIR

}

#########################################################
#    Description:  fn_check_reg_char
#    Input:  
#        $@: input param
#    Return: 0-success,1-failed
#########################################################
function fn_check_reg_char()
{
    local l_str=$1
    local l_str_maxlen=$2
    local l_str_ex=$3
    local len_str=${#l_str}

    if [ -z "$l_str" ];then
        echo "error: the string is empty, check string failed"
        return 1
    fi
    ##�ַ�������
    if [ -n "$l_str_maxlen" ] && [ -z "`echo $l_str_maxlen | sed 's/[0-9]//g'`" ];then
        if [ $len_str -gt $l_str_maxlen ]; then
            echo "error: The length of $l_str exceed max length $l_str_maxlen." 
            return 1
        fi
    fi
    #���ܰ��������ַ�
    if [ -n "$l_str_ex" ] && [ -n "`echo $l_str | grep -E [$l_str_ex] 2>/dev/null`" ];then
        echo "error: string $l_str included characters $l_str_ex."
        return 1
    fi
    ##���ֺ���ĸ��ͷ
    if [ -z "`echo $l_str | grep -E '^[a-z0-9A-Z]'`" ];then
        echo "error: string $l_str must start with a character ('0-9' or 'A-Z' or 'a-z')."
        return 1
    fi 
    ##ֻ�����ֺ���ĸ -_
    if [ -n "`echo $l_str | grep -E '[^a-z0-9A-Z_-]'`" ];then
        if [ -n "$l_str_ex" ] ;then
            ##�����־д����
            echo "error: string $l_str can only contain characters included by ('0-9', 'a-z', 'A-Z')"
        else
            echo "error: string $l_str can only contain characters included by ('0-9', 'a-z', 'A-Z', '-', '_')."
        fi

        return 1
    fi    

}

#########################################################
#    Description: fn_check_id 
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_id()
{
    local l_id=$1
    fn_check_reg_char "$l_id" "32" "-_"
    if [ $? -ne 0 ];then
        echo "error: check hotpatch id failed"
        return 1
    fi

    G_HOTPATCH_ID=$l_id
    G_HOTPATCH=${G_PREFIX}_${G_HOTPATCH_ID}.ko
    G_HOTPATCH_TAR=${G_PREFIX}_${G_HOTPATCH_ID}.tar.gz
    if [ -f "$G_HOTPATCH_DIR/$G_HOTPATCH_TAR" ];then
        echo "error: $G_HOTPATCH_DIR/$G_HOTPATCH_TAR is exist, check hotpatch id failed"
        return 1    
    fi
    return 0
}


#########################################################
#    Description: fn_check_jobs 
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_jobs()
{
    local l_jobs=$1
    if [ -z "`echo "$l_jobs" | grep ^[1-9]`" ] || [ -n "`echo "$l_jobs" | sed 's/[0-9]//g'`" ];then
        echo "error: the '-j' option requires a positive integral argument"
        return 1
    fi
    G_JOBS=$l_jobs
    return 0
}

#########################################################
#    Description: fn_check_patch
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_patch(){
    local l_patch=$1
    if [ ! -f "$1" ];then
        echo "error:patch file $1 does not exist"
        return 1
    fi
    G_PATCHFILE=$l_patch
}
#########################################################
#    Description: fn_check_diffext 
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_diffext()
{
    local l_diffext=$1

    if [ -z "$l_diffext" ];then
        echo "error: diffext is empty, check diffext failed"
        return 1
    fi
    G_DIFFEXT=$l_diffext
    return 0
}


#########################################################
#    Description: fn_check_kernelsrc 
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_kernelsrc()
{
    local l_kernelsrc=$1

    if [ ! -d "$l_kernelsrc" ];then
        echo "error: kernel src $l_kernelsrc does not exist, check kernel src failed"
        return 1
    fi
    G_KERNEL_SRC=$l_kernelsrc
    return 0
}

#########################################################
#    Description: fn_check_modulesrc 
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_modulesrc()
{
    local l_modulesrc=$1

    if [ ! -d "$l_modulesrc" ];then
        echo "error: module src $l_modulesrc does not exist, check module src failed"
        return 1
    fi
    echo "$l_modulesrc" | grep -q ^/ 
    if [ $? -ne 0 ];then
        echo "error: module src $l_modulesrc must be absolute path, check module src failed"
        return 1    
    fi
    G_MODULE_SRC="$l_modulesrc"
    return 0
}
#########################################################
#    Description: fn_check_makefile
#    Input:
#        $1:
#    Return: 0-success,1-failed
#########################################################
function fn_check_makefile()
{
    local l_module_makefile=$1

    if [ ! -f "$l_module_makefile" ] && [ "$(basename $l_module_makefile)" = "Makefile" ];then
        echo "error: module makefile $l_module_makefile does not exist or makefile name is not Makefile, check module makefile failed"
        return 1
    fi
    echo "$l_module_makefile" | grep -q ^/
    if [ $? -ne 0 ];then
        echo "error: module makefile $l_module_makefile must be absolute path, check module src failed"
        return 1
    fi
    G_MODULE_MAKEFILE=$l_module_makefile
    return 0
}
#########################################################
#    Description: fn_check_extra_flags 
#    Input:  
#        $1: 
#    Return: 0-success,1-failed
#########################################################
function fn_check_extra_flags()
{
    local l_extra_flags=$1

    if [ ! -f "$l_extra_flags" ];then
        echo "error: extra flags file $l_extra_flags does not exist, check extra flags failed"
        return 1
    fi
    G_EXT_FLAGS="`readlink -f ${l_extra_flags}`"
    cp -a $l_extra_flags $G_KPATCH_FLAGS
    if [ $? -ne 0 ];then
        echo "error: copy $l_extra_flags to $G_KPATCH_FLAGS failed"
        return 1
    fi    
    return 0
}

#########################################################
#    Description:  verify input param
#    Input:  
#        $@: input param
#    Return: 0-success,1-failed
#########################################################
function fn_verify_input()
{
    local input_param=$@

    if [ $# -lt 1 ]; then
        echo "error: missing param,please check it"
        fn_do_clean
        fn_usage
        exit 1
    fi

    if [ -z "`echo "$input_param" | grep -w "\-i"`" \
        -a -z "`echo $input_param | grep -w "\-\-id"`" \
        -a $# -gt 1 ];then
        echo "error: missing param -i or --id"
        fn_do_clean
        fn_usage
        exit 1
    fi
    if [ -z "`echo "$input_param" | grep -w "\-d"`" \
        -a -z "`echo $input_param | grep -w "\-\-diffext"`" \
        -a -z "`echo $input_param | grep -w "\-p"`" \
        -a -z "`echo $input_param | grep -w "\-\-patch"`" \
        -a $# -gt 1 ];then
        echo "error: missing param -d,--diffext or -p,--patch"
        fn_do_clean
        fn_usage
        exit 1
    fi

    while [ $# -ge 1 ]; do
        case "$1" in
            -h|--help)
                fn_usage
                fn_do_clean
                exit 0
                ;;
            -j|--jobs)
                fn_check_jobs $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;        
            -i|--id)
                fn_check_id $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            -d|--diffext)
                fn_check_diffext $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            -p|--patch)
                fn_check_patch $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            -k|--kernelsrc)
                fn_check_kernelsrc $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            -m|--modulesrc)
                fn_check_modulesrc $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            -f|--makefile)
                fn_check_makefile $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            --extra_flags)
                fn_check_extra_flags $2
                if [ $? -eq 0 ]; then
                    shift 2
                else
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                ;;
            --no_stack_check)
                export NO_STACK_CHECK="yes"
                shift 1
                ;;
            --debug_info)
                G_DEBUG_INFO="-d"                                                     
                shift 1 
                ;;
            --kallsyms)
                if [ "$2" == "" ];then
                    echo "error: param --kallsyms need file parameter"
                    fn_do_clean
                    fn_usage
                    exit 1
                fi
                export KALLSYMS=$(readlink -f $2)
                shift 2
                ;;

            *)
                echo "error: params is invalid,please check it."
                fn_do_clean
                fn_usage
                exit 1
                ;;
        esac
    done


    return 0
}

#########################################################
#    Description:  fn_makepatch
#    Input:  
#    Return: 0-success,1-failed
#########################################################
function fn_makepatch()
{
    local l_extra_module=
    local l_jobs=
    local l_ret=0
    local existflag=0

    G_PATCH_SRC=`readlink -f $G_KERNEL_SRC`
    if [ -d "$G_MODULE_SRC" ];then
        echo "make out of tree module hotpatch"
        G_PATCH_SRC=$G_MODULE_SRC
        l_extra_module="-m $G_MODULE_SRC"
        USERMODBUILDDIR=$G_MODULE_SRC
    fi 

    if [ -n "$G_JOBS" ];then
        l_jobs="--jobs=$G_JOBS"
    fi
    G_TMP_DIR=/tmp/${G_PREFIX}_${G_HOTPATCH_ID}
    mkdir -p $G_TMP_DIR

    if [ -z "$G_PATCHFILE" ];then
        #generate src patch file from G_DIFFEXT
        G_PATCHFILE=$G_TMP_DIR/$G_HOTPATCH_ID.patch
        rm -rf $G_PATCHFILE
        cd $G_PATCH_SRC &>/dev/null
        l_change_file=($(find -L -name "*$G_DIFFEXT" | xargs readlink -f 2>/dev/null| sort | uniq))
        echo "detect change files:${l_change_file[@]}"
        for file in ${l_change_file[@]};
        do
            file="./${file#$(readlink -f $G_PATCH_SRC)}"
            orig_file=${file%$G_DIFFEXT}
            if [ "${orig_file##*.}" == "h" ]; then
                existflag=1 && break
            fi
            diff -u $orig_file $file >> $G_PATCHFILE
        done
        cd - &>/dev/null
        if [ ${existflag} -eq 1 ]; then
            echo "error: do not modify the header file"
            return 1
        fi
        if [ -f "$G_PATCHFILE" ];then
            echo "make patch $G_PATCHFILE"
        else
            echo "no change detected"
            return 1	
        fi
    else
        cp $G_PATCHFILE $G_TMP_DIR/${G_HOTPATCH_ID}.patch
        G_PATCHFILE=$G_TMP_DIR/${G_HOTPATCH_ID}.patch
    fi


    cd $G_TMP_DIR &>/dev/null
    if [ -n "$G_MODULE_MAKEFILE" ];then
        USERMODBUILDDIR=$(dirname $G_MODULE_MAKEFILE)
    fi
    export USERMODBUILDDIR
    export USERMODFLAGS=`cat $G_KPATCH_FLAGS`
    export NO_PROFILING_CALLS="yes"
    export DISABLE_AFTER_LOAD="yes"
    export KEEP_JUMP_LABEL="yes"
    UNAME_R=$(uname -r)
    UNAME_R_ARCH=${UNAME_R##*.}
    SKIP_GCC_CHECK=""
    if [[ "${UNAME_R_ARCH}" != "$(uname -p)" ]];then
            echo "build in cross compile environment, skip gcc check"
            SKIP_GCC_CHECK="--skip-gcc-check"
    fi
    kpatch-build -s $G_PATCH_SRC -c $G_KERNEL_CONFIG -v $G_VMLINUX ${SKIP_GCC_CHECK} -n "${G_PREFIX}_${G_HOTPATCH_ID}" $G_DEBUG_INFO $G_PATCHFILE -R
    l_ret=$?
    cd - &>/dev/null
    if [ $l_ret -eq 0 ] && [ -f "$G_TMP_DIR/$G_HOTPATCH" ];then
        cd /tmp &>/dev/null
        l_env_file=$G_TMP_DIR/toolenv
        if [ -n "`which rpm 2>/dev/null`" ]; then
            echo "------------------------------------------------------------------------ " > "${l_env_file}"
            echo >> "${l_env_file}"
            echo "The kpatch tool version info and release date:" >> "${l_env_file}"
            rpm -qi kpatch >> "${l_env_file}"
            echo >> "${l_env_file}"
            echo "------------------------------------------------------------------------ " >> "${l_env_file}"
        else
            echo "------------------------------------------------------------------------ " >> "${l_env_file}"
        fi

        echo >> "${l_env_file}"
        echo "The hotpatch build time:" >> "${l_env_file}"
        echo "`date '+%Y-%m-%d %H:%M:%S'`" >> "${l_env_file}"
        echo >> "${l_env_file}"
        echo "------------------------------------------------------------------------ " >> "${l_env_file}"

        if [ -f "/etc/EulerLinux.conf" ]; then
            echo >> "${l_env_file}"
            echo "The euler compile env version info:" >> "${l_env_file}"
            cat /etc/EulerLinux.conf >> "${l_env_file}"
            echo >> "${l_env_file}"
            echo "------------------------------------------------------------------------ " >> "${l_env_file}"
        fi

        if [ -n "${G_MODULE_SRC}" ]; then
            echo >> "${l_env_file}"
            echo "The module hotpatch compile path info:" >> "${l_env_file}"
            echo "MODULE_SRC=${G_MODULE_SRC}" >> "${l_env_file}"
            echo "MODULE_MAKEFILE=${G_MODULE_MAKEFILE}" >> "${l_env_file}"
            echo >> "${l_env_file}"
            echo "------------------------------------------------------------------------ " >> "${l_env_file}"
        fi

        if [ -f "${G_EXT_FLAGS}" ]; then
            echo >> "${l_env_file}"
            echo "The module hotpatch compile flags info:" >> "${l_env_file}"
            cat "${G_EXT_FLAGS}" >> "${l_env_file}"
            echo >> "${l_env_file}"
            echo "------------------------------------------------------------------------ " >> "${l_env_file}"
        fi

        if [ -n "${G_DEBUG_INFO}" ]; then
            echo >> "${l_env_file}"
            echo "The debug option info:" >> "${l_env_file}"
            echo "${G_DEBUG_INFO}" >> "${l_env_file}"
            echo >> "${l_env_file}"
            echo "------------------------------------------------------------------------ " >> "${l_env_file}"
        fi

        tar -czf $G_HOTPATCH_TAR ${G_PREFIX}_${G_HOTPATCH_ID}
        mv ${G_HOTPATCH_TAR} ${G_HOTPATCH_DIR}
        cd - &>/dev/null
        if [ $? -ne 0 ];then
            echo "error: move ${G_HOTPATCH} to ${G_KSPLICE_HOTPATCH} failed"
            return 1            
        fi
    else
        echo "error: invoke kpatch-build shell script to build patch failed"
        return 1
    fi

}
#########################################################
#    Description:  main
#    Input:  
#    Return: 0-success,1-failed
#########################################################
function fn_main()
{
    local ret=
    local pid=
    fn_verify_input $@
    if [ $? -ne 0 ]; then
        fn_do_clean
        return 1
    fi

    fn_makepatch
    if [ $? -ne 0 ]; then
        fn_do_clean
        return 1
    fi    

    fn_do_clean 

    return 0

}

function fn_prepare()
{
    local src_dir=""
    kerver=`uname -r`
    if [ ! -d /usr/src/linux-$kerver ];then
        kerver=${kerver%.x86_64}
        kerver=${kerver%.aarch64}
        src_dir="kernels"
    fi
    echo kernel version:$kerver
    if [ ! -L kernel-source ];then
        if [ -d /arm/arm_kernel ];then
            ln -s /arm/arm_kernel/linux-$kerver kernel-source
        else
            ln -s /usr/src/$src_dir/linux-$kerver kernel-source
            cp /lib/modules/`uname -r`/build/Makefile /usr/src/$src_dir/linux-$kerver 
        fi
    fi
    if [ ! -L .config ];then
        if [ -d /arm/arm_kernel ];then
            ln -s /arm/arm_kernel/linux-$kerver/.config .config
        else
            ln -s /usr/src/$src_dir/linux-$kerver/.config  .config
            cp /lib/modules/`uname -r`/build/.config /usr/src/$src_dir/linux-$kerver 
        fi
    fi
    rm -rf $G_KPATCH_FLAGS
    touch $G_KPATCH_FLAGS
}

G_NUM=`pidof -x make_hotpatch | wc -w`
if [ $G_NUM -gt 2 ];then
    echo "[$0]someone is making, please try again later."
    exit 1
fi

fn_prepare
fn_main $@
exit $?