| #!/usr/bin/env python |
| # |
| # Copyright (C) 2013 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """Block diff utility.""" |
| |
| from __future__ import absolute_import |
| from __future__ import print_function |
| |
| # pylint: disable=import-error |
| import argparse |
| import sys |
| |
| |
| class BlockDiffError(Exception): |
| pass |
| |
| |
| def BlockDiff(block_size, file1, file2, name1, name2, max_length=-1): |
| """Performs a binary diff of two files by blocks. |
| |
| Args: |
| block_size: the size of a block to diff by |
| file1: first file object |
| file2: second file object |
| name1: name of first file (for error reporting) |
| name2: name of second file (for error reporting) |
| max_length: the maximum length to read/diff in bytes (optional) |
| Returns: |
| A list of (start, length) pairs representing block extents that differ |
| between the two files. |
| Raises: |
| BlockDiffError if there were errors while diffing. |
| |
| """ |
| if max_length < 0: |
| max_length = sys.maxsize |
| diff_list = [] |
| num_blocks = extent_start = extent_length = 0 |
| while max_length or extent_length: |
| read_length = min(max_length, block_size) |
| data1 = file1.read(read_length) |
| data2 = file2.read(read_length) |
| if len(data1) != len(data2): |
| raise BlockDiffError('read %d bytes from %s but %d bytes from %s' % |
| (len(data1), name1, len(data2), name2)) |
| |
| if data1 != data2: |
| # Data is different, mark it down. |
| if extent_length: |
| # Stretch the current diff extent. |
| extent_length += 1 |
| else: |
| # Start a new diff extent. |
| extent_start = num_blocks |
| extent_length = 1 |
| elif extent_length: |
| # Record the previous extent. |
| diff_list.append((extent_start, extent_length)) |
| extent_length = 0 |
| |
| # Are we done reading? |
| if not data1: |
| break |
| |
| max_length -= len(data1) |
| num_blocks += 1 |
| |
| return diff_list |
| |
| |
| def main(argv): |
| # Parse command-line arguments. |
| parser = argparse.ArgumentParser( |
| description='Compare FILE1 and FILE2 by blocks.', |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| |
| parser.add_argument('-b', '--block-size', metavar='NUM', type=int, |
| default=4096, help='the block size to use') |
| parser.add_argument('-m', '--max-length', metavar='NUM', type=int, default=-1, |
| help='maximum number of bytes to compare') |
| parser.add_argument('file1', metavar='FILE1') |
| parser.add_argument('file2', metavar='FILE2') |
| |
| args = parser.parse_args(argv[1:]) |
| |
| # Perform the block diff. |
| try: |
| with open(args.file1) as file1: |
| with open(args.file2) as file2: |
| diff_list = BlockDiff(args.block_size, file1, file2, |
| args.file1, args.file2, args.max_length) |
| except BlockDiffError as e: |
| print('Error: ' % e, file=sys.stderr) |
| return 2 |
| |
| # Print the diff, if such was found. |
| if diff_list: |
| total_diff_blocks = 0 |
| for extent_start, extent_length in diff_list: |
| total_diff_blocks += extent_length |
| print('%d->%d (%d)' % |
| (extent_start, extent_start + extent_length, extent_length)) |
| |
| print('total diff: %d blocks' % total_diff_blocks) |
| return 1 |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv)) |