diff options
author | Patrick Williams <patrick@stwcx.xyz> | 2015-09-15 22:41:29 +0300 |
---|---|---|
committer | Patrick Williams <patrick@stwcx.xyz> | 2015-09-15 22:41:29 +0300 |
commit | 21f9b84b4b729fbd7acbd465e7a3f726e4d20f91 (patch) | |
tree | eb2d091d427ca0813b445509d59cc8e27e8ad25f /yocto-poky/scripts/lib/wic/imager/direct.py | |
parent | 101cef31e2bf54c678501155cd2106251acbd076 (diff) | |
parent | c124f4f2e04dca16a428a76c89677328bc7bf908 (diff) | |
download | openbmc-21f9b84b4b729fbd7acbd465e7a3f726e4d20f91.tar.xz |
Merge commit 'c124f4f2e04dca16a428a76c89677328bc7bf908' as 'yocto-poky'
Diffstat (limited to 'yocto-poky/scripts/lib/wic/imager/direct.py')
-rw-r--r-- | yocto-poky/scripts/lib/wic/imager/direct.py | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/yocto-poky/scripts/lib/wic/imager/direct.py b/yocto-poky/scripts/lib/wic/imager/direct.py new file mode 100644 index 000000000..146a0d153 --- /dev/null +++ b/yocto-poky/scripts/lib/wic/imager/direct.py @@ -0,0 +1,378 @@ +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# Copyright (c) 2013, Intel Corporation. +# All rights reserved. +# +# 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. +# +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# DESCRIPTION +# This implements the 'direct' image creator class for 'wic' +# +# AUTHORS +# Tom Zanussi <tom.zanussi (at] linux.intel.com> +# + +import os +import shutil + +from wic import kickstart, msger +from wic.utils import fs_related +from wic.utils.oe.misc import get_bitbake_var +from wic.utils.partitionedfs import Image +from wic.utils.errors import CreatorError, ImageError +from wic.imager.baseimager import BaseImageCreator +from wic.plugin import pluginmgr +from wic.utils.oe.misc import exec_cmd + +disk_methods = { + "do_install_disk":None, +} + +class DirectImageCreator(BaseImageCreator): + """ + Installs a system into a file containing a partitioned disk image. + + DirectImageCreator is an advanced ImageCreator subclass; an image + file is formatted with a partition table, each partition created + from a rootfs or other OpenEmbedded build artifact and dd'ed into + the virtual disk. The disk image can subsequently be dd'ed onto + media and used on actual hardware. + """ + + def __init__(self, oe_builddir, image_output_dir, rootfs_dir, bootimg_dir, + kernel_dir, native_sysroot, compressor, creatoropts=None): + """ + Initialize a DirectImageCreator instance. + + This method takes the same arguments as ImageCreator.__init__() + """ + BaseImageCreator.__init__(self, creatoropts) + + self.__image = None + self.__disks = {} + self.__disk_format = "direct" + self._disk_names = [] + self.ptable_format = self.ks.handler.bootloader.ptable + + self.oe_builddir = oe_builddir + if image_output_dir: + self.tmpdir = image_output_dir + self.rootfs_dir = rootfs_dir + self.bootimg_dir = bootimg_dir + self.kernel_dir = kernel_dir + self.native_sysroot = native_sysroot + self.compressor = compressor + + def __get_part_num(self, num, parts): + """calculate the real partition number, accounting for partitions not + in the partition table and logical partitions + """ + realnum = 0 + for pnum, part in enumerate(parts, 1): + if not part.no_table: + realnum += 1 + if pnum == num: + if part.no_table: + return 0 + if self.ptable_format == 'msdos' and realnum > 3: + # account for logical partition numbering, ex. sda5.. + return realnum + 1 + return realnum + + def _write_fstab(self, image_rootfs): + """overriden to generate fstab (temporarily) in rootfs. This is called + from _create, make sure it doesn't get called from + BaseImage.create() + """ + if not image_rootfs: + return + + fstab_path = image_rootfs + "/etc/fstab" + if not os.path.isfile(fstab_path): + return + + with open(fstab_path) as fstab: + fstab_lines = fstab.readlines() + + if self._update_fstab(fstab_lines, self._get_parts()): + shutil.copyfile(fstab_path, fstab_path + ".orig") + + with open(fstab_path, "w") as fstab: + fstab.writelines(fstab_lines) + + return fstab_path + + def _update_fstab(self, fstab_lines, parts): + """Assume partition order same as in wks""" + updated = False + for num, part in enumerate(parts, 1): + pnum = self.__get_part_num(num, parts) + if not pnum or not part.mountpoint \ + or part.mountpoint in ("/", "/boot"): + continue + + # mmc device partitions are named mmcblk0p1, mmcblk0p2.. + prefix = 'p' if part.disk.startswith('mmcblk') else '' + device_name = "/dev/%s%s%d" % (part.disk, prefix, pnum) + + opts = part.fsopts if part.fsopts else "defaults" + line = "\t".join([device_name, part.mountpoint, part.fstype, + opts, "0", "0"]) + "\n" + + fstab_lines.append(line) + updated = True + + return updated + + def set_bootimg_dir(self, bootimg_dir): + """ + Accessor for bootimg_dir, the actual location used for the source + of the bootimg. Should be set by source plugins (only if they + change the default bootimg source) so the correct info gets + displayed for print_outimage_info(). + """ + self.bootimg_dir = bootimg_dir + + def _get_parts(self): + if not self.ks: + raise CreatorError("Failed to get partition info, " + "please check your kickstart setting.") + + # Set a default partition if no partition is given out + if not self.ks.handler.partition.partitions: + partstr = "part / --size 1900 --ondisk sda --fstype=ext3" + args = partstr.split() + part = self.ks.handler.partition.parse(args[1:]) + if part not in self.ks.handler.partition.partitions: + self.ks.handler.partition.partitions.append(part) + + # partitions list from kickstart file + return kickstart.get_partitions(self.ks) + + def get_disk_names(self): + """ Returns a list of physical target disk names (e.g., 'sdb') which + will be created. """ + + if self._disk_names: + return self._disk_names + + #get partition info from ks handler + parts = self._get_parts() + + for i in range(len(parts)): + if parts[i].disk: + disk_name = parts[i].disk + else: + raise CreatorError("Failed to create disks, no --ondisk " + "specified in partition line of ks file") + + if parts[i].mountpoint and not parts[i].fstype: + raise CreatorError("Failed to create disks, no --fstype " + "specified for partition with mountpoint " + "'%s' in the ks file") + + self._disk_names.append(disk_name) + + return self._disk_names + + def _full_name(self, name, extention): + """ Construct full file name for a file we generate. """ + return "%s-%s.%s" % (self.name, name, extention) + + def _full_path(self, path, name, extention): + """ Construct full file path to a file we generate. """ + return os.path.join(path, self._full_name(name, extention)) + + def get_default_source_plugin(self): + """ + The default source plugin i.e. the plugin that's consulted for + overall image generation tasks outside of any particular + partition. For convenience, we just hang it off the + bootloader handler since it's the one non-partition object in + any setup. By default the default plugin is set to the same + plugin as the /boot partition; since we hang it off the + bootloader object, the default can be explicitly set using the + --source bootloader param. + """ + return self.ks.handler.bootloader.source + + # + # Actual implemention + # + def _create(self): + """ + For 'wic', we already have our build artifacts - we just create + filesystems from the artifacts directly and combine them into + a partitioned image. + """ + parts = self._get_parts() + + self.__image = Image(self.native_sysroot) + + for part in parts: + # as a convenience, set source to the boot partition source + # instead of forcing it to be set via bootloader --source + if not self.ks.handler.bootloader.source and part.mountpoint == "/boot": + self.ks.handler.bootloader.source = part.source + + fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR")) + + for part in parts: + # get rootfs size from bitbake variable if it's not set in .ks file + if not part.size: + # and if rootfs name is specified for the partition + image_name = part.get_rootfs() + if image_name: + # Bitbake variable ROOTFS_SIZE is calculated in + # Image._get_rootfs_size method from meta/lib/oe/image.py + # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT, + # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE + rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name) + if rsize_bb: + # convert from Kb to Mb + part.size = int(round(float(rsize_bb) / 1024.)) + # need to create the filesystems in order to get their + # sizes before we can add them and do the layout. + # Image.create() actually calls __format_disks() to create + # the disk images and carve out the partitions, then + # self.assemble() calls Image.assemble() which calls + # __write_partitition() for each partition to dd the fs + # into the partitions. + part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir, + self.bootimg_dir, self.kernel_dir, self.native_sysroot) + + + self.__image.add_partition(int(part.size), + part.disk, + part.mountpoint, + part.source_file, + part.fstype, + part.label, + fsopts=part.fsopts, + boot=part.active, + align=part.align, + no_table=part.no_table, + part_type=part.part_type, + uuid=part.uuid) + + if fstab_path: + shutil.move(fstab_path + ".orig", fstab_path) + + self.__image.layout_partitions(self.ptable_format) + + self.__imgdir = self.workdir + for disk_name, disk in self.__image.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "direct") + msger.debug("Adding disk %s as %s with size %s bytes" \ + % (disk_name, full_path, disk['min_size'])) + disk_obj = fs_related.DiskImage(full_path, disk['min_size']) + self.__disks[disk_name] = disk_obj + self.__image.add_disk(disk_name, disk_obj) + + self.__image.create() + + def assemble(self): + """ + Assemble partitions into disk image(s) + """ + for disk_name, disk in self.__image.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "direct") + msger.debug("Assembling disk %s as %s with size %s bytes" \ + % (disk_name, full_path, disk['min_size'])) + self.__image.assemble(full_path) + + def finalize(self): + """ + Finalize the disk image. + + For example, prepare the image to be bootable by e.g. + creating and installing a bootloader configuration. + + """ + source_plugin = self.get_default_source_plugin() + if source_plugin: + self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods) + for disk_name, disk in self.__image.disks.items(): + self._source_methods["do_install_disk"](disk, disk_name, self, + self.workdir, + self.oe_builddir, + self.bootimg_dir, + self.kernel_dir, + self.native_sysroot) + # Compress the image + if self.compressor: + for disk_name, disk in self.__image.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "direct") + msger.debug("Compressing disk %s with %s" % \ + (disk_name, self.compressor)) + exec_cmd("%s %s" % (self.compressor, full_path)) + + def print_outimage_info(self): + """ + Print the image(s) and artifacts used, for the user. + """ + msg = "The new image(s) can be found here:\n" + + parts = self._get_parts() + + for disk_name in self.__image.disks: + extension = "direct" + {"gzip": ".gz", + "bzip2": ".bz2", + "xz": ".xz", + "": ""}.get(self.compressor) + full_path = self._full_path(self.__imgdir, disk_name, extension) + msg += ' %s\n\n' % full_path + + msg += 'The following build artifacts were used to create the image(s):\n' + for part in parts: + if part.get_rootfs() is None: + continue + if part.mountpoint == '/': + suffix = ':' + else: + suffix = '["%s"]:' % (part.mountpoint or part.label) + msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.get_rootfs()) + + msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir + msg += ' KERNEL_DIR: %s\n' % self.kernel_dir + msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot + + msger.info(msg) + + @property + def rootdev(self): + """ + Get root device name to use as a 'root' parameter + in kernel command line. + + Assume partition order same as in wks + """ + parts = self._get_parts() + for num, part in enumerate(parts, 1): + if part.mountpoint == "/": + if part.uuid: + return "PARTUUID=%s" % part.uuid + else: + suffix = 'p' if part.disk.startswith('mmcblk') else '' + pnum = self.__get_part_num(num, parts) + return "/dev/%s%s%-d" % (part.disk, suffix, pnum) + + def _cleanup(self): + if not self.__image is None: + try: + self.__image.cleanup() + except ImageError, err: + msger.warning("%s" % err) + |