#!/bin/bash
#---------------------------------------------------------------------
##
## @Synopsis Set of functions used by the internal sorcery scripts
##
## This should really be home to things related to the sorcery script
## itself, not a repository for other functions. (Andrew 5/29/04)
##
## @Copyright Original version Copyright 2001 by Kyle Sallee
## @Copyright Additions/Corrections Copyright 2002 by the Source Mage Team
##
#---------------------------------------------------------------------

#---------------------------------------------------------------------
## @param branch
##
## Updates the sorcery scripts to their latest version for specified
## branch.
##
#---------------------------------------------------------------------
function update_sorcery_scripts() {

  local BRANCH=$1

  debug "libsorcery" "update_sorcery_scripts() (sorcery-$BRANCH)"

  {
    #This section is to make sure that all sorcery dependencies are cast
    #Keep this outer if until basesystem is in the grimoires properly.
    #(2002/10/13)
    if [[ `codex_find_spell_by_name basesystem` ]] ; then
      if ! spell_ok basesystem  ; then
        cast basesystem
      fi
      if [ "$UPDATEFIX" == on ] ; then
        cleanse --fix basesystem
      fi
    fi
  }

  pushd $TMP_DIR &>/dev/null
  # sorcery is not a spell
  # there is no spoon
  SOURCE=sorcery-$BRANCH.tar.bz2
  SOURCE_DIRECTORY=$BUILD_DIRECTORY/sorcery
  SOURCE_URL=$SORCERY_URL/$SOURCE

  message  "${MESSAGE_COLOR}Downloading source file"       \
           "${FILE_COLOR}${SOURCE}${DEFAULT_COLOR}"     \
           "for sorcery update."
  url_download "$SOURCE" "$SOURCE_URL" "" "sr_target" "sr_type" || {
    message "${PROBLEM_COLOR}Failed to download sorcery${DEFAULT_COLOR}"
    return 1
  }
  if [[ $sr_type != file ]] ; then
    message "Downloaded type is not a file, file a bug if you see this"
    return 1
  fi
  [[ $sr_target != $SOURCE ]] && mv $sr_target $SOURCE

  gpg_verify_sorcery $SOURCE $SORCERY_URL
  rc=$?

  gpg_user_query $rc $SORCERY_BRANCH sorcery || return 1
  popd &>/dev/null

  local FILENAME=$TMP_DIR/$SOURCE
  # copy the tarball to /var/spool/sorcery after we've downloaded and
  # verified it.
  cp $FILENAME $SOURCE_CACHE

  mk_source_dir  $SOURCE_DIRECTORY              &&
  cd $BUILD_DIRECTORY
  bzip2  -cdf   $FILENAME                       |
  tar --owner=root --group=root -xf /dev/stdin  &&
  cd      $SOURCE_DIRECTORY                     &&

  echo `pwd`                                    || return 1

  # last chance to lock
  message -n "${MESSAGE_COLOR}Waiting for${DEFAULT_COLOR} ${SPELL_COLOR}all" \
             "${DEFAULT_COLOR} ${MESSAGE_COLOR}spells to complete..." \
             "${DEFAULT_COLOR}"
  lock_resources "solo" "cast" # prevent other casts
  excllock_resources "cast" "sorcery" # wait for other casts
  message "done."
  lock_resources "libgrimoire" "install"      &&

  if test -x ./uninstall && test -f /etc/sorcery/install.log ; then
    message uninstalling...
    ./uninstall
  else
    # rough equivalent to prepare_install...
    rm /etc/sorcery/* 2>/dev/null
    rm /etc/sorcery/licenses/*   2>/dev/null
    rm /etc/sorcery/mirrors/* 2>/dev/null
    rm /var/lib/sorcery/* 2>/dev/null
    rm -r /var/lib/sorcery/modules/* 2>/dev/null
    rm -r /var/lib/sorcery/archspecs/* 2>/dev/null
    rm -r /var/lib/sorcery/build/* 2>/dev/null
  fi
  ./install                                     &&

  cd /                                          &&
  unlock_resources "libgrimoire" "install"      &&

  SORCERY_VERSION=`cat /etc/sorcery/version`

  unlock_resources "solo" "cast"
  unlock_resources "cast" "sorcery"

  if [ $? -eq 0 ] ; then
    activity_log "update" "sorcery-$BRANCH" "$SORCERY_VERSION" "success"
    rm_source_dir
  else
    activity_log "update" "sorcery-$BRANCH" "$SORCERY_VERSION" "failure"
    if [[  $CLEAN_SOURCE == on ]]; then
      rm_source_dir
    fi
  fi
  message "${MESSAGE_COLOR}Current${SPELL_COLOR}" \
          "sorcery-$BRANCH${DEFAULT_COLOR} ${MESSAGE_COLOR}version" \
          "${VERSION_COLOR}$SORCERY_VERSION${DEFAULT_COLOR}"
  sorcery_history
  rm $FILENAME
}

#---------------------------------------------------------------------
##
## Menu query to user asking to set the nice value which sorcery should
## use to run proccesses at.
##
#---------------------------------------------------------------------
function set_nice()  {

  PROMPT="Please enter the process priority sorcery should run at."

  if  NICE=`eval $DIALOG '  --ok-label  "Commit"  \
                            --inputbox            \
                            "$PROMPT"             \
                            0 0  "$NICE"'`
  then
     modify_local_config "NICE" "$NICE"
  fi

}

#---------------------------------------------------------------------
##
## Menu query to user asking to set the umask value which sorcery should
## use to run proccesses with.
##
#---------------------------------------------------------------------
function set_umask()  {

  PROMPT="Please enter the permissions mask sorcery should run with."

  if  UMASK=`eval $DIALOG '  --ok-label  "Commit"  \
                            --inputbox            \
                            "$PROMPT"             \
                            0 0  "$UMASK"'`
  then
     modify_local_config "UMASK" "$UMASK"
  fi

}


#---------------------------------------------------------------------
## @param filename
##
## Displays and/or mails the contents of the file given as the first
## argument.
##
#---------------------------------------------------------------------
function report() {

  if  !  [  -f  $1  ];  then  return;  fi

  if  [  "$VIEW_REPORTS"  ==  "on"  ];  then

    debug  "libsorcery" "Prompting to view $2 for $SPELL"

    VIEW_PROMPT="View $2 for ${SPELL_COLOR}${SPELL}-${VERSION_COLOR}${VERSION}${DEFAULT_COLOR}?"
    sound  REPORT
    if  query  "$VIEW_PROMPT" n;  then  $PAGER  $1;  fi
  fi

  if  [  "$MAIL_REPORTS"  ==  "on"  ];  then
    debug  "libsorcery" "Mailing report ($2) for $SPELL to $SORCERER"
    date  -u  |
    mail  -s  "Sorcery Report : $HOSTNAME : $2 for $SPELL-$VERSION"  \
              -a $1 $SORCERER 2>/dev/null
  fi

  true

}


#---------------------------------------------------------------------
## @param filename
##
## Given a filename, will return the actual filename if a similar
## filename with a different extension exists.
##
#---------------------------------------------------------------------
function guess_filename()  {

  FILENAME=$1

  debug  "libsorcery" "Running guess_filename() on $FILENAME"

  BASENAME=`echo  $FILENAME         |
            sed  "s/\.tar\.gz$//"   |
            sed  "s/\.tgz$//"       |
            sed  "s/\.tar\.bz2$//"`

  if    [  -f  $FILENAME          ];  then  echo  $FILENAME
  elif  [  "$FUZZ"  ==  "off"     ];  then  return  1
  elif  [  -f  $BASENAME.tar.gz   ];  then  echo  $BASENAME.tar.gz
  elif  [  -f  $BASENAME.tar.bz2  ];  then  echo  $BASENAME.tar.bz2
  elif  [  -f  $BASENAME.tgz      ];  then  echo  $BASENAME.tgz
  else  false
  fi

}

#-------------------------------------------------------------------------
## check if the file exists, or one with a similar compression exists
## @param file
#-------------------------------------------------------------------------
function file_exists()  {
  guess_filename  $1  >  /dev/null
}

#---------------------------------------------------------------------
##
## Saves the current libraries associated with the spell, from /lib
## and /usr/lib into $SOURCE_DIRECTORY/old.libraries/
## Also runs ldconfig with the saved libraries.
##
#---------------------------------------------------------------------
function save_libraries()  {

  if    [  -z  "$SOURCE_DIRECTORY"  ]
  then  return
  fi

  debug  "libsorcery" "Running save_libraries()"

  OLD_LIBS=$SOURCE_DIRECTORY/old.libraries
  mkdir  -p  $OLD_LIBS

  SAVED=$OLD_LIBS/$SPELL.saved.libraries
  lock_file $SAVED
  rm  -rf  $SAVED

  OLD_VERSION=`installed_version  $SPELL`
      OLD_LOG=$INSTALL_LOGS/$SPELL-$OLD_VERSION

  grep   "^/lib/\|^/usr/lib"  $OLD_LOG  |
  while  read        LINE;  do

    if   [  -f      $LINE   ]  &&
         file  -bL  $LINE   |
         grep  -q   "shared object"
    then
      if  [  -h  $LINE  ];  then
        DEST=$(  basename  $(  ls   -la  "$LINE"  |
                               cut  -d  '>'  -f2  |
                               cut  -c  2-
                            )
              )
        ln  -sf  $DEST  $OLD_LIBS/`basename  $LINE`
      else
        cp  $LINE  $OLD_LIBS
      fi
      echo       $OLD_LIBS/`basename  $LINE`  >>  $SAVED
    fi


  done

  ldconfig  $OLD_LIBS
  unlock_file $SAVED

  if  [  -z   "$LD_LIBRARY_PATH"  ]
  then  export  LD_LIBRARY_PATH="$OLD_LIBS"
  else  export  LD_LIBRARY_PATH="$OLD_LIBS:$LD_LIBRARY_PATH"
  fi

  # NOTE: remove this in 1.14
  if  [  "$SPELL"  ==  "glibc"         ]   ||
      [  "$SPELL"  ==  "glibc-custom"  ] ;  then
    if [ -e $OLD_LIBS/ld-$OLD_VERSION.so ] ; then 
      [[ `uname -m` == ppc* ]] && ln  -sf  $OLD_LIBS/ld-$OLD_VERSION.so  ${INSTALL_ROOT}/lib/ld.so.1 ||
      ln  -sf  $OLD_LIBS/ld-$OLD_VERSION.so  ${INSTALL_ROOT}/lib/ld-linux.so.2 
    fi
  fi

}


#---------------------------------------------------------------------
##
## Runs default ldconfig to stop using the saved libraries
##
#---------------------------------------------------------------------
function release_saved_libraries()  {
  ldconfig
}


#---------------------------------------------------------------------
##
## Recovers from a CTRL-C while casting a spell
## FIXME: this should be a resurrect
##
#---------------------------------------------------------------------
function spell_recover()  {

  debug  "libsorcery" "Running spell_recover()"

  message  "${MESSAGE_COLOR}Aborting dispel of ${SPELL_COLOR}${SPELL}" \
           "${MESSAGE_COLOR} and recovering${DEFAULT_COLOR}"

  CURRENT_VERSION=$(installed_version  $SPELL)
  CACHE_COMP="$INSTALL_CACHE/$SPELL-$CURRENT_VERSION-$HOST.tar$EXTENSION"
  CONTINUE=false

  if    [  !      -f  $CACHE_COMP   ];  then
    false
  elif  [         -n  "$EXTENSION"  ];  then
    CONTINUE=true
  elif  $COMPRESSBIN  -tf  $CACHE_COMP
  then
    CONTINUE=true
  fi
 
  if  $CONTINUE
  then
 
    if  [  -n  "$EXTENSION"  ];  then
      $COMPRESSBIN  -cd  $CACHE_COMP  |  tar  -Pkx  2>/dev/null
    else
      tar  -Pkxf  $CACHE_COMP  2>/dev/null
    fi
    add_spell  $SPELL  installed  $CURRENT_VERSION
  else
    false
  fi

  exit  130

}


#---------------------------------------------------------------------
## @param spell name
## @Stdout section
## Returns the section a spell is in.
##
#---------------------------------------------------------------------
function find_section()  {

  debug  "libsorcery" "Running find_section() on $1"

  codex_get_spell_section_name  $1

}


#---------------------------------------------------------------------
##=item directories
##
## @Stdin list of files/dirs/..
## @Stdout list of dirs
##
## Reads a list from standard input, and prints out each entry that is
## a directory (and not a symbolic link to a directory).
#---------------------------------------------------------------------
function directories() {

  while  read       ITEM;  do
    if  [     -d  "$ITEM"  ]   &&
        !  [  -h  "$ITEM"  ];  then
      echo        "$ITEM"
    fi
  done

}


#---------------------------------------------------------------------
## @Stdin list of files/dirs/..
## @Stdout list of files
## Reads a list from standard input, and prints out each entry that is
## a file (and not a symbolic link to a file).
##
#---------------------------------------------------------------------
function files()  {

  while  read       ITEM;   do
 	
    if  [     -f  "$ITEM"  ]   &&
        !  [  -h  "$ITEM"  ];  then
      echo "$ITEM"
    fi
  done

}


#---------------------------------------------------------------------
## @Stdin list of files/dirs/..
## @Stdout list of symlinks
## Reads a list from standard input, and prints out each entry that is
## a symbolic linke
##
#---------------------------------------------------------------------
function symlinks()  {

  while  read    ITEM;   do
    if  [  -h  "$ITEM"  ];  then
      echo     "$ITEM"
    fi
  done

}


#---------------------------------------------------------------------
## @param filename
## @Stdin list of files/dirs/..
## @Stdout filtered list
## First argument is a file that contains (basic) grep regular expressions.
## They are joined with \|'s and given to grep -v.
## Which will filter standard input to remove entries that match.
## Used to filter out excluded or protected files from install logs.
##
#---------------------------------------------------------------------
function filter() {
  local thing RID_LIST
  local files each invert
  for each in $@; do
    test -f $each && files="$files $each"
  done
    
  if [[ $files ]] ; then
    RID_LIST=$(
      cat $files | while read thing; do
                     echo -n "$thing\|"
                   done
      echo -n /dev/null)
    grep -v "$RID_LIST"
  else
    cat
  fi
}

#---------------------------------------------------------------------
## @param filename
## @Stdin list of files/dirs/..
## @Stdout filtered list
## First argument is a file that contains (basic) grep regular expressions.
## They are joined with \|'s and given to grep
## Which will filter standard input to remove and remove entries
## that do not match.
## Used to filter in config files from install logs.
##
#---------------------------------------------------------------------
function filter_in() {
  local thing RID_LIST
  local files each
  for each in $@; do
    test -f $each && files="$files $each"
  done
    
  if [[ $files ]] ; then
    RID_LIST=$(
      cat $files | while read thing; do
                     echo -n "$thing\|"
                   done
      echo -n /dev/null)
    grep "$RID_LIST"
  else
    cat
  fi
}

function filter_generic() {
  local args=$1
  local filter_name=$2
  local system_filter=$3
  local grimoire_filter section_filter spell_filter spell_dir
  tablet_find_spell_dir $SPELL spell_dir &>/dev/null &&
  tablet_get_spell_filter $spell_dir $filter_name spell_filter &>/dev/null
  tablet_get_section_filter $spell_dir $filter_name section_filter &>/dev/null
  tablet_get_grimoire_filter $spell_dir $filter_name grimoire_filter &>/dev/null
  set -- $args
  if [ "$1" == -v ] ; then
    shift
    filter_in "$@" $system_filter $grimoire_filter $section_filter $spell_filter
  else
    filter "$@" $system_filter $grimoire_filter $section_filter $spell_filter
  fi
}

function filter_volatiles() {
  filter_generic "$*" volatiles $VOLATILES
}

function filter_configs() {
  filter_generic "$*" configs $CONFIGS
}

function filter_excluded() {
  filter_generic "$*" excluded $EXCLUDED
}

function filter_protected() {
  filter_generic "$*" protected $PROTECTED
}

#---------------------------------------------------------------------
## @Stdin list of files
## @Stdout list of directories
##
## Given a list of files from standard input, returns the directory
## of each file.
##
## Note: Is a duplicate of get_dirnames. 2003-09-28 Martin
#---------------------------------------------------------------------
function dirnames()  {
  while  read  FILE;  do  dirname  "$FILE";  done;
}

#---------------------------------------------------------------------
## @param queue filename
## @param items to remove ...
##
## The first argument is the name of the file containing the queue.
## If a second argument is given, any items in the queue that match
## the second argument are removed from the queue.  Otherwise, the top
## line of the queue is removed, and returned.
##
#---------------------------------------------------------------------
function pop_queue() {  (

  local ITEM=`esc_str $2`
  local exit_code=0

  tQUEUE_FILE=$( lock_start_transaction $1 )
  if  !  [  -f  "$1"  ];  then
    exit_code=1
  elif  [  -n  "$ITEM"  ];  then
    grep  -v  "^$ITEM\$"  $1 > $tQUEUE_FILE
  else
    FOUND=`sed  -n  1p  $1`

    if  [  -z  "$FOUND"  ];  then
      exit_code=1
    else
      FOUND=`esc_str $FOUND`
      grep  -v  "^$FOUND$"  $1 > tQUEUE_FILE
      echo  $FOUND
    fi
  fi
  lock_commit_transaction $1

  return $exit_code

) }


#---------------------------------------------------------------------
## @param filename
##
## The first argument is the name of the file that installed correctly
## and will be removed from the install file (this makes a failure of 
## the casting of the queue not have to recast completed spells).
##
#---------------------------------------------------------------------
function pop_install_queue() {  

  pop_queue  $INSTALL_QUEUE  "$1"

}


#---------------------------------------------------------------------
## @param queue filename
## @param item to add
## The first argument is the name of the file containing the queue.
## The second argument is an item to add to the end of the queue.  If
## the item exists anywhere in the queue, the item is removed from the
## queue before being added at the end.
##
#---------------------------------------------------------------------
function push_queue()  {

  pop_queue  "$1"  "$2";  echo  "$2" >>  $1;

}


#---------------------------------------------------------------------
## @param spell
##
## Adds the given spell to the install queue and removes it from the
## remove queue.
##
#---------------------------------------------------------------------
function push_install_queue() {

  pop_queue   $REMOVE_QUEUE    "$1"
  pop_queue   $INSTALL_QUEUE   "$1"
  !  spell_installed           "$1"  &&
  push_queue  $INSTALL_QUEUE   "$1"

}


#---------------------------------------------------------------------
## @param spell
##
## Adds the given spell to the remove queue and removes it from the
## install queue.
##
#---------------------------------------------------------------------
function push_remove_queue() {

  pop_queue   $INSTALL_QUEUE  "$1"
  pop_queue   $REMOVE_QUEUE   "$1"
  spell_installed             "$1"  &&
  push_queue  $REMOVE_QUEUE   "$1"

}

#---------------------------------------------------------------------
##
## Sets some environment variables (such as C<CFLAGS>) based on the
## option passed.  Options may be on or more of:  i386, i486, i586,
## pentium, pentium-mmx, i686, pentiumpro, pentium2, pentium3,
## pentium4, k6, k6-2, k6-3, athlon, athlon-tbird, athlon-4,
## athlon-xp, athlon-mp, powerpc, speedy, tiny, risky, strip,
## combreloc
##
#---------------------------------------------------------------------
function optimize()  {
  debug "libsorcery" "In optimize()"

  unset  CFLAGS CXXFLAGS LDFLAGS LC_ALL
  unset  COMBRELOC FAST PRELINK RISKY SMALL SPEEDY STRIP TINY

  # if user specified --no-opts then use only the args from the command line
  # otherwise do the normal stuff
  if [[ $NO_OPTIMIZATION_FLAGS ]] ; then
    export CFLAGS="$OVERRIDE_CFLAGS"
    export CXXFLAGS="$OVERRIDE_CXXFLAGS"
    export LDFLAGS="$OVERRIDE_LDLAGS"
  else
    set_architecture
    debug "ARCHITECTURE='${ARCHITECTURE}'"
    debug "libsorcery" "TARGET='${TARGET}'"
    debug "libsorcery" "OPTIMIZATIONS='${OPTIMIZATIONS}'"
    BUILD=${HOST}

    debug "libsorcery" "BUILD is $BUILD, HOST is $HOST"

    CFLAGS="$CFLAGS -pipe"
    for  PARAM  in  $OPTIMIZATIONS;  do
      case  $PARAM  in
        combreloc)
          LDFLAGS="$LDFLAGS -z combreloc"
          COMBRELOC="on"
          ;;

        prelink)
          CFLAGS="$CFLAGS -DPIC -fPIC"
          PRELINK="on"
          ;;

        risky)
          CFLAGS="$CFLAGS -ffast-math -funroll-loops"
          RISKY="on"
          ;;

        speedy)
          CFLAGS="$CFLAGS $FAST"
          SPEEDY="on"
          ;;

        strip)
          LDFLAGS="$LDFLAGS -s"
          STRIP="on"
          ;;

        tiny)
          CFLAGS="$CFLAGS $SMALL"
          TINY="on"
          ;;

      esac
    done
    # use echo to crunch all the whitespace out for broken configure scripts
    export CFLAGS=$(echo -O $CFLAGS $CUSTOM_CFLAGS $OVERRIDE_CFLAGS)
    export CXXFLAGS=$(echo $CFLAGS $CUSTOM_CXXFLAGS $OVERRIDE_CXXFLAGS)
    export LDFLAGS=$(echo $LDFLAGS $CUSTOM_LDFLAGS $OVERRIDE_LDLAGS)
  fi
  export LC_ALL="C"

}


#---------------------------------------------------------------------
## @Stdout install queue
##
## List files in install queue, give user chance to modify it.
##
#---------------------------------------------------------------------
function list_install_queue() {
  if  [  -f  $INSTALL_QUEUE  ];  then
    lock_file $INSTALL_QUEUE

    message -n "The following spells will be updated:"
    message "${SPELL_COLOR}"
    cat  $INSTALL_QUEUE | column
    message "${DEFAULT_COLOR}"

    if    query  "Do you wish to edit ${FILE_COLOR} $INSTALL_QUEUE ${DEFAULT_COLOR}?" n
    then  edit_file  $INSTALL_QUEUE
    fi

    if  [  -n  "`cat  $INSTALL_QUEUE`"  ];  then
      SPELLS=`cat $INSTALL_QUEUE`
    fi

    unlock_file $INSTALL_QUEUE
  else
    message "${MESSAGE_COLOR}No spells listed in queue.${DEFAULT_COLOR}"
  fi
}


#---------------------------------------------------------------------
##
## Checks all installed spells for newer versions, and creates an
## install queue.
##
#---------------------------------------------------------------------
function update_install_queue()  {
  local line spell curr_version curr_updated page_dir info curr_patchlevel
  local curr_sec_patch
  local tmp_queue=$TMP_DIR/install_queue
  touch $tmp_queue

  message  "${CHECK_COLOR}Generating list of spells to update... ${DEFAULT_COLOR} "
  for  line  in  `all_spell_status`;  do
    #0=spell, 1=date, 2=status, 3=version
    explode "$line" ":" "info"
    spell=${info[0]}
    curr_updated=${info[1]}
    status=${info[2]}
    curr_version=${info[3]}
    
    ( # do this in a subshell in case a spell mis-behaves and exits we
      # still want queueing to work
      if  [[  "$status"  ==  "installed"  ]] &&
        codex_set_current_spell_by_name  $spell;  then
        if  [[ $VERSION != $curr_version ]] ; then
          echo $spell >> $tmp_queue
        else
          curr_patchlevel=0
          curr_sec_patch=0
          if tablet_find_spell_dir $spell page_dir ; then
            curr_updated=0
            tablet_get_updated $page_dir curr_updated
            tablet_get_patchlevel $page_dir curr_patchlevel
            tablet_get_security_patch $page_dir curr_sec_patch
          fi
          if (( "${PATCHLEVEL:-0}" >  "$curr_patchlevel" )) ||
             # the following is subject to change
             (( "${SECURITY_PATCH:-0}" >  "$curr_sec_patch" )) ||
             (( "${UPDATED:-0}" >  "${curr_updated:-0}" )) ; then
            echo $spell >> $tmp_queue
          fi
        fi
      fi
    )
  done

  lock_file $INSTALL_QUEUE
  rm  -f  $INSTALL_QUEUE
  sort $tmp_queue | uniq > $INSTALL_QUEUE
  unlock_file $INSTALL_QUEUE

}

function update_security_install_queue()  {
  local line spell page_dir info curr_sec_patch=0
  local tmp_queue=$TMP_DIR/install_queue
  touch $tmp_queue

  message  "${CHECK_COLOR}Generating list of spells to update for" \
           "security reasons... ${DEFAULT_COLOR} "
  for  line  in  `all_spell_status`;  do
    #0=spell, 1=date, 2=status, 3=version
    explode "$line" ":" "info"
    spell=${info[0]}
    status=${info[2]}
    
    ( # do this in a subshell in case a spell mis-behaves and exits we
      # still want queueing to work
      if  [[  "$status"  ==  "installed"  ]] &&
          codex_set_current_spell_by_name  $spell &&
          tablet_find_spell_dir $spell page_dir ; then
        tablet_get_security_patch $page_dir curr_sec_patch &&
        if (( "${SECURITY_PATCH:-0}" >  "$curr_sec_patch" )) ; then
          echo $spell >> $tmp_queue
        fi
      fi
    )
  done

  lock_file $INSTALL_QUEUE
  rm  -f  $INSTALL_QUEUE
  sort $tmp_queue | uniq > $INSTALL_QUEUE
  unlock_file $INSTALL_QUEUE

}

#---------------------------------------------------------------------
##
## Builds all spells in the install queue.
##
#---------------------------------------------------------------------
function build_install_queue()  {

  if  [  -f  $INSTALL_QUEUE  ];  then
    lock_file $INSTALL_QUEUE

    message  "The following spells will be updated :"
    cat  $INSTALL_QUEUE

    unset  SPELL
    if    query  "Do you wish to edit ${FILE_COLOR}$INSTALL_QUEUE${DEFAULT_COLOR}?" n
    then  edit_file  $INSTALL_QUEUE
    fi

    if  [  -n  "`cat  $INSTALL_QUEUE`"  ];  then
      cast      `cat  $INSTALL_QUEUE`
      rm              $INSTALL_QUEUE
    fi

    unlock_file $INSTALL_QUEUE
  else
    message  "No spells will be updated."
  fi
}


#---------------------------------------------------------------------
## @Stdout history
##
## Display the history of the install queue for review
##
#---------------------------------------------------------------------
function install_queue_history() {
  local PAGER2
  if [ "$PAGER" == "less" ] ; then PAGER2="more"; else PAGER2="$PAGER"; fi
  local spell=
  for spell in `cat $INSTALL_QUEUE`; do
    local date="`grep "^${spell}:" $SPELL_STATUS | cut -d':' -f2`"
    local datedash="`echo "$date" | cut -c1-4`-`echo "$date" | cut -c5-6`-`echo "$date" | cut -c7-8`"
    (
      echo -e "Viewing history since last update ($datedash) for spell --  ${spell}  -- :" && 
      echo                 &&
      gaze history $spell
    ) |
      awk -v date="$date" '{ d = (strtonum(substr($0,1,4) substr($0,6,2) substr($0,9,2)) < date && strtonum(substr($0,1,4)) > 0) }; NR == 1, d { if (!d) print }' | 
      $PAGER2
    query "Would you like to ${RED} remove ${QUERY_COLOR} spell ${SPELL_COLOR} $spell ${QUERY_COLOR} from the install queue?" n &&
      sedit "s/^$spell$//" $INSTALL_QUEUE &&
      echo -e "${QUERY_COLOR} The spell ${SPELL_COLOR} $spell ${QUERY_COLOR} removed from $INSTALL_QUEUE.${DEFAULT_COLOR}"
  done
}

#---------------------------------------------------------------------
## @param grimoire-path
## @Stdout history
## Display the history of the grimoire given for review
#---------------------------------------------------------------------
function grimoire_history() {

  if [ -e $grimoire/ChangeLog ] ; then 
    local PAGER2
    if [ "$PAGER" == "less" ] ; then 
      PAGER2="more" 
    else 
      PAGER2="$PAGER" 
    fi
    local grimoire="$1"
    local grimoirename="${grimoire##*/}"
    local date="`cat "$STATE_DIRECTORY/$grimoirename.lastupdate" 2> /dev/null`"
    [ -z "$date" ] && date="20030818"
    local datedash="`echo "$date" | cut -c1-4`-`echo "$date" | cut -c5-6`-`echo "$date" | cut -c7-8`"
    (
      echo -e "Viewing history since last update ($datedash) for grimoire --  ${grimoirename}  -- :" && 
      echo                 &&
      cat "$grimoire/ChangeLog" 2> /dev/null
    ) |
    awk -v date="$date" '{ d = (strtonum(substr($0,1,4) substr($0,6,2) substr($0,9,2)) < date && strtonum(substr($0,1,4)) > 0) }; NR == 1, d { if (!d) print }' | 
    $PAGER2
  else
    echo  "No ChangeLog in the $grimoire Grimoire, skipping viewing..."
    echo  ""
  fi
  echo "`date +%Y%m%d`" > "$STATE_DIRECTORY/$grimoirename.lastupdate"
}


#---------------------------------------------------------------------
## @Stdout history
## Display the history of sorcery for review
##
#---------------------------------------------------------------------
function sorcery_history() {
  local PAGER2
  if [ "$PAGER" == "less" ] ; then PAGER2="more"; else PAGER2="$PAGER"; fi
  local date="`cat "$STATE_DIRECTORY/sorcery.lastupdate" 2> /dev/null`"
  [ -z "$date" ] && date="20030818"
  local datedash="`echo "$date" | cut -c1-4`-`echo "$date" | cut -c5-6`-`echo "$date" | cut -c7-8`"
  (
    echo -e "Viewing history since last update ($datedash) for sorcery :" && 
    echo                 &&
    cat "/usr/share/doc/sorcery/ChangeLog"
  ) |
    awk -v date="$date" '{ d = (strtonum(substr($0,1,4) substr($0,6,2) substr($0,9,2)) < date && strtonum(substr($0,1,4)) > 0) }; NR == 1, d { if (!d) print }' | 
    $PAGER2
  echo "`date +%Y%m%d`" > "$STATE_DIRECTORY/sorcery.lastupdate"
}


#---------------------------------------------------------------------
##
## Attempt to fix any spells that may be broken.
##
#---------------------------------------------------------------------
function fix_installed_spells()  {

  local ANSWER=

  [  "$AUTOFIX"  ==  "on"  ]  &&  ANSWER=y  ||  ANSWER=n
  if  query  "Attempt to fix spells that may have become broken ?" $ANSWER;  then
    cleanse --fix;
  fi

  if  [  "$AUTOPRUNE"  ==  "on"  ];  then  prune;  fi
}

#---------------------------------------------------------------------
## @Stdout log files list
## Returns a list of the log files for each spell.
##
#---------------------------------------------------------------------
function log_list()  {

  for  LINE  in  `all_spell_status`;  do
    SPELL=`echo  $LINE  |  cut  -d  :  -f1`
      VER=`echo  $LINE  |  cut  -d  :  -f4`
    echo  "$SPELL-$VER"
    echo  "$SPELL-$VER$EXTENSION"
  done

}


#---------------------------------------------------------------------
##
## Removes stale logs.
##
#---------------------------------------------------------------------
function clean_logs()  {

  message "${CHECK_COLOR}Cleaning log files... ${DEFAULT_COLOR} "

  debug  "libsorcery" "Running clean_logs()"

  LOGS=`log_list`

  for  FILE  in  `ls  $INSTALL_LOGS`;  do
    if  !  echo  -e  "$LOGS"  |  grep  -q  "^$FILE$";  then
      message  "Removing stale log  :  $INSTALL_LOGS/$FILE "
      rm                               $INSTALL_LOGS/$FILE
    fi
  done

  for  FILE  in  `ls  $COMPILE_LOGS`;  do
    if  !  echo  -e  "$LOGS"  |  grep  -q  "^$FILE$";  then
      message  "Removing stale log  :  $COMPILE_LOGS/$FILE "
      rm                               $COMPILE_LOGS/$FILE
    fi
  done

  for  FILE  in  `ls  $MD5SUM_LOGS`;  do
    if  !  echo  -e  "$LOGS"  |  grep  -q  "^$FILE$";  then
      message  "Removing stale log  :  $MD5SUM_LOGS/$FILE "
      rm                               $MD5SUM_LOGS/$FILE
    fi
  done
}


#---------------------------------------------------------------------
##
## Updates the activity log.
##
#---------------------------------------------------------------------
function activity_log()  { (

  lock_file $ACTIVITY_LOG

     DATE=`date  -u  +%Y%m%d:%H%M\(%z\)`
  COMMAND=$1
    SPELL=$2
  VERSION=$3
  OUTCOME=$4
     INFO=$5

  echo  -e "$DATE\t$COMMAND\t$SPELL\t$VERSION\t$OUTCOME\t$INFO"  >> $ACTIVITY_LOG
  unlock_file $ACTIVITY_LOG
) }


#---------------------------------------------------------------------
##
## Executes the spell's DETAILS file.
##
#---------------------------------------------------------------------
function run_details() {
  debug "libsorcery" "starting run_details for $SPELL"
  if  !  codex_set_current_spell_by_name  $SPELL;  then
    local  GRIMOIRES=`codex_get_all_grimoires`
    message  "${PROBLEM_COLOR}Unable to find spell"        \
             "${SPELL_COLOR}${SPELL}${PROBLEM_COLOR}"      \
             "in grimoire(s): $GRIMOIRES ${DEFAULT_COLOR}"
    false
  fi
}

#---------------------------------------------------------------------
##
## unsets variables set by DETAILS. <br>
## vars: SPELL VERSION SOURCE_DIRECTORY WEB_SITE UPDATED ENTERED SHORT SOURCE* 
## 
## Needs to be merged with the libcodex function since they are both
## needed to fully unset a DETAILS (afk 4/21/04)
##
#---------------------------------------------------------------------
function unset_details()  {

  local VARS="SPELL VERSION SOURCE_DIRECTORY WEB_SITE UPDATED ENTERED SHORT"
  local TEMP=""

  # Take care or SOURCEx and SOURCEx_URL
  for i in ${!SOURCE*} ; do
    TEMP=${TEMP}${i}\\n
  done
  VARS="$VARS `echo -e $TEMP | egrep "^SOURCE[[:digit:]]*(_URL)?$"`"
  unset TEMP

  for i in $VARS ; do
    unset $i
  done

}




#---------------------------------------------------------------------
##
## Runs C<track> and C<archive> on the current spell.
##
## THIS FUNCTION IS DEPRECIATED AND SHOULD NOT BE CALLED FOR ANY REASON
##
#---------------------------------------------------------------------
function boost() {

  debug  "libsorcery" "Running boost() on $SPELL THIS IS DEPRECIATED"
  echo "Running boost() on $SPELL THIS IS DEPRECIATED, please contact the spell's maintainer and ask them to remove this call"
  true
}


#---------------------------------------------------------------------
## @Stdout spelllist
## Returns a list of all installed spells that use linux-pam
##
## NOTE: this will become useless when triggers are introduced.
##
#---------------------------------------------------------------------
function find_pam_aware()  { (

  cat  $SPELL_STATUS    |
  while  read  LINE;  do

     SPELL="`echo  $LINE  |  cut  -d  :  -f1`"
    STATUS="`echo  $LINE  |  cut  -d  :  -f3`"

    if  [  "$STATUS"  ==  "installed"  ]   ||
        [  "$STATUS"  ==  "held"       ];  then

      SPELL_DIRECTORY=`codex_find_spell_by_name  $SPELL`

      if    [  -d  "$SPELL_DIRECTORY/pam.d"  ]  &&
            [  "$SPELL"  !=  "linux-pam"              ]
      then  echo  $SPELL
      fi

    fi
  done

) }


#---------------------------------------------------------------------
##
## Given a list of source files, returns true if each file exists.
##
#---------------------------------------------------------------------
function verify_source()  { (


  for  SOURCE_FILE  in  $@;  do
    if  !  guess_filename  $SOURCE_CACHE/$SOURCE_FILE  >/dev/null
    then
      message  "${PROBLEM_COLOR}Missing ${FILE_COLOR}${1}${DEFAULT_COLOR}"
      message  "${PROBLEM_COLOR}Cast aborting.${DEFAULT_COLOR}"
      activity_log  "cast"  "$SPELL"  "$VERSION"  "failed"  \
                    "because it was missing source:  $1"
      return 1 
    fi
  done

  return 0

) }


#---------------------------------------------------------------------
## @param spell
## @Stdout filelist
## Returns a list of each source file used by a spell.  Most have only
## one, but xfree86 for example splits the sources into three separate
## source files.
##
#---------------------------------------------------------------------
function sources()  { (

  local i srcVar

  if  [  -z  "$SOURCE"  ];  then
    codex_set_current_spell_by_name  $1
  fi
  get_spell_files

) }

#---------------------------------------------------------------------
## @param spell
## @Stdout filelist
## Returns a list of each source file used by a spell.  Most have only
## one, but xfree86 for example splits the sources into three separate
## source files.
##
#---------------------------------------------------------------------
function source_urls()  { (

  local i srcVar

  if  [  -z  "$SOURCE"  ];  then
    codex_set_current_spell_by_name  $1
  fi
  get_spell_files_and_urls|while read -a line; do
    unset line[0]
    echo "${line[*]}"
  done


) }


#---------------------------------------------------------------------
## @Stdout filelist
## Returns a list of files that should not be pruned.
##
#---------------------------------------------------------------------
function generate_keep_list()  {
  local source_keep=$1
  local cache_keep=$2
  local type=$3
  if [[ $type == installed_only ]] ; then
    {
      get_all_spells_with_status installed 
      get_all_spells_with_status held
    } | sort | uniq | while read spell ; do
      codex_find_spell_by_name $spell
    done
  else
    codex_get_all_spells
  fi |
  (
  echo sorcery-$SORCERY_BRANCH.tar.bz2
  while read SPELL_DIRECTORY ; do
    codex_set_current_spell_quick  $SPELL_DIRECTORY &>/dev/null
    sources 2>/dev/null
    ls /var/cache/sorcery/$SPELL-$VERSION-* 1>&2 2>/dev/null
    unset VERSION SPELL ${!SOURCE*}
  done
  ) 2> $cache_keep > $source_keep

  #Add the README in the cache to list of files to keep
  echo  README >> $cache_keep

}


#---------------------------------------------------------------------
##
## Removes unnecessary files from the source cache and install cache.
## 
#---------------------------------------------------------------------
function prune()  {

  debug  "libsorcery" "Running prune()"
  local NO_KEEP
  local NUM_NO_KEEP=0
  
  message "${MESSAGE_COLOR}Generating list of files to keep...${DEFAULT_COLOR}"
  local SOURCE_KEEP="$TMP_DIR/prune-keep-source"
  local CACHE_KEEP="$TMP_DIR/prune-keep-cache"
  rm -rf $CACHE_KEEP
  rm -rf $SOURCE_KEEP
  generate_keep_list  $SOURCE_KEEP $CACHE_KEEP $1

  message "${MESSAGE_COLOR}Cleaning up source cache" \
          "(${DEFAULT_COLOR}$SOURCE_CACHE${MESSAGE_COLOR})...${DEFAULT_COLOR}"
  local SOURCE_RM="$TMP_DIR/prune.rm.source"
  find $SOURCE_CACHE -follow -mindepth 1 -maxdepth 1 -type f|
  awk -F/ 'BEGIN {
             while ( getline < ARGV[1] ) {
               keep[$NF] = 1;
             }
             while ( getline < ARGV[2] ) {
               if ( keep[$NF] != 1 ) {
                 print $0;
               }
             }
           }' $SOURCE_KEEP - |sort|uniq > $SOURCE_RM
  if test -s $SOURCE_RM ; then
    NUM_NO_KEEP=$(cat $SOURCE_RM|wc -l)
    
    message "There are $NUM_NO_KEEP files that can be removed"
    if query "Edit the list?" "n" ; then
      edit_file $SOURCE_RM
    fi
    if query "Remove the files?" "y" ; then
      cat $SOURCE_RM|xargs rm
    fi
  fi
  
  message "${MESSAGE_COLOR}Cleaning up install cache" \
          "(${DEFAULT_COLOR}$INSTALL_CACHE${MESSAGE_COLOR})...${DEFAULT_COLOR}"
  local CACHE_RM="$TMP_DIR/prune.rm.cache"
  find $INSTALL_CACHE -mindepth 1 -maxdepth 1 -type f|
  awk -F/ 'BEGIN { 
             while ( getline < ARGV[1] ) {
               keep[$NF] = 1;
             }
             while ( getline < ARGV[2] ) {
               if ( keep[$NF] != 1 ) {
                 print $0;
               }
             }
           }' $CACHE_KEEP - |sort|uniq > $CACHE_RM
  if test -s $CACHE_RM ; then
    NUM_NO_KEEP=$( cat $CACHE_RM | wc -l )
    message "There are $NUM_NO_KEEP files that can be removed"
    if query "Edit the list?" "n" ; then
      edit_file $CACHE_RM
    fi
    if query "Remove the files?" "y" ; then
      cat $CACHE_RM|xargs rm
    fi
  fi

  rm  -f  $SOURCE_KEEP $CACHE_KEEP $SOURCE_RM $CACHE_RM

}


#---------------------------------------------------------------------
##
## Sets DISTCC=[on|off] depending on the value of DISTCC_HOSTS.
## Adds /var/lib/sorcery/build to the C<PATH> if necessary.
##
#---------------------------------------------------------------------
function invoke_build_dir() { 

  # This merits some explaination:
  #
  # In the /var/lib/sorcery/build dir are frontends for the compilers
  # they run CCACHE and DISTCC. By putting them in the front of the path
  # they get run first, then hand the work off to the real compilers.
  # The script also loads /etc/sorcery/compile_config, this made it
  # convenient for building stuff outside of sorcery and taking advantage
  # of ccache/distcc, just do "export  PATH="/var/lib/sorcery/build:$PATH""
  # and you're set, you can also edit the DISTCC hosts on the fly.
  #
  # Then someone had the bright idea "if we set DISTCC_HOSTS to the empty
  # string, distcc wont run!" due to a misconception in how this function
  # worked, that spell hack would only work /some/ of the time: if you
  # used ccache in addition to distcc, distcc wouldnt get disabled. *Sigh*
  #
  # SO...now we're stuck with a bunch of spells in the field disabling
  # distcc in an broken way, obviously they all cant change overnight. To
  # make matters worse inside make() we have to fiddle with the number of
  # make jobs to use in case distcc is disabled (badly).
  #
  # so the answer is: 
  # 1) make a new variable "USE_DISTCC" and set it to "off" if distcc
  # is being disabled by the spell
  # 2) check if USE_DISTCC == off in run_compiler, but still check the
  # other signs just in case its being run manually
  # 3) add a check for DISABLE_DISTCC so spells can start using that someday
  #   (phase this in after a few months, then phase out the old check below,
  #   but not in run_compiler)
  # 4) remove the old stupid logic for MAKE_NJOBS (num_hosts + 1),
  # instead split MAKE_NJOBS up, add a JOBS_PER_HOST multiplier, and add
  # MAKE_NJOBS to that. This results in the number of jobs being $MAKE_NJOBS
  # (default 1) when distcc is disabled (bug 6432), and allows an automatic
  # increase in the number of jobs per host if its desired.
  # 

  USE_DISTCC=off
  if [[ $DISABLE_DISTCC != yes ]] ; then
    if test -n "$DISTCC_HOSTS" && spell_ok distcc ; then
      USE_DISTCC=on
    fi
  fi

  if ! spell_ok ccache ; then
    CCACHE=off
  fi

  if [[ "$CCACHE" == on ]] || [[ "$USE_DISTCC" == on ]] ; then
    export  PATH="$RUN_COMPILER_DIR:$PATH" 
  fi
} 


#---------------------------------------------------------------------
## @param category/service
## @Stdout spelllist
##
## First argument is a category of spell.  Returns a list of spells
## that match that category.  For example,
## C<find_providers email-client> returns evolution,althea,mutt,etc.
##
## This will list spells exactly once, exiled spells are not listed.
## This also takes into account grimoire ordering. A spell must provide
## the category in the first grimoire the spell is seen in (because that
## is the only one that will be cast) otherwise it is not listed.
##
## Note to self: move to libcodex
##
#---------------------------------------------------------------------
function find_providers()  {
  local feature="$1"
  local provider GRIMOIRE spell_path
  local providers=$(
    for GRIMOIRE in $(codex_get_all_grimoires);  do
      gawk '/^'"$feature"'[[:blank:]]/ { print $2 }' \
        "$GRIMOIRE/$PROVIDE_INDEX_FILE"
    done | get_basenames|sort|uniq)

  # filter out providers we dont want, so far this is:
  # a) do not provide something in the first grimoire they are found in
  # b) are exiled.
  # ex: foo provides FOOBAR in the "stable" grimoire but not the "test"
  # grimoire and test is searched before stable in this case cast will
  # use the spell from test which does NOT provide FOOBAR, therefore
  # we should not list foo as a provider of FOOBAR
  for provider in $providers; do
    if ! spell_exiled $provider; then
      spell_path=$(codex_find_spell_by_name $provider)
      GRIMOIRE=$(dirname $( dirname $spell_path))
      # ensure the first place we look for the spell is a provider
      grep -q "^$feature $spell_path$" $GRIMOIRE/$PROVIDE_INDEX_FILE &&
        echo $provider
    fi
  done

}


#---------------------------------------------------------------------
## @Stdout filelist
## Returns false if one or more source files for the current spell
## are missing.
##
#---------------------------------------------------------------------
function verify_sources()  {
  verify_source  "`sources  $SPELL`";
}



#---------------------------------------------------------------------
##
## Will set the prefered compression type based on user filled option
## in dialog menu (either gzip/bzip).
## 
#---------------------------------------------------------------------
function set_compression_type() {

  local B_HELP="bzip2 compression (slow, small files)"
  local G_HELP="gzip compression (fast, larger files)"
  local T_HELP="no compression/tar (fastest, largest files)"

  while

    COMMAND=`eval $DIALOG '  --title "Currently using : $COMPRESSBIN "  \
                             --item-help                                \
                             --ok-label      "Select"                   \
                             --cancel-label  "Exit"                     \
                             --menu                                     \
                             ""                                         \
                             0 0 0                                      \
                             "B"  "bzip2 compression"        "$B_HELP"  \
                             "G"  "gzip compression"         "$G_HELP"  \
                             "T"  "no compression/tar"       "$T_HELP"'`

  do

    case  $COMMAND in

      B)  modify_local_config "COMPRESSBIN" "bzip2"  &&
          COMPRESSBIN="bzip2"                        &&
          modify_local_config "EXTENSION" ".bz2"     ;;
      G)  modify_local_config "COMPRESSBIN" "gzip"   &&
          COMPRESSBIN="gzip"                         &&
          modify_local_config "EXTENSION" ".gz"      ;;
      T)  modify_local_config "COMPRESSBIN" "tar"    &&
          COMPRESSBIN="tar"                          &&
          modify_local_config "EXTENSION" ""         ;;

    esac
      
  done

}

#---------------------------------------------------------------------
## @param filename
## @return 0 if filename seems to be compressed
## @return 1 otherwise
## Returns true if the name of the file indicates that it should be
## a compressed file.  In other words, this function returns true if
## the filename ends in .gz, .tgz, .bz2, .zip, .rpm, or .Z.
##
#---------------------------------------------------------------------
function filename_indicates_compression()  {
  local FILENAME=`echo "file$1" | sed 's/^.*\.gz$//'  \
                                | sed 's/^.*\.tgz$//' \
                                | sed 's/^.*\.bz2$//' \
                                | sed 's/^.*\.zip$//' \
                                | sed 's/^.*\.Z$//'   \
                                | sed 's/^.*\.rpm$//' \
                                | sed 's/^.*\.bz2$//'`
  [  -z  "$FILENAME"  ]
}


#---------------------------------------------------------------------
##
## 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
##
#---------------------------------------------------------------------
