/usr/sbin/cleanse

     1	#!/bin/bash
     2	
     3	#---------------------------------------------------------------------
     4	## This script should clean up the sorcery files, particularly
     5	## the depends and packages files. It should validate the files
     6	## remove corrupted lines, and ensure the information all agrees
     7	## with itself. It is also to clean out unused lines and files.
     8	##
     9	## @Copyright Copyright 2004 by Paul Mahon for Source Mage
    10	## @Licence Relased under the GNU GPL version 2
    11	##
    12	#---------------------------------------------------------------------
    13	
    14	#---------------------------------------------------------------------
    15	## This software is free software; you can redistribute it and/or modify
    16	## it under the terms of the GNU General Public License as published by
    17	## the Free Software Foundation; either version 2 of the License, or
    18	## (at your option) any later version.
    19	##
    20	## This software is distributed in the hope that it will be useful,
    21	## but WITHOUT ANY WARRANTY; without even the implied warranty of
    22	## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    23	## GNU General Public License for more details.
    24	##
    25	## You should have received a copy of the GNU General Public License
    26	## along with this software; if not, write to the Free Software
    27	## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    28	##
    29	#---------------------------------------------------------------------
    30	
    31	#--------------------
    32	## Gives the usage of this script
    33	## @param A switch that was not understood (optional)
    34	## @Stdout Usage statement
    35	#--------------------
    36	function usage()
    37	{
    38	  local s="cleanse"
    39	cat << EOF
    40	Invoke $s with one of the arguments below.
    41	
    42	Command         Description
    43	--delint        Finds stale dependency entries
    44	--delint full   will delint dependencies that are off
    45	--prune         Finds dispeled spells that should be cast
    46	--prune doit    will cause it to actually cast and dispel
    47	--packages      find bad entries in the installed spells file
    48	
    49	--fix           attempts to detect errors in cast spells and recasts broken ones
    50	--nofix         attempts to detect errors in cast spells but does not recast
    51	--nofix_quick   like --nofix, but does not check dependent spells.
    52	
    53	--tablet        clean up the tablet
    54	--tablet coalesce       clean up the tablet and hardlink duplicate files
    55	--tablet_spell  clean up the tablet for one spell
    56	--logs          cleans old logs in the same way a system-update does
    57	
    58	--sweep         Clean out old downloaded sources and cached binaries
    59	                old is defined as not capable of being used by any
    60	                spell/version in any grimoire on the system.
    61	
    62	--sweep_all     Clean out old downloaded sources and cached binaries
    63	                old is defined as not capable of being used by any spell
    64	                installed on the system. Only the first version of the
    65	                spell found is used.
    66	
    67	It is suggested that you try the following order the first time:
    68	    1) $s --packages
    69	    2) $s --delint
    70	    3) $s --tablet
    71	    4) $s --logs
    72	    5) $s --prune
    73	    6) $s --sweep
    74	    7) $s --fix
    75	EOF
    76	    [[ $# -gt 0 ]] && echo "Unknown $*"
    77	}
    78	
    79	
    80	#---------------------
    81	## Interpret the arguments to this script
    82	## @Args  --help            Display help">help">help
    83	## @Args  --delint [full]   Find stale dependency entries, with full it'll look at off entries too
    84	## @Args  --prune [doit]    Find missing spells, do the actions of "doit" is specified
    85	## @Args  --packages        Find packages entries that are corrupted, or non-existant
    86	## @Args  --tablet          Clean up the tablet
    87	## @args  --tablet_spell    Clean up the tablet for one spell
    88	## @Args  --logs            Remove stale logs
    89	## @Args  --sweep           Remove uneeded spool and cache files
    90	## @Args  --sweep_all       Remove uneeded spool and cache files
    91	## @Args  --fix             Find binaries that are missing libs and files, and recast broken spells
    92	## @Args  --nofix           Same as --fix, but won't recast.
    93	## @Args  --nofix_quick     Same as --nofix, but doesn't check dependent spells
    94	#---------------------
    95	function args()
    96	{
    97	  local rc
    98	  let rc=0
    99	  while [ $# -gt 0 ] ; do
   100	    arg=$1
   101	    shift
   102	
   103	    case $arg in
   104	      --help)    usage         ;;
   105	      --delint)
   106	        if [[ $1 == full ]] ; then
   107	          delint full
   108	          let rc+=$?
   109	          shift
   110	        else
   111	          delint
   112	          let rc+=$?
   113	        fi
   114	        ;;
   115	      --prune)
   116	        if [[ $1 == doit ]] ; then
   117	          prune_depends doit
   118	          let rc+=$?
   119	          shift
   120	        else
   121	          prune_depends
   122	          let rc+=$?
   123	        fi
   124	        ;;
   125	      --logs) clean_logs
   126	        let rc+=$?
   127	        ;;
   128	      --fix) cleanse_fix fix "$*"
   129	        let rc+=$?
   130	        shift $#
   131	        ;;
   132	      --nofix) cleanse_fix nofix "$*"
   133	        let rc+=$?
   134	        shift $#
   135	        ;;
   136	      --nofix_quick) cleanse_fix quick "$*"
   137	        let rc+=$?
   138	        shift $#
   139	        ;;
   140	      --sweep) prune
   141	        let rc+=$?
   142	        ;;
   143	      --sweep_all) prune installed_only
   144	        let rc+=$?
   145	        ;;
   146	      --packages) packages
   147	        let rc+=$?
   148	        ;;
   149	      --tablet)
   150	        tablet_cleanse_tablet $TABLET_PATH $BACKUPDIR
   151	        let rc+=$?
   152	        if [[ $1 == coalesce ]] ; then
   153	          tablet_coalesce_files $TABLET_PATH $BACKUPDIR
   154	          let rc+=$?
   155	          shift
   156	        fi
   157	        ;;
   158	      --tablet_spell)
   159	        tablet_cleanse_chapter $TABLET_PATH/$1 $BACKUPDIR yes
   160	        let rc+=$?
   161	        shift
   162	        ;;
   163	      *)  usage $arg $* ; return 1  ;;
   164	    esac
   165	  done
   166	  return ${rc:-0}
   167	}
   168	
   169	function verify_parameters()
   170	{
   171	  while [[ $# -gt 0 ]]; do
   172	    case $1 in
   173	        --delint|--prune|--tablet)
   174	          case $2 in
   175	            full|doit|coalesce) shift ;;
   176	          esac
   177	        ;;
   178	      --fix|--nofix|--nofix_quick) true ;;
   179	       --sweep_all|--sweep|--logs) true ;;
   180	        --tablet_spell|--packages) true ;;
   181	                               -*)  usage $*; exit 1 ;;
   182	    esac
   183	    shift
   184	  done
   185	}
   186	
   187	#-------------------
   188	## Find stale dependency entries, with full it'll look at off entries too
   189	## @param full  if set, looks at off dependencies as well as on
   190	## @Stdout Misc output and questions
   191	## @Stdin Answers to questions
   192	#-------------------
   193	function delint()
   194	{
   195	  message "Delinting..."
   196	  local cond='$3=="on"'
   197	  local options=()
   198	  local choice t lines regex
   199	  if [[ $1 == full ]] ; then
   200	    cond=''
   201	  fi
   202	
   203	  lock_file $DEPENDS_STATUS
   204	
   205	  message "Pass one (malformed depends lines)"
   206	  # This grep will pull out malformed lines from the depends file
   207	  regex='^[^:]*:[^:]*:(on|off):(optional|required|suggest|runtime):'
   208	  lines=$(grep -v -E "$regex" $DEPENDS_STATUS)
   209	  if [[ $lines ]] ; then
   210	    message "The following malformed lines were found"
   211	    message "and will cause problems with the following passes:"
   212	    message "$lines"
   213	    if query "Do you want them removed" "n" ; then
   214	      message "Removing lines..."
   215	      grep -E "$regex" $DEPENDS_STATUS > $TMP_DIR/delint.1
   216	      mv $TMP_DIR/delint.1 $DEPENDS_STATUS
   217	      t=$(date +%Y%m%d%s)
   218	      message "Saving bad lines to $BACKUPDIR/depends.malformed.$t"
   219	      echo "$lines" > $BACKUPDIR/depends.malformed.$t
   220	      message "done."
   221	    else
   222	      message "Not removing malformed lines, quitting."
   223	      return 1
   224	    fi
   225	  fi
   226	
   227	  generic_remove_stale "$DEPENDS_STATUS" depends two
   228	
   229	  message "Pass three (duplicate depends entries)"
   230	  for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1:\2/p' $DEPENDS_STATUS | sed 's/+/\\+/' | sort | uniq -d ) ; do
   231	    message
   232	    options=()
   233	    eval $(awk '/^'$LINE':/{
   234	      if(!ENTRIES[$0]) {
   235	        printf("options[%d]=\"%s\"\n", count++, $0); ENTRIES[$0]=1;
   236	      } }' $DEPENDS_STATUS)
   237	    if [ ${#options[@]} -gt 1 ] ; then
   238	      select_list choice "" "${options[@]}"
   239	      message "Removing other entries and keeping selected entry"
   240	    else
   241	      choice="${options[0]}"
   242	      message "Identical lines removed: $choice"
   243	    fi
   244	    grep -Ev "^$LINE:" $DEPENDS_STATUS > $TMP_DIR/delint.3
   245	    echo "$choice" >> $TMP_DIR/delint.3
   246	    mv $TMP_DIR/delint.3 $DEPENDS_STATUS
   247	  done
   248	
   249	  unlock_file $DEPENDS_STATUS
   250	
   251	  touch $SUB_DEPENDS_STATUS
   252	  lock_file $SUB_DEPENDS_STATUS
   253	  generic_remove_duplicate "$SUB_DEPENDS_STATUS" sub_depends four
   254	  generic_remove_stale "$SUB_DEPENDS_STATUS" sub_depends five
   255	  unlock_file $SUB_DEPENDS_STATUS
   256	
   257	  touch $TRIGGER_LIST
   258	  lock_file $TRIGGER_LIST
   259	  generic_remove_duplicate "$TRIGGER_LIST" trigger six
   260	  generic_remove_stale "$TRIGGER_LIST" trigger seven
   261	  unlock_file $TRIGGER_LIST
   262	
   263	  message "Delint done."
   264	}
   265	
   266	function generic_remove_duplicate() {
   267	  local LINE
   268	  local tmp=$TMP_DIR/delint.$2.dup
   269	  message "Pass $3 (duplicate $2 entries)"
   270	  for LINE in $(sort $1 | uniq -d ) ; do
   271	    if test $(grep -E "^$LINE$" "$1"|wc -l) -gt 1; then
   272	      message "Duplicated line removed: $LINE"
   273	      grep -Ev "^$LINE$" $1 > $tmp
   274	      echo "$LINE" >> $tmp
   275	      mv $tmp $1
   276	    fi
   277	  done
   278	}
   279	
   280	function generic_remove_stale() {
   281	  message "Pass $3 (stale $2 entries)"
   282	  local tmp=$TMP_DIR/delint.$2.stale
   283	  local t
   284	  awk -F: '
   285	    BEGIN {
   286	      while (getline < "'$SPELL_STATUS'")
   287	        Installed[$1] = 1;
   288	    }
   289	    {
   290	      if ( ! Installed[$1])
   291	        print $0;
   292	    }' "$1" |tee $tmp
   293	  t=$(wc -l $tmp | awk '{print $1;}')
   294	  message "\t$t bad lines, $(( $(wc -l $1| cut -f1 -d' ') - t))" \
   295	          "good lines in your $2 file."
   296	  if [[ $t -gt 0 ]] ; then
   297	    t=$(date +%Y%m%d%s)
   298	    message "\tSaving bad lines to $BACKUPDIR/$2.stale.$t"
   299	    cp $tmp $BACKUPDIR/$2.stale.$t
   300	    message "\tRemoving bad entries"
   301	    cat $1 >> $tmp
   302	    sort $tmp | uniq -u > $tmp.2
   303	    cp $tmp.2 $1
   304	  fi
   305	}
   306	
   307	#---------------------
   308	## Find missing spells, do the actions of "doit" is specified
   309	## @param  if set to "doit" it'll actual cast and dispel
   310	## @Stdout  questions about what to do about problems
   311	## @Stdin  responses to queries
   312	#---------------------
   313	function prune_depends()
   314	{
   315	  message "Pruning..."
   316	  local action=""
   317	  local LINE DISPEL_LIST CAST_LIST
   318	  local choice options provider spell
   319	
   320	  # actualy do the actions, or just output what should be done
   321	  if [[ $1 == doit ]] ; then
   322	    action="doit"
   323	  fi
   324	
   325	  # Lock the depends file so no one messes with it while it's being worked on
   326	  lock_file $DEPENDS_STATUS
   327	  lock_file $SPELL_STATUS
   328	
   329	  # Get a list of bad dependencies
   330	  # Output is in the form "SPELL DEPENDENCY optional/reqired PROVIDER"
   331	  bad=( $( awk -F: '
   332	    BEGIN {
   333	      while (getline < "'$SPELL_STATUS'")
   334	        Installed[$1] = 1;
   335	    }
   336	    $3=="on"{
   337	      if($2 ~ "[(].*[)]")
   338	        prov=gensub("^.*\\((.*)\\).*$", "\\1", 1, $2);
   339	      else
   340	        prov="__BLANK__";
   341	      gsub("\\(.*\\)", "", $2)
   342	      if ( Installed[$1] && ! Installed[$2] && $2!="")
   343	        printf("%s %s %s %s\n", $1, $2, $4, prov);
   344	     }' $DEPENDS_STATUS ) )
   345	
   346	  unlock_file $SPELL_STATUS
   347	  # Set the function args to the spells with problems
   348	  set -- ${bad[@]}
   349	  while [ $# -gt 0 ] ; do
   350	    options=()
   351	    message "$1 needs $2 ($3)."
   352	
   353	# Check if the dependency is a provider. IF so, the SA may have  switched
   354	# to another provider and should be given the option to recast with the
   355	# new provider
   356	  if [[ $4 != __BLANK__ ]] ; then
   357	    message -n "$2 used to provide $4. Now "
   358	    local CANDIDATES=$( find_providers $4 )
   359	    provider=""
   360	      for spell in $CANDIDATES; do
   361	        if spell_ok $spell; then
   362	          local provider=$spell
   363	          break
   364	        fi
   365	      done
   366	
   367	      if [[ ${provider} ]] ; then
   368	        message -n "$provider "
   369	        if [[ $3 == suggest ]] || [[ $3 == runtime ]]; then
   370	          options=( "Switch from $2 to ${provider}" )
   371	        else
   372	          options=( "Switch from $2 to ${provider} and recompile" )
   373	        fi
   374	      else
   375	        message -n "${PROBLEM_COLOR}no one${DEFAULT_COLOR} "
   376	      fi
   377	      message "provides it."
   378	    fi
   379	
   380	    # If the spell is optional, then the dependency can be removed and the spell recast
   381	    # If it's required, then it can't be removed
   382	    if [[ $3 == required ]] || [[ $3 == runtime ]]; then
   383	      options=( "Ignore" "Dispel $1" "Cast $2" "${options[@]}" )
   384	    elif [[ $3 == optional ]] ; then
   385	      options=( "Ignore" "Dispel $1" "Cast $2" "Recast $1 without $2" "${options[@]}" )
   386	    elif [[ $3 == suggest ]] ; then
   387	      options=( "Ignore" "Dispel $1" "Cast $2" "Disable $2 in $1" "${options[@]}" )
   388	    fi
   389	
   390	    #Get the user's choice of action
   391	    select_list choice "" "${options[@]}"
   392	    case ${choice} in
   393	      I*)  message "Ignoring."                ;;
   394	      Dispel*)  echo $1 >> $TMP_DIR/prune.dispel    ;;
   395	      C*)  echo $2 >> $TMP_DIR/prune.cast      ;;
   396	      R*)  echo $1 >> $TMP_DIR/prune.cast
   397	        # Revise choice in the depends file
   398	        sed 's/^\('$1':'$2':\)on\(:optional:.*\)$/\1off\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
   399	        mv $TMP_DIR/prune.1 $DEPENDS_STATUS
   400	        ;;
   401	      Disable*)
   402	        sed 's/^\('$1':'$2':\)on\(:suggest:.*\)$/\1off\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
   403	        mv $TMP_DIR/prune.1 $DEPENDS_STATUS
   404	        ;;
   405	      S*)  sed 's/^\('$1':\)'$2'\(('$4'):.*\)$/\1'$provider'\2/' $DEPENDS_STATUS > $TMP_DIR/prune.1
   406	        mv $TMP_DIR/prune.1 $DEPENDS_STATUS
   407	        if [[ $3 == required ]] || [[ $3 == optional ]]; then
   408	          echo $1 >>$TMP_DIR/prune.cast
   409	        fi
   410	        ;;
   411	      *)  message "We should never get here."
   412	        exit 1
   413	        ;;
   414	    esac
   415	    echo
   416	    shift 4
   417	  done
   418	
   419	  unlock_file $DEPENDS_STATUS
   420	
   421	  # put the spells in two files for later perusal
   422	  if [[ $action ]] ; then
   423	    [ -s $TMP_DIR/prune.dispel ] &&
   424	      dispel --noqueue $(sort -u $TMP_DIR/prune.dispel)
   425	    [ -s $TMP_DIR/prune.cast ] &&
   426	      cast -c $(sort -u $TMP_DIR/prune.cast)
   427	  else
   428	    local prune_dispel prune_cast t
   429	    prune_dispel=$(sort -u $TMP_DIR/prune.dispel 2>/dev/null)
   430	    prune_cast=$(sort -u $TMP_DIR/prune.cast 2>/dev/null)
   431	    if [[ -z $prune_dispel$prune_cast ]]; then
   432	      message "Pruning done."
   433	      return 0
   434	    fi
   435	    t=$(date +%Y%m%d%s)
   436	    [ "$prune_dispel" ] &&
   437	      echo dispel $prune_dispel > $BACKUPDIR/prune.$t
   438	    [ "$prune_cast" ] &&
   439	      echo cast -c $prune_cast >> $BACKUPDIR/prune.$t
   440	    cat $BACKUPDIR/prune.$t
   441	    message "For future reference, lines are stored in $BACKUPDIR/prune.$t"
   442	  fi
   443	  message "Pruning done."
   444	}
   445	
   446	#----------------------
   447	## Find packages entries that are corrupted, or non-existant
   448	## @Stdout Misc status output
   449	#----------------------
   450	function packages()
   451	{
   452	  local spell t LINE
   453	  message "Fixing packages..."
   454	  lock_file $SPELL_STATUS
   455	
   456	  message "Pass 1 (bad lines)"
   457	  sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/!p' $SPELL_STATUS > $TMP_DIR/packages.bad
   458	  if [ -s $TMP_DIR/packages.bad ] ; then
   459	    sed -n '/^[^:]*:[^:]*:[^:]*:[^:]*$/p' $SPELL_STATUS > $TMP_DIR/packages.good
   460	    echo "$(wc -l $TMP_DIR/packages.bad) bad lines, $(wc -l $TMP_DIR/packages.good) good lines in your packages file."
   461	    t=$(date +%Y%m%d%s)
   462	    message "\tSaving bad lines to $BACKUPDIR/packages.1.bad.$t"
   463	    mv $TMP_DIR/packages.bad $BACKUPDIR/packages.1.bad.$t
   464	    message "\tRemoving bad entries"
   465	    mv $TMP_DIR/packages.good $SPELL_STATUS
   466	  fi
   467	
   468	  message "Pass 2 (non-existant spells)"
   469	  t=""
   470	  for spell in $( sed -n 's/^\([^:]*\):.*$/\1/p' $SPELL_STATUS ) ; do
   471	    if ! [[ $( codex_find_spell_by_name $spell ) ]] ; then
   472	      message "\t$spell"
   473	      t="found"
   474	    fi
   475	  done
   476	  if [[ $t ]] ; then
   477	    message "Entries for non-existant spells will NOT be automaticly removed."
   478	    message "You may have removed the grimoire with the spell"
   479	    message "or the name may have changed. You should figure out where"
   480	    message "these spells went. They may have been deprecated and then"
   481	    message "removed. You may want to dispel these if that is the case."
   482	  fi
   483	
   484	  message "Pass 3 (duplicate entries)"
   485	  t=$(
   486	    for LINE in $( sed -n 's/^\([^:]*\):\([^:]*\):.*$/\1/p' $SPELL_STATUS | sort | uniq -d ) ; do
   487	      grep "^$LINE:" $SPELL_STATUS | sed 's/^/\t/'
   488	    done
   489	  )
   490	  if [[ $t ]] && query "\tRemove all but one of each set of duplicates?" "n" ; then
   491	    awk -F: '{if(!LINES[$1]) { LINES[$1]=1; print $0; } }' $SPELL_STATUS > $TMP_DIR/packages.3
   492	    message "\tRemoving bad entries"
   493	    mv $TMP_DIR/packages.3 $SPELL_STATUS
   494	  fi
   495	
   496	  unlock_file $SPELL_STATUS
   497	  message "Done fixing packages."
   498	}
   499	
   500	#-----------------------------
   501	##
   502	## Checks and fixes a list of spells. If no spells
   503	## are specified, then all installed spells are checked.
   504	## If the first arg is 'nofix', then no fixing will be done
   505	## Just error detection
   506	##
   507	## @Args [nofix] [spell [spell [...]]]
   508	## @Returns May return non zero if there was a problem.
   509	##
   510	#-----------------------------
   511	function cleanse_fix()
   512	{
   513	
   514	  local FIX=$1
   515	  shift 1
   516	  local SPELL
   517	  local SPELLS=$*
   518	
   519	  # find the binary executable make, we cant call it directly since make may
   520	  # be a function in this context
   521	  local REAL_MAKE
   522	  find_make REAL_MAKE || return $?
   523	
   524	  # given a list of spells to check
   525	  if [ -z "$SPELLS" ]; then
   526	    # I think the behavior will be to not check held spells by default
   527	    # because they are held, but if they are explicitly specified check them
   528	    SPELLS=$(get_all_spells_with_status installed|tr '\n' ' ')
   529	  else
   530	    local tmp_spells
   531	    # ensure they are all installed
   532	    for SPELL in $SPELLS ; do
   533	      spell_ok $SPELL &&
   534	      tmp_spells="$tmp_spells $SPELL" ||
   535	      message "${SPELL_COLOR}$SPELL${DEFAULT_COLOR}" \
   536	              "is not installed or held, not fixing"
   537	    done
   538	    SPELLS=$tmp_spells
   539	    if [ -z "$SPELLS" ] ; then
   540	      message "${PROBLEM_COLOR}no installed spells to fix${DEFAULT_COLOR}"
   541	      exit
   542	    fi
   543	  fi
   544	  # get the installed depends hash
   545	  compute_installed_depends depends_hash
   546	
   547	  # generate a makefile from the spells + depends hash
   548	  local MAKEFILE=$TMP_DIR/Makefile
   549	  (
   550	  for i in $( hash_get_table_fields depends_hash ) ; do
   551	    echo "$i : $(hash_get "depends_hash" "$i")"
   552	    echo -e "\t@echo $i"
   553	    echo
   554	  done
   555	  echo
   556	  echo %:
   557	  echo -e "\t@echo -n"
   558	  echo
   559	  echo "all : $SPELLS"
   560	  echo
   561	  echo ".PHONY : all $SPELLS"
   562	  ) > $MAKEFILE
   563	
   564	  # load our ldd hash with the spells we know we'll care about
   565	  # but no more then necessary
   566	  cleanse_fix_init_ldd_check $($REAL_MAKE -s -f $MAKEFILE all 2>/dev/null)
   567	
   568	  local PASSED FIXED HOPELESS TOTALNR CURNR unused
   569	
   570	  CURNR=0
   571	
   572	  if [ $FIX == quick ] ; then
   573	    TOTALNR=0 #counting them like this doesn't seem to take substantial time
   574	    for unused in $SPELLS ; do let TOTALNR++ ; done
   575	    for SPELL in $SPELLS ; do
   576	      let CURNR++
   577	      if cleanse_fix_run_checks $SPELL $CURNR $TOTALNR; then
   578	        PASSED="${PASSED}${SPELL}"$'\n'
   579	      else
   580	        message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
   581	                 "is broken${DEFAULT_COLOR}\n"
   582	        HOPELESS="${HOPELESS}${SPELL}"$'\n'
   583	      fi
   584	    done
   585	  else
   586	    # non-quick mode implies we check from the bottom up, so get all
   587	    # the spells..
   588	    TOTALNR=$($REAL_MAKE -s -f $MAKEFILE all 2>/dev/null|wc -l)
   589	    cleanse_fix_depends all
   590	  fi
   591	
   592	  if [[ $PASSED ]] ; then
   593	    message "${MESSAGE_COLOR}The following spells passed the" \
   594	            "check:${SPELL_COLOR}"
   595	    echo "$PASSED"|sort -u|column
   596	    message "${DEFAULT_COLOR}"
   597	  fi
   598	  if [[ $FIXED ]] ; then
   599	    message "${MESSAGE_COLOR}The following spells were broken and" \
   600	            "fixed:${QUERY_COLOR}"
   601	    echo "$FIXED"|sort -u|column
   602	    message "${DEFAULT_COLOR}"
   603	  fi
   604	  if [[ $HOPELESS ]] ; then
   605	    if [[ "$FIX" == quick ]]; then
   606	      message "${MESSAGE_COLOR}The following spells were broken:" \
   607	              "${PROBLEM_COLOR}"
   608	    else
   609	      message "${MESSAGE_COLOR}The following spells were broken and" \
   610	              "could not be fixed:${PROBLEM_COLOR}"
   611	    fi
   612	    echo "$HOPELESS"|sort -u|column
   613	    message "${DEFAULT_COLOR}"
   614	    return 1
   615	  fi
   616	  return 0
   617	}
   618	
   619	function cleanse_fix_depends() {
   620	  local SPELL=$1
   621	  # run the makefile to get the proper order
   622	  local FIX_ORDER=$($REAL_MAKE -s -f $MAKEFILE $1  2>/dev/null| grep -v "^$SPELL\$")
   623	  #for each spell
   624	  for SPELL in ${FIX_ORDER}; do
   625	    # check it
   626	    let CURNR++
   627	    if cleanse_fix_run_checks $SPELL $CURNR $TOTALNR; then
   628	      PASSED="${PASSED}${SPELL}"$'\n'
   629	    else
   630	      if [ $FIX == fix ] ; then
   631	        # if its broken try to fix it
   632	        cleanse_fix_spell $SPELL
   633	      else
   634	        message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
   635	                 "is broken${DEFAULT_COLOR} and not being fixed\n"
   636	        HOPELESS="${HOPELESS}${SPELL}"$'\n'
   637	      fi
   638	    fi
   639	  done
   640	}
   641	
   642	#----------------------------
   643	##
   644	## cast a spell and recheck it to see if its actually fixed
   645	## we assume in this case that all its dependents are fixed/hopeless
   646	##
   647	## if the spell cannot be fixed we complain and append it to the HOPELESS list
   648	##
   649	## @param Spell to check
   650	## @return 0 if spell is fixed 1 if not
   651	##
   652	#----------------------------
   653	function cleanse_fix_spell() {
   654	  local SPELL=$1
   655	  if spell_held $SPELL ; then
   656	    HOPELESS="${HOPELESS}${SPELL}"$'\n'
   657	    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
   658	             "is broken broken, but also held, not fixing${DEFAULT_COLOR}"
   659	    return 1
   660	  fi
   661	
   662	  cast -c $SPELL                &&
   663	  cleanse_fix_run_checks $SPELL &&
   664	  FIXED="${FIXED}${SPELL}"$'\n' &&
   665	  return 0
   666	
   667	  # if we get here the spell is deemed hopeless
   668	
   669	  HOPELESS="${HOPELESS}${SPELL}"$'\n'
   670	  message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}" \
   671	           "is hopelessly broken and we can't fix it${DEFAULT_COLOR}"
   672	  return 1
   673	}
   674	
   675	#--------------------------
   676	##
   677	## Run the checks on a spell.
   678	## @args Spell [check nr,total nr]
   679	## @return Should return 0 if all the checks completed
   680	##
   681	#--------------------------
   682	function cleanse_fix_run_checks()  {
   683	  local SPELL=$1
   684	  if [ $# -eq 1 ] ;then
   685	    set_term_title "Checking $SPELL"
   686	    message  "Checking ${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"
   687	  else
   688	    set_term_title "Checking $SPELL ($2 of $3)"
   689	    message  "Checking ${SPELL_COLOR}${SPELL}${DEFAULT_COLOR} ($2 of $3)"
   690	  fi
   691	  if ! spell_ok $SPELL; then
   692	    message "${SPELL_COLOR}${SPELL}${MESSAGE_COLOR_COLOR}" \
   693	            "was dispelled in the meanwhile, skipping check!${DEFAULT_COLOR}"
   694	    return 0
   695	  fi
   696	  local VERSION=$(private_installed_version $SPELL)
   697	
   698	  cleanse_fix_find_check    $SPELL  $VERSION    &&
   699	  cleanse_fix_ldd_check     $SPELL  $VERSION    &&
   700	  cleanse_fix_sym_check     $SPELL  $VERSION    &&
   701	  cleanse_fix_md5sum_check  $SPELL  $VERSION
   702	
   703	}
   704	
   705	
   706	#-----------------------
   707	##
   708	## Check that all files installed still exist.
   709	## @FIXME The skipped directories should be made configurable somewhere
   710	## @return 0 The files were all in existance
   711	## @return 1 At least one file was found missing
   712	##
   713	#-----------------------
   714	function cleanse_fix_find_check()  {
   715	  if  [ "$FIND_CHECK" == "off" ] ; then
   716	    return 0
   717	  fi
   718	
   719	  local SPELL=$1
   720	  local I_LOG="$INSTALL_LOGS/$SPELL-$2"
   721	  local BADLIST=""
   722	  local LINE
   723	
   724	  message "\tFile test"
   725	
   726	  if ! [ -f $I_LOG ] ; then
   727	    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"     \
   728	             "is missing an install log.${DEFAULT_COLOR}"
   729	    return 1;
   730	  fi
   731	
   732	  local size=$(wc -l < $I_LOG)
   733	  local count=0
   734	
   735	  local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
   736	  mkdir -p $TMP_DIR/${SPELL}
   737	  rm -f $TMP_I_LOG
   738	  log_adjuster $I_LOG /dev/stdout log filterable  |
   739	  filter_volatiles                                |
   740	  log_adjuster /dev/stdin $TMP_I_LOG filterable root
   741	
   742	  while read LINE ; do
   743	    progress_bar $count $size 50
   744	    if  [ !  -e  "$LINE"  ];  then
   745	      BADLIST="${BADLIST}${LINE}"$'\n'
   746	    fi
   747	    let count++
   748	  done < $TMP_I_LOG
   749	  rm -f $TMP_I_LOG
   750	  clear_line
   751	
   752	  if [[ $BADLIST ]] ; then
   753	    message "${PROBLEM_COLOR}The following files from"             \
   754	            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"               \
   755	            "${PROBLEM_COLOR}don't exist: "$'\n'"${DEFAULT_COLOR}"
   756	    message "$BADLIST"
   757	
   758	    return 1
   759	  fi
   760	
   761	  return 0
   762	
   763	}
   764	
   765	#-----------------------
   766	##
   767	## Initialize the ldd check, builds up a hash table in ldd_hash of
   768	## what library directories a spell has libraries in, this is because
   769	## some spells have their own private libraries and arent happy if
   770	## they dont know where they are
   771	##
   772	#-----------------------
   773	function cleanse_fix_init_ldd_check()  {
   774	  if  [ "$LDD_CHECK" == "off" ] ;  then
   775	    return 0
   776	  fi
   777	  local SPELL
   778	  local I_LOG
   779	  local size count
   780	  let size=0
   781	  let count=0
   782	  message "Doing initialization for the ldd check"
   783	  for SPELL in $* ; do
   784	    let size++
   785	  done
   786	  for SPELL in $* ; do
   787	    I_LOG=$INSTALL_LOGS/$SPELL-$(private_installed_version  $SPELL)
   788	    hash_put ldd_hash $SPELL "$(grep '\.so$' $I_LOG|get_dirnames|sort -u|tr '\n' :)"
   789	    progress_bar $count $size 50
   790	    let count++
   791	  done
   792	  clear_line
   793	}
   794	
   795	#-----------------------
   796	##
   797	## Check that all libraries the spell binaries need exist
   798	## @return 0 The libraries have their requirements met
   799	## @return 1 At least one library isnt happy
   800	##
   801	#-----------------------
   802	function cleanse_fix_ldd_check()  { (
   803	  if  [ "$LDD_CHECK" == "off" ] ;  then
   804	    return 0
   805	  fi
   806	  message "\tLibrary test"
   807	
   808	  local SPELL=$1
   809	  local I_LOG=$INSTALL_LOGS/$SPELL-$2
   810	  local BADLIST=""
   811	  local LINE
   812	
   813	  if ! [ -f $I_LOG ] ; then
   814	    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"     \
   815	             "is missing an install log.${DEFAULT_COLOR}"
   816	    return 1;
   817	  fi
   818	
   819	  local tmp
   820	  for each in $($REAL_MAKE -s -f $MAKEFILE $1 2>/dev/null); do
   821	    hash_get_ref ldd_hash $each tmp
   822	    [[ $tmp ]] && LD_LIBRARY_PATH="$tmp:${LD_LIBRARY_PATH}"
   823	  done
   824	
   825	  local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
   826	  mkdir -p $TMP_DIR/${SPELL}
   827	  log_adjuster $I_LOG /dev/stdout filterable      |
   828	  filter_volatiles                                |
   829	  log_adjuster /dev/stdin $TMP_I_LOG filterable root
   830	
   831	  local count size
   832	  let count=0
   833	  local size=$(wc -l < $TMP_I_LOG)
   834	  export LD_LIBRARY_PATH
   835	  while read LINE; do
   836	
   837	    progress_bar $count $size 50
   838	    let count++
   839	    if     [  -f  "$LINE"  ]     &&
   840	        file  -b  "$LINE"        |
   841	        grep  -q  "ELF"          &&
   842	        ldd       "$LINE"  2>&1  |
   843	        grep  -q  "not found"
   844	    then
   845	      BADLIST="${BADLIST}${LINE}"$'\n'
   846	    fi
   847	  done < $TMP_I_LOG
   848	  rm -f $TMP_I_LOG
   849	  clear_line
   850	
   851	  if [[ $BADLIST ]] ; then
   852	    message "${PROBLEM_COLOR}The following files from"                  \
   853	            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"                   \
   854	            "${PROBLEM_COLOR}have missing library dependencies:"       \
   855	            "${DEFAULT_COLOR}"
   856	    echo "$BADLIST"|while read LINE ; do
   857	      echo $LINE
   858	      ldd $LINE 2>&1|grep "not found"
   859	    done
   860	    return 1
   861	  fi
   862	
   863	  return 0
   864	
   865	) }
   866	
   867	#-----------------------
   868	##
   869	## Check that all symlink targets exist
   870	## @return 0 The symlinks point to real files
   871	## @return 1 At least one symlink doesnt have its target
   872	##
   873	#-----------------------
   874	function cleanse_fix_sym_check() {
   875	  if [ "$SYM_CHECK" == "off" ] ; then
   876	    return 0
   877	  fi
   878	  local SPELL=$1
   879	  local I_LOG="$INSTALL_LOGS/$SPELL-$2"
   880	  local BADLIST=""
   881	  local LINE
   882	
   883	  message "\tSymlink test"
   884	
   885	  if ! [ -f $I_LOG ] ; then
   886	    message  "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"     \
   887	             "is missing an install log.${DEFAULT_COLOR}"
   888	    return 1;
   889	  fi
   890	
   891	  local TMP_I_LOG=$TMP_DIR/${SPELL}/install.log
   892	  mkdir -p $TMP_DIR/${SPELL}
   893	  rm -f $TMP_I_LOG
   894	  log_adjuster $I_LOG /dev/stdout log filterable |
   895	  filter_volatiles                               |
   896	  log_adjuster /dev/stdin $TMP_I_LOG filterable root
   897	
   898	  local size=$(wc -l < $TMP_I_LOG)
   899	  local count=0
   900	
   901	  while read LINE ; do
   902	    progress_bar $count $size 50
   903	    # check that it is a symlink first, then if it is broken
   904	    # readlink -e requires all path elements to exist
   905	    if [[ -h $LINE ]] && ! readlink -q -e "$LINE" > /dev/null; then
   906	      BADLIST="${BADLIST}${LINE}"$'\n'
   907	    fi
   908	    let count++
   909	  done < $TMP_I_LOG
   910	  rm -f $TMP_I_LOG
   911	  clear_line
   912	
   913	  if [[ $BADLIST ]] ; then
   914	    message "${PROBLEM_COLOR}The following symlinks from"             \
   915	            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}"                  \
   916	            "${PROBLEM_COLOR}don't link to files it installed:"       \
   917	            "${DEFAULT_COLOR}"
   918	    message "$BADLIST"
   919	
   920	    return 1
   921	  fi
   922	
   923	  return 0
   924	}
   925	
   926	#-----------------------
   927	##
   928	## Verify the md5sum of all the files
   929	## @return 0 The md5sum of a file is incorrect
   930	## @return 1 At least one md5sum is wrong
   931	##
   932	#-----------------------
   933	function cleanse_fix_md5sum_check() {
   934	
   935	  if  [ "$MD5SUM_CHECK" == "off" ] ; then
   936	    return 0
   937	  fi
   938	  local SPELL=$1
   939	  local MD5_LOG="$MD5SUM_LOGS/$SPELL-$2"
   940	  local BADLIST
   941	  local TMP_MD5_LOG=$TMP_DIR/cleanse.md5sum.tmp.$SPELL.$$
   942	  local BROKEN1=$TMP_DIR/cleanse.md5sum.broken1.$SPELL.$$
   943	  local BROKEN2=$TMP_DIR/cleanse.md5sum.broken2.$SPELL.$$
   944	  local A=$'\a' # magic sed seperator, assume files dont have bells in the name
   945	
   946	  # Ignore standard paths we know will change, the install log, file log,
   947	  # compile log, and score files that live in /var/games (bug 7500)
   948	
   949	  message -n "\tChecking md5sums."
   950	
   951	  if ! [ -f $MD5_LOG ] ; then
   952	    message  "${SPELL_COLOR}${SPELL} "         \
   953	             "${PROBLEM_COLOR}is missing an md5 log.${DEFAULT_COLOR}"
   954	    return 1;
   955	  fi
   956	  touch $BROKEN1 $BROKEN2
   957	
   958	  message -n '.'
   959	
   960	  log_adjuster $MD5_LOG $TMP_MD5_LOG log root md5_log_filter
   961	
   962	
   963	  # for some reason stuffing the output of this in a variable loses the
   964	  # newlines and thus files with spaces in them get lost
   965	  LANG=C md5sum --check $TMP_MD5_LOG 2>/dev/null | grep ': FAILED$' |
   966	            sed "s${A}: FAILED\$${A}${A}" > $BROKEN1
   967	  # cant use awk above with : as a seperator due to files with :'s in their name
   968	
   969	  # filters dont work on md5sum logs, filter after md5sum is done
   970	  # this is lame but oh well, this function is ugly enough as it is
   971	  filter_volatiles < $BROKEN1 > $BROKEN2
   972	
   973	  message -n '.'
   974	
   975	  # the files with bad md5s may be owned by another spell (either legitimatly
   976	  # or by spell writer oversight) if so remove the entry from
   977	  # the spell's install and md5sum logs, and enter it in the possessed
   978	  # log. Someday dispel et. al. will use this for stuff, for now
   979	  # its for informational purposes
   980	
   981	  # we have to dump out to a file because I cant use message
   982	  # inside $( ), and variables are stupid about spaces
   983	  local FOUND_IN REAL_MD5 possessed INSTALL_LOG
   984	  rm $BROKEN1
   985	  while read LINE; do
   986	    message -n '.'
   987	    [[ ! $LINE ]]  && continue
   988	    test -h $LINE && continue
   989	    REAL_MD5=$(md5sum $LINE)
   990	    # find the file in some log somewhere...
   991	    FOUND_IN=$(echo $MD5SUM_LOGS/* | xargs grep -l "^$REAL_MD5\$")
   992	
   993	    # remove anything from a non-current version of the spell
   994	    # this is ugly but the only reasonable way I know of to do this...
   995	    FOUND_IN=$(for each in $FOUND_IN; do
   996	                # make the assumption that spell versions dont contain dashes...
   997	                # this actually isnt a very good assumption but works enough
   998	                # of the time
   999	                each_spell=$(smgl_basename $each|awk -F- '{for (i=1;i<NF-1;i++) {printf "%s-",$i}printf "%s\n",$i}')
  1000	                each_version=$(private_installed_version $each_spell)
  1001	                [[ $each_version ]] &&
  1002	                [[ $MD5SUM_LOGS/${each_spell}-${each_version} == $each ]] &&
  1003	                echo $each
  1004	              done)
  1005	
  1006	    if [[ ! $FOUND_IN ]]; then
  1007	      echo $LINE >> $BROKEN1
  1008	      continue
  1009	    fi
  1010	
  1011	    message "\n${PROBLEM_COLOR}Incorrect md5sum for" \
  1012	            "${FILE_COLOR}$LINE${DEFAULT_COLOR}"
  1013	    message "but matches in:\n$FOUND_IN"
  1014	
  1015	    if [ $FIX == fix ] || [ $FIX == quick ]; then
  1016	      message "Removing from $SPELL's install and md5sum logs."
  1017	
  1018	      INSTALL_LOG=$INSTALL_LOGS/$SPELL-$2
  1019	      # there is a slight bug with all these transactions due to
  1020	      # the fact that we have no stdin, if a transaction fails the user
  1021	      # is prompted, but since we're in this funny loop, it'll read from
  1022	      # the wrong place a future improvement would be
  1023	      # to use iterate or some other method that is not naive to
  1024	      # filenames with spaces in them
  1025	      lock_start_transaction "$INSTALL_LOG" tfile
  1026	      sed "s${A}^$LINE\$${A}${A}" $INSTALL_LOG|tr -s '\n'  > $tfile
  1027	      lock_commit_transaction $INSTALL_LOG
  1028	      message -n '.'
  1029	
  1030	      lock_start_transaction "$MD5_LOG" tfile
  1031	      sed "s$A.*  $LINE\$${A}${A}"  $MD5_LOG|tr -s '\n'  > $tfile
  1032	      lock_commit_transaction $MD5_LOG
  1033	      message -n '.'
  1034	
  1035	      test -d $POSSESSED_LOGS || mkdir -p $POSSESSED_LOGS
  1036	
  1037	      possessed=$POSSESSED_LOGS/$SPELL-$2
  1038	      lock_start_transaction "$possessed" tfile
  1039	      echo $LINE >> $tfile
  1040	      # remove duplicates
  1041	      cp $tfile $TMP_DIR/$SPELL.possessed.$$
  1042	      sort -u $TMP_DIR/$SPELL.possessed.$$ > $tfile
  1043	      rm  $TMP_DIR/$SPELL.possessed.$$
  1044	      lock_commit_transaction $possessed
  1045	
  1046	      message -n '.'
  1047	    fi
  1048	  done  < $BROKEN2
  1049	
  1050	  message -n '.'
  1051	
  1052	  if test -s $BROKEN1 ; then
  1053	    BADLIST=$(cat $BROKEN1)
  1054	  else
  1055	    BADLIST=""
  1056	  fi
  1057	  rm $BROKEN1 $BROKEN2 &>/dev/null
  1058	
  1059	  message ''
  1060	
  1061	
  1062	  if [[ $BADLIST ]] ; then
  1063	    message "${PROBLEM_COLOR}The following files have been modified from"  \
  1064	            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}:"
  1065	    message "$BADLIST"
  1066	    return 1
  1067	  fi
  1068	  return 0
  1069	}
  1070	
  1071	source /etc/sorcery/config
  1072	
  1073	if [ $UID -eq 0 ] ; then
  1074	
  1075	  # Make sure root is running this
  1076	  if [ $# -eq 0 ] ; then
  1077	    # IF there are no args, assume the full treatment is desired
  1078	    exec cleanse --packages --delint --logs --prune doit --tablet --sweep --fix
  1079	  fi
  1080	
  1081	  mk_tmp_dirs backup /tmp/cleanse
  1082	  BACKUPDIR=$TMP_DIR
  1083	  unset TMP_DIR
  1084	  mk_tmp_dirs cleanse
  1085	  if [ ! -d $TMP_DIR ] || [ ! -d $BACKUPDIR ] ; then
  1086	    message "Unable to make tmp dirs..."
  1087	    exit 1
  1088	  fi
  1089	  args "$@"
  1090	  rc=$?
  1091	  cleanup_tmp_dir $TMP_DIR
  1092	  message "Removed information is backed up to $BACKUPDIR."
  1093	  exit $rc
  1094	else
  1095	  if [[ $1 == --help ]] ; then
  1096	    usage
  1097	    exit 0
  1098	  fi
  1099	  # validate the parameters before su-ing, since we may still drop out
  1100	  verify_parameters "$@"
  1101	
  1102	  message "You must be root to run this command."
  1103	  message "Switching to root user..."
  1104	  PARAMS=$(consolidate_params "$@")
  1105	  exec su -c "cleanse $PARAMS" root
  1106	fi