#!/bin/sh

# shellcheck disable=SC2086,SC2119,SC2120

# shellcheck source=../lib/load-configuration
. "${0%/*}/../lib/load-configuration"

usage( ) {
  >&2 cat <<EOF

check_opcodes: [options] [ -a <arch> ] <package>

possible optons:
  -h|--help: Show this help page
  -a|--architecture: architecture family to check against, one of
                     i486, i686, pentium3 (meaning target architecture
                     the package should be runnable on)
  -v|--verbose: Verbose output, print result of check for logs
  -d|--debug: Debug output, used for development and testing

EOF
  exit 1
}

VERBOSE=0
DEBUG=0
EXIT_CODE=0

verbose( ) {
  if test $VERBOSE = 1; then
    echo "$@"
  fi
}

debug( ) {
  if test $DEBUG = 1; then
    echo "$@"
  fi
}

err( ) {
  echo "ERROR: $*"
  EXIT_CODE=1
}

tmp_dir=$(mktemp -d "${work_dir}/tmp.check-opcodes.XXXXXX")
trap 'rm -rf --one-file-system "${tmp_dir:?}"' EXIT

ARCH=i686

while getopts ":vda:h-:" opt; do
  case $opt in
    -)
      case "$OPTARG" in
        help)
          usage
          ;;
        verbose)
          VERBOSE=1
          ;;
        debug)
          DEBUG=1
          ;;
        *)
          echo "ERROR: Invalid option: --$OPTARG" >&2
          usage
          ;;
      esac
      ;;
    h)
      usage
      ;;
    v)
      VERBOSE=1
      ;;
    d)
      DEBUG=1
      ;;
    a)
      ARCH=$OPTARG
      ;;
    \?)
      echo "ERROR: Invalid option: -$OPTARG" >&2
      usage
      ;;
  esac
done

shift $((OPTIND-1))

PACKAGE=$1

if test "x$PACKAGE" = "x"; then
  echo "ERROR: Filename of a package required as argument" >&2
  usage
  exit 1
fi

OPCODE_ARGS=""
case $ARCH in
  i486)
    OPCODE_ARGS='-r -a 386 -v'
    ;;
  i686)
    OPCODE_ARGS='-s MMX -s SSE'
    ;;
  pentium3)
    OPCODE_ARGS='-s SSE2 -s SSE3'
    ;;
  *)
    echo "ERROR: architecture must currently be one of i486, i686 and pentium3" >&2
    usage
    exit 1
esac

debug "Unpacking $PACKAGE to $tmp_dir.."
bsdtar --no-fflags -x -C $tmp_dir -f $PACKAGE

debug "Checking for architecture: $ARCH ($OPCODE_ARGS).."

# shellcheck disable=SC2044
for absfile in $(find $tmp_dir \( -regextype grep -regex '.*\.so\(\.[0-9.]\+\)\?' -type f \) -o \( -executable -type f \) ); do
  file=$(basename $absfile)
  relfile=${absfile#$tmp_dir}
  debug "Checking file: $relfile"
  if ! readelf -a $absfile > $tmp_dir/$file.elf 2>/dev/null; then
    debug "readelf failed, ignoring file"
    continue
  fi
  if ! objdump -f $absfile > $tmp_dir/$file.objdump 2>/dev/null; then
    debug "objdump failed, ignoring file"
    continue
  fi
  file $absfile > $tmp_dir/$file.file
    
  arch=$(grep ^architecture $tmp_dir/$file.objdump | sed 's/^architecture: //g' | cut -f 1 -d ,)
  case $arch in
    i386:x86-64)
      arch='x86_64'
      ;;
    i386)
      arch='x86'
      ;;
    *)
      arch='unknown'
      ;;
  esac
  debug "  Objdump architecture: $arch"
  
  archelf=$(grep '^ \+Class' $tmp_dir/$file.elf | cut -f 2 -d : | tr -d ' ')
  case $archelf in
    ELF64)
      archelf='x86_64'
      ;;
    ELF32)
      archelf='x86'
      ;;
    *)
      archelf='unknown'
      ;;
  esac
  debug "  Readelf architecture: $archelf"

  if test $arch != $archelf; then
    err "ERROR: $file ambigous architecture information (objdump: $arch, ELF: $archelf)"
  fi
  
  if test $arch = "x86_64"; then
    err "ERROR: $file is a 64-bit library!"
    continue
  fi

  objdump -M intel -d $absfile > $tmp_dir/$file.asm
  bad_opcodes=$(${base_dir}/bin/opcode $OPCODE_ARGS -m 1 < $tmp_dir/$file.asm | wc -l)
  if test $bad_opcodes != 0; then
    case $ARCH in
      i486)
        err "$relfile is not built for plain i486 opcodes"
        ;;
      i686)
        err "$relfile contains MMX, SSE or newer opcodes"
        ;;
      pentium3)
        err "$relfile contains SSE2 or newer opcodes"
        ;;
    esac
    if test $DEBUG = 1; then
      ${base_dir}/bin/opcode $OPCODE_ARGS -B 2 -A 2 < $tmp_dir/$file.asm
    fi
  else
    if test $VERBOSE = 1; then
      verbose "OK: $relfile fullfills architecture constraint for $ARCH"
    fi
  fi
  
done

exit $EXIT_CODE