#!/bin/bash

############################################################################
#
# Copyright (C) 2008-2009 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.
#
############################################################################

############################################################################
#
# The recommended way to use this script is as follows:
# - Create an additional user, e.g. builder
# - Add a line to the sudoers file that allows that user to invoke yum and
#   yum-builddep. One way to do that is by invoking the following shell
#   commands as root:
#     useradd builder
#     echo 'builder ALL=(ALL) NOPASSWD: /usr/bin/yum, /usr/bin/yum-builddep' >>/etc/sudoers
#     visudo -sc
#
# For more information about building a custom RHEL / CentOS / Scientific Linux
# kernel RPM, see also:
# * CentOS wiki, "I Need to Build a Custom Kernel"
#   (http://wiki.centos.org/HowTos/Custom_Kernel).
# * Fedora wiki, "Building a custom kernel"
#   (http://fedoraproject.org/wiki/Building_a_custom_kernel).
#
############################################################################

scriptdir="$(dirname "$0")"
if [ "${scriptdir:0:1}" != "/" ]; then
    scriptdir="$PWD/${scriptdir}"
fi
scriptdir="${scriptdir%/.}"
source "${scriptdir}/rhel-rpm-functions"
scst_dir="$(dirname "$scriptdir")"
downloaddir=$HOME/software/downloads
rpmbuild_dir=$HOME/rpmbuild

if echo "$(uname -r)" | grep -q uek; then
    kernel="kernel-uek-$(uname -r)"
else
    kernel="kernel-$(uname -r)"
fi

releasever="$(sed -n -e 's/^.* release \([0-9.]*\).*$/\1/p' /etc/redhat-release)"
distro="$(sed -n -e 's/^\(.*\) release .*$/\1/p' /etc/redhat-release)"

install_prerequisites=true

usage() {
    echo "$(basename "$0") [-d <distro>] [-h] [-i] [-k <kver>] [-r <rel>]"
}

type /usr/bin/getopt >/dev/null || exit 1
options="$(/usr/bin/getopt d:hik:r: "$@")" || { usage; exit 1; }
set -- $options
while [ "$1" != "${1#-}" ]; do
    case "$1" in
	'-d') distro="$2";     shift; shift;;
	'-i') install_prerequisites="false"; shift;;
        '-k') kernel="$2";     shift; shift;;
	'-r') releasever="$2"; shift; shift;;
        '--') shift; break;;
        *)    usage; exit 1;;
    esac
done

arch="$(uname -m)"
kernel="${kernel%.${arch}}"
kernel_src_rpm="${kernel}.src.rpm"
kver="${kernel#kernel-}"

srpm_urls="$(get_srpm_urls "$distro" "$releasever" "$arch")" || exit $?

function log {
    echo
    printf "$@"
    echo
}

copy_best_matching_patch() {
    local f v

    v="${kver}"
    while [ "${v%[0-9a-z]}" != "$v" ]; do
	f="${1}-${v}.patch"
	if [ -e "$f" ]; then
	    copy_patch "$f" "$2"
	    return 0
	fi
	while [ "${v%[0-9a-z]}" != "$v" ]; do
	    v="${v%[0-9a-z]}"
	done
	v="${v%.}"
	v="${v%-}"
    done
    return 1
}

function copy_patch {
  local p="$1"
  while [ ! -e "$p" ]
  do
    local q="$(echo "$p" | sed 's/[.-][a-z0-9]*\.patch$/.patch/')"
    if [ "$q" = "$p" ]; then
      break;
    fi
    p="$q"
  done
  if [ ! -e "$p" ]; then
    echo "Error: patch $1 not found"
    echo "Please report this on the scst-devel mailing list"
    exit 1
  fi
  ln -s "$p" "$2"
}

function rpmbuild {
    /usr/bin/rpmbuild				\
	--define="%_topdir ${rpmbuild_dir}"	\
	--define="%buildid .scst"		\
	"$@"
}

if [ -e ${rpmbuild_dir} ]; then
  echo "You have to remove the ${rpmbuild_dir} directory before starting $0"
  exit 1
fi

if [ ! -e "${scst_dir}" ]; then
  echo "Error: directory ${scst_dir} not found. Please modify scst_dir in $0."
  exit 1
fi

log "Installing prerequisites"

if $install_prerequisites; then
    for p in \
asciidoc \
audit-libs-devel \
binutils-devel \
elfutils-devel \
elfutils-libelf-devel \
hmaccalc \
ncurses-devel \
newt-devel \
patchutils \
numactl-devel \
pciutils-devel \
'perl(ExtUtils::Embed)' \
pesign \
python-devel \
redhat-rpm-config \
rng-tools \
rpm-build \
rpmdevtools \
unifdef \
wget \
xmlto \
yum-utils \
zlib-devel \
; do sudo yum install -y -q "$p"; done
fi

rc=$?; if [ $rc != 0 ]; then exit $rc; fi

log "Creating directory ${rpmbuild_dir}"

mkdir -p ${rpmbuild_dir}/{BUILD,RPMS,SOURCES,SPECS,SRPMS}

log "Installing, unpacking and preparing kernel source files"

mkdir -p ${downloaddir}
if [ ! -e ${downloaddir}/${kernel_src_rpm} ]; then
  cd ${downloaddir}
  for dir in ${srpm_urls}
  do
    url="$dir/${kernel_src_rpm}"
    echo "Trying $url ..."
    if wget -q "$url"; then
      break
    fi
  done
  if [ ! -e ${downloaddir}/${kernel_src_rpm} ]; then
    echo "Downloading kernel source RPM ${kernel_src_rpm} failed."
    exit 1
  fi
fi

log "Installing kernel build prerequisites"

if $install_prerequisites; then
    sudo yum-builddep -q -y ${downloaddir}/${kernel_src_rpm}
fi

log "Installing kernel sources in ${rpmbuild_dir}"

cd ${rpmbuild_dir}
rpm --define="%_topdir ${rpmbuild_dir}" -i ${downloaddir}/${kernel_src_rpm} 2>&1 \
  | grep -v ' does not exist'
cd SPECS
{
  rpmbuild -bp --target=${arch} kernel*.spec
  rc=$?
  if [ rc != 0 ]; then
    exit $rc
  fi
} 2>&1 | tee prep-err.log

log "Copying SCST patches to the SOURCES directory"

cd ${rpmbuild_dir}/SOURCES
copy_best_matching_patch $scst_dir/scst/kernel/rhel/scst_exec_req_fifo scst_exec_req_fifo.patch ||
{
    echo "No matching scst_exec_req_fifo patch found for kernel version $kver";
    exit 1;
}
copy_best_matching_patch $scst_dir/iscsi-scst/kernel/patches/rhel/put_page_callback put_page_callback.patch ||
{
    echo "No matching put_page_callback patch found for kernel version $kver";
    exit 1;
}

log "Adding SCST patches in kernel.spec"

if [ ${kver#2.6.18} != $kver ]; then
if [ -e ${rpmbuild_dir}/SPECS/kernel-2.6.spec ]; then
# RHEL/CentOS/SL 5.6
patch -p1 ${rpmbuild_dir}/SPECS/kernel-2.6.spec <<'EOF' || exit $?
diff -u SPECS/kernel-2.6.spec{.orig,}
--- SPECS/kernel-2.6.spec.orig
+++ SPECS/kernel-2.6.spec
@@ -6386,6 +6386,9 @@
 # empty final patch file to facilitate testing of kernel patches
 Patch99999: linux-kernel-test.patch
 
+Patch200: scst_exec_req_fifo.patch
+Patch201: put_page_callback.patch
+
 # END OF PATCH DEFINITIONS
 
 BuildRoot: %{_tmppath}/kernel-%{KVERREL}-root
@@ -12593,6 +12596,10 @@
 rm -f kernel-%{kversion}-*-debug.config
 %endif
 
+%patch200 -p1
+%patch201 -p1
+sed -i.tmp -e 's/^CONFIG_SCSI_QLA_FC=.*/CONFIG_SCSI_QLA_FC=n/' *.config
+
 # now run oldconfig over all the config files
 for i in *.config
 do
EOF
else
# RHEL/CentOS/SL 5.7
patch -p1 ${rpmbuild_dir}/SPECS/kernel.spec <<'EOF' || exit $?
--- SPECS/kernel.spec.orig
+++ SPECS/kernel.spec
@@ -425,6 +425,9 @@
 Patch2: xen-config-2.6.18-redhat.patch
 Patch3: xen-2.6.18-redhat.patch
 
+Patch200: scst_exec_req_fifo.patch
+Patch201: put_page_callback.patch
+
 # empty final patch file to facilitate testing of kernel patches
 Patch99999: linux-kernel-test.patch
 
@@ -733,6 +736,10 @@
 rm -f kernel-%{version}-*-debug.config
 %endif
 
+%patch200 -p1
+%patch201 -p1
+sed -i.tmp -e 's/^CONFIG_SCSI_QLA_FC=.*/CONFIG_SCSI_QLA_FC=n/' *.config
+
 # now run oldconfig over all the config files
 for i in *.config
 do
EOF
fi
elif [ ${kver#2.6.32-71} != $kver ]; then
# RHEL/CentOS/SL 6.0
patch -p1 ${rpmbuild_dir}/SPECS/kernel.spec <<'EOF' || exit $?
diff -u SPECS/kernel.spec{.orig,}
--- SPECS/kernel.spec.orig
+++ SPECS/kernel.spec
@@ -601,6 +601,9 @@
 Source82: config-s390x-debug
 Source83: config-s390x-debug-rhel
 
+Patch200: scst_exec_req_fifo.patch
+Patch201: put_page_callback.patch
+
 # empty final patch file to facilitate testing of kernel patches
 Patch999999: linux-kernel-test.patch
 
@@ -891,6 +894,9 @@
 # Dynamically generate kernel .config files from config-* files
 make -f %{SOURCE20} VERSION=%{version} configs
 
+ApplyPatch scst_exec_req_fifo.patch
+ApplyPatch put_page_callback.patch
+
 ApplyOptionalPatch linux-kernel-test.patch
 
 # Any further pre-build tree manipulations happen here.
@@ -917,6 +923,8 @@
 for i in *.config
 do
   mv $i .config
+  echo "CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION=y" >> .config
+  sed -i.tmp -e 's/^CONFIG_SCSI_QLA_FC=.*/CONFIG_SCSI_QLA_FC=n/' .config
   Arch=`head -1 .config | cut -b 3-`
   make ARCH=$Arch %{oldconfig_target} > /dev/null
   echo "# $Arch" > configs/$i
EOF
elif [ ${kver#2.6.32-131} != $kver -o ${kver#2.6.32-220} != $kver \
       -o ${kver#2.6.32-279} != $kver -o ${kver#2.6.32-358} != $kver \
       -o ${kver#2.6.32-431} != $kver ]; then
# RHEL/CentOS/SL 6.1, 6.2, 6.3, 6.4 or 6.5
patch -p1 ${rpmbuild_dir}/SPECS/kernel.spec <<'EOF' || exit $?
# RHEL/CentOS/SL 6.x
diff -u SPECS/kernel.spec{.orig,}
--- SPECS/kernel.spec.orig
+++ SPECS/kernel.spec
@@ -601,6 +601,9 @@
 Source82: config-generic
 Source83: config-x86_64-debug-rhel
 
+Patch200: scst_exec_req_fifo.patch
+Patch201: put_page_callback.patch
+
 # empty final patch file to facilitate testing of kernel patches
 Patch999999: linux-kernel-test.patch
 
@@ -891,6 +894,9 @@
 # Dynamically generate kernel .config files from config-* files
 make -f %{SOURCE20} VERSION=%{version} configs
 
+ApplyPatch scst_exec_req_fifo.patch
+ApplyPatch put_page_callback.patch
+
 ApplyOptionalPatch linux-kernel-test.patch
 
 # Any further pre-build tree manipulations happen here.
@@ -917,6 +923,8 @@
 for i in *.config
 do
   mv $i .config
+  echo "CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION=y" >> .config
+  sed -i.tmp -e 's/^CONFIG_SCSI_QLA_FC=.*/CONFIG_SCSI_QLA_FC=n/' .config
   Arch=`head -1 .config | cut -b 3-`
   make ARCH=$Arch %{oldconfig_target} > /dev/null
   echo "# $Arch" > configs/$i
EOF
elif [ ${kver#2.6.32-504.} != $kver ]; then
# RHEL/CentOS/SL 6.6
patch -p1 ${rpmbuild_dir}/SPECS/kernel.spec <<'EOF' || exit $?
diff -u SPECS/kernel.spec{.orig,}
--- kernel.spec.orig	2014-12-03 16:20:14.764118318 +0100
+++ kernel.spec	2014-12-03 16:21:36.606089530 +0100
@@ -610,6 +610,9 @@
 Source85: config-powerpc64-debug-rhel
 Source86: config-s390x-debug-rhel
 
+Patch200: scst_exec_req_fifo.patch
+Patch201: put_page_callback.patch
+
 # empty final patch file to facilitate testing of kernel patches
 Patch999999: linux-kernel-test.patch
 
@@ -932,6 +935,9 @@
 # Dynamically generate kernel .config files from config-* files
 make -f %{SOURCE20} VERSION=%{version} configs
 
+ApplyPatch scst_exec_req_fifo.patch
+ApplyPatch put_page_callback.patch
+
 ApplyOptionalPatch linux-kernel-test.patch
 
 # Any further pre-build tree manipulations happen here.
@@ -958,6 +964,8 @@
 for i in *.config
 do
   mv $i .config
+  echo "CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION=y" >> .config
+  sed -i.tmp -e 's/^CONFIG_SCSI_QLA_FC=.*/CONFIG_SCSI_QLA_FC=n/' .config
   Arch=`head -1 .config | cut -b 3-`
   make ARCH=$Arch %{oldconfig_target} > /dev/null
   echo "# $Arch" > configs/$i
EOF
elif [ ${kver#3.10.0-12[13]} != $kver ]; then
# RHEL/CentOS/SL 7.0
patch -p1 ${rpmbuild_dir}/SPECS/kernel.spec <<'EOF' || exit $?
--- kernel.spec.orig	2014-05-23 10:09:17.707202148 +0200
+++ kernel.spec	2014-05-23 10:15:50.883937952 +0200
@@ -4,6 +4,7 @@
 Summary: The Linux kernel
 
 # % define buildid .local
+%define buildid .scst
 
 # For a stable, released kernel, released_kernel should be 1. For rawhide
 # and/or a kernel built from an rc or git snapshot, released_kernel should
@@ -367,6 +368,9 @@
 Source2000: cpupower.service
 Source2001: cpupower.config
 
+Patch200: scst_exec_req_fifo.patch
+Patch201: put_page_callback.patch
+
 # empty final patch to facilitate testing of kernel patches
 Patch999999: linux-kernel-test.patch
 
@@ -668,6 +672,9 @@
 # Drop some necessary files from the source dir into the buildroot
 cp $RPM_SOURCE_DIR/kernel-%{version}-*.config .
 
+ApplyPatch scst_exec_req_fifo.patch
+ApplyPatch put_page_callback.patch
+
 ApplyOptionalPatch linux-kernel-test.patch
 
 # Any further pre-build tree manipulations happen here.
@@ -700,6 +707,8 @@
 for i in *.config
 do
   mv $i .config
+  echo "CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION=y" >> .config
+  sed -i.tmp -e 's/^CONFIG_SCSI_QLA_FC=.*/CONFIG_SCSI_QLA_FC=n/' .config
   Arch=`head -1 .config | cut -b 3-`
   make %{?cross_opts} ARCH=$Arch listnewconfig | grep -E '^CONFIG_' >.newoptions || true
 %if %{listnewconfig_fail}
EOF
else
    log "Unrecognized kernel version ${kver}"
fi

log "Rebuilding kernel"

cd ${rpmbuild_dir}/SPECS
{
  rpmbuild -bb --target=${arch} --nodeps --with baseonly --with firmware --without kabichk kernel*.spec
  rc=$?
  if [ $rc != 0 ]; then
    exit $rc
  fi
} 2>&1 | tee build.log


log "Ready. You can now install the freshly built kernel RPM as follows:\nsudo rpm -ivh --force $HOME/rpmbuild/RPMS/${arch}/kernel-*.rpm"
