#!/usr/bin/perl -w

# Check function/variable classification of  LSB required interfaces 
# against libraries installed on system
#
# An error message will be displayed if the an interface has been
# defined in the LSB database as a function but is a variable on the
# system (or vice-versa)
#
# Also now check the size of data symbols
#
# (C) Copyright 2001 The Free Standards Group  Inc
#
# Chris Yeoh (cyeoh@samba.org), IBM
#
# This is $Revision: 1.1.1.1 $
# 
# $Log: check_lsb_symbols,v $
# Revision 1.1.1.1  2006/03/12 16:20:38  anderson
# LSB development tools
#
# Revision 1.12  2002/04/11 08:19:23  cyeoh
# Improves version checking to take into account aliases
#
# Revision 1.11  2002/04/11 06:58:38  cyeoh
# check versions of functions too. Adds output suitable
# for override_versions.txt file
#
# Revision 1.10  2002/04/09 09:13:50  cyeoh
# clarify warning message
#
# Revision 1.9  2002/04/09 06:32:27  cyeoh
# Add check for matching versions for interfaces between libraries and db
#
# Revision 1.8  2002/04/09 04:46:24  cyeoh
# skip version of symbols which are not linked against except when
# called by version
#
# Revision 1.7  2002/04/08 06:48:51  cyeoh
# DB query changes to handle db changes
# Handles variables with undefined (not just 0) size in db
#
# Revision 1.6  2002/04/08 05:51:15  cyeoh
# take into account arch specific dependencies of objdump output
#
# Revision 1.5  2002/04/08 04:52:57  cyeoh
# Fixes script for library table split
#
#

use strict;
use DBI;
use Getopt::Std;

use Env qw(LSBUSER LSBDBPASSWD LSBDB LSBDBHOST);

my($DBName) = $LSBDB;
my($DBUser) = $LSBUSER;
my($DBPass)  = $LSBDBPASSWD;
my($DBHost) = $LSBDBHOST;
my($TargetArch);
my($TargetArchId);
my(%Options);
my(%MissingData);

my(%ArchObjOffsets) = 
(
 "IA32" => { "OBJLINE" => 46, "SIZE_START" => 24, "SIZE_LENGTH" => 8,
           "VERSION_START" => 33},
 "IA64" => { "OBJLINE" => 62, "SIZE_START" => 32, "SIZE_LENGTH" => 16,
           "VERSION_START" => 49}
 );


#----------------------------------------------------------------------
# Look through local shared libraries for aliases
# We only do this because the database does not (yet) have this
# information.
sub GenerateAliases($)
{
  my($libfilename) = shift;
  my($aliases) = {};
  my(@searchDirs) = ("/", "/lib", "/usr/lib", "/usr/X11R6/lib");
  my($loop);
  my($useDir);
  my(%lookup);
  my(@nmLine);
  
  local(*NMOUTPUT);

  foreach $loop (@searchDirs)
  {
    if (-f "$loop/$libfilename")
    {
      $useDir = $loop;
      last;
    }
  }
  if (!defined($useDir))
  {
    die "Could not find $libfilename in search path (for aliases): @searchDirs\n";
  }
  
  if (system("nm -D $useDir/$libfilename | sort > /tmp/mkstublibs.$$")
      /256!=0)
  {
    die "nm of shared library failed\n";
  }
  
  my($line);
  if (open(NMOUTPUT, "/tmp/mkstublibs.$$"))
  {
    while (defined($line = <NMOUTPUT>))
    {
      chomp($line);
      @nmLine = split(/ /, $line);
      if ($#nmLine==2)
      {
        if ($nmLine[1] eq "D" || $nmLine[1] eq "T")
        {
          $lookup{$nmLine[0]} = $nmLine[2];
        }
      }
    }
    seek(NMOUTPUT, 0, 0);
    while (defined($line = <NMOUTPUT>))
    {
      chomp($line);
      @nmLine = split(/ /, $line);
      if ($#nmLine==2)
      {
        if ($nmLine[1] eq "V" || $nmLine[1] eq "W")
        {
          if (exists($lookup{$nmLine[0]}))
          {
#            print "Found alias: $nmLine[2] is alias to $lookup{$nmLine[0]}\n";
            $aliases->{$nmLine[2]} = $lookup{$nmLine[0]};
          }
          elsif ($nmLine[1] eq "W")
          {
            $aliases->{$nmLine[2]} = "__lsb_cy_dummy_function";
          }
        }
      }
    }
  }
  else
  {
    die "Failed to open temorary file: /tmp/mkstublibs.$$\n";
  }

  return $aliases;
}

#----------------------------------------------------------------------
#
sub LoadSymbolData($)
{
  my($libfilename) = shift;
  my($symbolInfo) = {};
  my(@searchDirs) = ("/", "/lib", "/usr/lib", "/usr/X11R6/lib");
  my($loop);
  my($useDir);
  my(%lookup);
  my(@nmLine);
  my($tmpfile) = "/tmp/$0.$$";
  my($tmpfile2) = "/tmp/$0.$$.1";
  local(*NMOUTPUT);
  local(*OBJDUMPOUT);

  foreach $loop (@searchDirs)
  {
    if (-f "$loop/$libfilename")
    {
      $useDir = $loop;
      last;
    }
  }
  if (!defined($useDir))
  {
    die "Could not find $libfilename in search path : @searchDirs\n";
  }
  
  if (system("nm -D $useDir/$libfilename | sort > $tmpfile")
      /256!=0)
  {
    die "nm of shared library failed\n";
  }
  
  if (system("objdump -T $useDir/$libfilename | egrep 'data|bss|text' | grep -v '(' > $tmpfile2")
      /256!=0)
  {
    die "objdump of shared library failed\n";
  }
  
  my($line);
  if (open(NMOUTPUT, "$tmpfile"))
  {
    while (defined($line = <NMOUTPUT>))
    {
      chomp($line);
      @nmLine = split(/ /, $line);
      if ($#nmLine==2)
      {
        if ($nmLine[1] eq "B" || $nmLine[1] eq "C" || 
            $nmLine[1] eq "D" || $nmLine[1] eq "G" ||
            $nmLine[1] eq "R" || $nmLine[1] eq "S")
        {
          if (defined ($symbolInfo->{$nmLine[2]}) 
              && $symbolInfo->{$nmLine[2]}{TYPE} ne "DATA")
          {
            print "redefining $nmLine[2] ($symbolInfo->{$nmLine[2]}{TYPE} to DATA)\n";
          }
          $symbolInfo->{$nmLine[2]}{TYPE} = "DATA";
        }
        elsif ($nmLine[1] eq "T")
        {
          if (defined ($symbolInfo->{$nmLine[2]})
              && $symbolInfo->{$nmLine[2]}{TYPE} ne "FUNC")
          {
            print "redefining $nmLine[2] ($symbolInfo->{$nmLine[2]}{TYPE} to FUNC)\n";
          }
          $symbolInfo->{$nmLine[2]}{TYPE} = "FUNC";
        }
#          elsif ($nmLine[1] eq "W" || $nmLine[1] eq "V")
#          {
#            $symbolInfo->{$nmLine[2]} = "ANY";
#          }
        else
        {
#          print STDERR "Ignoring unknown symbol type $nmLine[1] ($nmLine[2])\n";
        }
      }
    }
    close(NMOUTPUT);
    unlink($tmpfile);
  }
  else
  {
    die "Failed to open temorary file: $tmpfile\n";
  }

  die "Could not open temporary file: $tmpfile2\n"
      unless open(OBJDUMPOUT, $tmpfile2);
  my($name);
  my($size);
  my($version);
  while (defined($line = <OBJDUMPOUT>))
  {
#    print "$line";
    chomp($line);
    $line =~ s/\t/ /;
    $line =~ s/\.rodata /\.rodata/;
    $line =~ s/\.bss /\.bss   /;
    $line =~ s/\.sbss /\.sbss  /;
#    print "$line\n";
#    print length($line) . " " . $ArchObjOffsets{$TargetArch}{OBJLINE} . "\n";
    if (length($line)>$ArchObjOffsets{$TargetArch}{OBJLINE})
    {
      $name = substr($line, $ArchObjOffsets{$TargetArch}{OBJLINE}-1);
      $name =~ s/^\s+//;
      $name =~ s/\s+$//;
      $size = substr($line, $ArchObjOffsets{$TargetArch}{SIZE_START},
                     $ArchObjOffsets{$TargetArch}{SIZE_LENGTH});
      $size =~ s/^\s+//;
      $size =~ s/\s+$//;

      $version = substr($line, $ArchObjOffsets{$TargetArch}{VERSION_START},
                        $ArchObjOffsets{$TargetArch}{OBJLINE} -
                        $ArchObjOffsets{$TargetArch}{VERSION_START} -1);
      $version =~ s/^\s+//;
      $version =~ s/\s+$//;

#      print "got info for: $name " . $size . "($version)\n";
      $symbolInfo->{$name}{SIZE} = $size;
      $symbolInfo->{$name}{VERSION} = $version;
    }
  }
  close(OBJDUMPOUT);
#  unlink($tmpfile2);

  return $symbolInfo;
}


######################################################################
# Generate C stub code for a library
sub CheckSymbols($$$$$)
{
  my($dbh) = shift;
  my($libName) = shift;
  my($libId) = shift;
  my($symbolInfo) = shift;
  my($aliases) = shift;
  my($sth);
  my($row);
  my(%versionInfo);
  my($iface);
  my($viface);
  # Do functions first
  $sth = $dbh->prepare("SELECT DISTINCT Iname,Vname FROM Interface,LibGroup, ".
                       "LGInt LEFT JOIN Version ON Vid=Iversion ".
                       "WHERE Itype='Function' AND ".
                       "Interface.Iid=LGInt.LGIint AND Istatus='Included'".
                       "AND LGInt.LGIlibg=LibGroup.LGid ".
                       "AND LibGroup.LGlib=$libId ORDER BY Iname")
      || die $dbh->errstr;  
  $sth->execute() || die $sth->errstr;
  while ($row = $sth->fetchrow_hashref())
  {
    $iface = $row->{Iname};
    if (exists($aliases->{$iface}))
    {
      $iface = $aliases->{$iface};
      
    }
    if ($iface eq "__lsb_cy_dummy_function")
    {
      # dummy function as its just a weak function. its ok but we still
      # check the version is correct
      $viface = $row->{Iname};

      if (defined($symbolInfo->{$viface}{VERSION}))
      {
        if (!defined($row->{Vname}))
        {
          if ($symbolInfo->{$viface}{VERSION} ne "Base")
          {
            print "For $viface library has version "
                . $symbolInfo->{$viface}{VERSION} 
                . " but not defined in db\n";
            print "xxx $libName $viface $symbolInfo->{$viface}{VERSION}\n";
          }
        }
        elsif ($symbolInfo->{$viface}{VERSION} ne $row->{Vname})
        {
          print "For $viface library has version $symbolInfo->{$viface}{VERSION}"
              . " but defined as $row->{Vname} in db\n";
          print "xxx $libName $viface $symbolInfo->{$viface}{VERSION}\n";
        }
      }
      else
      {
        if (defined($row->{Vname}))
        {
          print "For $viface db has version $row->{Vname}"
              . " but not defined in library\n";
        }
      }

      next;
    }

#    print "$row->{Iname}\n";
#    print "$symbolInfo->{$row->{Iname}}\n";

    if (exists($symbolInfo->{$iface}))
    {
      if ($symbolInfo->{$iface}{TYPE} ne "FUNC" 
          && $symbolInfo->{$iface}{TYPE} ne "ANY")
      {
        print "$row->{Iname} is $symbolInfo->{$iface}{TYPE} but recorded as function\n";
      }

      # Version check
      # Check version of alias not the thing it points to
      $viface = $row->{Iname};
      if (defined($symbolInfo->{$viface}{VERSION}))
      {
        if (!defined($row->{Vname}))
        {
          if ($symbolInfo->{$viface}{VERSION} ne "Base")
          {
            print "For $viface library has version "
                . $symbolInfo->{$viface}{VERSION} 
                . " but not defined in db\n";
            print "xxx $libName $viface $symbolInfo->{$viface}{VERSION}\n";
          }
        }
        elsif ($symbolInfo->{$viface}{VERSION} ne $row->{Vname})
        {
          print "For $viface library has version $symbolInfo->{$viface}{VERSION}"
              . " but defined as $row->{Vname} in db\n";
          print "xxx $libName $viface $symbolInfo->{$viface}{VERSION}\n";
        }
      }
      else
      {
        if (defined($row->{Vname}))
        {
          print "For $viface db has version $row->{Vname}"
              . " but not defined in library\n";
        }
      }

    }
    else
    {
      print "No info on symbol $iface\n";
    }
  }

  # Do variables next
  $sth = $dbh->prepare("SELECT DISTINCT Iname,Vname,Ireturn,ATsize FROM ".
                       "Interface,LibGroup,LGInt ".
                       "LEFT JOIN Version ON Vid=Iversion ".
                       "LEFT JOIN ArchType ON ATtid=Ireturn and ".
                       "ATaid=$TargetArchId WHERE ".
                       "( Itype='Data' OR Itype='Alias' OR Itype='Common' ) " .
                       "AND Interface.Iid=LGInt.LGIint ".
                       "AND Istatus='Included' ".
                       "AND LGInt.LGIlibg=LibGroup.LGid ".
                       "AND LibGroup.LGlib=$libId ORDER BY Iname")
      || die $dbh->errstr;  
  $sth->execute() || die $sth->errstr;
  my($size);
  while ($row = $sth->fetchrow_hashref())
  {
    $iface = $row->{Iname};
#    print "checking interface: $iface\n";
    if (exists($aliases->{$iface}))
    {
      $iface = $aliases->{$iface};
    }
    if ($iface eq "__lsb_cy_dummy_function")
    {
      print "$row->{Iname} missing target weak in spec (also maybe its meant to be a function?\n";
      next;
    }
    if (exists($symbolInfo->{$iface}))
    {
      if ($symbolInfo->{$iface}{TYPE} ne "DATA"
          && $symbolInfo->{$iface}{TYPE} ne "ANY")
      {
        print "$iface is $symbolInfo->{$iface} but recorded data\n";
      }
      if (!defined($symbolInfo->{$iface}{SIZE}))
      {
        print "no size for $iface\n";
        next;
      }
      
      if (!defined($row->{ATsize}))
      {
        print "Size for $iface is not in database but in "
            . "the library as $symbolInfo->{$iface}{SIZE}\n";
      }
      elsif (hex($symbolInfo->{$iface}{SIZE}) != $row->{ATsize})
      {
        print "Size mismatch for $iface. Is $symbolInfo->{$iface}{SIZE} but in db as $row->{ATsize}\n";
      }

      # Version check
      $viface = $row->{Iname};
      if (defined($symbolInfo->{$viface}{VERSION}))
      {
        if (!defined($row->{Vname}))
        {
          if ($symbolInfo->{$viface}{VERSION} ne "Base")
          {
            print "For $viface library has version "
                . $symbolInfo->{$viface}{VERSION} 
                . " but not defined in db\n";
            print "xxx $libName $viface $symbolInfo->{$viface}{VERSION}\n";
          }
        }
        elsif ($symbolInfo->{$viface}{VERSION} ne $row->{Vname})
        {
          print "For $viface library has version $symbolInfo->{$viface}{VERSION}"
              . " but defined as $row->{Vname} in db\n";
          print "xxx $libName $viface $symbolInfo->{$viface}{VERSION}\n";
        }
      }
      else
      {
        if (defined($row->{Vname}))
        {
          print "For $viface db has version $row->{Vname}"
              . " but not defined in library\n";
        }
      }
    }
    else
    {
      print "No info on symbol $iface\n";
    }
  }
}


######################################################################
# Main bit
getopts('d:u:p:o:ha:', \%Options);

if (exists($Options{'h'}))
{
  print STDERR <<"EOM"
Usage $0 -a arch [-d db_name] [-u username] [-p password] [-o hostname] [-h]
    -h           Display this help
    -d db_name   Database name
    -u username  Name of user for db access
    -p password  Password for db access
    -o hostname  Hostname for DB
    -a arch      Architecture to generate shared libraries for
                 (Note this is not magic - you have to compile it on
                  the correct platform still!)
EOM
    ;
  exit(1);
}

if (defined($Options{'a'}))
{
  $TargetArch = $Options{'a'};
  if (!defined($ArchObjOffsets{$TargetArch}))
  {
    die "Need to define arch objdump output offsets in script\n";
  }
}
else
{
  die "Must define target architecture\n";
}

$DBUser = $Options{'u'} if exists($Options{'u'});
$DBPass = $Options{'p'} if exists($Options{'p'});
$DBHost = $Options{'o'} if exists($Options{'o'});
$DBName = $Options{'d'} if exists($Options{'d'});

my($dbh);
my($sth);
my($row);
my($data_source) = "DBI:mysql:database=$DBName";
if ($DBHost ne "")
{
  $data_source .= ";host=$DBHost";
}

$dbh = DBI->connect($data_source, $DBUser, $DBPass)
    || die "Could not connect to database\n";


# Get architecture information
$sth = $dbh->prepare("SELECT Aid from Architecture where Aname='$TargetArch'")
    || die $dbh->errstr;
$sth->execute() || die $sth->errstr;
$row = $sth->fetchrow_hashref;
if (!defined($row))
{
  die "Unknown Architecture $TargetArch\n";
}
else
{
  $TargetArchId = $row->{Aid};
}

# Get 'ALL' arch information
my($AllArch);
$sth = $dbh->prepare("SELECT Aid from Architecture where Aname='All'")
    || die $dbh->errstr;
$sth->execute() || die $sth->errstr;
$row = $sth->fetchrow_hashref;
if (!defined($row))
{
  die "Cannot find 'all' architecture\n";
}
else
{
  $AllArch = $row->{Aid};
}

# Get list of libraries we want to produce stub libraries for
$sth = $dbh->prepare("SELECT Lid,Lname,ALrunname FROM Library "
                     . "LEFT JOIN ArchLib ON Lid=ALlid AND "
                     . "(ALAid=$TargetArchId OR ALAid=$AllArch) "
                     . "where Library.Lstd='Yes' and ALrunname!=''")
    || die $dbh->errstr;
$sth->execute() || die $sth->errstr;

my($symInfo);
my($aliases);
while ($row = $sth->fetchrow_hashref)
{
  print "Checking library ($row->{ALrunname})\n";
  $symInfo = LoadSymbolData($row->{ALrunname});
  $aliases = GenerateAliases($row->{ALrunname});
  CheckSymbols($dbh, $row->{Lname}, $row->{Lid}, $symInfo, $aliases);
}


