#!/usr/bin/env bash

set -euo pipefail

build_ko() (
    local kernel_version="$1"
    local module_version="$2"
    local probe_type="$3"

    local module_src_dir
    if ((DOCKERIZED)); then
        module_src_dir="/kobuild-tmp/versions-src/${module_version}"
    else
        mkdir -p /scratch/module-src
        module_src_dir="/scratch/module-src/${module_version}"
        [[ -d "$module_src_dir" ]] || cp -r "/sources/${module_version}" "$module_src_dir"
    fi

    local kernel_src_dir="/scratch/kernel-src/${kernel_version}"
    if [[ ! -d "$kernel_src_dir" ]]; then
        rm -rf /scratch/kernel-src/* 2> /dev/null || true
        mkdir -p "$kernel_src_dir"
        tar -C "$kernel_src_dir" -xzf "/bundles/bundle-${kernel_version}.tgz"
    fi

    [[ -f "${kernel_src_dir}/BUNDLE_BUILD_DIR" ]] || {
        echo "No BUNDLE_BUILD_DIR entry found in kernel source bundle!"
        return 1
    }

    local kernel_build_dir
    kernel_build_dir="${kernel_src_dir}/$(cat "${kernel_src_dir}/BUNDLE_BUILD_DIR")"

    [[ -f "${kernel_src_dir}/BUNDLE_UNAME" ]] || {
        echo "No BUNDLE_UNAME entry found in kernel source bundle!"
        return 1
    }

    # Extract bundle details
    local bundle_uname bundle_version bundle_major bundle_distro
    bundle_uname="$(cat "${kernel_src_dir}/BUNDLE_UNAME")"
    bundle_version="$(cat "${kernel_src_dir}/BUNDLE_VERSION")"
    bundle_major="$(cat "${kernel_src_dir}/BUNDLE_MAJOR")"
    bundle_distro="$(cat "${kernel_src_dir}/BUNDLE_DISTRO")"

    cd "$module_src_dir"

    local rhel7_kernel_with_ebpf=false
    if ((bundle_version == 3 && bundle_major >= 10)); then
        if [[ "$bundle_distro" == "redhat" ]]; then
            rhel_build_id="$(echo "$bundle_uname" | awk -F'[-.]' '{ print $4 }')"
            if ((rhel_build_id >= 957)); then
                echo "Kernel ${bundle_uname} has backported eBPF support"
                rhel7_kernel_with_ebpf=true
            fi
        fi
    fi

    if [[ "$probe_type" == "mod" ]]; then
        # Attempting to run modpost will fail if it requires glibc version newer than
        # available in this distro. We skip building such kernel drivers for now.
        if ((DOCKERIZED)) && ! "${kernel_build_dir}/scripts/mod/modpost"; then
            echo >&2 "Failed to run kbuild tools, skipping module for ${kernel_version}"
            return 1
        fi

        echo "Building collector module for kernel version ${kernel_version} and module version ${module_version}."

        KERNELDIR="$kernel_build_dir" BUILD_ROOT="${kernel_src_dir}" make clean
        KERNELDIR="$kernel_build_dir" BUILD_ROOT="${kernel_src_dir}" make -j 6 all

        collector_ko=collector.ko
        strip -g "$collector_ko"

        ko_version="$(/sbin/modinfo "$collector_ko" | grep vermagic | tr -s " " | cut -d " " -f 2)"
        if [[ "$ko_version" != "$bundle_uname" ]]; then
            echo "Corrupted probe, KO_VERSION=$ko_version, BUNDLE_UNAME=$bundle_uname" >&2
            return 1
        fi

        mkdir -p "${MODULE_BASE_DIR}/${module_version}"
        gzip -c "$collector_ko" > "${MODULE_BASE_DIR}/${module_version}/collector-${kernel_version}.ko.gz"
        rm "$collector_ko"
    elif [[ "$probe_type" == "bpf" ]]; then
        # Build eBPF probe, if possible

        if [[ ! -d "${module_src_dir}/bpf" ]]; then
            echo "Module version does not support eBPF probe building, skipping ..."
            touch "${MODULE_BASE_DIR}/${module_version}/.collector-ebpf-${kernel_version}.unavail"
            return 0
        fi

        [[ -n "$bundle_version" && -n "$bundle_major" ]] || {
            echo >&2 "Bundle does not contain major/minor version information!"
            return 1
        }

        # Check if this module version supports RHEL 7.6 with backported eBPF support
        if [[ "$rhel7_kernel_with_ebpf" == true ]]; then
            if ! grep -qRIs "SUPPORTS_RHEL76_EBPF" "${module_src_dir}/bpf/quirks.h"; then
                echo "Module version ${module_version} does not support eBPF on RHEL 7"
                mkdir -p "${MODULE_BASE_DIR}/${module_version}"
                touch "${MODULE_BASE_DIR}/${module_version}/.collector-ebpf-${kernel_version}.unavail"
                return 0
            fi

        # Check kernel version is at least 4.14 (unless RHEL 7.6 kernel detected)
        else
            if ((bundle_version < 4 || (bundle_version == 4 && bundle_major < 14))); then
                echo "Kernel version ${kernel_version} does not support eBPF probe building, skipping ..."
                mkdir -p "${MODULE_BASE_DIR}/${module_version}"
                touch "${MODULE_BASE_DIR}/${module_version}/.collector-ebpf-${kernel_version}.unavail"
                return 0
            fi
        fi

        echo "Building collector eBPF probe for kernel version ${kernel_version} and module version ${module_version}."

        KERNELDIR="$kernel_build_dir" BUILD_ROOT="${kernel_src_dir}" make -j 6 -C bpf

        collector_probe="bpf/probe.o"

        if [[ ! -f "$collector_probe" || ! -s "$collector_probe" ]]; then
            echo "Empty or missing compiled bpf output"
            return 1
        fi

        mkdir -p "${MODULE_BASE_DIR}/${module_version}"
        gzip -c "$collector_probe" > "${MODULE_BASE_DIR}/${module_version}/collector-ebpf-${kernel_version}.o.gz"
        rm "$collector_probe"

    else
        echo >&2 "Invalid probe type '${probe_type}'"
        return 1
    fi
)

build_kos() {
    while read -r -a line || [[ "${#line[@]}" -gt 0 ]]; do
        local kernel_version="${line[0]}"
        local module_version="${line[1]}"
        local probe_type="${line[2]}"

        failure_output_file="${FAILURE_DIR}/${kernel_version}/${module_version}/${probe_type}.log"
        mkdir -p "$(dirname "$failure_output_file")"
        if ! build_ko "$kernel_version" "$module_version" "${probe_type}" 2> >(tee "$failure_output_file" >&2); then
            echo >&2 "Failed to build ${probe_type} probe version ${module_version} for kernel ${kernel_version}"
        else
            rm "$failure_output_file"
        fi
    done
}

DOCKERIZED=${DOCKERIZED:-0}

if ((DOCKERIZED)); then
    FAILURE_DIR="/FAILURES"
    MODULE_BASE_DIR="/kernel-modules"
    mkdir -p "$FAILURE_DIR"
    mkdir -p "$MODULE_BASE_DIR"
else
    FAILURE_DIR="/output/FAILURES"
    MODULE_BASE_DIR="/output"
fi

# The input is in the form <kernel-version> <module-version>. Sort it to make sure that we first build all modules
# for a given kernel version before advancing to the next kernel version.
sort | uniq | build_kos

# Remove empty directories
if ((DOCKERIZED)); then
    find "$FAILURE_DIR" -mindepth 1 -type d -empty -delete
fi
