#!/bin/dash
export PATH=/bin
l=/var/lib/rebase
b=/var/cache/rebase
eb="${b}/rebase_epoch"
db="${l}/dynpath.d"
lb="${l}/localpath.d"
ub="${l}/userpath.d"
df="${l}/dynfile.d"
lf="${l}/localfile.d"
uf="${l}/userfile.d"
for d in ${b} ${db} ${lb} ${ub} ${df} ${lf} ${uf} ; do
  if [ ! -d ${d} ] ; then
    echo "Directory ${d} does not exist, trying to re-create."
    mkdir -p ${d}
  fi
done

BaseAddress=''
if [ "x86_64" = $( uname -m ) ] ; then
  DefaultBaseAddress=0x400000000
else
  DefaultBaseAddress=0x070000000
fi    

cleardb="no"
rebuild="no"
noaslr="no"
verbose=""
debug="no"
pkgdone="no"
doSuffixes='dll|so|eln|oct|mex'
exeSuffixes='exe'
dyndirs=''
dynLocs=''
dynFLocs=''
ret='
'

dyndirs () {
  [ "done" = "${dyndirs}" ] && return
  local _IFS IFS dynPaths dynFiles
  _IFS="$IFS"
  IFS="${ret}"
  dynPaths="$( find ${db} ${lb} ${ub} -type f | sort -u )"
  for d in ${dynPaths} ; do
    for f in "$(cat ${d})" ; do
      [ -z "$f" ] || dynLocs="${dynLocs}${ret}${f}"
      [ "yes" = "${debug}" ] && echo "dynLoc:  <${f}> => ${dynLocs}"
    done
  done
  dynFiles=$( find ${df} ${lf} ${uf} -type f | sort -u )
  for d in ${dynFiles} ; do
    for f in "$(cat ${d})" ; do
      [ -z "$f" ] || dynFLocs="${dynFLocs}${ret}${f}"
      [ "yes" = "${debug}" ] && echo "dynFile:  <${f}> => ${dynFLocs}"
    done
  done
  dyndirs="done"
}

usage () {
  echo "
rebaselst [-h | --help | [ <cmd> | <cmd1> <cmd2> ... <cmdn> ]]

Commands, will be executed in order:
------------------------------------

--verbose
	Run some commands with verbose output.

--rebuild
	Later commands will discard information in cache files and
	rebuild them from scratch.

--no-rebuild
	Keep and use information in cache files.  This is the default,
	can be used to cancel effect of a \"--rebuild\" earlier on the
	command line.

update
	Update all caches.

lst
	Update cache for package installation lists.

dyn
	Update cache for dynamic language modules and libraries.

--cleardb
	Remove an existing rebase database before doing a \"rebase\".

--noaslr
	Remove ASLR and TSAware flags from dynamic objects.

rebase
	Rebase with the information in cache files (i.e does not imply
	an \"update\").

peflags
	Set PE flags on executables with the information in cache
	files (i.e does not imply an \"update\").


pkg
	Update first stage cache for package installation lists only.
	This step is implied by \"lst\" and available for debugging
	only.

"
}

check_file () {
  if [ "$2" = "yes" -a  -e $1 ] ; then
    echo "removing $1"
    rm -f $1
  fi
  if [ ! -e "${eb}" ] ; then
      touch -d "@0"                          "${eb}" || \
	  touch -d "1980-01-01 00:00 UTC"    "${eb}" || \
	  touch -d "1999-05-03 07:29:06 UTC" "${eb}" || \
	  touch -d "2002-08-17 07:00 CEST"   "${eb}"
  fi
  if [ ! -e "$1" ] ; then
    echo "creating empty $1"
    touch -r "${eb}"  "$1"
  fi
  if [ "$2" != "nowrite" -a  -e $1 ] ; then
    chmod 644 "$1"
  fi
}

old_file () {
  local f g
  f="$1"
  g="${f}.old"
  mv -f "${f}" "${g}"
  cat >"${f}" <<EOF
## autogenerated, do not edit!
EOF
}

update_file () {
  local f g
  f="$1"
  chmod 444 "${f}"
}

rebase_do () {
  local g
  if [ "$1" = "yes" ] ; then
    rm -f /etc/rebase.db.*
  fi
  shift
  g="${b}/rebase_all"
  echo "Rebasing with list ${g}, built from $@."
  cat $@ | grep -vE '^#' | sort -u >"${g}"
  if [ ! -e "/etc/rebase.db.i386" -a ! -e "/etc/rebase.db.x86_64" ] ; then
    BaseAddress="-b ${DefaultBaseAddress}"
  fi
  rebase ${BaseAddress} ${verbose} -n -s -T "${g}"
  if [ "noaslr" = "yes" ] ; then
    peflags ${verbose} -d0 -t0 -T "${g}"
  fi
  BaseAddress=''
}

peflags_do () {
  local g
  g="${b}/rebase_all_exe"
  echo "Setting PE flags with list ${g}, built from $@."
  cat $@ | grep -vE '^#|ddd\/Ddd\.exe$' | sort -u >"${g}"
  peflags ${verbose} -t1 -T "${g}"
}

rebase_pkg () {
  local IFS f g r
  if [ "${pkgdone}" = "no" ] ; then
    g="${b}/rebase_pkg"
    check_file "${g}" "${rebuild}"
    old_file "${g}"
    echo "Updating package information in ${g}."
    r=$( find /etc/setup -xdev -type f \! -name '*-debuginfo.lst.gz' -name '*.lst.gz' -newer "${g}.old" )
    if [ ! -z "${r}" ] ; then
      echo "${r}" | sed -e 's/^/  from /'
      echo "${r}" | sed -e '1i # rebase_pkg information' -e 's/^/#   from /' >>"$g"
      IFS="${ret}"
      gzip -d -c ${r} |
	sed -E \
	    -e '/\/$/d' \
            -e "/\.(${doSuffixes}|${exeSuffixes})\$/!{d}" \
	    -e '/(cygwin1|cyglsa.*)\.dll$/d' \
            -e '/\/(d?ash|rebase|peflags)\.exe$/d' \
	    -e '/\/octave\//!{/\.(mex|oct)$/d}' \
	    -e '/gnuplot\/demo\/plugin\/.*\.so/d' \
	    -e '/^usr\/lib\/ocaml\/.*\.so/d' \
	    -e '/sys-root\/mingw/d' \
	    -e 's/^/\//' \
	    >>"$g"
    fi
    update_file "${g}"
    pkgdone="yes"
  fi
}

rebase_exe () {
  local f g
  f="${b}/rebase_pkg"
  g="${b}/rebase_exe"
  check_file "${f}" "nowrite"
  check_file "${g}" "no"
  rebase_pkg
  old_file "${g}"
  if [ "${f}" -nt "${g}.old" ] ; then
    echo "Updating rebase information for installed executables in ${g}."
    grep -E "\.(${exeSuffixes})\$" "${f}" >>"${g}"
    update_file "${g}"
  fi
}

rebase_lst () {
  local f g
  f="${b}/rebase_pkg"
  rebase_pkg
  g="${b}/rebase_lst"
  check_file "${g}" "${rebuild}"
  old_file "${g}"
  if [ "${f}" -nt "${g}.old" ] ; then
    echo "Updating rebase information for installed dynamic objects in ${g}."
    grep -E "\.(${doSuffixes})\$" "${f}" >>"${g}"
  fi
  update_file "${g}"
}

rebase_dyn () {
  local IFS f g
  g="${b}/rebase_dyn"
  check_file "${g}" "${rebuild}"
  old_file "${g}"
  dyndirs
  if [ "x" = "${dynLocs:-x}" ] ; then
    [ "yes" = "${debug}" ] && echo "dynLocs empty"
    touch "${g}"
  else
    echo "Looking for dynamic language modules/libraries in:"
    IFS="${ret}"
    for d in ${dynLocs} ; do
      echo "  ${d}" ;
      [ -d "${d}" ] && find "${d}" -xdev -regextype posix-extended -regex ".*\.(${doSuffixes})$" -newer "${g}.old" >>"${g}"
    done
    for f in ${dynFLocs} ; do
      echo "  ${f}" ;
      grep -vE "\.(${exeSuffixes})\$" "${f}" >>"${g}"
    done
  fi
  echo "Updating rebase information for dynamic language modules/libraries ${g}."
  update_file "${g}"
}

rebase_dyn_exe () {
  local IFS f g
  g="${b}/rebase_dyn_exe"
  check_file "${g}" "${rebuild}"
  old_file "${g}"
  dyndirs
  if [ "x" = "${dynFLocs:-x}" ] ; then
    [ "yes" = "${debug}" ] && echo "dynFLocs empty"
    touch "${g}"
  else
    IFS="${ret}"
    for d in ${dynLocs} ; do
      echo "  ${d}" ;
      [ -d "${d}" ] && find "${d}" -xdev -regextype posix-extended -regex ".*\.(${exeSuffixes})$" -newer "${g}.old" >>"${g}"
    done
    for f in ${dynFLocs} ; do
      echo "  ${f}" ;
      grep -E "\.(${exeSuffixes})\$" "${f}" >>"${g}"
    done
  fi
  echo "Updating rebase information for user-defined executables ${g}."
  update_file "${g}"
}

if [ "$#" = "0" ] ; then
  set -- "--help"
fi
while [ "$#" -gt 0 ] ; do
  [ "yes" = "${debug}" ] && echo "in $#: $@"
  case "$1" in
    --verbose )
      verbose=--verbose
      ;;
    --debug )
      debug=yes
      ;;
    --cleardb )
      cleardb=yes
      ;;
    --rebuild )
      rebuild=yes
      ;;
    --noaslr )
      noaslr=yes
      ;;
    --no-rebuild )
      rebuild=no
      ;;
    rebase )
      rebase_do "${cleardb}" "${b}/rebase_dyn" "${b}/rebase_lst"
      ;;
    peflags )
      peflags_do "${b}/rebase_dyn_exe"
      ;;
    update )
      shift
      set -- dummy dyn lst $@
      ;;
    pkg )
      rebase_pkg
      ;;
    lst )
      rebase_lst
      rebase_exe
      ;;
    dyn )
      rebase_dyn
      rebase_dyn_exe
      ;;
    -h|--help|* )
      usage
      exit 127
      ;;
  esac
  shift
  [ "yes" = "${debug}" ] && echo "out $#: $@"
done
exit 0
