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