• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2
2#
3# Copyright (C) 2013 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""Block diff utility."""
19
20from __future__ import print_function
21
22# pylint: disable=import-error
23import argparse
24import sys
25
26
27class BlockDiffError(Exception):
28  pass
29
30
31def BlockDiff(block_size, file1, file2, name1, name2, max_length=-1):
32  """Performs a binary diff of two files by blocks.
33
34  Args:
35    block_size: the size of a block to diff by
36    file1: first file object
37    file2: second file object
38    name1: name of first file (for error reporting)
39    name2: name of second file (for error reporting)
40    max_length: the maximum length to read/diff in bytes (optional)
41  Returns:
42    A list of (start, length) pairs representing block extents that differ
43    between the two files.
44  Raises:
45    BlockDiffError if there were errors while diffing.
46
47  """
48  if max_length < 0:
49    max_length = sys.maxint
50  diff_list = []
51  num_blocks = extent_start = extent_length = 0
52  while max_length or extent_length:
53    read_length = min(max_length, block_size)
54    data1 = file1.read(read_length)
55    data2 = file2.read(read_length)
56    if len(data1) != len(data2):
57      raise BlockDiffError('read %d bytes from %s but %d bytes from %s' %
58                           (len(data1), name1, len(data2), name2))
59
60    if data1 != data2:
61      # Data is different, mark it down.
62      if extent_length:
63        # Stretch the current diff extent.
64        extent_length += 1
65      else:
66        # Start a new diff extent.
67        extent_start = num_blocks
68        extent_length = 1
69    elif extent_length:
70      # Record the previous extent.
71      diff_list.append((extent_start, extent_length))
72      extent_length = 0
73
74    # Are we done reading?
75    if not data1:
76      break
77
78    max_length -= len(data1)
79    num_blocks += 1
80
81  return diff_list
82
83
84def main(argv):
85  # Parse command-line arguments.
86  parser = argparse.ArgumentParser(
87      description='Compare FILE1 and FILE2 by blocks.',
88      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
89
90  parser.add_argument('-b', '--block-size', metavar='NUM', type=int,
91                      default=4096, help='the block size to use')
92  parser.add_argument('-m', '--max-length', metavar='NUM', type=int, default=-1,
93                      help='maximum number of bytes to compare')
94  parser.add_argument('file1', metavar='FILE1')
95  parser.add_argument('file2', metavar='FILE2')
96
97  args = parser.parse_args(argv[1:])
98
99  # Perform the block diff.
100  try:
101    with open(args.file1) as file1:
102      with open(args.file2) as file2:
103        diff_list = BlockDiff(args.block_size, file1, file2,
104                              args.file1, args.file2, args.max_length)
105  except BlockDiffError as e:
106    print('Error: ' % e, file=sys.stderr)
107    return 2
108
109  # Print the diff, if such was found.
110  if diff_list:
111    total_diff_blocks = 0
112    for extent_start, extent_length in diff_list:
113      total_diff_blocks += extent_length
114      print('%d->%d (%d)' %
115            (extent_start, extent_start + extent_length, extent_length))
116
117    print('total diff: %d blocks' % total_diff_blocks)
118    return 1
119
120  return 0
121
122
123if __name__ == '__main__':
124  sys.exit(main(sys.argv))
125