package pkg_ver;

require Exporter;

@ISA = qw(Exporter);
@EXPORT_OK = qw();

use strict;
use PackageTool;

#
# find_pkg_of_hard_links
#
sub find_pkg_of_hard_links($$$)
{
  my $pkg_func = shift;
  my $hard_link = shift;
  my $packages = shift;

  return "No corresponding hard links, I give up."
  unless defined($hard_link);

  print "Searching packages owning hard links ...\n";
  my $msg = &$pkg_func($hard_link, $packages);
  return $msg if (defined($msg));

  my $found = 0;
  while (my ($key, $value) = each(%$hard_link))
  {
    next if ($value == 1);
    printf "Hard link found: /%s\n", $key;
    $found = 1;
  }
  return undef;
}


#
# pkg_init_rpm
#
sub pkg_init_rpm()
{
  PackageTool::add_which(1, 'rpm');
  return undef;
}

#
# pkg_store_rpm
#
sub pkg_store_rpm
{
  my $line = shift;
  my $r_packages = shift;
  my $filename = shift;

  for $_(@$line)
  {
    m#^([^ ]+) ([^ ]+)# || return "Output of rpm -qf is invalid: $_";
    $$r_packages{$1} = $2;
  }
  return undef;
}

#
# pkg_check_rpm
#
sub pkg_check_rpm
{
  my $line = shift;
  my $r_used_files = shift;
  my $filename = shift;

  for $_(@$line)
  {
    my $file = substr($_, 1); # remove leading slash
    if (defined($$r_used_files{$file})) { $$r_used_files{$file}++; }
  }
  return undef;
}

#
# pkg_ver_rpm
#
# trivial single-pass 
#
sub pkg_ver_rpm($$)
{
  my $r_used_files = shift;
  my $r_packages = shift;

  print "Searching packages with rpm -qf ...\n";
  my $cmd =
    'rpm -q --qf \'%{name} %{version}-%{release}\n\' -f /'
    . join(' /', keys(%$r_used_files))
    . ' |';
  my $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_store_rpm', $cmd, $r_packages
  );
  return $msg if (defined($msg));

  # Unfortunately "rpm -qf --qf %{filenames}" does not work reliably,
  # e.g. returns "/usr/include" for /usr/include/elf.h
  # The check whether all used files were found we need a second pass

  print "Cross checking with rpm -ql ...\n";
  $cmd = 'rpm -ql ' . join(' ', keys(%$r_packages)) . ' |';
  $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_check_rpm', $cmd, $r_used_files
  );
  return $msg if (defined($msg));

  return undef;
}

#
# pkg_init_deb
#
sub pkg_init_deb()
{
  PackageTool::add_which(1, 'dpkg');
  PackageTool::add_which(1, 'debsums');
  PackageTool::add_which(1, 'readlink');
  return undef;
}

#
# pkg_store_deb_name
#
# parse output of 'dpkg -S'
#
sub pkg_store_deb_name
{
  my $line = shift;
  my $r_used_files = shift;
  my $r_packages = shift;
  my $filename = shift;

  for $_(@$line)
  {
    m#^(\S+): /(\S+)$# || return "Output of dpkg -S is invalid: $_";
    defined($$r_used_files{$2}) || return "dpkg -S undefined: $2";
    $$r_used_files{$2}++;
    $$r_packages{$1} = 0;
  }
  return undef;
}

#
# pkg_store_deb_ver
#
# read all lines of /var/lib/dpkg/status
#
sub pkg_store_deb_ver
{
  my $line = shift;
  my $r_packages = shift;
  my $filename = shift;

  my $package;
  for $_(@$line)
  {
    if (m/^Package: (.+)/) { $package = $1; }
    elsif (m/^Version: (.+)/ && defined($$r_packages{$package})) 
    {
      $$r_packages{$package} = $1;
    }
  }
  return undef;
}

#
# pkg_ver_deb
#
# less trivial two-pass
#
sub pkg_ver_deb()
{
  my $r_used_files = shift;
  my $r_packages = shift;

  print "Searching packages with dpkg -S ...\n";
  my $cmd = 'dpkg -S /' . join(' /', keys(%$r_used_files)) . ' |';
  my $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_store_deb_name', $cmd, $r_used_files, $r_packages
  );
  return $msg if (defined($msg));

  print "Reading package versions from /var/lib/dpkg/status ...\n";
  $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_store_deb_ver',
    '/var/lib/dpkg/status',
    $r_packages
  );
  return $msg if (defined($msg));

  return undef;
}

#
# pkg_notfound_deb
#
sub pkg_notfound_deb($$$)
{
  my $key = shift;
  my $r_hard_link = shift;
  my $r_packages = shift;

  return find_pkg_of_hard_links(
    \&pkg_ver_deb, $r_hard_link, $r_packages
  );
}

#
# pkg_init_slackware
#
sub pkg_init_slackware() { return undef; }

#
# decode_slackware_pkg
#
sub decode_slackware_pkg($)
{
  my $r_pkg_file = shift;

  my @segments = split(/-/, $$r_pkg_file);
  my $version = 0;
  if ($#segments >= 3)
  {
    $version = $segments[$#segments - 2];
    $$r_pkg_file = join '-', @segments[0 .. ($#segments - 3)];
  }
  return $version;
}

#
# pkg_store_slackware_name
#
# read each file in /var/log/packages/
# decoding of package names according to /sbin/upgradepkg of Slackware 8.1
#
sub pkg_store_slackware_name
{
  my $line = shift;
  my $pkg_file = shift;
  my $r_used_files =shift;
  my $r_packages = shift;
  my $filename = shift;

  my $version = decode_slackware_pkg \$pkg_file;

  # read up to 'FILE LIST:' delimiter
  my $i = 0;
  while($$line[$i++] ne 'FILE LIST:') {}

  # ok, now look whether one of the files is part of $$r_packages
  while($i <= $#$line)
  {
    my $key = $$line[$i++];
    next if (!defined($$r_used_files{$key}));
    $$r_packages{$pkg_file} = $version;
    $$r_used_files{$key}++;
  }
  return undef;
}

#
# pkg_ver_slackware
#
# has no index of packages at all - lot of work
# 
sub pkg_ver_slackware($$)
{ 
  my $r_used_files = shift;
  my $r_packages = shift;

  my $dir = '/var/log/packages/';
  opendir(DIR, $dir) || return "Can't opendir $dir: $!";
  for my $pkg_file(readdir(DIR))
  {
    next if ($pkg_file =~ m/^\./);
    my $msg = Misc::parse_file
      __PACKAGE__ . '::pkg_store_slackware_name',
      $dir . $pkg_file,
      $pkg_file,
      $r_used_files,
      $r_packages;
    return $msg if (defined($msg));
  }
  closedir DIR;
  return undef;
}

#
# pkg_special_slackware
#
sub pkg_special_slackware
{
  my $line = shift;
  my $regexp = shift;
  my $r_used_files =shift;

  my @search = grep { m/$regexp/ } @$line;
  return "/$regexp/ failed (returned $#search)." if ($#search != 0);
  printf "Taking %s for the real thing.\n", $search[0];
  return undef;
}

#
#
#
sub pkg_notfound_slackware($$$)
{
  my $key = shift;
  my $r_hard_link = shift;
  my $r_packages = shift;

  my $r_used_files; # oops. undefined symbol from somewhere

  if ($key eq 'bin/bash')
  {
    # printf "%s is a special case ...\n", $key;
    my @file = glob('/var/log/packages/bash-*');
    for my $file(@file)
    {
      printf "%s is special, searching %s ...\n", $key, $file;
      my $msg = Misc::parse_file
	__PACKAGE__ . '::pkg_special_slackware',
	$file,
	$key . '\\w',
        $r_used_files;
      return $msg if (defined($msg));

      $file =~ s#.*/##;
      my $version = decode_slackware_pkg \$file;
      $$r_packages{$file} = $version;
      return undef; 
    }
  }
  return find_pkg_of_hard_links(
    \&pkg_ver_slackware, $r_hard_link, $r_packages
  );
}

#
# pkg_init_SunOS
#
sub pkg_init_SunOS()
{
  PackageTool::add_which(1, 'pkgchk');
  PackageTool::add_which(1, 'pkginfo');
  PackageTool::add_which(0, 'isainfo');
  PackageTool::add_which(0, 'isalist');
  return undef;
}

#
# pkg_store_SunOS_name
#
sub pkg_store_SunOS_name
{
  my $line = shift;
  my $r_used_files = shift;
  my $r_packages = shift;
  my $filename = shift;

  for $_(@$line)
  {
    if (m#^Pathname: /(.*)$#)
    {
      defined($$r_used_files{$1})
      || return "Output of pkgchk -l -p is invalid: $_";
      $$r_used_files{$1}++;
      next;
    }
    $$r_packages{$1} = 0 if (m/^\t(.+)/);
  }
  return undef;
}

#
# pkg_store_SunOS_ver
#
sub pkg_store_SunOS_ver
{
  my $line = shift;
  my $r_packages = shift;
  my $filename = shift;

  my $pkg;
  for $_(@$line)
  {
    if (m/^([\w\d]+)/) { $pkg = $1; }
    elsif (m/^\s+\([^\)]*\)\s(.+)/) { $$r_packages{$pkg} = $1; }
  }
  return undef;
}

#
# pkg_ver_SunOS
#
# This is two-pass.
#
# "pkgchk -l -p '/usr/bin/ls /usr/bin/sed'" is nicer than directly reading
# /var/sadm/install/contents
#
# "pkginfo -x SUNWcsu" is nicer than directly reading /var/sadm/pkg/SUNWcsu
#
sub pkg_ver_SunOS($$)
{
  my $r_used_files = shift;
  my $r_packages = shift;

  print "Searching packages with pkgchk -l -p ...\n";
  my $cmd = 'pkgchk -l -p \'/' . join(' /', keys(%$r_used_files)) . '\' |';
  my $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_store_SunOS_name', $cmd, $r_used_files, $r_packages
  );
  return $msg if (defined($msg));

  print "Getting package versions with pkginfo -x ...\n";
  $cmd = 'pkginfo -x ' . join(' ', keys(%$r_packages)) . '\' |';
  $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_store_SunOS_ver', $cmd, $r_packages
  );
  return $msg if (defined($msg));

  return undef;
}

#
# pkg_store_FreeBSD
#
sub pkg_store_FreeBSD
{
  my $line = shift;
  my $r_packages = shift;
  my $filename = shift;

  for $_(@$line) { $$r_packages{$_} = '' if ($_ ne '?'); }
  return undef;
}

#
# pkg_init_FreeBSD
#
sub pkg_init_FreeBSD()
{
  PackageTool::add_which(1, 'pkg_info');
  return undef;
}

#
# pkg_ver_FreeBSD
#
sub pkg_ver_FreeBSD($$)
{
  my $r_used_files = shift;
  my $r_packages = shift;

  my $cmd = 'pkg_info -qW /' . join(' -W /', keys(%$r_used_files)) . ' |';
  my $msg = Misc::parse_file(
    __PACKAGE__ . '::pkg_store_FreeBSD', $cmd, $r_packages
  );
  return $msg if (defined($msg));

  return undef;
}

#
# pkg_notfound_FreeBSD
#
sub pkg_notfound_FreeBSD($$$)
{
  return undef;
}

1;
