#!/bin/sh
# Copyright 1994, 1998, 2000  Patrick Volkerding, Concord, CA, USA
# Copyright 2001, 2003  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.
#
# Sun Nov 26 12:38:25 CST 1995
# Added patch from Glenn Moloney <glenn@physics.unimelb.edu.au> to allow
# packages to be installed to directories other than /.
#
# Wed Mar 18 15:15:51 CST 1998
# Changed $TMP directory to /var/log/setup/tmp, and chmod'ed it 700 to close
# some security holes.
#
# Wed May 18 20:20:29 EEST 2005, Lasse Collin <lasse.collin@slackware.fi>
# - Added support for LZMA (.tlz), bzip2 (.tbz) and uncompressed tar packages
# - Three undocumented parameters for upgradepkg speed up
# - Added ftp and http support with wget
# - New --warn mode, this can be useful now

# If installpkg encounters a problem, it will return a non-zero error code.
# If it finds more than one problem (i.e. with a list of packages) you'll only
# hear about the most recent one. :)
# 1 = package corrupt
# 2 = (unused)
# 3 = does not end in .tgz, .tlz, .tbz or .tar
# 4 = not a file
# 5 = wget returned an error
# 10 = Not run by root
# 97 = Fatal error parsing command line
# 99 = Invalid command line parameters, usage shown
EXITSTATUS=0

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

# Disable pathname expansion:
set -f
# Set internal field separator to <newline>:
IFS='
'

usage() {
  cat << EOF

Usage: installpkg [options] package_file_name [package_file_name_2 ... ]

         package_file_name   Can be .tgz (tar+gzip), .tlz (tar+lzma),
                             .tbz (tar+bzip2) or .tar (plain tar archive).
                             Also HTTP and FTP URLs are supported.

Options: -d, --download-only Download the package to the cache directory
                             (/var/cache/packages) but do not install it.

         -w, --warn          Warn what files would be overwritten, but do
                             not install. This feature does not support
                             HTTP/FTP addresses.

         -q, --quiet         Show no status messages during installation
                             except wget download information.

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

         -N, --no-ldconfig   Don't run ldconfig after installing the package.
                             Use only if you know what you are doing!

EOF
  exit 0
}

package_description() {
  [ ! -f "$1" ] && return 0
  grep "^$packagebase:" "$1" 2> /dev/null
  [ "$shortname" != "$packagebase" ] && grep "^$shortname:" "$1" 2> /dev/null
}

test_integrity_and_calc_sizes() {
  # Function description:
  # - Test tarball integrity (return non-zero exit status if fails)
  # - Calculate uncompressed size of the package and set it to variable UNCOMPRESSED
  # - Check compressed size of the package and set it to variable COMPRESSED
  mknod "$TMP/fifo1.$$" p
  mknod "$TMP/fifo2.$$" p
  ( uncompress_pkg "$package" | tee "$TMP/fifo1.$$" | $TAR tf - 1> "$TMP/files.$shortname" 2> /dev/null
    echo $? > "$TMP/fifo2.$$" ) &
  UNCOMPRESSED=`wc -c < "$TMP/fifo1.$$" | tr -d ' '`
  ERRORCODE=`cat "$TMP/fifo2.$$"`
  rm -f "$TMP/fifo1.$$" "$TMP/fifo2.$$" # We need $shortname later so we don't delete it here...
  # Maybe this check below for corrupted archives could be improved?
  if [ "$ERRORCODE" != "0" -o "$UNCOMPRESSED" = "0" -o $(wc -l < "$TMP/files.$shortname") = 0 ]; then
    EXITSTATUS=1 # (package corrupt)
    rm -f "$TMP/files.$shortname" # ...but with corrupted package it's junk.
    # If the package is downloaded with wget (now or earlier), we'll remove it from cache:
    [ "$PACKAGE_IS_FROM_NETWORK" = "yes" ] && rm -f -- "$package"
    return 1 # Error
  fi
  # Set uncompressed and compressed sizes of the package:
  UNCOMPRESSED=$(($UNCOMPRESSED / 1024))
  COMPRESSED=$(ls -sk -- "$package" | tr -s ' ' | cut -f 1 -d ' ')
  return 0 # All OK
}

cleanup() {
  # If we used a scan directory, get rid of it:
  [ -d "$TMP/scan$$" ] && rm -rf "$TMP/scan$$"
  # /install/doinst.sh and /install/slack-* are reserved locations for the package system.
  [ -d "$ROOT/install" ] && rm -rf "$ROOT/install"
  # Other temporary files:
  rm -f "$TMP/files.$shortname"
}

# Parse options, -a is here for compatibility with original installpkg:
[ $# = 0 ] && usage
ARGS=$(getopt -a -n installpkg -o qwdNR:h \
  -l quiet,warn,download-only,no-ldconfig,root:,upgradepkg-integrity \
  -l upgradepkg-preinstall,upgradepkg-finalize,upgradepkg-install-new,help -- "$@")
[ $? != 0 ] && exit 99
eval set -- "$ARGS"
unset ARGS QUIET WARN DOWNLOAD_ONLY NOLDCONFIG UPGRADEPKG_STEP
while : ; do
  case "$1" in
    -q|--quiet)                 QUIET=yes ;;
    -w|--warn)                  WARN=yes ;;
    -d|--download-only)         DOWNLOAD_ONLY=yes ;;
    -N|--no-ldconfig)           NOLDCONFIG=yes ;;
    -R|--root)                  check_root_dir_exists "$2"; ROOT=$2; shift 1 ;;
    -h|--help)                  usage ;;
    # --upgradepkg-* options should be used *only* by upgradepkg!
    --upgradepkg-integrity)     UPGRADEPKG_STEP="integrity" ;;
    --upgradepkg-preinstall)    UPGRADEPKG_STEP="preinstall" ;;
    --upgradepkg-finalize)      UPGRADEPKG_STEP="finalize" ;;
    --upgradepkg-install-new)   UPGRADEPKG_STEP="install-new" ;;
    --)                         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

# If --warn mode was requested, produce the output and then exit:
if [ "$WARN" = "yes" ]; then
  while [ -f "$1" ]; do
    echo
    if is_url_package "$1"; then
      echo "Error: --warn does not support HTTP/FTP URLs."
      echo
      continue
    fi
    echo "#### Scanning the contents of $1..."
    mkdir -m 0700 -p "$TMP/scan$$"
    uncompress_pkg "$1" | $TAR xf - -C "$TMP/scan$$" install 2> /dev/null
    if [ -r "$TMP/scan$$/install/doinst.sh" ]; then
      if [ $(extract_links < "$TMP/scan$$/install/doinst.sh" | wc -l) != 0 ]; then
        for name in $(extract_links < "$TMP/scan$$/install/doinst.sh"); do
          [ -e "$ROOT/$name" ] && ls -lhd "$ROOT/$name" >> "$TMP/scan$$/symlinks"
        done
      fi
    fi
    uncompress_pkg "$1" | $TAR tf - > "$TMP/scan$$/list"
    for name in $(grep -v '/$' "$TMP/scan$$/list"); do
      [ -e "$ROOT/$name" ] && ls -lhd "$ROOT/$name" >> "$TMP/scan$$/files"
    done
    # Package has a directory and on disk exists a non-directory or
    # a symlink to a non-directory:
    for name in $(grep '/$' "$TMP/scan$$/list"); do
      [ -e "$ROOT/$name" -a ! -d "$ROOT/$name" ] && ls -lhd "$ROOT/$name" >> "$TMP/scan$$/files"
    done
    # Show results:
    if [ -f "$TMP/scan$$/symlinks" ]; then
      echo
      echo "# The following locations will be completely WIPED OUT to allow symbolic"
      echo "# links to be made. (We're talking 'rm -rf') These locations may be files,"
      echo "# or entire directories.  Be sure you've backed up anything at these"
      echo "# locations that you want to save before you install this package:"
      cat "$TMP/scan$$/symlinks"
    fi
    if [ -f "$TMP/scan$$/files" ]; then
      echo
      echo "# The following files will be overwritten when installing this package."
      echo "# Be sure they aren't important before you install this package:"
      cat "$TMP/scan$$/files"
    fi
    if [ ! -f "$TMP/scan$$/symlinks" -a ! -f "$TMP/scan$$/files" ]; then
      echo
      echo "# Package $1 will not overwrite any files."
      GREPEXPR="^(#| *$|\( cd .* ; (ln -sf|rm -rf) .* \)$|rm -rf -- '.*' #Symlink#$)"
      if grep -vE "$GREPEXPR" "$TMP/scan$$/install/doinst.sh" 1>/dev/null 2>/dev/null ; then
        echo "# To be very sure about this you should check the parts of"
        echo "# the doinst.sh script shown below:"
        grep -vE "$GREPEXPR" "$TMP/scan$$/install/doinst.sh"
      fi
    fi
    [ -d "$TMP/scan$$" ] && rm -rf "$TMP/scan$$"
    echo
    shift 1
  done
  exit 0
fi

# If called from upgradepkg, do not delete dotnew:
[ "$UPGRADEPKG_STEP" = "" ] && rm -f "$TMP/dotnew"

# Main loop:
DOWNLOAD_RETRY=no
NEW_KERNEL_INSTALLED=no
while [ $# != 0 ]; do

  package="$1"
  [ "$DOWNLOAD_RETRY" != "yes" ] && shift 1
  [ "$QUIET$UPGRADEPKG_STEP" = "" ] && echo
  [ "$QUIET" = "" ] && [ "$UPGRADEPKG_STEP" = "" -o "$UPGRADEPKG_STEP" = "integrity" ] && \
      ! is_valid_package_name "$package" && echo "WARNING: Non-standard package name"
  echo "$package" | is_kernel_packages && NEW_KERNEL_INSTALLED=yes

  # "shortname" isn't really THAT short... it's just the full name without .t??
  shortname=$(package_fullname "$package")
  # "packagetype" is "tgz", "tlz", "tbz", "tar" or ""
  packagetype=$(package_type "$package")
  # This is the base package name, used for grepping descriptions:
  packagebase=$(package_basename "$package")

  # Reject package if it does not end in .t??:
  if [ "$packagetype" = "" ]; then
    [ "$QUIET" = "" ] && echo "Cannot install $package: package does not end in .tgz, .tlz, .tbz or .tar"
    EXITSTATUS=3
    continue
  fi

  PACKAGE_IS_FROM_NETWORK=no # If pack is from net and KEEP_DOWNLOADED=0 then pack is removed after installing.
  # Handle HTTP and FTP:
  if is_url_package "$package"; then
    PACKAGE_IS_FROM_NETWORK=yes
    # Look from cache first (.tar is fastest and .tbz is slowest to decompress):
    if   [ -f "$PACKAGE_CACHE_DIR/$shortname.tar" ]; then
      package="$PACKAGE_CACHE_DIR/$shortname.tar"
    elif [ -f "$PACKAGE_CACHE_DIR/$shortname.tgz" ]; then
      package="$PACKAGE_CACHE_DIR/$shortname.tgz"
    elif [ -f "$PACKAGE_CACHE_DIR/$shortname.tlz" ]; then
      package="$PACKAGE_CACHE_DIR/$shortname.tlz"
    elif [ -f "$PACKAGE_CACHE_DIR/$shortname.tbz" ]; then
      package="$PACKAGE_CACHE_DIR/$shortname.tbz"
    fi
    if ! is_url_package "$package"; then
      [ "$QUIET" = "" ] && echo "Package found in cache: $package"
    else
      # No package found in cache, let's download it:
      rm -f "$PACKAGE_CACHE_DIR/$shortname.$packagetype" # Make sure it's not something stupid.
      download "$package" "$PACKAGE_CACHE_DIR/$shortname.$packagetype"
      if [ $? != 0 ]; then
        [ "$QUIET" = "" ] && echo "Cannot install $package: wget returned an error."
        rm -f "$PACKAGE_CACHE_DIR/$shortname.$packagetype" # Don't leave broken files in cache.
        EXITSTATUS=5
        continue
      fi
      package="$PACKAGE_CACHE_DIR/$shortname.$packagetype"
    fi
  fi

  if [ "$DOWNLOAD_ONLY" = "yes" ]; then
    [ "$QUIET" = "" ] && echo -e "Package $package saved to $PACKAGE_CACHE_DIR.\n"
    continue
  fi

  # Simple package integrity check:
  if [ ! -f "$package" ]; then
    [ "$QUIET" = "" ] && echo "Cannot install $package: package is not a regular file."
    EXITSTATUS=4
    continue
  fi

  [ "$QUIET$UPGRADEPKG_STEP" = "" ] && echo "Installing package $shortname..."

  # Test package integrity. If the package is downloaded (or from cache) then
  # redownload once in case of a corrupted package. This makes it simpler to
  # recover from a corrupted package left in cache. We try to avoid leaving
  # broken packages in cache but better safe than sorry.
  if ! test_integrity_and_calc_sizes; then
    if [ "$PACKAGE_IS_FROM_NETWORK" = "yes" -a "$DOWNLOAD_RETRY" != "yes" ]; then
      DOWNLOAD_RETRY=yes
      continue
    fi
    [ "$QUIET" = "" ] && echo "Unable to install $package: package is corrupt"
    EXITSTATUS=1
    DOWNLOAD_RETRY=no
    continue
  fi
  if [ "$UPGRADEPKG_STEP" = "integrity" ]; then
    # Store package size information for the next call from upgradepkg:
    echo "COMPRESSED=\"$COMPRESSED\"" > "$TMP/sizes.$shortname"
    echo "UNCOMPRESSED=\"$UNCOMPRESSED\"" >> "$TMP/sizes.$shortname"
    exit 0
  fi

  # Make sure we're not installing files on top of existing symbolic links:
  for file in $(grep -v '/$' "$TMP/files.$shortname"); do
    [ -L "$ROOT/$file" ] && rm -f "$ROOT/$file"
  done

  # Install the package:
  uncompress_pkg "$package" | $TAR -xlUpf - -C $ROOT/ > /dev/null 2> /dev/null
  if [ "$QUIET" = "" ]; then
    if [ $(package_description "$ROOT/install/slack-desc" | wc -l) -gt 0 ]; then
      echo
      echo "+=============================================================================="
      package_description "$ROOT/install/slack-desc" | uniq | sed 's/^[^: ]*:/|/;${/^| *$/d}'
      echo "+=============================================================================="
      echo
    else
      echo "Package has no description."
    fi
  fi
  [ -x "$ROOT/sbin/ldconfig" -a "$NOLDCONFIG" != "yes" ] && chroot "$ROOT/" "/sbin/ldconfig"
  if [ -f $ROOT/install/doinst.sh ]; then
    [ "$QUIET" = "" ] && echo "Executing install script for $shortname..."
    ( cd "$ROOT/" ; sh install/doinst.sh -install )
  fi

  # Fix permissions. In theory this should be never needed but world is not perfect:
  chmod 0755 "$ROOT/"
  [ -e "$ROOT/tmp" ] && chmod 1777 "$ROOT/tmp"

  # If we were called third time from upgradepkg we can exit now:
  if [ "$UPGRADEPKG_STEP" = "finalize" ]; then
    [ "$PACKAGE_IS_FROM_NETWORK" = "yes" -a "$KEEP_DOWNLOADED" = "0" ] && rm -f "$package"
    cleanup
    exit 0
  fi

  # If we are called second time from upgradepkg then load stored
  # variables UNCOMPRESSED and COMPRESSED:
  if [ "$UPGRADEPKG_STEP" = "preinstall" ]; then
    . "$TMP/sizes.$shortname"
    rm -f "$TMP/sizes.$shortname"
  fi

  # Write the package file database entry:
  echo "PACKAGE NAME:     $shortname" > "$ADM_DIR/packages/$shortname"
  printf "COMPRESSED PACKAGE SIZE:   %7d K\n" "$COMPRESSED" >> "$ADM_DIR/packages/$shortname"
  printf "UNCOMPRESSED PACKAGE SIZE: %7d K\n" "$UNCOMPRESSED" >> "$ADM_DIR/packages/$shortname"
  echo "PACKAGE LOCATION: $(cd "$(dirname "$package")"; pwd)/$(basename "$package")" >> "$ADM_DIR/packages/$shortname"
  echo "PACKAGE DESCRIPTION:" >> "$ADM_DIR/packages/$shortname"
  package_description "$ROOT/install/slack-desc" >> "$ADM_DIR/packages/$shortname" 2> /dev/null
  echo "FILE LIST:" >> "$ADM_DIR/packages/$shortname"
  if [ "`grep '^./' "$TMP/files.$shortname" | wc -l | tr -d ' '`" = "1" ]; then
    # Good.  We have a package that meets the Slackware spec.
    cat "$TMP/files.$shortname" >> "$ADM_DIR/packages/$shortname"
  else
    # Some dumb bunny built a package with something other than makepkg.  Bad!
    # Oh well.  Bound to happen.  Par for the course.  Fix it and move on...
    echo './' >> "$ADM_DIR/packages/$shortname"
    grep -v '^./$' "$TMP/files.$shortname" | cut -b3- >> "$ADM_DIR/packages/$shortname"
  fi

  # Copy doinst.sh to package database:
  if [ -r "$ROOT/install/doinst.sh" ]; then
    cp "$ROOT/install/doinst.sh" "$ADM_DIR/scripts/$shortname"
    chmod 0755 "$ADM_DIR/scripts/$shortname"
  fi

  # Put the list of the *.new files that still exist to a temporary file:
  for file in $(grep '\.new$' "$TMP/files.$shortname"); do
    [ -e "$ROOT/$file" ] && echo "$ROOT/$file" >> "$TMP/dotnew"
  done

  # Clean up the mess...
  [ "$PACKAGE_IS_FROM_NETWORK" = "yes" -a \
    "$KEEP_DOWNLOADED" = "0" -a \
    "$UPGRADEPKG_STEP" = "" ] && rm -f "$package"
  [ "$UPGRADEPKG_STEP" != "preinstall" ] && cleanup
  [ "$QUIET" = "" ] && echo
done

# Show *.new files from the installed packages:
if [ "$QUIET$UPGRADEPKG_STEP" = "" -a -f "$TMP/dotnew" ]; then
  echo "Remember to check the new configuration files:"
  cat "$TMP/dotnew"
  echo
fi
[ "$UPGRADEPKG_STEP" = "" ] && rm -f "$TMP/dotnew"

# Remind the user of the new kernel:
if [ "$QUIET$UPGRADEPKG_STEP" = "" -a "$NEW_KERNEL_INSTALLED" = "yes" ]; then
  show_new_kernel_message
  echo
fi

exit $EXITSTATUS
