[bmap-tools] [PATCH 01/14] Rename Fiemap.py to Filemap.py

Artem Bityutskiy dedekind1 at gmail.com
Tue Jan 21 12:34:22 EST 2014


From: Artem Bityutskiy <artem.bityutskiy at intel.com>

The FIEMAP ioctl is not supported by tmpfs, so currently bmaptool fails to
create the bmap file when the file resides on tmpfs. This is unfortunate.

However, tmpfs supports 'SEEK_HOLE' which we can use instead of FIEMAP.

This patch is a preparation for adding 'SEEK_HOLE' support. Namely, we re-name
the Fiemap.py module to Filemap.py, where we'll support both FIEMAP and
SEEK_HOLE. Variables which contain 'fiemap' are also re-named so that they now
contain 'filemap' instead.

Change-Id: I20eac7adbdc3ad6d39c8ce5b67a5cbdbdf2987be
Signed-off-by: Artem Bityutskiy <artem.bityutskiy at intel.com>
---
 bmaptools/BmapCreate.py |  12 +-
 bmaptools/Fiemap.py     | 293 ------------------------------------------------
 bmaptools/Filemap.py    | 293 ++++++++++++++++++++++++++++++++++++++++++++++++
 docs/README             |   4 +-
 tests/helpers.py        |   8 +-
 tests/test_api_base.py  |  10 +-
 tests/test_fiemap.py    | 148 ------------------------
 tests/test_filemap.py   | 148 ++++++++++++++++++++++++
 8 files changed, 458 insertions(+), 458 deletions(-)
 delete mode 100644 bmaptools/Fiemap.py
 create mode 100644 bmaptools/Filemap.py
 delete mode 100644 tests/test_fiemap.py
 create mode 100644 tests/test_filemap.py

diff --git a/bmaptools/BmapCreate.py b/bmaptools/BmapCreate.py
index 2cb7c06..e1447d4 100644
--- a/bmaptools/BmapCreate.py
+++ b/bmaptools/BmapCreate.py
@@ -44,7 +44,7 @@ This module uses the FIBMAP ioctl to detect holes.
 
 import hashlib
 from bmaptools.BmapHelpers import human_size
-from bmaptools import Fiemap
+from bmaptools import Filemap
 
 # The bmap format version we generate.
 #
@@ -165,16 +165,16 @@ class BmapCreate:
             self._bmap_path = bmap
             self._open_bmap_file()
 
-        self.fiemap = Fiemap.Fiemap(self._f_image)
+        self.filemap = Filemap.Fiemap(self._f_image)
 
-        self.image_size = self.fiemap.image_size
+        self.image_size = self.filemap.image_size
         self.image_size_human = human_size(self.image_size)
         if self.image_size == 0:
             raise Error("cannot generate bmap for zero-sized image file '%s'"
                         % self._image_path)
 
-        self.block_size = self.fiemap.block_size
-        self.blocks_cnt = self.fiemap.blocks_cnt
+        self.block_size = self.filemap.block_size
+        self.blocks_cnt = self.filemap.blocks_cnt
 
     def __del__(self):
         """The class destructor which closes the opened files."""
@@ -317,7 +317,7 @@ class BmapCreate:
         # Generate the block map and write it to the XML block map
         # file as we go.
         self.mapped_cnt = 0
-        for first, last in self.fiemap.get_mapped_ranges(0, self.blocks_cnt):
+        for first, last in self.filemap.get_mapped_ranges(0, self.blocks_cnt):
             self.mapped_cnt += last - first + 1
             if include_checksums:
                 chksum = self._calculate_chksum(first, last)
diff --git a/bmaptools/Fiemap.py b/bmaptools/Fiemap.py
deleted file mode 100644
index b5f7c2c..0000000
--- a/bmaptools/Fiemap.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# Copyright (c) 2012 Intel, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License, version 2,
-# as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-
-"""
-This module implements python API for the FIEMAP ioctl. The FIEMAP ioctl
-allows to find holes and mapped areas in a file.
-"""
-
-# Note, a lot of code in this module is not very readable, because it deals
-# with the rather complex FIEMAP ioctl. To understand the code, you need to
-# know the FIEMAP interface, which is documented in the
-# Documentation/filesystems/fiemap.txt file in the Linux kernel sources.
-
-# Disable the following pylint recommendations:
-#   * Too many instance attributes (R0902)
-# pylint: disable=R0902
-
-import os
-import struct
-import array
-import fcntl
-from bmaptools import BmapHelpers
-
-# Format string for 'struct fiemap'
-_FIEMAP_FORMAT = "=QQLLLL"
-# sizeof(struct fiemap)
-_FIEMAP_SIZE = struct.calcsize(_FIEMAP_FORMAT)
-# Format string for 'struct fiemap_extent'
-_FIEMAP_EXTENT_FORMAT = "=QQQQQLLLL"
-# sizeof(struct fiemap_extent)
-_FIEMAP_EXTENT_SIZE = struct.calcsize(_FIEMAP_EXTENT_FORMAT)
-# The FIEMAP ioctl number
-_FIEMAP_IOCTL = 0xC020660B
-# This FIEMAP ioctl flag which instructs the kernel to sync the file before
-# reading the block map
-_FIEMAP_FLAG_SYNC = 0x00000001
-
-# Minimum buffer which is required for 'class Fiemap' to operate
-MIN_BUFFER_SIZE = _FIEMAP_SIZE + _FIEMAP_EXTENT_SIZE
-# The default buffer size for 'class Fiemap'
-DEFAULT_BUFFER_SIZE = 256 * 1024
-
-class Error(Exception):
-    """
-    A class for exceptions generated by this module. We currently support only
-    one type of exceptions, and we basically throw human-readable problem
-    description in case of errors.
-    """
-    pass
-
-class Fiemap:
-    """
-    This class provides API to the FIEMAP ioctl. Namely, it allows to iterate
-    over all mapped blocks and over all holes.
-    """
-
-    def __init__(self, image, buf_size=DEFAULT_BUFFER_SIZE):
-        """
-        Initialize a class instance. The 'image' argument is full path to the
-        file to operate on, or a file object to operate on.
-
-        The 'buf_size' argument is the size of the buffer for 'struct
-        fiemap_extent' elements which will be used when invoking the FIEMAP
-        ioctl. The larger is the buffer, the less times the FIEMAP ioctl will
-        be invoked.
-
-        This class synchronizes the image file every time it invokes the FIEMAP
-        ioctl in order to work-around early FIEMAP implementation kernel bugs.
-        """
-
-        self._f_image_needs_close = False
-
-        if hasattr(image, "fileno"):
-            self._f_image = image
-            self._image_path = image.name
-        else:
-            self._image_path = image
-            self._open_image_file()
-
-        # Validate 'buf_size'
-        if buf_size < MIN_BUFFER_SIZE:
-            raise Error("too small buffer (%d bytes), minimum is %d bytes"
-                    % (buf_size, MIN_BUFFER_SIZE))
-
-        # How many 'struct fiemap_extent' elements fit the buffer
-        buf_size -= _FIEMAP_SIZE
-        self._fiemap_extent_cnt = buf_size / _FIEMAP_EXTENT_SIZE
-        self._buf_size = self._fiemap_extent_cnt * _FIEMAP_EXTENT_SIZE
-        self._buf_size += _FIEMAP_SIZE
-
-        # Allocate a mutable buffer for the FIEMAP ioctl
-        self._buf = array.array('B', [0] * self._buf_size)
-
-        self.image_size = os.fstat(self._f_image.fileno()).st_size
-
-        try:
-            self.block_size = BmapHelpers.get_block_size(self._f_image)
-        except IOError as err:
-            raise Error("cannot get block size for '%s': %s"
-                        % (self._image_path, err))
-
-        self.blocks_cnt = self.image_size + self.block_size - 1
-        self.blocks_cnt /= self.block_size
-
-        # Synchronize the image file to make sure FIEMAP returns correct values
-        try:
-            self._f_image.flush()
-        except IOError as err:
-            raise Error("cannot flush image file '%s': %s"
-                        % (self._image_path, err))
-        try:
-            os.fsync(self._f_image.fileno()),
-        except OSError as err:
-            raise Error("cannot synchronize image file '%s': %s "
-                        % (self._image_path, err.strerror))
-
-        # Check if the FIEMAP ioctl is supported
-        self.block_is_mapped(0)
-
-    def __del__(self):
-        """The class destructor which closes the opened files."""
-        if self._f_image_needs_close:
-            self._f_image.close()
-
-    def _open_image_file(self):
-        """Open the image file."""
-        try:
-            self._f_image = open(self._image_path, 'rb')
-        except IOError as err:
-            raise Error("cannot open image file '%s': %s"
-                        % (self._image_path, err))
-
-        self._f_image_needs_close = True
-
-    def _invoke_fiemap(self, block, count):
-        """
-        Invoke the FIEMAP ioctl for 'count' blocks of the file starting from
-        block number 'block'.
-
-        The full result of the operation is stored in 'self._buf' on exit.
-        Returns the unpacked 'struct fiemap' data structure in form of a python
-        list (just like 'struct.upack()').
-        """
-
-        if self.blocks_cnt != 0 and (block < 0 or block >= self.blocks_cnt):
-            raise Error("bad block number %d, should be within [0, %d]"
-                        % (block, self.blocks_cnt))
-
-        # Initialize the 'struct fiemap' part of the buffer. We use the
-        # '_FIEMAP_FLAG_SYNC' flag in order to make sure the file is
-        # synchronized. The reason for this is that early FIEMAP
-        # implementations had many bugs related to cached dirty data, and
-        # synchronizing the file is a necessary work-around.
-        struct.pack_into(_FIEMAP_FORMAT, self._buf, 0, block * self.block_size,
-                         count * self.block_size, _FIEMAP_FLAG_SYNC, 0,
-                         self._fiemap_extent_cnt, 0)
-
-        try:
-            fcntl.ioctl(self._f_image, _FIEMAP_IOCTL, self._buf, 1)
-        except IOError as err:
-            error_msg = "the FIEMAP ioctl failed for '%s': %s" \
-                        % (self._image_path, err)
-            if err.errno == os.errno.EPERM or err.errno == os.errno.EACCES:
-                # The FIEMAP ioctl was added in kernel version 2.6.28 in 2008
-                error_msg += " (looks like your kernel does not support FIEMAP)"
-
-            raise Error(error_msg)
-
-        return struct.unpack(_FIEMAP_FORMAT, self._buf[:_FIEMAP_SIZE])
-
-    def block_is_mapped(self, block):
-        """
-        This function returns 'True' if block number 'block' of the image file
-        is mapped and 'False' otherwise.
-        """
-
-        struct_fiemap = self._invoke_fiemap(block, 1)
-
-        # The 3rd element of 'struct_fiemap' is the 'fm_mapped_extents' field.
-        # If it contains zero, the block is not mapped, otherwise it is
-        # mapped.
-        return bool(struct_fiemap[3])
-
-    def block_is_unmapped(self, block):
-        """
-        This function returns 'True' if block number 'block' of the image file
-        is not mapped (hole) and 'False' otherwise.
-        """
-
-        return not self.block_is_mapped(block)
-
-    def _unpack_fiemap_extent(self, index):
-        """
-        Unpack a 'struct fiemap_extent' structure object number 'index' from
-        the internal 'self._buf' buffer.
-        """
-
-        offset = _FIEMAP_SIZE + _FIEMAP_EXTENT_SIZE * index
-        return struct.unpack(_FIEMAP_EXTENT_FORMAT,
-                             self._buf[offset : offset + _FIEMAP_EXTENT_SIZE])
-
-    def _do_get_mapped_ranges(self, start, count):
-        """
-        Implements most the functionality for the  'get_mapped_ranges()'
-        generator: invokes the FIEMAP ioctl, walks through the mapped extents
-        and yields mapped block ranges. However, the ranges may be consecutive
-        (e.g., (1, 100), (100, 200)) and 'get_mapped_ranges()' simply merges
-        them.
-        """
-
-        block = start
-        while block < start + count:
-            struct_fiemap = self._invoke_fiemap(block, count)
-
-            mapped_extents = struct_fiemap[3]
-            if mapped_extents == 0:
-                # No more mapped blocks
-                return
-
-            extent = 0
-            while extent < mapped_extents:
-                fiemap_extent = self._unpack_fiemap_extent(extent)
-
-                # Start of the extent
-                extent_start = fiemap_extent[0]
-                # Starting block number of the extent
-                extent_block = extent_start / self.block_size
-                # Length of the extent
-                extent_len = fiemap_extent[2]
-                # Count of blocks in the extent
-                extent_count = extent_len / self.block_size
-
-                # Extent length and offset have to be block-aligned
-                assert extent_start % self.block_size == 0
-                assert extent_len % self.block_size == 0
-
-                if extent_block > start + count - 1:
-                    return
-
-                first = max(extent_block, block)
-                last = min(extent_block + extent_count, start + count) - 1
-                yield (first, last)
-
-                extent += 1
-
-            block = extent_block + extent_count
-
-    def get_mapped_ranges(self, start, count):
-        """
-        A generator which yields ranges of mapped blocks in the file. The
-        ranges are tuples of 2 elements: [first, last], where 'first' is the
-        first mapped block and 'last' is the last mapped block.
-
-        The ranges are yielded for the area of the file of size 'count' blocks,
-        starting from block 'start'.
-        """
-
-        iterator = self._do_get_mapped_ranges(start, count)
-
-        first_prev, last_prev = iterator.next()
-
-        for first, last in iterator:
-            if last_prev == first - 1:
-                last_prev = last
-            else:
-                yield (first_prev, last_prev)
-                first_prev, last_prev = first, last
-
-        yield (first_prev, last_prev)
-
-    def get_unmapped_ranges(self, start, count):
-        """
-        Just like 'get_mapped_ranges()', but yields unmapped block ranges
-        instead (holes).
-        """
-
-        hole_first = start
-        for first, last in self._do_get_mapped_ranges(start, count):
-            if first > hole_first:
-                yield (hole_first, first - 1)
-
-            hole_first = last + 1
-
-        if hole_first < start + count:
-            yield (hole_first, start + count - 1)
diff --git a/bmaptools/Filemap.py b/bmaptools/Filemap.py
new file mode 100644
index 0000000..b5f7c2c
--- /dev/null
+++ b/bmaptools/Filemap.py
@@ -0,0 +1,293 @@
+# Copyright (c) 2012 Intel, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+"""
+This module implements python API for the FIEMAP ioctl. The FIEMAP ioctl
+allows to find holes and mapped areas in a file.
+"""
+
+# Note, a lot of code in this module is not very readable, because it deals
+# with the rather complex FIEMAP ioctl. To understand the code, you need to
+# know the FIEMAP interface, which is documented in the
+# Documentation/filesystems/fiemap.txt file in the Linux kernel sources.
+
+# Disable the following pylint recommendations:
+#   * Too many instance attributes (R0902)
+# pylint: disable=R0902
+
+import os
+import struct
+import array
+import fcntl
+from bmaptools import BmapHelpers
+
+# Format string for 'struct fiemap'
+_FIEMAP_FORMAT = "=QQLLLL"
+# sizeof(struct fiemap)
+_FIEMAP_SIZE = struct.calcsize(_FIEMAP_FORMAT)
+# Format string for 'struct fiemap_extent'
+_FIEMAP_EXTENT_FORMAT = "=QQQQQLLLL"
+# sizeof(struct fiemap_extent)
+_FIEMAP_EXTENT_SIZE = struct.calcsize(_FIEMAP_EXTENT_FORMAT)
+# The FIEMAP ioctl number
+_FIEMAP_IOCTL = 0xC020660B
+# This FIEMAP ioctl flag which instructs the kernel to sync the file before
+# reading the block map
+_FIEMAP_FLAG_SYNC = 0x00000001
+
+# Minimum buffer which is required for 'class Fiemap' to operate
+MIN_BUFFER_SIZE = _FIEMAP_SIZE + _FIEMAP_EXTENT_SIZE
+# The default buffer size for 'class Fiemap'
+DEFAULT_BUFFER_SIZE = 256 * 1024
+
+class Error(Exception):
+    """
+    A class for exceptions generated by this module. We currently support only
+    one type of exceptions, and we basically throw human-readable problem
+    description in case of errors.
+    """
+    pass
+
+class Fiemap:
+    """
+    This class provides API to the FIEMAP ioctl. Namely, it allows to iterate
+    over all mapped blocks and over all holes.
+    """
+
+    def __init__(self, image, buf_size=DEFAULT_BUFFER_SIZE):
+        """
+        Initialize a class instance. The 'image' argument is full path to the
+        file to operate on, or a file object to operate on.
+
+        The 'buf_size' argument is the size of the buffer for 'struct
+        fiemap_extent' elements which will be used when invoking the FIEMAP
+        ioctl. The larger is the buffer, the less times the FIEMAP ioctl will
+        be invoked.
+
+        This class synchronizes the image file every time it invokes the FIEMAP
+        ioctl in order to work-around early FIEMAP implementation kernel bugs.
+        """
+
+        self._f_image_needs_close = False
+
+        if hasattr(image, "fileno"):
+            self._f_image = image
+            self._image_path = image.name
+        else:
+            self._image_path = image
+            self._open_image_file()
+
+        # Validate 'buf_size'
+        if buf_size < MIN_BUFFER_SIZE:
+            raise Error("too small buffer (%d bytes), minimum is %d bytes"
+                    % (buf_size, MIN_BUFFER_SIZE))
+
+        # How many 'struct fiemap_extent' elements fit the buffer
+        buf_size -= _FIEMAP_SIZE
+        self._fiemap_extent_cnt = buf_size / _FIEMAP_EXTENT_SIZE
+        self._buf_size = self._fiemap_extent_cnt * _FIEMAP_EXTENT_SIZE
+        self._buf_size += _FIEMAP_SIZE
+
+        # Allocate a mutable buffer for the FIEMAP ioctl
+        self._buf = array.array('B', [0] * self._buf_size)
+
+        self.image_size = os.fstat(self._f_image.fileno()).st_size
+
+        try:
+            self.block_size = BmapHelpers.get_block_size(self._f_image)
+        except IOError as err:
+            raise Error("cannot get block size for '%s': %s"
+                        % (self._image_path, err))
+
+        self.blocks_cnt = self.image_size + self.block_size - 1
+        self.blocks_cnt /= self.block_size
+
+        # Synchronize the image file to make sure FIEMAP returns correct values
+        try:
+            self._f_image.flush()
+        except IOError as err:
+            raise Error("cannot flush image file '%s': %s"
+                        % (self._image_path, err))
+        try:
+            os.fsync(self._f_image.fileno()),
+        except OSError as err:
+            raise Error("cannot synchronize image file '%s': %s "
+                        % (self._image_path, err.strerror))
+
+        # Check if the FIEMAP ioctl is supported
+        self.block_is_mapped(0)
+
+    def __del__(self):
+        """The class destructor which closes the opened files."""
+        if self._f_image_needs_close:
+            self._f_image.close()
+
+    def _open_image_file(self):
+        """Open the image file."""
+        try:
+            self._f_image = open(self._image_path, 'rb')
+        except IOError as err:
+            raise Error("cannot open image file '%s': %s"
+                        % (self._image_path, err))
+
+        self._f_image_needs_close = True
+
+    def _invoke_fiemap(self, block, count):
+        """
+        Invoke the FIEMAP ioctl for 'count' blocks of the file starting from
+        block number 'block'.
+
+        The full result of the operation is stored in 'self._buf' on exit.
+        Returns the unpacked 'struct fiemap' data structure in form of a python
+        list (just like 'struct.upack()').
+        """
+
+        if self.blocks_cnt != 0 and (block < 0 or block >= self.blocks_cnt):
+            raise Error("bad block number %d, should be within [0, %d]"
+                        % (block, self.blocks_cnt))
+
+        # Initialize the 'struct fiemap' part of the buffer. We use the
+        # '_FIEMAP_FLAG_SYNC' flag in order to make sure the file is
+        # synchronized. The reason for this is that early FIEMAP
+        # implementations had many bugs related to cached dirty data, and
+        # synchronizing the file is a necessary work-around.
+        struct.pack_into(_FIEMAP_FORMAT, self._buf, 0, block * self.block_size,
+                         count * self.block_size, _FIEMAP_FLAG_SYNC, 0,
+                         self._fiemap_extent_cnt, 0)
+
+        try:
+            fcntl.ioctl(self._f_image, _FIEMAP_IOCTL, self._buf, 1)
+        except IOError as err:
+            error_msg = "the FIEMAP ioctl failed for '%s': %s" \
+                        % (self._image_path, err)
+            if err.errno == os.errno.EPERM or err.errno == os.errno.EACCES:
+                # The FIEMAP ioctl was added in kernel version 2.6.28 in 2008
+                error_msg += " (looks like your kernel does not support FIEMAP)"
+
+            raise Error(error_msg)
+
+        return struct.unpack(_FIEMAP_FORMAT, self._buf[:_FIEMAP_SIZE])
+
+    def block_is_mapped(self, block):
+        """
+        This function returns 'True' if block number 'block' of the image file
+        is mapped and 'False' otherwise.
+        """
+
+        struct_fiemap = self._invoke_fiemap(block, 1)
+
+        # The 3rd element of 'struct_fiemap' is the 'fm_mapped_extents' field.
+        # If it contains zero, the block is not mapped, otherwise it is
+        # mapped.
+        return bool(struct_fiemap[3])
+
+    def block_is_unmapped(self, block):
+        """
+        This function returns 'True' if block number 'block' of the image file
+        is not mapped (hole) and 'False' otherwise.
+        """
+
+        return not self.block_is_mapped(block)
+
+    def _unpack_fiemap_extent(self, index):
+        """
+        Unpack a 'struct fiemap_extent' structure object number 'index' from
+        the internal 'self._buf' buffer.
+        """
+
+        offset = _FIEMAP_SIZE + _FIEMAP_EXTENT_SIZE * index
+        return struct.unpack(_FIEMAP_EXTENT_FORMAT,
+                             self._buf[offset : offset + _FIEMAP_EXTENT_SIZE])
+
+    def _do_get_mapped_ranges(self, start, count):
+        """
+        Implements most the functionality for the  'get_mapped_ranges()'
+        generator: invokes the FIEMAP ioctl, walks through the mapped extents
+        and yields mapped block ranges. However, the ranges may be consecutive
+        (e.g., (1, 100), (100, 200)) and 'get_mapped_ranges()' simply merges
+        them.
+        """
+
+        block = start
+        while block < start + count:
+            struct_fiemap = self._invoke_fiemap(block, count)
+
+            mapped_extents = struct_fiemap[3]
+            if mapped_extents == 0:
+                # No more mapped blocks
+                return
+
+            extent = 0
+            while extent < mapped_extents:
+                fiemap_extent = self._unpack_fiemap_extent(extent)
+
+                # Start of the extent
+                extent_start = fiemap_extent[0]
+                # Starting block number of the extent
+                extent_block = extent_start / self.block_size
+                # Length of the extent
+                extent_len = fiemap_extent[2]
+                # Count of blocks in the extent
+                extent_count = extent_len / self.block_size
+
+                # Extent length and offset have to be block-aligned
+                assert extent_start % self.block_size == 0
+                assert extent_len % self.block_size == 0
+
+                if extent_block > start + count - 1:
+                    return
+
+                first = max(extent_block, block)
+                last = min(extent_block + extent_count, start + count) - 1
+                yield (first, last)
+
+                extent += 1
+
+            block = extent_block + extent_count
+
+    def get_mapped_ranges(self, start, count):
+        """
+        A generator which yields ranges of mapped blocks in the file. The
+        ranges are tuples of 2 elements: [first, last], where 'first' is the
+        first mapped block and 'last' is the last mapped block.
+
+        The ranges are yielded for the area of the file of size 'count' blocks,
+        starting from block 'start'.
+        """
+
+        iterator = self._do_get_mapped_ranges(start, count)
+
+        first_prev, last_prev = iterator.next()
+
+        for first, last in iterator:
+            if last_prev == first - 1:
+                last_prev = last
+            else:
+                yield (first_prev, last_prev)
+                first_prev, last_prev = first, last
+
+        yield (first_prev, last_prev)
+
+    def get_unmapped_ranges(self, start, count):
+        """
+        Just like 'get_mapped_ranges()', but yields unmapped block ranges
+        instead (holes).
+        """
+
+        hole_first = start
+        for first, last in self._do_get_mapped_ranges(start, count):
+            if first > hole_first:
+                yield (hole_first, first - 1)
+
+            hole_first = last + 1
+
+        if hole_first < start + count:
+            yield (hole_first, start + count - 1)
diff --git a/docs/README b/docs/README
index 99eb1ea..f5eafcf 100644
--- a/docs/README
+++ b/docs/README
@@ -55,7 +55,7 @@ The project structure
 | - tests/               | Contains the project unit-tests.                    |
 |   | - test_api_base.py | Tests the base API modules: 'BmapCreate.py' and     |
 |   |                    | 'BmapCopy.py'.                                      |
-|   | - test_fiemap.py   | Tests the 'Fiemap.py' module.                       |
+|   | - test_fiemap.py   | Tests the 'Filemap.py' module.                      |
 |   | - test_compat.py   | Tests that new BmapCopy implementations support old |
 |   |                    | bmap formats, and old BmapCopy implementations      |
 |   |                    | support new compatible bmap fomrats.                |
@@ -67,7 +67,7 @@ The project structure
 |   |                    | functionality.                                      |
 |   | - BmapCreate.py    | Creates a bmap for a given file.                    |
 |   | - BmapCopy.py      | Implements copying of an image using its bmap.      |
-|   | - Fiemap.py        | Implements python API to the Linux FIEMAP ioctl.    |
+|   | - Filemap.py       | Allows for reading files' block map.                |
 |   | - BmapHelpers.py   | Just helper functions used all over the project.    |
 |   | - TransRead.py     | Provides a transparent way to read various kind of  |
 |   |                    | files (compressed, etc)                             |
diff --git a/tests/helpers.py b/tests/helpers.py
index c0d1607..dffdd03 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -31,9 +31,9 @@ def _create_random_sparse_file(file_obj, size):
     """
     Create a sparse file with randomly distributed holes. The mapped areas are
     filled with semi-random data. Returns a tuple containing 2 lists:
-      1. a list of mapped block ranges, same as 'Fiemap.get_mapped_ranges()'
+      1. a list of mapped block ranges, same as 'Filemap.get_mapped_ranges()'
       2. a list of unmapped block ranges (holes), same as
-         'Fiemap.get_unmapped_ranges()'
+         'Filemap.get_unmapped_ranges()'
     """
 
     file_obj.truncate(0)
@@ -113,9 +113,9 @@ def generate_test_files(max_size=4*1024*1024, directory=None, delete=True):
     The generator yields tuples consisting of the following elements:
       1. the test file object
       2. file size in bytes
-      3. a list of mapped block ranges, same as 'Fiemap.get_mapped_ranges()'
+      3. a list of mapped block ranges, same as 'Filemap.get_mapped_ranges()'
       4. a list of unmapped block ranges (holes), same as
-         'Fiemap.get_unmapped_ranges()'
+         'Filemap.get_unmapped_ranges()'
     """
 
     #
diff --git a/tests/test_api_base.py b/tests/test_api_base.py
index ee60164..c073205 100644
--- a/tests/test_api_base.py
+++ b/tests/test_api_base.py
@@ -39,7 +39,7 @@ except ImportError:
     import unittest
 
 from tests import helpers
-from bmaptools import BmapCreate, Fiemap, TransRead
+from bmaptools import BmapCreate, Filemap, TransRead
 
 class Error(Exception):
     """A class for exceptions generated by this test."""
@@ -51,11 +51,11 @@ def _compare_holes(file1, file2):
     The 'file1' and 'file2' arguments may be full file paths or file objects.
     """
 
-    fiemap1 = Fiemap.Fiemap(file1)
-    fiemap2 = Fiemap.Fiemap(file2)
+    filemap1 = Filemap.Fiemap(file1)
+    filemap2 = Filemap.Fiemap(file2)
 
-    iterator1 = fiemap1.get_unmapped_ranges(0, fiemap1.blocks_cnt)
-    iterator2 = fiemap2.get_unmapped_ranges(0, fiemap2.blocks_cnt)
+    iterator1 = filemap1.get_unmapped_ranges(0, filemap1.blocks_cnt)
+    iterator2 = filemap2.get_unmapped_ranges(0, filemap2.blocks_cnt)
 
     iterator = itertools.izip_longest(iterator1, iterator2)
     for range1, range2 in iterator:
diff --git a/tests/test_fiemap.py b/tests/test_fiemap.py
deleted file mode 100644
index 621aa72..0000000
--- a/tests/test_fiemap.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright (c) 2012-2013 Intel, Inc.
-# License: GPLv2
-# Author: Artem Bityutskiy <artem.bityutskiy at linux.intel.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License, version 2,
-# as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-
-"""
-This test verifies Fiemap module functionality. It generates random sparse
-files and makes sure FIEMAP returns correct information about the holes.
-"""
-
-# Disable the following pylint recommendations:
-#   *  Too many public methods - R0904
-#   *  Too many arguments - R0913
-# pylint: disable=R0904
-# pylint: disable=R0913
-
-
-import random
-import itertools
-
-# This is a work-around for Centos 6
-try:
-    import unittest2 as unittest # pylint: disable=F0401
-except ImportError:
-    import unittest
-
-
-import tests.helpers
-from bmaptools import Fiemap
-
-class Error(Exception):
-    """A class for exceptions generated by this test."""
-    pass
-
-def _check_ranges(f_image, fiemap, first_block, blocks_cnt,
-                  ranges, ranges_type):
-    """
-    This is a helper function for '_do_test()' which compares the correct
-    'ranges' list of mapped or unmapped blocks ranges for file object 'f_image'
-    with what the Fiemap module reports. The 'ranges_type' argument defines
-    whether the 'ranges' list is a list of mapped or unmapped blocks. The
-    'first_block' and 'blocks_cnt' define the subset of blocks in 'f_image'
-    that should be verified by this function.
-    """
-
-    if ranges_type is "mapped":
-        fiemap_iterator = fiemap.get_mapped_ranges(first_block, blocks_cnt)
-    elif ranges_type is "unmapped":
-        fiemap_iterator = fiemap.get_unmapped_ranges(first_block, blocks_cnt)
-    else:
-        raise Error("incorrect list type")
-
-    last_block = first_block + blocks_cnt - 1
-
-    # The 'ranges' list contains all ranges, from block zero to the last
-    # block. However, we are conducting a test for 'blocks_cnt' of blocks
-    # starting from block 'first_block'. Create an iterator which filters
-    # those block ranges from the 'ranges' list, that are out of the
-    # 'first_block'/'blocks_cnt' file region.
-    ranges_iterator = ( x for x in ranges if x[1] >= first_block and
-                                             x[0] <= last_block )
-    iterator = itertools.izip_longest(ranges_iterator, fiemap_iterator)
-
-    # Iterate over both - the (filtered) 'ranges' list which contains correct
-    # ranges and the Fiemap generator, and verify the mapped/unmapped ranges
-    # returned by the Fiemap module.
-    for correct, check in iterator:
-
-        # The first and the last range of the filtered 'ranges' list may still
-        # be out of the limit - correct them in this case
-        if correct[0] < first_block:
-            correct = (first_block, correct[1])
-        if correct[1] > last_block:
-            correct = (correct[0], last_block)
-
-        if check[0] > check[1] or check != correct:
-            raise Error("bad or unmatching %s range for file '%s': correct "
-                        "is %d-%d, get_%s_ranges(%d, %d) returned %d-%d"
-                        % (ranges_type, f_image.name, correct[0], correct[1],
-                           ranges_type, first_block, blocks_cnt,
-                           check[0], check[1]))
-
-def _do_test(f_image, mapped, unmapped, buf_size=Fiemap.DEFAULT_BUFFER_SIZE):
-    """
-    Verify that Fiemap reports the correct mapped and unmapped areas for the
-    'f_image' file object. The 'mapped' and 'unmapped' lists contain the
-    correct ranges. The 'buf_size' argument specifies the internal buffer size
-    of the 'Fiemap' class.
-    """
-
-    # Make sure that Fiemap's get_mapped_ranges() returns the same ranges as
-    # we have in the 'mapped' list.
-    fiemap = Fiemap.Fiemap(f_image, buf_size)
-
-    # Check both 'get_mapped_ranges()' and 'get_unmapped_ranges()' for the
-    # entire file.
-    first_block = 0
-    blocks_cnt = fiemap.blocks_cnt
-    _check_ranges(f_image, fiemap, first_block, blocks_cnt, mapped, "mapped")
-    _check_ranges(f_image, fiemap, first_block, blocks_cnt, unmapped,
-                  "unmapped")
-
-    # Select a random area in the file and repeat the test few times
-    for _ in xrange(0, 10):
-        first_block = random.randint(0, fiemap.blocks_cnt - 1)
-        blocks_cnt = random.randint(1, fiemap.blocks_cnt - first_block)
-        _check_ranges(f_image, fiemap, first_block, blocks_cnt, mapped,
-                      "mapped")
-        _check_ranges(f_image, fiemap, first_block, blocks_cnt, unmapped,
-                      "unmapped")
-
-class TestCreateCopy(unittest.TestCase):
-    """
-    The test class for this unit tests. Basically executes the '_do_test()'
-    function for different sparse files.
-    """
-
-    def test(self): # pylint: disable=R0201
-        """
-        The test entry point. Executes the '_do_test()' function for files of
-        different sizes, holes distribution and format.
-        """
-
-        # Delete all the test-related temporary files automatically
-        delete = True
-        # Create all the test-related temporary files in current directory (the
-        # default "/tmp" will not work in case of tmpfs which does not support
-        # FIEMAP).
-        directory = '.'
-        # Maximum size of the random files used in this test
-        max_size = 16 * 1024 * 1024
-
-        iterator = tests.helpers.generate_test_files(max_size, directory,
-                                                     delete)
-        for f_image, _, mapped, unmapped in iterator:
-            _do_test(f_image, mapped, unmapped)
-            _do_test(f_image, mapped, unmapped, Fiemap.MIN_BUFFER_SIZE)
-            _do_test(f_image, mapped, unmapped, Fiemap.MIN_BUFFER_SIZE * 2)
-            _do_test(f_image, mapped, unmapped, Fiemap.DEFAULT_BUFFER_SIZE / 2)
-            _do_test(f_image, mapped, unmapped, Fiemap.DEFAULT_BUFFER_SIZE * 2)
diff --git a/tests/test_filemap.py b/tests/test_filemap.py
new file mode 100644
index 0000000..6de2cee
--- /dev/null
+++ b/tests/test_filemap.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2012-2013 Intel, Inc.
+# License: GPLv2
+# Author: Artem Bityutskiy <artem.bityutskiy at linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+"""
+This test verifies 'Filemap' module functionality. It generates random sparse
+files and makes sure the module returns correct information about the holes.
+"""
+
+# Disable the following pylint recommendations:
+#   *  Too many public methods - R0904
+#   *  Too many arguments - R0913
+# pylint: disable=R0904
+# pylint: disable=R0913
+
+
+import random
+import itertools
+
+# This is a work-around for Centos 6
+try:
+    import unittest2 as unittest # pylint: disable=F0401
+except ImportError:
+    import unittest
+
+
+import tests.helpers
+from bmaptools import Filemap
+
+class Error(Exception):
+    """A class for exceptions generated by this test."""
+    pass
+
+def _check_ranges(f_image, filemap, first_block, blocks_cnt,
+                  ranges, ranges_type):
+    """
+    This is a helper function for '_do_test()' which compares the correct
+    'ranges' list of mapped or unmapped blocks ranges for file object 'f_image'
+    with what the 'Filemap' module reports. The 'ranges_type' argument defines
+    whether the 'ranges' list is a list of mapped or unmapped blocks. The
+    'first_block' and 'blocks_cnt' define the subset of blocks in 'f_image'
+    that should be verified by this function.
+    """
+
+    if ranges_type is "mapped":
+        filemap_iterator = filemap.get_mapped_ranges(first_block, blocks_cnt)
+    elif ranges_type is "unmapped":
+        filemap_iterator = filemap.get_unmapped_ranges(first_block, blocks_cnt)
+    else:
+        raise Error("incorrect list type")
+
+    last_block = first_block + blocks_cnt - 1
+
+    # The 'ranges' list contains all ranges, from block zero to the last
+    # block. However, we are conducting a test for 'blocks_cnt' of blocks
+    # starting from block 'first_block'. Create an iterator which filters
+    # those block ranges from the 'ranges' list, that are out of the
+    # 'first_block'/'blocks_cnt' file region.
+    ranges_iterator = ( x for x in ranges if x[1] >= first_block and
+                                             x[0] <= last_block )
+    iterator = itertools.izip_longest(ranges_iterator, filemap_iterator)
+
+    # Iterate over both - the (filtered) 'ranges' list which contains correct
+    # ranges and the Filemap generator, and verify the mapped/unmapped ranges
+    # returned by the 'Filemap' module.
+    for correct, check in iterator:
+
+        # The first and the last range of the filtered 'ranges' list may still
+        # be out of the limit - correct them in this case
+        if correct[0] < first_block:
+            correct = (first_block, correct[1])
+        if correct[1] > last_block:
+            correct = (correct[0], last_block)
+
+        if check[0] > check[1] or check != correct:
+            raise Error("bad or unmatching %s range for file '%s': correct "
+                        "is %d-%d, get_%s_ranges(%d, %d) returned %d-%d"
+                        % (ranges_type, f_image.name, correct[0], correct[1],
+                           ranges_type, first_block, blocks_cnt,
+                           check[0], check[1]))
+
+def _do_test(f_image, mapped, unmapped, buf_size=Filemap.DEFAULT_BUFFER_SIZE):
+    """
+    Verify that Filemap reports the correct mapped and unmapped areas for the
+    'f_image' file object. The 'mapped' and 'unmapped' lists contain the
+    correct ranges. The 'buf_size' argument specifies the internal buffer size
+    of the 'Filemap' class.
+    """
+
+    # Make sure that 'Filemap' module's 'get_mapped_ranges()' returns the same
+    # ranges as we have in the 'mapped' list.
+    filemap = Filemap.Fiemap(f_image, buf_size)
+
+    # Check both 'get_mapped_ranges()' and 'get_unmapped_ranges()' for the
+    # entire file.
+    first_block = 0
+    blocks_cnt = filemap.blocks_cnt
+    _check_ranges(f_image, filemap, first_block, blocks_cnt, mapped, "mapped")
+    _check_ranges(f_image, filemap, first_block, blocks_cnt, unmapped,
+                  "unmapped")
+
+    # Select a random area in the file and repeat the test few times
+    for _ in xrange(0, 10):
+        first_block = random.randint(0, filemap.blocks_cnt - 1)
+        blocks_cnt = random.randint(1, filemap.blocks_cnt - first_block)
+        _check_ranges(f_image, filemap, first_block, blocks_cnt, mapped,
+                      "mapped")
+        _check_ranges(f_image, filemap, first_block, blocks_cnt, unmapped,
+                      "unmapped")
+
+class TestCreateCopy(unittest.TestCase):
+    """
+    The test class for this unit tests. Basically executes the '_do_test()'
+    function for different sparse files.
+    """
+
+    def test(self): # pylint: disable=R0201
+        """
+        The test entry point. Executes the '_do_test()' function for files of
+        different sizes, holes distribution and format.
+        """
+
+        # Delete all the test-related temporary files automatically
+        delete = True
+        # Create all the test-related temporary files in current directory (the
+        # default "/tmp" will not work in case of tmpfs which does not support
+        # FIEMAP).
+        directory = '.'
+        # Maximum size of the random files used in this test
+        max_size = 16 * 1024 * 1024
+
+        iterator = tests.helpers.generate_test_files(max_size, directory,
+                                                     delete)
+        for f_image, _, mapped, unmapped in iterator:
+            _do_test(f_image, mapped, unmapped)
+            _do_test(f_image, mapped, unmapped, Filemap.MIN_BUFFER_SIZE)
+            _do_test(f_image, mapped, unmapped, Filemap.MIN_BUFFER_SIZE * 2)
+            _do_test(f_image, mapped, unmapped, Filemap.DEFAULT_BUFFER_SIZE / 2)
+            _do_test(f_image, mapped, unmapped, Filemap.DEFAULT_BUFFER_SIZE * 2)
-- 
1.8.3.1




More information about the Bmap-tools mailing list