#!/bin/sh
#
# Check what's installed on a PCP/PCPQA VM looking for missing apps
# and packages
#

_usage()
{
    echo "Usage: $0 [-v]"
    exit 1
}

verbose=false
while getopts 'v?' p
do
    case "$p"
    in
	v)	verbose=true
		;;

	?)	_usage
		# NOTREACHED
    esac
done
shift `expr $OPTIND - 1`
[ $# -eq 0 ] || _usage

tmp=/var/tmp/$$
trap "rm -f $tmp.*; exit 0" 0 1 2 3 15

# same function is in allow-pmlc-access ... need to track changes
#
_getnetworkaddr()
{
    if `which ifconfig >/dev/null 2>&1`
    then
	# expecting ifconfig lines like ...
        # inet 192.168.1.222  netmask 255.255.255.0  broadcast 192.168.1.255
	# inet addr:192.168.1.100  Bcast:192.168.1.255  Mask:255.255.255.0
	#
	# skip ip tunnels/vpns like
	# inet addr:10.8.3.138  P-t-P:10.8.3.137  Mask:255.255.255.255
	#
	ifconfig \
	| grep -v '[ :]127\.0\.0' \
	| sed -n >$tmp.addr \
	    -e '/^[ 	]*inet .*cast[ :]/{
s/^[ 	]*inet[ 	]//
s/^addr://
s/[ 	][ 	]*broadcast[ 	][^ 	]*//
s/:Bcast:[^ ]*//
s/[ 	][ 	]*netmask[ 	]/ /
s/[ 	][ 	]*Mask:/ /
p
}'
	#debug# cat $tmp.addr >&2
	num=`wc -l <$tmp.addr | sed -e 's/ //g'`
	case "$num"
	in
	    1)	# good
		addr=`cat $tmp.addr | sed -e 's/ .*//'`
		mask=`cat $tmp.addr | sed -e 's/.* //'`
		case "$mask"
		in
		    255.255.255.0|0xffffff00)	# /24 network
			echo "$addr" | sed -e 's/\.[0-9]*$/.*/'
			;;
		    # pmcd's [access] is not smart enough to handle other
		    # than /24 networks, so map the other likely options
		    # to the broader /24 network
		    #
		    255.255.255.128|255.255.255.192|255.255.255.224|255.255.255.240|255.255.255.248|255.255.255.252|255.255.255.254)
			echo "$addr" | sed -e 's/\.[0-9]*$/.*/'
			;;
		    *)
			echo >&2 "_getnetworkaddr: Warning: cannot handle network mask: $mask"
			;;
		esac
		;;
	    0)	# none?
		echo >&2 "_getnetworkaddr: Warning: cannot get network address"
		;;
	    *)	# multiple?
		echo >&2 "_getnetworkaddr: Warning: cannot handle multiple network addresses:"
		cat >&2 $tmp.addr
		;;
	esac
    else
	echo >&2 '_getnetworkaddr: Warning: no ifconfig?'
    fi
}

# version1 is on line 1
# version2 is on line 2
# relop is "<", "<=", "=", ">=" or ">"
# return value is 0 (true) if version1 relop version2 is true
# else return value is 1
#
_compare()
{
    relop="$1"
    awk -F. >$tmp.compare '
NR == 1	{ for (i = 1; i <= NF; i++)
	    v[i] = $i
	  nv = NF
	  next
	}
NR == 2	{ nf = NF
	  if (nv > nf) nf = nv
	  for (i = 1; i <= nf; i++) {
	    if (v[i]+0 == $i+0) continue
	    if (v[i]+0 < $i+0) {
		print "<"
		exit
	    }
	    if (v[i]+0 > $i+0) {
		print ">"
		exit
	    }
	  }
	  print "="
	}'
    ret=1
    case `cat $tmp.compare`
    in
	"<")
	    [ "$relop" = "<" -o "$relop" = "<=" ] && ret=0
	    ;;
	"=")
	    [ "$relop" = "=" -o "$relop" = "<=" -o "$relop" = ">=" ] && ret=0
	    ;;
	">")
	    [ "$relop" = ">" -o "$relop" = ">=" ] && ret=0
	    ;;
	*)
	    echo "Arrgh ... installed version $version, want $relop $specversion,"
	    echo "but failed to extract relop (`cat $tmp.compare`)"
	    ;;
    esac
    return $ret
}

# add additional and optional directories
for dir in /sbin /usr/sbin
do
    if [ -d "$dir" ]
    then
	if echo ":$PATH:" | grep -q ":$dir:"
	then
	    :
	else
	    export PATH="$PATH:$dir"
	    #debug# echo add $dir to \$PATH
	fi
    fi
done

# perl ? apt-get install -qqy --force-yes libxml-tokeparser-perl
# perl ? apt-get install -qqy --force-yes libspreadsheet-read-perl
cat <<End-of-File >$tmp.control
# one line per app, alternate items separated by white space
# first item ending in ? means this item is a guard, if it does not
# exist silently ignore the rest of the line
# tests items can be
#	starting with a /	test for existence of file or directory
#                               (alternates separated by |)
#	containing ::		test for presence of the associated Perl module
#	otherwise		test for an executable with which(1)
#                               (alternates separated by |)
#
# text after # is treated as a comment
# text after [ is treated as annotation
#
# executables
bash
sed
grep
ed
git
make
gcc
autoconf
flex
bison
gdb
mktemp
gawk
cpan				[perl-CPAN (rpm) (QA optional)]
# optional executables
dpkg?	dpkg-buildpackage	[dpkg-dev]
dpkg?	dh			[debhelper]
dpkg?	chrpath			[chrpath]
dpkg?	sysv-rc-conf		[sysv-rc-conf]
rpm?	rpmbuild		[rpm-build]
# other build prerequisites
dpkg?	/usr/share/doc/pkg-config	[pkg-config]
dpkg?	/usr/share/doc/python-all-dev	[python-all-dev]
dpkg?	/usr/include/microhttpd.h	[libmicrohttpd-dev (build optional)]
rpm?	/usr/include/microhttpd.h	[libmicrohttpd-devel (build optional)]
dpkg?	/usr/include/cairo/cairo.h	[libcairo2-dev (build optional)]
rpm?	/usr/include/cairo/cairo.h	[cairo-devel (build optional)]
dpkg?	/usr/include/sasl/sasl.h	[libsasl2-dev (build optional)]
rpm?	/usr/include/sasl/sasl.h	[cyrus-sasl-devel (build optional)]
rpm?	/usr/include/systemd/sd-daemon.h	[systemd-devel (build optional)]
dpkg?	/usr/include/infiniband/umad.h	[libibumad-dev (build optional)]
rpm?	/usr/include/infiniband/umad.h	[libibumad-devel (build optional)]
dpkg?	/usr/include/infiniband/mad.h	[libibmad-dev (build optional)]
rpm?	/usr/include/infiniband/mad.h	[libibmad-devel (build optional)]
dpkg?	/usr/include/avahi-common	[libavahi-common-dev (build optional)]
rpm?	/usr/include/avahi-common	[avahi-devel (build optional)]
dpkg?	/usr/include/papi.h		[libpapi-dev (build optional)]
rpm?	/usr/include/papi.h		[papi-devel (build optional)]
dpkg?	/usr/include/perfmon/pfmlib_perf_event.h	[libpfm4-dev (build optional)]
rpm?	/usr/include/perfmon/pfmlib_perf_event.h	[libpfm-devel (build optional)]
rpm?	/usr/include/rpm/header.h	[rpm-devel (build optional)]
dpkg?	g++				[g++]
rpm?	g++				[gcc-c++]
# files
dpkg?	/usr/include/readline/readline.h	[libreadline-dev]
rpm?	/usr/include/readline/readline.h	[readline-devel]
dpkg?	/usr/include/ncurses.h	[libncurses5-dev]
rpm?	/usr/include/ncurses.h	[ncurses-devel] # is this a bug in the spec file?
/usr/lib/python*/*-packages/six.py	[python-six]
dpkg?	/usr/lib/python*/*-packages/jsonpointer.py	[python-json-pointer]
rpm?	/usr/lib/python*/*-packages/jsonpointer.py	[python-jsonpointer]
# perl modules
Time::HiRes			[Perl Time-HiRes perl-Time-HiRes(rpm)]
Date::Format			[Perl TimeDate perl-TimeDate(rpm)]
Date::Parse			[Perl TimeDate perl-TimeDate(rpm)]
Getopt::Std			[base Perl]
dpkg?	ExtUtils::MakeMaker	[libextutils-autoinstall-perl]
rpm?	ExtUtils::MakeMaker	[perl-ExtUtils-MakeMaker]
dpkg?	XML::TokeParser		[libxml-tokeparser-perl]
rpm?	XML::TokeParser		[perl-XML-TokeParser or from cpan]
rpm?	RRDs::			[rrdtool-perl or perl-rrdtool or rrdtool]
dpkg?	RRDs::			[librrds-perl]
dpkg?	Spreadsheet::Read	[libspreadsheet-read-perl (QA optional)]
rpm?	Spreadsheet::Read	[perl-Spreadsheet-Read or from cpan (QA optional)]
dpkg?	Spreadsheet::WriteExcel	[libspreadsheet-writeexcel-perl]
rpm?	Spreadsheet::WriteExcel	[perl-Spreadsheet-WriteExcel or from cpan]
rpm?	JSON::			[perl-JSON]
Spreadsheet::ReadSXC		[perl-Spreadsheet-ReadSXC or from cpan (QA optional)]
rpm	time			[time]
# other run-time
sadf				[sysstat]
# QA
bc				[(QA optional)]
curl				[(QA optional)]
socat				[(QA optional)]
avahi-browse			[avahi-tools or avahi-utils (QA optional)]
dpkg?	Spreadsheet::XLSX	[libspreadsheet-xlsx-perl (QA optional)]
rpm?	Spreadsheet::XLSX	[perl-Spreadsheet-XLSX or from cpan (QA optional)]
dpkg?	Text::CSV_XS		[Text-CSV_XS (QA optional)]
rpm?	Text::CSV_XS		[perl-Text-CSV_XS (QA optional)]
crontab				[from vixie-cron?]
valgrind			[(QA optional)]
dpkg?	mail			[bsd-mailx (QA optional)]
rpm?	mail			[mailx (QA optional)]
dpkg?	apache2			[apache2 (QA optional)]
rpm?	httpd|/etc/apache2/httpd.conf	[httpd or apache2 (QA optional)]
dpkg?	realpath		[realpath]
dpkg?	unbound-control		[unbound (QA optional)]
rpm?	/usr/share/doc/python-requests*|/usr/share/doc/packages/python-requests	[python-requests (QA optional)]
dpkg?	/usr/share/doc/python-requests|/usr/share/doc/packages/python-requests	[python-requests (QA optional)]
rpm?	/usr/bin/logconv.pl	[389-ds-base (QA optional)]
rpm?	/usr/lib*/sasl2/libplain.so	[cyrus-sasl-plain (QA optional)]
dpkg?	DBI::			[libclass-dbi-perl (postgresql QA optional)]
rpm?	DBI::			[perl-DBI (postgresql QA optional)]
dpkg?	DBD::Pg			[libdbd-pg-perl (postgresql QA optional)]
rpm?	DBD::Pg			[perl-DBD-Pg (postgresql QA optional)]
# pcp-gui
dpkg?	qmake			[qt4-qmake (build optional)]
rpm?	qmake|/usr/lib*/qt4/bin/qmake		[qt4-devel (build optional)]
dpkg?	/usr/lib/libSoQt4.so|/usr/lib/*/libSoQt4.so	[libsoqt-dev (build optional)]
rpm?	/usr/lib*/libQtCore.so|/usr/lib/qt4/lib/libQtCore.so	[qt4-devel (build optional)]
dpkg?	/usr/include/Inventor/Qt/SoQt.h	[libsoqt-dev-common (build optional)]
dpkg?	/usr/include/Inventor/SoPath.h	[libcoin60-dev or libcoin80-dev (build optional)]
# python
rpm?	/usr/include/python*/Python.h	[python-devel]
dpkg?	/usr/include/python*/Python.h	[python-dev]
rpm?	/usr/lib*/python*/curses	[python-curses]
# nss
dpkg?	/usr/include/nspr/nspr.h	[libnspr4-dev (build optional)]
rpm?	/usr/include/nspr4/nspr.h	[nspr-devel or mozilla-nspr-devel (build optional)]
dpkg?	/usr/include/nss/nss.h		[libnss3-dev (build optional)]
rpm?	/usr/include/nss3/nss.h		[nss-devel or mozilla-nss-devel (build optional)]
dpkg?	certutil			[libnss3-tools (build optional)]
rpm?	certutil			[nss-tools or mozilla-nss-tools (build optional)]
End-of-File

if which python >/dev/null 2>&1
then
    # For python-ctypes, check for python before 2.5 ... expect something like
    # Python 2.7.3
    eval `python -V 2>&1 | sed -e 's/Python //' -e 's/^/maj=/' -e 's/\./ min=/' -e 's/\..*//'`
    if [ -n "$maj" -a -n "$min" ]
    then
	rm -f $tmp.need
	if [ "$maj" -lt 2 ]
	then
	    touch $tmp.need
	elif [ "$maj" -eq 2 -a "$min" -lt 5 ]
	then
	    touch $tmp.need
	fi
	[ -f $tmp.need ] && \
	    echo "rpm?	/usr/share/doc/python-ctypes*	[python-ctypes]" >>$tmp.control
    fi
fi

# platform specific cases
#
# tests here are copied from $(TOPDIR)/configure.ac
#
if [ -f /etc/fedora-release ]
then
    # Fedora
    echo "/usr/sbin/qshape		[postfix-perl-scripts]" >>$tmp.control
elif [ -f /etc/redhat-release ]
then
    # Redhat or CentOS
    echo "/usr/sbin/qshape		[postfix-perl-scripts]" >>$tmp.control
    echo "host				[bind-utils]" >>$tmp.control
elif [ -f /etc/SuSE-release ]
then
    echo "/usr/share/doc/packages/postfix-doc/auxiliary/qshape/qshape.pl		[postfix-doc]" >>$tmp.control
fi

cat $tmp.control \
| sed -e 's/#.*//' -e '/^[ 	]*$/d' \
| while read apps
do
    rm -f $tmp.ok
    rm -f $tmp.echo
    for app in $apps
    do
	case $app
	in
	    \[*)
	    	break
		;;
	    *\?)
	    	app=`echo $app | sed -e 's/?$//'`
		optional=true
		;;
	    *)
		optional=false
		;;
	esac
	case $app
	in
	    \[*)
	    	break
		;;
	    *::)
		# special case Perl, no module name
		echo "use `echo $app | sed -e 's/::$//'`;" | perl >/dev/null 2>&1
		ok=$?
		;;

	    *::*)
		# normal case Perl, with module name
		echo "use $app;" | perl >/dev/null 2>&1
		ok=$?
		;;
	    *)  # file, directory or executable tests, separated by |
		rm -f $tmp.tmp
		for obj in `echo "$app" | sed -e 's/|/ /g'`
		do
		    case "$obj"
		    in
			/*)
			    if [ -f "$obj" -o -d "$obj" ]
			    then
				touch $tmp.tmp
				break
			    fi
			    ;;
			*)
			    if which $obj >/dev/null 2>&1
			    then
				touch $tmp.tmp
				break
			    fi
			    ;;
		    esac
		done
		[ -f $tmp.tmp ]
		ok=$?
		;;
	esac
	if $verbose
	then
	    if [ "$app" = dpkg -o "$app" = rpm ]
	    then
		# rpm? and dpkg? guards are not really useful to report
		#
		:
	    else
		echo -n "$app ... "
		$optional && echo -n "[optional]"
		if [ $ok = 0 ]
		then
		    echo -n "yes "
		else
		    echo -n "no "
		fi
		touch $tmp.echo
	    fi
	fi
	if [ $ok = 0 ]
	then
	    $optional && continue
	    touch $tmp.ok
	    break
	else
	    if $optional
	    then
		# guard not true, skip checks for other apps
		touch $tmp.ok
		break
	    fi
	fi
    done
    [ -f $tmp.echo ] && echo
    if [ ! -f $tmp.ok ]
    then
	echo "Missing: `echo $apps \
	    | sed \
		-e 's/[ 	][ 	]*/ /g' \
		-e '/ /{
s/? /?@/
:loop1
s/\(\[.*\) /\1@/
t loop1
:loop2
s/ \([^[]\)/@|@\1/
t loop2
s/@/ /g
}'`"
    fi
done

# Some platform-specific and special case tests
#
if which dpkg >/dev/null 2>&1
then
    # Debian based
    #
    dpkg -l >$tmp.tmp
    for pkg in python-all python-all-dev
    do
	if grep "^ii *$pkg" <$tmp.tmp >/dev/null
	then
	    :
	else
	    echo "Need # apt-get install $pkg"
	fi
    done
    # There are some version dependencies in the debian/control file
    # (see Build-Depends and *-Version lines) and some are found in
    # configure.ac ... both need to be mirrored here
    #
    cat <<End-of-File >$tmp.control
# one line per package
# pkg-name	relop	version
# text after # is treated as a comment
# debian/control
debhelper	>=	5
perl		>=	5.6
python		>=	2.6
# configure.ac
libmicrohttpd	>	0.9.9
libcairo2-dev	>=	1.2
End-of-File
    cat $tmp.control \
    | sed -e 's/#.*//' -e '/^[ 	]*$/d' \
    | while read pkg relop ctlversion
    do
	# dpkg -l lines like ...
	# ii  debhelper      9.20131227ub all          ...
	#
	if grep "ii[ 	][ 	]*$pkg[ 	]" $tmp.tmp >/dev/null 2>&1
	then
	    version=`awk <$tmp.tmp '$2 == "'"$pkg"'" { print $3 }'`
	    if [ -z "$version" ]
	    then
		echo "Arrgh ... failed to get version for $pkg from ..."
		cat $tmp.tmp
		continue
	    fi
	    ( echo $version; echo $ctlversion ) | _compare $relop
	    if [ $? = 0 ]
	    then
		$verbose && echo "$pkg: version installed $version, need $relop $ctlversion, OK"
	    else
		echo "Warning: $pkg version installed $version, need $relop $ctlversion"
	    fi
	fi
    done
fi

if which rpm >/dev/null 2>&1
then
    # RPM based, there are some version dependencies in the spec
    # file (see BuildRequires: lines build/rpm/pcp.spec.in) and some
    # are found in configure.ac ... both need to be mirrored here
    #
    cat <<End-of-File >$tmp.control
# one line per rpm
# rpm-name	relop	version	pcp-pkg
# text after # is treated as a comment
# pcp.spec.in
qt-devel|qt4-devel|libqt4-devel	>=	4.4
libpfm-devel			>=	4.4	pcp-pmda-perfevent
libpfm|libpfm4			>=	4.4	pcp-pmda-perfevent
libibmad-devel			>=	1.1.7	pcp-pmda-infiniband
libibmad|libibmad5		>=	1.1.7	pcp-pmda-infiniband
libibumad-devel			>=	1.1.7	pcp-pmda-infiniband
libibumad|libibumad3		>=	1.1.7	pcp-pmda-infiniband
End-of-File
    cat $tmp.control \
    | sed -e 's/#.*//' -e '/^[ 	]*$/d' \
    | while read rpmlist relop specversion pcp_pkg
    do
	[ -n "$pcp_pkg" ] && pcp_pkg=" for $pcp_pkg"
	rm -f $tmp.found $tmp.notfound
	for rpm in `echo "$rpmlist" | sed -e 's/|/ /g'`
	do
	    rpm -q $rpm >$tmp.tmp 2>/dev/null
	    if grep 'is not installed' $tmp.tmp >/dev/null 2>&1
	    then
		$verbose && echo "$rpm: not installed, need $relop $specversion$pcp_pkg, OK"
		echo "Warning: $rpm not installed, need $relop $specversion$pcp_pkg" >>$tmp.notfound
	    else
		touch $tmp.found
		version=`sed <$tmp.tmp -e "s/^$rpm-//" -e 's/-.*//'`
		( echo $version; echo $specversion ) | _compare $relop
		if [ $? = 0 ]
		then
		    $verbose && echo "$rpm: version installed $version, need $relop $specversion$pcp_pkg, OK"
		else
		    echo "Warning: $rpm version installed $version, need $relop $specversion$pcp_pkg"
		fi
	    fi
	done
	if [ -f $tmp.found ]
	then
	    :
	else
	    $verbose || cat $tmp.notfound
	fi
    done
fi

if which pkg-config >/dev/null 2>&1
then
    # PKG_CHECK_MODULES() in configure.ac
    #
    cat <<End-of-File >$tmp.control
# one line per rpm
# lib-name	relop	version	pcp-pkg
# text after # is treated as a comment
libmicrohttpd	>	0.9.9
cairo		>=	1.2
cairo-ft	>=	1.2
cairo-png	>=	1.2
End-of-File
    cat $tmp.control \
    | sed -e 's/#.*//' -e '/^[ 	]*$/d' \
    | while read lib relop version pcp_pkg
    do
	[ -n "$pcp_pkg" ] && pcp_pkg=" for $pcp_pkg"
	libversion=`pkg-config --modversion "$lib" 2>/dev/null`
	if [ -z "$libversion" ]
	then
	    echo "Warning: Package $lib not known to pkg-config , need $relop $version$pcp_pkg"
	else
	    ( echo $libversion; echo $version ) | _compare $relop
	    if [ $? = 0 ]
	    then
		$verbose && echo "$lib: version installed $libversion, need $relop $version$pcp_pkg, OK"
	    else
		echo "Warning: $lib version installed $libversion, need $relop $version$pcp_pkg"
	    fi
	fi
    done
fi

# Networking goo
#
_check_host()
{
    ipaddr=`sed -n </etc/hosts -e '/^#/d' -e '/::/d' -e 's/$/ /' -e "/[ 	]$1[ 	]/"'{
s/[ 	].*//
p
}'`
    if [ -z "$ipaddr" ]
    then
	echo "No /etc/hosts entry for $1"
	return
    fi

    if [ `echo "$ipaddr" | wc -l | sed -e 's/  *//g'` -gt 1 ]
    then
	echo "Multiple /etc/hosts entries for $1"
	return
    fi

    ifconfig \
    | awk '
/^[^ 	]/	{ iface = $1; next }
/inet addr:'$ipaddr' / || /inet '$ipaddr' / {
			  if (iface == "lo")
			    print "Warning: '$1' associated with loopback network interface"
			  found = 1
			  exit
			}
END		{ if (found != 1)
		    print "Warning: '$1' ('$ipaddr') not associated with a network interface"
		}'
}

host=`hostname`
_check_host $host
if which pmhostname >/dev/null 2>&1
then
    pmhost=`pmhostname`
    if [ -z "$pmhost" ]
    then
	echo "Warning: pmhostname returns nothing!"
    else
	case $pmhost
	in
	    $host|$host.*)
		    ;;
	    *)
		    echo "Warning: hostname ($host) is not a prefix of pmhostname ($pmhost)"
		    ;;
	esac
	_check_host $pmhost
    fi
fi

if [ -f /etc/pcp.conf ]
then
    . /etc/pcp.conf

    # need QA access to pmlogger via pmlc from local subnet
    #
    network=`_getnetworkaddr`
    if [ -n "$network" ]
    then
	if [ -f $PCP_VAR_DIR/config/pmlogger/config.default ]
	then
	    if grep -q "allow $network" $PCP_VAR_DIR/config/pmlogger/config.default
	    then
		:
	    else
		echo "Missing: \"allow $network : all;\" [access] in $PCP_VAR_DIR/config/pmlogger/config.default"
		echo "Use \"$ sudo -E .../qa/admin/allow-pmlc-access\" to fix this."
	    fi
	else
	    echo "Warning: \"$PCP_VAR_DIR/config/pcp/pmlogger/config.default\" is missing"
	fi
    else
	echo "Please ignore Warnings from _getnetworkaddr unless you wish to run the"
	echo "full PCP QA suite."
    fi
else
    echo "Warning: \"/etc/pcp.conf\" is missing"
fi

sudo -u pcp [ -x $HOME ] || echo "Error: $HOME is not searchable by user \"pcp\""
