普通文本  |  116行  |  3.17 KB

#!/usr/bin/python2
#
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Block diff utility."""

from __future__ import print_function

import optparse
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.maxint
  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 = optparse.OptionParser(
      usage='Usage: %prog FILE1 FILE2',
      description='Compare FILE1 and FILE2 by blocks.')

  parser.add_option('-b', '--block-size', metavar='NUM', type=int, default=4096,
                    help='the block size to use (default: %default)')
  parser.add_option('-m', '--max-length', metavar='NUM', type=int, default=-1,
                    help='maximum number of bytes to compared')

  opts, args = parser.parse_args(argv[1:])

  try:
    name1, name2 = args
  except ValueError:
    parser.error('unexpected number of arguments')

  # Perform the block diff.
  try:
    with open(name1) as file1:
      with open(name2) as file2:
        diff_list = BlockDiff(opts.block_size, file1, file2, name1, name2,
                              opts.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))