#!/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
#

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

use Mysql;

use Env qw(LSBUSER LSBDBPASSWD LSBDB LSBDBHOST);

my ($DBName) = $LSBDB;
my ($DBUser) = $LSBUSER;
my ($DBPass) = $LSBDBPASSWD;
my ($DBHost) = $LSBDBHOST;
my ($lsbversion);
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
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 =~ /^\#/ ) {
                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, skipping\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 =~ /^\#/ ) {
                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";
    }
}

######################################################################
# Create temporary table containing all symbols assigned to library
sub CreateTmpLibTable($$) {
    my ($dbh)     = shift;
    my ($libId)   = shift;
    my ($sth);
    my ($create);

    $create = "CREATE TEMPORARY TABLE tmp_LibInts_$libId "
        . "(KEY `Iid`(`Iid`)) "
        . "SELECT Iid, Iname, Ireturn, Itype FROM Interface "
        . "LEFT JOIN LGInt ON LGIint=Iid "
        . "LEFT JOIN LibGroup ON LGid=LGIlibg "
        . "WHERE LGlib=$libId";
    $sth = $dbh->prepare($create) || die $dbh->errstr;
    $sth->execute() || die $sth->errstr;

    $create = "CREATE TEMPORARY TABLE tmp_LibIncludedInts_$libId "
        . "(KEY `Iid`(`Iid`), KEY `AIarch`(`AIarch`), KEY `Iname`(`Iname`,`AIarch`), KEY `Itype`(`Itype`,`AIarch`)) "
        . "SELECT Iid, Iname, Ireturn, Itype, AIarch, AIversion FROM tmp_LibInts_$libId "
        . "LEFT JOIN ArchInt ON AIint=Iid "
        . "WHERE (AIappearedin <= '$lsbversion' and AIappearedin<>'') "
        . "AND (AIwithdrawnin IS NULL OR AIwithdrawnin >'$lsbversion') ";
    $sth = $dbh->prepare($create) || die $dbh->errstr;
    $sth->execute() || die $sth->errstr;
}

######################################################################
# Generate C stub code for a library
sub GenerateLibrary($$$) {
    my ($dbh)     = shift;
    my ($libName) = shift;
    my ($libId)   = shift;
    my ($sth);
    my ($select);
    my ($sthCheck);
    my ($selectCheck);
    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,AIarch,Vname FROM tmp_LibIncludedInts_$libId "
      . "LEFT JOIN Version ON Vid=AIversion "
      . "WHERE Itype='Function' "
      . "AND ( AIarch=$TargetArchId OR AIarch=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->{Vname} ){print "$row->{Iname}\tNULL Vname\n";}
        #     if( !$row->{Vname} && $row->{AIarch}
        # 	&& $row->{AIarch}!=$TargetArchId && $row->{AIarch}!=1 )
        # 		{ 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 ( ( $TargetArchId != 1 ) && ( $row->{AIarch} == 1 ) ) {
            $selectCheck =
                "SELECT Iid FROM tmp_LibIncludedInts_$libId "
              . "WHERE Iname='$row->{Iname}' "
              . "AND AIarch=$TargetArchId "
              . "LIMIT 1";

            $sthCheck = $dbh->prepare($selectCheck) || die $dbh->errstr;
            $sthCheck->execute() || die $sthCheck->errstr;
            if ( $sthCheck->fetchrow_hashref() ) {
                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 tmp_LibIncludedInts_$libId "
      . "LEFT JOIN Version ON Vid=AIversion "
      . "LEFT JOIN ArchType ON ATaid=$TargetArchId AND ATtid=Ireturn "
      . "WHERE "
      . "( Itype='Data' OR Itype='Alias' OR Itype='Common' ) "
      . "AND ( AIarch=$TargetArchId OR AIarch=1 ) "
      . "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 ( ( $TargetArchId != 1 ) && ( $row->{AIarch} == 1 ) ) {
            $selectCheck =
                "SELECT DISTINCT Iid FROM tmp_LibIncludedInts_$libId "
              . "LEFT JOIN ArchType ON ATtid=Ireturn and "
              . "ATaid=$TargetArchId WHERE "
              . "tmp_LibIncludedInts_$libId.Iname='$row->{Iname}' "
              . "AND AIarch=$TargetArchId "
              . "LIMIT 1";

            $sthCheck = $dbh->prepare($selectCheck) || die $dbh->errstr;
            $sthCheck->execute() || die $sthCheck->errstr;
            if ( $sthCheck->fetchrow_hashref() ) {
                next;
            }
        }

        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 ) {
            my $selectGenericSize;
            my $sthGenericSize;
            my $rowGenericSize;

            $selectGenericSize = "SELECT ATsize FROM ArchType ";
            $selectGenericSize .=
              "WHERE ATaid=1 AND ATtid=" . $row->{"Ireturn"};
            $sthGenericSize = $dbh->prepare($selectGenericSize)
              || die $dbh->errstr;
            $sthGenericSize->execute() || die $sthGenericSize->errstr;
            if ( $rowGenericSize = $sthGenericSize->fetchrow_hashref() ) {
                $row->{"ATsize"} = $rowGenericSize->{"ATsize"};
            }
        }

        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:v:o:ha:', \%Options );

if ( ( exists( $Options{'h'} ) ) or ( not exists( $Options{'v'} ) ) ) {
    print STDERR <<"EOM"
Usage $0 -a arch -v lsbversion [-d db_name] [-u username] [-p password] [-o hostname] [-h]
    -h           Display this help
    -v           Target LSB version
    -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";
}

$lsbversion = $Options{'v'};

$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
my $select;
$select =
    "SELECT Lid,Lname,Aname,ALrunname,ALaid FROM Library "
  . "LEFT JOIN ArchLib ON Lid=ALlid "
  . "LEFT JOIN Architecture ON Aid=ALaid "
  . "WHERE (ALappearedin <= \'$lsbversion\' and ALappearedin<>\'\') "
  . "AND (ALwithdrawnin IS NULL OR ALwithdrawnin > \'$lsbversion\') "
  . "AND ( ALaid=\'$TargetArchId\' OR ALaid=1 ) "
  . "AND ALrunname<>\'\' "
  . "ORDER BY Lname";
$sth = $dbh->prepare($select)
  || 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";
    CreateTmpLibTable( $dbh, $row->{Lid} );
    GenerateLibrary( $dbh, $row->{Lname}, $row->{Lid} );
}
