#!/bin/bash

################################################################################
#
# Driver for Xilinx network controllers and boards
# Copyright 2021 Xilinx Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation, incorporated herein by reference.
#
################################################################################

######################################################################

# To test: Make changes to RHEL* or SLE* to cut down the run and commit
#  ./rpmbuild-master  -w /root/my.tgz -o <os> -a <ach> -v tip net

me=$(basename "$0")
bin=$(cd "$(dirname "$0")" && /bin/pwd)

err  () { echo >&2 "$*";    }
log  () { err "$me: $*";    }
vlog () { $verbose && err "$me: $*"; }
fail () { log "$*"; exit 2; }
try  () { "$@" || fail "'$*' failed"; }
trace() { log "invoking: $*"; "$@"; }
vtrace() { vlog "invoking: $*"; "$@"; }


function usage() {
  err
  err "usage:"
  err "  $me [options] <tag> <net|onload>"
  err
  err "description:"
  err "  Master script that controls the build of the binaries for a"
  err "  software release."
  err
  err "options:"
  err "  -o <os>                  -- specify os (eg. RHEL3)"
  err "  -a <arch>                -- specify arch (eg. i686)"
  err "  -k <kernels-file>        -- specify file listing kernel versions"
  err "  -c <config-file>         -- override config file"
  err "  -u <user>                -- override user (default is rpmbuild)"
  err "  -p <product>             -- select product (default is all)"
  err "  -s <source-rpm>          -- use existing source RPM"
  err "  -t <src-tree>            -- use existing source tree"
  err "  -d <distfiles-version>   -- specify distfiles version"
  err "  -w <tar-gz-filename>     -- create tar.gz of binary RPMs"
  err "  -v                       -- verbose (show all build output)"
  err
  exit 2
}


# Returns the name of the device on which the given directory resides.
function partition_of() {
  df -k "$1" | { read; cat; } | cut -f1 -d\  | head -1
}


function get_partition() { grep -iE "^$config:" $conf | cut -d: -f2; }
function get_disttag()   { grep -iE "^$config:" $conf | cut -d: -f3; }
function get_arch()      { grep -iE "^$config:" $conf | cut -d: -f4; }


function filterconfigs() {
  str="$1"
  shift
  while [ $# -gt 0 ]; do
    grep "^$1:" "$conf" | grep -i ":$str\>" | sed 's/:.*//'
    shift
  done
}


function setupmounts() {
  local dev="$1"
  local mp="$2"

  try [ -n "$mp" ]

  [ -d "$mp" ] || try trace mkdir "$mp"
  [ -f "$mp/etc/passwd" ] || try trace mount "$dev" "$mp"

  for d in /proc /home /project/hg /misc/apps; do
    try mkdir -p "$mp/$d"
  done


  # Test for something INSIDE the mount point
  # Note that bind mounts to automounted directories only see dirs
  #
  # TODO use "mount" output to decide if to mount
  #  - copy files into chroot manually to avoid need to mount
  [ -f /tmp/rpmtest-real-rootfs ] ||
    try touch /tmp/rpmtest-real-rootfs
  [ -f "$mp/tmp/rpmtest-real-rootfs" ] ||
    try trace mount -o bind /tmp "$mp/tmp"
  [ -f "$mp/proc/cpuinfo" ] ||
    try trace mount -t proc none "$mp/proc"
  [ -d "$mp/home/regression" ] ||
    try trace mount ukfiler:/vol/vol1/home "$mp/home"
  [ -d "$mp/project/hg/incoming" ] ||
    try trace mount -o bind /project/hg "$mp/project/hg"
  [ -d "$mp/misc/apps/Linux" ] ||
    try trace mount -o bind /misc/apps "$mp/misc/apps"

  # Some distribs populate /dev dynamically.  We just need to ensure
  # that /dev/null is present and correct.
  [ -c "$mp/dev/null" ] || {
    try trace mknod --mode=666 "$mp/dev/null" c 1 3
    try [ -c "$mp/dev/null" ]
  }

  # redirection from a sub-shell in export.sh (BASH) wants to access /dev/fd
  [ -d "/dev/fd" ] || {
      try ln -s /proc/self/fd /dev/fd
  }

  # Double check that lot worked.
  try [ -f "$mp/etc/passwd"   ]
  try [ -f "$mp/proc/cpuinfo" ]
  for d in /home /project/hg/incoming /misc/apps/Linux; do
    try [ -d "$mp/$d" ]
  done
}


function mountworkingdir() {
  local where="$1"
  local dir="$2"
  dev=$(partition_of "$dir")

  # For the moment we require that the working directory be on the root
  # partition.  Difficult to handle otherwise.
  try [ "$dev" = "$(partition_of /)" ]

  devd=$(echo "$dev" | sed 's+/+_+g')
  where="$where/$devd"
  try mkdir -p "$where"
  [ -f "$where/etc/passwd" ] || try trace mount "$dev" "$where"
  echo "$where$dir"
}


function cleanupmounts() {
  local mp="$1"
  [ -f "$mp/etc/passwd"         ] || return 0
  [ -f "$mp/proc/cpuinfo"       ] && trace umount "$mp/proc"
  for d in /proc/cpuinfo /home /project/hg/incoming /misc/apps/Linux /tmp; do
    [ -e "$mp$d" ] && trace umount "$(dirname "$mp$d")"
  done
  for d in $(cd $mp/mnt && /bin/ls); do
    [ -f "$mp/mnt/$d/etc/passwd" ] && {
      trace umount "$mp/mnt/$d"
      trace rmdir "$mp/mnt/$d"
    }
  done
  trace umount "$mp"
  trace rmdir "$mp"
}


function cleanup() {
  for d in $(cd /mnt && /bin/ls); do
    d="/mnt/$d"
    [ -f "$d/etc/passwd" ] || continue
    cleanupmounts "$d"
  done
}


function grabsrpm() {
  local srpm="$1"
  local tag="$2"
  local dir="$3"
  local source=
  local dest=

  log "Source rpm: $tree"
  try mkdir -p "$dir/v5/rpm"
  ( cd "$dir/v5"
    mkdir -p rpm/{BUILD,RPMS,SRPMS,SPECS,SOURCES}
    mkdir -p rpm/RPMS/{athlon,i{3,4,5,6}86,noarch}
  ) || exit 2

  try cp "$srpm" "$dir/v5/rpm/SRPMS"

  for dest in SPECS/xilinx-efct.mmakerpm SOURCES/xilinx-efct-dkms.conf SPECS/xilinx-efct-dkms.spec SPECS/xilinx-efct-kmp.spec; do
    # If the script exists alongside the SRPM, grab it from there.
    source="$(dirname "$srpm")/../$dest"
    if [ -f "$source" ]; then
      try cp "$source" "$dir/v5/rpm/$dest"
    else
      # Otherwise try to grab it from hg.
      try pushd /project/hg/incoming/v5 >/dev/null
      try hg cat -r"$tag" scripts/rpm/"$(basename "$dest")" \
	>"$dir/v5/rpm/$dest"
      try popd >/dev/null
    fi
  done
  try [ -x "$dir/v5/rpm/SPECS/xilinx-efct.mmakerpm" ]
}


function grabtree() {
  local tree="$1"
  local tag="$2"
  local dir="$3"

  log "Source tree: $tree"
  log
  log "Make source rpm."
  try mkdir -p "$dir/v5/rpm"
  ( cd "$tree" && try "scripts/mmakesrpm" "--$type" --dest "$dir/v5/rpm" ) || exit 2
  try [ -d "$dir/v5/rpm/SRPMS" ]
  for dest in SOURCES/xilinx-efct-dkms.conf SPECS/xilinx-efct-dkms.spec SPECS/xilinx-efct-kmp.spec; do
    source="$tree/scripts/rpm/$(basename "$dest")"
    try cp "$source" "$dir/v5/rpm/$dest"
  done
}


function grabtag() {
  local tag="$1"
  local dir="$2"
  local disttag="$3";
  log
  log "Checkout source tree."
  try mkdir -p "$dir"
  try pushd /project/hg/incoming/v5 >/dev/null
  try hg archive -r"$tag" "$dir/v5"
  try popd >/dev/null
  try pushd /project/hg/incoming/distfiles >/dev/null
  try hg archive -r"$disttag" "$dir/distfiles"
  try popd >/dev/null
  try [ -d "$dir/v5/src" ]
  try [ -d "$dir/v5/scripts" ]

  grabtree "$dir/v5" "$tag" "$dir"
}


function doconfig() {
  local config="$1"
  local tag="$2"
  local dir="$3"
  local kernels_file="$4"
  local disttag=$(get_disttag)

  [ -z "$kernels_file" ] &&
    kernels_file="$bin/${disttag}_$(get_arch)"

  log "config=$(grep "^$config:" $conf)"

  mount_partition=$(get_partition)
  vlog "mount_partition=$mount_partition"
  mount_point=/mnt/$(basename "$mount_partition")

  setupmounts "$mount_partition" "$mount_point"
  mpdir=$(mountworkingdir "$mount_point/mnt" "$dir") || exit 2
  vlog "dir=$dir mpdir=$mpdir"

  # Get $dir relative to the build partition.
  local rdir=${mpdir##$mount_point}

  # Chroot into the new environment, and do the work.
  log "chroot to $mount_point:  rdir=$rdir"

  log; log
  log "==============================================================="
  log "MASTER RUNNING: rpmbuild-master-inner.sh $tag $disttag $type $product"
  log "==============================================================="
  log; log

  # RUN RPMBUILD-MASTER-INNER
  vtrace chroot "$mount_point" bash "$rdir/bin/rpmbuild-master-inner.sh" \
	"$tag" "$kernels_file" "$rdir" "$user" "$disttag" "$type" "$product"
  rc=$?
  if [ $rc != 0 ]; then
    log "***** FAILED rpmbuild-master-inner rc=$rc *****"
    return $rc
  fi

  # Run a post build script as root - not in the chroot
  while IFS=: read ptype kernels; do
      builder_post=${dir}/bin/${ptype}-builder-post
      if [ -x ${builder_post} ]; then

	  log; log
	  log "==============================================================="
	  log "MASTER RUNNING: ${builder_post} ${dir} ${tag} ${type} ${kernels}"
	  log "==============================================================="
	  log; log

	  # RUN BUILDER POST
	  ${builder_post} ${dir} ${tag} ${type} ${kernels}
	  rc=$?
	  if [ $rc != 0 ]; then
	      log "***** FAILED ${ptype}-builder-post rc=$rc *****"
	      return $rc
	  fi
      fi
  done < "$kernels_file"

  return 0
}


######################################################################
# main()

conf="$bin/rpmbuild-$(hostname -s).conf"
kernels_file=
os=
arch=
user=rpmbuild
tree=
export verbose=false
srpm=
archive=
distfiles_version=
product="all"
# Parse the command line arguments.
while [ $# -gt 0 ]; do
  case "$1" in
    -o)	os="$2"; shift;;
    -a)	arch="$2"; shift;;
    -k)	kernels_file="$2"; shift;;
    -c)	conf="$2"; shift;;
    -u)	user="$2"; shift;;
    -t)	tree="$2"; shift;;
    -s)	srpm="$2"; shift;;
    -d) distfiles_version="$2"; shift;;
    -w) archive="$2"; shift;;
    -p)	product="$2"; shift;;
    -v)	verbose=true;;
    -*)	usage;;
    *)	break;;
  esac
  shift
done

[ $# = 2 ] || usage
[ -n "$tree" ] && [ -n "$srpm" ] && usage

[ "`whoami`" != root ] && fail "You have to run this as root."

tag="$1"
type="$2"

[ "$tag" = clean ] && { cleanup; exit; }

configs=$(grep "^[0-9]" "$conf" | sed 's/:.*//')
[ -n "$os"   ] && configs=$(filterconfigs "$os" $configs)
[ -n "$arch" ] && configs=$(filterconfigs "$arch" $configs)
[ -z "$distfiles_version" ] && distfiles_version=$tag

[ -n "$configs" ] || fail "No config files match. Please check -o and -a"

dir=$(mktemp -d "/tmp/etherfabric-$tag.XXXXXX")
try chmod go+rx "$dir"

log "$(date)"
# NB. rpmbuild-release-master depends on the following output:
log "Build tree: $dir"

if [ -n "$srpm" ]; then
  grabsrpm "$srpm" "$tag" "$dir"
elif [ -n "$tree" ]; then
  grabtree "$tree" "$tag" "$dir"
else
  grabtag "$tag" "$dir" "$distfiles_version"
fi
try mkdir -p "$dir/v5/rpm/ddisk" # as this gets tar'ed at the end

# Prepare DKMS directories
try rm -rf "$dir/v5/dkms"
try mkdir -p "$dir/v5/dkms"/{usr_src,var_lib}
try cp /var/lib/dkms/dkms_dbversion $dir/v5/dkms/var_lib

# Make another copy of the source for DKMS
try rpm -i --define "_topdir $dir/v5/rpm" "$dir/v5/rpm/SRPMS"/xilinx-efct-*.src.rpm
version="$(sed 's/^%define pkgversion //; t; d' "$dir/v5/rpm/SPECS/xilinx-efct.spec")"
try rpmbuild -bp --define "_topdir $dir/v5/rpm" "$dir/v5/rpm/SPECS/xilinx-efct.spec"
mv "$dir/v5/rpm/BUILD/xilinx-efct-$version" "$dir/v5/dkms/usr_src/"
sed "s/^PACKAGE_VERSION=.*/PACKAGE_VERSION=$version/" \
  < "$dir/v5/rpm/SOURCES/xilinx-efct-dkms.conf" \
  > "$dir/v5/dkms/usr_src/xilinx-efct-$version/dkms.conf"

# Add the source to DKMS.  It will create a sym-link to the source using
# an absolute path which won't work in the chroot, so replace that with
# a relative path.
try dkms add --dkmstree "$dir/v5/dkms/var_lib" \
  --sourcetree "$dir/v5/dkms/usr_src" \
  -m xilinx-efct -v "$version"
rm -f "$dir/v5/dkms/var_lib/xilinx-efct/$version/source"
try ln -s "../../../usr_src/xilinx-efct-$version" \
  "$dir/v5/dkms/var_lib/xilinx-efct/$version/source"

# Update version for KMPs and build the source package.
sed -i 's/^\(Version:\).*/\1	'"$version"'/' "$dir/v5/rpm/SPECS/xilinx-efct-kmp.spec"
try rpmbuild -bs --define "_topdir $dir/v5/rpm" "$dir/v5/rpm/SPECS/xilinx-efct-kmp.spec"

# Put a copy of the scripts into the working directory, and change
# ownership.
try cp -a "$bin" "$dir/v5/bin"
try cp "$(which dkms)" "$dir/v5/bin"
try chown -R "$user" "$dir"

failed=

for config in $configs; do
  doconfig "$config" "$tag" "$dir/v5" "$kernels_file" ||
    failed="$failed $config"
done

# Collect up all the successful DKMS builds into a tarball and then an
# RPM.  The "--all" option unfortunately only covers kernel versions
# that dkms thinks are installed, so we list all the kernel versions
# explicitly.
if [ -n "$(shopt -qs nullglob && echo "$dir/v5/dkms/var_lib/xilinx-efct/$version"/2.*/*)" ]; then
  try dkms mktarball --dkmstree "$dir/v5/dkms/var_lib" \
    --sourcetree "$dir/v5/dkms/usr_src" \
    -m xilinx-efct -v "$version" \
    --archive "xilinx-efct-$version.dkms.tar.gz" \
    $(cd "$dir/v5/dkms/var_lib/xilinx-efct/$version" && echo 2.*/* | sed 's/\b\(2\.[^ ]*\)\/\([^ ]*\)\b/-k \1 -a \2/g')
  try mv "$dir/v5/dkms/var_lib/xilinx-efct/$version/tarball/xilinx-efct-$version.dkms.tar.gz" \
    "$dir/v5/rpm/SOURCES/"
  sed -i 's/^\(Version:\).*/\1	'"$version"'/' "$dir/v5/rpm/SPECS/xilinx-efct-dkms.spec"
  rpmbuild -bb --define "_topdir $dir/v5/rpm" "$dir/v5/rpm/SPECS/xilinx-efct-dkms.spec"
fi

rc=0
[ -n "$failed" ] && {
  echo "**** The following configurations had errors:"
  for c in $failed; do
    grep "^$c:" "$conf" | sed 's/^/  /'
  done
  rc=1
}
log "All done: $(date)"
log "Output is at: $dir/v5"
if [ -n "$archive" ]; then
  [ -f "$dir/v5/rpm/$archive" ] && args="-avzf" || args="-cvzf"
  try tar --exclude ddiskit-0.9.9 -C "$dir/v5/rpm" "$args" "$archive" RPMS SRPMS ddisk
  log "tar file output at '$archive'"
fi
log "rc=$rc"
exit $rc
