#!/bin/bash

############################################################################
#
# Script for converting the SCST source tree as it exists in the Subversion
# repository to a Linux kernel patch.
#
# Copyright (C) 2008-2020 Bart Van Assche <bvanassche@acm.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, version 2
# of the License.
#
# 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.
#
############################################################################

########################
# Function definitions #
########################

# shellcheck source=./kernel-functions
source "$(dirname "$0")/kernel-functions"

function usage {
  echo "Usage: $0 [-d] [-h] [-n] [-p <dir>] [-s] [-u] <kernel version>"
  echo "where: "
  echo "        -d - enable patch specialization debugging"
  echo "        -h - show this text"
  echo "        -n - do not delete code disabled via preprocessor statements"
  echo "        -p - generate multiple patches instead of one big patch into"\
       "the specified directory."
  echo "        -s - disable patch specialization."
  echo "        -u - enables #define GENERATING_UPSTREAM_PATCH."
}

# Convert an existing patch.
# $1: path of patch to be added.
# $2: path in kernel tree of file to be patched.
function add_patch {
  if [ ! -e "$1" ]; then
    echo "Error: could not find $1." >&2
    exit 1
  fi

  sed -e "s:^--- [^ ]*:--- linux-${kver}/$2:" \
      -e "s:^+++ [^ ]*:+++ linux-${kver}/$2:" \
      < "$1"
}

# Generate a patch for a file to be added to the kernel source tree, and strip
# trailing whitespace from C source files while converting the file to patch
# format.
# $1: path of file to be added.
# $2: path in kernel tree where file should be added.
function add_file {
  local a b

  if [ ! -e "$1" ]; then
    echo "Error: could not find $1." >&2
    exit 1
  fi

  # Only include files that were not generated by the build process
  # -- skip *.mod.c.
  if [ "$1" = "${1%.mod.c}" ] && [ "$1" ]; then
    cat <<EOF
diff -uprN linux-${kver}/$2 linux-${kver}/$2
--- linux-${kver}/$2
+++ linux-${kver}/$2
@@ -0,0 +1,$(wc -l "$1" | { read -r a b; echo "$a"; echo "$b" >/dev/null; }) @@
EOF
    # Insert a '+'-sign at the start of each line.
    sed -e 's/^/+/' < "$1" | \
    if [ "${replace_sbug_by_bug}" = "true" ]; then
      sed -e 's/sBUG(\([^)]*\)/BUG(\1/g' -e 's/sBUG_ON(\([^)]*\)/BUG_ON(\1/g'
    else
      cat
    fi \
    |  \
    if [ "${2%.[ch]}" != "$2" ]; then
      # Make sure that labels (goto-targets) are left-aligned.
      sed -e 's/^ \([^ ]*:\)$/\1/'
    else
      cat
    fi
  fi
}

function add_empty_file {
  cat <<EOF
diff -uprN linux-${kver}/$1 linux-${kver}/$1
--- linux-${kver}/$1
+++ linux-${kver}/$1
@@ -0,0 +1,1 @@
+
EOF
}

# Run the script specialize_patch with appropriate options on the patch
# passed via stdin and send the specialized patch to stdout.
function specialize_patch {
  local ao
  set -- ${1//^/ }
  local kver=$1
  local distro=$2
  local release=$3
  set -- ${release//./ }
  local releasevermajor="$1"
  local releaseverminor="$2"
  case "$distro" in
      CentOS|AlmaLinux)
	  if [ -n "$releasevermajor" ]; then
	      ao=(
		  -v "RHEL_MAJOR=$releasevermajor"
		  -v "RHEL_MINOR=$releaseverminor"
		  -v "RHEL_RELEASE_CODE=$((releasevermajor * 256 +
					   releaseverminor))"
	      )
	  fi
	  ;;
      UEK)
	  ao=(-v UEK_KABI_RENAME=1 -v UEK_RELEASE=${releasevermajor})
	  ;;
  esac
  local kver3
  set -- ${kver//[.-]/ }
  if [ -n "$3" ]; then
      kver3=$1.$2.$3
  else
      kver3=$1.$2
  fi
  if [ "${enable_specialize}" = "true" ]; then
    if [ "${generating_upstream_patch}" = "true" ]; then
      scripts/filter-trace-entry-exit
    else
      cat
    fi |
    "$(dirname "$0")/specialize-patch" \
         "${specialize_patch_options[@]}" \
         -v kernel_version="${kver3}" \
         -v SCST_IO_CONTEXT="${scst_io_context}" "${ao[@]}"
  else
    cat
  fi
}

# Read a patch from stdin, specialize it for kernel version ${full_kver}
# and write the output either to stdout or to the file $1 (if not empty),
# depending on the value of the variable ${multiple_patches}.
function process_patch {
  local tmppatch
  if [ "${multiple_patches}" = "true" ]; then
    if [ "$1" != "" ]; then
      if [ -e "${patchdir}/$1" ]; then
        echo "Warning: overwriting ${patchdir}/$1"
      fi
      tmppatch="$(/bin/mktemp)"
      (
        specialize_patch "${full_kver}"
      ) >"${tmppatch}"
      touch "${tmppatch}"
      {
        if [ -e /usr/bin/diffstat ]; then
          awk 'BEGIN{h=1}/^diff/{h=0}/^---/{h=0}h!=0{print}' < "${tmppatch}"
          echo "---"
          diffstat "${tmppatch}"
          echo ""
          awk 'BEGIN{h=1}/^diff/{h=0}/^---/{h=0}h==0{print}' < "${tmppatch}"
        fi
      } \
        > "${patchdir}/$(basename "$1")"
      rm -f "${tmppatch}"
    else
      # echo "Discarded $(wc -l) lines."
      true
    fi
  else
    specialize_patch "${full_kver}"
  fi
}

# Returns 0 (true) if SCST core file "$1" should be added in a separate patch,
# and 1 (false) if not.
function in_separate_patch {
  echo "${source_files_in_separate_patch}" | grep -qE "^$1 | $1 | $1\$|^$1\$"
}


#########################
# Argument verification #
#########################

debug_specialize="false"
enable_specialize="true"
generating_upstream_patch="false"
multiple_patches="false"
patchdir=""
replace_sbug_by_bug="true"
specialize_patch_options=(-v "delete_disabled_code=1")
srpt="true"

if [ ! -e scst ] || [ ! -e iscsi-scst ] || [ ! -e srpt ] ||
       [ ! -e scst_local ]; then
  echo "Please run this script from inside the SCST subversion source tree."
  exit 1
fi

# shellcheck disable=SC2046
set -- $(/usr/bin/getopt dhlmnp:su "$@")
while [ "$1" != "${1#-}" ]
do
  case "$1" in
    '-d') debug_specialize="true"; shift;;
    '-h') usage; exit 1;;
    '-l') shift;;
    '-n') specialize_patch_options=(-v "blank_deleted_code=1")
          shift
          ;;
    '-p') multiple_patches="true"; patchdir="$2"; shift; shift;;
    '-s') enable_specialize="false"; shift;;
    '-u') generating_upstream_patch="true"; shift;;
    '--') shift;;
    *)    usage; exit 1;;
  esac
done

if [ $# != 1 ]; then
  usage
  exit 1
fi

# Strip patch level from the kernel version number.
full_kver="$1"
kver="$(kernel_version "$1")"

# Include fcst in the patch for kernel versions 2.6.33 and later.
if kernel_version_le "2.6.37" "${kver}"; then
  include_fcst="true"
else
  include_fcst="false"
fi

# See also commit 89d9a567952b ("[SCSI] add support for per-host cmd pools";
# v3.15).
if kernel_version_lt "$kver" 3.18; then
    qla2x00t="true"
    qla2x00t_32gbit="false"
else
    qla2x00t="false"
    qla2x00t_32gbit="true"
fi

if [ -e "scst/kernel/in-tree/Makefile.scst-${kver}" ]; then
  scst_makefile="Makefile.scst-${kver}"
else
  scst_makefile="Makefile.scst"
fi

# Make sure that for kernel 2.6.26 and later the line
# "#define CONFIG_SCST_PROC" is removed from scst/include/scst.h.
if grep -qw scst_sysfs scst/kernel/in-tree/${scst_makefile} \
   || [ "${generating_upstream_patch}" = "true" ];
then
  specialize_patch_options+=(-v "config_scst_proc_undefined=1")
else
  include_proc_impl="true"
fi
if [ "${debug_specialize}" = "true" ]; then
  specialize_patch_options+=(-v "debug=1")
fi
if [ "${generating_upstream_patch}" = "true" ]; then
  specialize_patch_options+=(-v "generating_upstream_patch_defined=1"
			     -v "config_tcp_zero_copy_transfer_completion_notification_undefined=1")
fi

if [ "${multiple_patches}" = "true" ]; then
  if [ -e "${patchdir}" ]; then
    echo "Patch output directory ${patchdir} already exists."
  fi
  mkdir -p "${patchdir}"
  if [ ! -d "${patchdir}" ]; then
    echo "Error: ${patchdir} is not a directory."
  fi
fi


####################
# Patch Generation #
####################

for f in fcst/linux-patches/series-"${kver}"*
do
   if [ -e "$f" ]; then
       fcst_patch_series="$f"
   fi
done

# General kernel patches.

scst_io_context=0

scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h \
scst/include/scst_event.h scst/include/backport.h"
scst_04_main="scst/src/scst_main.c scst/src/scst_module.c scst/src/scst_priv.h \
scst/src/scst_copy_mgr.c scst/src/scst_dlm.c scst/src/scst_dlm.h \
scst/src/scst_event.c scst/src/scst_no_dlm.c"
scst_05_targ="scst/src/scst_targ.c scst/src/scst_local_cmd.c \
scst/src/scst_local_cmd.h"
scst_06_lib="scst/src/scst_lib.c"
scst_07_pres="scst/src/scst_pres.h scst/src/scst_pres.c"
scst_08_sysfs="scst/src/scst_sysfs.c"
scst_09_debug="scst/include/scst_debug.h scst/src/scst_debug.c"
scst_proc="scst/src/scst_proc.c"
scst_10_sgv="scst/include/scst_sgv.h scst/src/scst_mem.h scst/src/scst_mem.c doc/scst_pg.sgml"
scst_user="scst/include/scst_user.h scst/src/dev_handlers/scst_user.c"
scst_13_vdisk="scst/src/dev_handlers/scst_vdisk.c"
scst_14_tg="scst/src/scst_tg.c"
separate_patches="scst_03_public_headers scst_04_main scst_05_targ scst_06_lib scst_07_pres scst_08_sysfs scst_09_debug scst_10_sgv scst_user scst_13_vdisk scst_14_tg"

# Suppress shellcheck warnings about unused variables.
echo "$scst_03_public_headers $scst_04_main $scst_05_targ $scst_06_lib $scst_07_pres $scst_08_sysfs $scst_09_debug $scst_proc $scst_10_sgv $scst_user $scst_13_vdisk $scst_14_tg" >/dev/null

if [ "$include_proc_impl" = "true" ]; then
  separate_patches+=" scst_proc"
fi
source_files_in_separate_patch=""
for s in ${separate_patches}
do
  source_files_in_separate_patch+=" $(set | \
                    sed -n -e "s/^$s='\(.*\)'$/\1/p" -e "s/^$s=\(.*\)$/\1/p")"
done


# Directory drivers/

(
if [ -e "scst/kernel/in-tree/Kconfig.drivers.Linux-${kver}.patch" ]; then
  add_patch "scst/kernel/in-tree/Kconfig.drivers.Linux-${kver}.patch" \
            "drivers/Kconfig"
else
  add_patch "scst/kernel/in-tree/Kconfig.drivers.Linux.patch" \
            "drivers/Kconfig"
fi

if [ "${full_kver#2.6.32-}" = "${full_kver}" ] &&
       [ -e "scst/kernel/in-tree/Makefile.drivers.Linux-${kver}.patch" ]; then
  add_patch "scst/kernel/in-tree/Makefile.drivers.Linux-${kver}.patch"\
            "drivers/Makefile"
else
  add_patch "scst/kernel/in-tree/Makefile.drivers.Linux.patch"\
            "drivers/Makefile"
fi
) \
| process_patch "scst_01_drivers_kbuild.diff"


# Directory drivers/scst/

(
tmpdir="/tmp/scst-$$"
mkdir -p "${tmpdir}"

tmp_Kconfig="${tmpdir}/Kconfig.scst-${kver}"
# shellcheck disable=SC2002
cat "scst/kernel/in-tree/Kconfig.scst" | \
if [ "${include_fcst}" = true ]; then
  cat
else
  grep -v '^source "drivers/scst/fcst/Kconfig"$'
fi >"${tmp_Kconfig}"
add_file "${tmp_Kconfig}" "drivers/scst/Kconfig"

tmp_Makefile="${tmpdir}/${scst_makefile}"
# shellcheck disable=SC2002
cat "scst/kernel/in-tree/${scst_makefile}" | \
if [ "$include_proc_impl" != "true" ]; then
  grep -v 'scst_proc'
else
  cat
fi | \
  if [ "${include_fcst}" = true ] && [ "${kver}" != "2.6.37" ] &&
	 [ "${kver}" != "2.6.38" ]; then
  cat
else
  sed -e 's: fcst/* : :'
fi >"$tmp_Makefile"
add_file "$tmp_Makefile" "drivers/scst/Makefile"

rm -rf "${tmpdir}"
) \
| process_patch "scst_02_scst_kbuild.diff"

for s in ${separate_patches}
do
  {
    for f in $(eval 'echo "$'"{$s}"'"')
    do
      if [ "${f#scst/include}" != "${f}" ]; then
	add_file "${f}" "include/scst/${f#scst/include/}"
      elif [ "${f#doc}" != "${f}" ]; then
	add_file "${f}" "Documentation/scst/${f#doc/}"
      else
	add_file "${f}" "drivers/scst/${f#scst/src/}"
      fi
    done
    if grep -q /scst_itf_ver.h scst/src/Makefile &&
       [ "$s" = "scst_03_public_headers" ]; then
      tmp_itf_ver="$(mktemp /tmp/scst-itf-ver.h.XXXXXXXXXX)"
      cat <<"EOF" >"$tmp_itf_ver"
/* Autogenerated, don't edit */

#define SCST_INTF_VER "SCST_INTF_VER"
#define SCST_CONST_INTF_VER "SCST_CONST_INTF_VER"
#define DEV_USER_INTF_VER "DEV_USER_INTF_VER"
EOF
      add_file "$tmp_itf_ver" "include/scst/scst_itf_ver.h"
      rm -f "$tmp_itf_ver"
    fi
  } |
  process_patch "${s}.diff"
done

{
  if [ -e scst/README_in-tree ]; then
    add_file "scst/README_in-tree" "Documentation/scst/README.scst"
  fi
  add_file "scst/SysfsRules" "Documentation/scst/SysfsRules"
} | process_patch "scst_11_core_doc.diff"


# Directory drivers/scst/dev_handlers/
if [ -e "scst/kernel/in-tree/Makefile.dev_handlers-${kver}" ]
then
  add_file "scst/kernel/in-tree/Makefile.dev_handlers-${kver}" \
           "drivers/scst/dev_handlers/Makefile" \
  | process_patch "scst_11_dev_handlers_makefile.diff"
else
  add_file "scst/kernel/in-tree/Makefile.dev_handlers" \
           "drivers/scst/dev_handlers/Makefile" \
  | process_patch "scst_11_dev_handlers_makefile.diff"
fi

for f in scst/src/dev_handlers/*.[ch]; do
  [ -e "$f" ] || continue
  if ! in_separate_patch "${f}"; then
    add_file "${f}" "drivers/scst/dev_handlers/${f#scst/src/dev_handlers/}"
  fi
done \
| process_patch "scst_14_passthrough.diff"


# Directory drivers/scst/fcst/

if [ "${include_fcst}" = true ]; then
  if [ -e "${fcst_patch_series}" ]; then
    grep -v '^#' "${fcst_patch_series}" |
      while read -r f; do
        cat "fcst/linux-patches/${f}"
      done
  fi

  for f in fcst/Kbuild fcst/Makefile_in-tree; do
    [ -e "$f" ] && add_file "$f" "drivers/scst/fcst/Makefile"
  done

  add_file "fcst/Kconfig" "drivers/scst/fcst/Kconfig"

  for f in fcst/*.[ch]; do
    [ -e "$f" ] || continue
    add_file "${f}" "drivers/scst/fcst/${f#fcst/}"
  done
fi \
| process_patch "fcst.diff"

add_file "fcst/README" "Documentation/scst/README.fcst" \
| process_patch "fcst-doc.diff"


# Directory drivers/scst/iscsi-scst/

# Make sure the file iscsi-scst/iscsi_scst_itf_ver.h is up to date.
make -s -C iscsi-scst include/iscsi_scst_itf_ver.h

(
for f in iscsi-scst/include/*h; do
    [ -e "$f" ] || continue
    case "${f}" in
      "iscsi-scst/include/iscsi_scst_itf_ver.h")
          ;;
      "iscsi-scst/include/iscsit_transport.h")
          add_file "${f}" "include/scst/${f#iscsi-scst/include/}"
          ;;
      *)
          add_file "${f}" "include/scst/${f#iscsi-scst/include/}"
          ;;
    esac
done

add_file "iscsi-scst/include/iscsi_scst_itf_ver.h" "include/scst/iscsi_scst_itf_ver.h"

add_file "iscsi-scst/kernel/Makefile.in-kernel" \
         "drivers/scst/iscsi-scst/Makefile"

add_file "iscsi-scst/kernel/Kconfig"  "drivers/scst/iscsi-scst/Kconfig"

for f in iscsi-scst/kernel/*.[ch]; do
  [ -e "$f" ] || continue
  add_file "${f}" "drivers/scst/iscsi-scst/${f#iscsi-scst/kernel/}"
done

for f in iscsi-scst/kernel/isert-scst/*.[ch]; do
  [ -e "$f" ] || continue
  add_file "${f}" "drivers/scst/iscsi-scst/isert-scst/${f#iscsi-scst/kernel/isert-scst/}"
done
add_file "iscsi-scst/kernel/isert-scst/Makefile.in-kernel" "drivers/scst/iscsi-scst/isert-scst/Makefile"
add_file "iscsi-scst/kernel/isert-scst/Kconfig" "drivers/scst/iscsi-scst/isert-scst/Kconfig"
) \
| process_patch "iscsi-scst.diff"

add_file "iscsi-scst/README_in-tree" "Documentation/scst/README.iscsi" \
| process_patch "iscsi-scst-doc.diff"


# Directory drivers/scsi/qla2xxx/

if [ "${qla2x00t}" = "true" ]; then

  ( cd qla2x00t && "$PWD/generate-in-tree-patches" "$1" ) || exit $?

  for f in qla2x00t/in-tree-patches/"$1"/*.patch; do
    [ -e "$f" ] || continue
    g="${f#qla2x00t/in-tree-patches/$1/}"
    g="${g%.patch}"
    add_patch "${f}" "drivers/scsi/qla2xxx/${g}"
  done

  add_file "qla2x00t/qla2x_tgt.h" \
           "drivers/scsi/qla2xxx/qla2x_tgt.h"

  add_file "qla2x00t/qla2x_tgt_def.h" \
           "drivers/scsi/qla2xxx/qla2x_tgt_def.h"

  for f in qla2x00t/qla2x00-target/*.[ch]; do
    [ -e "$f" ] &&
    add_file "${f}" "drivers/scsi/qla2xxx/${f#qla2x00t/qla2x00-target/}"
  done

  add_file "qla2x00t/qla2x00-target/README" \
           "Documentation/scst/README.qla2x00t" \
  | process_patch "qla2x00t-doc.diff"

elif [ "${qla2x00t_32gbit}" = "true" ]; then

  ( cd qla2x00t-32gbit && "$PWD/generate-in-tree-patches" "$1" ) || exit $?

  for f in qla2x00t-32gbit/in-tree-patches/"$1"/*.patch; do
    [ -e "$f" ] || continue
    g="${f#qla2x00t-32gbit/in-tree-patches/$1/}"
    g="${g%.patch}"
    if [ "$g" = "qla.h" ]; then
	add_patch "${f}" "include/trace/events/${g}"
    else
	add_patch "${f}" "drivers/scsi/qla2xxx/${g}"
    fi
  done

  for f in qla2x00t-32gbit/qla2x00-target/*.[ch]; do
    [ -e "$f" ] || continue
    add_file "${f}" "drivers/scsi/qla2xxx/${f#qla2x00t-32gbit/qla2x00-target/}"
  done

  add_file "qla2x00t-32gbit/qla2x00-target/README" \
           "Documentation/scst/README.qla2x00t" \
  | process_patch "qla2x00t-doc.diff"

fi \
| process_patch "qla2x00t.diff"


# Directory drivers/scst/srpt

{

cat <<EOF
This patch adds the kernel module ib_srpt, which is a SCSI RDMA Protocol (SRP)
target implementation. This driver uses the InfiniBand stack and the SCST core.

It is a high performance driver capable of handling 600K+ 4K random write
IOPS by a single target as well as 2.5+ GB/s sequential throughput over
a single QDR IB port.

It was originally developed by Vu Pham (Mellanox) and has been optimized by
Bart Van Assche.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Cc: Vu Pham <vu@mellanox.com>
Cc: Roland Dreier <rdreier@cisco.com>
Cc: David Dillow <dillowda@ornl.gov>
EOF
if [ "$srpt" = "true" ]; then

  add_file  "srpt/README_in-tree"         "Documentation/scst/README.srpt"

  add_file  "srpt/src/Kconfig"            "drivers/scst/srpt/Kconfig"

  add_file  "srpt/src/Makefile.in_kernel" "drivers/scst/srpt/Makefile"

  for f in srpt/src/*.[ch]; do
    [ -e "$f" ] || continue
    add_file "${f}" "drivers/scst/srpt/${f#srpt/src/}"
  done

else

  add_empty_file "drivers/scst/srpt/Kconfig"

  add_empty_file "drivers/scst/srpt/Makefile"

fi \
} | process_patch "scst_17_srpt.diff"


# Directory drivers/scst/scst_local

(
  add_file "scst_local/README"          "Documentation/scst/README.scst_local"

  add_file "scst_local/in-tree/Kconfig" "drivers/scst/scst_local/Kconfig"

  if [ -e "scst_local/in-tree/Makefile-${kver}" ]; then
    add_file "scst_local/in-tree/Makefile-${kver}" "drivers/scst/scst_local/Makefile"
  else
    add_file "scst_local/in-tree/Makefile" "drivers/scst/scst_local/Makefile"
  fi

  add_file "scst_local/scst_local.c" "drivers/scst/scst_local/scst_local.c"
) \
| process_patch "scst_16_local.diff"
