Add block map functionality
authorArtem Bityutskiy <artem.bityutskiy@intel.com>
Fri, 28 Sep 2012 10:26:56 +0000 (13:26 +0300)
committerArtem Bityutskiy <artem.bityutskiy@intel.com>
Mon, 8 Oct 2012 08:51:29 +0000 (11:51 +0300)
This patch add a 'generate_bmap()' method to the RawImageCreator
class and makes the '--generate-bmap' option actually work. The generated
file has '.bmap' extention and the same name as the raw image file.

Signed-off-by: Artem Bityutskiy <artem.bityutskiy@intel.com>
Change-Id: I54ab3bc7274160c697dce4f8e296b46dda28cdd2

mic/imager/raw.py
plugins/imager/raw_plugin.py

index c95eafa..d543cb2 100644 (file)
@@ -18,7 +18,9 @@
 import os
 import stat
 import shutil
-
+from fcntl import ioctl
+from struct import pack, unpack
+from itertools import groupby
 from urlgrabber import progress
 
 from mic import kickstart, msger
@@ -503,3 +505,140 @@ class RawImageCreator(BaseImageCreator):
         cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
         cfg.write(xml)
         cfg.close()
+
+    def _bmap_file_start(self, block_size, image_size, blocks_cnt):
+        """ A helper function which generates the starting contents of the
+        block map file: the header comment, image size, block size, etc. """
+
+        xml = "<?xml version=\"1.0\" ?>\n\n"
+        xml += "<!-- This file contains block map for an image file. The block map\n"
+        xml += "     is basically a list of block numbers of the image file. It lists\n"
+        xml += "     only those blocks which contain data (boot sector, partition\n"
+        xml += "     table, file-system metadata, files, directories, extents, etc).\n"
+        xml += "     These blocks have to be copied to the target device. The other\n"
+        xml += "     blocks do not contain any useful data and do not have to be\n"
+        xml += "     copied to the target device. Thus, using the block map users can\n"
+        xml += "     flash the image faster. The block map is just an optimization. -->\n\n"
+
+        xml += "<bmap version=\"1.0\">\n"
+        xml += "\t<!-- Image size in bytes -->\n"
+        xml += "\t<ImageSize> %u </ImageSize>\n\n" % image_size
+
+        xml += "\t<!-- Image size, but human-friendly -->\n"
+        xml += "\t<ImageSizeHuman> %s </ImageSizeHuman>\n\n" \
+               % misc.human_size(image_size)
+
+        xml += "\t<!-- Size of a block in bytes -->\n"
+        xml += "\t<BlockSize> %u </BlockSize>\n\n" % block_size
+
+        xml += "\t<!-- Size of a block in bytes -->\n"
+        xml += "\t<BlocksCount> %u </BlocksCount>\n\n" % blocks_cnt
+
+        xml += "\t<!-- The block map which consists of elements which may\n"
+        xml += "\t     either be a range of blocks or a single block -->\n"
+        xml += "\t<BlockMap>\n"
+
+        return xml
+
+    def _bmap_file_end(self, mapped_cnt, block_size, blocks_cnt):
+        """ A helper funstion which generates the final parts of the block map
+        file: the ending tags and the information about the amount of mapped
+        blocks. """
+
+        xml = "\t</BlockMap>\n\n"
+
+        xml += "\t<!-- Count of mapped blocks -->\n"
+        xml += "\t<MappedBlocksCount> %u </MappedBlocksCount>\n\n" % mapped_cnt
+
+        xml += "\t<!-- Amount of mapped data in a human-friendly form -->\n"
+        xml += "\t<MappedSize> %s </MappedSize>\n\n" \
+                % misc.human_size(mapped_cnt * block_size)
+
+        xml += "\t<!-- Amount of mapped blocks as a percent of total amount of\n"
+        xml += "\t     blocks -->\n"
+        xml += "\t<MappedBlocksCountPercent> %.1f </MappedBlocksCountPercent>\n" % \
+                ((mapped_cnt * 100.0) / blocks_cnt)
+        xml += "</bmap>"
+
+        return xml
+
+    def _get_ranges(self, f_image, blocks_cnt):
+        """ A helper for 'generate_bmap()' which generates ranges of mapped
+        blocks. It uses the FIBMAP ioctl to check which blocks are mapped. Of
+        course, the image file must have been created as a sparse file
+        originally, otherwise all blocks will be mapped. And it is also
+        essential to generate the block map before the file had been copied
+        anywhere or compressed, because othewise we lose the information about
+        unmapped blocks. """
+
+        def is_mapped(block):
+            """ Returns True if block 'block' of the image file is mapped and
+            False otherwise.
+
+            Implementation details: this function uses the FIBMAP ioctl (number
+            1) to get detect whether 'block' is mapped to a disk block. The ioctl
+            returns zero if 'block' is not mapped and non-zero disk block number
+            if it is mapped. """
+
+            return unpack('I', ioctl(f_image, 1, pack('I', block)))[0] != 0
+
+        for key, group in groupby(xrange(blocks_cnt), is_mapped):
+            if key:
+                # Find the first and the last elements of the group
+                first = group.next()
+                last = first
+                for last in group:
+                    pass
+                yield first, last
+
+
+    def generate_bmap(self):
+        """ Generate block map file for an image. The idea is that while disk
+        images we generate may be large (e.g., 4GiB), they may actually contain
+        only little real data, e.g., 512MiB. This data are files, directories,
+        file-system meta-data, partition table, etc. In other words, when
+        flashing the image to the target device, you do not have to copy all the
+        4GiB of data, you can copy only 512MiB of it, which is 4 times faster.
+
+        This function generates the block map file for an arbitrary image that
+        mic has generated. The block map file is basically an XML file which
+        contains a list of blocks which have to be copied to the target device.
+        The other blocks are not used and there is no need to copy them.
+
+        This function assumes the image file was originally created as a sparse
+        file. To generate the block map we use the FIBMAP ioctl. """
+
+        if self.bmap_needed is None:
+            return
+
+        msger.info("Generating the map file(s)")
+
+        for name in self.__disks.keys():
+            image = self._full_path(self.__imgdir, name, self.__disk_format)
+            bmap_file = self._full_path(self.__imgdir, name, "bmap")
+
+            msger.debug("Generating block map file '%s'" % bmap_file)
+
+            image_size = os.path.getsize(image)
+
+            with open(bmap_file, "w") as f_bmap, open(image) as f_image:
+                # Get the block size of the host file-system for the image file
+                # by calling the FIGETBSZ ioctl (number 2).
+                block_size = unpack('I', ioctl(f_image, 2, pack('I', 0)))[0]
+                blocks_cnt = (image_size + block_size - 1) / block_size
+
+                # Write general information to the block map file, without
+                # block map itself, which will be written next.
+                xml = self._bmap_file_start(block_size, image_size, blocks_cnt)
+                f_bmap.write(xml)
+
+                # Generate the block map and write it to the XML block map file
+                # as we go.
+                mapped_cnt = 0
+                for first, last in self._get_ranges(f_image, blocks_cnt):
+                    mapped_cnt += last - first + 1
+                    f_bmap.write("\t\t<Range> %s-%s </Range>\n" % (first, last))
+
+                # Finish the block map file
+                xml = self._bmap_file_end(mapped_cnt, block_size, blocks_cnt)
+                f_bmap.write(xml)
index 5f04562..2de058f 100644 (file)
@@ -114,6 +114,7 @@ class RawPlugin(ImagerPlugin):
             creator.configure(creatoropts["repomd"])
             creator.copy_kernel()
             creator.unmount()
+            creator.generate_bmap()
             creator.package(creatoropts["outdir"])
             if creatoropts['release'] is not None:
                 creator.release_output(ksconf, creatoropts['outdir'], creatoropts['release'])