#!/bin/bash

PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin

dirname="${0%/*}"
if [ "$dirname" = "$0" ]; then dirname="."; fi

if [ "$IN_PRIVATE_NS" = "" ]
then
  if [ "$(id -r -u)" = "0" ]
  then
    echo "Do not run as root. The unittest system uses user namespaces instead."
    exit 1
  fi
  exec $dirname/tools/newns $0 "$@"
fi

haderror=""
missing=""

for tab in mangle raw filter nat
do
  for ip in ip ip6
  do
    module="${ip}table_${tab}"
    if [ ! "$(lsmod | cut -f1 -d' ' | grep $module)" ]
    then
      test "$missing" = "" && echo "Missing module(s). Run:"
      echo "  sudo modprobe ${module}"
      missing="Y"
      haderror="Y"
    fi
  done
done

if [ ! -x $dirname/tools/clean-iptables ]
then
  echo "Executable $dirname/tools/clean-iptables script not found"
  echo ""
  haderror="Y"
fi

if [ ! -f ../sbin/install.config.in ]
then
  echo "../sbin/install.config.in missing: run configure"
  echo ""
  haderror="Y"
fi


if [ "$haderror" -o $# -lt 1 ]
then
  if [ "$haderror" ]
  then
    echo ""
    echo ""
  fi
  echo "Usage: ./unittest [-v] [--strace] all|test-dir|test-dir/test.conf [...]"
  echo ""
  echo "Example use of strace: see all non-absolute path executions:"
  echo "  grep 'execve[^,]*, \[\"[^/]' strace.*.txt | less"
  exit 1
fi

if [ ! -r /proc/net/ip_tables_names ]
then
  echo "Faking /proc/net/ip_tables_names"
  $dirname/tools/newns --fake-proc || exit 1
fi

echo "Running in separate namespace"
ip link add veth0 type veth peer name veth1 || exit 1

if ! MYTMP="`mktemp -d -t firehol-unittest-XXXXXX`"
then
            echo >&2
            echo >&2
            echo >&2 "Cannot create temporary directory."
            echo >&2
            exit 1
fi
export MYTMP

myexit() {
  status=$?
  rm -f /var/run/firehol.lck
  rm -rf $MYTMP
  exit $status
}

trap myexit INT
trap myexit HUP
trap myexit 0

TESTDIR=`pwd`/
export TESTDIR

# Force the programs to find our special configuration
export FIREHOL_OVERRIDE_PROGRAM_DIR=$MYTMP/prog
mkdir -p "$FIREHOL_OVERRIDE_PROGRAM_DIR"
sed -e "s#[@].*POST[@]#$MYTMP#" ../sbin/install.config.in > "$FIREHOL_OVERRIDE_PROGRAM_DIR/install.config"
cp $dirname/../sbin/functions.* "$FIREHOL_OVERRIDE_PROGRAM_DIR"
cp $dirname/../sbin/services.* "$FIREHOL_OVERRIDE_PROGRAM_DIR/"

verbose=0
if [ "$1" = "-v" ]
then
  shift
  verbose=1
fi

kcov=`which kcov 2> /dev/null`
if [ "$kcov" ]
then
  export kcov="$kcov coverage"
  if [ $verbose -eq 1 ]
  then
    echo "Using $kcov for coverage testing"
  fi
fi

strace=0
if [ "$1" = "--strace" ]
then
  shift
  strace=1
  STRACE_CMD=`which strace 2> /dev/null`
  if [ -z "$STRACE_CMD" ]
  then
    echo "strace not found!"
    exit 1
  fi
fi

if [ $# -eq 1 -a "$1" = "all" ]
then
  find firehol fireqos link-balancer vnetbuild update-ipsets -type f -name '*.conf' | sort > $MYTMP/all-tests
else
  find "$@" -type f -name '*.conf' | sort > $MYTMP/all-tests
fi

newext() {
  echo "$2" | sed -e "s;\.conf$;.$1;"
}

clear_iptables() {
  test -d $MYTMP || exit 3
  cat > $MYTMP/reset <<-!
	*raw
	:PREROUTING ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	COMMIT
	*nat
	:PREROUTING ACCEPT [0:0]
	:INPUT ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	:POSTROUTING ACCEPT [0:0]
	COMMIT
	*mangle
	:PREROUTING ACCEPT [0:0]
	:INPUT ACCEPT [0:0]
	:FORWARD ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	:POSTROUTING ACCEPT [0:0]
	COMMIT
	*filter
	:INPUT ACCEPT [0:0]
	:FORWARD ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	COMMIT
	!
  iptables-restore < $MYTMP/reset
  st1=$?

  cat > $MYTMP/reset <<-!
	*raw
	:PREROUTING ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	COMMIT
	*nat
	:PREROUTING ACCEPT [0:0]
	:INPUT ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	:POSTROUTING ACCEPT [0:0]
	COMMIT
	*mangle
	:PREROUTING ACCEPT [0:0]
	:INPUT ACCEPT [0:0]
	:FORWARD ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	:POSTROUTING ACCEPT [0:0]
	COMMIT
	*filter
	:INPUT ACCEPT [0:0]
	:FORWARD ACCEPT [0:0]
	:OUTPUT ACCEPT [0:0]
	COMMIT
	!
  ip6tables-restore < $MYTMP/reset
  st2=$?
  rm -f /var/run/firehol.lck

  if [ $st1 -ne 0 -o  $st2 -ne 0 ]
  then
    exit 2
  fi
}

#set -x
started=`date`
errors=0
total=0
while read conf
do
  export conf
  export pre_sh=$(newext pre.sh $conf)
  export run_sh=$(newext run.sh $conf)
  export post_sh=$(newext post.sh $conf)

  export runlog=$(newext log $conf)

  export out4=$(newext out4 $conf)
  export out6=$(newext out6 $conf)
  export aud4=$(newext aud4 $conf)
  export aud6=$(newext aud6 $conf)

  export outqdisc=$(newext qdisc.out $conf)
  export outclass=$(newext class.out $conf)
  export outfilter=$(newext filter.out $conf)
  export audqdisc=$(newext qdisc.aud $conf)
  export audclass=$(newext class.aud $conf)
  export audfilter=$(newext filter.aud $conf)

  export outrules=$(newext rules.out $conf)
  export outtable=$(newext table.out $conf)
  export audrules=$(newext rules.aud $conf)
  export audtable=$(newext table.aud $conf)

  export outns=$(newext ns.out $conf)
  export audns=$(newext ns.aud $conf)

  if [ $verbose -eq 1 ]
  then
    echo "$conf: start test"
  fi

  case $conf in
    *firehol/*/*.conf)
      clear_iptables
      program=firehol
    ;;
    *fireqos/*/*.conf)
      program=fireqos
    ;;
    *link-balancer/*/*.conf)
      program=link-balancer
    ;;
    *vnetbuild/*/*.conf)
      program=vnetbuild
    ;;
    *update-ipsets/*/*.conf)
      program=update-ipsets
    ;;
    *)
      program=
    ;;
  esac

  if [ ! "$program" ]
  then
    echo "Cannot determine program for $conf"
  else
    script=$dirname/../sbin/${program}
    export script
    total=$((total + 1))

    if [ $strace -eq 1 ]
    then
      export kcov="$STRACE_CMD -f -o `pwd`/strace.${total}.txt"
    fi

    # Define our configuration directory exactly as we want it
    rm -rf $MYTMP/firehol
    mkdir $MYTMP/firehol

    # Default special cases:
    #  - egrep because /sbin/egrep makes use of PATH to find 'grep -E'
    #    the unit tests set PATH to empty so we must use the explicit one
    #  - logger so we get e.g. panics in our logs, not on the system console
    #  - LB_RUN_DIR + FIREQOS_LOCK_FILE + FIREQOS_DIR + RUN_PARENT_DIR etc.
    #     keep within our mounts
    #  - PATH reset to ensure it is off (some programs reset it)
    cat > $MYTMP/firehol/firehol-defaults.conf <<-!
		EGREP_CMD='/bin/grep -E'
		LOGGER_CMD='/bin/echo logger:'
		LB_RUN_DIR=/var/run/firehol/link-balancer
		FIREQOS_DIR=/var/run/firehol/fireqos
		FIREQOS_LOCK_FILE=/var/run/firehol/fireqos.lock
		UPDATE_IPSETS_LOCK_FILE=/var/run/firehol/update-ipsets.lock
		RUN_PARENT_DIR=/var/run/firehol
		CACHE_DIR=/var/run/firehol/update-ipsets/cache
		LIB_DIR=/var/run/firehol/update-ipsets/lib
		WEB_DIR=/var/run/firehol/webdir
		FIREQOS_INTERFACE_DEFAULT_CLASSID=8000
		export PATH=
	!

    if [ $verbose -eq 1 ]
    then
      echo "$conf: setup"
    else
      echo "Test: $conf"
    fi
    if [ -x "$pre_sh" ]
    then
      # We can control completely with a testname.pre.sh
      "$pre_sh" "$conf"
    else
      # Or just take the defaults
      mkdir -p /var/run/firehol
      mkdir -p $MYTMP/run
      mkdir -p $MYTMP/firehol/services
    fi

    if [ $verbose -eq 1 ]
    then
      echo "$conf: run"
      #set -x
    fi
    if [ -x "$run_sh" ]
    then
      # We can control completely with a testname.pre.sh
      PATH= "$run_sh" "$conf" > "$runlog" 2>&1 < /dev/null
      status=$?
    else
      # Or just take the defaults
      case $program in
        firehol|fireqos|vnetbuild)
          PATH= $kcov "$script" "$conf" start > "$runlog" 2>&1 < /dev/null
          status=$?
        ;;
        link-balancer|update-ipsets)
          cp "$conf" $MYTMP/firehol/${program}.conf
          $kcov "$script" > "$runlog" 2>&1 < /dev/null
          status=$?
        ;;
      esac
    fi
      set +x

    if [ $verbose -eq 1 ]
    then
      echo "$conf: post-run"
    fi
    if [ $status -eq 0 -a -x "$post_sh" ]
    then
      "$post_sh" "$conf"
      status=$?
    fi

    if [ $verbose -eq 1 ]
    then
      echo "$conf: checks"
    fi
    if [ $status -ne 0 ]
    then
      errors=$((errors + 1))
      echo "Unexpected run error - check $runlog"
    elif grep -q ': line [0-9]*:' "$runlog"
    then
      errors=$((errors + 1))
      echo "Unexpected runtime errors - check $runlog"
    else
      case $program in
        firehol)
          iptables-save > "$out4".raw
          ip6tables-save > "$out6".raw
          $dirname/tools/clean-iptables "$out4".raw > "$out4"
          $dirname/tools/clean-iptables "$out6".raw > "$out6"
          if ! cmp "$aud4" "$out4"
          then
            errors=$((errors + 1))
          elif ! cmp "$aud6" "$out6"
          then
            errors=$((errors + 1))
          fi
        ;;
        fireqos)
          tc qdisc show dev veth0 > "$outqdisc.raw"
          tc class show dev veth0 > "$outclass".raw
          tc filter show dev veth0 > "$outfilter".raw
          $dirname/tools/clean-qdisc "$outqdisc".raw > "$outqdisc"
          $dirname/tools/clean-class "$outclass".raw > "$outclass"
          $dirname/tools/clean-filter "$outfilter".raw > "$outfilter"
          if ! cmp "$audqdisc" "$outqdisc"
          then
            errors=$((errors + 1))
          elif ! cmp "$audclass" "$outclass"
          then
            errors=$((errors + 1))
          elif ! cmp "$audfilter" "$outfilter"
          then
            errors=$((errors + 1))
          fi
        ;;
        link-balancer)
          ip rule list > "$outrules.raw"
          $dirname/tools/clean-rules "$outrules".raw > "$outrules"
          for n in $(ip rule list | sed -ne 's/.*lookup //p' | sort -n)
          do
            echo "===== TABLE $n ====="
            ip route list table $n
          done > "$outtable.raw"
          $dirname/tools/clean-table "$outtable".raw > "$outtable"
          if ! cmp "$audrules" "$outrules"
          then
            errors=$((errors + 1))
          elif ! cmp "$audtable" "$outtable"
          then
            errors=$((errors + 1))
          fi
        ;;
        vnetbuild)
          ip netns list > "$outns"
          if ! cmp "$audns" "$outns"
          then
            errors=$((errors + 1))
          fi
        ;;
      esac
    fi
  fi
done < $MYTMP/all-tests

echo " Started: $started"
echo "Finished: $(date)"
echo "   Tests: ${total}"
echo "  Errors: ${errors}"

if [ $total -eq 0 ]
then
  exit 4
fi
if [ $errors -gt 0 ]
then
  exit 5
fi
exit 0
