#!/usr/bin/perl -w

# Tool to generate source code for stub libraries
#
# (C) Copyright 2001 The Free Standards Group  Inc
#
# Chris Yeoh (cyeoh@samba.org), IBM
#
# This is $Revision: 1.1.1.1 $
#
# $Log: mkstublibs,v $
# Revision 1.1.1.1  2006/03/12 16:20:38  anderson
# LSB development tools
#
# Revision 1.28  2003/05/14 15:52:24  mwichmann
# Use .pushsection/.popsection instead for the emitted __asm__ statements
#
# Revision 1.27  2003/05/13 23:12:35  mwichmann
# Work around stricter assembler on some platforms - make sure to
# switch back to text section after using __asm__ to add a data
# section symbol.
#
# Revision 1.26  2003/05/13 23:10:47  mwichmann
# Avoid complaints from stricter gcc 3.3
#
# Revision 1.25  2003/02/19 16:55:22  anderson
# Don't include deprecate interfaces in the stub library
#
# Revision 1.24  2002/11/01 04:01:08  cyeoh
# Update PPC32 stub libraries with db sync and add missing
# variable sizes
#
# Revision 1.23  2002/10/08 19:35:09  anderson
# Further cleanups of the symbol version handling
#
# Revision 1.22  2002/10/08 19:08:42  anderson
# Do a better job with missing symbol version information
#
# Revision 1.21  2002/08/22 00:12:47  anderson
# Add support for Interfaces which are Deprecated
#
# Revision 1.20  2002/06/05 04:39:20  cyeoh
# Adds necessary internal glibc symbol to stub libraries.
#
# Revision 1.19  2002/04/25 13:33:49  anderson
# Changes to work with the new per Architecture symbols versions.
#
# Revision 1.18  2002/04/11 08:20:03  cyeoh
# Adds ability to overide version of functions
#
# Revision 1.17  2002/04/09 09:10:33  cyeoh
# Adds ability to override symbol versions in db
#
# Revision 1.16  2002/04/09 08:42:16  cyeoh
# Remove dead alias code
# No need to check size if variable is an alias
#
# Revision 1.15  2002/04/09 04:47:40  cyeoh
# Moves missing_data.txt file into arch specific directory
# Moves overrides from perl script into missing_data.txt file
# since they are architecture specific
#
# Revision 1.14  2002/04/09 02:21:12  cyeoh
# Removes IA32 stub library code as it is moving to a subdirectory
# Modifies Makefile to generate stub library code for each architecture
# in subdirectories
#
# Revision 1.13  2002/03/25 23:33:19  anderson
# Skip entries that don't have a runname. Used to indicate a placehold in the gLSB
#
# Revision 1.12  2002/03/25 19:37:06  anderson
# Split the Library table into Library and ArchLib, analagous to how other
# tables have been done. This allows the library runname to be architecture
# sensitvie. Scripts and the resulting output C code should now correctly
# reflect the right library names.
#
# Revision 1.11  2001/12/20 02:28:53  cyeoh
# Change logic so warning when db size information is overridden by
# local file. Missing data sizes already exist in missing_data.txt file
#
# Revision 1.10  2001/12/19 21:58:34  anderson
# Opps. Missed on of the Xt things
#
# Revision 1.9  2001/12/19 21:48:00  anderson
# Add overrides for the Xt structures that aren't quite right in the DB
#
# Revision 1.8  2001/12/19 03:34:28  cyeoh
# Add overriding fixes for variables with incorrect data sizes
# in the db
#
# Revision 1.7  2001/12/18 01:04:58  cyeoh
# Remove warning message
#
# Revision 1.6  2001/11/20 04:03:57  anderson
# Change to pick up the DB access into from the environment as is done for
# all of the other scripts
# Eliminate most of the workaround required becasue of bad data.
# Eliminate several of the proceedural heuristics required to work around
# bad data.
# The Aliases are still needed until that info can be reflected in the DB.
# Another pass will be required to finish eliminating workarounds.
#
# Revision 1.5  2001/10/24 05:19:56  cyeoh
# Add workaround for missing symbols
#
# Revision 1.4  2001/10/22 07:28:47  cyeoh
# Add temporary work around for symbols either misclassified or missing
# from the LSB db
#
# Revision 1.3  2001/10/19 07:19:45  cyeoh
# Applies workaround for variables which are in reality just aliases to
# other variables but this information is not stored in the database
# so we use a hardcoded lookup table.
#
# Revision 1.2  2001/09/04 02:27:42  cyeoh
# Load data file containing symbol/size pairs. This is a workaround
# for missing data in the LSB database. It is also IA32 specific.
#
# Revision 1.1  2001/08/27 08:01:08  cyeoh
# Adds core files needed for lsb stub library generation
#
#

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(%OverrideVersions);
my(%LibcAliases) = (
  "sys_errlist" => "_sys_errlist",
  "tzname" => "__tzname",
  "daylight" => "__daylight",
  "timezone" => "__timezone",
  "_environ" => "__environ",
  "environ" => "__environ",
  "___brk_addr" => "__curbrk",
  "program_invocation_name" => "__progname_full",
  "program_invocation_short_name" => "__progname",
  "_h_errno" => "h_errno" );

my($DestinationDirectory) = "";

#----------------------------------------------------------------------
# Load data missing from database for size of variables
# IA32 specific!
sub LoadMissingData()
{
  local(*DATAFILE);
  my($line);
  my($library, $symbol, $size);
  my($missing_file) = "$DestinationDirectory/missing_data.txt";
  if (open(DATAFILE, $missing_file))
  {
    while (defined($line = <DATAFILE>))
    {
      if ($line =~ /^\#/)
      {
        # Comment
        next;
      }
      else
      {
        chomp($line);
        ($library, $symbol, $size) = split(/\s+/, $line);
        if (defined($MissingData{$library}{$symbol}))
        {
          print "Warning: $symbol defined twice in $missing_file\n";
        }
        $MissingData{$library}{$symbol} = hex($size);
      }
    }
    close(DATAFILE);
  }
  else
  {
    print "Could not open $missing_file\n";
  }
}

#----------------------------------------------------------------------
# Load overriding symbol version data
sub LoadOverrideSymbolData()
{
  local(*DATAFILE);
  my($line);
  my($library, $symbol, $version);
  my($missing_file) = "$DestinationDirectory/override_versions.txt";
  if (open(DATAFILE, $missing_file))
  {
    while (defined($line = <DATAFILE>))
    {
      if ($line =~ /^\#/)
      {
        # Comment
        next;
      }
      else
      {
        chomp($line);
        ($library, $symbol, $version) = split(/\s+/, $line);
        $version =~ s/^\s+//;
        $version =~ s/\s+$//;
        if (defined($OverrideVersions{$library}{$symbol}))
        {
          print "Warning: $symbol defined twice in $missing_file\n";
        }
        $OverrideVersions{$library}{$symbol} = $version;
#        print "Got $library, $symbol, $version\n"
      }
    }
    close(DATAFILE);
  }
  else
  {
    print "Could not open $missing_file\n";
  }
}

######################################################################
# Generate C stub code for a library
sub GenerateLibrary($$$)
{
  my($dbh) = shift;
  my($libName) = shift;
  my($libId) = shift;
  my($sth);
  my($select);
  my($row);
  my(%versionInfo);
  local(*STUBFILE);
  local(*VERSIONFILE);
  my($version);
  my($lastint);

  open(STUBFILE, ">$DestinationDirectory/$libName.c")
      || die "Could not open $DestinationDirectory/$libName.c to write\n";
  open(VERSIONFILE, ">$DestinationDirectory/$libName.Version") 
      || die "Could not open $DestinationDirectory/$libName.Version to write\n";
  # Do functions first
  $select = "SELECT DISTINCT Iname,Iarch,AIarch,Vname FROM Interface ".
                       "LEFT JOIN LGInt ON LGIint=Iid ".
                       "LEFT JOIN LibGroup ON LGIlibg=LGid ".
                       "LEFT JOIN ArchInt ON AIint=Iid ".
                       "LEFT JOIN Version ON Vid=AIversion ".
                       "WHERE Itype='Function' ".
		       "AND ( Istatus='Included' ) ".
                       "AND LibGroup.LGlib=$libId ".
                       "AND ( AIarch=$TargetArchId OR Iarch=$TargetArchId ".
                       "OR ( AIarch!=$TargetArchId AND Iarch=1 ) ".
                       "OR ( AIarch IS NULL AND Iarch=1 ) ) ".
                       "ORDER BY Iname";
#  print STDERR $select,"\n";
  $sth = $dbh->prepare($select) || die $dbh->errstr;  
  $sth->execute() || die $sth->errstr;
  $lastint="";
  while ($row = $sth->fetchrow_hashref())
  {
#if($row->{Vname}){print "$row->{Iname}\t$row->{AIarch}\t$row->{Vname}\n";}
    if( $row->{AIarch} && $row->{AIarch}!=1 && $row->{AIarch}!=$TargetArchId  ) { next; }

    #if( !$row->{AIarch} ){print "$row->{Iname}\tNULL AIarch\n";}
    if( !$row->{AIarch} && 
	( $row->{Iarch}!=$TargetArchId && $row->{Iarch}!=1 ) )
		{ print "Skipping bad AIarch for $row->{Iname}\n";next; }
    #if( !$row->{Vname} ){print "$row->{Iname}\tNULL Vname\n";}
    if( !$row->{Vname} && $row->{AIarch}
	&& $row->{AIarch}!=$TargetArchId && $row->{Iarch}!=1
	&& $row->{Iarch} != $row->{AIarch} )
		{ print "Skipping bad Vname for $row->{Iname}\n";next; }

    #if( $row->{Iname} eq $lastint ) { print "skipping dup $lastint\n"; }
    if( $row->{Iname} eq $lastint ) { next; }
#if($row->{Vname}){print "$row->{Iname}\t$row->{AIarch}\t$row->{Vname}\n";}
    $lastint=$row->{Iname};
    print(STUBFILE "void $row->{Iname}() {} ;\n");

    # Add ability to override versions of functions
    undef($version);
    $version = $row->{Vname} unless !defined($row->{Vname});
    if (defined($OverrideVersions{$libName})
        && defined($OverrideVersions{$libName}{$row->{Iname}})
        && $OverrideVersions{$libName}{$row->{Iname}} ne $version)
    {
      print "Overriding $row->{Iname} with symbol version '$OverrideVersions{$libName}{$row->{Iname}}' ";
      if (defined($version))
      {
        print "was '$version'\n";
      }
      else
      {
        print "was unversioned\n";
      }
      $version = $OverrideVersions{$libName}{$row->{Iname}};
    }

    if (defined($version))
    {
      # Unversioned symbols will not have the Vname field defined
      if (!defined($versionInfo{$version}))
      {
        $versionInfo{$version} = ();
      }
      push(@{$versionInfo{$version}}, $row->{Iname});
    }
  }

  # Do variables next
  $select = "SELECT DISTINCT Iname,AIarch,Itype,Vname,Ireturn,ATsize FROM Interface ".
                       "LEFT JOIN LGInt ON LGIint=Iid ".
                       "LEFT JOIN LibGroup ON LGIlibg=LGid ".
                       "LEFT JOIN ArchInt ON AIint=Iid ".
                       "LEFT JOIN Version ON Vid=AIversion ".
                       "LEFT JOIN ArchType ON ATtid=Ireturn and ".
                       "ATaid=$TargetArchId WHERE ".
                       "( Itype='Data' OR Itype='Alias' OR Itype='Common' ) ".
		       "AND ( Istatus='Included' OR Istatus='Deprecated' ) ".
                       "AND ( AIarch=$TargetArchId OR Iarch=$TargetArchId OR IArch=1) ".
                       "AND LibGroup.LGlib=$libId ORDER BY Iname";
# print "$select\n";
  $sth = $dbh->prepare($select) || die $dbh->errstr;  
  $sth->execute() || die $sth->errstr;
  my($size);
  $lastint="";
  while ($row = $sth->fetchrow_hashref())
  {
    if( $row->{AIarch} && $row->{AIarch}!=1 && $row->{AIarch}!=$TargetArchId  ) { next; }
    if( $row->{Iname} eq $lastint ) { print "skipping dup $lastint\n";next; }
    $lastint=$row->{Iname};
    # Work out size of data variable
    $size = 0;
    if (!defined($row->{"ATsize"}) || $row->{"ATsize"} == 0)
    {
      if (defined($MissingData{$libName}) 
          && defined($MissingData{$libName}{$row->{Iname}}))
      {
        print STDOUT "Size not available for symbol $row->{Iname}"
            . "  but overriding with $MissingData{$libName}{$row->{Iname}}\n";
        $size = $MissingData{$libName}{$row->{Iname}};
      }
      else
      {
        # Size is not required for aliases
        if ($row->{Itype} ne "Alias")
        {
          print "No data for symbol $row->{Iname} ($row->{Ireturn})\n";
        }
      }
#      $size = 1000; # temporarily make it big to avoid problems
    }
    else
    {
      $size = $row->{"ATsize"};

      if (defined($MissingData{$libName}) 
          && defined($MissingData{$libName}{$row->{Iname}})
          && $MissingData{$libName}{$row->{Iname}} != $size)
      {
        print STDOUT "Overriding size of $row->{Iname} from $size to "
            . $MissingData{$libName}{$row->{Iname}} . "\n";
        $size = $MissingData{$libName}{$row->{Iname}};
      }
    }

    if($row->{Itype} eq "Common" )
    {
      print(STUBFILE "__asm__(\".comm $row->{Iname},$size\");\n");
    }
    elsif($row->{Itype} eq "Alias" )
    {
      print(STUBFILE "__asm__(\".weak $row->{Iname}; $row->{Iname} = $LibcAliases{$row->{Iname}}\");\n");
    }
    elsif($row->{Itype} eq "Data" )
    {
      my($varname) = $row->{Iname};
      print(STUBFILE "__asm__(\".globl $varname; .pushsection .data; .type $varname,\@object; .size $varname, $size; $varname: .long 0; .popsection\");\n");
    }
    else
    {
      print "Skipping ",$row->{Iname},"\n";
    }

    undef($version);
    $version = $row->{Vname} unless !defined($row->{Vname});
    if (defined($OverrideVersions{$libName})
        && defined($OverrideVersions{$libName}{$row->{Iname}})
        && $OverrideVersions{$libName}{$row->{Iname}} ne $version)
    {
      print "Overriding $row->{Iname} with symbol version '$OverrideVersions{$libName}{$row->{Iname}}' ";
      if (defined($version))
      {
        print "was '$version'\n";
      }
      else
      {
        print "was unversioned\n";
      }
      $version = $OverrideVersions{$libName}{$row->{Iname}};
    }
    
    if (defined($version))
    {
      # Unversioned symbols will not have the Vname field defined
      if (!defined($versionInfo{$version}))
      {
        $versionInfo{$version} = ();
      }
      push(@{$versionInfo{$version}}, $row->{Iname});
    }
    
  }

  # Dump version information
  my($symbol);
  my($symbols);
  print VERSIONFILE "LSB_DUMMY { __LSB_DUMMY; };\n";
  foreach $version (sort keys %versionInfo)
  {
    $symbols = $versionInfo{$version};
    print VERSIONFILE "$version {\n";

    foreach $symbol (@$symbols)
    {
      print VERSIONFILE "  $symbol;\n";
    }
    print VERSIONFILE "};\n";
  }

  # Big Ugly Hack Alert!
  if ($libName eq "libc")
  {
    # We need to tack the following onto the libc stub library
    # _IO_stdin_used is an internal glibc symbol and shouldn't be used
    # but unless its present in the stub library then IO falls into a
    # big heap.
    print STUBFILE <<EOF
extern const int _IO_stdin_used;
__asm__(".weak _IO_stdin_used;.weak _LSB_IO_stdin_used; _LSB_IO_stdin_used=_IO_stdin_used ");
EOF
  }


  close(STUBFILE);
  close(VERSIONFILE);
}


######################################################################
# 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'};
}
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 (defined($DBHost) && $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};
  $DestinationDirectory = $TargetArch;
}

if (! -d $DestinationDirectory)
{
  mkdir($DestinationDirectory);
}

LoadMissingData();
LoadOverrideSymbolData();

# Map of name to run-time name of shared library
local(*LIBMAPFILE);
open(LIBMAPFILE, ">$DestinationDirectory/LibNameMap.txt") 
    || die "Could not open file LibNameMap.txt file for writing\n";

# Get list of libraries we want to produce stub libraries for
$sth = $dbh->prepare("SELECT Lid,Lname,Aname,ALrunname,ALaid FROM Library "
                     . "LEFT JOIN ArchLib ON Lid=ALlid "
                     . "LEFT JOIN Architecture ON Aid=ALaid "
                     . "WHERE Library.Lstd='Yes' "
                     . "AND ( ALaid=\'$TargetArchId\' OR Aname='All' ) "
                     . "AND ALrunname!='' "
                     . "ORDER BY Lname")
    || die $dbh->errstr;
$sth->execute() || die $sth->errstr;

my($lastlib);

$lastlib="";
while ($row = $sth->fetchrow_hashref)
{
  if( $row->{Lname} eq $lastlib ) {next; }
  $lastlib=$row->{Lname};
  print "Generating c stubs for $row->{Lname} library ($row->{ALrunname})\n";
  print LIBMAPFILE "$row->{Lname} $row->{ALrunname}\n";
  GenerateLibrary($dbh, $row->{Lname}, $row->{Lid});
}

