#!/bin/bash
#---------------------------------------------------------------------
## @Synopsis Functions for dealing with tracking of files, and other installwatch related things.
## @Copyright Copyright (C) 2004 The Source Mage Team <http://www.sourcemage.org>
## Functions for dealing with tracking of files, and other installwatch related things.
#---------------------------------------------------------------------

#---------------------------------------------------------------------
## @Stdin list of files
## @Stdout list of files
## Reads a list of files from standard in, and returns a list of the
## files that exist.
##
#---------------------------------------------------------------------
function exists()  {
  while  read  ITEM;  do  [  -e  "$ITEM"  ]  &&  echo  "$ITEM";  done;
}


#---------------------------------------------------------------------
##
## Given a list of files it will notify installwatch of them.
## Useful for spells whose components are not dynamically linked
## to glibc. Uses simple hack of touching files while
## installwatch is running.
##
#---------------------------------------------------------------------
function real_track_manual()  {
  if  [[  -z  "$INSTALLWATCHFILE"  ]];  then
    echo "Can't tell installwatch to manually track... installwatch isn't running."
    return 1
  fi
  local  i
  for i in $* ; do
    [ -e $i ] && touch $i
  done
  return 0
}


#---------------------------------------------------------------------
##
## Sets up installwatch.
##
#---------------------------------------------------------------------
function real_invoke_installwatch()  {
  if  [  -e  /usr/lib/installwatch.so  ];  then
    export  INSTALLWATCHFILE=$IW_LOG
    export  LD_PRELOAD=/usr/lib/installwatch.so
  fi
}


#---------------------------------------------------------------------
##
## Stops using installwatch
##
#---------------------------------------------------------------------
function real_devoke_installwatch()  {
  unset  LD_PRELOAD
  unset  INSTALLWATCHFILE
}


#---------------------------------------------------------------------
##
## Parses the installwatch log for files installed by a spell.
##
#---------------------------------------------------------------------
function parse_iw()  {
  local INPUT=$1

  # it is EXTREMELY IMPORTANT that this variable contains an actual
  # tab character and not some number of spaces. Otherwise BAD THINGS
  # will happen.
  local TAB="	"
  OMIT_IN="${TAB}rename\|${TAB}symlink\|${TAB}unlink"

  grep -v "$OMIT_IN" $INPUT | cut -f3 | grep "^/"
  cat                $INPUT | cut -f4 | grep "^/"
}


#---------------------------------------------------------------------
##
## Creates the install log containing all files installed by the spell.
##
#---------------------------------------------------------------------
function create_install_log()  {
  debug  "libtrack" "$FUNCNAME on $SPELL"
  local INPUT=$1
  local OUTPUT=$2

  rm -f $OUTPUT
  parse_iw $INPUT                     |
  sort                                |
  uniq                                |
  install_log_filter $INSTALL_ROOT "" |
  grep -v -x ""                       |
  filter_excluded                     |
  install_log_filter "" $INSTALL_ROOT |
  exists                              >  $OUTPUT

  echo "$C_LOG_COMP"                  >> $OUTPUT
  echo "$MD5_LOG"                     >> $OUTPUT
  echo "$INST_LOG"                    >> $OUTPUT

}

#---------------------------------------------------------------------
## Makes a list of files with the md5sum
#---------------------------------------------------------------------
function create_md5list() {
  local INPUT=$1
  local OUTPUT=$2
  debug  "libtrack" "$FUNCNAME on $SPELL"

  [[ $__MODIFIED_FILES ]] || export __MODIFIED_FILES="$TMP_DIR/modified_files"
  touch $__MODIFIED_FILES
  filter "$__MODIFIED_FILES" < $INPUT | while read LINE ; do
    debug "libtrack" "Checking file $LINE"
    if  [ -f "$LINE" ] ; then
      debug "libtrack" "Running md5 on $LINE"
      md5sum "$LINE"
    fi
  done 2>/dev/null > $OUTPUT
}

#---------------------------------------------------------------------
## Notes that a file was previously modified so that its md5 is
## deliberatly munged
#---------------------------------------------------------------------
function mark_file_modified() {
  [[ "$1" ]] || return 1
  [[ $__MODIFIED_FILES ]] || export __MODIFIED_FILES="$TMP_DIR/modified_files"
  echo "^$1\$" >> $__MODIFIED_FILES
}


#---------------------------------------------------------------------
## @param file to check
## @param md5 file (optional)
#---------------------------------------------------------------------
function check_if_modified() {
  local to=$1
  local md5_log=$2
  if ! [[ $2 ]] ; then
    md5_log="$TMP_DIR/$SPELL.md5"
    if [[ $OLD_SPELL_VERSION ]] ; then
      old_md5_log="$MD5SUM_LOGS/$SPELL-$OLD_SPELL_VERSION"
      # log must be in filterable form
      log_adjuster "$old_md5_log" "$md5_log" filterable root
    else
      old_md5_log=/dev/null
    fi
  fi
  local my_md5=$(md5sum "$to")
  if test -f "$md5_log" && grep -q "$my_md5" "$md5_log"; then
    false
  else
    true
  fi
}

#---------------------------------------------------------------------
##
## Creates a bzip/gzip'ed tar file containing an archived backup of
## file specified on standard input into $CACHE_COMP
## @stdin files, one per line, to put into the archive
## @param directory input files are relative to install root for regular
## files state root for state files
##
#---------------------------------------------------------------------
function create_cache_archive()  {

  debug  "libtrack" "$FUNCNAME on $SPELL"
  if    [  "$ARCHIVE"  ==  "off"  ]; then
    debug "libtrack" "$FUNCNAME - ARCHIVE=$ARCHIVE, aborting archival."
    return
  fi
  debug "libtrack" "$FUNCNAME - ARCHIVE=$ARCHIVE, archiving."
  local input=$1
  local CACHE=$2
  local CACHE_COMP=$3

  message  "${MESSAGE_COLOR}Creating cache file" \
           "${FILE_COLOR}${CACHE_COMP}${DEFAULT_COLOR}"

  local TMP_DATA=$TMP_DIR/foo.data
  local TMP_MDATA=$TMP_DIR/foo.mdata
  seperate_state_files $input $TMP_DATA $TMP_MDATA

  pushd $STATE_ROOT/ &>/dev/null
  install_log_filter $STATE_ROOT "." < $TMP_MDATA |
  tar --no-recursion -cPf "$CACHE" -T -
  popd &>/dev/null

  pushd $INSTALL_ROOT/ &>/dev/null
  install_log_filter $INSTALL_ROOT "." < $TMP_DATA |
  tar --no-recursion -rPf "$CACHE" -T -
  rm $TMP_DATA $TMP_MDATA
  popd &>/dev/null
  if  [  "$COMPRESSBIN" != "tar"  ]; then
    $COMPRESSBIN -c $CACHE > $CACHE_COMP
    rm $CACHE
  fi
}

#---------------------------------------------------------------------
# this is to filter the install log from one form to another
# for install_root/track_root conversions
#---------------------------------------------------------------------
function install_log_filter() {
  sed "s:^$1:$2:"
}
function md5_log_filter() {
  sed "s:  $1:  $2:"
}

#---------------------------------------------------------------------
# @param input file (can be /dev/stdin)
# @param output file (can be /dev/stdout)
# @param input format (root/log/filterable)
# @param output format (root/log/filterable)
# @param filter callback (optional install_log_filter, could be
#                        md5_log_filter)
#
# This filters an install log from a given format into another format
#
# root: relative to / all paths are relative to / file existence tests
# should work, INSTALL_ROOT and STATE_ROOT are prepended to data and
# state files respectively
#
# log: relative to track_root etc, format used in the logs (see note on
# special behavior below)
#
# filterable: track_root/install_root/state_root stripped out files can
# have filters applied to them
#
# "Special" handling applies depending on whether STATE_ROOT is inside
# or outside INSTALL_ROOT.
# For converting into log format:
# If STATE_ROOT is within INSTALL_ROOT
# eg: STATE_ROOT=/opt/stuff INSTALL_ROOT=/opt/stuff
# or  STATE_ROOT=/opt/stuff/state INSTALL_ROOT=/opt/stuff
# the portion of INSTALL_ROOT within STATE_ROOT is replaced with TRACK_ROOT
# if STATE_ROOT is outside of INSTALL_ROOT (eg /opt/stuff and /opt/state)
# then STATE_ROOT is left as is
#
# Converting from log to root format is the inverse, and of course going
# to filterable format just requires removing whatever the expected prefix is.
#
#
#
#---------------------------------------------------------------------
function log_adjuster() {
  local input=$1
  local output=$2
  local informat=$3
  local outformat=$4
  local callback=${5:-install_log_filter}

  local data_in data_out metadata_in metadata_out cat_metadata


  if [[ "$informat" == root ]] ; then
    data_in=$INSTALL_ROOT
    if [[ "$outformat" == log ]] ; then
      # root to log
      data_out=$TRACK_ROOT

      # if the STATE_ROOT is within the install root, then the state files are
      # adjusted relative to track_root, otherwise they are left as is
      if ! [[ ${STATE_ROOT##$INSTALL_ROOT*} ]] ; then
        metadata_in=$STATE_ROOT
        metadata_out=$TRACK_ROOT${STATE_ROOT##$INSTALL_ROOT}
      else
        cat_metadata=yes
      fi
    elif [[ $outformat == filterable ]] ; then
      # root to filterable
      data_out=""
      metadata_in=$STATE_ROOT
      metadata_out=""
    fi
  elif [[ "$informat" == log ]] ; then
    data_in=$TRACK_ROOT
    if [[ "$outformat" == root ]] ; then
      # log to root
      data_out=$INSTALL_ROOT
      if ! [[ ${STATE_ROOT##$INSTALL_ROOT*} ]] ; then
        # we actually could do this another way by stripping off
        # $TRACK_ROOT${STATE_ROOT##$INSTALL_ROOT}, and replacing
        # it with $STATE_ROOT, but i think below is simpler and equivalent
        metadata_in=$TRACK_ROOT
        metadata_out=$INSTALL_ROOT
      else
        cat_metadata=yes
      fi
    elif [[ "$outformat" == filterable ]] ; then
      # log to filterable
      data_out=""
      metadata_out=""
      if ! [[ ${STATE_ROOT##$INSTALL_ROOT*} ]] ; then
        metadata_in=$TRACK_ROOT${STATE_ROOT##$INSTALL_ROOT}
      else
        metadata_in=$STATE_ROOT
      fi
    fi
  elif [[ "$informat" == filterable ]] ; then
    data_in=""
    metadata_in=""
    if [[ "$outformat" == root ]] ; then
      # filterable to root
      data_out=$INSTALL_ROOT
      metadata_out=$STATE_ROOT
    elif [[ "$outformat" == log ]] ; then
      # filterable to log
      data_out=$TRACK_ROOT
      if ! [[ ${STATE_ROOT##$INSTALL_ROOT*} ]] ; then
        metadata_out=$TRACK_ROOT${STATE_ROOT##$INSTALL_ROOT}
      else
        metadata_out=$STATE_ROOT
      fi
    fi
  fi

  local TMP_SSF=$TMP_DIR/$RANDOM
  while ! mkdir $TMP_SSF; do
    TMP_SSF=$TMP_DIR/$RANDOM
    sleep 1
  done
  local TMP_DATA=$TMP_SSF/foo.data
  local TMP_MDATA=$TMP_SSF/foo.mdata
  
  seperate_state_files $input $TMP_DATA $TMP_MDATA
  {
    if [[ $cat_metadata ]] ; then
      cat $TMP_MDATA
    else
      eval "$callback \"$metadata_in\" \"$metadata_out\" < $TMP_MDATA"
    fi
    eval "$callback \"$data_in\" \"$data_out\" < $TMP_DATA"
  } > $output
  rm $TMP_DATA $TMP_MDATA
  rmdir $TMP_SSF
}

#---------------------------------------------------------------------
## Split a log file into data that should be TRACK_ROOT'd versus
## STATE_ROOT'd.
##
## @param filename or - (or /dev/stdin) for a pipe. This routine will
##        read the input only once in-order to work with pipes.
## @param filename for non-state files, possibly /dev/stdout or /dev/stderr
## @param filename for state files, possibly /dev/stdout or /dev/stderr
##        don't use the same stream for both types.
##
#---------------------------------------------------------------------
function seperate_state_files() {
  local REAL_LOG_DIR=${LOG_DIRECTORY#$STATE_ROOT}
  local REAL_STATE_DIR=${STATE_DIRECTORY#$STATE_ROOT}

  # the input file is almost certainly a pipe, and things get weird
  # since we have to grep twice, so just dump the data into a unique
  # file, the loop below shouldn't go forever unless there's ~30,000 pipes

  local FILE=$TMP_DIR/ssf.$RANDOM
  while test -e $FILE; do
    FILE=$TMP_DIR/ssf.$RANDOM
  done
  cat $1 > $FILE
  grep -v "$REAL_LOG_DIR\|$REAL_STATE_DIR" $FILE | grep -xv '' > $2
  grep    "$REAL_LOG_DIR\|$REAL_STATE_DIR" $FILE | grep -xv '' > $3
  rm $FILE
  return 0
}

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