#!/bin/bash

IFS=: ldpath=($LD_LIBRARY_PATH)
while read dir; do
    if [ "${dir:0:1}" == "#" ]; then
	continue
    elif [ -d "$dir" ]; then
	ldpath=("${ldpath[@]}" "$dir")
    fi
done </etc/ld.so.conf
ldpath=("${ldpath[@]}" /usr/lib /lib)
IFS=: apath=($PATH)

findlib() {
    for d in "${ldpath[@]}"; do
	if [ -r "$d/$1" ]; then
	    echo "$d/$1"
	    return 0
	fi
    done
    return 1
}

findbin() {
    for d in "${apath[@]}"; do
	if [ -r "$d/$1" ]; then
	    echo "$d/$1"
	    return 0
	fi
    done
    return 1
}

has() {
    for obj in "${found[@]}"; do
	if [ "$obj" = "$1" ]; then return 0; fi
    done
    return 1
}

excluded() {
    for obj in "${exclude[@]}"; do
	if [ -d "$obj" ] && [[ "$1" == "$obj"* ]]; then return 0; fi
	if [ "$1" -ef "$obj" ]; then return 0; fi
    done
    return 1
}

examine() {
    local tfile lib lib2
    tfile="$(mktemp /tmp/lddotXXXXXX)"
    objdump -p "$1" | sed -n 's/^.*NEEDED \+\(lib.*\)/\1/p' >"$tfile"
    while read lib; do
	if ! lib2="$(findlib "$lib")"; then
	    echo "\"$lib\" [ color=red ];"
	    echo "lddot: $lib: not found" >&2
	    echo "\"$1\" -> \"$lib\";"
	    continue
	fi
	lib="$lib2"
	unset lib2
	if excluded "$lib"; then continue; fi
	echo "\"$1\" -> \"$lib\";"
	if has "$lib"; then continue; fi
	found=("${found[@]}" "$lib")
	examine "$lib"
    done <"$tfile"
    rm -f "$tfile"
    return 0
}

found=()
exclude=()
programs=()

while [ $# -gt 0 ]; do
    arg="$1"
    shift
    case "$arg" in
	-x)
	    exclude=("${exclude[@]}" "$1")
	    shift
	    ;;
	*)
	    programs=("${programs[@]}" "$arg")
    esac
done

if [ ${#programs[@]} -lt 1 ]; then
    echo "usage: lddot [-x EXCLUDED] PROGRAM..." >&2
    exit 1
fi

echo "digraph {"
rv=0
for bin in "${programs[@]}"; do
    if [[ "$bin" != */* ]]; then
	if ! bin2="$(findbin "$bin")"; then
	    echo "lddot: $bin: not found" >&2
	    rv=1
	    continue
	fi
	bin="$bin2"
	unset bin2
    fi
    if [ ! -r "$bin" ]; then
	echo "lddot: $bin: not readable" >&2
	rv=1
	continue
    fi
    echo "\"$bin\" [ shape=box ];"
    if ! examine "$bin"; then rv=1; fi
done
echo "}"

exit $rv
