#!/bin/bash ############################################################################ # # Script for running those SCST regression tests that can be run automatically. # # Copyright (C) 2008-2020 Bart Van Assche # # 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. # ############################################################################ ############################################################################ # This script performs the following actions: # - Creates a temporary directory for storing the output of the regression # tests. No existing files are modified by this script. # - Verifies whether the top-level *.patch files apply cleanly to the SCST # tree. # - Duplicates the entire source tree to the temporary directory and # compiles the SCST source code. # - Duplicates the entire source tree to the temporary directory, runs # 'make 2release' and again compiles the SCST source code. # - Checks whether the specified kernel version is present # in the directory specified through option -c. # - If the source code of the specified kernel version is not present, # download it. # - Convert the SCST source code into a kernel patch. # - Extract the kernel sources. # - Run checkpatch on the SCST kernel patch. # - Apply the SCST kernel patch to the kernel tree. # - Run 'make allmodconfig'. # - Run the sparse source code checker on the SCST directory. # - Run 'make headers_check'. # - Compile the kernel tree. # - Run 'make checkstack'. # - Run 'make namespacecheck'. # - Run 'make htmldocs'. # # Note: the results of the individual steps are not verified by this script # -- the output generated by the individual steps has to be verified by # reviewing the output files written into the temporary directory. ############################################################################ ######################## # Function definitions # ######################## # shellcheck source=./kernel-functions source "$(dirname "$0")/kernel-functions" function usage { echo "Usage: $0 [-c ] [-d ] [-f] [-h] [-j ] [-p ] [-q] ..." echo " -c - cache directory for Linux kernel tarballs." echo " -d - directory for temporary regression test files." echo " -h - display this help information." echo " -j - number of jobs that 'make' should run simultaneously." echo " -k - remove temporary files before exiting." echo " -q - download kernel sources silently." echo " ... - kernel versions to test." } # Test whether the *.patch files in the SCST top-level directory apply cleanly # to the SCST tree. Does not modify any files nor produce any output files. function test_scst_tree_patches { local rc=0 echo "Testing whether the SCST patches apply cleanly to the SCST tree ..." for p in *.patch srpt/patches/scst*.patch do if [ -e "$p" ] && ! patch -p0 -f --dry-run -s <"$p" &>/dev/null; then echo "ERROR: patch $p does not apply cleanly." rc=1 fi done if [ "${rc}" = 0 ]; then echo "OK" fi } # Copy the entire SCST source code tree from "$1" into the current directory. # Only copy those files which are administered by Subversion. function duplicate_scst_source_tree { if [ -e "$1/AskingQuestions" ]; then "${scriptsdir}"/list-source-files "$1" | tar -C "$1" --files-from=- -c -f - | tar -x -f - else return 1 fi } function make_rpm { local outputfile t for t in rpm scst-dkms-rpm; do outputfile="${outputdir}/make-${t}-output.txt" echo "Testing whether 'make ${t}' works fine ..." if make ${t} > "${outputfile}" 2>&1; then echo "OK" else echo "FAILED" fi done } function make_dpkg { local outputfile="${outputdir}/make-dpkg-output.txt" outputfile="${outputdir}/make-${t}-output.txt" echo "Testing whether 'make dpkg' works fine ..." if make dpkg > "${outputfile}" 2>&1; then echo "OK" else echo "FAILED" fi } function compile_scst { ( for d in scst iscsi-scst qla2x00t qla2x00t-32gbit srpt scst_local fcst usr; do if [[ $d = qla2x00t || $d = qla2x00t-32gbit ]]; then BUILD_2X_MODULE=y CONFIG_SCSI_QLA_FC=y CONFIG_SCSI_QLA2XXX_TARGET=y \ make -j"$(nproc)" -C "$d" || return $? else make -j"$(nproc)" -C "$d" || return $? fi done ) } # Compile the unpatched SCST source code. function compile_scst_unpatched { local scst="$PWD" local outputfile="${outputdir}/compilation-output-unpatched.txt" local workingdirectory="${outputdir}/scst-unpatched" echo "Testing whether the SCST tree compiles fine ..." ( if mkdir -p "${workingdirectory}" \ && cd "${workingdirectory}" \ && duplicate_scst_source_tree "${scst}" \ && compile_scst &> "${outputfile}" then echo "OK" else echo "FAILED" fi ) } # Compile the unpatched SCST source code without DLM. function compile_scst_no_dlm { local scst="$PWD" local outputfile="${outputdir}/compilation-output-no-dlm.txt" local workingdirectory="${outputdir}/scst-no-dlm" echo "Testing whether the SCST tree compiles fine without DLM support ..." ( if mkdir -p "${workingdirectory}" \ && cd "${workingdirectory}" \ && duplicate_scst_source_tree "${scst}" \ && CONFIG_SCST_NO_DLM=y compile_scst &> "${outputfile}" then echo "OK" else echo "FAILED" fi ) } # Test out-of-tree compilation against the kernel header files in # /lib/modules/$(uname -r)/build. function compile_scst_patched { local scst="$PWD" local outputfile="${outputdir}/compilation-output-$1.txt" local workingdirectory="${outputdir}/scst-$1" echo "Testing whether the $1 SCST tree compiles fine ..." ( if mkdir -p "${workingdirectory}" \ && cd "${workingdirectory}" \ && duplicate_scst_source_tree "${scst}" \ && make -s "$1" \ && compile_scst &> "${outputfile}" then echo "OK" else echo "FAILED" fi ) } # Generate a kernel patch from the SCST source tree for kernel version $1 # and with generate-kernel-patch options $2. $1 may be a # ${kver}^${distro}^${release} triplet. function generate_kernel_patch { local kver local patchfile="${outputdir}/scst-${1/^*}-kernel.patch" local patchfile_m="${outputdir}/scst-${1/^*}-kernel-matching-line-numbers.patch" local driver_options="" kver="$(kernel_version "${1/^*}")" driver_options=(-l) "${scriptsdir}"/generate-kernel-patch "${driver_options[@]}" "$2" "$1" > "${patchfile}" "${scriptsdir}"/generate-kernel-patch "${driver_options[@]}" -n "$2" "$1" > "${patchfile_m}" "${scriptsdir}"/generate-kernel-patch "${driver_options[@]}" -p "${outputdir}/${patchdir}" "$2" "$1" } # Run checkpatch on the generated kernel patch. Assumes that there is a # vanilla kernel tree present in directory "${outputdir}/linux-$1", and leaves # this kernel tree clean. function run_checkpatch { local kver plevel errors warnings local outputfile="${outputdir}/checkpatch-$1-output.txt" local patchfile="${outputdir}/scst-$1-kernel.patch" kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" if [ -e "${outputdir}/linux-$1/scripts/checkpatch.pl" ]; then if [ "${multiple_patches}" = "false" ]; then echo "Running checkpatch on the SCST kernel patch ..." ( cd "${outputdir}/linux-$1" \ && scripts/checkpatch.pl --no-tree --no-signoff --strict - < "${patchfile}" &> "${outputfile}") else echo "Running checkpatch on the SCST kernel patches ..." rm -f "${outputfile}" ( cd "${outputdir}/linux-$1" \ && for p in "${outputdir}/${patchdir}"/* do echo "==== $p" >>"${outputfile}" scripts/checkpatch.pl --no-tree --no-signoff --strict - < "$p" >> "${outputfile}" 2>&1 done ) fi errors=$(grep -c '^ERROR' "${outputfile}") warnings=$(grep -c '^WARNING' "${outputfile}") checks=$(grep -c '^CHECK' "${outputfile}") echo "${errors} errors / ${warnings} warnings / ${checks} checks." grep -E '^WARNING|^ERROR|^CHECK' "${outputfile}" | sort | grep -v 'WARNING: missing space after return type' | sed 's/^CHECK: Avoid CamelCase:.*/CHECK: Avoid CamelCase/' | uniq -c else echo "Skipping checkpatch step for kernel $1." fi return 0 } function patch_and_configure_kernel { local kver kver="$(kernel_version "$1")" local patchfile="${outputdir}/scst-$1-kernel-matching-line-numbers.patch" local patchoutput="${outputdir}/patch-command-output-$1.txt" local configureoutput="${outputdir}/configure-output.txt" local disable=" \ CONFIG_BINARY_PRINTF \ CONFIG_BLK_DEV_IO_TRACE \ CONFIG_BRANCH_PROFILE_NONE \ CONFIG_CONTEXT_SWITCH_TRACER \ CONFIG_CTF \ CONFIG_DEBUG_SECTION_MISMATCH \ CONFIG_DEBUG_STRICT_USER_COPY_CHECKS \ CONFIG_DTRACE \ CONFIG_DT_CORE \ CONFIG_DYNAMIC_FTRACE \ CONFIG_EVENT_TRACE_TEST_SYSCALLS \ CONFIG_FTRACE \ CONFIG_FTRACE_MCOUNT_RECORD \ CONFIG_FTRACE_NMI_ENTER \ CONFIG_FTRACE_SELFTEST \ CONFIG_FTRACE_STARTUP_TEST \ CONFIG_FTRACE_SYSCALLS \ CONFIG_FUNCTION_GRAPH_TRACER \ CONFIG_FUNCTION_PROFILER \ CONFIG_FUNCTION_TRACER \ CONFIG_GENERIC_TRACER \ CONFIG_HAVE_FTRACE_NMI_ENTER \ CONFIG_HEADERS_CHECK \ CONFIG_IRQSOFF_TRACER \ CONFIG_IWLWIFI_DEVICE_TRACING \ CONFIG_IWM_TRACING \ CONFIG_KALLMODSYMS \ CONFIG_KCOV \ CONFIG_KVM_MMU_AUDIT \ CONFIG_MAC80211_DRIVER_API_TRACER \ CONFIG_MMIOTRACE \ CONFIG_NET_DROP_MONITOR \ CONFIG_NOP_TRACER \ CONFIG_RETPOLINE \ CONFIG_RH_KABI_SIZE_ALIGN_CHECKS \ CONFIG_UEK_KABI_SIZE_ALIGN_CHECKS \ CONFIG_SCHED_TRACER \ CONFIG_SECURITY_SELINUX \ CONFIG_STACK_TRACER \ CONFIG_STACK_VALIDATION \ CONFIG_TARGET_CORE \ CONFIG_TRACEPOINTS \ CONFIG_TRACER_MAX_TRACE \ CONFIG_TRACE_BRANCH_PROFILING \ CONFIG_TRACING \ CONFIG_UNWINDER_ORC \ CONFIG_X86_32 \ CONFIG_X86_X32 \ CONFIG_X86_KERNEL_IBT \ CONFIG_LTO_CLANG \ " local enable="CONFIG_UNWINDER_FRAME_POINTER" # SCST does not need event tracing on supported kernels disable="$disable CONFIG_EVENT_TRACING" echo "Patching and configuring kernel ..." if [ "$ipv6" = "false" ]; then disable="$disable CONFIG_IPV6" fi ( cd "${outputdir}/linux-$1" && if [ "${multiple_patches}" = "false" ]; then if ! patch -p1 -f -s <"${patchfile}" >"${patchoutput}"; then echo "Error: applying ${patchfile} failed." exit 1 fi else rm -f "${patchoutput}" for p in "${outputdir}/${patchdir}"/*; do echo "==== $p" >>"${patchoutput}" patch -p1 -f -s <"${p}" >>"${patchoutput}" 2>&1 done fi && make -s allmodconfig &>"${outputdir}/make-config-output.txt" && for c in $disable; do sed -i.tmp "s/^$c=[ym]\$/$c=n/" .config; done && for c in $enable; do sed -i.tmp "s/^\(# \)*$c\(=.*\| is not set\)\$/$c=y/" .config; done && make -s oldconfig "${configureoutput}" && for c in $enable; do grep -q "^$c=[ym]" .config || echo "Enabling $c failed." done ) } # Patches and compiles a kernel tree. Assumes that there is a vanilla kernel # tree present in directory "${outputdir}/linux-$1". function compile_patched_kernel { local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/kernel-$1-compilation-output.txt" echo "Compiling kernel $1 ..." ( ( cd "${outputdir}/linux-$1" \ && LC_ALL=C make -s -k bzImage modules ) ) &> "${outputfile}" echo "See also ${outputfile}." return 0 } # Compile subdirectories $2..$n of the patched kernel tree linux-$1. function compile_kernel { local kver plevel local k=$1 kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/compilation-$1-output.txt" shift echo "Compiling the patched kernel ..." if (cd "${outputdir}/linux-$k" \ && make -s modules_prepare \ && make -s scripts \ && for subdir; do LC_ALL=C KBUILD_MODPOST_WARN=1 make -j"$(nproc)" -k M="${subdir}"; done ) &> "${outputfile}" then local errors warnings errors=$(grep -c ' error:' "${outputfile}") warnings=$(grep -c ' warning:' "${outputfile}") echo "${errors} errors / ${warnings} warnings." else echo FAILED fi grep -E 'warning:|error:' < "${outputfile}" | sort | uniq -c return 0 } # Run the source code verification tool 'sparse' on the SCST code. Assumes that # there is a patched kernel tree present in directory "${outputdir}/linux-$1". # For more information about endianness annotations, see also # http://lwn.net/Articles/205624/. function run_sparse { local k="$1" local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/sparse-$1-output.txt" shift echo "Running sparse on the patched kernel in $* ..." if (cd "${outputdir}/linux-$k" \ && make -s modules_prepare \ && make -s scripts \ && if grep -q '^CONFIG_PPC=y$' .config; then LC_ALL=C KBUILD_MODPOST_WARN=1 make -k M=arch/powerpc/lib; fi \ && for subdir; do LC_ALL=C KBUILD_MODPOST_WARN=1 make -k C=2 CF="-D__CHECK_ENDIAN__ -DCONFIG_SPARSE_RCU_POINTER" M="${subdir}" done ) &> "${outputfile}" then local errors warnings errors=$(grep -c ' error:' "${outputfile}") warnings=$(grep -c ' warning:' "${outputfile}") echo "${errors} errors / ${warnings} warnings." grep -E ' warning:| error:' < "${outputfile}" \ | sed -e 's/^[^:]*:[0-9:]* //' \ -e "s/context imbalance in '[^']*':/context imbalance in :/g" \ -e "s/context problem in '[^']*': '[^']*'/context problem in : /g" \ -e "s/function '[^']*'/function/g" \ -e "s/symbol '[^']*'/symbol/g" \ | sort \ | uniq -c else echo FAILED fi return 0 } # Run smatch for kernel version $1 on directories $2..$n function run_smatch { local k="$1" local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/smatch-$1-output.txt" local disable="" # "CONFIG_DYNAMIC_DEBUG" shift echo "Running smatch on the patched kernel in $* ..." if (cd "${outputdir}/linux-$k" && for c in $disable; do sed -i.tmp "s/^$c=y\$/$c=n/" .config; done && make -s oldconfig "${outputfile}" then local errors warnings errors=$(grep -c ' error:' "${outputfile}") warnings=$(grep -c ' warn:' "${outputfile}") echo "${errors} errors / ${warnings} warnings." grep -E ' info:| warn:| error:' < "${outputfile}" | sed 's/^\([^:]*\):[0-9:]* /\1: /;s/\((see line \)[0-9]*\()\)/\1...\2/' | sort | uniq -c else echo FAILED fi return 0 } function run_checkstack { local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/checkstack-$1-output.txt" echo "Running checkstack on the patched $1 kernel ..." ( cd "${outputdir}/linux-$1" \ && make -s modules_prepare \ && make -s scripts \ && LC_ALL=C KBUILD_MODPOST_WARN=1 make -k checkstack ) &> "${outputfile}" echo "See also ${outputfile}." return 0 } function run_namespacecheck { local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/namespacecheck-$1-output.txt" echo "Running namespacecheck on the patched $1 kernel ..." ( cd "${outputdir}/linux-$1" \ && make -s modules_prepare \ && make -s scripts \ && LC_ALL=C KBUILD_MODPOST_WARN=1 make -k namespacecheck ) &> "${outputfile}" echo "See also ${outputfile}." return 0 } function run_headers_check { local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/headers_check-$1-output.txt" echo "Running headers check on the patched $1 kernel ..." ( cd "${outputdir}/linux-$1" \ && make -s modules_prepare \ && make -s scripts \ && LC_ALL=C KBUILD_MODPOST_WARN=1 make -k headers_check ) &> "${outputfile}" local errors errors=$(grep -c '^[^ ]' "${outputfile}") echo "${errors} errors." grep '^[^ ]' "${outputfile}" | sed 's/.*: //' | sort | uniq -c return 0 } function run_make_htmldocs { local kver plevel kver="$(kernel_version "$1")" plevel="$(patchlevel "$1")" local outputfile="${outputdir}/htmldocs-$1-output.txt" echo "Generating HTML documentation for the patched $1 kernel ..." ( cd "${outputdir}/linux-$1" \ && make -s modules_prepare \ && make -s scripts \ && LC_ALL=C KBUILD_MODPOST_WARN=1 make -k htmldocs ) &> "${outputfile}" echo "See also ${outputfile}." return 0 } ######################### # Argument verification # ######################### export LC_ALL=C if [ ! -e scst ] || [ ! -e iscsi-scst ] || [ ! -e srpt ]; then echo "Please run this script from inside the SCST subversion source tree." exit 1 fi scriptsdir="$(dirname "$0")" if [ "${scriptsdir:0:1}" != "/" ]; then scriptsdir="$PWD/${scriptsdir}" fi # Where to store persistenly downloaded kernel tarballs and kernel patches. kernel_sources="$HOME/software/downloads" kernel_versions="" # Directory in which the regression test output files will be stored. Must be # an absolute path. outputdir="${PWD}/regression-test-output-$(date +%Y-%m-%d_%Hh%Mm%Ss)" # Driver configuration. multiple_patches="false" remove_temporary_files_at_end="false" run_local_compilation="true" quiet_download="false" { # shellcheck disable=SC2046 set -- $(/usr/bin/getopt "c:d:j:hklpq" "$@") } while [ "$1" != "${1#-}" ] do case "$1" in '-c') kernel_sources="$2"; shift; shift;; '-d') outputdir="$2"; shift; shift;; '-h') usage; exit 1;; '-j') export MAKEFLAGS="-j$2"; shift; shift;; '-k') remove_temporary_files_at_end="true"; shift;; '-l') run_local_compilation="false"; shift;; '-p') multiple_patches="true"; shift;; '-q') quiet_download="true"; shift;; '--') shift;; *) usage; exit 1;; esac done kernel_versions="$*" if [ "${kernel_versions}" = "" ]; then usage exit 1 fi #################### # Regression tests # #################### if [ "$(type -p sparse)" = "" ]; then echo "Error: sparse has not yet been installed." echo "See also http://www.kernel.org/pub/software/devel/sparse/." fi if [ "$(type -p smatch)" = "" ]; then echo "Error: smatch has not yet been installed." echo "See also http://smatch.sourceforge.net/." fi if ! mkdir -p "${outputdir}"; then if [ -e "${outputdir}" ]; then echo "Error: directory ${outputdir} already exists." else echo "Error: could not create directory ${outputdir}." fi fi test_scst_tree_patches || exit $? if [ "${run_local_compilation}" = "true" ]; then compile_scst_unpatched || exit $? compile_scst_no_dlm || exit $? compile_scst_patched 2release || exit $? compile_scst_patched 2perf || exit $? if type rpmbuild >/dev/null 2>&1; then make_rpm || exit $? fi if type debuild >/dev/null 2>&1; then make_dpkg || exit $? fi fi for kv in ${kernel_versions} do echo "==========================" printf "= kernel %-15s =\n" "${kv}" echo "==========================" full_check="false" generate_kernel_patch_options="" ibmvio="false" run_checkpatch="true" run_sparse="true" run_smatch="true" ipv6="true" global_multiple_patches="${multiple_patches}" while true; do kv_without_opt="${kv%-?}" if [ "${kv_without_opt}" = "${kv}" ]; then kv_without_opt="${kv%-??}" fi kopt="${kv#${kv_without_opt}}" case "${kopt}" in '-4') ipv6="false";; '-f') full_check="true";; '-i') ibmvio="true";; '-nc') run_checkpatch="false";; '-ns') run_sparse="false";; '-nm') run_smatch="false";; '-u') generate_kernel_patch_options="-u";; '-p') multiple_patches="true";; *) break;; esac kv="${kv_without_opt}" done patchdir="patchdir-${kv}" k="${kv}" generate_kernel_patch "$k" "${generate_kernel_patch_options}" || continue if ! ( cd "${outputdir}" && download_and_extract_kernel_tree "$k" ); then echo "Error: download_and_extract_kernel_tree $k failed" continue fi k="${k/^*}" if [ "${run_checkpatch}" = "true" ]; then run_checkpatch "$k" fi patch_and_configure_kernel "$k" || continue case "$k" in 3.10.0-*) # Make the CentOS 7.x build less noisy. KCFLAGS+=" -Wno-deprecated-declarations"; esac # See also commit bd664f6b3e37 ("disable new gcc-7.1.1 warnings for now"; # v4.13) and commit 6f303d60534c ("gcc-9: silence 'address-of-packed-member' # warning"; v5.1). for w in -Wno-format-truncation -Wno-format-overflow -Wno-int-in-bool-context -Wno-address-of-packed-member; do if gcc -c -xc -E - "$w" &/dev/null; then KCFLAGS+=" $w" fi done export KCFLAGS subdirs=(drivers/scst drivers/scsi/qla2xxx) if [ "${run_sparse}" = "true" ]; then run_sparse "$k" "${subdirs[@]}" mv "${outputdir}/sparse-$k-output.txt" \ "${outputdir}/sparse-$k-scst-output.txt" if [ "${ibmvio}" = "true" ]; then run_sparse "$k" drivers/scsi libsrp.ko scsi_sysfs.ko \ scsi_transport_fc.ko scsi_transport_srp.ko mv "${outputdir}/sparse-$k-output.txt" \ "${outputdir}/sparse-$k-scsi-output.txt" fi fi if [ "${run_smatch}" = "true" ]; then run_smatch "$k" "${subdirs[@]}" fi compile_kernel "$k" "${subdirs[@]}" if [ "${full_check}" = "true" ]; then run_headers_check "$k" compile_patched_kernel "$k" run_checkstack "$k" run_namespacecheck "$k" run_make_htmldocs "$k" fi if [ "${remove_temporary_files_at_end}" = "true" ]; then rm -rf "${outputdir}" mkdir -p "${outputdir}" fi multiple_patches="${global_multiple_patches}" done