#!/bin/bash

# Version 1.4
#- patches from Wade Fitzpatrick for equery and fgrep suggestions (bug 125674), 
#- patches from Joerge Plate for grep being called with too many arguments (bug 114155)
#- finally updated emerge syntax, brought up by nyhm (with some input from cab) in bug 128130.
# Version 1.3
# sort of a changelog if you want to call it that...
# grep -E '/usr/lib*/perl5/' */*/CONTENTS > to make my list :)
# version 1.2 - swtaylor gave some good pointers on making the tmp files, as well as reminding me of grep's -f functionality :)
# version 1.1 - Mr. Bones gave a lot of good input on cleaning up the script
# Version 1 - stuff

# First and foremost - make sure we have a perl to work with...
PERL=$(which perl)
if [ "${PERL}x" == "x" ]; then
   echo "NO PERL INSTALLED!! (at least not in your path)"
   exit
fi
eval $(perl '-V:version')
PERL_VERSION=${version}
gPERL_VERSION=`echo $PERL_VERSION|sed -e 's|\.|\\\.|g'`
source /sbin/functions.sh || {
        echo "$0: Could not source /sbin/functions.sh!"
        exit 1
}

TMPDIR=${TMPDIR:-/tmp}

PKGDIR=$(/usr/bin/portageq vdb_path)
DATESTAMP=$(date +"%Y%m%d%H%M%S")
LOG=$(mktemp ${TMPDIR}/perl-cleaner.log.$DATESTAMP.XXXXXXXXXX)
PAGER=${PAGER:-more}

# Set up our temporary files
MODULES_LIST=$(mktemp ${TMPDIR}/modules.list.XXXXXXXXXX)
EBUILDS_PREINSTALL=$(mktemp ${TMPDIR}/ebuilds.preinstall.XXXXXXXXXX)
EBUILDS_ORDERED=$(mktemp ${TMPDIR}/ebuilds.ordered.XXXXXXXXXX)
EBUILDS_REINSTALL=$(mktemp ${TMPDIR}/ebuilds.reinstall.XXXXXXXXXX)

function postclean {
   for FILE in ${MODULES_LIST} ${EBUILDS_PREINSTALL} ${EBUILDS_ORDERED} ${EBUILDS_REINSTALL}; do

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

   done

   if [ -s $LOG ]; then
      echo
      echo "For a complete log, please read $LOG"
      echo
   else
      if [ -f $LOG ]; then
        rm -f $LOG
      fi
   fi
}

# This is to clean out the old .ph files generated in our last perl install
function ph_clean() {
   echo ""
   echo "$(date) : Beginning a clean up of .ph files" | tee -a $LOG
   echo "Excluding perl-${PERL_VERSION} from cleaning" | tee -a $LOG

   local PV=${version}
   local gPV=`echo $PV|sed -e 's|\.|\\\.|g'`
   INC=$(perl -e 'for $line (@INC) { next if $line eq "."; next if $line =~
   m/'${PV}'/; print "$line\n" }')
   if [ "${INC}x" != "x" ]; then
   echo "Locating ph files for removal"
      for file in $(find $INC -name "*.ph" -type f 2>/dev/null); do
         if [ ! $(echo "$file"|grep $gPV) ]; then
            echo ""
            echo "$(date) : Removing old ph file: $file" | tee -a $LOG
            rm $file
         fi
      done
   fi

   # Silently remove those dirs that we just emptied
   find $INC -depth -type d 2>/dev/null | grep -v $gPV | xargs -r rmdir 2>/dev/null
}

# Generate ph files; this is useful if we've upgraded packages with headers so that perl knows the new info
function ph_update() {
   echo ""
   echo "$(date) : Updating ph files" | tee -a $LOG
   cd /usr/include; h2ph * | tee -a $LOG
   cd /usr/include; h2ph -r sys/* arpa/* netinet/* bits/* security/* asm/* gnu/* linux/* gentoo* | tee -a $LOG
   cd /usr/include/linux; h2ph * | tee -a $LOG
}


# Build a list of modules installed under older perls - only valid if the module was an ebuild :)
function module_list() {
   echo ""
   echo "$(date) : Building list of modules for reinstall" | tee -a $LOG
   echo "Locating modules for reinstall"
    for checkfile in `find $PKGDIR -maxdepth 3 -mindepth 3 -name "CONTENTS" |xargs grep -El '/usr/lib*/perl5/' `; do 
	if [ "`grep -l "${gPERL_VERSION}" $checkfile`x" = "x" ]; then
		 echo "$checkfile" >> ${MODULES_LIST} 
	fi; 
    done
}

function alternate_module_list() {
   # Takes longer to run, but identifes modules not associated with
   # an ebuild.
   #
   # Reset INC - INC is dynamically generated, and if we removed any ph 
   # files - and they were the only thing left in a dir - then there's 
   # no sense in revisiting that dir
   echo ""
   echo "$(date) : Building list of modules for reinstall" | tee -a $LOG
   INC=$(perl -e 'for $line (@INC) { next if $line eq "."; next if $line =~ m/'${PERL_VERSION}'/; print "$line\n" }')
   INSTALLED_EBUILDS=$(find $PKGDIR -name CONTENTS)
   echo "Locating modules for reinstall"
   for file in $(find $INC -iname "*.pm" \! -type f 2>/dev/null | fgrep -v "${gPERL_VERSION}"| sort -u); do
      PKG=$(for ebuild in $INSTALLED_EBUILDS; do if fgrep -l $file $ebuild; then break; fi; done)
      if [ -z "$PKG" ] ; then
         echo "Warning: $file is not owned by a currently installed ebuild"
      else
         echo "$PKG" >>${MODULES_LIST}
      fi
   done
}


# The meat of it - rebuilding the ebuilds
# ALL emerges are oneshots - we don't want to mess with the world file
# We first attempt to emerge the specific module that was installed last time
# If that fails, we attempt to install a newer version

function ebuild_rebuild() {

   echo ""
   echo "$(date) : Rebuilding modules: Building list of ebuilds" | tee -a $LOG
   if [ -s ${MODULES_LIST} ]; then
      for line in $(sort -u ${MODULES_LIST}); do
         echo "$line"|sed -e 's|.*pkg/||' -e 's|/CONTENTS||'|fgrep -v "dev-lang/perl" >>${EBUILDS_PREINSTALL}
      done
   fi

   # If they asked for interactive, let them see what will be reinstalled
   if [ -s ${EBUILDS_PREINSTALL} ]; then

      if [ ! -z $ASK ]; then
         echo "Press Enter to see the list of ebuilds we'll be evaluating"
         read key
         $PAGER ${EBUILDS_PREINSTALL}
         printf "Continue? (Y/N) "
         read ANSWER
         if [ $(echo "${ANSWER}" | grep -i -e "^n|N" ) ]; then
            echo "$(date) : USER ABORTED REBUILD">>$LOG
            exit
         fi
      fi

      for EBUILD in $(cat ${EBUILDS_PREINSTALL} ); do
# Use the esync cache if available
         if [ -x /usr/bin/esearch ]; then
            EBUILD=`echo $EBUILD | sed -e 's/-[0-9].*//'`
            INFO=$(/usr/bin/esearch --fullname --instonly --own='%p:%vi:%va:%m' $EBUILD)
            #FULLNAME=`echo $INFO | cut -d':' -f1`
            INSTALLED=`echo $INFO | cut -d':' -f2`
            AVAILABLE=`echo $INFO | cut -d':' -f3`
            MASKED=`echo $INFO | cut -d':' -f4`
            if [ ! -z "$MASKED" ]; then
               echo ""
               echo "$(date) : There are no unmasked ebuilds to satisfy $EBUILD. Skipping" | tee -a $LOG
               sleep 2
            else
               if [ "$INSTALLED" != "$AVAILABLE" -a -n "$ASK" ]; then
                  printf "${EBUILD}-${INSTALLED} is not the latest available. Use version ${AVAILABLE}? (Y/n) "
                  read ANSWER
                  if [ $(echo "${ANSWER}" | grep -i "^n" ) ]; then
                     # re-install the current version
                     echo "=${EBUILD}-${INSTALLED}" >> ${EBUILDS_ORDERED}
                  else
                     # we want the latest available
                     echo "$EBUILD" >> ${EBUILDS_ORDERED}
                     echo "$(date) : User chose to install ${EBUILD}-${AVAILABLE}" >> $LOG
                  fi
               else
                  # assume we want the latest available
                  echo "$EBUILD" >> ${EBUILDS_ORDERED}
               fi
            fi
         else
# No cache available - use the old method
         if emerge --oneshot -p "=$EBUILD"|egrep -q ".*ebuilds.*satisfy"; then
            if emerge --oneshot -p ">=$EBUILD"|egrep -q ".*ebuilds.*satisfy"; then
               echo "$(date) : There are no unmasked ebuilds to satisfy $EBUILD. Skipping" | tee -a $LOG
               sleep 2
            else
               if [ ! -z $ASK ]; then
                  printf "${EBUILD} isn't available, but a new version is. Install? (Y/N) "
                  read ANSWER
                  if [ $(echo "${ANSWER}" | egrep -e "^y|Y" ) ]; then
                     echo ">=$EBUILD" >> ${EBUILDS_ORDERED}
                     echo "$(date) : User chose to install >=${EBUILD}">>$LOG
                  fi
               else
                  echo ">=$EBUILD" >>${EBUILDS_ORDERED}
               fi
            fi
         else
            echo "=$EBUILD">>${EBUILDS_ORDERED}
         fi
         fi
      done

      if [ -s ${EBUILDS_ORDERED} ]; then
         if [ ! -z $ASK ]; then
            echo "Press Enter to see the final list of ebuilds to install"
            read key
            $PAGER ${EBUILDS_ORDERED}
            printf "Continue? (Y/N) "
            read ANSWER
            if [ $(echo "${ANSWER}" | egrep -e "^n|N" ) ]; then
               echo "$(date) : USER ABORTED REBUILD">>$LOG
               exit
            fi
         fi

         # Cut down to one line so portage can handle ordering these appropriately
         emerge -pq --oneshot $(cat ${EBUILDS_ORDERED} ) | fgrep ebuild | sed -e 's:\([^ ]\+\):=\1:g' -e 's:.*\] \([^ ]*\) .*:\1:'>>${EBUILDS_REINSTALL}

         echo ""
         echo "Reinstalling ebuilds"
         echo "$(date) : Ebuilds to reinstall: ">>$LOG
         cat ${EBUILDS_REINSTALL}>>$LOG
         echo >>$LOG

         # Now that we have them in the right order, emerge them one at a time
         # This is to avoid problems if one doesn't emerge correctly

         for EBUILD in $(cat ${EBUILDS_REINSTALL}); do
            emerge --oneshot ${EMERGE_OPTIONS} "$EBUILD"
         done
      else
         echo
         echo "Nothing to reinstall!"
         echo
      fi
   else
      echo
      echo "Nothing to reinstall!"
      echo
   fi

}

# Locate .so's and binaries linked against libperl.so
# The coup is in ! -newer libperl.so - cut out anything that was obviously installed
# after our last install of libperl, which should cut out the false positives.

function libperl_list() {
   echo ""
   echo "$(date) : Locating ebuilds linked against libperl" | tee -a $LOG
   for i in $(find $(egrep -v "^#" /etc/ld.so.conf) -type f -name '*.so*' ! -newer /usr/lib/libperl.so 2>/dev/null) \
            $(find $(echo $PATH | sed 's/:/ /g') -type f -perm +0111 ! -newer /usr/lib/libperl.so 2>/dev/null) ;
   do
      if [ -f ${i} ]; then
         if ldd ${i} 2>&1 | fgrep "libperl" - >/dev/null; then
            for file in $PKGDIR/*/*/CONTENTS; do
               fgrep -l " $i " $file
            done >>${MODULES_LIST};
         fi   
      fi
   done 

}

# Assuming a successful module run, look to see whats left over
function leftovers() {
   echo ""
   echo "$(date) : Finding left over modules" | tee -a $LOG

   echo ""
   echo "$(date) : The following files remain. These were either installed by hand" | tee -a $LOG
   echo "$(date) : or edited. This script cannot deal with them." | tee -a $LOG
   echo | tee -a $LOG


   INC=$(perl -e 'for $line (@INC) { next if $line eq "."; next if $line =~ m/'${PERL_VERSION}'/; print "$line\n" }')
   for file in $(find $INC -type f 2>/dev/null |fgrep -v  "${gPERL_VERSION}" ) ; do
      echo "$(date) : ${file}" | tee -a $LOG
   done
}

function usage() {
   echo "Usage: $0 [options] [ask]"
   printf "\tmodules - rebuild perl modules for old installs of perl\n"
   printf "\tallmodules - rebuild perl modules for any install of perl\n"
   printf "\tlibperl - rebuild anything linked against libperl\n"
   printf "\tph-clean - clean out old ph files from a previous perl\n"
   printf "\tphupdate - update existing ph files, useful after an upgrade to system parts like the kernel\n"
   printf "\tphall - clean out old ph files and run phupdate\n"
   printf "\tall - rebuild modules, libperl linkages, clean ph files, and rebuild them\n"
   printf "\treallyall - rebuild modules for any install of perl, libperl linkages, clean ph files, and rebuild them\n"
   printf "\n"
   printf "\task - ask for confirmation on each emerge"
   printf "\n\n"
   exit 0
}

if [ -z "$1" ]; then
   usage
fi

EMERGE_OPTIONS=""
ASK=""
MODULES=false
LIBPERL=false
PHCLEAN=false
PHUPDATE=false
FORCE=false
LEFTOVERS=false
while [ ! -z "$1" ] ; do
   case "$1" in
      help | --help | -h )
         usage
         ;;
      leftovers )
         LEFTOVERS=true
         shift
         ;;
      modules )
         MODULES=true
#         LEFTOVERS=true
         shift
         ;;
      allmodules )
         MODULES=true
         FORCE=true
         shift
         ;;
      libperl )
         LIBPERL=true
         shift
         ;;
      ph-clean )
         PHCLEAN=true
         shift
         ;;
      phupdate )
         PHUPDATE=true
         shift
         ;;
      phall )
         PHCLEAN=true
         PHUPDATE=true
         shift
         ;;
      all )
         MODULES=true
         LIBPERL=true
         PHCLEAN=true
         PHUPDATE=true
         LEFTOVERS=true
         shift
         ;;
      reallyall )
         MODULES=true
         LIBPERL=true
         PHCLEAN=true
         PHUPDATE=true
         FORCE=true
	shift
         ;;
      ask )
         ASK="true"
         EMERGE_OPTIONS="${EMERGE_OPTIONS} --ask"
         shift
         ;;
      force )
         FORCE=true
         shift
         ;;
      * )
         EMERGE_OPTIONS="${EMERGE_OPTIONS} $1"
         shift
         ;;
   esac
done

$FORCE && PERL_VERSION="0.0.0" && gPERL_VERSION="0\.0\.0"
$PHCLEAN && ph_clean
$PHUPDATE && ph_update
$MODULES && module_list
$LIBPERL && libperl_list
($MODULES || $LIBPERL) && ebuild_rebuild
$LEFTOVERS && leftovers

#postclean

exit
# vim:ts=3:sw=3:et
