# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   Name: rpms.sh - part of the BeakerLib project
#   Description: Package manipulation helpers
#
#   Author: Petr Muller <pmuller@redhat.com>
#   Author: Jan Hutar <jhutar@redhat.com>
#   Author: Petr Splichal <psplicha@redhat.com>
#   Author: Ales Zelinka <azelinka@redhat.com>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   Copyright (c) 2008-2010 Red Hat, Inc. All rights reserved.
#
#   This copyrighted material is made available to anyone wishing
#   to use, modify, copy, or redistribute it subject to the terms
#   and conditions of the GNU General Public License version 2.
#
#   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, write to the Free
#   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
#   Boston, MA 02110-1301, USA.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

: <<'=cut'
=pod

=head1 NAME

BeakerLib - rpms - Package manipulation helpers

=head1 DESCRIPTION

Functions in this BeakerLib script are used for RPM manipulation.

=head1 FUNCTIONS

=cut

. $BEAKERLIB/testing.sh
. $BEAKERLIB/infrastructure.sh

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Internal Stuff
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

__INTERNAL_RpmPresent() {
    local assert=$1
    local name=$2
    local version=$3
    local release=$4
    local arch=$5

    local package=$name-$version-$release.$arch
    [ "$arch"    == "" ] && package=$name-$version-$release
    [ "$release" == "" ] && package=$name-$version
    [ "$version" == "" ] && package=$name

    export __INTERNAL_RPM_ASSERTED_PACKAGES="$__INTERNAL_RPM_ASSERTED_PACKAGES $name"
    rljRpmLog "$name"

    if [ -n "$package" ]; then
        rpm -q $package
        local status=$?
    else
        local status=100
    fi

    if [ "$assert" == "assert" ] ; then
        __INTERNAL_ConditionalAssert "Checking for the presence of $package rpm" $status
    elif [ "$assert" == "assert_inverted" ] ; then
        if [ $status -eq 1 ]; then
            status=0
        elif [ $status -eq 0 ]; then
            status=1
        fi
        __INTERNAL_ConditionalAssert "Checking for the non-presence of $package rpm" $status
    elif [ $status -eq 0 ] ; then
        rlLog "Package $package is present"
    else
        rlLog "Package $package is not present"
    fi

    if rpm -q --quiet $package
    then
      rlLog "Package versions:"
      rpm -q --qf '%{name}-%{version}-%{release}.%{arch}\n' $package | while read line
      do
        rlLog "  $line"
      done
    fi

    return $status
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlCheckRpm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head2 Rpm Handling

=head3 rlCheckRpm

Check whether a package is installed.

    rlCheckRpm name [version] [release] [arch]

=over

=item name

Package name like C<kernel>

=item version

Package version like C<2.6.25.6>

=item release

Package release like C<55.fc9>

=item arch

Package architucture like C<i386>

=back

Returns 0 if the specified package is installed.

=cut

rlCheckRpm() {
    __INTERNAL_RpmPresent noassert $1 $2 $3 $4
}

# backward compatibility
rlRpmPresent() {
    rlLogWarning "rlRpmPresent is obsoleted by rlCheckRpm"
    __INTERNAL_RpmPresent noassert $1 $2 $3 $4
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlAssertRpm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlAssertRpm

Assertion making sure that a package is installed.

    rlAssertRpm name [version] [release] [arch]>
    rlAssertRpm --all

=over

=item name

Package name like C<kernel>

=item version

Package version like C<2.6.25.6>

=item release

Package release like C<55.fc9>

=item arch

Package architucture like C<i386>

=item --all

Assert all packages listed in the $PACKAGES $REQUIRES and $COLLECTIONS
environment variables.

=back

Returns 0 and asserts PASS if the specified package is installed.

=cut

rlAssertRpm() {
    local package packages
    if [ "$1" = "--all" ] ; then
        packages="$PACKAGES $REQUIRES $COLLECTIONS"
        if [ "$packages" = "  " ] ; then
            __INTERNAL_ConditionalAssert "rlAssertRpm: No package provided" 1
            return
        fi
        for package in $packages ; do
            rlAssertRpm $package
        done
    else
        __INTERNAL_RpmPresent assert $1 $2 $3 $4
    fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlAssertNotRpm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlAssertNotRpm

Assertion making sure that a package is not installed. This is
just inverse of C<rlAssertRpm>.

    rlAssertNotRpm name [version] [release] [arch]>

=over

=item name

Package name like C<kernel>

=item version

Package version like C<2.6.25.6>

=item release

Package release like C<55.fc9>

=item arch

Package architucture like C<i386>

=back

Returns 0 and asserts PASS if the specified package is not installed.

=head3 Example

Function C<rlAssertRpm> is useful especially in prepare phase
where it causes abort if a package is missing, while
C<rlCheckRpm> is handy when doing something like:

    if ! rlCheckRpm package; then
         yum install package
         rlAssertRpm package
    fi

=cut

rlAssertNotRpm() {
    __INTERNAL_RpmPresent assert_inverted $1 $2 $3 $4
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlAssertBinaryOrigin
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlAssertBinaryOrigin

Assertion making sure that given binary is owned by (one of) the given
package(s).

    rlAssertBinaryOrigin binary package [package2 [...]]
    PACKAGES=... rlAssertBinaryOrigin binary

=over

=item binary

Binary name like C<ksh> or C</bin/ksh>

=item package

List of packages like C<ksh mksh>. The parameter is optional. If missing,
contents of environment variable $PACKAGES are taken into account.

=back

Returns 0 and asserts PASS if the specified binary belongs to (one of) the given package(s).
Returns 1 and asserts FAIL if the specified binary does not belong to (any of) the given package(s).
Returns 2 and asserts FAIL if the specified binary is not found.
Returns 3 and asserts FAIL if no packages are given.
Returns 100 and asserts FAIL if invoked with no parameters.

=head3 Example

Function C<rlAssertBinaryOrigin> is useful especially in prepare phase
where it causes abort if a binary is missing or is owned by different package:

    PACKAGES=mksh rlAssertBinaryOrigin ksh
    or
    rlAssertBinaryOrigin ksh mksh

Returns true if ksh is owned by the mksh package (in this case: /bin/ksh is a
symlink pointing to /bin/mksh).

=cut

rlAssertBinaryOrigin() {
    # without parameters, exit immediatelly
    local status CMD FULL_CMD
    status=0
    [ $# -eq 0 ] && {
       status=100
       rlLogError "rlAssertBinaryOrigin called without parameters"
       __INTERNAL_ConditionalAssert "rlAssertBinaryOrigin: No binary given" $status
       return $status
    }

    CMD=$1
    shift

    # if not given explicitly as param, take PKGS from $PACKAGES
    local PKGS=$@
    [ $# -eq 0 ] && PKGS=$PACKAGES
    if [ -z "$PKGS" ]; then
        status=3
       __INTERNAL_ConditionalAssert "rlAssertBinaryOrigin: No packages given" $status
       return $status
   fi

    status=2
    FULL_CMD=$(which $CMD) &>/dev/null && \
    {
        status=1
        # expand symlinks (if any)
        local BINARY=$(readlink -f $FULL_CMD)

        # get the rpm owning the binary
        local BINARY_RPM=$(rpm -qf --qf="%{name}\n" $BINARY | uniq)

        rlLogDebug "Binary rpm: $BINARY_RPM"

        local TESTED_RPM
        for TESTED_RPM in $PKGS ; do
            if [ "$TESTED_RPM" = "$BINARY_RPM" ] ; then
                status=0
                echo $BINARY_RPM
                break
            fi
        done
    }

    [ $status -eq 2 ] && rlLogError "$CMD: command not found"
    [ $status -eq 1 ] && rlLogError "$BINARY belongs to $BINARY_RPM"

    __INTERNAL_ConditionalAssert "Binary '$CMD' should belong to: $PKGS" $status
    return $status
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlGetMakefileRequires
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlGetMakefileRequires

Prints comma separated list of requirements defined in Makefile using 'Requires'
attribute.

Return 0 if success.

=cut

rlGetMakefileRequires() {
  [[ -s ./Makefile ]] || {
    rlLogError "Could not find ./Makefile or the file is empty"
    return 1
  }
  grep '"Requires:' Makefile | sed -e 's/.*Requires: *\(.*\)".*/\1/' | tr ' ' '\n' | sort | uniq | tr '\n' ' ' | sed -r 's/^ +//;s/ +$//;s/ +/ /g'
  return 0
}; # end of rlGetMakefileRequires


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlCheckRequirements
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlCheckRequirements

Check that all given requirements are covered eigther by installed package or by
binary available in PATHs or by some package's provides.

    rlRun "rlCheckRequirements REQ..."

=over

=item REQ

Requirement to be checked. It can be package name, provides string or binary
name.

=back

Returns number of unsatisfied requirements.

=cut
#'

rlCheckRequirements() {
  local req res=0 package binary provides LOG=() LOG2 l=0 ll
  for req in "$@"; do
    package="$(rpm -q "$req" 2> /dev/null)"
    if [[ $? -eq 0 ]]; then
      LOG=("${LOG[@]}" "$package" "covers requirement '$req'")
      rljRpmLog "$package"
    else
      binary="$(which "$req" 2> /dev/null)"
      if [[ $? -eq 0 ]]; then
        package="$(rpm -qf "$binary")"
        LOG=("${LOG[@]}" "$package" "covers requirement '$req' by binary '$binary' from package '$package'")
        rljRpmLog "$package"
      else
        package="$(rpm -q --whatprovides "$req" 2> /dev/null)"
        if [[ $? -eq 0 ]]; then
          LOG=("${LOG[@]}" "$package" "covers requirement '$req'")
          rljRpmLog "$package"
        else
          rlLogWarning "requirement '$req' not satisfied"
          let res++
        fi
      fi
    fi
  done
  LOG2=("${LOG[@]}")
  while [[ ${#LOG2[@]} -gt 0 ]]; do
    [[ ${#LOG2} -gt $l ]] && l=${#LOG2}
    LOG2=("${LOG2[@]:2}")
  done
  local spaces=''
  for ll in `seq $l`; do spaces="$spaces "; done
  while [[ ${#LOG[@]} -gt 0 ]]; do
    let ll=$l-${#LOG}+1
    rlLog "package '$LOG' ${spaces:0:$ll} ${LOG[1]}"
    LOG=("${LOG[@]:2}")
  done
  return $res
}; # end of rlCheckRequirements


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlCheckMakefileRequires
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlCheckMakefileRequires

This is just a bit smarted wrapper of

C<rlCheckRequirements $(rlGetMakefileRequires)>

=head3 Example

    rlRun "rlCheckMakefileRequires"

Return 255 if requirements could not be retrieved, 0 if all requirements are
satisfied or number of unsatisfied requirements.



=cut

rlCheckMakefileRequires() {
  local req
  req="$(rlGetMakefileRequires)" || return 255
  eval rlCheckRequirements $req
}; # end of rlCheckMakefileRequires


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlAssertRequired
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<=cut
=pod

=head3 rlAssertRequired

Ensures that all Requires, specified in beakerlib-style beaker-wizard layout
Makefile, are installed.

    rlAssertRequired

Prints out a verbose list of installed/missing packages during operation.

Returns 0 if all required packages are installed, 1 if one
or more packages are missing or if no Makefile is present.

=cut

rlAssertRequired(){
    local MAKEFILE="Makefile"

    if [ ! -e "$MAKEFILE" ]; then
        rlLogError "rlAssertRequired: $MAKEFILE not present"
        return 1
    fi

    list="$(grep 'Requires:' $MAKEFILE)"
    if [ -z "$list" ]; then
        rlLogError "rlAssertRequired: $MAKEFILE does not contain 'Requires:'"
        return 1
    fi

    list=$(echo "$list" | sed -r 's/^[ \t]+@echo "Requires:[ \t]+([^"]+)" >> \$\(METADATA\)$/\1/' | tr '\n' ' ')

    rpm -q $list

    __INTERNAL_ConditionalAssert "Assert required packages installed" $?

    return $?
}


: <<=cut
=pod

=head2 Getting RPMs

=head3 Download methods

Functions handling rpm downloading/installing can use more methods for actual
download of the rpm.

Currently there are two download methonds available:

=over

=item direct

Use use dirct download from build system (brew).

=item yum

Use yumdownloader.

=back

The methods and their order are defined by BEAKERLIB_RPM_DOWNLOAD_METHODS
variable as space separated list. By default it is 'direct yum'. This can be
overridden by user.
There may be done also additions  or changes to the original value,
e.g. BEAKERLIB_RPM_DOWNLOAD_METHODS='yum ${BEAKERLIB_RPM_DOWNLOAD_METHODS/yum/}'

=head3

Beakerlib is prepared for more Koji-based sources of packages while usigng
direct download method. By default packages are fetched from Koji, particularly
from https://kojipkgs.fedoraproject.org/packages.

=cut


# return information of the first matching package
# $1 - method (rpm | repoquery)
# $2 - packages (NVR)
__INTERNAL_rpmGetPackageInfo() {
  local package_info package_info_raw res=0
  rlLogDebug "${FUNCNAME}(): getting package info for '$2'"
  package_info_raw="$($1 --qf "%{name} %{version} %{release} %{arch} %{sourcerpm}\n" -q "$2")"
  [[ $? -ne 0 || -z "$package_info_raw" ]] && {
    rlLogDebug "${FUNCNAME}(): package_info_raw: '$package_info_raw'"
    rlLogInfo "rpm '$2' not available using '$1' tool"
    let res++
  }
  if [[ $res -eq 0 ]]; then
    rlLogDebug "${FUNCNAME}(): package_info_raw: '$package_info_raw'"
    # get first package
    local tmp=( $(echo "$package_info_raw" | head -n 1 ) )
    # extract component name from source_rpm
    package_info="${tmp[@]:0:4} $(__INTERNAL_getNVRA "${tmp[4]/.rpm}")" || {
      rlLogError "parsing package info '$package_info_raw' failed"
      let res++
    }
    rlLogDebug "${FUNCNAME}(): package_info: '$package_info'"
    rlLogDebug "got rpm info for '$2' via '$1'"
    echo "$package_info"
  fi
  rlLogDebug "${FUNCNAME}(): returning $res"
  return $res
}


BEAKERLIB_rpm_fetch_base_url=( "https://kojipkgs.fedoraproject.org/packages" )
BEAKERLIB_rpm_packageinfo_base_url=( "http://koji.fedoraproject.org/koji" )

# generate combinations for various methods and parameters of finding the package
__INTERNAL_rpmInitUrl() {
    local i j k

    rlLogDebug "${FUNCNAME}(): preparing download variants"

    __INTERNAL_rpmGetNextUrl_phase=()
    for k in rpm repoquery; do
      for j in nvr n; do
        for i in "${BEAKERLIB_rpm_fetch_base_url[@]}"; do
          __INTERNAL_rpmGetNextUrl_phase+=( "$k" ); # tool
          __INTERNAL_rpmGetNextUrl_phase+=( "$j" ); # package_spec
          __INTERNAL_rpmGetNextUrl_phase+=( "$i" ); # base_url
        done
      done
    done
    for j in koji; do
      for i in "${BEAKERLIB_rpm_packageinfo_base_url[@]}"; do
        __INTERNAL_rpmGetNextUrl_phase+=( "$j" ) ; # tool
        __INTERNAL_rpmGetNextUrl_phase+=( "nvra.rpm" )   ; # package_spec
        __INTERNAL_rpmGetNextUrl_phase+=( "$i" ) ; # base_url
      done
    done
    rlLogDebug "${FUNCNAME}(): $(set | grep ^__INTERNAL_rpmGetNextUrl_phase=)"
}


__INTERNAL_WGET="wget -t 3 -T 180 -w 20 --waitretry=30 --no-check-certificate --progress=dot:giga"

# __INTERNAL_rpmGetNextUrl N V R A | --source N V R
__INTERNAL_rpmGetNextUrl() {
    local source=''
    [[ "$1" == "--source" ]] && {
        source="$1"
        shift
    }
    local N=$1 V=$2 R=$3 A=$4 nil res url
    rlLogDebug "${FUNCNAME}(): process $N-$V-$R.$A"
    while [[ -n "$__INTERNAL_rpmGetNextUrl_phase" ]]; do
      res=0
      local tool=${__INTERNAL_rpmGetNextUrl_phase[0]}
      local package_spec=${__INTERNAL_rpmGetNextUrl_phase[1]}
      local base_url=${__INTERNAL_rpmGetNextUrl_phase[2]}
      #rlLogDebug "${FUNCNAME}(): $(set | grep ^__INTERNAL_rpmGetNextUrl_phase=)"
      rlLogDebug "${FUNCNAME}(): remove first three indices of __INTERNAL_rpmGetNextUrl_phase"
      __INTERNAL_rpmGetNextUrl_phase=( "${__INTERNAL_rpmGetNextUrl_phase[@]:3}" )
      rlLogDebug "trying tool $tool with $package_spec"
      case $tool,$package_spec in
        *,nvr)
          IFS=' ' read nil nil nil nil CN CV CR CA < <(__INTERNAL_rpmGetPackageInfo $tool "$N-$V-$R")
          res=$?
          if [[ -n "$source" ]]; then
            url="$base_url/$CN/$CV/$CR/src/$CN-$CV-$CR.src.rpm"
          else
            url="$base_url/$CN/$CV/$CR/$A/$N-$V-$R.$A.rpm"
          fi
          ;;
        *,n)
          IFS=' ' read nil nil nil nil CN CV CR CA < <(__INTERNAL_rpmGetPackageInfo $tool "$N")
          res=$?
          if [[ -n "$source" ]]; then
            url="$base_url/$CN/$V/$R/src/$CN-$V-$R.src.rpm"
          else
            url="$base_url/$CN/$V/$R/$A/$N-$V-$R.$A.rpm"
          fi
          ;;
        koji,nvra.rpm)
          rlLogDebug "$FUNCNAME(): get rpm info"
          local rpm_info=$($__INTERNAL_WGET -O - "$base_url/search?match=exact&type=rpm&terms=$N-$V-$R.$A.rpm")
          [[ $? -ne 0 || -z "$rpm_info" ]] && {
            rlLogError "could not download rpm information"
            let res++
            continue
          }
          #[[ "$DEBUG" ]] && rlLogDebug "rpm_info='$rpm_info'"
          local buildurl=$(echo "$rpm_info" | grep Version | grep -o '[^"]*buildID=[^"]*')
          [[ $? -ne 0 || -z "$buildurl" ]] && {
            rlLogError "could not find buildID"
            let res++
            continue
          }
          rlLogDebug "$FUNCNAME(): extracted buildurl='$buildurl'"
          [[ "$buildurl" =~ http ]] || buildurl="$base_url/$buildurl"
          rlLogDebug "$FUNCNAME(): using buildurl='$buildurl'"
          local buildinfo=$($__INTERNAL_WGET -O - "$buildurl")
          [[ $? -ne 0 || -z "$buildinfo" ]] && {
            rlLogError "could not download build information"
            let res++
            continue
          }
          #[[ -n "$DEBUG" ]] && rlLogDebug "buildinfo='$buildinfo'"
          if [[ -n "$source" ]]; then
            url=$(echo "$buildinfo" | grep download | grep -o 'http[^"]*.src.rpm')
          else
            url=$(echo "$buildinfo" | grep download | grep -o "http[^\"]*/$N-$V-$R.$A.rpm")
          fi
          [[ $? -ne 0 ]] && {
            rlLogError "could not find package url"
            let res++
            continue
          }
          ;;
        *)
          rlLogDebug "$FUNCNAME(): unknown case"
          rlLogError "there's a bug in the code, unknown case!"
          let res++
          break
      esac
      [[ $res -eq 0 ]] && break
    done

    [[ -z "$url" ]] && {
      rlLogError "could not find package url"
      let res++
    }

    rlLogDebug "$FUNCNAME(): using url='$url'"

    [[ $res -eq 0 ]] && __INTERNAL_RETURN_VALUE="$url"

    rlLogDebug "${FUNCNAME}(): returning $res"
    return $res
}


__INTERNAL_getNVRA() {
    rlLogDebug "${FUNCNAME}(): parsing NVRA for '$1'"
    local pat='(.+)-([^-]+)-(.+)\.([^.]+)$'
    [[ "$1" =~ $pat ]] && echo "${BASH_REMATCH[@]:1}"
}


__INTERNAL_rpmDirectDownload() {
    local url pkg quiet=''
    [[ "$1" == "--quiet" ]] && {
      quiet="$1"
      shift
    }

    __INTERNAL_rpmInitUrl
    while __INTERNAL_rpmGetNextUrl "$@"; do
        url="$__INTERNAL_RETURN_VALUE"; unset __INTERNAL_RETURN_VALUE
        local pkg=$(basename "$url")
        rlLog "trying download from '$url'"
        if $__INTERNAL_WGET $quiet -O $pkg "$url"; then
            rlLogDebug "$FUNCNAME(): package '$pkg' was successfully downloaded"
            echo "$pkg"
            return 0
        fi
        rm -f "$pkg"
        rlLogWarning "package '$pkg' was not successfully downloaded"
    done
    rlLogError "package '$pkg' was not successfully downloaded"
    rlLogDebug "${FUNCNAME}(): returning 1"
    return 1
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# __INTERNAL_rpmGetWithYumDownloader
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#  Download package using yumdownloader

__INTERNAL_rpmGetWithYumDownloader() {
    local source='' quiet=''
    while [[ "${1:0:2}" == "--" ]]; do
      case $1 in
        --quiet)
          quiet="$1"
        ;;
        --source)
          source="$1"
        ;;
      esac
      shift
    done

    local package="$1-$2-$3.$4"
    [[ -n "$source" ]] && package="$1-$2-$3"
    rlLogDebug "${FUNCNAME}(): Trying yumdownloader to download $package"
    if ! which yumdownloader &> /dev/null ; then
        rlLogInfo "yumdownloader not installed: attempting to install yum-utils"
        yum install $quiet -y yum-utils >&2
    fi

    if ! which yumdownloader &> /dev/null ; then
        rlLogError "yumdownloader not installed even after instalation attempt"
        return 1
    else
        local tmp
        if tmp=$(mktemp -d); then
            rlLogDebug "$FUNCNAME(): downloading package to tmp dir '$tmp'"
            ( cd $tmp; yumdownloader $quiet -y $source $package >&2 ) || {
                rlLogError "yum downloader failed"
                rm -rf $tmp
                return 1
            }
            local pkg=$( ls -1 $tmp )
            rlLogDebug "$FUNCNAME(): got '$pkg'"
            local pkg_cnt=$( echo "$pkg" | wc -w )
            rlLogDebug "$FUNCNAME(): pkg_cnt=$pkg_cnt"
            [[ $pkg_cnt -eq 0 ]] && {
                rlLogError "did not download anything"
                rm -rf $tmp
                return 1
            }
            [[ $pkg_cnt -gt 1 ]] && rlLogWarning "got more than one package"
            rm -f $pkg
            rlLogDebug "$FUNCNAME(): moving package to local dir"
            mv $tmp/* ./
            rlLogDebug "$FUNCNAME(): removing tmp dir '$tmp'"
            rm -rf $tmp
            rlLogDebug "$FUNCNAME(): Download with yumdownloader successful"
            echo "$pkg"
            return 0
        else
            rlLogError "could not create tmp dir"
            return 1
        fi
    fi
}

# magic to allow this:
# BEAKERLIB_RPM_DOWNLOAD_METHODS="yum ${BEAKERLIB_RPM_DOWNLOAD_METHODS/yum/}"
__INTERNAL_BEAKERLIB_RPM_DOWNLOAD_METHODS_default='direct yum'
__INTERNAL_BEAKERLIB_RPM_DOWNLOAD_METHODS_cmd="BEAKERLIB_RPM_DOWNLOAD_METHODS=\"${BEAKERLIB_RPM_DOWNLOAD_METHODS:-$__INTERNAL_BEAKERLIB_RPM_DOWNLOAD_METHODS_default}\""
BEAKERLIB_RPM_DOWNLOAD_METHODS=$__INTERNAL_BEAKERLIB_RPM_DOWNLOAD_METHODS_default
eval "$__INTERNAL_BEAKERLIB_RPM_DOWNLOAD_METHODS_cmd"
unset __INTERNAL_BEAKERLIB_RPM_DOWNLOAD_METHODS_cmd

__INTERNAL_rpmDownload() {
    local method rpm res

    rlLogDebug "$FUNCNAME(): trying $* with methods '$BEAKERLIB_RPM_DOWNLOAD_METHODS'"

    for method in $BEAKERLIB_RPM_DOWNLOAD_METHODS; do
        rlLogDebug "trying to get the package using method '$method'"
        case $method in
            direct)
                if rpm="$(__INTERNAL_rpmDirectDownload "$@")"; then
                    res=0
                    break
                else
                    rlLogWarning "could not get package unsing method '$method'"
                    let res++
                fi
            ;;
            yum)
                if rpm="$(__INTERNAL_rpmGetWithYumDownloader "$@")"; then
                    res=0
                    break
                else
                    rlLogWarning "could not get package unsing method '$method'"
                    let res++
                fi
            ;;
            *)
                rlLogError "invalid fetch method '$method'"
                return 1
            ;;
        esac
    done
    [[ $res -eq 0 ]] && echo "$rpm"
    rlLogDebug "$FUNCNAME(): returning $res"
    return $res
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlRpmInstall
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<=cut
=pod

=head3 rlRpmInstall

Try to install specified package from local Red Hat sources.

    rlRpmInstall [--quiet] package version release arch

=over

=item --quiet

Make the download and the install process be quiet.

=item package

Package name like C<kernel>

=item version

Package version like C<2.6.25.6>

=item release

Package release like C<55.fc9>

=item arch

Package arch like C<i386>

=back

Returns 0 if specified package was installed succesfully.

=cut

rlRpmInstall(){
    local quiet=''
    [[ "$1" == "--quiet" ]] && {
      quiet="$1"
      shift
    }

    if [ $# -ne 4 ]; then
        rlLogError "Missing parameter(s)"
        return 1
    fi

    local N=$1;
    local V=$2;
    local R=$3;
    local A=$4;
    rlLog "Installing RPM $N-$V-$R.$A"

    if rlCheckRpm $N $V $R $A; then
        rlLog "$FUNCNAME: $N-$V-$R.$A already present. Doing nothing"
        return 0
    else
        local tmp=$(mktemp -d)
        ( cd $tmp; __INTERNAL_rpmDownload $quiet $N $V $R $A )
        if [ $? -eq 0 ]; then
            rlLog "RPM: $N-$V-$R.$A.rpm"
            if [[ -z "$quiet" ]]; then
              rpm -Uhv --oldpackage "$tmp/$N-$V-$R.$A.rpm"
            else
              rpm -Uq --oldpackage "$tmp/$N-$V-$R.$A.rpm"
            fi
            local ECODE=$?
            if [ $ECODE -eq 0 ] ; then
                rlLogInfo "$FUNCNAME: RPM installed successfully"
            else
                rlLogError "$FUNCNAME: RPM installation failed"
            fi
            rm -rf "$tmp"
            return $ECODE
        else
           rlLogError "$FUNCNAME: RPM not found"
        fi
        return 1
    fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlRpmDownload
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<=cut
=pod

=head3 rlRpmDownload

Try to download specified package.

    rlRpmDownload [--quiet] {package version release arch|N-V-R.A}
    rlRpmDownload [--quiet] --source {package version release|N-V-R}

=over

=item --quiet

Make the download process be quiet.

=item package

Package name like C<kernel>

=item version

Package version like C<2.6.25.6>

=item release

Package release like C<55.fc9>

=item arch

Package arch like C<i386>

=back

Returns 0 if specified package was downloaded succesfully.

=cut

rlRpmDownload(){
    local source='' res pkg N V R A quiet=''
    while [[ "${1:0:2}" == "--" ]]; do
      case $1 in
        --quiet)
          quiet="$1"
        ;;
        --source)
          source="$1"
        ;;
      esac
      shift
    done

    if [[ $# -eq 1 ]]; then
        local package="$1"
        [[ -n "$source" ]] && package="$package.src"
        if ! IFS=' ' read N V R A < <(__INTERNAL_getNVRA "$package"); then
            rlLogError "$FUNCNAME: Bad N.V.R-A format"
            return 1
        fi
    elif [[ -z "$source" && $# -eq 4 ]]; then
        N="$1"
        V="$2"
        R="$3"
        A="$4"
    elif [[ -n "$source" && $# -ge 3 ]]; then
        N="$1"
        V="$2"
        R="$3"
        A=""
    else
        rlLogError "$FUNCNAME: invalid parameter(s)"
        return 1
    fi

    rlLog "$FUNCNAME: Fetching ${source:+source }RPM $N-$V-$R.$A"

    if pkg=$(__INTERNAL_rpmDownload $quiet $source $N $V $R $A); then
        rlLog "RPM: $pkg"
        echo "$pkg"
        return 0
    else
        rlLogError "$FUNCNAME: RPM download failed"
        return 1
    fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlFetchSrcForInstalled
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<=cut
=pod

=head3 rlFetchSrcForInstalled

Tries various ways to download source rpm for specified installed rpm.

    rlFetchSrcForInstalled [--quiet] package

=over

=item --quiet

Make the download process be quiet.

=item package

Installed package name like C<kernel>. It accepts in-direct names.
Eg for the package name C<krb5-libs> will the function download
the C<krb5> source rpm.

=back

Returns 0 if the source package was succesfully downloaded.

=cut

rlFetchSrcForInstalled(){
    local quiet=''
    [[ "$1" == "--quiet" ]] && {
      quiet="$1"
      shift
    }

    local PKGNAME=$1 srcrpm
    local N V R nil

    if ! IFS=' ' read N V R nil < <((__INTERNAL_rpmGetPackageInfo rpm "$PKGNAME")); then
        rlLogError "$FUNCNAME: The package is not installed, can't download the source"
        return 1
    fi
    rlLog "$FUNCNAME: Fetching source rpm for installed $N-$V-$R"

    if srcrpm="$(__INTERNAL_rpmDownload $quiet --source $N $V $R)"; then
        echo "$srcrpm"
        return 0
    else
        rlLogError "$FUNCNAME: could not get package"
        return 1
    fi
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUTHORS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head1 AUTHORS

=over

=item *

Petr Muller <pmuller@redhat.com>

=item *

Jan Hutar <jhutar@redhat.com>

=item *

Petr Splichal <psplicha@redhat.com>

=item *

Ales Zelinka <azelinka@redhat.com>

=item *

Dalibor Pospisil <dapospis@redhat.com>

=back

=cut
