#!/bin/bash

# This is the maximum depth to which dependencies are resolved
MAXDEPTH=14
SKIP_LIBC="yes"
SKIP_LIBWINE="yes"

function skip_name
{
    [[ -n "$SKIP_LIBC" && "$1" =~ .*lib(c|m)\.so.* ||
       -n "$SKIP_LIBWINE" && "$1" =~ .*libwine\.so.* ]]
}

# analyze a given file on its
# dependecies using ldd and write
# the results to a given temporary file
#
# Usage: analyze [OUTPUTFILE] [INPUTFILE] [NODESTYLE]
function analyze
{
    local OUT=$1
    local IN=$2
    local NODESTYLE="$3"
    local NAME=$(basename $IN)

    for i in $LIST; do
        if [ "$i" == "$NAME" ]; then
            # This file was already parsed
            return
        fi
    done
    # Put the file in the list of all files
    LIST="$LIST $NAME"

    add_subgraph "$NAME"

    DEPTH=$[$DEPTH + 1]
    if [ $DEPTH -ge $MAXDEPTH ]; then
        echo "MAXDEPTH of $MAXDEPTH reached at file $IN." >&2
        echo "Continuing with next file..." >&2
        return
    fi

    echo "Parsing file:              $IN"

    $READELF $IN &> $READELFTMPFILE
    ELFRET=$?

    if [ $ELFRET != 0 ]; then
        echo "ERROR: ELF reader $IN returned error code $RET" >&2
	echo "  \"$NAME\" [color=red];" >> $OUT
        #echo "ERROR:" >&2
        #cat $TMPFILE >&2
        #echo "Aborting..."
        #rm $TMPFILE
        #rm $READELFTMPFILE
        #rm $LDDTMPFILE
        #exit 1
    fi

    DEPENDENCIES=$(cat $READELFTMPFILE | grep NEEDED | awk '{if (substr($NF,1,1) == "[") print substr($NF, 2, length($NF) - 2); else print $NF}')

    [ "${IN#/home/}" != "$IN" ] && NAME="POL/$NAME"

    for DEP in $DEPENDENCIES; do
        if [ -n "$DEP" ]; then

            ldd $IN &> $LDDTMPFILE
            LDDRET=$?

            if [ $LDDRET != 0 ]; then
                    echo "ERROR: ldd $IN returned error code $RET" >&2
		    echo "  \"$NAME\" [color=red];" >> $OUT
                #echo "ERROR:" >&2
                #cat $TMPFILE >&2
                #echo "Aborting..."
                #rm $TMPFILE
                #rm $READELFTMPFILE
                #rm $LDDTMPFILE
                #exit 1
            fi

	    if grep -q "$DEP => not found" $LDDTMPFILE; then
		if [ "$NODESTYLE" ]; then
		    echo "$NODESTYLE" >> $OUT
		    NODESTYLE=""
		fi
		echo -e "  \"$NAME\" -> \"$DEP\";" >> $OUT
		echo -e "  \"$DEP\" [color=red];" >> $OUT
	    elif ! skip_name "$DEP"; then
		DEPPATH=$(grep "$DEP =>" $LDDTMPFILE | awk '{print $3}')
		if [ -n "$DEPPATH" ]; then
		    if [ "$NODESTYLE" ]; then
			echo "$NODESTYLE" >> $OUT
			NODESTYLE=""
		    fi
		    EDGESTYLE=""
		    if [ "${DEPPATH#/home/}" != "$DEPPATH" ]; then
			DEP="POL/$DEP"
			[ "${IN#/home/}" = "$IN" ] && EDGESTYLE=" [style=bold,color=red]"
		    else
			[ "${IN#/home/}" != "$IN" ] && EDGESTYLE=" [color=cyan]"
		    fi
                    echo -e "  \"$NAME\" -> \"$DEP\"$EDGESTYLE;" >> $OUT
                    analyze $OUT $DEPPATH
		fi
            fi
        fi
    done

    DEPTH=$[$DEPTH - 1]
}

# put given name in the right subgraph
#
# Usage: add_subgraph [NODENAME]

function add_subgraph {
    local NAME="$1"
    case "$NAME" in
	POL/[a-c]*.so)
	    LIST_SUBGRAPH1="${LIST_SUBGRAPH1:+$LIST_SUBGRAPH1 }$NAME"
	    ;;
	POL/[d-f]*.so)
	    LIST_SUBGRAPH2="${LIST_SUBGRAPH2:+$LIST_SUBGRAPH2 }$NAME"
	    ;;
	POL/[g-i]*.so)
	    LIST_SUBGRAPH3="${LIST_SUBGRAPH3:+$LIST_SUBGRAPH3 }$NAME"
	    ;;
	POL/[j-l]*.so)
	    LIST_SUBGRAPH4="${LIST_SUBGRAPH4:+$LIST_SUBGRAPH4 }$NAME"
	    ;;
	POL/[m-o]*.so)
	    LIST_SUBGRAPH5="${LIST_SUBGRAPH5:+$LIST_SUBGRAPH5 }$NAME"
	    ;;
	POL/[p-r]*.so)
	    LIST_SUBGRAPH6="${LIST_SUBGRAPH6:+$LIST_SUBGRAPH6 }$NAME"
	    ;;
	POL/[s-u]*.so)
	    LIST_SUBGRAPH7="${LIST_SUBGRAPH7:+$LIST_SUBGRAPH7 }$NAME"
	    ;;
	POL/[v-x]*.so)
	    LIST_SUBGRAPH8="${LIST_SUBGRAPH8:+$LIST_SUBGRAPH8 }$NAME"
	    ;;
	POL/[y-z]*.so)
	    LIST_SUBGRAPH9="${LIST_SUBGRAPH9:+$LIST_SUBGRAPH9 }$NAME"
	    ;;
	POL/*.so)
	    LIST_SUBGRAPHO="${LIST_SUBGRAPHO:+$LIST_SUBGRAPHO }$NAME"
    esac
}

# output extra subgraphs attributes to
# a given temporary file
#
# Usage: subgraphs [OUTPUTFILE]

function subgraphs
{
    local OUT=$1
    local IN

    for IN in "$LIST_SUBGRAPH1" "$LIST_SUBGRAPH2" "$LIST_SUBGRAPH3" "$LIST_SUBGRAPH4" "$LIST_SUBGRAPH5" "$LIST_SUBGRAPH6" "$LIST_SUBGRAPH7" "$LIST_SUBGRAPH8" "$LIST_SUBGRAPH9" "$LIST_SUBGRAPHO"; do
	if [ -n "$IN" ]; then
	    echo -n "{ rank=same; " >> $OUT
	    for node in $IN; do
		echo -n "\"$node\"; " >> $OUT
	    done
	    echo "}" >> $OUT
	fi
    done
}

########################################
# main                                 #
########################################

if [ $# -lt 2 ];
    then
    echo "Usage:"
    echo "  $0 [filename...] [outputimage]"
    echo ""
    echo "This tools analyses a shared library or an executable (or executables)"
    echo "and generates a dependency graph as a PNG image."
    echo ""
    echo "GraphViz must be installed for this tool to work."
    echo ""
    exit 1
fi

if ! type dot 2> /dev/null; then
    echo "GraphViz must be installed for this tool to work." >&2
    exit 1
fi

DEPTH=0
INPUTS=$1
shift
while [ $# -gt 1 ]; do
    INPUTS="$INPUTS $1"
    shift
done
OUTPUT=$1
TMPFILE=$(mktemp -t)
LDDTMPFILE=$(mktemp -t)
READELFTMPFILE=$(mktemp -t)
LIST=""

for INPUT in $INPUTS; do
    if [ ! -e $INPUT ]; then
	echo "ERROR: File not found: $INPUT" >&2
	echo "Aborting..." >&2
	exit 2
    fi
done

# Use either readelf or dump
# Linux has readelf, Solaris has dump
READELF=$(type readelf 2> /dev/null)
if [ $? != 0 ]; then
  READELF=$(type dump 2> /dev/null)
  if [ $? != 0 ]; then
    echo "Unable to find ELF reader" >&2
    exit 1
  fi
  READELF="dump -Lv"
else
  READELF="readelf -d"
fi



echo "Analyzing dependencies of: $INPUTS"
echo "Creating output as:        $OUTPUT"
echo ""

(echo "digraph DependencyTree {") > $TMPFILE
for INPUT in $INPUTS; do
    ROOTINPUT="$(basename $INPUT)"
    [ "${INPUT#/home/}" != "$INPUT" ] && ROOTINPUT="POL/$ROOTINPUT"
#    echo "  \"$ROOTINPUT\" [shape=box];" >> $TMPFILE
    analyze $TMPFILE "$INPUT" "  \"$ROOTINPUT\" [shape=box];"
done
subgraphs $TMPFILE
echo "}" >> $TMPFILE

#cat $TMPFILE # output generated dotfile for debugging purposes
dot -Tpng $TMPFILE -o$OUTPUT

rm $LDDTMPFILE
#rm $TMPFILE
echo "Temporary file: $TMPFILE"

exit 0
