#!/bin/sh
# Slackware remove package script
#
# Revision 1.9-Slackles
# Sat Apr 23 13:09:21 EEST 2005, Lasse Collin <lasse.collin@slackware.fi>
# - Removed unneeded 'sort' commands
# - Added support for .tlz, .tbz and .tar package names
# - Support for new style quoted symlink creation (doinst.sh) scripts
# - Miscellaneous bugfixes & cleanups
#
# Revision 1.8 Thu Nov 22 14:00:13 PST 2001 volkerding Rel $
# - Move $TMP underneath $ROOT
# - Understand the idea of a base package name, so that packages
#   can be removed with any of these notations:
#   removepkg foo-1.0-i386-1.tgz
#   removepkg foo-1.0-i386-1
#   removepkg foo.tgz
#   removepkg foo
#
# Revision 1.7  2001/03/30 12:36:28 volkerding
# - Strip extra ".tgz" from input names.
#
# Revision 1.6  1999/03/25 18:26:41 volkerding
# - Use external $ROOT variable, like installpkg.
#
# Revision 1.5.1  1998/03/18 15:37:28 volkerding
# - Since removepkg is always run by root, the temp directory has been
#   moved from /tmp to a private directory to avoid symlink attacks from
#   malicious users.
#
# Revision 1.5  1997/06/26 12:09:53  franke
# - Fixed old bug in TRIGGER regex setting
# - -preserve/-copy options now preserve non-unique files
#   and empty directories also
#
# Revision 1.4  1997/06/09 13:21:36  franke
# - Package file preserve (-preserve, -copy) added.
# - Don't execute "rm -rf" lines from doinst.sh, removing links explicit.
# - Warning on no longer existing files added.
# - Warning on files changed after package installation added.
# - Intermediate file preserve (-keep) added.
# - Check for required files/links now done on a combined list.
# - Write access to /var/log/{packages,scripts} no longer necessary for -warn.
#
# Revision 1.3  1997/06/08 13:03:05  franke
# Merged with revision 1.1.1.1
#
# Revision 1.2  1996/06/01 20:04:26  franke
# Delete empty directories & formated manual pages added
#
# Revision 1.1.1.1  1995/12/18 21:20:42  volkerding
# Original Version from Slackware 3.1
#
# Revision 1.1  1995/06/05 22:49:11  volkerding
# Original Version from Slackware 3.0
#

# Copyright 1994, 1995, 1998  Patrick Volkerding, Moorhead, Minnesota USA
# Copyright 2001, Slackware Linux, Inc., Concord, CA USA
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

# Load shared functions:
. /usr/share/pkgtools/shared_functions.sh

# Disable pathname expansion (we will enable it separately when needed):
set -f
# Set internal field separator to <newline>:
IFS='
'

usage() {
  cat << EOF

Usage: removepkg [options] package [package2 ... ]

Options: -w, --warn      List files that would be removed, but do not
                         remove them.

         -p, --preserve  Reconstruct the complete package subtree under
                         /var/log/setup/tmp/preserved_packages/packagename
                         while removing the package.

         -c, --copy      Construct a copy of the package subtree, but do not
                         remove the package. Equivalent to --warn --preserve.

         -k, --keep      Save the intermediate files created by removepkg.

         -R, --root dir  Use a different root directory. This option overrides
                         the \$ROOT environment variable.

EOF
  exit 0
}

cat_except() {
  ( cd "$1" && cat `ls -A1 | sed "/^$2\$/d"` )
}

preserve_file() {
  if [ "$PRESERVE" = "true" ]; then
    F=$(basename "$1")
    D=$(dirname "$1")
    if [ ! -d "$PRES_DIR/$PKGNAME/$D" ]; then
      mkdir -p "$PRES_DIR/$PKGNAME/$D" || return 1
    fi
    cp -p "$ROOT/$D/$F" "$PRES_DIR/$PKGNAME/$D" || return 1
  fi
  return 0
}

preserve_dir() {
  if [ "$PRESERVE" = "true" -a ! -d "$PRES_DIR/$PKGNAME/$1" ]; then
    mkdir -p "$PRES_DIR/$PKGNAME/$1" || return 1
  fi
  return 0
}

keep_files() {
  for FILE in $(cat); do
    if [ ! -d "$ROOT/$FILE" ]; then
      if [ -r "$ROOT/$FILE" ]; then
        [ "$UPGRADEPKG_QUIET" != "true" ] && echo "  --> $ROOT/$FILE was found in another package. Skipping."
        preserve_file "$FILE"
      else
        [ "$UPGRADEPKG_QUIET" != "true" -a "$(echo "$FILE" | cut -b1-8)" != "install/" ] && \
            echo "WARNING: Nonexistent $ROOT/$FILE was found in another package. Skipping."
      fi
    else
      preserve_dir "$FILE"
    fi
  done
}

keep_links() {
  for LINK in $(cat); do
    [ "$UPGRADEPKG_QUIET" = "true" ] && continue
    if [ -L "$ROOT/$LINK" ]; then
      echo "  --> $ROOT/$LINK (symlink) was found in another package. Skipping."
    else
      echo "WARNING: Nonexistent $ROOT/$LINK (symlink) was found in another package. Skipping."
    fi
  done
}

delete_files() {
  for FILE in $(cat); do
    if [ ! -d "$ROOT/$FILE" ]; then
      if [ -r "$ROOT/$FILE" ]; then
        if [ "$ROOT/$FILE" -nt "$ADM_DIR/packages/$PKGNAME" ]; then
          echo "WARNING: $ROOT/$FILE changed after package installation."
        fi
        if [ "$WARN" != "true" ]; then
          echo "  --> Deleting $ROOT/$FILE"
          preserve_file "$FILE" && rm -f "$ROOT/$FILE"
        else
          echo "  --> $ROOT/$FILE would be deleted"
          preserve_file "$FILE"
        fi
      elif [ "$UPGRADEPKG_QUIET" != "true" ]; then
        echo "  --> $ROOT/$FILE no longer exists. Skipping."
      fi
    else
      preserve_dir "$FILE"
    fi
  done
}

delete_links() {
  for LINK in $(cat); do
    if [ -L "$ROOT/$LINK" ]; then
      if [ "$WARN" != "true" ]; then
        echo "  --> Deleting symlink $ROOT/$LINK"
        rm -f "$ROOT/$LINK"
      else
        echo "  --> $ROOT/$LINK (symlink) would be deleted"
      fi
    elif [ "$UPGRADEPKG_QUIET" != "true" ]; then
      echo "  --> $ROOT/$LINK (symlink) no longer exists. Skipping."
    fi
  done
}

delete_dirs() {
  for DIR in $(sort -r); do
    if [ -d "$ROOT/$DIR" ]; then
      if [ "$WARN" != "true" ]; then
        if [ `ls -a "$ROOT/$DIR" | wc -l` -eq 2 ]; then
          echo "  --> Deleting empty directory $ROOT/$DIR"
          rmdir "$ROOT/$DIR"
        else
          echo "WARNING: Unique directory $ROOT/$DIR contains new files"
        fi
      else
        echo "  --> $ROOT/$DIR (dir) would be deleted if empty"
      fi
    fi
  done
}

delete_cats() {
  for FILE in $(sed -n 's,/man\(./[^/]*$\),/cat\1,p'); do
    if [ -f "$ROOT/$FILE" ]; then
      if [ "$WARN" != "true" ]; then
        echo "  --> Deleting $ROOT/$FILE (fmt man page)"
        rm -f "$ROOT/$FILE"
      else
        echo "  --> $ROOT/$FILE (fmt man page) would be deleted"
      fi
    fi
  done
}


# Parse options, -a is here for compatibility with original removepkg:
[ $# = 0 ] && usage
ARGS=$(getopt -a -n removepkg -o ckpwR:h \
  -l copy,keep,preserve,warn,root:,upgradepkg-quiet,help -- "$@")
[ $? != 0 ] && exit 99
eval set -- "$ARGS"
unset ARGS WARN PRESERVE KEEP UPGRADEKPG_QUIET
while : ; do
  case "$1" in
    -c|--copy)            WARN=true; PRESERVE=true ;;
    -k|--keep)            KEEP=true ;;
    -p|--preserve)        PRESERVE=true ;;
    -w|--warn)            WARN=true ;;
    -R|--root)            check_root_dir_exists "$2"; ROOT=$2; shift 1 ;;
    --upgradepkg-quiet)   UPGRADEPKG_QUIET=true ;; # Undocumented parameter used by upgradepkg.
    -h|--help)            usage ;;
    --)                   shift 1; break ;;
    *)                    exit_getopt_error ;;
  esac
  shift 1
done
[ $# = 0 ] && exit_no_packages_specified
check_is_run_by_root

initialize_variables_and_package_database
PRES_DIR=$TMP/preserved_packages

if [ "$WARN" = "true" ]; then
  echo "Only warning... not actually removing any files."
  [ "$PRESERVE" = "true" ] && echo "Package contents is copied to $PRES_DIR."
  echo "Here's what would be removed (and left behind) if you"
  echo "removed the package(s):"
else
  [ "$PRESERVE" = "true" ] && echo "Package contents is copied to $PRES_DIR."
fi

# Conversion to 'comm' utility by Mark Wisdom.
# is pretty nifty! :^)
while [ $# != 0 ] ; do
  PKGLIST="$1"
  shift 1
  PKGNAME=$(package_fullname "$PKGLIST")
  [ "$UPGRADEPKG_QUIET" != "true" ] && echo
  # If we don't have a package match here, then we will attempt to find
  # a package using the long name format (name-version-arch-build) for
  # which the base package name was given.  On a properly-managed machine,
  # there should only be one package installed with a given basename, but
  # we don't enforce this policy.  If there's more than one, only one will
  # be removed.  If you want to remove them all, you'll need to run
  # removepkg again until it removes all the same-named packages.
  if [ ! -e "$ADM_DIR/packages/$PKGNAME" ]; then
    SHORT=`package_basename "$PKGNAME"`
    set +f # Temporarily enable pathname expansion
    for long_package in "$ADM_DIR/packages/$PKGNAME"* ; do
      if [ "`package_basename "$PKGNAME"`" = "`package_basename "$long_package"`" ]; then
        PKGNAME=`basename "$long_package"`
      fi
    done
    set -f
  fi
  if [ ! -r "$ADM_DIR/packages/$PKGNAME" ]; then
    echo "No such package: $ADM_DIR/packages/$PKGNAME. Can't remove."
    continue
  fi
  # Now we now we have a match, let's remove the package:
  [ "$WARN" != "true" ] && echo "Removing package $ADM_DIR/packages/$PKGNAME..."
  if fgrep "./" "$ADM_DIR/packages/$PKGNAME" 1> /dev/null 2>&1; then
    TRIGGER="^\.\/"
  else
    TRIGGER="FILE LIST:"
  fi
  [ "$WARN" != "true" -a "$UPGRADEPKG_QUIET" != "true" ] && echo "Removing files:"
  sed -n "/$TRIGGER/,/^$/p" "$ADM_DIR/packages/$PKGNAME" | \
    fgrep -v "FILE LIST:" | sort -u > "$TMP/delete_list$$"
  # Pat's new-new && improved pre-removal routine.
  cat_except "$ADM_DIR/packages" "$PKGNAME" > "$TMP/required_list$$"
  if [ -r "$ADM_DIR/scripts/$PKGNAME" ]; then
    extract_links < "$ADM_DIR/scripts/$PKGNAME" | sort -u > "$TMP/del_link_list$$"
    cat_except "$ADM_DIR/scripts" "$PKGNAME" | extract_links > "$TMP/required_links$$"
    mv "$TMP/required_list$$" "$TMP/required_files$$"
    sort -u "$TMP/required_links$$" "$TMP/required_files$$" > "$TMP/required_list$$"
    comm -12 "$TMP/del_link_list$$" "$TMP/required_list$$" | keep_links
    comm -23 "$TMP/del_link_list$$" "$TMP/required_list$$" | delete_links
  else
    ( set +f ; cat "$ADM_DIR/scripts/"* | extract_links > "$TMP/required_links$$" )
    mv "$TMP/required_list$$" "$TMP/required_files$$"
    sort -u "$TMP/required_links$$" "$TMP/required_files$$" > "$TMP/required_list$$"
  fi
  comm -12 "$TMP/delete_list$$" "$TMP/required_list$$" | keep_files
  comm -23 "$TMP/delete_list$$" "$TMP/required_list$$" > "$TMP/uniq_list$$"
  delete_files < "$TMP/uniq_list$$"
  delete_dirs < "$TMP/uniq_list$$"
  delete_cats < "$TMP/uniq_list$$"
  if [ "$KEEP" != "true" ]; then
    rm -f "$TMP/delete_list$$" "$TMP/required_files$$" "$TMP/uniq_list$$" \
          "$TMP/del_link_list$$" "$TMP/required_links$$" "$TMP/required_list$$"
  fi
  if [ "$PRESERVE" = "true" -a -r "$ADM_DIR/scripts/$PKGNAME" ]; then
    [ ! -d "$PRES_DIR/$PKGNAME/install" ] && mkdir -p "$PRES_DIR/$PKGNAME/install"
    cp -p "$ADM_DIR/scripts/$PKGNAME" "$PRES_DIR/$PKGNAME/install/doinst.sh"
  fi
  if [ "$WARN" != "true" ]; then
    [ ! -d "$ADM_DIR/removed_packages" ] && mkdir -m 0755 "$ADM_DIR/removed_packages"
    [ ! -d "$ADM_DIR/removed_scripts"  ] && mkdir -m 0755 "$ADM_DIR/removed_scripts"
    mv "$ADM_DIR/packages/$PKGNAME" "$ADM_DIR/removed_packages"
    [ -r "$ADM_DIR/scripts/$PKGNAME" ] &&  mv "$ADM_DIR/scripts/$PKGNAME" "$ADM_DIR/removed_scripts"
  fi
done

[ "$UPGRADEPKG_QUIET" != "true" ] && echo
