[Branch,~linaro-image-tools/linaro-image-tools/trunk] Rev 355: Merge lp:~mabac/linaro-image-tools/image-support-for-android; Add --image_file option to linaro-a...

Message ID 20110614094912.14176.21265.launchpad@loganberry.canonical.com
State Accepted
Headers show

Commit Message

Mattias Backman June 14, 2011, 9:49 a.m.
Merge authors:
  Mattias Backman (mabac)
Related merge proposals:
  https://code.launchpad.net/~mabac/linaro-image-tools/image-support-for-android/+merge/62950
  proposed by: Mattias Backman (mabac)
  review: Approve - Loïc Minier (lool)
  https://code.launchpad.net/~mabac/linaro-image-tools/sector-size/+merge/62884
  proposed by: Mattias Backman (mabac)
  review: Approve - Loïc Minier (lool)
------------------------------------------------------------
revno: 355 [merge]
committer: Mattias Backman <mattias.backman@linaro.org>
branch nick: linaro-image-tools
timestamp: Tue 2011-06-14 11:39:13 +0200
message:
  Merge lp:~mabac/linaro-image-tools/image-support-for-android; Add --image_file option to linaro-android-media-create as requested in lp:787972
modified:
  linaro-android-media-create
  linaro_image_tools/media_create/__init__.py
  linaro_image_tools/media_create/partitions.py
  linaro_image_tools/media_create/tests/test_media_create.py


--
lp:linaro-image-tools
https://code.launchpad.net/~linaro-image-tools/linaro-image-tools/trunk

You are subscribed to branch lp:linaro-image-tools.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-image-tools/linaro-image-tools/trunk/+edit-subscription

Patch

=== modified file 'linaro-android-media-create'
--- linaro-android-media-create	2011-04-29 10:53:52 +0000
+++ linaro-android-media-create	2011-05-26 09:16:35 +0000
@@ -118,9 +118,8 @@ 
     if media.is_block_device:
         if not confirm_device_selection_and_ensure_it_is_ready(args.device):
             sys.exit(1)
-    elif not args.should_format_rootfs or not args.should_format_bootfs:
-        print ("Do not use --no-boot or --no-part in conjunction with "
-               "--image_file.")
+    elif not args.should_create_partitions:
+        print ("Do not use --no-part in conjunction with --image_file.")
         sys.exit(1)
     else:
         # All good, move on.
@@ -137,8 +136,8 @@ 
     # Create partitions    
     boot_partition, system_partition, cache_partition, \
         data_partition, sdcard_partition = setup_android_partitions( \
-        board_config, media, args.boot_label, args.should_create_partitions,
-        args.should_align_boot_part)
+        board_config, media, args.image_size, args.boot_label,
+        args.should_create_partitions, args.should_align_boot_part)
 
     populate_partition(BOOT_DIR + "/boot", BOOT_DISK, boot_partition)
     board_config.populate_boot_script(boot_partition, BOOT_DISK, args.consoles)

=== modified file 'linaro_image_tools/media_create/__init__.py'
--- linaro_image_tools/media_create/__init__.py	2011-04-27 13:43:59 +0000
+++ linaro_image_tools/media_create/__init__.py	2011-05-26 09:16:35 +0000
@@ -124,8 +124,16 @@ 
 def get_android_args_parser():
     """Get the ArgumentParser for the arguments given on the command line."""
     parser = argparse.ArgumentParser()
+    group = parser.add_mutually_exclusive_group(required=True)
+    group.add_argument(
+        '--mmc', dest='device', help='The storage device to use.')
+    group.add_argument(
+        '--image_file', dest='device',
+        help='File where we should write the image file.')
     parser.add_argument(
-        '--mmc', required=True, dest='device', help='The storage device to use.')
+        '--image_size', default='2G',
+        help=('The image size, specified in mega/giga bytes (e.g. 3000M or '
+              '3G); use with --image_file only'))
     parser.add_argument(
         '--dev', required=True, dest='board', choices=ANDROID_KNOWN_BOARDS,
         help='Generate an SD card or image for the given board.')

=== modified file 'linaro_image_tools/media_create/partitions.py'
--- linaro_image_tools/media_create/partitions.py	2011-05-06 13:58:54 +0000
+++ linaro_image_tools/media_create/partitions.py	2011-05-31 06:45:22 +0000
@@ -41,22 +41,39 @@ 
 UDISKS = "org.freedesktop.UDisks"
 
 
-def setup_android_partitions(board_config, media, bootfs_label,
+def setup_android_partitions(board_config, media, image_size, bootfs_label,
                      should_create_partitions, should_align_boot_part=False):
     cylinders = None
+    if not media.is_block_device:
+        image_size_in_bytes = convert_size_to_bytes(image_size)
+        cylinders = image_size_in_bytes / CYLINDER_SIZE
+        proc = cmd_runner.run(
+            ['dd', 'of=%s' % media.path,
+             'bs=1', 'seek=%s' % image_size_in_bytes, 'count=0'],
+            stderr=open('/dev/null', 'w'))
+        proc.wait()
 
     if should_create_partitions:
         create_partitions(
             board_config, media, HEADS, SECTORS, cylinders,
             should_align_boot_part=should_align_boot_part)
 
-    bootfs, system, cache, data, sdcard = \
-        get_android_partitions_for_media (media, board_config)
-    ensure_partition_is_not_mounted(bootfs)
-    ensure_partition_is_not_mounted(system)
-    ensure_partition_is_not_mounted(cache)
-    ensure_partition_is_not_mounted(data)
-    ensure_partition_is_not_mounted(sdcard)
+    if media.is_block_device:
+        bootfs, system, cache, data, sdcard = \
+            get_android_partitions_for_media (media, board_config)
+        ensure_partition_is_not_mounted(bootfs)
+        ensure_partition_is_not_mounted(system)
+        ensure_partition_is_not_mounted(cache)
+        ensure_partition_is_not_mounted(data)
+        ensure_partition_is_not_mounted(sdcard)
+    else:
+        partitions = get_android_loopback_devices(media.path)
+        bootfs = partitions[0]
+        system = partitions[1]
+        cache = partitions[2]
+        data = partitions[4]
+        sdcard = partitions[5]
+            
 
     print "\nFormating boot partition\n"
     proc = cmd_runner.run(
@@ -195,6 +212,21 @@ 
     return boot_device, root_device
 
 
+def get_android_loopback_devices(image_file):
+    """Return the loopback devices for the given image file.
+
+    Assumes a particular order of devices in the file.
+    Register the loopback devices as well.
+    """
+    devices = []
+    device_info = calculate_android_partition_size_and_offset(image_file)
+    for device_offset, device_size in device_info:
+        devices.append(register_loopback(image_file, device_offset,
+                                         device_size))
+
+    return devices
+
+
 def register_loopback(image_file, offset, size):
     """Register a loopback device with an atexit handler to de-register it."""
     def undo(device):
@@ -231,8 +263,8 @@ 
                 partition.type)
         if 'boot' in partition.getFlagsAsString():
             geometry = partition.geometry
-            vfat_offset = geometry.start * 512
-            vfat_size = geometry.length * 512
+            vfat_offset = geometry.start * SECTOR_SIZE
+            vfat_size = geometry.length * SECTOR_SIZE
             vfat_partition = partition
         elif vfat_partition is not None:
             # next partition after boot partition is the root partition
@@ -241,8 +273,8 @@ 
             # iterate disk.partitions which only returns
             # parted.PARTITION_NORMAL partitions
             geometry = partition.geometry
-            linux_offset = geometry.start * 512
-            linux_size = geometry.length * 512
+            linux_offset = geometry.start * SECTOR_SIZE
+            linux_size = geometry.length * SECTOR_SIZE
             linux_partition = partition
             break
 
@@ -252,6 +284,33 @@ 
         "Couldn't find root partition on %s" % image_file)
     return vfat_size, vfat_offset, linux_size, linux_offset
 
+
+def calculate_android_partition_size_and_offset(image_file):
+    """Return the size and offset of the android partitions.
+
+    Both the size and offset are in bytes.
+
+    :param image_file: A string containing the path to the image_file.
+    :return: A list of (offset, size) pairs.
+    """
+    # Here we can use parted.Device to read the partitions because we're
+    # reading from a regular file rather than a block device.  If it was a
+    # block device we'd need root rights.
+    disk = Disk(Device(image_file))
+    partition_info = []
+    for partition in disk.partitions:
+        geometry = partition.geometry
+        partition_info.append((geometry.start * SECTOR_SIZE,
+                               geometry.length * SECTOR_SIZE))
+        # NB: don't use vfat_partition.nextPartition() as that might return
+        # a partition of type PARTITION_FREESPACE; it's much easier to
+        # iterate disk.partitions which only returns
+        # parted.PARTITION_NORMAL partitions
+
+    assert len(partition_info) == 6
+    return partition_info
+
+
 def get_android_partitions_for_media(media, board_config):
     """Return the device files for all the Android partitions of media.
 
@@ -276,6 +335,14 @@ 
 
     assert boot_partition is not None, (
         "Could not find boot partition for %s" % media.path)
+    assert system_partition is not None, (
+        "Could not find system partition for %s" % media.path)
+    assert cache_partition is not None, (
+        "Could not find cache partition for %s" % media.path)
+    assert data_partition is not None, (
+        "Could not find data partition for %s" % media.path)
+    assert sdcard_partition is not None, (
+        "Could not find sdcard partition for %s" % media.path)
 
     return boot_partition, system_partition, cache_partition, \
         data_partition, sdcard_partition

=== modified file 'linaro_image_tools/media_create/tests/test_media_create.py'
--- linaro_image_tools/media_create/tests/test_media_create.py	2011-05-26 20:02:13 +0000
+++ linaro_image_tools/media_create/tests/test_media_create.py	2011-06-14 08:01:43 +0000
@@ -71,10 +71,12 @@ 
     HEADS,
     SECTORS,
     calculate_partition_size_and_offset,
+    calculate_android_partition_size_and_offset,
     convert_size_to_bytes,
     create_partitions,
     ensure_partition_is_not_mounted,
     get_boot_and_root_loopback_devices,
+    get_android_loopback_devices,
     get_boot_and_root_partitions_for_media,
     Media,
     run_sfdisk_commands,
@@ -878,6 +880,20 @@ 
         # Stub time.sleep() as create_partitions() use that.
         self.orig_sleep = time.sleep
         time.sleep = lambda s: None
+        self.android_image_size = 256 * 1024**2
+        # Extended partition info takes 32 sectors from the first ext partition
+        ext_part_size = 32
+        self.android_offsets_and_sizes = [
+            (63 * SECTOR_SIZE, 32768 * SECTOR_SIZE),
+            (32831 * SECTOR_SIZE, 65536 * SECTOR_SIZE),
+            (98367 * SECTOR_SIZE, 65536 * SECTOR_SIZE),
+            (294975 * SECTOR_SIZE, (self.android_image_size - 
+                                     294975 * SECTOR_SIZE)),
+            ((294975 + ext_part_size) * SECTOR_SIZE,
+             (131072 - ext_part_size) * SECTOR_SIZE),
+            ((426047 + ext_part_size) * SECTOR_SIZE, 
+             self.android_image_size - (426047 + ext_part_size) * SECTOR_SIZE)
+            ]
 
     def tearDown(self):
         super(TestPartitionSetup, self).tearDown()
@@ -886,7 +902,13 @@ 
     def _create_tmpfile(self):
         # boot part at +8 MiB, root part at +16 MiB
         return self._create_qemu_img_with_partitions(
-            '16384,15746,0x0C,*\n32768,,,-')
+            '16384,15746,0x0C,*\n32768,,,-', '30M')
+
+    def _create_android_tmpfile(self):
+        # boot, system, cache, (extended), userdata and sdcard partitions
+        return self._create_qemu_img_with_partitions(
+            '63,32768,0x0C,*\n32831,65536,L\n98367,65536,L\n294975,-,E\n' \
+                '294975,131072,L\n426047,,,-', '%s' % self.android_image_size)
 
     def test_convert_size_no_suffix(self):
         self.assertEqual(524288, convert_size_to_bytes('524288'))
@@ -908,10 +930,19 @@ 
             [8061952L, 8388608L, 14680064L, 16777216L],
             [vfat_size, vfat_offset, linux_size, linux_offset])
 
+    def test_calculate_android_partition_size_and_offset(self):
+        tmpfile = self._create_android_tmpfile()
+        device_info = calculate_android_partition_size_and_offset(tmpfile)
+        # We use map(None, ...) since it would catch if the lists are not of
+        # equal length and zip() would not in all cases.
+        for device_pair, expected_pair in map(None, device_info,
+                                              self.android_offsets_and_sizes):
+            self.assertEqual(device_pair, expected_pair)
+
     def test_partition_numbering(self):
         # another Linux partition at +24 MiB after the boot/root parts
         tmpfile = self._create_qemu_img_with_partitions(
-            '16384,15746,0x0C,*\n32768,15427,,-\n49152,,,-')
+            '16384,15746,0x0C,*\n32768,15427,,-\n49152,,,-', '30M')
         vfat_size, vfat_offset, linux_size, linux_offset = (
             calculate_partition_size_and_offset(tmpfile))
         # check that the linux partition offset starts at +16 MiB so that it's
@@ -941,10 +972,10 @@ 
             ("%s%d" % (tmpfile, 2), "%s%d" % (tmpfile, 3)),
             get_boot_and_root_partitions_for_media(media, boards.Mx5Config))
 
-    def _create_qemu_img_with_partitions(self, sfdisk_commands):
+    def _create_qemu_img_with_partitions(self, sfdisk_commands, tempfile_size):
         tmpfile = self.createTempFileAsFixture()
         proc = cmd_runner.run(
-            ['dd', 'of=%s' % tmpfile, 'bs=1', 'seek=30M', 'count=0'],
+            ['dd', 'of=%s' % tmpfile, 'bs=1', 'seek=%s' % tempfile_size, 'count=0'],
             stderr=open('/dev/null', 'w'))
         proc.communicate()
         stdout, stderr = run_sfdisk_commands(
@@ -1000,6 +1031,38 @@ 
              '%s losetup -d ' % sudo_args],
             popen_fixture.mock.commands_executed)
 
+    def test_get_android_loopback_devices(self):
+        tmpfile = self._create_android_tmpfile()
+        atexit_fixture = self.useFixture(MockSomethingFixture(
+            atexit, 'register', AtExitRegister()))
+        popen_fixture = self.useFixture(MockCmdRunnerPopenFixture())
+        # We can't test the return value of get_boot_and_root_loopback_devices
+        # because it'd require running losetup as root, so we just make sure
+        # it calls losetup correctly.
+        get_android_loopback_devices(tmpfile)
+        self.assertEqual(
+            ['%s losetup -f --show %s --offset %s --sizelimit %s'
+                % (sudo_args, tmpfile, offset, size) for (offset, size) in 
+             self.android_offsets_and_sizes],
+            popen_fixture.mock.commands_executed)
+
+        # get_boot_and_root_loopback_devices will also setup two exit handlers
+        # to de-register the loopback devices set up above.
+        self.assertEqual(6, len(atexit_fixture.mock.funcs))
+        popen_fixture.mock.calls = []
+        atexit_fixture.mock.run_funcs()
+        # We did not really run losetup above (as it requires root) so here we
+        # don't have a device to pass to 'losetup -d', but when a device is
+        # setup it is passed to the atexit handler.
+        self.assertEquals(
+            ['%s losetup -d ' % sudo_args,
+             '%s losetup -d ' % sudo_args,
+             '%s losetup -d ' % sudo_args,
+             '%s losetup -d ' % sudo_args,
+             '%s losetup -d ' % sudo_args,
+             '%s losetup -d ' % sudo_args],
+            popen_fixture.mock.commands_executed)
+
     def test_setup_partitions_for_image_file(self):
         # In practice we could pass an empty image file to setup_partitions,
         # but here we mock Popen() and thanks to that the image is not setup