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.
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.
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]
22 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
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.
31 # The tool works by comparing objects in two trees/proto areas (one build with,
32 # and without the source changes.
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
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
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.
51 # For patch deliverable purposes, it's advised to have nightly do a clobber,
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.
57 # Use the -i option in conjunction with -v and -V to dive deeper and have wsdiff(1)
58 # report with more verbosity.
60 # Usage: wsdiff [-vVt] [-r results ] [-i filelist ] old new
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:
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
72 import datetime
, fnmatch
, getopt
, os
, profile
, commands
73 import re
, resource
, select
, shutil
, signal
, string
, struct
, sys
, tempfile
74 import time
, threading
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
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()
90 # Variable for thread control
91 keep_processing
= True
93 # Default search path for wsdiff
94 wsdiff_path
= [ "/usr/bin",
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.
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.
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"
118 # Debug message to be printed to the screen, and the log file
121 # Add prefix to highlight debugging message
124 output_lock
.acquire()
125 print >> sys
.stdout
, msg
127 output_lock
.release()
134 # Informational message to be printed to the screen, and the log file
137 output_lock
.acquire()
138 print >> sys
.stdout
, msg
140 output_lock
.release()
147 # Error message to be printed to the screen, and the log file
150 output_lock
.acquire()
151 print >> sys
.stderr
, "ERROR:", msg
153 output_lock
.release()
156 print >> log
, "ERROR:", msg
160 # Informational message to be printed only to the log, if there is one.
170 # Flag a detected file difference
171 # Display the fileName to stdout, and log the difference
173 def difference(f
, dtype
, diffs
) :
175 if f
in wsdiff_exceptions
:
178 output_lock
.acquire()
180 differentFiles
.append(f
)
182 print >> sys
.stdout
, f
184 output_lock
.release()
186 log_difference(f
, dtype
, diffs
)
189 # Do the actual logging of the difference to the results file
191 def log_difference(f
, dtype
, diffs
) :
196 print >> log
, "NOTE:", dtype
, "difference detected."
202 if not vdiffs
and difflen
> diffs_sz_thresh
:
203 print >> log
, diffs
[:diffs_sz_thresh
]
205 "... truncated due to length: " \
206 "use -v to override ..."
215 # diff generating routines
219 # Return human readable diffs from two temporary files
221 def diffFileData(tmpf1
, tmpf2
) :
225 # Filter the data through od(1) if the data is detected
227 if isBinary(tmpf1
) or isBinary(tmpf2
) :
229 tmp_od1
= tmpf1
+ ".od"
230 tmp_od2
= tmpf2
+ ".od"
232 cmd
= od_cmd
+ " -c -t x4" + " " + tmpf1
+ " > " + tmp_od1
234 cmd
= od_cmd
+ " -c -t x4" + " " + tmpf2
+ " > " + tmp_od2
241 data
= commands
.getoutput(diff_cmd
+ " " + tmpf1
+ " " + tmpf2
)
242 # Remove the temp files as we no longer need them.
247 error("diffFileData: unlink failed %s" % e
)
251 error("diffFileData: unlink failed %s" % e
)
253 error("failed to get output of command: " + diff_cmd
+ " " \
254 + tmpf1
+ " " + tmpf2
)
256 # Send exception for the failed command up
263 # Return human readable diffs betweeen two datasets
265 def diffData(base
, ptch
, d1
, d2
) :
267 t
= threading
.currentThread()
268 tmpFile1
= tmpDir1
+ os
.path
.basename(base
) + t
.getName()
269 tmpFile2
= tmpDir2
+ os
.path
.basename(ptch
) + t
.getName()
272 fd1
= open(tmpFile1
, "w")
274 error("failed to open: " + tmpFile1
)
278 fd2
= open(tmpFile2
, "w")
280 error("failed to open: " + tmpFile2
)
288 return diffFileData(tmpFile1
, tmpFile2
)
291 # Misc utility functions
294 # Prune off the leading prefix from string s
295 def str_prefix_trunc(s
, prefix
) :
296 snipLen
= len(prefix
)
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
305 root_arch_str
= "root_" + arch
307 pos
= fn
.find(root_arch_str
)
311 pos
= fn
.find("/", pos
)
318 # Usage / argument processing
322 # Display usage message
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"""
337 # Process command line options
344 global reportAllSects
347 validOpts
= 'di:r:vVst?'
355 # getopt.getopt() returns:
356 # an option/value tuple
357 # a list of remaining non-option arguments
359 # A correct wsdiff invocation will have exactly two non option
360 # arguments, the paths to the base (old), ptch (new) proto areas
362 optlist
, args
= getopt
.getopt(sys
.argv
[1:], validOpts
)
363 except getopt
.error
, val
:
369 for opt
,val
in optlist
:
382 reportAllSects
= True
391 if len(baseRoot
) == 0 or len(ptchRoot
) == 0 :
394 if logging
and len(results
) == 0 :
397 if vdiffs
and not logging
:
398 error("The -v option requires a results file (-r)")
401 if reportAllSects
and not logging
:
402 error("The -V option requires a results file (-r)")
406 return baseRoot
, fileNamesFile
, localTools
, ptchRoot
, results
409 # File identification
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.
418 def getTheFileType(f
) :
420 extensions
= { 'a' : 'ELF Object Archive',
421 'jar' : 'Java Archive',
423 'ln' : 'Lint Library',
424 'db' : 'Sqlite Database' }
427 if os
.stat(f
)[ST_SIZE
] == 0 :
430 error("failed to stat " + f
)
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
]
445 # Return non-zero if "f" is an ELF file
452 error("failed to open: " + f
)
454 magic
= fd
.read(len(elfmagic
))
457 if magic
== elfmagic
:
462 # Return non-zero is "f" is binary.
463 # Consider the file to be binary if it contains any null characters
469 error("failed to open: " + f
)
474 if s
.find('\0') == -1 :
480 # Directory traversal and file finding
484 # Return a sorted list of files found under the specified directory
487 for path
, subdirs
, files
in os
.walk(d
) :
490 yield os
.path
.join(path
, name
)
493 # Examine all files in base, ptch
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)
499 def protoCatalog(base
, ptch
) :
501 compFiles
= [] # List of files in both proto areas
502 ptchList
= [] # List of file in patch proto area
504 newFiles
= [] # New files detected
505 deletedFiles
= [] # Deleted files
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")
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")
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
) :
523 fileName
= fn
[baseStringLength
:]
524 compFiles
.append(fileName
)
525 debug("Found " + str(len(compFiles
)) + " files")
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
) :
533 fileName
= fn
[ptchStringLength
:]
534 ptchList
.append(fileName
)
535 debug("Found " + str(len(ptchList
)) + " files")
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")
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
:
549 compFiles
.remove(fileName
)
551 error("filelist.remove() failed")
552 debug("List for comparison reduced to " + str(len(compFiles
)) \
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")
562 return compFiles
, newFiles
, deletedFiles
565 # Examine the files listed in the input file list
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)
571 def flistCatalog(base
, ptch
, flist
) :
572 compFiles
= [] # List of files in both proto areas
573 newFiles
= [] # New files detected
574 deletedFiles
= [] # Deleted files
577 fd
= open(flist
, "r")
579 error("could not open: " + flist
)
583 files
= fd
.readlines()
593 # the fileNames have a trailing '\n'
596 # The objects in the file list have paths relative
597 # to $ROOT or to the base/ptch directory specified on
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
) :
603 elif os
.path
.exists(base
+ "root_" + arch
+ "/" + f
) :
604 fn
= "root_" + arch
+ "/" + f
609 if not os
.path
.exists(ptch
+ fn
) :
612 if os
.path
.exists(ptch
+ f
) :
614 elif os
.path
.exists(ptch
+ "root_" + arch
+ "/" + f
) :
615 fn
= "root_" + arch
+ "/" + f
619 if os
.path
.islink(base
+ fn
) : # ignore links
621 if os
.path
.islink(ptch
+ fn
) :
624 if base_present
and ptch_present
:
627 deletedFiles
.append(fn
)
631 if os
.path
.islink(base
+ fn
) and \
632 os
.path
.islink(ptch
+ fn
) :
634 error(f
+ " in file list, but not in either tree. " + \
637 return compFiles
, newFiles
, deletedFiles
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/
646 def find_tool(tool
) :
648 # First, check what was passed
649 if os
.path
.exists(tool
) :
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
+ " "
658 location
= pdir
+ "/" + arch
+ "/" + tool
659 if os
.path
.exists(location
) :
660 return location
+ " "
662 error("Could not find path to: " + tool
);
667 # ELF file comparison helper routines
671 # Return a dictionary of ELF section types keyed by section name
673 def get_elfheader(f
) :
677 hstring
= commands
.getoutput(elfdump_cmd
+ " -c " + f
)
679 if len(hstring
) == 0 :
680 error("Failed to dump ELF header for " + f
)
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:");
691 section
= sect
[datap
:].split()[1]
692 datap
= sect
.find("sh_type:");
694 error("Could not get type for sect: " + section
+ \
696 sh_type
= sect
[datap
:].split()[2]
697 header
[section
] = sh_type
702 # Extract data in the specified ELF section from the given file
704 def extract_elf_section(f
, section
) :
706 data
= commands
.getoutput(dump_cmd
+ " -sn " + section
+ " " + f
)
709 error(dump_cmd
+ "yielded no data on section " + section
+ \
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
:];
722 # Return a (hopefully meaningful) human readable set of diffs
723 # for the specified ELF section between f1 and f2
725 # Depending on the section, various means for dumping and diffing
726 # the data may be employed.
728 text_sections
= [ '.text', '.init', '.fini' ]
729 def diff_elf_section(f1
, f2
, section
, sh_type
) :
731 t
= threading
.currentThread()
732 tmpFile1
= tmpDir1
+ os
.path
.basename(f1
) + t
.getName()
733 tmpFile2
= tmpDir2
+ os
.path
.basename(f2
) + t
.getName()
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
+ \
759 cmd2
= elfdump_cmd
+ " -s -N " + section
+ " " + f2
+ \
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
771 cmd1
= elfdump_cmd
+ " -w " + tmpFile1
+ " -N " + \
773 cmd2
= elfdump_cmd
+ " -w " + tmpFile2
+ " -N " + \
779 data
= diffFileData(tmpFile1
, tmpFile2
)
781 # remove temp files as we no longer need them
785 error("diff_elf_section: unlink failed %s" % e
)
789 error("diff_elf_section: unlink failed %s" % e
)
794 # compare the relevant sections of two ELF binaries
795 # and report any differences
797 # Returns: 1 if any differenes found
798 # 0 if no differences found
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",
817 sections_preferred
= [ ".rodata.str1.8",
825 def compareElfs(base
, ptch
, quiet
) :
830 base_header
= get_elfheader(base
)
833 sections
= base_header
.keys()
836 ptch_header
= get_elfheader(ptch
)
839 e2_only_sections
= ptch_header
.keys()
841 e1_only_sections
= []
843 fileName
= fnFormat(base
)
845 # Derive the list of ELF sections found only in
847 for sect
in sections
:
848 if not sect
in e2_only_sections
:
849 e1_only_sections
.append(sect
)
851 e2_only_sections
.remove(sect
)
853 if len(e1_only_sections
) > 0 :
860 for sect
in e1_only_sections
:
861 slist
= slist
+ sect
+ "\t"
862 data
= "ELF sections found in " + \
863 base
+ " but not in " + ptch
+ \
866 difference(fileName
, "ELF", data
)
869 if len(e2_only_sections
) > 0 :
876 for sect
in e2_only_sections
:
877 slist
= slist
+ sect
+ "\t"
878 data
= "ELF sections found in " + \
879 ptch
+ " but not in " + base
+ \
882 difference(fileName
, "ELF", data
)
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
)
892 # Compare ELF sections
894 for sect
in sections
:
896 if sect
in sections_to_skip
:
900 s1
= extract_elf_section(base
, sect
);
905 s2
= extract_elf_section(ptch
, sect
);
909 if len(s1
) != len (s2
) or s1
!= s2
:
911 sh_type
= base_header
[sect
]
912 data
= diff_elf_section(base
, ptch
, \
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
, \
923 difference(fileName
, "ELF " + sect
, \
926 if not reportAllSects
:
928 first_section
= False
933 # recursively remove 2 directories
935 # Used for removal of temporary directory strucures (ignores any errors).
937 def clearTmpDirs(dir1
, dir2
) :
939 if os
.path
.isdir(dir1
) > 0 :
940 shutil
.rmtree(dir1
, True)
942 if os
.path
.isdir(dir2
) > 0 :
943 shutil
.rmtree(dir2
, True)
947 # Archive object comparison
949 # Returns 1 if difference detected
950 # 0 if no difference detected
953 def compareArchives(base
, ptch
, fileType
) :
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()
961 # Be optimistic and first try a straight file compare
962 # as it will allow us to finish up quickly.
964 if compareBasic(base
, ptch
, True, fileType
) == 0 :
968 os
.makedirs(ArchTmpDir1
)
970 error("compareArchives: makedir failed %s" % e
)
973 os
.makedirs(ArchTmpDir2
)
975 error("compareArchives: makedir failed %s" % e
)
978 # copy over the objects to the temp areas, and
980 baseCmd
= "cp -fp " + base
+ " " + ArchTmpDir1
981 status
, output
= commands
.getstatusoutput(baseCmd
)
983 error(baseCmd
+ " failed: " + output
)
984 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
987 ptchCmd
= "cp -fp " + ptch
+ " " + ArchTmpDir2
988 status
, output
= commands
.getstatusoutput(ptchCmd
)
990 error(ptchCmd
+ " failed: " + output
)
991 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
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
1006 error("unexpected file type: " + fileType
)
1007 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
1013 baseFlist
= list(findFiles(ArchTmpDir1
))
1014 ptchFlist
= list(findFiles(ArchTmpDir2
))
1016 # Trim leading path off base/ptch file lists
1018 for fn
in baseFlist
:
1019 flist
.append(str_prefix_trunc(fn
, ArchTmpDir1
))
1023 for fn
in ptchFlist
:
1024 flist
.append(str_prefix_trunc(fn
, ArchTmpDir2
))
1027 for fn
in ptchFlist
:
1028 if not fn
in baseFlist
:
1029 difference(fileName
, fileType
, \
1030 fn
+ " added to " + fileName
)
1031 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
1034 for fn
in baseFlist
:
1035 if not fn
in ptchFlist
:
1036 difference(fileName
, fileType
, \
1037 fn
+ " removed from " + fileName
)
1038 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
1041 differs
= compareOneFile((ArchTmpDir1
+ fn
), \
1042 (ArchTmpDir2
+ fn
), True)
1044 difference(fileName
, fileType
, \
1045 fn
+ " in " + fileName
+ " differs")
1046 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
1049 clearTmpDirs(ArchTmpDir1
, ArchTmpDir2
)
1053 # (Basic) file comparison
1055 # There's some special case code here for Javadoc HTML files
1057 # Returns 1 if difference detected
1058 # 0 if no difference detected
1061 def compareBasic(base
, ptch
, quiet
, fileType
) :
1063 fileName
= fnFormat(base
);
1065 if quiet
and os
.stat(base
)[ST_SIZE
] != os
.stat(ptch
)[ST_SIZE
] :
1069 baseFile
= open(base
)
1071 error("could not open " + base
)
1074 ptchFile
= open(ptch
)
1076 error("could not open " + ptch
)
1079 baseData
= baseFile
.read()
1080 ptchData
= ptchFile
.read()
1086 if fileType
== "HTML" :
1088 toSnipBeginStr
= "<!-- Generated by javadoc"
1089 toSnipEndStr
= "-->\n"
1092 toSnipBegin
= string
.find(baseData
, toSnipBeginStr
)
1093 if toSnipBegin
!= -1 :
1094 toSnipEnd
= string
.find(baseData
[toSnipBegin
:], \
1097 baseData
= baseData
[:toSnipBegin
] + \
1098 baseData
[toSnipBegin
+ toSnipEnd
:]
1099 ptchData
= ptchData
[:toSnipBegin
] + \
1100 ptchData
[toSnipBegin
+ toSnipEnd
:]
1103 if baseData
!= ptchData
:
1106 if len(baseData
) != len(ptchData
) or baseData
!= ptchData
:
1107 diffs
= diffData(base
, ptch
, baseData
, ptchData
)
1108 difference(fileName
, fileType
, diffs
)
1114 # Compare two objects by producing a data dump from
1115 # each object, and then comparing the dump data
1117 # Returns: 1 if a difference is detected
1118 # 0 if no difference detected
1121 def compareByDumping(base
, ptch
, quiet
, fileType
) :
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()
1128 if fileType
== "Lint Library" :
1129 baseCmd
= lintdump_cmd
+ " -ir " + base
+ \
1130 " | egrep -v '(LINTOBJ|LINTMOD):'" + \
1131 " | grep -v PASS[1-3]:" + \
1133 ptchCmd
= lintdump_cmd
+ " -ir " + ptch
+ \
1134 " | egrep -v '(LINTOBJ|LINTMOD):'" + \
1135 " | grep -v PASS[1-3]:" + \
1137 elif fileType
== "Sqlite Database" :
1138 baseCmd
= "echo .dump | " + sqlite_cmd
+ base
+ " > " + \
1140 ptchCmd
= "echo .dump | " + sqlite_cmd
+ ptch
+ " > " + \
1147 baseFile
= open(tmpFile1
)
1149 error("could not open: " + tmpFile1
)
1152 ptchFile
= open(tmpFile2
)
1154 error("could not open: " + tmpFile2
)
1157 baseData
= baseFile
.read()
1158 ptchData
= ptchFile
.read()
1163 if len(baseData
) != len(ptchData
) or baseData
!= ptchData
:
1165 data
= diffFileData(tmpFile1
, tmpFile2
);
1169 error("compareByDumping: unlink failed %s" % e
)
1173 error("compareByDumping: unlink failed %s" % e
)
1174 difference(fileName
, fileType
, data
)
1177 # Remove the temporary files now.
1181 error("compareByDumping: unlink failed %s" % e
)
1185 error("compareByDumping: unlink failed %s" % e
)
1191 # SIGINT signal handler. Changes thread control variable to tell the threads
1192 # to finish their current job and exit.
1194 def discontinue_processing(signl
, frme
):
1195 global keep_processing
1197 print >> sys
.stderr
, "Caught Ctrl-C, stopping the threads"
1198 keep_processing
= False
1204 # worker thread for changedFiles processing
1206 class workerThread(threading
.Thread
) :
1212 global keep_processing
1214 while (keep_processing
) :
1215 # grab the lock to changedFiles and remove one member
1219 fn
= changedFiles
.pop()
1221 # there is nothing more to do
1226 base
= baseRoot
+ fn
1227 ptch
= ptchRoot
+ fn
1229 compareOneFile(base
, ptch
, False)
1233 # Compare two objects. Detect type changes.
1234 # Vector off to the appropriate type specific
1235 # compare routine based on the type.
1237 def compareOneFile(base
, ptch
, quiet
) :
1239 # Verify the file types.
1240 # If they are different, indicate this and move on
1241 btype
= getTheFileType(base
)
1242 ptype
= getTheFileType(ptch
)
1244 if btype
== 'Error' or ptype
== 'Error' :
1247 fileName
= fnFormat(base
)
1249 if (btype
!= ptype
) :
1251 difference(fileName
, "file type", btype
+ " to " + ptype
)
1256 if (fileType
== 'ELF') :
1257 return compareElfs(base
, ptch
, quiet
)
1259 elif (fileType
== 'Java Archive' or fileType
== 'ELF Object Archive') :
1260 return compareArchives(base
, ptch
, fileType
)
1262 elif (fileType
== 'HTML') :
1263 return compareBasic(base
, ptch
, quiet
, fileType
)
1265 elif ( fileType
== 'Lint Library' ) :
1266 return compareByDumping(base
, ptch
, quiet
, fileType
)
1268 elif ( fileType
== 'Sqlite Database' ) :
1269 return compareByDumping(base
, ptch
, quiet
, fileType
)
1272 # it has to be some variety of text file
1273 return compareBasic(base
, ptch
, quiet
, fileType
)
1275 # Cleanup and self-terminate
1278 debug("Performing cleanup (" + str(ret
) + ")")
1279 if os
.path
.isdir(tmpDir1
) > 0 :
1280 shutil
.rmtree(tmpDir1
)
1282 if os
.path
.isdir(tmpDir2
) > 0 :
1283 shutil
.rmtree(tmpDir2
)
1295 # Globals relating to command line options
1296 global logging
, vdiffs
, reportAllSects
1298 # Named temporary files / directories
1299 global tmpDir1
, tmpDir2
1302 global lintdump_cmd
, elfdump_cmd
, dump_cmd
, dis_cmd
, od_cmd
, diff_cmd
, sqlite_cmd
1304 # Default search path
1307 # Essentially "uname -p"
1310 # changed files for worker thread processing
1315 # Sort the list of files from a temporary file
1317 global differentFiles
1319 # Debugging indicator
1322 # Some globals need to be initialized
1323 debugon
= logging
= vdiffs
= reportAllSects
= sorted = False
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
1334 baseRoot
, fileNamesFile
, localTools
, ptchRoot
, results
= args()
1337 # Set up the results/log file
1341 log
= open(results
, "w")
1344 error("failed to open log file: " + log
)
1347 dateTimeStr
= "# %04d-%02d-%02d at %02d:%02d:%02d" % time
.localtime()[:6]
1348 v_info("# This file was produced by wsdiff")
1351 # Changed files (used only for the sorted case)
1356 # Build paths to the tools required tools
1358 # Try to look for tools in $SRC/tools if the "-t" option
1361 arch
= commands
.getoutput("uname -p")
1364 src
= os
.environ
['SRC']
1366 error("-t specified, but $SRC not set. Cannot find $SRC/tools")
1369 wsdiff_path
.insert(0, src
+ "/tools/proto/opt/onbld/bin")
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")
1380 # Set resource limit for number of open files as high as possible.
1381 # This might get handy with big number of threads.
1383 (nofile_soft
, nofile_hard
) = resource
.getrlimit(resource
.RLIMIT_NOFILE
)
1385 resource
.setrlimit(resource
.RLIMIT_NOFILE
,
1386 (nofile_hard
, nofile_hard
))
1388 error("cannot set resource limits for number of open files")
1392 # validate the base and patch paths
1394 if baseRoot
[-1] != '/' :
1397 if ptchRoot
[-1] != '/' :
1400 if not os
.path
.exists(baseRoot
) :
1401 error("old proto area: " + baseRoot
+ " does not exist")
1404 if not os
.path
.exists(ptchRoot
) :
1405 error("new proto area: " + ptchRoot
+ \
1410 # log some information identifying the run
1412 v_info("Old proto area: " + baseRoot
)
1413 v_info("New proto area: " + ptchRoot
)
1414 v_info("Results file: " + results
+ "\n")
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
1421 tmpDir1
= "/tmp/wsdiff_tmp1_" + str(pid
) + "/"
1422 tmpDir2
= "/tmp/wsdiff_tmp2_" + str(pid
) + "/"
1424 os
.makedirs(tmpDir1
)
1426 error("main: makedir failed %s" % e
)
1428 os
.makedirs(tmpDir2
)
1430 error("main: makedir failed %s" % e
)
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
1437 if fileNamesFile
!= "" :
1438 changedFiles
, newFiles
, deletedFiles
= \
1439 flistCatalog(baseRoot
, ptchRoot
, fileNamesFile
)
1441 changedFiles
, newFiles
, deletedFiles
= \
1442 protoCatalog(baseRoot
, ptchRoot
)
1444 if len(newFiles
) > 0 :
1446 info("\nNew objects found: ")
1450 for fn
in newFiles
:
1453 if len(deletedFiles
) > 0 :
1455 info("\nObjects removed: ")
1459 for fn
in deletedFiles
:
1463 info("\nChanged objects: ")
1465 debug("The list will appear after the processing is done")
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
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
1478 max_threads
= int(os
.environ
['DMAKE_MAX_JOBS'])
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 :
1487 max_threads
+= max_threads
/5
1489 # Set signal handler to attempt graceful exit
1490 debug("Setting signal handler")
1491 signal
.signal( signal
.SIGINT
, discontinue_processing
)
1493 # Create and unleash the threads
1494 # Only at most max_threads must be running at any moment
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()
1502 # Wait for the threads to finish and do cleanup if interrupted
1503 debug("Waiting for the threads to finish")
1505 if not True in [thread
.isAlive() for thread
in mythreads
]:
1508 # Some threads are still going
1511 # Interrupted by SIGINT
1512 if keep_processing
== False :
1515 # If the list of differences was sorted it is stored in an array
1517 differentFiles
.sort()
1518 for f
in differentFiles
:
1521 # We're done, cleanup.
1524 if __name__
== '__main__' :
1527 except KeyboardInterrupt :