blob: feb5bd910a12694ce6dff14302189b93a55ce718 [file] [log] [blame]
Mike Frysinger3ba7a082017-10-06 01:23:28 -04001#!/usr/bin/python
2#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Block diff utility."""
8
9import optparse
10import sys
11
12
13class BlockDiffError(Exception):
14 pass
15
16
17def BlockDiff(block_size, file1, file2, name1, name2, max_length=-1):
18 """Performs a binary diff of two files by blocks.
19
20 Args:
21 block_size: the size of a block to diff by
22 file1: first file object
23 file2: second file object
24 name1: name of first file (for error reporting)
25 name2: name of second file (for error reporting)
26 max_length: the maximum length to read/diff in bytes (optional)
27 Returns:
28 A list of (start, length) pairs representing block extents that differ
29 between the two files.
30 Raises:
31 BlockDiffError if there were errors while diffing.
32
33 """
34 if max_length < 0:
35 max_length = sys.maxint
36 diff_list = []
37 num_blocks = extent_start = extent_length = 0
38 while max_length or extent_length:
39 read_length = min(max_length, block_size)
40 data1 = file1.read(read_length)
41 data2 = file2.read(read_length)
42 if len(data1) != len(data2):
43 raise BlockDiffError('read %d bytes from %s but %d bytes from %s' %
44 (len(data1), name1, len(data2), name2))
45
46 if data1 != data2:
47 # Data is different, mark it down.
48 if extent_length:
49 # Stretch the current diff extent.
50 extent_length += 1
51 else:
52 # Start a new diff extent.
53 extent_start = num_blocks
54 extent_length = 1
55 elif extent_length:
56 # Record the previous extent.
57 diff_list.append((extent_start, extent_length))
58 extent_length = 0
59
60 # Are we done reading?
61 if not data1:
62 break
63
64 max_length -= len(data1)
65 num_blocks += 1
66
67 return diff_list
68
69
70def main(argv):
71 # Parse command-line arguments.
72 parser = optparse.OptionParser(
73 usage='Usage: %prog FILE1 FILE2',
74 description='Compare FILE1 and FILE2 by blocks.')
75
76 parser.add_option('-b', '--block-size', metavar='NUM', type=int, default=4096,
77 help='the block size to use (default: %default)')
78 parser.add_option('-m', '--max-length', metavar='NUM', type=int, default=-1,
79 help='maximum number of bytes to compared')
80
81 opts, args = parser.parse_args(argv[1:])
82
83 try:
84 name1, name2 = args
85 except ValueError:
86 parser.error('unexpected number of arguments')
87
88 # Perform the block diff.
89 try:
90 with open(name1) as file1:
91 with open(name2) as file2:
92 diff_list = BlockDiff(opts.block_size, file1, file2, name1, name2,
93 opts.max_length)
94 except BlockDiffError as e:
95 print >> sys.stderr, 'Error:', e
96 return 2
97
98 # Print the diff, if such was found.
99 if diff_list:
100 total_diff_blocks = 0
101 for extent_start, extent_length in diff_list:
102 total_diff_blocks += extent_length
103 print('%d->%d (%d)' %
104 (extent_start, extent_start + extent_length, extent_length))
105
106 print 'total diff: %d blocks' % total_diff_blocks
107 return 1
108
109 return 0
110
111
112if __name__ == '__main__':
113 sys.exit(main(sys.argv))