#!/usr/bin/perl
########################################################
# URPM Repos Closure Checker 1.2 for Mandriva Linux
# A tool for checking closure of a set of RPM packages
#
# Copyright (C) 2012 ROSA Laboratory.
# Written by Andrey Ponomarenko
#
# PLATFORMS
# =========
#  Linux (Mandriva)
#
# REQUIREMENTS
# ============
#  - urpmi
#  - Perl5 (>=5.8)
#  - Wget
#
# 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.
#
# 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/>.
########################################################
use Getopt::Long;
Getopt::Long::Configure ("posix_default", "no_ignore_case");
use Cwd qw(abs_path cwd);
use File::Path qw(mkpath rmtree);
use File::Temp qw(tempdir);
use File::Copy qw(copy move);
use Data::Dumper;
use strict;

my $TOOL_VERSION = "1.2";
my $CmdName = get_filename($0);

my ($Help, $ShowVersion, $RPMlist, $RPMdir, $StaticMode,
$DynamicMode, $CheckRelease, $CheckSignature, $SelectRepos,
$NoClean, $Root, $HDlist, $FileDeps, $ResDir);

my $ShortUsage = "URPM Repos Closure Checker $TOOL_VERSION for Mandriva Linux
A tool for checking closure of a set of RPM packages
Copyright (C) 2012 ROSA Laboratory
License: GNU GPL

Usage: $CmdName [options]
Example: $CmdName --hdlist=hdlist.txt

More info: $CmdName --help\n";

if($#ARGV==-1) {
    print $ShortUsage."\n";
    exit(0);
}

GetOptions("h|help!"=>\$Help,
  "v|version!"=>\$ShowVersion,
  "l|list=s"=>\$RPMlist,
  "d|dir=s"=>\$RPMdir,
  "hdlist=s"=>\$HDlist,
  "file-deps=s"=>\$FileDeps,
  "s|static!"=>\$StaticMode,
  "dynamic!"=>\$DynamicMode,
  "r|check-release!"=>\$CheckRelease,
  "sign|check-signature!"=>\$CheckSignature,
  "media=s"=>\$SelectRepos,
  "noclean!"=>\$NoClean,
  "root=s"=>\$Root,
  "o|res=s"=>\$ResDir
) or ERR_MESSAGE();

my %EXIT_CODES = (
    "SUCCESS" => 0,
    "ERROR" => 1,
    "FAILED" => 2
);

my $HelpMessage = "
NAME:
  URPM Repos Closure Checker 1.0 for Mandriva Linux
  A tool for checking closure of a set of RPM packages

USAGE:
  $CmdName --hdlist=hdlist.txt
  $CmdName --hdlist=http://mirror.yandex.ru/mandriva/.../synthesis.hdlist.cz
  $CmdName --dir=rpms/ --static --file-deps=file-deps.txt
  $CmdName --list=list.txt --dynamic

OPTIONS:
  -h|-help
      Print this help.

  -v|-version
      Print version information.

  -hdlist <path>
      Path or URL of HDlist (synthesis) to check.

  -d|-dir <path>
      The directory with RPM packages to check.

  -l|-list <path>
      The list of packages to check.

  -file-deps <path>
      Read file-deps to ignore some unresolved
      dependencies.

  -s|-static
      Check statically if all required dependencies are
      satisfied by provided dependencies in the set of
      RPM packages.

  -dynamic
      Install a set of RPM packages to the local chroot
      and check if extra packages were installed.

  -r|-check-release
      Check installation media (DVD).

  -sign|-check-signature
      Validate package signatures.

  -noclean
      Do not clean urpmi cache.

  -root <path>
      Where to install packages.
      Default:
          /tmp/...

EXIT CODES:
      0 - Suceess. The tool has run without any errors
      non-zero - Failed or the tool has run with errors. In particular:
      1 - Failed to run the tool
      2 - Discovered dependency problems
      
\n";

sub HELP_MESSAGE() {
    print $HelpMessage;
}

sub ERR_MESSAGE()
{
    print $ShortUsage;
    exit(1);
}

my %Cache;
my $RPM_CACHE = "/var/cache/urpmi/rpms";
my $TMP_DIR = tempdir(CLEANUP=>1);
my %InstalledPackage;
my %RequiredBy;
my $TEST_MEDIA = "test_media";
my %Packages;
my %BrokenSignature;
my %InstallFailed;
my $RESULTS_DIR = "repoclosure_reports";

sub appendFile($$)
{
    my ($Path, $Content) = @_;
    return if(not $Path);
    if(my $Dir = get_dirname($Path)) {
        mkpath($Dir);
    }
    open(FILE, ">>".$Path) || die ("can't open file \'$Path\': $!\n");
    print FILE $Content;
    close(FILE);
}

sub writeFile($$)
{
    my ($Path, $Content) = @_;
    return if(not $Path);
    if(my $Dir = get_dirname($Path)) {
        mkpath($Dir);
    }
    open (FILE, ">".$Path) || die ("can't open file \'$Path\': $!\n");
    print FILE $Content;
    close(FILE);
}

sub readFile($)
{
    my $Path = $_[0];
    return "" if(not $Path or not -f $Path);
    open (FILE, $Path);
    local $/ = undef;
    my $Content = <FILE>;
    close(FILE);
    return $Content;
}

sub get_filename($)
{ # much faster than basename() from File::Basename module
    my $Path = $_[0];
    $Path=~s/[\/\\]+\Z//;
    return $Cache{"get_filename"}{$Path} if($Cache{"get_filename"}{$Path});
    if($Path=~/([^\/\\]+)\Z/) {
        return ($Cache{"get_filename"}{$Path} = $1);
    }
    return "";
}

sub get_dirname($)
{ # much faster than dirname() from File::Basename module
    my $Path = $_[0];
    $Path=~s/[\/\\]+\Z//;
    if($Path=~/\A(.*)[\/\\]+([^\/\\]*)\Z/) {
        return $1;
    }
    return "";
}

sub searchRPMs($)
{
    my $Path = $_[0];
    if(not $Path or not -d $Path) {
        return ();
    }
    my @RPMs = split("\n", `find $Path -type f -name "*.rpm"`); # -maxdepth 1
    return sort {lc($a) cmp lc($b)} @RPMs;
}

sub addMedia($)
{
    my $Dir = $_[0];
    if(not $Dir or not -d $Dir) {
        return;
    }
    my %Media = map {$_=>1} split(/\n+/, `urpmq --list-media`);
    if($Media{$TEST_MEDIA}) {
        removeMedia();
    }
    $Dir = abs_path($Dir);
    system("/usr/sbin/urpmi.addmedia $TEST_MEDIA $Dir");
    system("/usr/sbin/urpmi.update $TEST_MEDIA");
}

sub removeMedia() {
    system("/usr/sbin/urpmi.removemedia $TEST_MEDIA");
}

sub installPackage($)
{
    my $Package = $_[0];
    my $Cmd = "/usr/sbin/urpmi";
    if($CheckRelease)
    { # from CD or DVD
        $Cmd .= " --media=$TEST_MEDIA";
    }
    elsif($SelectRepos)
    {
        if(-d $SelectRepos) {
            $Cmd .= " --media=$TEST_MEDIA";
        }
        else {
            $Cmd .= " --media=$SelectRepos";
        }
    }
    # create root where to install packages
    if(not -d $TMP_DIR."/root") {
        mkpath($TMP_DIR."/root");
    }
    if(not $CheckRelease) {
        $Cmd .= " --no-install";
    }
    if($Root) {
        $Cmd .= " --root=\"$Root\"";
    }
    else {
        $Cmd .= " --root=\"$TMP_DIR/root\"";
    }
    $Cmd .= " --noclean --auto --force";
    $Cmd .= " $Package";
    print "Running $Cmd\n";
    my $LogPath = $TMP_DIR."/ilog.txt";
    system($Cmd." >$LogPath 2>&1");
    my $Log = readFile($LogPath);
    appendFile("$RESULTS_DIR/install-log.txt", $Log);
    $Log=~s/The following packages have to be removed (.|\n)*\Z//g;
    if($Log=~/ (unsatisfied|conflicts with|missing) ([\w\-\/]*)/i)
    {
        my ($Reason, $Dep) = ($1, $2);
        $InstallFailed{getPName($Package)}=1;
        print "  FAILED: due to $Reason $Dep\n";
    }
    if($CheckRelease)
    { # installed
        while($Log=~s/(installing\s+)([^\/\s]+\.rpm)(\s|\Z)/$1/)
        {
            my $RpmName = $2;
            print "  $RpmName\n";
        }
    }
    else
    { # downloaded
        while($Log=~s/(\/)([^\/\s]+\.rpm)(\s|\Z)/$1$3/)
        {
            my $RpmName = $2;
            print "  $RpmName\n";
            $RequiredBy{getPName($RPM_CACHE."/".$RpmName)}=getPName($Package);
        }
    }
}

sub get_RPMname($)
{
    my $Path = $_[0];
    my $Name = get_filename($Path);
    if($Cache{"get_RPMname"}{$Name}) {
        return $Cache{"get_RPMname"}{$Name};
    }
    if(not $Path or not -f $Path) {
        return "";
    }
    return ($Cache{"get_RPMname"}{$Name} = `rpm -qp --queryformat \%{name} $Path`);
}

sub sepDep($)
{
    my $Dep = $_[0];
    if($Dep=~/\A(.+?)(\s+|\[)(=|==|<=|>=|<|>)\s+(.+?)(\]|\Z)/)
    {
        my ($N, $O, $V) = ($1, $3, $4);
        # canonify version (1:3.2.5-5:2011.0)
        $V=~s/\A[^\-\:]+\://;# cut prefix (1:)
        return ($N, $O, $V);
    }
    else {
        return ($Dep, "", "");
    }
}

sub showDep($$$)
{
    my ($N, $O, $V) = @_;
    if($O and $V) {
        return $N." ".$O." ".$V;
    }
    else {
        return $N
    }
}

sub sepVersion($)
{
    my $V = $_[0];
    if($V=~/\A(.+)(\-[^\-\:]+)(\:[^\:]+|)\Z/)
    { # 3.2.5-5:2011.0
        return ($1, $2, $3);
    }
    return ($V, "", "");
}

sub simpleVersion($)
{ # x.y.z-r:n to x.y.z.r.n
    my $V = $_[0];
    $V=~s/[\-:]/\./g; # -5:2011.0
    $V=~s/[a-z]+/\./ig; # 10-12mdk
    $V=~s/\.\Z//g;
    return $V;
}

sub formatVersions(@)
{ # V1 - provided
  # V2 - required
    my ($V1, $V2) = @_;
    my ($V1_M, $V1_R, $V1_RR) = sepVersion($V1);
    my ($V2_M, $V2_R, $V2_RR) = sepVersion($V2);
    if(not $V2_RR) {
        $V1_RR = "";
    }
    if(not $V2_R) {
        $V1_R = "";
    }
    $V1 = $V1_M.$V1_R.$V1_RR;
    $V2 = $V2_M.$V2_R.$V2_RR;
    return (simpleVersion($V1), simpleVersion($V2));
}

sub cmpVersions($$)
{ # compare two versions
  # 3.2.5-5:2011.0
    my ($V1, $V2) = formatVersions(@_);
    return 0 if($V1 eq $V2);
    my @V1Parts = split(/\./, $V1);
    my @V2Parts = split(/\./, $V2);
    for (my $i = 0; $i <= $#V1Parts && $i <= $#V2Parts; $i++)
    {
        return -1 if(int($V1Parts[$i]) < int($V2Parts[$i]));
        return 1 if(int($V1Parts[$i]) > int($V2Parts[$i]));
    }
    return -1 if($#V1Parts < $#V2Parts);
    return 1 if($#V1Parts > $#V2Parts);
    return 0;
}

sub checkDeps($$$$)
{
    my ($N, $O, $V, $Provides) = @_;
    if(not $O or not $V)
    { # requires any version
        return 1;
    }
    foreach my $OP (keys(%{$Provides}))
    {
        if(not $OP)
        { # provides any version
            return 1;
        }
        foreach my $VP (keys(%{$Provides->{$OP}}))
        {
            if($O eq "=" or $O eq "==")
            {
                if(cmpVersions($VP, $V)==0)
                { # requires the same version
                    return 1;
                }
            }
            elsif($O eq "<=")
            {
                if(cmpVersions($VP, $V)<=0) {
                    return 1;
                }
            }
            elsif($O eq ">=")
            {
                if(cmpVersions($VP, $V)>=0) {
                    return 1;
                }
            }
            elsif($O eq "<")
            {
                if(cmpVersions($VP, $V)<0) {
                    return 1;
                }
            }
            elsif($O eq ">")
            {
                if(cmpVersions($VP, $V)>0) {
                    return 1;
                }
            }
        }
    }
    return 0;
}

sub checkSignature($)
{
    my $Path = $_[0];
    my $Info = `rpm --checksig $Path`;
    if($Info!~/ OK(\s|\Z)/) {
        $BrokenSignature{getPName($Path)}=1;
        return 0;
    }
    return 1;
}

sub checkRoot()
{
    if(not -w "/usr") {
        print STDERR "ERROR: you should be root\n";
        exit(1);
    }
}

sub readRPMlist($$)
{
    my ($Path, $Type) = @_;
    if(not -f $Path)
    {
        print "ERROR: cannot access \'$Path\'\n";
        exit(1);
    }
    my @RPMs = split(/\s+/, readFile($Path));
    if($#RPMs==-1) {
        print STDERR "ERROR: the list of packages is empty\n";
        exit(1);
    }
    if($Type eq "RPMs")
    {
        foreach my $P (@RPMs)
        {
            if($P!~/\.rpm\Z/)
            {
                print STDERR "ERROR: file \'$P\' is not RPM package\n";
                exit(1);
            }
            elsif(not -f $P)
            {
                print STDERR "ERROR: cannot access \'$P\'\n";
                exit(1);
            }
        }
    }
    return @RPMs;
}

sub checkRelease()
{
    checkRoot();
    if(not $RPMdir and not $RPMlist)
    {
        print "ERROR: --dir or --list option should be specified\n";
        exit(1);
    }
    clearCache();
    my @RPMs = ();
    if($RPMlist)
    {
        @RPMs = readRPMlist($RPMlist, "RPMs");
        $RPMdir = get_dirname($RPMs[0]);
        if(not $RPMdir) {
            $RPMdir = ".";
        }
    }
    else
    {
        if(not -d $RPMdir)
        {
            print "ERROR: cannot access \'$RPMdir\'\n";
            exit(1);
        }
        @RPMs = searchRPMs($RPMdir);
    }
    addMedia($RPMdir);
    foreach my $Path (@RPMs)
    { # add to cache
        if(not -f $RPM_CACHE."/".get_filename($Path)) {
            # copy($Path, $RPM_CACHE);
        }
    }
    foreach my $Path (@RPMs)
    {
        installPackage($Path);
        $Packages{get_filename($Path)} = 1;
    }
    removeMedia();
    checkResult();
}

sub dynamicCheck()
{
    checkRoot();
    if(not $RPMdir and not $RPMlist)
    {
        print "ERROR: --dir or --list option should be specified\n";
        exit(1);
    }
    clearCache();
    my @RPMs = ();
    if($RPMdir)
    { # --dir option
        if(not -d $RPMdir)
        {
            print "ERROR: cannot access \'$RPMdir\'\n";
            exit(1);
        }
        @RPMs = searchRPMs($RPMdir);
        foreach my $Path (@RPMs)
        { # add to cache
            copy($Path, $RPM_CACHE);
        }
        if(-d $SelectRepos) {
            addMedia($SelectRepos);
        }
        foreach my $Path (@RPMs)
        {
            installPackage($Path);
            $Packages{get_RPMname($Path)} = 1;
            $Packages{get_filename($Path)} = 1;
        }
        if(-d $SelectRepos) {
            removeMedia();
        }
    }
    elsif($RPMlist)
    {
        @RPMs = readRPMlist($RPMlist, "Names");
        if(-d $SelectRepos) {
            addMedia($SelectRepos);
        }
        foreach my $Name (@RPMs)
        {
            installPackage($Name);
            $Packages{$Name} = 1;
        }
        if(-d $SelectRepos) {
            removeMedia();
        }
    }
    checkResult();
}

sub getPName($)
{ # package ID
    my $Path = $_[0];
    if($RPMdir or not -f $Path)
    { # input: RPMs
        return get_filename($Path);
    }
    else
    { # input: RPM names
        return get_RPMname($Path);
    }
}

sub isInstalled($)
{
    my $Name = $_[0];
    if($InstallFailed{$Name}) {
        return 0;
    }
    if(not $CheckRelease) {
        if(not $InstalledPackage{$Name}) {
            return 0;
        }
    }
    return 1;
}

sub checkResult()
{
    my (%ExtraPackages, %BrokenPackages) = ();
    foreach my $Path (searchRPMs($RPM_CACHE))
    { # extra
        my $Name = getPName($Path);
        $InstalledPackage{$Name} = 1;
        if(not $Packages{$Name}) {
            $ExtraPackages{$Name} = $Path;
        }
    }
    foreach my $Name (keys(%Packages))
    { # broken
        if(not isInstalled($Name)) {
            $BrokenPackages{$Name}=1;
        }
    }
    if(my @Names = sort {lc($a) cmp lc($b)} keys(%ExtraPackages))
    {
        my $Report = "Extra Packages:\n\n";
        foreach my $Name (@Names)
        {
            $Report .= $Name;
            if(my $Req = $RequiredBy{$Name}) {
                $Report .= " (required by: $Req)";
            }
            $Report .= "\n";
        }
        print $Report;
        writeFile("$RESULTS_DIR/extra-packages.txt", $Report);
    }
    if(my @Names = sort {lc($a) cmp lc($b)} keys(%BrokenPackages))
    {
        my $Report = "Broken Packages:\n\n";
        foreach my $Name (@Names) {
            $Report .= "$Name\n";
        }
        print $Report;
        writeFile("$RESULTS_DIR/broken-packages.txt", $Report);
    }
    print "Report has been generated to:\n  $RESULTS_DIR/extra-packages.txt\n  $RESULTS_DIR/broken-packages.txt\n";
    if(keys(%ExtraPackages) or keys(%BrokenPackages))
    {
        exit($EXIT_CODES{"FAILED"});
    }
    else {
        exit($EXIT_CODES{"SUCCESS"});
    }
}

sub sigCheck()
{
    if(not $RPMdir and not $RPMlist)
    {
        print "ERROR: --dir or --list option should be specified\n";
        exit(1);
    }
    print "Checking RPMs ...\n";
    my @RPMs = ();
    if($RPMdir)
    {
        if(not -d $RPMdir)
        {
            print "ERROR: cannot access \'$RPMdir\'\n";
            exit(1);
        }
        @RPMs = searchRPMs($RPMdir);
    }
    elsif($RPMlist) {
        @RPMs = readRPMlist($RPMlist, "RPMs");
    }
    foreach my $Path (@RPMs)
    {
        print "Checking ".get_filename($Path)."\n";
        if(not checkSignature($Path)) {
            print "  FAILED: invalid signature\n";
        }
    }
    if(my @Names = sort {lc($a) cmp lc($b)} keys(%BrokenSignature))
    {
        my $Report = "Broken Signature:\n\n";
        foreach my $Name (@Names) {
            $Report .= "$Name\n";
        }
        print $Report;
        writeFile("$RESULTS_DIR/report.txt", $Report);
    }
    print "Report has been generated to:\n  $RESULTS_DIR/report.txt\n";
    if(keys(%BrokenSignature)) {
        exit($EXIT_CODES{"FAILED"});
    }
    else {
        exit($EXIT_CODES{"SUCCESS"});
    }
}

sub readLineNum($$)
{
    my ($Path, $Num) = @_;
    return "" if(not $Path or not -f $Path);
    open (FILE, $Path);
    foreach (1 ... $Num) {
        <FILE>;
    }
    my $Line = <FILE>;
    close(FILE);
    return $Line;
}

sub cmd_find($$$$)
{
    my ($Path, $Type, $Name, $MaxDepth) = @_;
    return () if(not $Path or not -e $Path);
    my $Cmd = "find \"$Path\"";
    if($MaxDepth) {
        $Cmd .= " -maxdepth $MaxDepth";
    }
    if($Type) {
        $Cmd .= " -type $Type";
    }
    if($Name) {
        if($Name=~/\]/) {
            $Cmd .= " -regex \"$Name\"";
        }
        else {
            $Cmd .= " -name \"$Name\"";
        }
    }
    return split(/\n/, `$Cmd`);
}

sub staticCheck()
{
    if(not $RPMdir and not $HDlist and not $RPMlist)
    {
        print "ERROR: --hdlist, --dir or --list option should be specified\n";
        exit(1);
    }
    my (%Dep, %RPMdep) = ();
    if($RPMdir or $RPMlist)
    {
        print "Checking RPMs ...\n";
        my @RPMs = ();
        if($RPMdir)
        {
            if(not -d $RPMdir)
            {
                print "ERROR: cannot access \'$RPMdir\'\n";
                exit(1);
            }
            @RPMs = searchRPMs($RPMdir);
        }
        elsif($RPMlist) {
            @RPMs = readRPMlist($RPMlist, "RPMs");
        }
        foreach my $Path (@RPMs)
        {
            my $Name = get_filename($Path);
            foreach my $Type ("provides", "suggests", "requires")
            {
                foreach my $D (split("\n", `rpm -qp -$Type $Path`))
                {
                    my ($N, $O, $V) = sepDep($D);
                    $Dep{$Type}{$N}{$O}{$V}=$Name;
                    $RPMdep{$Type}{$Name}{$N}=1;
                }
            }
        }
    }
    elsif($HDlist)
    {
        my $Content = readFile($HDlist);
        if($HDlist=~/(http|https|ftp):\/\//)
        {
            print "Downloading HDlist ...\n";
            my $DownloadTo = $TMP_DIR."/extract/".get_filename($HDlist);
            $DownloadTo=~s/\.cz/\.gz/g; # cz == gz
            my $Dir = get_dirname($DownloadTo);
            mkdir($Dir);
            system("wget -U '' --no-check-certificate \"$HDlist\" --connect-timeout=5 --tries=1 --output-document=\"$DownloadTo\" >/dev/null 2>&1");
            if(not -f $DownloadTo
            or not -s $DownloadTo) {
                print "ERROR: cannot access \'$HDlist\'\n";
                exit(1);
            }
            
            my %Extract = (
                "xz"=>"unxz",
                "lzma"=>"unlzma",
                "gz"=>"gunzip"
            );
            if($DownloadTo=~/\.(gz|xz|lzma)\Z/)
            {
                my ($Format, $Cmd) = ($1, $Extract{$1});
                if($Cmd) {
                    system("cd $Dir && $Cmd $DownloadTo");
                }
                my @Files = cmd_find($Dir, "f", "", "");
                if(not @Files) {
                    print "ERROR: cannot extract \'$HDlist\'\n";
                    exit(1);
                }
                $DownloadTo = $Files[0];
            }
            if(my $Line = readLineNum($DownloadTo, 1))
            {
                if($Line!~/\A\@\w+\@/) {
                    print "ERROR: unknown format of hdlist\n";
                    exit(1);
                }
            }
            $Content = readFile($DownloadTo);
        }
        else
        {
            if(not -f $HDlist)
            {
                print "ERROR: cannot access \'$HDlist\'\n";
                exit(1);
            }
            $Content = readFile($HDlist);
        }
        print "Checking HDlist ...\n";
        my $Name = "";
        foreach (reverse(split(/\n/, $Content)))
        {
            $_=~s/\A\@//g;
            my @Parts = split("\@", $_);
            my $Type = $Parts[0];
            if($Type eq "info") {
                $Name = $Parts[1];
            }
            elsif($Type=~/\A(requires|provides|suggests)\Z/)
            {
                foreach my $D (@Parts)
                {
                    my ($N, $O, $V) = sepDep($D);
                    $N=~s/\[\*\]//g;# /sbin/ldconfig[*]
                    $Dep{$Type}{$N}{$O}{$V}=$Name;
                    $RPMdep{$Type}{$Name}{$D} = 1;
                }
            }
        }
    }
    my %IgnoreDeps = ();
    if($FileDeps)
    {
        if(not -f $FileDeps)
        {
            print "ERROR: cannot access \'$FileDeps\'\n";
            exit(1);
        }
        %IgnoreDeps = map {$_=>1} split(/\s+/, readFile($FileDeps));
    }
    my (%Unresolved, %UnresolvedSuggested, %Broken) = ();
    foreach my $N (sort {lc($a) cmp lc($b)} keys(%{$Dep{"requires"}}))
    {
        foreach my $O (keys(%{$Dep{"requires"}{$N}}))
        {
            foreach my $V (keys(%{$Dep{"requires"}{$N}{$O}}))
            {
                if(not defined $Dep{"provides"}{$N}
                or not checkDeps($N, $O, $V, $Dep{"provides"}{$N}))
                { # unresolved
                    if($N=~/\Arpmlib\(\w+\)\Z/)
                    { # rpmlib(PayloadIsLzma), rpmlib(VersionedDependencies), ...
                        next;
                    }
                    if($IgnoreDeps{$N}) {
                        next;
                    }
                    my $Name = $Dep{"requires"}{$N}{$O}{$V};
                    if($RPMdep{"suggests"}{$Name}{$N}) {
                        $UnresolvedSuggested{$N}{$O}{$V} = $Name;
                    }
                    else {
                        $Unresolved{$N}{$O}{$V} = $Name;
                    }
                    $Broken{$Name}=1;
                }
            }
        }
    }
    my $Report = "";
    if(my @Ns = sort {lc($a) cmp lc($b)} keys(%Unresolved))
    {
        $Report .= "\nUnresolved \"Required\" Dependencies (".($#Ns+1)."):\n\n";
        foreach my $N (@Ns)
        {
            foreach my $O (keys(%{$Unresolved{$N}}))
            {
                foreach my $V (keys(%{$Unresolved{$N}{$O}}))
                {
                    $Report .= showDep($N, $O, $V)." (required by ".$Unresolved{$N}{$O}{$V}.")\n";
                }
            }
        }
    }
    if(my @Ns = sort {lc($a) cmp lc($b)} keys(%UnresolvedSuggested))
    {
        if($Report) {
            $Report .= "\n";
        }
        $Report .= "\nUnresolved \"Suggested\" Dependencies (".($#Ns+1)."):\n\n";
        foreach my $N (@Ns)
        {
            foreach my $O (keys(%{$UnresolvedSuggested{$N}}))
            {
                foreach my $V (keys(%{$UnresolvedSuggested{$N}{$O}}))
                {
                    $Report .= showDep($N, $O, $V)." (required by ".$UnresolvedSuggested{$N}{$O}{$V}.")\n";
                }
            }
        }
    }
    if(my @Ns = sort {lc($a) cmp lc($b)} keys(%Broken))
    {
        $Report .= "\nBroken Packages (".($#Ns+1)."):\n\n";
        foreach my $N (@Ns)
        {
            $Report .= getPackageName($N)."\n";
        }
    }
    if($Report)
    {
        print $Report."\n";
        writeFile("$RESULTS_DIR/report.txt", $Report);
    }
    writeFile("$RESULTS_DIR/debug/rpm-provides.txt", Dumper(\%{$RPMdep{"provides"}}));
    writeFile("$RESULTS_DIR/debug/rpm-requires.txt", Dumper(\%{$RPMdep{"requires"}}));
    writeFile("$RESULTS_DIR/debug/rpm-suggests.txt", Dumper(\%{$RPMdep{"suggests"}}));
    print "Report has been generated to:\n  $RESULTS_DIR/report.txt\n";
    if(keys(%Unresolved)) {
        exit($EXIT_CODES{"FAILED"});
    }
    else {
        exit($EXIT_CODES{"SUCCESS"});
    }
}

sub getPackageName($)
{
    my $Name = $_[0];
    if($Name=~/\d(mdv|mdk)\d+/) {
        $Name=~s/\-[^\-]+\Z//;
        $Name=~s/\-[^\-]+\Z//;
    }
    else {
        $Name=~s/\-[^\-]+\Z//;
        $Name=~s/\-[^\-]+\Z//;
        $Name=~s/\-[^\-]+\Z//;
    }
    return $Name;
}

sub clearCache()
{
    if(not $NoClean)
    {
        rmtree($RPM_CACHE);
        mkpath($RPM_CACHE);
    }
}

sub scenario()
{
    if($Help)
    {
        HELP_MESSAGE();
        exit(0);
    }
    if($ShowVersion)
    {
        print "URPM Repos Closure Checker $TOOL_VERSION for Mandriva Linux\nCopyright (C) 2012 ROSA Laboratory\nLicense: GPL <http://www.gnu.org/licenses/>\nThis program is free software: you can redistribute it and/or modify it.\n\nWritten by Andrey Ponomarenko.\n";
        exit(0);
    }
    if($HDlist) {
        $StaticMode = 1;
    }
    if($Root)
    {
        if(not -d $Root) {
            print STDERR "ERROR: cannot access \'$Root\'\n";
            exit(1);
        }
    }
    if($ResDir) {
        $RESULTS_DIR = $ResDir;
    }
    if(-d $RESULTS_DIR)
    {
        # print "Removing old $RESULTS_DIR directory\n";
        rmtree($RESULTS_DIR);
    }
    if($CheckSignature)
    {
        if(not $ResDir) {
            $RESULTS_DIR .= "/signature";
        }
        sigCheck();
        exit(0);
    }
    if($StaticMode)
    {
        if(not $ResDir) {
            $RESULTS_DIR .= "/static";
        }
        staticCheck();
    }
    if($CheckRelease)
    {
        if(not $ResDir) {
            $RESULTS_DIR .= "/release";
        }
        checkRelease();
        exit(0);
    }
    if($DynamicMode)
    {
        if(not $ResDir) {
            $RESULTS_DIR .= "/dynamic";
        }
        dynamicCheck();
    }
    exit(0);
}

scenario();

