#!/bin/bash

#---------------------------------------------------------------------
## This script should clean up the sorcery files, particularly
## the depends and packages files. It should validate the files 
## remove corrupted lines, and ensure the information all agrees
## with itself. It is also to clean out unused lines and files.
##
## @Copyright Copyright 2004 by Paul Mahon for Source Mage
## @Licence Relased under the GNU GPL version 2
##
#---------------------------------------------------------------------

#---------------------------------------------------------------------
## This software is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This software is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this software; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##
#---------------------------------------------------------------------

#--------------------
## Gives the usage of this script
## @param A switch that was not understood (optional)
## @Stdout Usage statement
#--------------------
function usage()
{
	local s=$(basename $0)
cat << EOF
Invoke $s with one of the arguments below. 

Command		Description
--delint	Finds stale dependency entries
--delint full	will delint dependencies that are off
--prune		Finds dispeled spells that should be cast
--prune doit	will cause it to actually cast and dispel
--packages	find bad entries in the installed spells file

--fix		attempts to detect errors in cast spells and recasts broken ones
--nofix		attempts to detect errors in cast spells but does not recast
--nofix_quick	like --nofix, but does not check dependent spells.

--tablet	clean up the tablet
--tablet_spell	clean up the tablet for one spell
--logs		cleans old logs in the same way a system-update does

--sweep		Clean out old downloaded sources and cached binaries
		old is defined as not capable of being used by any
		spell/version in any grimoire on the system.

--sweep_all	Clean out old downloaded sources and cached binaries
		old is defined as not capable of being used by any spell
		installed on the system. Only the first version of the
		spell found is used.

It is suggested that you try the following order the first time:
	1) $s --packages
	2) $s --delint
	3) $s --tablet
	4) $s --logs
	5) $s --prune
	6) $s --sweep
	7) $s --fix
EOF
	[[ $# -gt 0 ]] && echo "Unknown $*"
}


#---------------------
## Interpret the arguments to this script
## @Args  --help            Display help
## @Args  --delint [full]   Find stale dependency entries, with full it'll
##                          look at off entries too
## @Args  --prune [doit]    Find missing spells, do the actions of "doit" is
##                          specified
## @Args  --packages        Find packages entries that are corrupted, or
##                          non-existant
## @Args  --tablet          Clean up the tablet
## @args  --tablet_spell    Clean up the tablet for one spell
## @Args  --logs            Remove stale logs
## @Args  --sweep           Remove uneeded spool and cache files
## @Args  --sweep_all       Remove uneeded spool and cache files
## @Args  --fix             Find binaries that are missing libs and files,
##                          and recast broken spells
## @Args  --nofix           Same as --fix, but won't recast.
## @Args  --nofix_quick     Same as --nofix, but doesn't check dependent spells
#---------------------
function args()
{
  local rc
  let rc=0
  while [ $# -gt 0 ] ; do
    arg=$1
    shift
  
    case $arg in
      --help)    usage         ;;
      --delint)  
        if [[ $1 == full ]] ; then
          delint full
          let rc+=$?
          shift
        else
          delint
          let rc+=$?
        fi
        ;;
      --prune)
        if [[ $1 == doit ]] ; then
          prune_depends doit
          let rc+=$?
          shift
        else
          prune_depends
          let rc+=$?
        fi
        ;;
      --logs) clean_logs
        let rc+=$?
        ;;
      --fix) cleanse_fix fix "$*"
        let rc+=$?
        shift $#
        ;;
      --nofix) cleanse_fix nofix "$*"
        let rc+=$?
        shift $#
        ;;
      --nofix_quick) cleanse_fix quick "$*"
        let rc+=$?
        shift $#
        ;;
      --sweep) prune
        let rc+=$?
        ;;
      --sweep_all) prune installed_only
        let rc+=$?
        ;;
      --packages) packages
        let rc+=$?
        ;;
      --tablet)
        tablet_cleanse_tablet $TABLET_PATH $BACKUPDIR
        let rc+=$?
        if [[ $1 == coalesce ]] ; then
          tablet_coalesce_files $TABLET_PATH
          let rc+=$?
          shift
        fi
        ;;
      --tablet_spell)
        tablet_cleanse_chapter $TABLET_PATH/$1 $BACKUPDIR
        let rc+=$?
        shift
        ;;
      *)  usage $arg $* ; return 1  ;;
    esac
  done
  return ${rc:-0}
}

#-------------------
## Find stale dependency entries, with full it'll look at off entries too
## @param full  if set, looks at off dependencies as well as on
## @Stdout Misc output and questions
## @Stdin Answers to questions
#-------------------
function delint()
{
  message "Delinting..."
  local cond='$3=="on"'
  local options=()
  local choice t lines regex
  if [[ $1 == full ]] ; then
    cond=''
  fi
  
  lock_file $DEPENDS_STATUS
  
  message "Pass one (malformed depends lines)"
  # This grep will pull out malformed lines from the depends file
  regex='^[^:]*:[^:]*:(on|off):(optional|required):'
  lines=$(grep -v -E "$regex" $DEPENDS_STATUS)
  if [[ $lines ]] ; then
    message "The following malformed lines were found"
    message "and will cause problems with the following passes:"
    message "$lines"
    if query "Do you want them removed" "n" ; then  
      message "Removing lines..."
      grep -E "$regex" $DEPENDS_STATUS > $TMP_DIR/delint.1
      mv $TMP_DIR/delint.1 $DEPENDS_STATUS
      t=$(date +%Y%m%d%s)
      message "Saving bad lines to $BACKUPDIR/depends.malformed.$t"
      echo "$lines" > $BACKUPDIR/depends.malformed.$t
      message "done."
    else
      message "Not removing malformed lines, quitting."
      return 1
    fi
  fi
  
  generic_remove_stale "$DEPENDS_STATUS" depends two

  message "Pass three (duplicate depends entries)"
  for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1:\2/p' $DEPENDS_STATUS | sed 's/+/\\+/' | sort | uniq -d ) ; do
    message
    options=()
    eval $(awk '/^'$LINE':/{ 
      if(!ENTRIES[$0]) {  
        printf("options[%d]=\"%s\"\n", count++, $0); ENTRIES[$0]=1;
      } }' $DEPENDS_STATUS)
    if [ ${#options[@]} -gt 1 ] ; then
      select_list choice 0 "${options[@]}"
      message "Removing other entries and keeping selected entry"
    else
      choice="${options[0]}"
      message "Identical lines removed: $choice"
    fi
    grep -Ev "^$LINE:" $DEPENDS_STATUS > $TMP_DIR/delint.3
    echo "$choice" >> $TMP_DIR/delint.3
    mv $TMP_DIR/delint.3 $DEPENDS_STATUS
  done

  unlock_file $DEPENDS_STATUS

  touch $TRIGGER_LIST
  lock_file $TRIGGER_LIST
  generic_remove_duplicate "$TRIGGER_LIST" trigger four
  generic_remove_stale "$TRIGGER_LIST" trigger five
  unlock_file $TRIGGER_LIST

  message "Delint done."
}

function generic_remove_duplicate() {
  local LINE
  local tmp=$TMP_DIR/delint.$2.dup
  message "Pass $3 (duplicate $2 entries)"
  for LINE in $(sort $1 | uniq -d ) ; do
    if test $(grep -E "^$LINE$" "$1"|wc -l) -gt 1; then
      message "Duplicated line removed: $LINE"
      grep -Ev "^$LINE$" $1 > $tmp
      echo "$LINE" >> $tmp
      mv $tmp $1
    fi
  done
}

function generic_remove_stale() {
  message "Pass $3 (stale $2 entries)"
  local tmp=$TMP_DIR/delint.$2.stale
  local t
  awk -F: '
    BEGIN { 
      while (getline < "'$SPELL_STATUS'") 
        Installed[$1] = 1; 
    } 
    { 
      if ( ! Installed[$1]) 
        print $0;  
    }' "$1" |tee $tmp
  t=$(wc -l $tmp | awk '{print $1;}')
  message "\t$t bad lines, $(( $(wc -l $1| cut -f1 -d' ') - t))" \
          "good lines in your $2 file."
  if [[ $t -gt 0 ]] ; then
    t=$(date +%Y%m%d%s)
    message "\tSaving bad lines to $BACKUPDIR/$2.stale.$t"
    cp $tmp $BACKUPDIR/$2.stale.$t
    message "\tRemoving bad entries"
    cat $1 >> $tmp
    sort $tmp | uniq -u > $tmp.2
    cp $tmp.2 $1
  fi
}

#---------------------
## Find missing spells, do the actions of "doit" is specified
## @param  if set to "doit" it'll actual cast and dispel
## @Stdout  questions about what to do about problems
## @Stdin  responses to queries
#---------------------
function prune_depends()
{
  message "Pruning..."
  local action=""
  local LINE DISPEL_LIST CAST_LIST
  local choice options provider spell
  
  # actualy do the actions, or just output what should be done
  if [[ $1 == doit ]] ; then
    action="doit"
  fi
  
  # Lock the depends file so no one messes with it while it's being worked on
  lock_file $DEPENDS_STATUS  
  
  # Get a list of bad dependencies
  # Output is in the form "SPELL DEPENDENCY optional/reqired PROVIDER"
  bad=( $( awk -F: '
    BEGIN { 
      while (getline < "'$SPELL_STATUS'") 
        Installed[$1] = 1; 
    } 
    $3=="on"{ 
	  if($2 ~ "[(].*[)]")
	    prov=gensub("^.*\\((.*)\\).*$", "\\1", 1, $2);
      else
        prov="__BLANK__";
      gsub("\\(.*\\)", "", $2)
      if ( Installed[$1] && ! Installed[$2] && $2!="") 
        printf("%s %s %s %s\n", $1, $2, $4, prov);
     }' $DEPENDS_STATUS ) )

  # Set the function args to the spells with problems
  set -- ${bad[@]}
  while [ $# -gt 0 ] ; do
    options=()
    message "$1 needs $2 ($3)."
	
	# Check if the dependency is a provider. IF so, the SA may have  switched
	# to another provider and should be given the option to recast with the
	# new provider
	if [[ $4 != __BLANK__ ]] ; then
	  message -n "$2 used to provide $4. Now "
	  local CANDIDATES=$( find_providers $4 )
	  provider=""
      for spell in $CANDIDATES; do
        if spell_installed $spell || spell_held $spell; then
		  local provider=$spell
		  break
		fi
      done


      if [[ ${provider} ]] ; then
        message -n "$provider "
	    options=( "Switch from $2 to ${provider} and recompile" )
      else
        message -n "${PROBLEM_COLOR}no one${DEFAULT_COLOR} "
	  fi
	  message "provides it."
    fi
	
    # If the spell is optional, then the dependency can be removed and the spell recast
    # If it's required, then it can't be removed
    if [[ $3 == required ]] ; then
      options=( "Ignore" "Dispel $1" "Cast $2" "${options[@]}" )
    elif [[ $3 == optional ]] ; then
      options=( "Ignore" "Dispel $1" "Cast $2" "Recast $1 without $2" "${options[@]}" )
    fi
    
    #Get the user's choice of action
    select_list choice 0 "${options[@]}" 
    case ${choice} in
      I*)  message "Ignoring."                ;;
      D*)  echo $1 >> $TMP_DIR/prune.dispel    ;;
      C*)  echo $2 >> $TMP_DIR/prune.cast      ;;
      R*)  echo $1 >> $TMP_DIR/prune.cast
        # Revise choice in the depends file
        sed 's/^\('$1':'$2':\)on\(:optional:.*\)$/\1off\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
        mv $TMP_DIR/prune.1 $DEPENDS_STATUS
        ;;
	  S*)  sed 's/^\('$1':\)'$2'\(('$4'):.*\)$/\1'$provider'\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
	  	mv $TMP_DIR/prune.1 $DEPENDS_STATUS
		echo $1 >>$TMP_DIR/prune.cast
		;;
      *)  message "We should never get here." 
        exit 1
        ;;
    esac
    shift 4
  done
  
  unlock_file $DEPENDS_STATUS  
  
  # put the spells in two files for later perusal
  if [[ $action ]] ; then
    [ -s $TMP_DIR/prune.dispel ] && 
      dispel $( sort $TMP_DIR/prune.dispel | uniq )
    [ -s $TMP_DIR/prune.cast ] && 
      cast -c $( sort $TMP_DIR/prune.cast | uniq )
  else
    t=$(date +%Y%m%d%s)
    echo dispel $( sort $TMP_DIR/prune.dispel 2>/dev/null  | uniq ) > $BACKUPDIR/prune.$t
    echo cast -c $( sort $TMP_DIR/prune.cast 2>/dev/null | uniq ) >> $BACKUPDIR/prune.$t
    cat $BACKUPDIR/prune.$t
    message "For future reference, lines are stored in $BACKUPDIR/prune.$t"
  fi
  message "Pruning done."
}

#----------------------
## Find packages entries that are corrupted, or non-existant
## @Stdout Misc status output
#----------------------
function packages()
{
  local spell t LINE
  message "Fixing packages..."
  lock_file $SPELL_STATUS
  
  message "Pass 1 (bad lines)"
  sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/!p' $SPELL_STATUS > $TMP_DIR/packages.bad
  if [ -s $TMP_DIR/packages.bad ] ; then
    sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/p' $SPELL_STATUS > $TMP_DIR/packages.good
    echo "$(wc -l $TMP_DIR/packages.bad) bad lines, $(wc -l $TMP_DIR/packages.good) good lines in your packages file."
    t=$(date +%Y%m%d%s)
    message "\tSaving bad lines to $BACKUPDIR/packages.1.bad.$t"
    mv $TMP_DIR/packages.bad $BACKUPDIR/packages.1.bad.$t
    message "\tRemoving bad entries"
    mv $TMP_DIR/packages.good $SPELL_STATUS
  fi
  
  message "Pass 2 (non-existant spells)"
  t=""
  for spell in $( sed -n 's/^\([^:]*\):.*$/\1/p' $SPELL_STATUS ) ; do
    if ! [[ $( codex_find_spell_by_name $spell ) ]] ; then
      message "\t$spell"
      t="found"
    fi
  done
  if [[ $t ]] ; then
    message "Entries for non-existant spells will NOT be automaticly removed."
    message "You may have removed the grimoire with the spell"
    message "or the name may have changed. You should figure out where"
    message "these spells went. They may have been deprecated and then"
    message "removed. You may want to dispel these if that is the case."
  fi
    
  message "Pass 3 (duplicate entries)"
  t=$(
    for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1/p' $SPELL_STATUS | sort | uniq -d ) ; do
      grep "^$LINE:" $SPELL_STATUS | sed 's/^/\t/'
    done
  )
  if [[ $t ]] && query "\tRemove all but one of each set of duplicates?" "n" ; then
    awk -F: '{if(!LINES[$1]) { LINES[$1]=1; print $0; } }' $SPELL_STATUS > $TMP_DIR/packages.3
    message "\tRemoving bad entries"
    mv $TMP_DIR/packages.3 $SPELL_STATUS
  fi
  
  unlock_file $SPELL_STATUS
  message "Done fixing packages."
}

#-----------------------------
##
## Checks and fixes a list of spells. If no spells
## are specified, then all installed spells are checked.
## If the first arg is 'nofix', then no fixing will be done
## Just error detection
##
## @Args [nofix] [spell [spell [...]]]
## @Returns May return non zero if there was a problem.
##
#-----------------------------
function cleanse_fix()
{

  local FIX=$1
  shift 1
  local SPELL
  local SPELLS=$*

  # find the binary executable make, we cant call it directly since make may
  # be a function in this context
  local REAL_MAKE
  find_make REAL_MAKE || return $?

  # given a list of spells to check
  if [ -z "$SPELLS" ]; then
    # I think the behavior will be to not check held spells by default
    # because they are held, but if they are explicitly specified check them
    SPELLS=$(get_all_spells_with_status installed|tr '\n' ' ')
  else
    local tmp_spells
    # ensure they are all installed
    for SPELL in $SPELLS ; do
      spell_ok $SPELL &&
      tmp_spells="$tmp_spells $SPELL" ||
      message "${SPELL_COLOR}$SPELL${DEFAULT_COLOR}" \
              "is not installed or held, not fixing"
    done
    SPELLS=$tmp_spells
    if [ -z "$SPELLS" ] ; then
      message "${PROBLEM_COLOR}no installed spells to fix${DEFAULT_COLOR}"
      exit
    fi
  fi
  # get the installed depends hash
  compute_installed_depends depends_hash

  # generate a makefile from the spells + depends hash
  local MAKEFILE=$TMP_DIR/Makefile
  (
  for i in $( hash_get_table_fields depends_hash ) ; do
    echo "$i : $(hash_get "depends_hash" "$i")"
    echo -e "\t@echo $i"
    echo
  done
  echo
  echo %:
  echo -e "\t@echo -n"
  echo
  echo "all : $SPELLS"
  echo
  echo ".PHONY : all $SPELLS"
  ) > $MAKEFILE
  
  # load our ldd hash with the spells we know we'll care about
  # but no more then necessary
  cleanse_fix_init_ldd_check $($REAL_MAKE -s -f $MAKEFILE all 2>/dev/null)

  local PASSED FIXED HOPELESS TOTALNR CURNR unused
 
  CURNR=0
 
  if [ $FIX == quick ] ; then
    TOTALNR=0 #counting them like this doesn't seem to take substantial time
    for unused in $SPELLS ; do let TOTALNR++ ; done
    for SPELL in $SPELLS ; do
      let CURNR++
      if cleanse_fix_run_checks $SPELL $CURNR $TOTALNR; then
        PASSED="${PASSED}${SPELL}"$'\n'
      else
        message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
                 "is broken${DEFAULT_COLOR}\n"
        HOPELESS="${HOPELESS}${SPELL}"$'\n'
      fi
    done
  else
    # non-quick mode implies we check from the bottom up, so get all
    # the spells..
    TOTALNR=$($REAL_MAKE -s -f $MAKEFILE all 2>/dev/null|wc -l)
    cleanse_fix_depends all
  fi

  if [[ $PASSED ]] ; then
    message "${MESSAGE_COLOR}The following spells passed the" \
            "check:${SPELL_COLOR}"
    echo "$PASSED"|sort|uniq|column
    message "${DEFAULT_COLOR}"
  fi
  if [[ $FIXED ]] ; then
    message "${MESSAGE_COLOR}The following spells were broken and" \
            "fixed:${QUERY_COLOR}"
    echo "$FIXED"|sort|uniq|column
    message "${DEFAULT_COLOR}"
  fi
  if [[ $HOPELESS ]] ; then
    if [[ "$FIX" == quick ]]; then
      message "${MESSAGE_COLOR}The following spells were broken:" \
              "${PROBLEM_COLOR}"
    else
      message "${MESSAGE_COLOR}The following spells were broken and" \
              "could not be fixed:${PROBLEM_COLOR}"
    fi
    echo "$HOPELESS"|sort|uniq|column
    message "${DEFAULT_COLOR}"
    return 1
  fi
  return 0
}

function cleanse_fix_depends() {
  local SPELL=$1
  # run the makefile to get the proper order
  local FIX_ORDER=$($REAL_MAKE -s -f $MAKEFILE $1  2>/dev/null| grep -v "^$SPELL\$")
  #for each spell
  for SPELL in ${FIX_ORDER}; do
    # check it
    let CURNR++
    if cleanse_fix_run_checks $SPELL $CURNR $TOTALNR; then
      PASSED="${PASSED}${SPELL}"$'\n'
    else
      if [ $FIX == fix ] ; then
        # if its broken try to fix it
        cleanse_fix_spell $SPELL
      else
        message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
                 "is broken${DEFAULT_COLOR} and not being fixed\n"
        HOPELESS="${HOPELESS}${SPELL}"$'\n'
      fi
    fi
  done
}

#----------------------------
##
## cast a spell and recheck it to see if its actually fixed
## we assume in this case that all its dependents are fixed/hopeless
##
## if the spell cannot be fixed we complain and append it to the HOPELESS list
##
## @param Spell to check
## @return 0 if spell is fixed 1 if not
##
#----------------------------
function cleanse_fix_spell() {
  local SPELL=$1
  if spell_held $SPELL ; then
    HOPELESS="${HOPELESS}${SPELL}"$'\n'
    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
             "is broken broken, but also held, not fixing${DEFAULT_COLOR}"
    return 1
  fi

  cast -c $SPELL                && 
  cleanse_fix_run_checks $SPELL &&
  FIXED="${FIXED}${SPELL}"$'\n' &&
  return 0
  
  # if we get here the spell is deemed hopeless

  HOPELESS="${HOPELESS}${SPELL}"$'\n'
  message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
           "is hopelessly broken and we can't fix it${DEFAULT_COLOR}"
  return 1
}

#--------------------------
##
## Run the checks on a spell.
## @args Spell [check nr,total nr]
## @return Should return 0 if all the checks completed
##
#--------------------------
function cleanse_fix_run_checks()  {
  local SPELL=$1
  if [ $# -eq 1 ] ;then
    message  "Checking ${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"
  else
    message  "Checking ${SPELL_COLOR}${SPELL}${DEFAULT_COLOR} ($2 of $3)"
  fi
  local VERSION=$(installed_version $SPELL)

  cleanse_fix_find_check    $SPELL  $VERSION    &&
  cleanse_fix_ldd_check     $SPELL  $VERSION    &&
  cleanse_fix_sym_check     $SPELL  $VERSION    &&
  cleanse_fix_md5sum_check  $SPELL  $VERSION

}


#-----------------------
##
## Check that all files installed still exist.
## @FIXME The skipped directories should be made configurable somewhere
## @return 0 The files were all in existance
## @return 1 At least one file was found missing
## 
#-----------------------
function cleanse_fix_find_check()  {
  if  [ "$FIND_CHECK" == "off" ] ; then
    return 0
  fi

  local SPELL=$1
  local I_LOG="$INSTALL_LOGS/$SPELL-$2"
  local BADLIST=""
  local LINE

  message "\tFile test"

  if ! [ -f $I_LOG ] ; then
    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"     \
             "is missing an install log.${DEFAULT_COLOR}"
    return 1;
  fi

  local size=$(wc -l $I_LOG | awk '{print $1;}')
  local count=0

  local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
  mkdir -p $TMP_DIR/${SPELL}
  rm -f $TMP_I_LOG
  log_adjuster $I_LOG /dev/stdout log filterable  |
  filter_volatiles                                |
  log_adjuster /dev/stdin $TMP_I_LOG filterable root

  while read LINE ; do
    progress_bar $count $size 50
    if  [ !  -e  "$LINE"  ];  then
      BADLIST="${BADLIST}${LINE}"$'\n'
    fi
    let count++
  done < $TMP_I_LOG
  rm -f $TMP_I_LOG
  clear_line

  if [[ $BADLIST ]] ; then
    message "${PROBLEM_COLOR}The following files from"             \
            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"               \
            "${PROBLEM_COLOR}don't exist: "$'\n'"${DEFAULT_COLOR}" 
    message "$BADLIST"

    return 1
  fi

  return 0

}

#-----------------------
##
## Initialize the ldd check, builds up a hash table in ldd_hash of
## what library directories a spell has libraries in, this is because
## some spells have their own private libraries and arent happy if
## they dont know where they are
## 
#-----------------------
function cleanse_fix_init_ldd_check()  { 
  if  [ "$LDD_CHECK" == "off" ] ;  then
    return 0
  fi
  local SPELL
  local I_LOG
  local size count
  let size=0
  let count=0
  message "Doing initialization for the ldd check"
  for SPELL in $* ; do
    let size++
  done
  for SPELL in $* ; do
    I_LOG=$INSTALL_LOGS/$SPELL-$(installed_version  $SPELL)
    hash_put ldd_hash $SPELL "$(grep '\.so$' $I_LOG|get_dirnames|sort|uniq|tr '\n' :)"
    progress_bar $count $size 50
    let count++
  done
  clear_line
}

#-----------------------
##
## Check that all libraries the spell binaries need exist
## @return 0 The libraries have their requirements met
## @return 1 At least one library isnt happy
## 
#-----------------------
function cleanse_fix_ldd_check()  { (
  if  [ "$LDD_CHECK" == "off" ] ;  then
    return 0
  fi
  message "\tLibrary test"

  local SPELL=$1
  local I_LOG=$INSTALL_LOGS/$SPELL-$2
  local BADLIST=""
  local LINE

  if ! [ -f $I_LOG ] ; then
    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"     \
             "is missing an install log.${DEFAULT_COLOR}"
    return 1;
  fi

  local tmp
  for each in $($REAL_MAKE -s -f $MAKEFILE $1 2>/dev/null); do
    tmp=$(hash_get ldd_hash $each)
    [[ $tmp ]] && LD_LIBRARY_PATH="$(hash_get ldd_hash $each):${LD_LIBRARY_PATH}"
  done

  local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
  mkdir -p $TMP_DIR/${SPELL}
  log_adjuster $I_LOG /dev/stdout filterable      |
  filter_volatiles                                |
  log_adjuster /dev/stdin $TMP_I_LOG filterable root

  local count size
  let count=0
  local size=$(wc -l $TMP_I_LOG | awk '{print $1;}')
  export LD_LIBRARY_PATH
  while read LINE; do

    progress_bar $count $size 50
    let count++
    if     [  -f  "$LINE"  ]     &&
        file  -b  "$LINE"        |
        grep  -q  "ELF"          &&
        ldd       "$LINE"  2>&1  |
        grep  -q  "not found"
    then
      BADLIST="${BADLIST}${LINE}"$'\n'
    fi
  done < $TMP_I_LOG
  rm -f $TMP_I_LOG
  clear_line

  if [[ $BADLIST ]] ; then
    message "${PROBLEM_COLOR}The following files from"                  \
            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"                   \
            "${PROBLEM_COLOR}have missing library dependencies:"       \
            "${DEFAULT_COLOR}"
    echo "$BADLIST"|while read LINE ; do
      echo $LINE
      ldd $LINE 2>&1|grep "not found"
    done
    return 1
  fi

  return 0

) }

#-----------------------
##
## Check that all symlink targets exist
## @return 0 The symlinks point to real files
## @return 1 At least one symlink doesnt have its target
## 
#-----------------------
function cleanse_fix_sym_check() {
  if [ "$SYM_CHECK" == "off" ] ; then
    return 0
  fi
  local SPELL=$1
  local I_LOG="$INSTALL_LOGS/$SPELL-$2"
  local BADLIST=""
  local LINE

  message "\tSymlink test"

  if ! [ -f $I_LOG ] ; then
    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"     \
             "is missing an install log.${DEFAULT_COLOR}"
    return 1;
  fi

  local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
  mkdir -p $TMP_DIR/${SPELL}
  rm -f $TMP_I_LOG
  log_adjuster $I_LOG /dev/stdout log filterable |
  filter_volatiles                               |
  log_adjuster /dev/stdin $TMP_I_LOG filterable root

  local size=$(wc -l $TMP_I_LOG | awk '{print $1;}')
  local count=0

  while read LINE ; do
    progress_bar $count $size 50
    # Add this if so 'file' doesn't have to run for everything,
    # check that's a symlink first
    if [ -h "$LINE" ] && file "$LINE" | grep "broken symbolic link" ; then
      BADLIST="${BADLIST}${LINE}"$'\n'
    fi
    let count++
  done < $TMP_I_LOG
  rm -f $TMP_I_LOG
  clear_line

  if [[ $BADLIST ]] ; then
    message "${PROBLEM_COLOR}The following symlinks from "             \
            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"                  \
            "${PROBLEM_COLOR}don't link to files it installed:"       \
            "${DEFAULT_COLOR}"
    message "$BADLIST"

    return 1
  fi

  return 0
}

#-----------------------
##
## Verify the md5sum of all the files
## @return 0 The md5sum of a file is incorrect
## @return 1 At least one md5sum is wrong
## 
#-----------------------
function cleanse_fix_md5sum_check() {

  if  [ "$MD5SUM_CHECK" == "off" ] ; then
    return 0
  fi
  local SPELL=$1
  local MD5_LOG="$MD5SUM_LOGS/$SPELL-$2"
  local BADLIST
  local TMP_MD5_LOG=$TMP_DIR/cleanse.md5sum.tmp.$SPELL.$$
  local BROKEN1=$TMP_DIR/cleanse.md5sum.broken1.$SPELL.$$
  local BROKEN2=$TMP_DIR/cleanse.md5sum.broken2.$SPELL.$$
  local A=$'\a' # magic sed seperator, assume files dont have bells in the name

  # Ignore standard paths we know will change, the install log, file log,
  # compile log, and score files that live in /var/games (bug 7500)

  message -n "\tChecking md5sums."

  if ! [ -f $MD5_LOG ] ; then
    message  "${SPELL_COLOR}${SPELL} "         \
             "${PROBLEM_COLOR}is missing an md5 log.${DEFAULT_COLOR}"
    return 1;
  fi
  touch $BROKEN1 $BROKEN2

  message -n '.'

  log_adjuster $MD5_LOG $TMP_MD5_LOG log root md5_log_filter


  # for some reason stuffing the output of this in a variable loses the
  # newlines and thus files with spaces in them get lost
  md5sum --check $TMP_MD5_LOG 2>/dev/null | grep ': FAILED$' |
            sed "s${A}: FAILED\$${A}${A}" > $BROKEN1
  # cant use awk above with : as a seperator due to files with :'s in their name

  # filters dont work on md5sum logs, filter after md5sum is done
  # this is lame but oh well, this function is ugly enough as it is
  filter_volatiles < $BROKEN1 > $BROKEN2

  message -n '.'

  # the files with bad md5s may be owned by another spell (either legitimatly
  # or by spell writer oversight) if so remove the entry from
  # the spell's install and md5sum logs, and enter it in the possessed
  # log. Someday dispel et. al. will use this for stuff, for now
  # its for informational purposes

  # we have to dump out to a file because I cant use message
  # inside $( ), and variables are stupid about spaces
  local FOUND_IN REAL_MD5 possessed INSTALL_LOG
  rm $BROKEN1
  while read LINE; do
    message -n '.'
    [[ ! $LINE ]]  && continue
    test -h $LINE && continue
    REAL_MD5=$(md5sum $LINE)
    # find the file in some log somewhere...
    FOUND_IN=$(grep -l "^$REAL_MD5\$" $MD5SUM_LOGS/*)

    # remove anything from a non-current version of the spell
    # this is ugly but the only reasonable way I know of to do this...
    FOUND_IN=$(for each in $FOUND_IN; do
                # make the assumption that spell versions dont contain dashes...
                # this actually isnt a very good assumption but works enough
                # of the time
                each_spell=$(basename $each|awk -F- '{for (i=1;i<NF-1;i++) {printf "%s-",$i}printf "%s\n",$i}')
                each_version=$(installed_version $each_spell)
                [[ $each_version ]] &&
                [[ $MD5SUM_LOGS/${each_spell}-${each_version} == $each ]] &&
                echo $each
              done)

    if [[ ! $FOUND_IN ]]; then
      echo $LINE >> $BROKEN1
      continue
    fi

    message "\n${PROBLEM_COLOR}Incorrect md5sum for" \
            "${FILE_COLOR}$LINE${DEFAULT_COLOR}"
    message "but matches in:\n$FOUND_IN"

    if [ $FIX == fix ] || [ $FIX == quick ]; then
      message "Removing from $SPELL's install and md5sum logs."

      INSTALL_LOG=$INSTALL_LOGS/$SPELL-$2
      # there is a slight bug with all these transactions due to
      # the fact that we have no stdin, if a transaction fails the user
      # is prompted, but since we're in this funny loop, it'll read from
      # the wrong place a future improvement would be
      # to use iterate or some other method that is not naive to
      # filenames with spaces in them
      tfile=`lock_start_transaction $INSTALL_LOG`
      sed "s${A}^$LINE\$${A}${A}" $INSTALL_LOG|tr -s '\n'  > $tfile
      lock_commit_transaction $INSTALL_LOG
      message -n '.'

      tfile=`lock_start_transaction $MD5_LOG`
      sed "s$A.*  $LINE\$${A}${A}"  $MD5_LOG|tr -s '\n'  > $tfile
      lock_commit_transaction $MD5_LOG
      message -n '.'

      test -d $POSSESSED_LOGS || mkdir -p $POSSESSED_LOGS
      
      possessed=$POSSESSED_LOGS/$SPELL-$2
      tfile=`lock_start_transaction $possessed`
      echo $LINE >> $tfile
      # remove duplicates
      cp $tfile $TMP_DIR/$SPELL.possessed.$$
      sort $TMP_DIR/$SPELL.possessed.$$|uniq > $tfile
      rm  $TMP_DIR/$SPELL.possessed.$$
      lock_commit_transaction $possessed

      message -n '.'
    fi
  done  < $BROKEN2

  message -n '.'

  if test -s $BROKEN1 ; then
    BADLIST=$(cat $BROKEN1)
  else
    BADLIST=""
  fi
  rm $BROKEN1 $BROKEN2 &>/dev/null

  message ''


  if [[ $BADLIST ]] ; then
    message "${PROBLEM_COLOR}The following files have been modified from"  \
            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}:"
    message "$BADLIST"
    return 1
  fi
  return 0
}

source /etc/sorcery/config

if [ $UID -eq 0 ] ; then

  # Make sure root is running this
  if [ $# -eq 0 ] ; then
    # IF there are no args, assume the full treatment is desired
    exec $0 --packages --delint --logs --prune doit --tablet --sweep --fix
  fi

  mk_tmp_dirs backup /tmp/cleanse
  BACKUPDIR=$TMP_DIR
  unset TMP_DIR
  mk_tmp_dirs cleanse
  if [ ! -d $TMP_DIR ] || [ ! -d $BACKUPDIR ] ; then
    message "Unable to make tmp dirs..."
    exit 1
  fi
  args "$@"
  rc=$?
  cleanup_tmp_dir $TMP_DIR
  message "Removed information is backed up to $BACKUPDIR."
  exit $rc
else
  if [[ $1 == --help ]] ; then
    usage
    exit 0
  fi
  message "You must be root to run this command."
  message "Switching to root user..."
  PARAMS=$(consolidate_params "$@")
  exec su -c "$0 $PARAMS" root
fi
