diff options
author | esaxe <none@none> | 2006-08-11 18:11:49 -0700 |
---|---|---|
committer | esaxe <none@none> | 2006-08-11 18:11:49 -0700 |
commit | 96ccc8cb05e8790fcc24931dcb155b76e810295c (patch) | |
tree | 56aca851486d7a2dd9066999fe275e83bcfc5891 /usr/src/tools/scripts/wsdiff.py | |
parent | 5988135d82ecba94b97669a9f2b7016b58ad691e (diff) | |
download | illumos-joyent-96ccc8cb05e8790fcc24931dcb155b76e810295c.tar.gz |
5016095 nightly should help determine what really changes from build to build
6404876 uses of __DATE__ / __TIME__ should be purged from ON
6404908 ON builds should employ a static globalization prefix
Diffstat (limited to 'usr/src/tools/scripts/wsdiff.py')
-rwxr-xr-x | usr/src/tools/scripts/wsdiff.py | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/usr/src/tools/scripts/wsdiff.py b/usr/src/tools/scripts/wsdiff.py new file mode 100755 index 0000000000..113365475d --- /dev/null +++ b/usr/src/tools/scripts/wsdiff.py @@ -0,0 +1,1216 @@ +#!/usr/sfw/bin/python +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" + +# +# wsdiff(1) is a tool that can be used to determine which compiled objects +# have changed as a result of a given source change. Developers backporting +# new features, RFEs and bug fixes need to be able to identify the set of +# patch deliverables necessary for feature/fix realization on a patched system. +# +# The tool works by comparing objects in two trees/proto areas (one build with, +# and without the source changes. +# +# Using wsdiff(1) is fairly simple: +# - Bringover to a fresh workspace +# - Perform a full non-debug build (clobber if workspace isn't fresh) +# - Move the proto area aside, call it proto.old, or something. +# - Integrate your changes to the workspace +# - Perform another full non-debug clobber build. +# - Use wsdiff(1) to see what changed: +# $ wsdiff proto.old proto +# +# By default, wsdiff will print the list of changed objects / deliverables to +# stdout. If a results file is specified via -r, the list of differing objects, +# and details about why wsdiff(1) thinks they are different will be logged to +# the results file. +# +# By invoking nightly(1) with the -w option to NIGHTLY_FLAGS, nightly(1) will use +# wsdiff(1) to report on what objects changed since the last build. +# +# For patch deliverable purposes, it's advised to have nightly do a clobber, +# non-debug build. +# +# Think about the results. Was something flagged that you don't expect? Go look +# at the results file to see details about the differences. +# +# Use the -i option in conjunction with -v and -V to dive deeper and have wsdiff(1) +# report with more verbosity. +# +# Usage: wsdiff [-vVt] [-r results ] [-i filelist ] old new +# +# Where "old" is the path to the proto area build without the changes, and +# "new" is the path to the proto area built with the changes. The following +# options are supported: +# +# -v Do not truncate observed diffs in results +# -V Log *all* ELF sect diffs vs. logging the first diff found +# -t Use onbld tools in $SRC/tools +# -r Log results and observed differences +# -i Tell wsdiff which objects to compare via an input file list + +import datetime, fnmatch, getopt, profile, os, popen2, commands +import re, select, string, struct, sys, tempfile, time +from stat import * + +# Human readable diffs truncated by default if longer than this +# Specifying -v on the command line will override +diffs_sz_thresh = 4096 + +# Default search path for wsdiff +wsdiff_path = [ "/usr/bin", + "/usr/ccs/bin", + "/lib/svc/bin", + "/opt/onbld/bin" ] + +# These are objects that wsdiff will notice look different, but will not report. +# Existence of an exceptions list, and adding things here is *dangerous*, +# and therefore the *only* reasons why anything would be listed here is because +# the objects do not build deterministically, yet we *cannot* fix this. +# +# These perl libraries use __DATE__ and therefore always look different. +# Ideally, we would purge use the use of __DATE__ from the source, but because +# this is source we wish to distribute with Solaris "unchanged", we cannot modify. +# +wsdiff_exceptions = [ "usr/perl5/5.8.4/lib/sun4-solaris-64int/CORE/libperl.so.1", + "usr/perl5/5.6.1/lib/sun4-solaris-64int/CORE/libperl.so.1", + "usr/perl5/5.8.4/lib/i86pc-solaris-64int/CORE/libperl.so.1", + "usr/perl5/5.6.1/lib/i86pc-solaris-64int/CORE/libperl.so.1" + ] + +##### +# Logging routines +# + +# Informational message to be printed to the screen, and the log file +def info(msg) : + + print >> sys.stdout, msg + if logging : + print >> log, msg + sys.stdout.flush() + +# Error message to be printed to the screen, and the log file +def error(msg) : + + print >> sys.stderr, "ERROR:", msg + sys.stderr.flush() + if logging : + print >> log, "ERROR:", msg + log.flush() + +# Informational message to be printed only to the log, if there is one. +def v_info(msg) : + + if logging : + print >> log, msg + log.flush() + +# +# Flag a detected file difference +# Display the fileName to stdout, and log the difference +# +def difference(f, dtype, diffs) : + + if f in wsdiff_exceptions : + return + + print >> sys.stdout, f + sys.stdout.flush() + + log_difference(f, dtype, diffs) + +# +# Do the actual logging of the difference to the results file +# +def log_difference(f, dtype, diffs) : + if logging : + print >> log, f + print >> log, "NOTE:", dtype, "difference detected." + + difflen = len(diffs) + if difflen > 0 : + print >> log + + if not vdiffs and difflen > diffs_sz_thresh : + print >> log, diffs[:diffs_sz_thresh] + print >> log, \ + "... truncated due to length: " \ + "use -v to override ..." + else : + print >> log, diffs + print >> log, "\n" + log.flush() + + +##### +# diff generating routines +# + +# +# Return human readable diffs from two temporary files +# +def diffFileData(tmpf1, tmpf2) : + + # Filter the data through od(1) if the data is detected + # as being binary + if isBinary(tmpf1) or isBinary(tmpf2) : + tmp_od1 = tmpf1 + ".od" + tmp_od2 = tmpf2 + ".od" + + cmd = od_cmd + " -c -t x4" + " " + tmpf1 + " > " + tmp_od1 + os.system(cmd) + cmd = od_cmd + " -c -t x4" + " " + tmpf2 + " > " + tmp_od2 + os.system(cmd) + + tmpf1 = tmp_od1 + tmpf2 = tmp_od2 + + data = commands.getoutput(diff_cmd + " " + tmpf1 + " " + tmpf2) + + return data + +# +# Return human readable diffs betweeen two datasets +# +def diffData(d1, d2) : + + global tmpFile1 + global tmpFile2 + + try: + fd1 = open(tmpFile1, "w") + except: + error("failed to open: " + tmpFile1) + cleanup(1) + try: + fd2 = open(tmpFile2, "w") + except: + error("failed to open: " + tmpFile2) + cleanup(1) + + fd1.write(d1) + fd2.write(d2) + fd1.close() + fd2.close() + + return diffFileData(tmpFile1, tmpFile2) + +##### +# Misc utility functions +# + +# Prune off the leading prefix from string s +def str_prefix_trunc(s, prefix) : + snipLen = len(prefix) + return s[snipLen:] + +# +# Prune off leading proto path goo (if there is one) to yield +# the deliverable's eventual path relative to root +# e.g. proto.base/root_sparc/usr/src/cmd/prstat => usr/src/cmd/prstat +# +def fnFormat(fn) : + root_arch_str = "root_" + arch + + pos = fn.find(root_arch_str) + if pos == -1 : + return fn + + pos = fn.find("/", pos) + if pos == -1 : + return fn + + return fn[pos + 1:] + +##### +# Usage / argument processing +# + +# +# Display usage message +# +def usage() : + sys.stdout.flush() + print >> sys.stderr, """Usage: wsdiff [-vVt] [-r results ] [-i filelist ] old new + -v Do not truncate observed diffs in results + -V Log *all* ELF sect diffs vs. logging the first diff found + -t Use onbld tools in $SRC/tools + -r Log results and observed differences + -i Tell wsdiff which objects to compare via an input file list""" + sys.exit(1) + +# +# Process command line options +# +def args() : + + global logging + global vdiffs + global reportAllSects + + validOpts = 'i:r:vVt?' + + baseRoot = "" + ptchRoot = "" + fileNamesFile = "" + results = "" + localTools = False + + # getopt.getopt() returns: + # an option/value tuple + # a list of remaining non-option arguments + # + # A correct wsdiff invocation will have exactly two non option + # arguments, the paths to the base (old), ptch (new) proto areas + try: + optlist, args = getopt.getopt(sys.argv[1:], validOpts) + except getopt.error, val: + usage() + + if len(args) != 2 : + usage(); + + for opt,val in optlist : + if opt == '-i' : + fileNamesFile = val + elif opt == '-r' : + results = val + logging = True + elif opt == '-v' : + vdiffs = True + elif opt == '-V' : + reportAllSects = True + elif opt == '-t': + localTools = True + else: + usage() + + baseRoot = args[0] + ptchRoot = args[1] + + if len(baseRoot) == 0 or len(ptchRoot) == 0 : + usage() + + if logging and len(results) == 0 : + usage() + + if vdiffs and not logging : + error("The -v option requires a results file (-r)") + sys.exit(1) + + if reportAllSects and not logging : + error("The -V option requires a results file (-r)") + sys.exit(1) + + # alphabetical order + return baseRoot, fileNamesFile, localTools, ptchRoot, results + +##### +# File identification +# + +# +# Identify the file type. +# If it's not ELF, use the file extension to identify +# certain file types that require special handling to +# compare. Otherwise just return a basic "ASCII" type. +# +def getTheFileType(f) : + + extensions = { 'a' : 'ELF Object Archive', + 'jar' : 'Java Archive', + 'html' : 'HTML', + 'ln' : 'Lint Library', + 'db' : 'Sqlite Database' } + + if os.stat(f)[ST_SIZE] == 0 : + return 'ASCII' + + if isELF(f) == 1 : + return 'ELF' + + fnamelist = f.split('.') + if len(fnamelist) > 1 : # Test the file extension + extension = fnamelist[-1] + if extension in extensions.keys(): + return extensions[extension] + + return 'ASCII' + +# +# Return non-zero if "f" is an ELF file +# +elfmagic = '\177ELF' +def isELF(f) : + try: + fd = open(f) + except: + error("failed to open: " + f) + return 0 + magic = fd.read(len(elfmagic)) + fd.close() + + if magic == elfmagic : + return 1 + return 0 + +# +# Return non-zero is "f" is binary. +# Consider the file to be binary if it contains any null characters +# +def isBinary(f) : + try: + fd = open(f) + except: + error("failed to open: " + f) + return 0 + s = fd.read() + fd.close() + + if s.find('\0') == -1 : + return 0 + else : + return 1 + +##### +# Directory traversal and file finding +# + +# +# Return a sorted list of files found under the specified directory +# +def findFiles(d) : + for path, subdirs, files in os.walk(d) : + files.sort() + for name in files : + yield os.path.join(path, name) + +# +# Examine all files in base, ptch +# +# Return a list of files appearing in both proto areas, +# a list of new files (files found only in ptch) and +# a list of deleted files (files found only in base) +# +def protoCatalog(base, ptch) : + compFiles = [] # List of files in both proto areas + ptchList = [] # List of file in patch proto area + + newFiles = [] # New files detected + deletedFiles = [] # Deleted files + + baseFilesList = list(findFiles(base)) + baseStringLength = len(base) + + ptchFilesList = list(findFiles(ptch)) + ptchStringLength = len(ptch) + + # Inventory files in the base proto area + for fn in baseFilesList : + if os.path.islink(fn) : + continue + + fileName = fn[baseStringLength:] + compFiles.append(fileName) + + # Inventory files in the patch proto area + for fn in ptchFilesList : + if os.path.islink(fn) : + continue + + fileName = fn[ptchStringLength:] + ptchList.append(fileName) + + # Deleted files appear in the base area, but not the patch area + for fileName in compFiles : + if not fileName in ptchList : + deletedFiles.append(fileName) + + # Eliminate "deleted" files from the list of objects appearing + # in both the base and patch proto areas + for fileName in deletedFiles : + try: + compFiles.remove(fileName) + except: + error("filelist.remove() failed") + + # New files appear in the patch area, but not the base + for fileName in ptchList : + if not fileName in compFiles : + newFiles.append(fileName) + + return compFiles, newFiles, deletedFiles + +# +# Examine the files listed in the input file list +# +# Return a list of files appearing in both proto areas, +# a list of new files (files found only in ptch) and +# a list of deleted files (files found only in base) +# +def flistCatalog(base, ptch, flist) : + compFiles = [] # List of files in both proto areas + newFiles = [] # New files detected + deletedFiles = [] # Deleted files + + try: + fd = open(flist, "r") + except: + error("could not open: " + flist) + cleanup(1) + + files = [] + files = fd.readlines() + + for f in files : + ptch_present = True + base_present = True + + if f == '\n' : + continue + + # the fileNames have a trailing '\n' + f = f.rstrip() + + # The objects in the file list have paths relative + # to $ROOT or to the base/ptch directory specified on + # the command line. + # If it's relative to $ROOT, we'll need to add back the + # root_`uname -p` goo we stripped off in fnFormat() + if os.path.exists(base + f) : + fn = f; + elif os.path.exists(base + "root_" + arch + "/" + f) : + fn = "root_" + arch + "/" + f + else : + base_present = False + + if base_present : + if not os.path.exists(ptch + fn) : + ptch_present = False + else : + if os.path.exists(ptch + f) : + fn = f + elif os.path.exists(ptch + "root_" + arch + "/" + f) : + fn = "root_" + arch + "/" + f + else : + ptch_present = False + + if os.path.islink(base + fn) : # ignore links + base_present = False + if os.path.islink(ptch + fn) : + ptch_present = False + + if base_present and ptch_present : + compFiles.append(fn) + elif base_present : + deletedFiles.append(fn) + elif ptch_present : + newFiles.append(fn) + else : + if os.path.islink(base + fn) and os.path.islink(ptch + fn) : + continue + error(f + " in file list, but not in either tree. Skipping...") + + return compFiles, newFiles, deletedFiles + + +# +# Build a fully qualified path to an external tool/utility. +# Consider the default system locations. For onbld tools, if +# the -t option was specified, we'll try to use built tools in $SRC tools, +# and otherwise, we'll fall back on /opt/onbld/ +# +def find_tool(tool) : + + # First, check what was passed + if os.path.exists(tool) : + return tool + + # Next try in wsdiff path + for pdir in wsdiff_path : + location = pdir + "/" + tool + if os.path.exists(location) : + return location + " " + + location = pdir + "/" + arch + "/" + tool + if os.path.exists(location) : + return location + " " + + error("Could not find path to: " + tool); + sys.exit(1); + + +##### +# ELF file comparison helper routines +# + +# +# Return a dictionary of ELF section types keyed by section name +# +def get_elfheader(f) : + + header = {} + + hstring = commands.getoutput(elfdump_cmd + " -c " + f) + + if len(hstring) == 0 : + error("Failed to dump ELF header for " + f) + return + + # elfdump(1) dumps the section headers with the section name + # following "sh_name:", and the section type following "sh_type:" + sections = hstring.split("Section Header") + for sect in sections : + datap = sect.find("sh_name:"); + if datap == -1 : + continue + section = sect[datap:].split()[1] + datap = sect.find("sh_type:"); + if datap == -1 : + error("Could not get type for sect: " + section + \ + " in " + f) + sh_type = sect[datap:].split()[2] + header[section] = sh_type + + return header + +# +# Extract data in the specified ELF section from the given file +# +def extract_elf_section(f, section) : + + data = commands.getoutput(dump_cmd + " -sn " + section + " " + f) + + if len(data) == 0 : + error(cmd + " yielded no data") + return + + # dump(1) displays the file name to start... + # get past it to the data itself + dbegin = data.find(":") + 1 + data = data[dbegin:]; + + return (data) + +# +# Return a (hopefully meaningful) human readable set of diffs +# for the specified ELF section between f1 and f2 +# +# Depending on the section, various means for dumping and diffing +# the data may be employed. +# +text_sections = [ '.text', '.init', '.fini' ] +def diff_elf_section(f1, f2, section, sh_type) : + + if (sh_type == "SHT_RELA") : # sh_type == SHT_RELA + cmd1 = elfdump_cmd + " -r " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -r " + f2 + " > " + tmpFile2 + elif (section == ".group") : + cmd1 = elfdump_cmd + " -g " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -g " + f2 + " > " + tmpFile2 + elif (section == ".hash") : + cmd1 = elfdump_cmd + " -h " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -h " + f2 + " > " + tmpFile2 + elif (section == ".dynamic") : + cmd1 = elfdump_cmd + " -d " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -d " + f2 + " > " + tmpFile2 + elif (section == ".got") : + cmd1 = elfdump_cmd + " -G " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -G " + f2 + " > " + tmpFile2 + elif (section == ".SUNW_cap") : + cmd1 = elfdump_cmd + " -H " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -H " + f2 + " > " + tmpFile2 + elif (section == ".interp") : + cmd1 = elfdump_cmd + " -i " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -i " + f2 + " > " + tmpFile2 + elif (section == ".symtab" or section == ".dynsym") : + cmd1 = elfdump_cmd + " -s -N " + section + " " + f1 + " > " + tmpFile1 + cmd2 = elfdump_cmd + " -s -N " + section + " " + f2 + " > " + tmpFile2 + elif (section in text_sections) : + # dis sometimes complains when it hits something it doesn't + # know how to disassemble. Just ignore it, as the output + # being generated here is human readable, and we've already + # correctly flagged the difference. + cmd1 = dis_cmd + " -t " + section + " " + f1 + \ + " 2>/dev/null | grep -v disassembly > " + tmpFile1 + cmd2 = dis_cmd + " -t " + section + " " + f2 + \ + " 2>/dev/null | grep -v disassembly > " + tmpFile2 + else : + cmd1 = elfdump_cmd + " -w " + tmpFile1 + " -N " + \ + section + " " + f1 + cmd2 = elfdump_cmd + " -w " + tmpFile2 + " -N " + \ + section + " " + f2 + + os.system(cmd1) + os.system(cmd2) + + data = diffFileData(tmpFile1, tmpFile2) + + return (data) + +# +# compare the relevant sections of two ELF binaries +# and report any differences +# +# Returns: 1 if any differenes found +# 0 if no differences found +# -1 on error +# + +# Sections deliberately not considered when comparing two ELF +# binaries. Differences observed in these sections are not considered +# significant where patch deliverable identification is concerned. +sections_to_skip = [ ".SUNW_signature", + ".comment", + ".SUNW_ctf", + ".debug", + ".plt", + ".rela.bss", + ".rela.plt", + ".line", + ".note", + ] + +sections_preferred = [ ".rodata.str1.8", + ".rodata.str1.1", + ".rodata", + ".data1", + ".data", + ".text", + ] + +def compareElfs(base, ptch, quiet) : + + global logging + + base_header = get_elfheader(base) + sections = base_header.keys() + + ptch_header = get_elfheader(ptch) + e2_only_sections = ptch_header.keys() + + e1_only_sections = [] + + fileName = fnFormat(base) + + # Derive the list of ELF sections found only in + # either e1 or e2. + for sect in sections : + if not sect in e2_only_sections : + e1_only_sections.append(sect) + else : + e2_only_sections.remove(sect) + + if len(e1_only_sections) > 0 : + if quiet : + return 1 + info(fileName); + if not logging : + return 1 + + slist = "" + for sect in e1_only_sections : + slist = slist + sect + "\t" + v_info("\nELF sections found in " + \ + base + " but not in " + ptch) + v_info("\n" + slist) + return 1 + + if len(e2_only_sections) > 0 : + if quiet : + return 1 + + info(fileName); + if not logging : + return 1 + + slist = "" + for sect in e2_only_sections : + slist = slist + sect + "\t" + v_info("\nELF sections found in " + \ + ptch + " but not in " + base) + v_info("\n" + slist) + return 1 + + # Look for preferred sections, and put those at the + # top of the list of sections to compare + for psect in sections_preferred : + if psect in sections : + sections.remove(psect) + sections.insert(0, psect) + + # Compare ELF sections + first_section = True + for sect in sections : + + if sect in sections_to_skip : + continue + + s1 = extract_elf_section(base, sect); + s2 = extract_elf_section(ptch, sect); + + if len(s1) != len (s2) or s1 != s2: + if not quiet: + sh_type = base_header[sect] + data = diff_elf_section(base, ptch, sect, \ + sh_type) + + # If all ELF sections are being reported, then + # invoke difference() to flag the file name to + # stdout only once. Any other section differences + # should be logged to the results file directly + if not first_section : + log_difference(fileName, "ELF " + sect, data) + else : + difference(fileName, "ELF " + sect, data) + + if not reportAllSects : + return 1 + first_section = False + return 0 + +##### +# Archive object comparison +# +# Returns 1 if difference detected +# 0 if no difference detected +# -1 on error +# +def compareArchives(base, ptch, fileType) : + + fileName = fnFormat(base) + + # clear the temp directories + baseCmd = "rm -rf " + tmpDir1 + "*" + status, output = commands.getstatusoutput(baseCmd) + if status != 0 : + error(baseCmd + " failed: " + output) + return -1 + + ptchCmd = "rm -rf " + tmpDir2 + "*" + status, output = commands.getstatusoutput(ptchCmd) + if status != 0 : + error(ptchCmd + " failed: " + output) + return -1 + + # + # Be optimistic and first try a straight file compare + # as it will allow us to finish up quickly. + if compareBasic(base, ptch, True, fileType) == 0 : + return 0 + + # copy over the objects to the temp areas, and + # unpack them + baseCmd = "cp -fp " + base + " " + tmpDir1 + status, output = commands.getstatusoutput(baseCmd) + if status != 0 : + error(baseCmd + " failed: " + output) + return -1 + + ptchCmd = "cp -fp " + ptch + " " + tmpDir2 + status, output = commands.getstatusoutput(ptchCmd) + if status != 0 : + error(ptchCmd + " failed: " + output) + return -1 + + bname = string.split(fileName, '/')[-1] + if fileType == "Java Archive" : + baseCmd = "cd " + tmpDir1 + "; " + "jar xf " + bname + \ + "; rm -f " + bname + " META-INF/MANIFEST.MF" + ptchCmd = "cd " + tmpDir2 + "; " + "jar xf " + bname + \ + "; rm -f " + bname + " META-INF/MANIFEST.MF" + elif fileType == "ELF Object Archive" : + baseCmd = "cd " + tmpDir1 + "; " + "/usr/ccs/bin/ar x " + \ + bname + "; rm -f " + bname + ptchCmd = "cd " + tmpDir2 + "; " + "/usr/ccs/bin/ar x " + \ + bname + "; rm -f " + bname + else : + error("unexpected file type: " + fileType) + return -1 + + os.system(baseCmd) + os.system(ptchCmd) + + baseFlist = list(findFiles(tmpDir1)) + ptchFlist = list(findFiles(tmpDir2)) + + # Trim leading path off base/ptch file lists + flist = [] + for fn in baseFlist : + flist.append(str_prefix_trunc(fn, tmpDir1)) + baseFlist = flist + + flist = [] + for fn in ptchFlist : + flist.append(str_prefix_trunc(fn, tmpDir2)) + ptchFlist = flist + + for fn in ptchFlist : + if not fn in baseFlist : + difference(fileName, fileType, \ + fn + " added to " + fileName) + return 1 + + for fn in baseFlist : + if not fn in ptchFlist : + difference(fileName, fileType, \ + fn + " removed from " + fileName) + return 1 + + differs = compareOneFile((tmpDir1 + fn), (tmpDir2 + fn), True) + if differs : + difference(fileName, fileType, \ + fn + " in " + fileName + " differs") + return 1 + return 0 + +##### +# (Basic) file comparison +# +# There's some special case code here for Javadoc HTML files +# +# Returns 1 if difference detected +# 0 if no difference detected +# -1 on error +# +def compareBasic(base, ptch, quiet, fileType) : + + fileName = fnFormat(base); + + if quiet and os.stat(base)[ST_SIZE] != os.stat(ptch)[ST_SIZE] : + return 1 + + try: + baseFile = open(base) + except: + error("could not open " + base) + return -1 + try: + ptchFile = open(ptch) + except: + error("could not open " + ptch) + return -1 + + baseData = baseFile.read() + ptchData = ptchFile.read() + + baseFile.close() + ptchFile.close() + + needToSnip = False + if fileType == "HTML" : + needToSnip = True + toSnipBeginStr = "<!-- Generated by javadoc" + toSnipEndStr = "-->\n" + + if needToSnip : + toSnipBegin = string.find(baseData, toSnipBeginStr) + if toSnipBegin != -1 : + toSnipEnd = string.find(baseData[toSnipBegin:], \ + toSnipEndStr) + \ + len(toSnipEndStr) + baseData = baseData[:toSnipBegin] + \ + baseData[toSnipBegin + toSnipEnd:] + ptchData = ptchData[:toSnipBegin] + \ + ptchData[toSnipBegin + toSnipEnd:] + + if quiet : + if baseData != ptchData : + return 1 + else : + if len(baseData) != len(ptchData) or baseData != ptchData : + diffs = diffData(baseData, ptchData) + difference(fileName, fileType, diffs) + return 1 + return 0 + + +##### +# Compare two objects by producing a data dump from +# each object, and then comparing the dump data +# +# Returns: 1 if a difference is detected +# 0 if no difference detected +# -1 upon error +# +def compareByDumping(base, ptch, quiet, fileType) : + + fileName = fnFormat(base); + + if fileType == "Lint Library" : + baseCmd = lintdump_cmd + " -ir " + base + \ + " | grep -v LINTLIB:" + " > " + tmpFile1 + ptchCmd = lintdump_cmd + " -ir " + ptch + \ + " | grep -v LINTLIB:" + " > " + tmpFile2 + elif fileType == "Sqlite Database" : + baseCmd = "echo .dump | " + sqlite_cmd + base + " > " + \ + tmpFile1 + ptchCmd = "echo .dump | " + sqlite_cmd + ptch + " > " + \ + tmpFile2 + + os.system(baseCmd) + os.system(ptchCmd) + + try: + baseFile = open(tmpFile1) + except: + error("could not open: " + tmpFile1) + try: + ptchFile = open(tmpFile2) + except: + error("could not open: " + tmpFile2) + + baseData = baseFile.read() + ptchData = ptchFile.read() + + baseFile.close() + ptchFile.close() + + if len(baseData) != len(ptchData) or baseData != ptchData : + if not quiet : + data = diffFileData(tmpFile1, tmpFile2); + difference(fileName, fileType, data) + return 1 + return 0 + +##### +# Compare two objects. Detect type changes. +# Vector off to the appropriate type specific +# compare routine based on the type. +# +def compareOneFile(base, ptch, quiet) : + + # Verify the file types. + # If they are different, indicate this and move on + btype = getTheFileType(base) + ptype = getTheFileType(ptch) + + fileName = fnFormat(base) + + if (btype != ptype) : + difference(fileName, "file type", btype + " to " + ptype) + return 1 + else : + fileType = btype + + if (fileType == 'ELF') : + return compareElfs(base, ptch, quiet) + + elif (fileType == 'Java Archive' or fileType == 'ELF Object Archive') : + return compareArchives(base, ptch, fileType) + + elif (fileType == 'HTML') : + return compareBasic(base, ptch, quiet, fileType) + + elif ( fileType == 'Lint Library' ) : + return compareByDumping(base, ptch, quiet, fileType) + + elif ( fileType == 'Sqlite Database' ) : + return compareByDumping(base, ptch, quiet, fileType) + else : + # it has to be some variety of text file + return compareBasic(base, ptch, quiet, fileType) + +# Cleanup and self-terminate +def cleanup(ret) : + + if len(tmpDir1) > 0 and len(tmpDir2) > 0 : + + baseCmd = "rm -rf " + tmpDir1 + ptchCmd = "rm -rf " + tmpDir2 + + os.system(baseCmd) + os.system(ptchCmd) + + if logging : + log.close() + + sys.exit(ret) + +def main() : + + # Log file handle + global log + + # Globals relating to command line options + global logging, vdiffs, reportAllSects + + # Named temporary files / directories + global tmpDir1, tmpDir2, tmpFile1, tmpFile2 + + # Command paths + global lintdump_cmd, elfdump_cmd, dump_cmd, dis_cmd, od_cmd, diff_cmd, sqlite_cmd + + # Default search path + global wsdiff_path + + # Essentially "uname -p" + global arch + + # Some globals need to be initialized + logging = vdiffs = reportAllSects = False + + + # Process command line arguments + # Return values are returned from args() in alpha order + # (Yes, python functions can return multiple values (ewww)) + # Note that args() also set the globals: + # logging to True if verbose logging (to a file) was enabled + # vdiffs to True if logged differences aren't to be truncated + # reportAllSects to True if all ELF section differences are to be reported + # + baseRoot, fileNamesFile, localTools, ptchRoot, results = args() + + # + # Set up the results/log file + # + if logging : + try: + log = open(results, "w") + except: + logging = False + error("failed to open log file: " + log) + sys.exit(1) + + dateTimeStr= "# %d/%d/%d at %d:%d:%d" % time.localtime()[:6] + v_info("# This file was produced by wsdiff") + v_info(dateTimeStr) + + # + # Build paths to the tools required tools + # + # Try to look for tools in $SRC/tools if the "-t" option + # was specified + # + arch = commands.getoutput("uname -p") + if localTools : + try: + src = os.environ['SRC'] + except: + error("-t specified, but $SRC not set. Cannot find $SRC/tools") + src = "" + if len(src) > 0 : + wsdiff_path.insert(0, src + "/tools/proto/opt/onbld/bin") + + lintdump_cmd = find_tool("lintdump") + elfdump_cmd = find_tool("elfdump") + dump_cmd = find_tool("dump") + od_cmd = find_tool("od") + dis_cmd = find_tool("dis") + diff_cmd = find_tool("diff") + sqlite_cmd = find_tool("sqlite") + + # + # validate the base and patch paths + # + if baseRoot[-1] != '/' : + baseRoot += '/' + + if ptchRoot[-1] != '/' : + ptchRoot += '/' + + if not os.path.exists(baseRoot) : + error("old proto area: " + baseRoot + " does not exist") + sys.exit(1) + + if not os.path.exists(ptchRoot) : + error("new proto area: " + ptchRoot + \ + " does not exist") + sys.exit(1) + + # + # log some information identifying the run + # + v_info("Old proto area: " + baseRoot) + v_info("New proto area: " + ptchRoot) + v_info("Results file: " + results + "\n") + + # + # Set up the temporary directories / files + # Could use python's tmpdir routines, but these should + # be easier to identify / keep around for debugging + pid = os.getpid() + tmpDir1 = "/tmp/wsdiff_tmp1_" + str(pid) + "/" + tmpDir2 = "/tmp/wsdiff_tmp2_" + str(pid) + "/" + if not os.path.exists(tmpDir1) : + os.makedirs(tmpDir1) + if not os.path.exists(tmpDir2) : + os.makedirs(tmpDir2) + + tmpFile1 = tmpDir1 + "f1" + tmpFile2 = tmpDir2 + "f2" + + # Derive a catalog of new, deleted, and to-be-compared objects + # either from the specified base and patch proto areas, or from + # from an input file list + newOrDeleted = False + + if fileNamesFile != "" : + changedFiles, newFiles, deletedFiles = \ + flistCatalog(baseRoot, ptchRoot, fileNamesFile) + else : + changedFiles, newFiles, deletedFiles = protoCatalog(baseRoot, ptchRoot) + + if len(newFiles) > 0 : + newOrDeleted = True + info("\nNew objects found: ") + + for fn in newFiles : + info(fnFormat(fn)) + + if len(deletedFiles) > 0 : + newOrDeleted = True + info("\nObjects removed: ") + + for fn in deletedFiles : + info(fnFormat(fn)) + + if newOrDeleted : + info("\nChanged objects: "); + + + # Here's where all the heavy lifting happens + # Perform a comparison on each object appearing in + # both proto areas. compareOneFile will examine the + # file types of each object, and will vector off to + # the appropriate comparison routine, where the compare + # will happen, and any differences will be reported / logged + for fn in changedFiles : + base = baseRoot + fn + ptch = ptchRoot + fn + + compareOneFile(base, ptch, False) + + # We're done, cleanup. + cleanup(0) + +if __name__ == '__main__' : + try: + main() + except KeyboardInterrupt : + cleanup(1); + + |