1#!/usr/bin/perl -w 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22 23# 24# Copyright 2009 Sun Microsystems, Inc. All rights reserved. 25# Use is subject to license terms. 26# 27 28# 29# interface_cmp audits two interface definition files (as created by 30# interface_check) against one another, and confirms that: 31# 32# o All versioned libraries that were present in the previous interface 33# are present in the new interface 34# 35# o for each non-private interface in a library confirm that no symbols 36# have been removed and that no symbols have been added to it between 37# the two revisions 38# 39# Return codes: 40# 41# 0 All interfaces in the new release are identical in old release. 42# 1 Something is different refer to the error messages. 43 44 45use strict; 46 47use POSIX qw(getenv); 48use Getopt::Std; 49use File::Basename; 50 51#### Define all global variables (required for strict) 52use vars qw($Prog); 53use vars qw(%opt); 54use vars qw(%old_hash %old_alias %new_hash %new_alias); 55 56# Exception Arrays: 57# 58# The ADDSYM and DELSYM exceptions are maintained on the @AddSymList 59# and @DelSymList arrays, respectively. Each array element is a reference 60# to a subarray of triples: 61# (sym_re, ver_re, obj_re) 62# where each item in the tripple is a regular expression, used to 63# match a particular symbol/version/object combination. 64# 65# The EMPTY_TOPVERSION exceptions are maintained on the @EmptyTopVerList 66# array. Each array element is a reference to a subarray of pairs: 67# (ver_re, obj_re) 68# where each item in the pair is a regular expression, used to 69# match a particular version/object combination. 70# 71use vars qw(@AddSymList @DelSymList @EmptyTopVerList); 72 73 74## LoadExceptions 75# 76# Locate the exceptions file and process its contents. We can't use 77# onbld_elfmod::LoadExceptionsToEXRE() for this, because our exceptions 78# need to support more than a single regular expression. 79# 80# exit: 81# @AddSymList, @DelSymList, and @EmptyTopVerList have been updated 82# 83# note: 84# We expand strings of the form MACH(dir) to match the given 85# directory as well as any 64-bit architecture subdirectory that 86# might be present (i.e. amd64, sparcv9). 87# 88sub LoadExceptions { 89 my $file; 90 my $Line; 91 my $LineNum = 0; 92 my $err = 0; 93 94 # Locate the exception file 95 FILE: { 96 # If -e is specified, that file must be used 97 if ($opt{e}) { 98 $file = $opt{e}; 99 last FILE; 100 } 101 102 # If this is an activated workspace, use the exception 103 # file found in the exceptions_list directory. 104 if (defined($ENV{CODEMGR_WS})) { 105 $file = "$ENV{CODEMGR_WS}/exception_lists/interface_cmp"; 106 last FILE if (-f $file); 107 } 108 109 # As a final backstop, the SUNWonbld package provides a 110 # copy of the exception file. This can be useful if we 111 # are being used with an older workspace. 112 # 113 # This script is installed in the SUNWonbld bin directory, 114 # while the exception file is in etc/exception_lists. Find 115 # it relative to the script location given by $0. 116 $file = dirname($0) . "/../etc/exception_lists/interface_cmp"; 117 last FILE if (-f $file); 118 119 # No exception file was found. 120 return; 121 } 122 123 open (EFILE, $file) || 124 die "$Prog: unable to open exceptions file: $file"; 125 while ($Line = onbld_elfmod::GetLine(\*EFILE, \$LineNum)) { 126 127 # Expand MACH() 128 $Line =~ s/MACH\(([^)]+)\)/$1(\/amd64|\/sparcv9)?/g; 129 130 if ($Line =~ /^DELSYM\s+/) { 131 my ($item, $sym_re, $ver_re, $obj_re) = 132 split(/\s+/, $Line, 4); 133 push @DelSymList, [ $sym_re, $ver_re, $obj_re ]; 134 next; 135 } 136 137 if ($Line =~ /^ADDSYM\s+/) { 138 my ($item, $sym_re, $ver_re, $obj_re) = 139 split(/\s+/, $Line, 4); 140 push @AddSymList, [ $sym_re, $ver_re, $obj_re ]; 141 next; 142 } 143 144 if ($Line =~ /^EMPTY_TOPVERSION\s+/) { 145 my ($item, $ver_re, $obj_re) = split(/\s+/, $Line, 3); 146 push @EmptyTopVerList, [ $ver_re, $obj_re ]; 147 next; 148 } 149 150 $err++; 151 printf(STDERR "$file: Unrecognized option: ". 152 "line $LineNum: $Line\n"); 153 } 154 close EFILE; 155 156 exit 1 if ($err != 0); 157} 158 159## ExSym(SymList, sym, ver, obj) 160# 161# Compare a given symbol/version/object combination against the 162# exceptions found in the given list. 163# 164# entry: 165# SymList - Reference to @AddSymList, or @DelSymList. 166# sym, ver, obj - Combination to be compared against exception list 167# 168# exit: 169# Returns True (1) if there is a match, and False (0) otherwise. 170# 171sub ExSym { 172 my ($SymList, $sym, $ver, $obj) = @_; 173 174 foreach my $ex (@$SymList) { 175 return 1 if ($obj =~ /$$ex[2]/) && ($ver =~ /$$ex[1]/) && 176 ($sym =~ /$$ex[0]/); 177 } 178 179 return 0; 180} 181 182## ExTopVer(ver, obj) 183# 184# Compare a given version/object combination against the pairs found 185# in @EmptyTopVerList. 186# 187# entry: 188# ver, obj - Combination to be compared against empty top version list 189# 190# exit: 191# Returns True (1) if there is a match, and False (0) otherwise. 192# 193sub ExTopVer { 194 my ($ver, $obj) = @_; 195 196 foreach my $ex (@EmptyTopVerList) { 197 return 1 if ($obj =~ /$$ex[1]/) && ($ver =~ /$$ex[0]/); 198 } 199 200 return 0; 201} 202 203## ExpandInheritance(objhashref) 204# 205# For each version contained in the specified object hash reference, 206# add the inherited symbols. 207# 208sub ExpandInheritance { 209 my $obj = $_[0]; 210 211 # Versions to process. Typically, inheriting versions come before 212 # the versions they inherit. Processing the list in reverse order 213 # maximizes the odds that a needed sub-version will have already 214 # have been processed. 215 my @vers = reverse(@{$obj->{'VERSION_NAMES'}}); 216 217 # Versions to process in the next pass 218 my @next_vers = (); 219 220 # Hash, indexed by version name, that reflects whether the version 221 # has been expanded yet or not. 222 my %done = (); 223 224 while (scalar(@vers) > 0) { 225 foreach my $name (@vers) { 226 my $i; 227 my $defer = 0; 228 my $cur_version = $obj->{'VERSION_INFO'}{$name}; 229 my ($top, $direct, $total, $symhash, $inheritarr) = 230 @{$cur_version}; 231 232 # In order to expand this version, all the inherited 233 # versions must already have been done. If not, put 234 # this version on @next_vers for the next pass. 235 my $num = scalar(@$inheritarr); 236 for ($i = 0; $i < $num; $i++) { 237 if (!$done{$inheritarr->[$i]}) { 238 $defer = 1; 239 push @next_vers, $name; 240 last; 241 } 242 } 243 next if ($defer); 244 245 # Add all the symbols from the inherited versions 246 # to this one. 247 for ($i = 0; $i < $num; $i++) { 248 my $i_version = 249 $obj->{'VERSION_INFO'}{$inheritarr->[$i]}; 250 my $i_symhash = $i_version->[3]; 251 252 foreach my $sym (keys %$i_symhash) { 253 if (!defined($cur_version->[3]{$sym})) { 254 $cur_version->[2]++; 255 $cur_version->[3]{$sym} = 'INHERIT'; 256 } 257 } 258 } 259 260 $done{$name} = 1; 261 } 262 263 @vers = @next_vers; 264 @next_vers = (); 265 } 266} 267 268## ReadInterface(file, alias) 269# 270# Read the interface description file, as produced by interface_check, and 271# return a hash describing it. 272# 273# entry: 274# file - Interface file to read. 275# alias - Refence to hash to be filled in with any aliases 276# that are seen in the file. The alias name is the key, 277# and the object is the value. 278# 279# exit: 280# The hash referenced by alias has been updated. 281# 282# The return value is a hash that encapsulates the interface 283# information. This hash returned uses the object names as the 284# key. Each key references a sub-hash that contains information 285# for that object: 286# 287# CLASS -> ELFCLASS 288# TYPE -> ELF type 289# VERSION_NAMES -> Reference to array [1..n] of version names, in the 290# order they come from the input file. 291# VERSION_INFO -> Reference to hash indexed by version name, yielding 292# a reference to an array containing information about 293# that version. 294# 295# The arrays referenced via VERSION_INFO are of the form: 296# 297# (top, new, total, symhashref, inheritarrref) 298# 299# where: 300# top - 1 if version is a TOP_VERSION, 0 for a regular VERSION 301# new - Number of symbols defined explicitly by version 302# total - Number of symbols included in version, both new, 303# and via inheritance. 304# symhashref - Reference to hash indexed by symbol names, and 305# yielding true (1). 306# inheritarrref - Reference to array of names of versions 307# inherited by this one. 308# 309sub ReadInterface { 310 my ($file, $alias) = @_; 311 my %main_hash = (); 312 my $Line; 313 my $LineNum = 0; 314 my $obj_name; 315 my $obj_hash; 316 my $sym_ok = 0; 317 my $cur_version; 318 319 open(FILE, $file) || die "$Prog: Unable to open: $file"; 320 321 # Until we see an OBJECT line, nothing else is valid. To 322 # simplify the error handling, use a simple initial loop to 323 # read the file up to that point 324 while ($Line = onbld_elfmod::GetLine(\*FILE, \$LineNum)) { 325 if ($Line =~ s/^OBJECT\s+//i) { 326 $obj_name = $Line; 327 $main_hash{$obj_name} = {}; 328 $obj_hash = $main_hash{$obj_name}; 329 last; 330 } 331 die "$file: OBJECT expected on line $LineNum: $Line\n"; 332 } 333 334 # Read the remainder of the file 335 while ($Line = onbld_elfmod::GetLine(\*FILE, \$LineNum)) { 336 # Items are parsed in order of decreasing frequency 337 338 if ($Line =~ 339 /^SYMBOL\s+([^\s]+)$/i) { 340 my $sym = $1; 341 342 die "$file: SYMBOL not expected on line $LineNum: $Line\n" 343 if !$sym_ok; 344 345 $cur_version->[1]++; 346 $cur_version->[2]++; 347 $cur_version->[3]{$sym} = 'NEW'; 348 next; 349 } 350 351 if ($Line =~ /^((TOP_)?VERSION)\s+([^\s]+)(\s+\{(.*)\})?\s*$/i) { 352 my ($top, $name, $inherit) = ($2, $3, $5); 353 354 $top = defined($top) ? 1 : 0; 355 356 my @inheritarr = defined($inherit) ? 357 split /[,{\s]+/, $inherit : (); 358 359 $cur_version = [ $top, 0, 0, {}, \@inheritarr ]; 360 $obj_hash->{'VERSION_INFO'}{$name} = $cur_version; 361 362 push @{$obj_hash->{'VERSION_NAMES'}}, $name; 363 $sym_ok = 1; 364 next; 365 } 366 367 if ($Line =~ /^OBJECT\s+([^\s]+)$/i) { 368 my $prev_obj_hash = $obj_hash; 369 $obj_name = $1; 370 $main_hash{$obj_name} = {}; 371 $obj_hash = $main_hash{$obj_name}; 372 373 # Expand the versions for the object just processed 374 ExpandInheritance($prev_obj_hash); 375 next; 376 } 377 378 if ($Line =~ /^CLASS\s+([^\s]+)$/i) { 379 $obj_hash->{'CLASS'} = $1; 380 next; 381 } 382 383 if ($Line =~ /^TYPE\s+([^\s]+)$/i) { 384 $obj_hash->{'TYPE'} = $1; 385 next; 386 } 387 388 if ($Line =~ /^ALIAS\s+([^\s]+)$/i) { 389 $$alias{$1} = $obj_name; 390 next; 391 } 392 393 die "$file: unrecognized item on line $LineNum: $Line\n"; 394 } 395 close FILE; 396 397 # Expand the versions for the final object from the file 398 ExpandInheritance($obj_hash); 399 400 return %main_hash; 401} 402 403## PrintInterface(main_hash, alias) 404# 405# Dump the contents of main_hash and alias to stdout in the same format 406# used by interface_check to produce the input interface file. This output 407# should diff cleanly against the original (ignoring the header comments). 408# 409sub PrintInterface { 410 my ($main_hash, $alias_hash) = @_; 411 412 foreach my $obj (sort keys %$main_hash) { 413 print "OBJECT\t$obj\n"; 414 print "CLASS\t$main_hash->{$obj}{'CLASS'}\n"; 415 print "TYPE\t$main_hash->{$obj}{'TYPE'}\n"; 416 417 # This is inefficient, but good enough for debugging 418 # Look at all the aliases and print those that belong 419 # to this object. 420 foreach my $alias (sort keys %$alias_hash) { 421 print "ALIAS\t$alias\n" 422 if ($obj eq $alias_hash->{$alias}); 423 } 424 425 next if !defined($main_hash->{$obj}{'VERSION_NAMES'}); 426 427 my $num = scalar(@{$main_hash->{$obj}{'VERSION_NAMES'}}); 428 my $i; 429 for ($i = 0; $i < $num; $i++) { 430 my $name = $main_hash->{$obj}{'VERSION_NAMES'}[$i]; 431 my ($top, $direct, $total, $symhash, $inheritarr) = 432 @{$main_hash->{$obj}{'VERSION_INFO'}{$name}}; 433 434 $top = $top ? "TOP_" : ''; 435 436 my $inherit = (scalar(@$inheritarr) > 0) ? 437 "\t{" . join(', ', @{$inheritarr}) . "}" : ''; 438 439 print "${top}VERSION\t$name$inherit\n"; 440 441 foreach my $sym (sort keys %$symhash) { 442 print "\t$symhash->{$sym}\t$sym\n"; 443 } 444 } 445 } 446} 447 448## compare() 449# 450# Compare the old interface definition contained in (%old_hash, %old_alias) 451# with the new interface contained in (%new_hash, %new_alias). 452# 453sub compare { 454 foreach my $old_obj (sort keys %old_hash) { 455 my $new_obj = $old_obj; 456 my $Ttl = 0; 457 458 # If the object does not exist in the new interface, 459 # then see if there's an alias for it. Failing that, 460 # we simply ignore the object. 461 if (!defined($new_hash{$new_obj})) { 462 next if !defined($new_alias{$new_obj}); 463 $new_obj = $new_alias{$new_obj}; 464 } 465 466 my $old = $old_hash{$old_obj}; 467 my $new = $new_hash{$new_obj}; 468 469 # Every version in the old object must exist in the new object, 470 # and there must be exactly the same symbols in each. 471 my $num = scalar(@{$old->{'VERSION_NAMES'}}); 472 for (my $i = 0; $i < $num; $i++) { 473 my $name = $old->{'VERSION_NAMES'}[$i]; 474 475 # New object must have this version 476 if (!defined($new->{'VERSION_INFO'}{$name})) { 477 onbld_elfmod::OutMsg2(\*STDOUT, \$Ttl, $old_obj, 478 $new_obj, "$name: deleted version"); 479 next; 480 } 481 482 my ($old_top, $old_direct, $old_total, $old_symhash) = 483 @{$old->{'VERSION_INFO'}{$name}}; 484 my ($new_top, $new_direct, $new_total, $new_symhash) = 485 @{$new->{'VERSION_INFO'}{$name}}; 486 487 # If this is an empty top version, and the old object 488 # has the EMPTY_TOPVERSION exception set, then we 489 # skip it as if it were not present. 490 next if $old_top && ($old_direct == 0) && 491 ExTopVer($name, $old_obj); 492 493 # We check that every symbol in the old object is 494 # in the new one to detect deleted symbols. We then 495 # check that every symbol in the new object is also 496 # in the old object, to find added symbols. If the 497 # "deleted" check is clean, and the two objects have 498 # the same number of symbols in their versions, then we 499 # can skip the "added" test, because we know that 500 # there is no room for an addition to have happened. 501 # Since most objects satisfy these constraints, we 502 # end up doing roughly half the number of comparisons 503 # that would otherwise be needed. 504 my $check_added_syms = 505 ($old_total == $new_total) ? 0: 1; 506 507 # Every symbol in the old version must be in the new one 508 foreach my $sym (sort keys %$old_symhash) { 509 if (!defined($new_symhash->{$sym})) { 510 onbld_elfmod::OutMsg2(\*STDOUT, 511 \$Ttl, $old_obj, $new_obj, 512 "$name: deleted interface: $sym") 513 if !ExSym(\@DelSymList, 514 $sym, $name, $new_obj); 515 $check_added_syms = 1; 516 } 517 } 518 519 # Do the "added" check, unless we can optimize it away. 520 # Every symbol in the new version must be in the old one. 521 if ($check_added_syms) { 522 foreach my $sym (sort keys %$new_symhash) { 523 if (!defined($old_symhash->{$sym})) { 524 next if ExSym(\@AddSymList, 525 $sym, $name, $new_obj); 526 onbld_elfmod::OutMsg2(\*STDOUT, 527 \$Ttl, $old_obj, $new_obj, 528 "$name: added interface: $sym"); 529 } 530 } 531 } 532 533 # We want to ensure that version numbers in an 534 # inheritance chain don't go up by more than 1 in 535 # any given release. If the version names are in the 536 # standard SUNW_x.y[.z] format, we can compare the 537 # two top versions and see if this has happened. 538 # 539 # For a given SUNW_x.y[.z], valid sucessors would 540 # be SUNW_x.(y+1) or SUNW_x.y.(z+1), where z is 541 # assumed to be 0 if not present. 542 # 543 # This check only makes sense when the new interface 544 # is a direct decendent of the old one, as specified 545 # via the -d option. If the two interfaces are more 546 # than one release apart, we should not do this test. 547 if ($opt{d} && $old_top && !$new_top && 548 ($name =~ /^SUNW_(\d+)\.(\d+)(\.(\d+))?/)) { 549 my $iname1 = "SUNW_$1." . ($2 + 1); 550 my $iname2; 551 if (defined($4)) { 552 $iname2 = "SUNW_$1.$2." . ($4 + 1); 553 } else { 554 $iname2 = "SUNW_$1.$2.1"; 555 } 556 557 if (defined($new->{'VERSION_INFO'}{$iname1}) || 558 defined($new->{'VERSION_INFO'}{$iname2})) { 559 my $i_top = 560 $new->{'VERSION_INFO'}{$iname1}[0] || 561 $new->{'VERSION_INFO'}{$iname2}[0]; 562 if (!$i_top) { 563 onbld_elfmod::OutMsg2(\*STDOUT, 564 \$Ttl, $old_obj, $new_obj, 565 "$name: inconsistant " . 566 "version increment: " . 567 "expect $iname1 or $iname2 ". 568 "to replace top version"); 569 } 570 } else { 571 onbld_elfmod::OutMsg2(\*STDOUT, 572 \$Ttl, $old_obj, $new_obj, 573 "$name: expected superseding " . 574 "top version to $name not " . 575 "present: $iname1 or $iname2"); 576 } 577 } 578 } 579 580 581 # Empty versions in the established interface description 582 # are usually the result of fixing a versioning mistake 583 # at some point in the past. These versions are part of 584 # the public record, and cannot be changed now. However, if 585 # comparing two interface descriptions from the same gate, 586 # flag any empty versions in the new interface description 587 # that are not present in the old one. These have yet to 588 # become part of the official interface, and should be removed 589 # before they do. 590 next if !$opt{d}; 591 592 $num = scalar(@{$new->{'VERSION_NAMES'}}); 593 for (my $i = 0; $i < $num; $i++) { 594 my $name = $new->{'VERSION_NAMES'}[$i]; 595 596 # If old object has this version, skip it 597 next if defined($old->{'VERSION_INFO'}{$name}); 598 599 # If explicitly whitelisted, skip it 600 next if ExTopVer($name, $new_obj); 601 602 my ($new_top, $new_direct, $new_total, $new_symhash) = 603 @{$new->{'VERSION_INFO'}{$name}}; 604 605 if ($new_direct == 0) { 606 onbld_elfmod::OutMsg2(\*STDOUT, 607 \$Ttl, $old_obj, $new_obj, 608 "$name: invalid empty new version"); 609 } 610 } 611 } 612 613} 614 615 616 617# ----------------------------------------------------------------------------- 618 619# Establish a program name for any error diagnostics. 620chomp($Prog = `basename $0`); 621 622# The onbld_elfmod package is maintained in the same directory as this 623# script, and is installed in ../lib/perl. Use the local one if present, 624# and the installed one otherwise. 625my $moddir = dirname($0); 626$moddir = "$moddir/../lib/perl" if ! -f "$moddir/onbld_elfmod.pm"; 627require "$moddir/onbld_elfmod.pm"; 628 629# Check that we have arguments. Normally, 2 plain arguments are required, 630# but if -t is present, only one is allowed. 631if ((getopts('de:ot', \%opt) == 0) || (scalar(@ARGV) != ($opt{t} ? 1 : 2))) { 632 print "usage: $Prog [-dot] [-e exfile] old new\n"; 633 print "\t[-d]\t\tnew is a direct decendent of old\n"; 634 print "\t[-e exfile]\texceptions file\n"; 635 print "\t[-o]\t\tproduce one-liner output (prefixed with pathname)\n"; 636 print "\t[-t]\tParse old, and recreate to stdout\n"; 637 exit 1; 638} 639 640# Locate and process the exceptions file 641LoadExceptions(); 642 643%old_alias = (); 644%old_hash = ReadInterface($ARGV[0], \%old_alias); 645 646# If -t is present, only one argument is allowed --- we parse it, and then 647# print the same information back to stderr in the same format as the original. 648# This is useful for debugging, to verify that the parsing is correct. 649if ($opt{t}) { 650 PrintInterface(\%old_hash, \%old_alias); 651 exit 0; 652} 653 654%new_alias = (); 655%new_hash = ReadInterface($ARGV[1], \%new_alias); 656 657compare(); 658 659exit 0; 660