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