#!/bin/bash

#Preupgrade Assistant performs system upgradability assessment
#and gathers information required for successful operating system upgrade.
#Copyright (C) 2013 Red Hat Inc.
#Ondrej Vasik <ovasik@redhat.com>, Petr Stodulka <pstodulk@redhat.com>
#
#This program 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 3 of the License, or
#(at your option) any later version.
#
#This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
. /usr/share/preupgrade/common.sh
check_rpm_to "" ""
#END GENERATED SECTION

_NOAUTO_POSTSCRIPT="noauto_postupgrade.d/install_rpmlist.sh"
_DST_NOAUTO_POSTSCRIPT="$NOAUTO_POSTUPGRADE_D/install_rpmlist.sh"
FILENAME_BASIS="RHRHEL7rpmlist_replaced"

get_repo_id() {
  [ -n "$1" ] && \
    grep -E "^[^-]*-$1;" "$COMMON_DIR/default_nreponames" | cut -d ";" -f3
}

[ ! -f "$VALUE_RPM_RHSIGNED" ] && \
  log_error "Signed RPM list not found. Contact the support." && \
  exit $RESULT_ERROR

[ ! -r "$COMMON_DIR" ] || [ ! -r "$_NOAUTO_POSTSCRIPT" ] && \
  log_error "Directory for common files not found. Contact the support." && \
  exit $RESULT_ERROR

ReplacedPkgs=$(mktemp .replacedpkgsXXX --tmpdir=/tmp)
MoveReplacedPkgs=$(mktemp .mvreplacedpkgsXXX --tmpdir=/tmp)
#RemovedOrObsoletedPkgs=$(mktemp .removedpkgsXXX --tmpdir=/tmp)
NotBasePkgs=$(mktemp .notbasepkgsXXX --tmpdir=/tmp)
cat $COMMON_DIR/default*_*replaced* | cut -f1,3 -d' ' | tr ' ' '|' | sort | uniq >"$ReplacedPkgs"
# without move to base channel - hasn't benefit for us
grep -Hr "..*" $COMMON_DIR/default*_moved-replaced_?* | sed -r "s|^$COMMON_DIR/([^:]+):([^[:space:]]*).*$|\2 \1|" | sort | uniq >"$MoveReplacedPkgs"
grep -Hr "..*" $COMMON_DIR/default-*_replaced | sed -r "s|^$COMMON_DIR/([^:]+):([^[:space:]]*).*$|\2 \1|" | sort | uniq >"$NotBasePkgs"
#cat $COMMON_DIR/default*_removed $COMMON_DIR/default*_*obsolete* \
#    | cut -f1 -d' ' | sort | uniq > "$RemovedOrObsoletedPkgs"

[ ! -r "$COMMON_DIR/ProvidesonlyMissing" ] \
       || [ ! -r "$COMMON_DIR/BothMissing" ] \
       || [ ! -r "$COMMON_DIR/default_nreponames" ] \
       || [ ! -r "$ReplacedPkgs" ] \
       || [ ! -r "$MoveReplacedPkgs" ] \
       || [ ! -r "$NotBasePkgs" ] && {
  log_error "Package change lists not found. Contact the support."
  rm -f "$ReplacedPkgs" "$MoveReplacedPkgs" "$NotBasePkgs"
  exit $RESULT_ERROR
}

found=0
optional=0
notprovided=0
other_repositories=""
removeme=""
statuscode=$RESULT_INFORMATIONAL # PASS is separated on the bottom
rm -f "$KICKSTART_DIR/${FILENAME_BASIS}"* >/dev/null
rm -f solution.txt

# create these 2 files - just to be sure - should be created always
touch "$KICKSTART_DIR/${FILENAME_BASIS}"
touch "$KICKSTART_DIR/${FILENAME_BASIS}-notbase"

cp "$_NOAUTO_POSTSCRIPT" "$_DST_NOAUTO_POSTSCRIPT"

echo \
"Between Red Hat Enterprise Linux 6 and Red Hat Enterprise Linux 7, some packages have either been replaced or
renamed.  The new packages are compatible with the previous versions.
In some cases, the Preupgrade Assistant will migrate the package after the
upgrade has completed.

Note: This tool will not check debug repositories.  Red Hat
recommends that all debuginfo packages are removed before the upgrade and
manually reinstalled as required on the upgraded system.

The following packages were replaced:" >solution.txt

###################################

#Check for package replacements in the packages
while read line
do
  orig_pkg=$(echo "$line" | cut -f1 )

  replaced_line=$(grep -e "^$orig_pkg|" "$ReplacedPkgs")
  [ -z "$replaced_line" ] && {
    # this could be obsoleted or removed package
    # in that case just skip it
    # grep "^$orig_pkg$" "$RemovedOrObsoletedPkgs" >/dev/null && continue

    # or it could be kept package, which we want totally install if we can.
    # Kept packages are handled by content notbase-channels
    # (originally labeled optionnal-channel) which is more suitable for this
    # so skip it too
    continue
  }


  repl_pkgs=$(echo "$replaced_line" | cut -d'|' -f2 )
  is_moved=0
  is_not_base=0
  filename_suffix=""
  msg_channel=""
  req_pkgs=""
  msg_req=""
  for k in $(rpm -q --whatrequires $orig_pkg | grep -v "^no package requires" \
    | rev | cut -d'-' -f3- | rev | sort | uniq )
  do
    is_pkg_installed "$k" || continue
    is_dist_native "$k" || msg_req="${req_pkgs}$k "
  done
  [ -n "$req_pkgs" ] && {
    req_pkgs="${req_pkgs% }"
    msg_req=" (required by package(s) not signed by Red Hat:$req_pkg)"
  }
  channel="$(grep "^$orig_pkg[[:space:]]" "$MoveReplacedPkgs" | rev | cut -d "_" -f 1 | rev)"

  func_log_risk=log_high_risk
  #packages from *debug repositories aren't important - ignore them (at least for now)
  # below
  #[[ "$channel" =~ debug$ && -z "$msg_req" ]] && func_log_risk=log_slight_risk

  if [ -n "$channel" ]; then
    [[ "$channel" =~ debug$ ]] && continue
    is_moved=1
  else
    channel=$(grep "^$orig_pkg[[:space:]]" "$NotBasePkgs" | sed -r "s/^.*default-(.*)_replaced$/\1/" )
    [[ "$channel" =~ debug$ ]] && continue
    [ -n "$channel" ] && is_not_base=1
  fi


  if [[ $is_moved -eq 1 || $is_not_base -eq 1 ]]; then
    [ "$channel" == "optional" ] && optional=1 || {
      [ $UPGRADE -eq 1] && func_log_risk=log_high_risk
    }
    other_repositories="${other_repositories}$channel "
    msg_channel=" ($channel channel in Red Hat Enterprise Linux 7)"
    statuscode=$RESULT_FAILED
    filename_suffix="-notbase"
  fi

  echo "${orig_pkg}|$req_pkgs|$(echo $repl_pkgs | tr ',' ' ')|$(get_repo_id $channel)" >> "$KICKSTART_DIR/${FILENAME_BASIS}${filename_suffix}"
  removeme="$removeme $orig_pkg"

  # logs / prints
  [ -n "$msg_req" ] && log_slight_risk "The ${orig_pkg}$msg_req package replaced between Red Hat Enterprise Linux 6 and Red Hat Enterprise Linux 7"
  [ $is_moved -eq 1 ] && $func_log_risk "The $orig_pkg package replacement moved to the $channel channel between Red Hat Enterprise Linux 6 and Red Hat Enterprise Linux 7. Enable this channel for the upgrade."
  [ $is_not_base -eq 1 ] && $func_log_risk "The $orig_pkg package replacement is a part of the $channel channel in Red Hat Enterprise Linux 7. Enable this channel for the upgrade."
  echo "${orig_pkg}$msg_req was replaced by ${repl_pkgs}$msg_channel" >>solution.txt
  found=1
done < <(get_dist_native_list | sort | uniq)

echo \
"
If a package not signed by Red Hat requires any of these packages, monitor them closely. Although the replacement should be compatible,
it may have some minor differences, even in the case of common
application lifecycles." >>solution.txt

[ -n "$other_repositories" ] && {
  echo -n "
One or more replacement packages are available only in other repositories.
Provide these repositories to make the upgrade or migration successful." >>solution.txt
[ $UPGRADE -eq 1 ] && echo -n "
Be aware, that for in-place upgrades, only the optional repository is supported.
Packages from other repositories should be removed first." >> solution.txt

 [ $optional -eq 1 ] && [ $UPGRADE -eq 1 ] && echo "
For this purpose, if you want to upgrade, use the following additional option
to redhat-upgrade-tool:

    --addrepo rhel-7-optional=<path to the optional repository>

Alternatively, you could remove all packages where the replacement is a 
part of the Red Hat Enterprise Linux 7 Optional repository before you start the system upgrade." >> solution.txt
# another channels could be added when we support addons

  [ $MIGRATE -eq 1 ] && {
    #migrate_repos="$(echo "${other_repositories% }" | tr ' ' '\n' | sort | uniq \
    #                  | sed -r "s/^(.+)$/rhel-7-\1=baseurl=<RHEL-7-\1>/")"
    regexp_part="$(echo "${other_repositories}" | tr ' ' '|' | sed -e "s/^|*//" -e "s/|*$//" | sort | uniq )"
    migrate_repos="$(grep -E "^[^-]*(-($regexp_part))?;" < "$COMMON_DIR/default_nreponames")"
    repos_texts="$(echo "$migrate_repos" | cut -d ";" -f4 )"

    echo "
If you want to migrate, register your machine with subscription-manager
after the first boot of your new system and attach subscriptions that provide:
$repos_texts

Then enable any equivalent repositories (if they are disabled) and
install any needed packages.
For this purpose (installation), you can run a prepared script:
$_DST_NOAUTO_POSTSCRIPT $KICKSTART_DIR/${FILENAME_BASIS}-notbase

which will install any remaining available packages from these repositories.
" >> solution.txt
  }
}
rm -f "$ReplacedPkgs" "$MoveReplacedPkgs" "$NotBasePkgs"

[ $UPGRADE -eq 1 ] && {
  #Relevant only for in-place upgrade. It's not important for migration

  #Packages not handled properly according to http://fedoraproject.org/wiki/Packaging:Guidelines#Renaming.2FReplacing_Existing_Packages
  # -> Package should have both obsoletes and provides, otherwise it can cause troubles during the update.
  l=""
  for i in $(get_dist_native_list | sort | uniq)
  do
    # For now, handle them same way, we want them installed...
    # Notice: not-base channel are important mainly for migration, where
    # not-base channels are not available during system installation. In-place
    # upgrade can update these packages directly, so keep here we will
    # check all replaced packages.
    m=$(grep "^$i|" "$COMMON_DIR/ProvidesonlyMissing")
    [ -n "$m" ] || m=$(grep "^$i|" "$COMMON_DIR/BothMissing")
    [ -z "$m" ] && continue
    replacement=$(echo $m | cut -d'|' -f2)
    notprovided=1
    log_debug "Package $i is not in the RPM 'provides' directives of the replacement; $replacement. The in-place upgrade might not work properly and will be finished by the postupgrade script."
    l="$l $m"
  done

  #Create a postupgrade script which ensures that the replacement packages are installed
  mkdir $VALUE_TMP_PREUPGRADE/postupgrade.d/replacedpkg 2>/dev/null
  cat <<\EOF >$VALUE_TMP_PREUPGRADE/postupgrade.d/replacedpkg/fixreplaced.sh
#!/bin/bash

#Generated file, part of preupgrade-assistant content, should not be used
#separately, see preupgrade-assistant license for licensing details
#Do the upgrade for the packages with potentially broken obsoletes/provides

prep_source_right() {
  # return 0 - mounted successfully
  # return 1 - nothing to do
  # return 2 - mount failed

  RHELUP_CONF="/root/preupgrade/upgrade.conf"
  mount_path="$(grep "^device" "$RHELUP_CONF" | sed -r "s/^.*rawmnt='([^']+)', .*$/\1/")"
  iso_path="$(grep "^iso" "$RHELUP_CONF" | cut -d " " -f 3- | grep -vE "^None$")"
  device_line="$(grep "^device" "$RHELUP_CONF"  | cut -d " " -f 3- | grep -vE "^None$")"
  device_path="$(echo "$device_line"  | sed -r "s/^.*dev='([^']+)',.*/\1/")"
  fs_type="$(echo "$device_line" | grep -o "type='[^']*'," | sed -r "s/^type='(.*)',$/\1/" )"
  m_opts="$(echo "$device_line" | grep -o "opts='[^']*'," | sed -r "s/^opts='(.*)',$/\1/" )"

  # is used iso or device? if not, return 1
  [ -n "$mount_path" ] && { [ -n "$iso_path" ] || [ -n "$device_path" ]; } || return 1
  mountpoint -q "$mount_path" && return 1 # is already mounted
  if [ -n "$iso_path" ]; then
    mount -t iso9660 -o loop,ro "$iso_path"  "$mount_path" || return 2
  else
    # device
    [ -n "$fs_type" ] && fs_type="-t $fs_type"
    [ -n "$m_opts" ] && m_opts="-o $m_opts"
    mount $fs_type $m_opts "$device_path" "$mount_path" || return 2
  fi

  return 0
}


for i in $(echo "SEDMEHERE")
do
  old="$(echo $i | cut -d'|' -f1)"
  new="$(echo $i | cut -d'|' -f2 | tr ',' ' ')"
  #we want to remove the old package if still present
  rpm -q $old 2>/dev/null >/dev/null && {
  #Store the modified files as .preupsave
  for j in $(rpm -V $old | rev | cut -d' ' -f1 | rev | grep -v "(replaced)")
  do
    cp $j $j.preupsave
    echo "Storing modified file $j from $old as $j.preupsave"
  done
  #deinstall the old package
  rpm -e $old --nodeps
  echo "Package $old uninstalled"
  }
  #do we already have all new installed? Skip it...
  rpm -q $new >/dev/null && continue
  yum install -y $new || {
    prep_source_right && \
      yum install -y $new
  }
  rpm -q $new 2>/dev/null >/dev/null && echo "Package(s) $new installed" && continue
  #when we are here, installation got wrong and we should warn the user.
  echo  "The automatic installation of package(s) $new failed, install it manually."
done
for old in $(echo "SEDME2HERE")
do
  #we want to remove the old package if still present
  rpm -q $old 2>/dev/null >/dev/null && {
  #Store the modified files as .preupsave
  for j in $(rpm -V $old | rev | cut -d' ' -f1 | rev | grep -v "(replaced)")
  do
    cp $j $j.preupsave
    echo "Storing modified file $j from $old as $j.preupsave"
  done
  #deinstall the old package
  rpm -e $old --nodeps
  echo "Package $old uninstalled"
  }
done
EOF
  sed -i -e "s/SEDMEHERE/$l/" $VALUE_TMP_PREUPGRADE/postupgrade.d/replacedpkg/fixreplaced.sh
  sed -i -e "s/SEDME2HERE/$removeme/" $VALUE_TMP_PREUPGRADE/postupgrade.d/replacedpkg/fixreplaced.sh
  chmod +x $VALUE_TMP_PREUPGRADE/postupgrade.d/replacedpkg/fixreplaced.sh
}

# TBD Do the comps groups replacements (when someone had full yum group
# on RHEL 6, assume he wants it on RHEL 7 as well, rather than having only
# limited set of packages)

# it looks better sorted
for file in $(ls $KICKSTART_DIR/${FILENAME_BASIS}*); do
  # add header line
  echo "# old-package|required-by-pkgs|replaced-by-pkgs|repo-id" > ${file}.bak
  cat "$file" | sort | uniq >> ${file}.bak
  mv ${file}.bak $file
done

echo -n "
 * ${FILENAME_BASIS} - This file contains a list of packages which replace original Red Hat Enterprise Linux 6 packages on the Red Hat Enterprise Linux 7 system and are available in the 'base' channel. These packages will always be installed.
 * ${FILENAME_BASIS}-notbase - Similar to the ${FILENAME_BASIS} file but the packages are a part of other channels and must be installed manually.
" >> "$KICKSTART_README"

[ $notprovided -eq 1 ] && [ -z "$other_repositories" ] && statuscode=$RESULT_FIXED

[ $found -eq 1 ] && log_slight_risk "\
Some packages installed on the system changed their name between Red Hat Enterprise Linux 6 and Red Hat Enterprise Linux 7. Although they should be compatible, monitor them after the update." && exit $statuscode

rm -f solution.txt && touch solution.txt

exit $RESULT_PASS
