#!/usr/bin/env bash
#
# dep-search - utility for searching library dependencies
# of Windows executables and shared libraries.
#
# The BSD 3-Clause License. https://www.opensource.org/licenses/BSD-3-Clause
#
# This file is part of 'MSYS2' project.
# Copyright (c) 2014 by Alexpux (alexpux doggy gmail dotty com)
# All rights reserved.
#
# Project: MSYS2 ( https://sourceforge.net/projects/msys2/ )
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
# - Redistributions of source code must retain the above copyright 
#     notice, this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright 
#     notice, this list of conditions and the following disclaimer in 
#     the documentation and/or other materials provided with the distribution.
# - Neither the name of the 'MinGW-W64' nor the names of its contributors may 
#     be used to endorse or promote products derived from this software 
#     without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
# A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

# **************************************************************************

#   dep-search uses quite a few external programs during its execution. You
#   need to have at least the following installed for dep-search to function:
#   binutils, grep, sed
#

declare -r depsearch_name='dep-search'
declare -r depsearch_version='0.1'
_search_dir=$(pwd)
_search_file=
_search_pattern=
_search_mode=all
_prog_output=plain
_run_mode=dir
_verbose=0
export LC_ALL=C

### SUBROUTINES ###

plain() {
  local mesg=$1; shift
  printf "${BOLD}    ${mesg}${ALL_OFF}\n" "$@" >&2
}

print_msg0() {
  local mesg=$1; shift
  printf "${YELLOW}=> ${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

print_msg1() {
  local mesg=$1; shift
  printf "${GREEN}==> ${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

print_msg2() {
  local mesg=$1; shift
  printf "${BLUE}  ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

print_error() {
  local mesg=$1; shift
  printf "${RED}==> ERROR:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

die() {
  # $1 - message to die
  printf "$1"
  exit 1
}

usage() {
  printf "$depsearch_name (MSYS2) %s\n" "$depsearch_version"
  echo
  printf -- "Usage: %s [options]\n" "$0"
  echo
  printf -- "Options:\n"
  printf -- "  --directory=<dir>  Search dependencies in specified directory\n"
  printf -- "  --file=<filename>  Search dependencies for specified executable\n"
  printf -- "  --library=<library_pattern>  Pattern of library that need to be resolved as dependency\n"
  printf -- "  --output=[list|plain]  Output search result for human(plain) or as list of executables.\n"
  printf -- "  --verbose  Output more information.\n"
  echo
}

version() {
  printf "$depsearch_name (MSYS2) %s\n" "$depsearch_version"
  printf -- "Copyright (c) 2014 by Alexpux (alexpux doggy gmail dotty com).\n"
}

process_binary() {
  # $1 - windows PE file
  # $2 - search pattern
  # process all libraries needed by the binary
  if [[ -n $2 ]]; then
    local _lower_pattern=$(echo $2 | tr 'A-Z' 'a-z')
  fi
  local _sofile=
  for _sofile in $(LC_ALL=C objdump -x "$1" 2>/dev/null | sed -nr 's/.*DLL Name: (.*).*/\1/p')
  do
    if [[ -n $2 ]]; then
      local _lower_sofile=$(echo $_sofile | tr 'A-Z' 'a-z')
      if [ "$(echo $_lower_sofile | grep -Eio $_lower_pattern)" = "$_lower_pattern" ]; then
        if [[ $_verbose = 1 ]]; then
          print_msg2 "$1: $_sofile"
        else
          print_msg2 "$(basename $1): $_sofile"
        fi
      fi
    else
        print_msg2 "$_sofile"
    fi
  done
}

process_directory() {
  # $1 - derectory to search
  # $2 - search pattern
  local _file_list=$(find "$1" -type f -name "*.dll" -o -name "*.exe" -o -name "*.so" -o -name "*.pyd")
  local _it=
  if [[ ${#_file_list[@]} = 0 ]]; then
    print_msg1 "There are no executables or dll's in $1."
  else
    for _it in ${_file_list[@]}; do
      if [[ $_verbose = 1 ]]; then
        print_msg1 "Processing $_it"
      fi
      process_binary "$_it" "$2"
    done
  fi
}

# check if messages are to be printed using color
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
if [[ -t 2 && ! $USE_COLOR = "n" ]]; then
  # prefer terminal safe colored and bold text when tput is supported
  if /usr/bin/tput setaf 0 &>/dev/null; then
    ALL_OFF="$(/usr/bin/tput sgr0)"
    BOLD="$(/usr/bin/tput bold)"
    BLUE="${BOLD}$(/usr/bin/tput setaf 4)"
    GREEN="${BOLD}$(/usr/bin/tput setaf 2)"
    RED="${BOLD}$(/usr/bin/tput setaf 1)"
    YELLOW="${BOLD}$(/usr/bin/tput setaf 3)"
  else
    ALL_OFF="\e[1;0m"
    BOLD="\e[1;1m"
    BLUE="${BOLD}\e[1;34m"
    GREEN="${BOLD}\e[1;32m"
    RED="${BOLD}\e[1;31m"
    YELLOW="${BOLD}\e[1;33m"
  fi
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW

if [[ $1 = -@(h|-help) ]]; then
  usage
  exit 0
elif [[ $1 = -@(V|-version) ]]; then
  version
  exit 0
fi

# parse arguments
while [[ $# > 0 ]]; do
  case $1 in
    --directory=*)
      _search_dir=${1/--directory=/}
      _run_mode=dir
      if [[ ! -d $_search_dir ]]; then
        print_error "Search directory doesn't exists"
        exit 1
      fi
    ;;
    --file=*)
      _search_file=${1/--file=/}
      _run_mode=file
      if [[ ! -f $_search_file ]]; then
        print_error "File $_search_file doesn't exists"
        exit 1
      fi
    ;;
    --library=*)
      _search_pattern=${1/--library=/}
      _search_mode=single
    ;;
    --output=*)
      _prog_output=${1/--output=/}
    ;;
    --verbose)
      _verbose=1
    ;;
    --help) usage ;;
    *)
      die "bad command line: \"$1\". terminate."
    ;;
  esac
  shift
done

_ext_msg=
if [[ -n $_search_pattern ]]; then
  _ext_msg="on $_search_pattern"
fi

if [[ "$_run_mode" = "file" ]]; then
  print_msg0 "Scanning file %s for dependencies %s" "$_search_file" "$_ext_msg"
  process_binary "$_search_file" "$_search_pattern"
else
  print_msg0 "Scanning directory %s for dependencies %s" "$_search_dir" "$_ext_msg"
  process_directory "$_search_dir" "$_search_pattern"
fi
    
exit 0