diff options
Diffstat (limited to 'yocto-poky/scripts/lib/wic/utils/partitionedfs.py')
-rw-r--r-- | yocto-poky/scripts/lib/wic/utils/partitionedfs.py | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/yocto-poky/scripts/lib/wic/utils/partitionedfs.py b/yocto-poky/scripts/lib/wic/utils/partitionedfs.py new file mode 100644 index 0000000000..5a103bbc7e --- /dev/null +++ b/yocto-poky/scripts/lib/wic/utils/partitionedfs.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python -tt +# +# Copyright (c) 2009, 2010, 2011 Intel, Inc. +# Copyright (c) 2007, 2008 Red Hat, Inc. +# Copyright (c) 2008 Daniel P. Berrange +# Copyright (c) 2008 David P. Huff +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; version 2 of the License +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., 59 +# Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os +from wic import msger +from wic.utils.errors import ImageError +from wic.utils.oe.misc import exec_cmd, exec_native_cmd + +# Overhead of the MBR partitioning scheme (just one sector) +MBR_OVERHEAD = 1 + +# Overhead of the GPT partitioning scheme +GPT_OVERHEAD = 34 + +# Size of a sector in bytes +SECTOR_SIZE = 512 + +class Image(object): + """ + Generic base object for an image. + + An Image is a container for a set of DiskImages and associated + partitions. + """ + def __init__(self, native_sysroot=None): + self.disks = {} + self.partitions = [] + # Size of a sector used in calculations + self.sector_size = SECTOR_SIZE + self._partitions_layed_out = False + self.native_sysroot = native_sysroot + + def __add_disk(self, disk_name): + """ Add a disk 'disk_name' to the internal list of disks. Note, + 'disk_name' is the name of the disk in the target system + (e.g., sdb). """ + + if disk_name in self.disks: + # We already have this disk + return + + assert not self._partitions_layed_out + + self.disks[disk_name] = \ + {'disk': None, # Disk object + 'numpart': 0, # Number of allocate partitions + 'realpart': 0, # Number of partitions in the partition table + 'partitions': [], # Indexes to self.partitions + 'offset': 0, # Offset of next partition (in sectors) + # Minimum required disk size to fit all partitions (in bytes) + 'min_size': 0, + 'ptable_format': "msdos"} # Partition table format + + def add_disk(self, disk_name, disk_obj): + """ Add a disk object which have to be partitioned. More than one disk + can be added. In case of multiple disks, disk partitions have to be + added for each disk separately with 'add_partition()". """ + + self.__add_disk(disk_name) + self.disks[disk_name]['disk'] = disk_obj + + def __add_partition(self, part): + """ This is a helper function for 'add_partition()' which adds a + partition to the internal list of partitions. """ + + assert not self._partitions_layed_out + + self.partitions.append(part) + self.__add_disk(part['disk_name']) + + def add_partition(self, size, disk_name, mountpoint, source_file=None, fstype=None, + label=None, fsopts=None, boot=False, align=None, no_table=False, + part_type=None, uuid=None): + """ Add the next partition. Prtitions have to be added in the + first-to-last order. """ + + ks_pnum = len(self.partitions) + + # Converting kB to sectors for parted + size = size * 1024 / self.sector_size + + # We still need partition for "/" or non-subvolume + if mountpoint == "/" or not fsopts: + part = {'ks_pnum': ks_pnum, # Partition number in the KS file + 'size': size, # In sectors + 'mountpoint': mountpoint, # Mount relative to chroot + 'source_file': source_file, # partition contents + 'fstype': fstype, # Filesystem type + 'fsopts': fsopts, # Filesystem mount options + 'label': label, # Partition label + 'disk_name': disk_name, # physical disk name holding partition + 'device': None, # kpartx device node for partition + 'num': None, # Partition number + 'boot': boot, # Bootable flag + 'align': align, # Partition alignment + 'no_table' : no_table, # Partition does not appear in partition table + 'part_type' : part_type, # Partition type + 'uuid': uuid} # Partition UUID + + self.__add_partition(part) + + def layout_partitions(self, ptable_format="msdos"): + """ Layout the partitions, meaning calculate the position of every + partition on the disk. The 'ptable_format' parameter defines the + partition table format and may be "msdos". """ + + msger.debug("Assigning %s partitions to disks" % ptable_format) + + if self._partitions_layed_out: + return + + self._partitions_layed_out = True + + # Go through partitions in the order they are added in .ks file + for num in range(len(self.partitions)): + part = self.partitions[num] + + if not self.disks.has_key(part['disk_name']): + raise ImageError("No disk %s for partition %s" \ + % (part['disk_name'], part['mountpoint'])) + + if ptable_format == 'msdos' and part['part_type']: + # The --part-type can also be implemented for MBR partitions, + # in which case it would map to the 1-byte "partition type" + # filed at offset 3 of the partition entry. + raise ImageError("setting custom partition type is not " \ + "implemented for msdos partitions") + + # Get the disk where the partition is located + disk = self.disks[part['disk_name']] + disk['numpart'] += 1 + if not part['no_table']: + disk['realpart'] += 1 + disk['ptable_format'] = ptable_format + + if disk['numpart'] == 1: + if ptable_format == "msdos": + overhead = MBR_OVERHEAD + elif ptable_format == "gpt": + overhead = GPT_OVERHEAD + + # Skip one sector required for the partitioning scheme overhead + disk['offset'] += overhead + + if disk['realpart'] > 3: + # Reserve a sector for EBR for every logical partition + # before alignment is performed. + if ptable_format == "msdos": + disk['offset'] += 1 + + + if part['align']: + # If not first partition and we do have alignment set we need + # to align the partition. + # FIXME: This leaves a empty spaces to the disk. To fill the + # gaps we could enlargea the previous partition? + + # Calc how much the alignment is off. + align_sectors = disk['offset'] % (part['align'] * 1024 / self.sector_size) + + if align_sectors: + # If partition is not aligned as required, we need + # to move forward to the next alignment point + align_sectors = (part['align'] * 1024 / self.sector_size) - align_sectors + + msger.debug("Realignment for %s%s with %s sectors, original" + " offset %s, target alignment is %sK." % + (part['disk_name'], disk['numpart'], align_sectors, + disk['offset'], part['align'])) + + # increase the offset so we actually start the partition on right alignment + disk['offset'] += align_sectors + + part['start'] = disk['offset'] + disk['offset'] += part['size'] + + part['type'] = 'primary' + if not part['no_table']: + part['num'] = disk['realpart'] + else: + part['num'] = 0 + + if disk['ptable_format'] == "msdos": + if disk['realpart'] > 3: + part['type'] = 'logical' + part['num'] = disk['realpart'] + 1 + + disk['partitions'].append(num) + msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d " + "sectors (%d bytes)." \ + % (part['mountpoint'], part['disk_name'], part['num'], + part['start'], part['start'] + part['size'] - 1, + part['size'], part['size'] * self.sector_size)) + + # Once all the partitions have been layed out, we can calculate the + # minumim disk sizes. + for disk in self.disks.values(): + disk['min_size'] = disk['offset'] + if disk['ptable_format'] == "gpt": + disk['min_size'] += GPT_OVERHEAD + + disk['min_size'] *= self.sector_size + + def __create_partition(self, device, parttype, fstype, start, size): + """ Create a partition on an image described by the 'device' object. """ + + # Start is included to the size so we need to substract one from the end. + end = start + size - 1 + msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" % + (parttype, start, end, size)) + + cmd = "parted -s %s unit s mkpart %s" % (device, parttype) + if fstype: + cmd += " %s" % fstype + cmd += " %d %d" % (start, end) + + return exec_native_cmd(cmd, self.native_sysroot) + + def __format_disks(self): + self.layout_partitions() + + for dev in self.disks.keys(): + disk = self.disks[dev] + msger.debug("Initializing partition table for %s" % \ + (disk['disk'].device)) + exec_native_cmd("parted -s %s mklabel %s" % \ + (disk['disk'].device, disk['ptable_format']), + self.native_sysroot) + + msger.debug("Creating partitions") + + for part in self.partitions: + if part['num'] == 0: + continue + + disk = self.disks[part['disk_name']] + if disk['ptable_format'] == "msdos" and part['num'] == 5: + # Create an extended partition (note: extended + # partition is described in MBR and contains all + # logical partitions). The logical partitions save a + # sector for an EBR just before the start of a + # partition. The extended partition must start one + # sector before the start of the first logical + # partition. This way the first EBR is inside of the + # extended partition. Since the extended partitions + # starts a sector before the first logical partition, + # add a sector at the back, so that there is enough + # room for all logical partitions. + self.__create_partition(disk['disk'].device, "extended", + None, part['start'] - 1, + disk['offset'] - part['start'] + 1) + + if part['fstype'] == "swap": + parted_fs_type = "linux-swap" + elif part['fstype'] == "vfat": + parted_fs_type = "fat32" + elif part['fstype'] == "msdos": + parted_fs_type = "fat16" + elif part['fstype'] == "ontrackdm6aux3": + parted_fs_type = "ontrackdm6aux3" + else: + # Type for ext2/ext3/ext4/btrfs + parted_fs_type = "ext2" + + # Boot ROM of OMAP boards require vfat boot partition to have an + # even number of sectors. + if part['mountpoint'] == "/boot" and part['fstype'] in ["vfat", "msdos"] \ + and part['size'] % 2: + msger.debug("Substracting one sector from '%s' partition to " \ + "get even number of sectors for the partition" % \ + part['mountpoint']) + part['size'] -= 1 + + self.__create_partition(disk['disk'].device, part['type'], + parted_fs_type, part['start'], part['size']) + + if part['part_type']: + msger.debug("partition %d: set type UID to %s" % \ + (part['num'], part['part_type'])) + exec_native_cmd("sgdisk --typecode=%d:%s %s" % \ + (part['num'], part['part_type'], + disk['disk'].device), self.native_sysroot) + + if part['uuid']: + msger.debug("partition %d: set UUID to %s" % \ + (part['num'], part['uuid'])) + exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \ + (part['num'], part['uuid'], disk['disk'].device), + self.native_sysroot) + + if part['boot']: + flag_name = "legacy_boot" if disk['ptable_format'] == 'gpt' else "boot" + msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \ + (flag_name, part['num'], disk['disk'].device)) + exec_native_cmd("parted -s %s set %d %s on" % \ + (disk['disk'].device, part['num'], flag_name), + self.native_sysroot) + + # Parted defaults to enabling the lba flag for fat16 partitions, + # which causes compatibility issues with some firmware (and really + # isn't necessary). + if parted_fs_type == "fat16": + if disk['ptable_format'] == 'msdos': + msger.debug("Disable 'lba' flag for partition '%s' on disk '%s'" % \ + (part['num'], disk['disk'].device)) + exec_native_cmd("parted -s %s set %d lba off" % \ + (disk['disk'].device, part['num']), + self.native_sysroot) + + def cleanup(self): + if self.disks: + for dev in self.disks: + disk = self.disks[dev] + try: + disk['disk'].cleanup() + except: + pass + + def assemble(self, image_file): + msger.debug("Installing partitions") + + for part in self.partitions: + source = part['source_file'] + if source: + # install source_file contents into a partition + cmd = "dd if=%s of=%s bs=%d seek=%d count=%d conv=notrunc" % \ + (source, image_file, self.sector_size, + part['start'], part['size']) + exec_cmd(cmd) + + msger.debug("Installed %s in partition %d, sectors %d-%d, " + "size %d sectors" % \ + (source, part['num'], part['start'], + part['start'] + part['size'] - 1, part['size'])) + + os.rename(source, image_file + '.p%d' % part['num']) + + def create(self): + for dev in self.disks.keys(): + disk = self.disks[dev] + disk['disk'].create() + + self.__format_disks() + + return |