#!/bin/sh

#
# Dasygenis Minas : Script to check that all required dynamic link libraries are present
# http://arch.ece.uowm.gr
#
#
# This script aids in locating missing libraries for dynamic linking
# When you upgrade a package, some times the old library is deleted, like
# libperl.so.10 and a new library is installed, like libperl.so.11 . In this
# case, programs that do require the libperl.so.10 will stop working [if they
# are not rebuilt]. This script locates such situations and proposes a fix
# by locating the new libraries and proposing the command to be executed.
#
#
#
# Install: 
# Put at cron a line @daily ( check_requisite_library_files.sh )
# also, you can execute it on demand. 
# Use -v for verbose mode [print dots]
#
# Execute:
# ./check_requisite_library_files.sh doit
#
# License: Creative Common 3.0 with attribution
#
#TODO: Create a manpage from /usr/share/examples/mdoc/example.1
#
# March, 2014
# November 2014: Added pkg support
# December 2016: Allowed soft links to be scanned, because some times we put an execution link on /usr/local/bin
version="017"
#
# Thank to everyone who contributes to this script:
# <dteske@FreeBSD.org>

log="/tmp/requisite_library_files.log.txt"

if [ -f $log ] ; then 
	rm $log
fi

#Originally, we believe that no missing files exist
found_missing=0

ignore_files="
/usr/local/bin/scripts/network/www/stream/images
/scripts/network/www/stream/images
"

directories="
/usr/local/bin /usr/local/sbin /usr/local/lib /usr/lib /usr/bin /usr/sbin /bin /sbin /lib
"

#Add extra path declerations...
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/samba4/private:/usr/local/bin/scripts/messages/virtualpythonSMS/bin/

# Not currently user
files_that_should_be_deleted="
/usr/src/contrib/gdb/include/ansidecl.h
/usr/local/include/ansidecl.h
"

files_that_should_be_deleted="
"

for ifile in $files_that_should_be_deleted
do
	if [ -f "$ifile"  ] ; then
		echo "***WARNING*** : File $ifile which is present on your system should be moved to /tmp [compilation problems may arise if present]"  | tee -a $log
	fi
done


#if no output is given then no end line
output_on_screen=0

#if we are going to print dots, then we need the last echo
if [ "$#" -gt 0 ] ; then
	output_on_screen=1
fi

# 
verbose=0
fix=0

#If user has specified -v, then we need dots
if [ $# -ge 1 ] ; then
	if [ "$1" = "-v" -o "$2" = "-v" ] ; then
		echo "Verbose mode"
		verbose=1
	fi

	if [ "$1" = "fix" -o "$2" = "fix" ] ; then
		if [ $verbose -eq 1 ]; then
			echo -n "FIXing mode"
		fi
		fix=1
	fi
fi







#We check one - by - one every directory
for x in $directories
do
	#if folder is absent, just skip it
	if [ ! -d "$x" ] ; then
		continue
	fi

#first lets check if we have a void link
grp="|"
for i in $ignore_files
do
	grp="$grp grep -v $i | "
done
grp="$grp cat"

void=`find $x -type l -a \! -exec test -e {} \; -print 2> /dev/null $grp `


 if [ "$void" ] ; then
	echo " " | tee -a $log
	echo "Found void links, that should be removed:" | tee -a $log
	echo $void | tee -a $log
	if [ $fix -eq 1 ] ; then
		echo -n "Removing links: " | tee -a $log
		echo -n 3;sleep 1;echo -n 2;sleep 1;echo -n 1;sleep 1;echo 0
		total=`echo $void | wc -w`
		icounter=1

		for ifile in $void
		do
			echo "[ $icounter / $total ] $ifile" | tee -a $log
			sleep 1
			rm $ifile
			icounter=`expr $icounter + 1`
		done

	note="End if fix parameter was 1"
	fi 
 fi

 #We search for files and links to files
 find $x -type f -o -type l -print | while read i
 do

 # If user has given a command line parameter, print progress
 if [ "$verbose" -eq 1 ] ; then
	echo -n "."
 fi

 #if this is not a file go to next
 if [ ! -f "$i" -a ! -h "$1" ] ; then
	continue
 fi

#Only shared files are checked
#files that end in .a are ignored, like /usr/lib/libssp_nonshared.a
#Don't process ".a" files
 if [ "$i" = "${i%.a}" ]; then
        status=$( file "$i" )
	#if this is a text or link just set status NULL which will trigger the "continue" bellow
	#from status remove the first xxxxx part ( xxxxx : yyyyy)
        #case "${status#*:}" in *text*|*"link "*) status= ;; esac
        case "${status#*:}" in *text*) status= ;; esac
 fi

 #just skip the file if it is *text* or *lin*
 [ "$status" ] || continue

 #this is a file that uses shared libraries


#To check for missing libraries, we examine the lines with => 
# [ such as libc.so.7 => /lib/libc.so.7]
# and if in this line there is no / then we have a missing library
# [ such as libXfont.so.1 => not found ]
# sometimes we have some ldd output error [such as not dynamic, which
# we mute it by redirecting it to /dev/null
missing=$( ldd "$i" 2>/dev/null | awk '/=/ && $0!~"/" {print $1}'  | grep -v 'linux-vdso.so.1' )

#Note, on previous line we want the full missing filename, and not 
#just the "not found" line which would be printed if we used -f'%p\n'



if [ "$missing" ]; then
	#Ooops..we just found a missing library. Lift the flag.
	#WARNING: This is inside a | while.... so it wont be transfered to main
	found_missing=1
	#we are going to print smt on the screen. So, the script outputs info.
	output_on_screen=1
	echo " " | tee -a $log
	echo "WARNING: Missing library(-ies) for app: $i" | tee -a $log
	echo "--------------------------------------------------------------------" | tee -a $log
	if [ -x /usr/sbin/pkg ] ; then
		note="FreeBSD System"
		pkg which $i | tee -a $log
		pkg which $i > /dev/null 2>&1
		if [ $? -eq 0 ] ; then
	 	 echo "You can force reinstall the package that provided [$i] by issuing: pkg install -f `pkg which $i | awk '{print $NF}'`"
		fi
	fi

	if [ -x /usr/bin/dpkg ] ; then
		note="Linux System"
		origin=`dpkg -S $i| cut -f1 -d ":"` | tee -a $log
		echo "This was installed by: $origin " | tee -a $log
		echo  "You can do a: apt-get install --reinstall $origin " | tee -a $log
	fi
	echo "=====================================================================" | tee -a $log
	#lets cut out the version string:libavresample.so.1.2.0=>libavresample.so
	basedll=`echo $i | sed -E 's/^(.*\.so).*$/\1/g'`
	#lets print all similar dlls
	if [ ! -z "${basedll}" ] ; then 
		ls -l ${basedll}*
	fi

	echo "|+++++++++++++   MISSING LIBRARES   +++++++++++++++++++++++++++++++++|" | tee -a $log
	#For file $i, we are going to grab the missing libraries at $j
	echo "$missing" | while read j
	do
		#extract the basename of the missing file
		#it deletes the longest match of * from the start to last /
		missingfile="${j##*/}"
		msg="WARNING: Missing Library for file: [$i] - Library name: [$j]"
		logger $msg
		echo $msg | tee -a $log
		echo "------->"$missingfile | tee -a $log

		#now $missingfile holds the name with dots, for example:libgcrypt.so.18
		#we want to remove the version digits
		#

		#lets try to find similar
		#fix: -f 1-2 assumes name.type.version (2 dots), so we want
		#name.type and scrap version
		#

		#
		#Construct the base, by scraping out the version. We now have library.so
		base="${missingfile%%[$IFS]*}"
		#delete the .so* from the end, and put the .so. Thus, we constructed our base
		base="${base%.so*}.so"	

		counter_of_suggestions=0
		allsimilarfiles=$( locate "$base" | awk '!/(lib32|compat)/{print}' )
		for similarfile in $allsimilarfiles
		do
		
		#if we do have a suggestion
		if [ ! -z "$similarfile" ] ; then
			similarbase="${similarfile%/*}"
		
			# if file still exists then it is valid choice
			if [ -f "$similarfile" ] ; then
				#Sometimes the lib file is located at the folder that is not picked up by ldconfig
				
				#so a suggestion: "ln -s /otherlib/library.so.10 /otherlib/library.so.10" is not valid
				missinglib=$j
				counter_of_suggestions=$(( $counter_of_suggestions + 1 ))
				if [  "$similarfile" != "${similarbase}/${missinglib}" ]; then 
					echo "MaYbe${counter_of_suggestions}a: ln -s $similarfile ${similarbase}/${missinglib}" | tee -a $log
				else
					echo "MayBe${counter_of_suggestions}b: ln -s $similarfile ${similarbase}/${missinglib} --NOT SUGGESTED, seems ${similarbase}  not in ldconfig path" | tee -a $log
				fi
				

				# if simlarbase is not /lib /usr/local/lib /usr/lib, then
				# the ldd may have trouble locating. In this case, do add
				# a secondary choise

				if [  "$similarbase" != "/lib" -a ! "$similarbase" != "/usr/lib" -a  ! "$similarbase" != "/usr/local/lib" ] ; then
					counter_of_suggestions=$(( $counter_of_suggestions + 1 ))
					echo "MaybE${counter_of_suggestions}: ln -s $similarfile /usr/local/lib/${missinglib}"  | tee -a $log
				fi
			#end if the file exists
			fi

		#if we do have a suggestion
		else
			echo "Sorry, no suggestions" | tee -a $log
		#end if we do have a suggestion
		fi


	done


	if [ -x /usr/bin/dpkg ]; then
		basemissing=`echo $j | cut -f 1 -d "."`
		data=`apt-cache search $basemissing`
		if [ ! -z "$data" ] ; then
			echo "----Possible Suggestions for $basemissing ----" | tee -a $log
			apt-cache search $basemissing
			echo "---- -------------------------------------- ----" | tee -a $log
		fi
	fi
	done
	note="we are done for every missing library"
	echo "---------- END FOR THIS FILE ---------------------------" | tee -a $log
	echo " " | tee -a $log
fi

done
note="we are done for every file in that folder"

done
note="we are done for every folder"


# print the last carriage-return for new line
if [ ! "$output_on_screen" = "0" ] ; then
	if [ $verbose -eq 1 ]; then
		echo " "
	fi
fi


note="Have we raised the found_missing flag?"
if [ "$found_missing" -eq 1 ] ; then
	echo " "
	echo "==> Please check log: $log"
	echo " "
	echo "Notice: A missing library was detected at this system during dasygenis automatic l-check. More details at the logfile." | tee -a $log | wall -g wheel
fi


exit
	


