Home | History | Annotate | Download | only in scripts
      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 
     74 import datetime, fnmatch, getopt, profile, os, popen2, commands
     75 import re, select, string, struct, sys, tempfile, time
     76 from stat import *
     77 
     78 # Human readable diffs truncated by default if longer than this
     79 # Specifying -v on the command line will override
     80 diffs_sz_thresh = 4096
     81 
     82 # Default search path for wsdiff
     83 wsdiff_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 #
     97 wsdiff_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
    108 def 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
    116 def 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.
    125 def 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 #
    135 def 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 #
    148 def 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 #
    175 def 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 #
    198 def 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
    226 def 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 # 
    235 def 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 #
    255 def 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 #
    268 def 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 #
    341 def 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 #
    371 elfmagic = '\177ELF'
    372 def 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 # 
    389 def 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 #
    410 def 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 #
    423 def 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 #
    479 def 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 #
    551 def 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 # 
    578 def 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 #
    608 def 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 #
    630 text_sections = [ '.text', '.init', '.fini' ]
    631 def 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.
    691 sections_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 
    703 sections_preferred = [ ".rodata.str1.8",
    704 		       ".rodata.str1.1",
    705 		       ".rodata",
    706 		       ".data1",
    707 		       ".data",
    708 		       ".text",
    709 		       ]
    710 
    711 def 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 #
    808 def 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 #
    905 def compareBasic(base,