From patchwork Tue Jun 14 09:49:12 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Mattias Backman X-Patchwork-Id: 1893 Return-Path: Delivered-To: unknown Received: from imap.gmail.com (74.125.47.109) by localhost6.localdomain6 with IMAP4-SSL; 14 Jun 2011 16:46:27 -0000 Delivered-To: patches@linaro.org Received: by 10.52.183.130 with SMTP id em2cs52703vdc; Tue, 14 Jun 2011 02:49:14 -0700 (PDT) Received: by 10.227.128.141 with SMTP id k13mr6274648wbs.81.1308044953461; Tue, 14 Jun 2011 02:49:13 -0700 (PDT) Received: from adelie.canonical.com (adelie.canonical.com [91.189.90.139]) by mx.google.com with ESMTP id et14si14836287wbb.34.2011.06.14.02.49.13; Tue, 14 Jun 2011 02:49:13 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.139 as permitted sender) client-ip=91.189.90.139; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.139 as permitted sender) smtp.mail=bounces@canonical.com Received: from loganberry.canonical.com ([91.189.90.37]) by adelie.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1QWQFE-0004Jk-Rt for ; Tue, 14 Jun 2011 09:49:12 +0000 Received: from loganberry.canonical.com (localhost [127.0.0.1]) by loganberry.canonical.com (Postfix) with ESMTP id CC9E12E899B for ; Tue, 14 Jun 2011 09:49:12 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: linaro-image-tools X-Launchpad-Branch: ~linaro-image-tools/linaro-image-tools/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 355 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [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> Date: Tue, 14 Jun 2011 09:49:12 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="13216"; Instance="initZopeless config overlay" X-Launchpad-Hash: a5ad037cd572ca9a25923309f89d41decebd66c7 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 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 === 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