1#!/usr/sfw/bin/python 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# Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23# Use is subject to license terms. 24# 25#ident "%Z%%M% %I% %E% SMI" 26 27# 28# wsdiff(1) is a tool that can be used to determine which compiled objects 29# have changed as a result of a given source change. Developers backporting 30# new features, RFEs and bug fixes need to be able to identify the set of 31# patch deliverables necessary for feature/fix realization on a patched system. 32# 33# The tool works by comparing objects in two trees/proto areas (one build with, 34# and without the source changes. 35# 36# Using wsdiff(1) is fairly simple: 37# - Bringover to a fresh workspace 38# - Perform a full non-debug build (clobber if workspace isn't fresh) 39# - Move the proto area aside, call it proto.old, or something. 40# - Integrate your changes to the workspace 41# - Perform another full non-debug clobber build. 42# - Use wsdiff(1) to see what changed: 43# $ wsdiff proto.old proto 44# 45# By default, wsdiff will print the list of changed objects / deliverables to 46# stdout. If a results file is specified via -r, the list of differing objects, 47# and details about why wsdiff(1) thinks they are different will be logged to 48# the results file. 49# 50# By invoking nightly(1) with the -w option to NIGHTLY_FLAGS, nightly(1) will use 51# wsdiff(1) to report on what objects changed since the last build. 52# 53# For patch deliverable purposes, it's advised to have nightly do a clobber, 54# non-debug build. 55# 56# Think about the results. Was something flagged that you don't expect? Go look 57# at the results file to see details about the differences. 58# 59# Use the -i option in conjunction with -v and -V to dive deeper and have wsdiff(1) 60# report with more verbosity. 61# 62# Usage: wsdiff [-vVt] [-r results ] [-i filelist ] old new 63# 64# Where "old" is the path to the proto area build without the changes, and 65# "new" is the path to the proto area built with the changes. The following 66# options are supported: 67# 68# -v Do not truncate observed diffs in results 69# -V Log *all* ELF sect diffs vs. logging the first diff found 70# -t Use onbld tools in $SRC/tools 71# -r Log results and observed differences 72# -i Tell wsdiff which objects to compare via an input file list 73 74import datetime, fnmatch, getopt, profile, os, popen2, commands 75import re, select, string, struct, sys, tempfile, time 76from stat import * 77 78# Human readable diffs truncated by default if longer than this 79# Specifying -v on the command line will override 80diffs_sz_thresh = 4096 81 82# Default search path for wsdiff 83wsdiff_path = [ "/usr/bin", 84 "/usr/ccs/bin", 85 "/lib/svc/bin", 86 "/opt/onbld/bin" ] 87 88# These are objects that wsdiff will notice look different, but will not report. 89# Existence of an exceptions list, and adding things here is *dangerous*, 90# and therefore the *only* reasons why anything would be listed here is because 91# the objects do not build deterministically, yet we *cannot* fix this. 92# 93# These perl libraries use __DATE__ and therefore always look different. 94# Ideally, we would purge use the use of __DATE__ from the source, but because 95# this is source we wish to distribute with Solaris "unchanged", we cannot modify. 96# 97wsdiff_exceptions = [ "usr/perl5/5.8.4/lib/sun4-solaris-64int/CORE/libperl.so.1", 98 "usr/perl5/5.6.1/lib/sun4-solaris-64int/CORE/libperl.so.1", 99 "usr/perl5/5.8.4/lib/i86pc-solaris-64int/CORE/libperl.so.1", 100 "usr/perl5/5.6.1/lib/i86pc-solaris-64int/CORE/libperl.so.1" 101 ] 102 103##### 104# Logging routines 105# 106 107# Informational message to be printed to the screen, and the log file 108def info(msg) : 109 110 print >> sys.stdout, msg 111 if logging : 112 print >> log, msg 113 sys.stdout.flush() 114 115# Error message to be printed to the screen, and the log file 116def error(msg) : 117 118 print >> sys.stderr, "ERROR:", msg 119 sys.stderr.flush() 120 if logging : 121 print >> log, "ERROR:", msg 122 log.flush() 123 124# Informational message to be printed only to the log, if there is one. 125def v_info(msg) : 126 127 if logging : 128 print >> log, msg 129 log.flush() 130 131# 132# Flag a detected file difference 133# Display the fileName to stdout, and log the difference 134# 135def difference(f, dtype, diffs) : 136 137 if f in wsdiff_exceptions : 138 return 139 140 print >> sys.stdout, f 141 sys.stdout.flush() 142 143 log_difference(f, dtype, diffs) 144 145# 146# Do the actual logging of the difference to the results file 147# 148def log_difference(f, dtype, diffs) : 149 if logging : 150 print >> log, f 151 print >> log, "NOTE:", dtype, "difference detected." 152 153 difflen = len(diffs) 154 if difflen > 0 : 155 print >> log 156 157 if not vdiffs and difflen > diffs_sz_thresh : 158 print >> log, diffs[:diffs_sz_thresh] 159 print >> log, \ 160 "... truncated due to length: " \ 161 "use -v to override ..." 162 else : 163 print >> log, diffs 164 print >> log, "\n" 165 log.flush() 166 167 168##### 169# diff generating routines 170# 171 172# 173# Return human readable diffs from two temporary files 174# 175def diffFileData(tmpf1, tmpf2) : 176 177 # Filter the data through od(1) if the data is detected 178 # as being binary 179 if isBinary(tmpf1) or isBinary(tmpf2) : 180 tmp_od1 = tmpf1 + ".od" 181 tmp_od2 = tmpf2 + ".od" 182 183 cmd = od_cmd + " -c -t x4" + " " + tmpf1 + " > " + tmp_od1 184 os.system(cmd) 185 cmd = od_cmd + " -c -t x4" + " " + tmpf2 + " > " + tmp_od2 186 os.system(cmd) 187 188 tmpf1 = tmp_od1 189 tmpf2 = tmp_od2 190 191 data = commands.getoutput(diff_cmd + " " + tmpf1 + " " + tmpf2) 192 193 return data 194 195# 196# Return human readable diffs betweeen two datasets 197# 198def diffData(d1, d2) : 199 200 global tmpFile1 201 global tmpFile2 202 203 try: 204 fd1 = open(tmpFile1, "w") 205 except: 206 error("failed to open: " + tmpFile1) 207 cleanup(1) 208 try: 209 fd2 = open(tmpFile2, "w") 210 except: 211 error("failed to open: " + tmpFile2) 212 cleanup(1) 213 214 fd1.write(d1) 215 fd2.write(d2) 216 fd1.close() 217 fd2.close() 218 219 return diffFileData(tmpFile1, tmpFile2) 220 221##### 222# Misc utility functions 223# 224 225# Prune off the leading prefix from string s 226def str_prefix_trunc(s, prefix) : 227 snipLen = len(prefix) 228 return s[snipLen:] 229 230# 231# Prune off leading proto path goo (if there is one) to yield 232# the deliverable's eventual path relative to root 233# e.g. proto.base/root_sparc/usr/src/cmd/prstat => usr/src/cmd/prstat 234# 235def fnFormat(fn) : 236 root_arch_str = "root_" + arch 237 238 pos = fn.find(root_arch_str) 239 if pos == -1 : 240 return fn 241 242 pos = fn.find("/", pos) 243 if pos == -1 : 244 return fn 245 246 return fn[pos + 1:] 247 248##### 249# Usage / argument processing 250# 251 252# 253# Display usage message 254# 255def usage() : 256 sys.stdout.flush() 257 print >> sys.stderr, """Usage: wsdiff [-vVt] [-r results ] [-i filelist ] old new 258 -v Do not truncate observed diffs in results 259 -V Log *all* ELF sect diffs vs. logging the first diff found 260 -t Use onbld tools in $SRC/tools 261 -r Log results and observed differences 262 -i Tell wsdiff which objects to compare via an input file list""" 263 sys.exit(1) 264 265# 266# Process command line options 267# 268def args() : 269 270 global logging 271 global vdiffs 272 global reportAllSects 273 274 validOpts = 'i:r:vVt?' 275 276 baseRoot = "" 277 ptchRoot = "" 278 fileNamesFile = "" 279 results = "" 280 localTools = False 281 282 # getopt.getopt() returns: 283 # an option/value tuple 284 # a list of remaining non-option arguments 285 # 286 # A correct wsdiff invocation will have exactly two non option 287 # arguments, the paths to the base (old), ptch (new) proto areas 288 try: 289 optlist, args = getopt.getopt(sys.argv[1:], validOpts) 290 except getopt.error, val: 291 usage() 292 293 if len(args) != 2 : 294 usage(); 295 296 for opt,val in optlist : 297 if opt == '-i' : 298 fileNamesFile = val 299 elif opt == '-r' : 300 results = val 301 logging = True 302 elif opt == '-v' : 303 vdiffs = True 304 elif opt == '-V' : 305 reportAllSects = True 306 elif opt == '-t': 307 localTools = True 308 else: 309 usage() 310 311 baseRoot = args[0] 312 ptchRoot = args[1] 313 314 if len(baseRoot) == 0 or len(ptchRoot) == 0 : 315 usage() 316 317 if logging and len(results) == 0 : 318 usage() 319 320 if vdiffs and not logging : 321 error("The -v option requires a results file (-r)") 322 sys.exit(1) 323 324 if reportAllSects and not logging : 325 error("The -V option requires a results file (-r)") 326 sys.exit(1) 327 328 # alphabetical order 329 return baseRoot, fileNamesFile, localTools, ptchRoot, results 330 331##### 332# File identification 333# 334 335# 336# Identify the file type. 337# If it's not ELF, use the file extension to identify 338# certain file types that require special handling to 339# compare. Otherwise just return a basic "ASCII" type. 340# 341def getTheFileType(f) : 342 343 extensions = { 'a' : 'ELF Object Archive', 344 'jar' : 'Java Archive', 345 'html' : 'HTML', 346 'ln' : 'Lint Library', 347 'esa' : 'Elfsign Activation', 348 'db' : 'Sqlite Database' } 349 350 try: 351 if os.stat(f)[ST_SIZE] == 0 : 352 return 'ASCII' 353 except: 354 error("failed to stat " + f) 355 return 'Error' 356 357 if isELF(f) == 1 : 358 return 'ELF' 359 360 fnamelist = f.split('.') 361 if len(fnamelist) > 1 : # Test the file extension 362 extension = fnamelist[-1] 363 if extension in extensions.keys(): 364 return extensions[extension] 365 366 return 'ASCII' 367 368# 369# Return non-zero if "f" is an ELF file 370# 371elfmagic = '\177ELF' 372def isELF(f) : 373 try: 374 fd = open(f) 375 except: 376 error("failed to open: " + f) 377 return 0 378 magic = fd.read(len(elfmagic)) 379 fd.close() 380 381 if magic == elfmagic : 382 return 1 383 return 0 384 385# 386# Return non-zero is "f" is binary. 387# Consider the file to be binary if it contains any null characters 388# 389def isBinary(f) : 390 try: 391 fd = open(f) 392 except: 393 error("failed to open: " + f) 394 return 0 395 s = fd.read() 396 fd.close() 397 398 if s.find('\0') == -1 : 399 return 0 400 else : 401 return 1 402 403##### 404# Directory traversal and file finding 405# 406 407# 408# Return a sorted list of files found under the specified directory 409# 410def findFiles(d) : 411 for path, subdirs, files in os.walk(d) : 412 files.sort() 413 for name in files : 414 yield os.path.join(path, name) 415 416# 417# Examine all files in base, ptch 418# 419# Return a list of files appearing in both proto areas, 420# a list of new files (files found only in ptch) and 421# a list of deleted files (files found only in base) 422# 423def protoCatalog(base, ptch) : 424 compFiles = [] # List of files in both proto areas 425 ptchList = [] # List of file in patch proto area 426 427 newFiles = [] # New files detected 428 deletedFiles = [] # Deleted files 429 430 baseFilesList = list(findFiles(base)) 431 baseStringLength = len(base) 432 433 ptchFilesList = list(findFiles(ptch)) 434 ptchStringLength = len(ptch) 435 436 # Inventory files in the base proto area 437 for fn in baseFilesList : 438 if os.path.islink(fn) : 439 continue 440 441 fileName = fn[baseStringLength:] 442 compFiles.append(fileName) 443 444 # Inventory files in the patch proto area 445 for fn in ptchFilesList : 446 if os.path.islink(fn) : 447 continue 448 449 fileName = fn[ptchStringLength:] 450 ptchList.append(fileName) 451 452 # Deleted files appear in the base area, but not the patch area 453 for fileName in compFiles : 454 if not fileName in ptchList : 455 deletedFiles.append(fileName) 456 457 # Eliminate "deleted" files from the list of objects appearing 458 # in both the base and patch proto areas 459 for fileName in deletedFiles : 460 try: 461 compFiles.remove(fileName) 462 except: 463 error("filelist.remove() failed") 464 465 # New files appear in the patch area, but not the base 466 for fileName in ptchList : 467 if not fileName in compFiles : 468 newFiles.append(fileName) 469 470 return compFiles, newFiles, deletedFiles 471 472# 473# Examine the files listed in the input file list 474# 475# Return a list of files appearing in both proto areas, 476# a list of new files (files found only in ptch) and 477# a list of deleted files (files found only in base) 478# 479def flistCatalog(base, ptch, flist) : 480 compFiles = [] # List of files in both proto areas 481 newFiles = [] # New files detected 482 deletedFiles = [] # Deleted files 483 484 try: 485 fd = open(flist, "r") 486 except: 487 error("could not open: " + flist) 488 cleanup(1) 489 490 files = [] 491 files = fd.readlines() 492 493 for f in files : 494 ptch_present = True 495 base_present = True 496 497 if f == '\n' : 498 continue 499 500 # the fileNames have a trailing '\n' 501 f = f.rstrip() 502 503 # The objects in the file list have paths relative 504 # to $ROOT or to the base/ptch directory specified on 505 # the command line. 506 # If it's relative to $ROOT, we'll need to add back the 507 # root_`uname -p` goo we stripped off in fnFormat() 508 if os.path.exists(base + f) : 509 fn = f; 510 elif os.path.exists(base + "root_" + arch + "/" + f) : 511 fn = "root_" + arch + "/" + f 512 else : 513 base_present = False 514 515 if base_present : 516 if not os.path.exists(ptch + fn) : 517 ptch_present = False 518 else : 519 if os.path.exists(ptch + f) : 520 fn = f 521 elif os.path.exists(ptch + "root_" + arch + "/" + f) : 522 fn = "root_" + arch + "/" + f 523 else : 524 ptch_present = False 525 526 if os.path.islink(base + fn) : # ignore links 527 base_present = False 528 if os.path.islink(ptch + fn) : 529 ptch_present = False 530 531 if base_present and ptch_present : 532 compFiles.append(fn) 533 elif base_present : 534 deletedFiles.append(fn) 535 elif ptch_present : 536 newFiles.append(fn) 537 else : 538 if os.path.islink(base + fn) and os.path.islink(ptch + fn) : 539 continue 540 error(f + " in file list, but not in either tree. Skipping...") 541 542 return compFiles, newFiles, deletedFiles 543 544 545# 546# Build a fully qualified path to an external tool/utility. 547# Consider the default system locations. For onbld tools, if 548# the -t option was specified, we'll try to use built tools in $SRC tools, 549# and otherwise, we'll fall back on /opt/onbld/ 550# 551def find_tool(tool) : 552 553 # First, check what was passed 554 if os.path.exists(tool) : 555 return tool 556 557 # Next try in wsdiff path 558 for pdir in wsdiff_path : 559 location = pdir + "/" + tool 560 if os.path.exists(location) : 561 return location + " " 562 563 location = pdir + "/" + arch + "/" + tool 564 if os.path.exists(location) : 565 return location + " " 566 567 error("Could not find path to: " + tool); 568 sys.exit(1); 569 570 571##### 572# ELF file comparison helper routines 573# 574 575# 576# Return a dictionary of ELF section types keyed by section name 577# 578def get_elfheader(f) : 579 580 header = {} 581 582 hstring = commands.getoutput(elfdump_cmd + " -c " + f) 583 584 if len(hstring) == 0 : 585 error("Failed to dump ELF header for " + f) 586 return 587 588 # elfdump(1) dumps the section headers with the section name 589 # following "sh_name:", and the section type following "sh_type:" 590 sections = hstring.split("Section Header") 591 for sect in sections : 592 datap = sect.find("sh_name:"); 593 if datap == -1 : 594 continue 595 section = sect[datap:].split()[1] 596 datap = sect.find("sh_type:"); 597 if datap == -1 : 598 error("Could not get type for sect: " + section + \ 599 " in " + f) 600 sh_type = sect[datap:].split()[2] 601 header[section] = sh_type 602 603 return header 604 605# 606# Extract data in the specified ELF section from the given file 607# 608def extract_elf_section(f, section) : 609 610 data = commands.getoutput(dump_cmd + " -sn " + section + " " + f) 611 612 if len(data) == 0 : 613 error(cmd + " yielded no data") 614 return 615 616 # dump(1) displays the file name to start... 617 # get past it to the data itself 618 dbegin = data.find(":") + 1 619 data = data[dbegin:]; 620 621 return (data) 622 623# 624# Return a (hopefully meaningful) human readable set of diffs 625# for the specified ELF section between f1 and f2 626# 627# Depending on the section, various means for dumping and diffing 628# the data may be employed. 629# 630text_sections = [ '.text', '.init', '.fini' ] 631def diff_elf_section(f1, f2, section, sh_type) : 632 633 if (sh_type == "SHT_RELA") : # sh_type == SHT_RELA 634 cmd1 = elfdump_cmd + " -r " + f1 + " > " + tmpFile1 635 cmd2 = elfdump_cmd + " -r " + f2 + " > " + tmpFile2 636 elif (section == ".group") : 637 cmd1 = elfdump_cmd + " -g " + f1 + " > " + tmpFile1 638 cmd2 = elfdump_cmd + " -g " + f2 + " > " + tmpFile2 639 elif (section == ".hash") : 640 cmd1 = elfdump_cmd + " -h " + f1 + " > " + tmpFile1 641 cmd2 = elfdump_cmd + " -h " + f2 + " > " + tmpFile2 642 elif (section == ".dynamic") : 643 cmd1 = elfdump_cmd + " -d " + f1 + " > " + tmpFile1 644 cmd2 = elfdump_cmd + " -d " + f2 + " > " + tmpFile2 645 elif (section == ".got") : 646 cmd1 = elfdump_cmd + " -G " + f1 + " > " + tmpFile1 647 cmd2 = elfdump_cmd + " -G " + f2 + " > " + tmpFile2 648 elif (section == ".SUNW_cap") : 649 cmd1 = elfdump_cmd + " -H " + f1 + " > " + tmpFile1 650 cmd2 = elfdump_cmd + " -H " + f2 + " > " + tmpFile2 651 elif (section == ".interp") : 652 cmd1 = elfdump_cmd + " -i " + f1 + " > " + tmpFile1 653 cmd2 = elfdump_cmd + " -i " + f2 + " > " + tmpFile2 654 elif (section == ".symtab" or section == ".dynsym") : 655 cmd1 = elfdump_cmd + " -s -N " + section + " " + f1 + " > " + tmpFile1 656 cmd2 = elfdump_cmd + " -s -N " + section + " " + f2 + " > " + tmpFile2 657 elif (section in text_sections) : 658 # dis sometimes complains when it hits something it doesn't 659 # know how to disassemble. Just ignore it, as the output 660 # being generated here is human readable, and we've already 661 # correctly flagged the difference. 662 cmd1 = dis_cmd + " -t " + section + " " + f1 + \ 663 " 2>/dev/null | grep -v disassembly > " + tmpFile1 664 cmd2 = dis_cmd + " -t " + section + " " + f2 + \ 665 " 2>/dev/null | grep -v disassembly > " + tmpFile2 666 else : 667 cmd1 = elfdump_cmd + " -w " + tmpFile1 + " -N " + \ 668 section + " " + f1 669 cmd2 = elfdump_cmd + " -w " + tmpFile2 + " -N " + \ 670 section + " " + f2 671 672 os.system(cmd1) 673 os.system(cmd2) 674 675 data = diffFileData(tmpFile1, tmpFile2) 676 677 return (data) 678 679# 680# compare the relevant sections of two ELF binaries 681# and report any differences 682# 683# Returns: 1 if any differenes found 684# 0 if no differences found 685# -1 on error 686# 687 688# Sections deliberately not considered when comparing two ELF 689# binaries. Differences observed in these sections are not considered 690# significant where patch deliverable identification is concerned. 691sections_to_skip = [ ".SUNW_signature", 692 ".comment", 693 ".SUNW_ctf", 694 ".debug", 695 ".plt", 696 ".rela.bss", 697 ".rela.plt", 698 ".line", 699 ".note", 700 ".compcom", 701 ] 702 703sections_preferred = [ ".rodata.str1.8", 704 ".rodata.str1.1", 705 ".rodata", 706 ".data1", 707 ".data", 708 ".text", 709 ] 710 711def compareElfs(base, ptch, quiet) : 712 713 global logging 714 715 base_header = get_elfheader(base) 716 sections = base_header.keys() 717 718 ptch_header = get_elfheader(ptch) 719 e2_only_sections = ptch_header.keys() 720 721 e1_only_sections = [] 722 723 fileName = fnFormat(base) 724 725 # Derive the list of ELF sections found only in 726 # either e1 or e2. 727 for sect in sections : 728 if not sect in e2_only_sections : 729 e1_only_sections.append(sect) 730 else : 731 e2_only_sections.remove(sect) 732 733 if len(e1_only_sections) > 0 : 734 if quiet : 735 return 1 736 info(fileName); 737 if not logging : 738 return 1 739 740 slist = "" 741 for sect in e1_only_sections : 742 slist = slist + sect + "\t" 743 v_info("\nELF sections found in " + \ 744 base + " but not in " + ptch) 745 v_info("\n" + slist) 746 return 1 747 748 if len(e2_only_sections) > 0 : 749 if quiet : 750 return 1 751 752 info(fileName); 753 if not logging : 754 return 1 755 756 slist = "" 757 for sect in e2_only_sections : 758 slist = slist + sect + "\t" 759 v_info("\nELF sections found in " + \ 760 ptch + " but not in " + base) 761 v_info("\n" + slist) 762 return 1 763 764 # Look for preferred sections, and put those at the 765 # top of the list of sections to compare 766 for psect in sections_preferred : 767 if psect in sections : 768 sections.remove(psect) 769 sections.insert(0, psect) 770 771 # Compare ELF sections 772 first_section = True 773 for sect in sections : 774 775 if sect in sections_to_skip : 776 continue 777 778 s1 = extract_elf_section(base, sect); 779 s2 = extract_elf_section(ptch, sect); 780 781 if len(s1) != len (s2) or s1 != s2: 782 if not quiet: 783 sh_type = base_header[sect] 784 data = diff_elf_section(base, ptch, sect, \ 785 sh_type) 786 787 # If all ELF sections are being reported, then 788 # invoke difference() to flag the file name to 789 # stdout only once. Any other section differences 790 # should be logged to the results file directly 791 if not first_section : 792 log_difference(fileName, "ELF " + sect, data) 793 else : 794 difference(fileName, "ELF " + sect, data) 795 796 if not reportAllSects : 797 return 1 798 first_section = False 799 return 0 800 801##### 802# Archive object comparison 803# 804# Returns 1 if difference detected 805# 0 if no difference detected 806# -1 on error 807# 808def compareArchives(base, ptch, fileType) : 809 810 fileName = fnFormat(base) 811 812 # clear the temp directories 813 baseCmd = "rm -rf " + tmpDir1 + "*" 814 status, output = commands.getstatusoutput(baseCmd) 815 if status != 0 : 816 error(baseCmd + " failed: " + output) 817 return -1 818 819 ptchCmd = "rm -rf " + tmpDir2 + "*" 820 status, output = commands.getstatusoutput(ptchCmd) 821 if status != 0 : 822 error(ptchCmd + " failed: " + output) 823 return -1 824 825 # 826 # Be optimistic and first try a straight file compare 827 # as it will allow us to finish up quickly. 828 if compareBasic(base, ptch, True, fileType) == 0 : 829 return 0 830 831 # copy over the objects to the temp areas, and 832 # unpack them 833 baseCmd = "cp -fp " + base + " " + tmpDir1 834 status, output = commands.getstatusoutput(baseCmd) 835 if status != 0 : 836 error(baseCmd + " failed: " + output) 837 return -1 838 839 ptchCmd = "cp -fp " + ptch + " " + tmpDir2 840 status, output = commands.getstatusoutput(ptchCmd) 841 if status != 0 : 842 error(ptchCmd + " failed: " + output) 843 return -1 844 845 bname = string.split(fileName, '/')[-1] 846 if fileType == "Java Archive" : 847 baseCmd = "cd " + tmpDir1 + "; " + "jar xf " + bname + \ 848 "; rm -f " + bname + " META-INF/MANIFEST.MF" 849 ptchCmd = "cd " + tmpDir2 + "; " + "jar xf " + bname + \ 850 "; rm -f " + bname + " META-INF/MANIFEST.MF" 851 elif fileType == "ELF Object Archive" : 852 baseCmd = "cd " + tmpDir1 + "; " + "/usr/ccs/bin/ar x " + \ 853 bname + "; rm -f " + bname 854 ptchCmd = "cd " + tmpDir2 + "; " + "/usr/ccs/bin/ar x " + \ 855 bname + "; rm -f " + bname 856 else : 857 error("unexpected file type: " + fileType) 858 return -1 859 860 os.system(baseCmd) 861 os.system(ptchCmd) 862 863 baseFlist = list(findFiles(tmpDir1)) 864 ptchFlist = list(findFiles(tmpDir2)) 865 866 # Trim leading path off base/ptch file lists 867 flist = [] 868 for fn in baseFlist : 869 flist.append(str_prefix_trunc(fn, tmpDir1)) 870 baseFlist = flist 871 872 flist = [] 873 for fn in ptchFlist : 874 flist.append(str_prefix_trunc(fn, tmpDir2)) 875 ptchFlist = flist 876 877 for fn in ptchFlist : 878 if not fn in baseFlist : 879 difference(fileName, fileType, \ 880 fn + " added to " + fileName) 881 return 1 882 883 for fn in baseFlist : 884 if not fn in ptchFlist : 885 difference(fileName, fileType, \ 886 fn + " removed from " + fileName) 887 return 1 888 889 differs = compareOneFile((tmpDir1 + fn), (tmpDir2 + fn), True) 890 if differs : 891 difference(fileName, fileType, \ 892 fn + " in " + fileName + " differs") 893 return 1 894 return 0 895 896##### 897# (Basic) file comparison 898# 899# There's some special case code here for Javadoc HTML files 900# 901# Returns 1 if difference detected 902# 0 if no difference detected 903# -1 on error 904# 905def compareBasic(base, ptch, quiet, fileType) : 906 907 fileName = fnFormat(base); 908 909 if quiet and os.stat(base)[ST_SIZE] != os.stat(ptch)[ST_SIZE] : 910 return 1 911 912 try: 913 baseFile = open(base) 914 except: 915 error("could not open " + base) 916 return -1 917 try: 918 ptchFile = open(ptch) 919 except: 920 error("could not open " + ptch) 921 return -1 922 923 baseData = baseFile.read() 924 ptchData = ptchFile.read() 925 926 baseFile.close() 927 ptchFile.close() 928 929 needToSnip = False 930 if fileType == "HTML" : 931 needToSnip = True 932 toSnipBeginStr = "<!-- Generated by javadoc" 933 toSnipEndStr = "-->\n" 934 935 if needToSnip : 936 toSnipBegin = string.find(baseData, toSnipBeginStr) 937 if toSnipBegin != -1 : 938 toSnipEnd = string.find(baseData[toSnipBegin:], \ 939 toSnipEndStr) + \ 940 len(toSnipEndStr) 941 baseData = baseData[:toSnipBegin] + \ 942 baseData[toSnipBegin + toSnipEnd:] 943 ptchData = ptchData[:toSnipBegin] + \ 944 ptchData[toSnipBegin + toSnipEnd:] 945 946 if quiet : 947 if baseData != ptchData : 948 return 1 949 else : 950 if len(baseData) != len(ptchData) or baseData != ptchData : 951 diffs = diffData(baseData, ptchData) 952 difference(fileName, fileType, diffs) 953 return 1 954 return 0 955 956 957##### 958# Compare two objects by producing a data dump from 959# each object, and then comparing the dump data 960# 961# Returns: 1 if a difference is detected 962# 0 if no difference detected 963# -1 upon error 964# 965def compareByDumping(base, ptch, quiet, fileType) : 966 967 fileName = fnFormat(base); 968 969 if fileType == "Lint Library" : 970 baseCmd = lintdump_cmd + " -ir " + base + \ 971 " | egrep -v '(LINTOBJ|LINTMOD):'" + " > " + tmpFile1 972 ptchCmd = lintdump_cmd + " -ir " + ptch + \ 973 " | egrep -v '(LINTOBJ|LINTMOD):'" + " > " + tmpFile2 974 elif fileType == "Sqlite Database" : 975 baseCmd = "echo .dump | " + sqlite_cmd + base + " > " + \ 976 tmpFile1 977 ptchCmd = "echo .dump | " + sqlite_cmd + ptch + " > " + \ 978 tmpFile2 979 980 os.system(baseCmd) 981 os.system(ptchCmd) 982 983 try: 984 baseFile = open(tmpFile1) 985 except: 986 error("could not open: " + tmpFile1) 987 try: 988 ptchFile = open(tmpFile2) 989 except: 990 error("could not open: " + tmpFile2) 991 992 baseData = baseFile.read() 993 ptchData = ptchFile.read() 994 995 baseFile.close() 996 ptchFile.close() 997 998 if len(baseData) != len(ptchData) or baseData != ptchData : 999 if not quiet : 1000 data = diffFileData(tmpFile1, tmpFile2); 1001 difference(fileName, fileType, data) 1002 return 1 1003 return 0 1004 1005##### 1006# Compare two elfsign activation files. This ignores the activation 1007# files themselves and reports a difference if and only if the 1008# corresponding base files are different. 1009# 1010# Returns 1 if difference detected 1011# 0 if no difference detected 1012# -1 on error 1013# 1014def compareActivation(base, ptch, quiet, fileType) : 1015 1016 fileName = fnFormat(base) 1017 1018 # Drop the .esa suffix from both filenames. 1019 base = base[0:base.rfind('.esa')] 1020 ptch = ptch[0:ptch.rfind('.esa')] 1021 1022 result = compareOneFile(base, ptch, True) 1023 if result == -1 : 1024 error("unable to compare " + fileName) 1025 elif result == 1 : 1026 if not quiet : 1027 difference(fileName, fileType, \ 1028 "change in corresponding ELF file") 1029 1030 return result 1031 1032##### 1033# Compare two objects. Detect type changes. 1034# Vector off to the appropriate type specific 1035# compare routine based on the type. 1036# 1037def compareOneFile(base, ptch, quiet) : 1038 1039 # Verify the file types. 1040 # If they are different, indicate this and move on 1041 btype = getTheFileType(base) 1042 ptype = getTheFileType(ptch) 1043 1044 if btype == 'Error' or ptype == 'Error' : 1045 return -1 1046 1047 fileName = fnFormat(base) 1048 1049 if (btype != ptype) : 1050 if not quiet : 1051 difference(fileName, "file type", btype + " to " + ptype) 1052 return 1 1053 else : 1054 fileType = btype 1055 1056 if (fileType == 'ELF') : 1057 return compareElfs(base, ptch, quiet) 1058 1059 elif (fileType == 'Java Archive' or fileType == 'ELF Object Archive') : 1060 return compareArchives(base, ptch, fileType) 1061 1062 elif (fileType == 'HTML') : 1063 return compareBasic(base, ptch, quiet, fileType) 1064 1065 elif ( fileType == 'Lint Library' ) : 1066 return compareByDumping(base, ptch, quiet, fileType) 1067 1068 elif ( fileType == 'Sqlite Database' ) : 1069 return compareByDumping(base, ptch, quiet, fileType) 1070 1071 elif ( fileType == 'Elfsign Activation' ) : 1072 return compareActivation(base, ptch, quiet, fileType) 1073 1074 else : 1075 # it has to be some variety of text file 1076 return compareBasic(base, ptch, quiet, fileType) 1077 1078# Cleanup and self-terminate 1079def cleanup(ret) : 1080 1081 if len(tmpDir1) > 0 and len(tmpDir2) > 0 : 1082 1083 baseCmd = "rm -rf " + tmpDir1 1084 ptchCmd = "rm -rf " + tmpDir2 1085 1086 os.system(baseCmd) 1087 os.system(ptchCmd) 1088 1089 if logging : 1090 log.close() 1091 1092 sys.exit(ret) 1093 1094def main() : 1095 1096 # Log file handle 1097 global log 1098 1099 # Globals relating to command line options 1100 global logging, vdiffs, reportAllSects 1101 1102 # Named temporary files / directories 1103 global tmpDir1, tmpDir2, tmpFile1, tmpFile2 1104 1105 # Command paths 1106 global lintdump_cmd, elfdump_cmd, dump_cmd, dis_cmd, od_cmd, diff_cmd, sqlite_cmd 1107 1108 # Default search path 1109 global wsdiff_path 1110 1111 # Essentially "uname -p" 1112 global arch 1113 1114 # Some globals need to be initialized 1115 logging = vdiffs = reportAllSects = False 1116 1117 1118 # Process command line arguments 1119 # Return values are returned from args() in alpha order 1120 # (Yes, python functions can return multiple values (ewww)) 1121 # Note that args() also set the globals: 1122 # logging to True if verbose logging (to a file) was enabled 1123 # vdiffs to True if logged differences aren't to be truncated 1124 # reportAllSects to True if all ELF section differences are to be reported 1125 # 1126 baseRoot, fileNamesFile, localTools, ptchRoot, results = args() 1127 1128 # 1129 # Set up the results/log file 1130 # 1131 if logging : 1132 try: 1133 log = open(results, "w") 1134 except: 1135 logging = False 1136 error("failed to open log file: " + log) 1137 sys.exit(1) 1138 1139 dateTimeStr= "# %d/%d/%d at %d:%d:%d" % time.localtime()[:6] 1140 v_info("# This file was produced by wsdiff") 1141 v_info(dateTimeStr) 1142 1143 # 1144 # Build paths to the tools required tools 1145 # 1146 # Try to look for tools in $SRC/tools if the "-t" option 1147 # was specified 1148 # 1149 arch = commands.getoutput("uname -p") 1150 if localTools : 1151 try: 1152 src = os.environ['SRC'] 1153 except: 1154 error("-t specified, but $SRC not set. Cannot find $SRC/tools") 1155 src = "" 1156 if len(src) > 0 : 1157 wsdiff_path.insert(0, src + "/tools/proto/opt/onbld/bin") 1158 1159 lintdump_cmd = find_tool("lintdump") 1160 elfdump_cmd = find_tool("elfdump") 1161 dump_cmd = find_tool("dump") 1162 od_cmd = find_tool("od") 1163 dis_cmd = find_tool("dis") 1164 diff_cmd = find_tool("diff") 1165 sqlite_cmd = find_tool("sqlite") 1166 1167 # 1168 # validate the base and patch paths 1169 # 1170 if baseRoot[-1] != '/' : 1171 baseRoot += '/' 1172 1173 if ptchRoot[-1] != '/' : 1174 ptchRoot += '/' 1175 1176 if not os.path.exists(baseRoot) : 1177 error("old proto area: " + baseRoot + " does not exist") 1178 sys.exit(1) 1179 1180 if not os.path.exists(ptchRoot) : 1181 error("new proto area: " + ptchRoot + \ 1182 " does not exist") 1183 sys.exit(1) 1184 1185 # 1186 # log some information identifying the run 1187 # 1188 v_info("Old proto area: " + baseRoot) 1189 v_info("New proto area: " + ptchRoot) 1190 v_info("Results file: " + results + "\n") 1191 1192 # 1193 # Set up the temporary directories / files 1194 # Could use python's tmpdir routines, but these should 1195 # be easier to identify / keep around for debugging 1196 pid = os.getpid() 1197 tmpDir1 = "/tmp/wsdiff_tmp1_" + str(pid) + "/" 1198 tmpDir2 = "/tmp/wsdiff_tmp2_" + str(pid) + "/" 1199 if not os.path.exists(tmpDir1) : 1200 os.makedirs(tmpDir1) 1201 if not os.path.exists(tmpDir2) : 1202 os.makedirs(tmpDir2) 1203 1204 tmpFile1 = tmpDir1 + "f1" 1205 tmpFile2 = tmpDir2 + "f2" 1206 1207 # Derive a catalog of new, deleted, and to-be-compared objects 1208 # either from the specified base and patch proto areas, or from 1209 # from an input file list 1210 newOrDeleted = False 1211 1212 if fileNamesFile != "" : 1213 changedFiles, newFiles, deletedFiles = \ 1214 flistCatalog(baseRoot, ptchRoot, fileNamesFile) 1215 else : 1216 changedFiles, newFiles, deletedFiles = protoCatalog(baseRoot, ptchRoot) 1217 1218 if len(newFiles) > 0 : 1219 newOrDeleted = True 1220 info("\nNew objects found: ") 1221 1222 for fn in newFiles : 1223 info(fnFormat(fn)) 1224 1225 if len(deletedFiles) > 0 : 1226 newOrDeleted = True 1227 info("\nObjects removed: ") 1228 1229 for fn in deletedFiles : 1230 info(fnFormat(fn)) 1231 1232 if newOrDeleted : 1233 info("\nChanged objects: "); 1234 1235 1236 # Here's where all the heavy lifting happens 1237 # Perform a comparison on each object appearing in 1238 # both proto areas. compareOneFile will examine the 1239 # file types of each object, and will vector off to 1240 # the appropriate comparison routine, where the compare 1241 # will happen, and any differences will be reported / logged 1242 for fn in changedFiles : 1243 base = baseRoot + fn 1244 ptch = ptchRoot + fn 1245 1246 compareOneFile(base, ptch, False) 1247 1248 # We're done, cleanup. 1249 cleanup(0) 1250 1251if __name__ == '__main__' : 1252 try: 1253 main() 1254 except KeyboardInterrupt : 1255 cleanup(1); 1256 1257 1258