#!/bin/bash
#
#core-functions: version 1.0
#
#a script containing some useful functions
#
#usage:
#
#--------------------------------------------------------------


#--------------------------------------------------------------


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#		1 - FILE MANAGEMENT SECTION		      #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#
#	the following functions are used to check type and existance
#	of tgz's and to perform special remotion of files
#

#--------------------------------------------------------------
#some functions for file management
#--------------------------------------------------------------

function removedep ()
#a function to delete all database files related to a tgz
#$1 is the tgz name
{
rm $LOGDIR/$1 && [ -f $MISDIR/$1 ] && rm $MISDIR/$1
}

#--------------------------------------------------------------
#some functions to check tgz existance and type
#--------------------------------------------------------------

function noarchTest ()
#this function check a packege name ($1) testing if it is a "noarch" tgz
#the standard format is assumed: name1-...-nameN-version-arch-build.tgz
{
NOARCHTEST=`echo $1 | awk -F - '{printf $(NF-1)}'`
[ "$NOARCHTEST" = "noarch" ] && {
	echo -e "\n$1\nis a \"noarch\" package, it hasn't any dependency and can't be detected as dependency";
	return 1;
}
return 0;
}


function checkName ()
#this function performs some tests to the arglist
{
#check the arglist: if no file is passed we exit showing an help message
[ -z "$1" ] && exit 1

#else...
#check if it is not a known tgz
[ ! -f $1 ] && { echo -e "\n$1 not found..."; return 1; }

#if the tgz is a "noarch" package there aren't ELF files inside...
noarchTest $1
[ $? -eq 1 ] && return 2

return 0;
}



function normalizeName () {
#
# the following piece of code requires a bit of explanation:
# tracepkg from v.1.1.0 uses 3 different kinds of name formats:
# MODE0= used if a install/upgrade task is executed
# MODE1 and MODE2 = used otherwise
#
# MODE0 requires a full file name specification (including the path -global or local)
# because it is mandatory to identify uniquely the tgz
#
# MODE1 requires the full path to the package database
# eg: /var/log/packages/namefield1-...-namefieldN-version-arch-build
#
# MODE2 requires just the package name
#
# if we are performing some tasks different from install/upgrade, we
# have to check if the provided file name is MODE1 or MODE2!
# this operation is performed by the normalizeName function
#
# note that if more that 1 version of a package is installed (eg. 2 glibc versions)
# MODE1 will pass all the installed versions. moreover, if 2 packages share the
# same string/regex provided as input they are both selected (eg. gimp vs gimp-print)
#
OUTPUT=""		#list of all retrieved tgzs
SEARCHTHIS=""	#empty regex

TGZ_=`ls $PKGDIR`

for uFILE in $@; do
	NAME=`basename $uFILE .tgz`
	[ "$uFILE" == "$NAME" ] && {
	#if the basename matches the input this means that we have to search for the full name!
	#actually we don't search it but we build a regex for grep
		SEARCHTHIS="$SEARCHTHIS$NAME\|"
	} || {
	#else we can return our input
		OUTPUT="$OUTPUT $uFILE"
	}
done
#clean our regex... mail me any better idea!
[ -n "$SEARCHTHIS" ] && {
	SEARCHTHIS=${SEARCHTHIS::$((${#SEARCHTHIS}-2))}

#echo searchthis is: $SEARCHTHIS

	#NOW we search all the required files
	CANDIDATES_=`echo $TGZ_ | sed -e 's| |\n|g' | grep -i "$SEARCHTHIS"`

	#echo $CANDIDATES_
	for BNAME in $CANDIDATES_; do
		#we store them with the FULL path
		OUTPUT="$OUTPUT $PKGDIR/$BNAME"
	done
}

echo $OUTPUT

#I have not implemented a sort command... there should not be any need...
}

#--------------------------------------------------------------


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#		2 - USER I/O SECTION			      #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#
#	the following functions are used to request user input
#	or to show some special prompts
#

#--------------------------------------------------------------
#some function to request user choice
#--------------------------------------------------------------

function ask_yesno ()
#YES_NO is a global value
#ask user waiting for a predefined answer: 'yes' or 'no'
#$1 is the question string
{
YES_NO="unk"
while [ ! "$YES_NO" = "yes" ] && [ ! "$YES_NO" = "no" ]; do
	read -p "$1" YES_NO
done
}


function ask_yesnoall ()
#YES_NO is a global value
#ask user waiting for a predefined answer: 'yes', 'no' or 'all'
#$1 is the question string
{
YES_NO="unk"
while [ ! "$YES_NO" = "yes" ] && [ ! "$YES_NO" = "no" ] && [ ! "$YES_NO" = "all" ]; do
	read -p "$1" YES_NO;
done
}

#--------------------------------------------------------------
#some prompt functions
#--------------------------------------------------------------

function hilightDependant ()
#a function to make the list of dependant file _REALLY_ visible! :D
#$1 is the tgz which is dependency for some other tgz's
#$2-$n is the list of those tgz's dependant on $1
{
echo ""
echo "############################################################################################"
echo -e "  --> the following packages depend on $(basename $1 .tgz):"
echo ${@:2} | sed -e 's/ /\n/g'
echo "############################################################################################"
echo ""
}


function rootOnly ()
#just echo a warning message and exit if uid != 0 (if you are not root)
{
echo "* you must be logged as root to perform this operation"
exit 1
}


function usage ()
#do you guess what this function does? :)
{
cat << USAGE

usage:
tracepkg [--sync | --resync | -V] | [[--remove | --remove-root | --install | --install-new | --reinstall | --build | -s | -d | -m | -v] packagename] | [--upgrade [oldpackagename%]packagename ]

*'man tracepkg' for detailed info*

USAGE
}

#--------------------------------------------------------------


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#		3 - DEPENDENCY SCAN/EDIT SECTION	      #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#
#	the following functions are used to check dependency files
#	and to modify them when required (usally after an installation/
#	remotion/upgrade)
#

#--------------------------------------------------------------
#some functions to scan/edit the database
#--------------------------------------------------------------

function checkList ()
#this function scan the db retriving tgz's dependant on $1
#$1 is an installed tgz
{
#for jj in /$LOGDIR/*; do
#	FILENAME_=`grep -wl $1 $jj`
#	if [ ! -z $FILENAME_ ]; then
#		PKGNAME="/$PKGDIR/"`basename $FILENAME_ .tgz`
#		echo $PKGNAME
#		(( CHECK++ ))
#	fi
#done

FILELIST__=(`grep -wl $(basename $1 .tgz) $LOGDIR/*`)

FILELIST__=(${FILELIST__[@]/`basename $LOGDIR`/`basename $PKGDIR`})
echo ${FILELIST__[@]} | sed -e 's/ /\n/g'

return ${#FILELIST__[@]}
}


function updateData ()
#have a look to missing dependencies: if a missing dependency database file contains
#a file name that is stored into a new installed tgz's...
#well, probably we have found and "resolved" a new dependency.
#$@ is the list of all new installed tgz's
{
#
#	now let me explain the following:
#	if we have a missing dependency, ldd can only store its name
#	but not its full path. it appens that some files have the
#	same name but different paths: they are shipped by different tgz's.
#
#	For example:
#	some ssl files are shipped with acrobat readed tgz too...
#
#	hey, what the hell are you saying? :)
#
#	I'm trying to explain you that is not sufficient to know
#	if the name of a missing dependency is stored into the new installed tgz: we must know it's full path!!!
#	Shortly we must re-run the builddb script for all tgz's with a
#	missing dependency list containing the name of a file avaible into the new tgz.
#	only in this way we can know if the new file has been
#	stored into the location where the ld searches for...
#
#of course this is probably a really slow way... I'm thinking to modularize the builddb file
#so that we can call only the required functions and not the full script... can it be useful?!?!

for DEPFILE in $MISDIR/*; do
#for each missing dependency stored into the current file
	grep -m 1 -w -f $DEPFILE $@ > /dev/null
	[ $? -eq 0 ] && {
	#if we have found at least one match into one new installed tgz
	#we rebuild the db for the old tgz related to the current file
		DFNAME=`basename $DEPFILE tgz`
		PKGNAME=$PKGDIR/$DFNAME
		echo "  --> building $DFNAME"

		$INSTDIR/builddb $PKGNAME > /dev/null
#		break;	#we break coz is not useful to now if we have one or more new tgz's matching dependencies...
	}
done
}


function rebuildData ()
#rebuild dependency informations for files dependant on removed/upgraded tgz's
#$1 is the list of packages whose dependency list is to be updated
#$2 is a swap file
#
#	not really performat...
#
{
FILELIST=(`echo $@ | sort | uniq`)

for FILE in ${FILELIST[@]}; do
	if [ -f $FILE ]; then
	#if we haven't removed also a file dependant on a given tgz
	#or if we haven't upgraded also a file dependant on a given tgz
		echo "  --> building `basename $FILE .tgz`"
		$INSTDIR/builddb $FILE > /dev/null
	fi
done
}

#--------------------------------------------------------------


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#		4 - DEPENDENCY TRACING SECTION		      #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#
#	the following functions are used as auxiliary stuff
#	during the tracing of the whole dependency tree of
#	a given tgz.
#

#--------------------------------------------------------------
#some functions used to get binary dependencies of a tgz:
#they are obtained from the tgz log file
#--------------------------------------------------------------

function fillFileArray ()
#this function scans the list of file contained into a tgz
#extracting only binary executables or libraries compliant to the ELF format
#$1 is the name of file containing the list of contents of a tgz:
#e.g. /var/log/packages/aaa_elflibs-10.2.0-i486-3
#FILELIST is an external value
{

#retrive all and only files stored into /lib/,/bin/,/sbin/,/libexec/ paths
SPACEFIX=;	#used to prevent that a filename with spaces inside will be truncated in two or more files
TEMPFILELIST=(`sed -n -e "1,/^FILE\ LIST:/!{/\/$/!{s/ /$SPACEFIX/g; /\/bin\/\|\/lib\/\|\/sbin\/\|\/libexec\/\|^lib\/\|^bin\/\|^sbin\//p}}" $1`)

#clean data removing postponed .new extension (if found)
TEMPFILELIST=(${TEMPFILELIST[@]/%.new})

#check for existance of each file. is a file doesn't exist this means that it has been renamed and/or moved
for (( i=0; i<${#TEMPFILELIST[@]}; i++ )); do
	#remove all spacefies (this slow down the whole activity if we don't add a check to selectively
	#apply this code only when required... for now just use this)
	TEMPFILELIST[$i]="`echo ${TEMPFILELIST[$i]} | sed -e "s/$SPACEFIX/ /g"`"

	[[ -f "/""${TEMPFILELIST[$i]}" || -b "/""${TEMPFILELIST[$i]}" || -c "/""${TEMPFILELIST[$i]}" || \
	-p "/""${TEMPFILELIST[$i]}" ]] && \
		TEMPFILELIST[$i]="/""${TEMPFILELIST[$i]}" || \
		{ echo "  --> "/""${TEMPFILELIST[$i]}" NOT FOUND <--"; TEMPFILELIST[$i]="";  }
done

#store all and only binaries and libraries ELF compliant
[ ! ${#TEMPFILELIST[@]} -eq 0 ] && \
FILELIST=(`echo ${TEMPFILELIST[@]} | xargs file | grep -w "ELF $BIT-bit $ENC shared object\|ELF $BIT-bit $ENC executable" | \
	awk -F : '{printf $1"\n"}'`)
}


function grabException ()
#this function perform a check to the tgz name.
#
#	there are some tgz "badly managed" by the common routine: bash, glibc-*, aaa_elflibs.
#	if one of this is scanned we skip the common routine defined by the function 'fillFileArray'
#	forcing the content of the external value FILELIST (and the related external value J)
#
#	to use a predefined function is not a really good idea:
#	any change into the package definition = a change into this script... mmm...
#
#$1 is the name of file containing the list of contents of a tgz:
#e.g. /var/log/packages/aaa_elflibs-10.2.0-i486-3
{
EXCEPTIONNAME=$1
[ "${EXCEPTIONNAME:0:4}" = "bash" ] && {
	FILELIST[0]="/bin/bash"
	(( j++ ))
	return 1
}

[ "${EXCEPTIONNAME:0:5}" = "glibc" ] || [ "${EXCEPTIONNAME:0:11}" = "aaa_elflibs" ] && {
	echo "No installed dependencies found for $1";
	exit 0
}

return 0
}


function searchDep ()
#retrive an installed binary dependency ($1) of a tgz ($2).
#the search is performed via the 'grep' command, using the options passed via the third argument ($3). By design:
#
#	$3   must be a string containing all and only options avaible for the version
#	     of the 'grep' utility currently installed into the system (see man grep(1)).
#
#thanx to DaNiMoTh for inspiration :)
#and to krisi for the many "lol's" referred to the time used for the db creation :)
#your lol's haz been a cornerstone for the tuning of the script :P
#
#	still buggy... what's about a renamed or a moved+renamed file?
#	something like bash.new into the bash package???
#
#results are stored into an external value called TDEPFILES
{
DEP=$1;
LDEPLIST="";

#if it's a symbolic link we dereference it
[ -h $DEP ] && DEP=$(readlink -f $DEP)

#remove prepended "/" character
DEP=${DEP:1}

#search inside the given tgz as first... we detect what I call auto-dependency...
grep "$3" $DEP $2 > /dev/null
[ ! $? -eq 0 ] && {
#if we have not found an auto-dependency... well, let search for a
#real dependency into all other packages (we include $2 again coz this is faster)
#	grep "$5" $DEP $PKGDIR/* >> $3
	LDEPLIST="$LDEPLIST $(grep "$3" $DEP $PKGDIR/*)"
	[ ! $? -eq 0 ] && {
		#if we didn't find the full path we search for the file name only...
		#this is useful if the file has been moved during installation
		DEPMOVED=`basename $DEP`;

#		echo "* performing alternative search for $DEP" >$3

		#if we didn't find files probably they are part of the glibc-* and aaa_elflibs* packages.
		#infact their files are "not well" detected by ldd
		#there's also a bad python package... and this is not so nice:
		#again: every change in the tgz=a change in the script...
# 		[ -e $PKGDIR/python-[0-9]* ] && grep "$5" $DEPMOVED $PKGDIR/{glibc-*,aaa_elflibs*,python-[0-9]*} >> $3 \
# 		|| grep "$5" $DEPMOVED $PKGDIR/{glibc-*,aaa_elflibs*} >> $3
		[ -f $PKGDIR/python-[0-9]* ] && \
			LDEPLIST="$LDEPLIST $(grep "$3" $DEPMOVED $PKGDIR/{glibc-*,aaa_elflibs*,python-[0-9]*})" \
		|| \
			LDEPLIST="$LDEPLIST $(grep "$3" $DEPMOVED $PKGDIR/{glibc-*,aaa_elflibs*})"
		[ ! $? -eq 0 ] && {

			#if we didn't find files again let search into the given package ($2)...
			#never say never :)
 			grep "$3" $DEPMOVED $2 > /dev/null
			[ ! $? -eq 0 ] && {
			#if we didn't find files we need a new solution...
			#let me think. For now enjoy the lack of perfection ;)
				echo -e "----------------------------------------------------------------------\n\
$DEPMOVED not found (trace)\n\
----------------------------------------------------------------------"
				return 1;	#file not found...
			}
 			return 1;	#autodependency...
		}
		TDEPFILES="$TDEPFILES $LDEPLIST"
		return 0;	#dependency...
	}
	TDEPFILES="$TDEPFILES $LDEPLIST"
	return 0;	#dependency...
}
return 1;	#autodependency...
}

#--------------------------------------------------------------
#some functions used to retrive tgz dependencies of a tgz
#--------------------------------------------------------------

function retriveRoutine ()
#retrive dependency tree of a given package ($1)
#it uses $@:2 as cache (here all non scanned packages are stored)
{
index=0;
NOTSCANNED_=(${@:2})
SCANNED_=("$1") #init the while cycle...

while [ $index -lt ${#SCANNED_[@]} ]; do
	DEPFILE=$LOGDIR/`basename ${SCANNED_[(($index))]} .tgz`
	echo ${NOTSCANNED_[@]} | grep $DEPFILE > /dev/null
	[ $? -eq 0 ] && {
		#if the given $FILE has not been previously scanned
		#we scan it now an we remove it from the list of not scanned files
		NOTSCANNED_=(${NOTSCANNED_[@]/$DEPFILE})
		SCANNED_=(${SCANNED_[@]} `cat $DEPFILE`)
	}
	(( index++ ));
done

echo "${SCANNED_[@]/$1}%${NOTSCANNED_[@]}"
}


function computeShared ()
#compute the number of shared dependencies of a tgz
#SHARED is a global value
#
#this function is used by (and only by) the following loopShared function
{
NUMOFSHAREDS=0;
for ((i=0; i<${#SHARED[@]}; i++)); do
	NUMOFSHAREDS=$(($NUMOFSHAREDS+${SHARED[i]}));
done

return $NUMOFSHARED
}


#if we have shared dependencies, also their dependencies are shared...
# shortly, have a look if some unshared dependencies belong to some shared dependencies
# becoming theirselves shared (really tricky I know...)
#this function does this for us
function loopShared ()
#SHARED is a global value
{
#compute the number of shareds at step 0
computeShared
SHTGZK=$?
SHTGZK_LESS_1=$(($SHTGZK-1)) #init the value so that it's different to SHTGZK

#convergence should be sure! no more then 2-steps... (just intuition not math :P)
while [ ! $SHTGZK_LESS_1 -eq $SHTGZK ]; do
	k=0
	for FILE in $@; do
		if [ ${SHARED[$k]} -eq 1 ]; then DEPFILE=`basename $FILE .tgz`

			#check for unshareds belonging to shareds...
			for ((j=0; j<$k; j++)); do
				[ ${SHARED[$j]} -eq 0 ] && {
					grep ${@:$(($j+1)):1} $LOGDIR/$DEPFILE &> /dev/null
					[ $? -eq 0 ] && SHARED[$j]=1
				}
			done
			#again...
			for ((j=$(($k+1)); j<${#@}; j++)); do
				[ ${SHARED[$j]} -eq 0 ] && {
					grep ${@:$(($j+1)):1} $LOGDIR/$DEPFILE &> /dev/null
					[ $? -eq 0 ] && SHARED[$j]=1
				}
			done
		fi
		(( k++ ))
	done

	#update values
	SHTGZK_LESS_1=$SHTGZK
	#compute the number of shareds at step k
	computeShared
	SHTGZK=$?
done
}


#--------------------------------------------------------------


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#		5 - WRAPPER SECTION			      #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#
#	some functions to loop action against the whole arglist
#

function buildFile ()
#rebuild the db for each given tgz
{
FILENAMES=`normalizeName $@`

for NAME in $FILENAMES; do
#check if the file requires a depencency list
	checkName $NAME
	[ $? -eq 0 ] && $INSTDIR/builddb $NAME
done
}


function dummyBuild ()
#rebuild the db for each given tgz
{
FILENAMES=`normalizeName $@`

for NAME in $FILENAMES; do
#check if the file requires a depencency list
        #NAME="$PKGDIR/"`basename $i .tgz`
	checkName $NAME
	[ $? -eq 0 ] && $INSTDIR/dummydb $NAME
done
}


function removeFromRoot ()
#remove from root (see 'removeroot') each given tgz
{
FILENAMES=`normalizeName $@`
for NAME in $FILENAMES; do
	checkName $NAME
	[ $? -eq 0 ] && $INSTDIR/removeroot $NAME
#	shift 1
done
}


function scanDatabase ()
#retrive all tgz dependant on the given argument
{
FILENAMES=`normalizeName $@`

for NAME in $FILENAMES; do
	#NAME="$PKGDIR/"`basename $1 .tgz`
	checkName $NAME
	[ $? -eq 0 ] && $INSTDIR/scandb $NAME
#	shift 1
done
}


function getInstalledDependencies ()
#retrive installed dependencies (really original name :) for a given tgz
{
FILENAMES=`normalizeName $@`
for NAME in $FILENAMES; do
#	NAME="$PKGDIR/"`basename $1 .tgz`
	checkName $NAME
	[ $? -eq 0 ] && $INSTDIR/retrivedb $NAME
#	shift 1
done
}


function getMissingDependencies ()
#retrive missing dependencies (again, really original name :) for a given tgz
{
FILENAMES=`normalizeName $@`
for NAME in $FILENAMES; do
#	NAME="$PKGDIR/"`basename $1 .tgz`
	checkName $NAME
	[ $? -eq 0 ] && $INSTDIR/retrivems $NAME
#	shift 1
done
}

#--------------------------------------------------------------


#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#		? - MISCELLANEA SECTION			      #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#
#	the following functions have not been written for specific
#	purpouses or need to be catalogated but are really usefull
#


function checkenv ()
#check environment searching all required dependencies
#if all files are found an hidden file is stored into /var/log and named tracepkg:
#/var/log/.tracepkg
{
#release condidate stuff. this is just garbage to be removed in v.1.0
[ -f /var/log/.tracepkg ] && rm -f /var/log/.tracepkg &> /dev/null
[ -f /var/log/.tracepkgrc2 ] && rm -f /var/log/.tracepkgrc2 &> /dev/null
[ -f /var/log/.tracepkgrc3 ] && rm -f /var/log/.tracepkgrc3 &> /dev/null

[ ! `which awk` ] && {
	echo "  --> please install gawk package"
	exit 0
	}
}



function goodkill ()
{
echo
echo "WARNING: tracepkg interrupted."
echo "WARNING: this may lead to some incomplete operations if a long option was in use (see man tracepkg), including:"
echo "WARNING: 1- I/O operations on files stored in /var/log/{dependencies,missing};"
echo "WARNING: 2- common troubles caused by interruption of pkgtools execution."
echo

rm /var/run/tracepkg.pid 2> /dev/null

INTERRUPTED=1;
}

trap goodkill 1 2 9 15