diff options
author | Ed Bartosh <ed.bartosh@linux.intel.com> | 2017-01-31 11:46:11 +0200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-02-02 17:37:34 +0000 |
commit | d5db8c2ee91bdd51bfbb2ebf61aea8ff0378d512 (patch) | |
tree | 5f78b1d721474a05f358ebdedff0756399df0d44 /scripts/lib/wic/plugins/imager/direct.py | |
parent | c5706f71dee531557a6b1290665283b8637fff6e (diff) | |
download | openembedded-core-contrib-d5db8c2ee91bdd51bfbb2ebf61aea8ff0378d512.tar.gz |
wic: renamd direct_plugin.py -> direct.py
As this files is located in plugins/imager subdirectory it's
obvious that it's an imager plugin. Renamed to direct.py to
be consistent with plugin naming scheme.
[YOCTO #10619]
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
Diffstat (limited to 'scripts/lib/wic/plugins/imager/direct.py')
-rw-r--r-- | scripts/lib/wic/plugins/imager/direct.py | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/scripts/lib/wic/plugins/imager/direct.py b/scripts/lib/wic/plugins/imager/direct.py new file mode 100644 index 0000000000..9cd7068333 --- /dev/null +++ b/scripts/lib/wic/plugins/imager/direct.py @@ -0,0 +1,418 @@ +# 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' imager plugin class for 'wic' +# +# AUTHORS +# Tom Zanussi <tom.zanussi (at] linux.intel.com> +# +import os +import shutil +import uuid +import tempfile + +from time import strftime +from os.path import basename, splitext + +from wic import msger +from wic.ksparser import KickStart, KickStartError +from wic.plugin import pluginmgr +from wic.pluginbase import ImagerPlugin +from wic.utils import errors +from wic.utils.errors import CreatorError, ImageError +from wic.utils.oe.misc import get_bitbake_var, exec_cmd, exec_native_cmd +from wic.utils.partitionedfs import Image + +class DirectPlugin(ImagerPlugin): + """ + Install a system into a file containing a partitioned disk image. + + 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. + """ + + name = 'direct' + + @staticmethod + def do_create(opts, *args): + """ + Create direct image, called from creator as 'direct' cmd + """ + native_sysroot, kernel_dir, bootimg_dir, rootfs_dir, ksconf, \ + outdir, oe_builddir, compressor = args + + try: + ksobj = KickStart(ksconf) + except KickStartError as err: + msger.error(str(err)) + + name = "%s-%s" % (splitext(basename(ksconf))[0], + strftime("%Y%m%d%H%M")) + + # parse possible 'rootfs=name' items + krootfs_dir = dict(rdir.split('=') for rdir in rootfs_dir.split(' ')) + + creator = DirectImageCreator(name, ksobj, oe_builddir, outdir, + krootfs_dir, bootimg_dir, kernel_dir, + native_sysroot, compressor, opts.bmap) + try: + creator.create() + creator.assemble() + creator.finalize() + creator.print_info() + except errors.CreatorError: + raise + finally: + creator.cleanup() + +class DiskImage(): + """ + A Disk backed by a file. + """ + def __init__(self, device, size): + self.size = size + self.device = device + self.created = False + + def create(self): + if self.created: + return + # create sparse disk image + with open(self.device, 'w') as sparse: + os.ftruncate(sparse.fileno(), self.size) + + self.created = True + +class DirectImageCreator: + """ + 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, name, ksobj, oe_builddir, outdir, + rootfs_dir, bootimg_dir, kernel_dir, + native_sysroot, compressor, bmap=False): + """ + Initialize a DirectImageCreator instance. + + This method takes the same arguments as ImageCreator.__init__() + """ + self.name = name + self.outdir = outdir + self.workdir = tempfile.mktemp(prefix='wic') + self.ks = ksobj + + self._image = None + self._disks = {} + self._disk_format = "direct" + self._disk_names = [] + self.ptable_format = self.ks.bootloader.ptable + + self.oe_builddir = oe_builddir + self.rootfs_dir = rootfs_dir + self.bootimg_dir = bootimg_dir + self.kernel_dir = kernel_dir + self.native_sysroot = native_sysroot + self.compressor = compressor + self.bmap = bmap + + 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.partitions: + partstr = "part / --size 1900 --ondisk sda --fstype=ext3" + args = partstr.split() + part = self.ks.parse(args[1:]) + if part not in self.ks.partitions: + self.ks.partitions.append(part) + + # partitions list from kickstart file + return self.ks.partitions + + def _full_path(self, path, name, extention): + """ Construct full file path to a file we generate. """ + return os.path.join(path, "%s-%s.%s" % (self.name, name, extention)) + + # + # 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) + + disk_ids = {} + for num, part in enumerate(parts, 1): + # as a convenience, set source to the boot partition source + # instead of forcing it to be set via bootloader --source + if not self.ks.bootloader.source and part.mountpoint == "/boot": + self.ks.bootloader.source = part.source + + # generate parition UUIDs + if not part.uuid and part.use_uuid: + if self.ptable_format == 'gpt': + part.uuid = str(uuid.uuid4()) + else: # msdos partition table + if part.disk not in disk_ids: + disk_ids[part.disk] = int.from_bytes(os.urandom(4), 'little') + disk_id = disk_ids[part.disk] + part.uuid = '%0x-%02d' % (disk_id, self._get_part_num(num, parts)) + + 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 = self.rootfs_dir.get(part.rootfs_dir) + if image_name and os.path.sep not in 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: + part.size = int(round(float(rsize_bb))) + # 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(part.disk_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, + system_id=part.system_id) + + if fstab_path: + shutil.move(fstab_path + ".orig", fstab_path) + + self._image.layout_partitions(self.ptable_format) + + for disk_name, disk in self._image.disks.items(): + full_path = self._full_path(self.workdir, disk_name, "direct") + msger.debug("Adding disk %s as %s with size %s bytes" \ + % (disk_name, full_path, disk['min_size'])) + disk_obj = DiskImage(full_path, disk['min_size']) + #self._disks[disk_name] = disk_obj + self._image.add_disk(disk_name, disk_obj, disk_ids.get(disk_name)) + + 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.workdir, 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.ks.bootloader.source + if source_plugin: + name = "do_install_disk" + methods = pluginmgr.get_source_plugin_methods(source_plugin, + {name: None}) + for disk_name, disk in self._image.disks.items(): + methods["do_install_disk"](disk, disk_name, self, self.workdir, + self.oe_builddir, self.bootimg_dir, + self.kernel_dir, self.native_sysroot) + + for disk_name, disk in self._image.disks.items(): + full_path = self._full_path(self.workdir, disk_name, "direct") + # Generate .bmap + if self.bmap: + msger.debug("Generating bmap file for %s" % disk_name) + exec_native_cmd("bmaptool create %s -o %s.bmap" % (full_path, full_path), + self.native_sysroot) + # Compress the image + if self.compressor: + msger.debug("Compressing disk %s with %s" % (disk_name, self.compressor)) + exec_cmd("%s %s" % (self.compressor, full_path)) + + def print_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.outdir, 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.rootfs_dir 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.rootfs_dir) + + 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 self._image: + try: + self._image.cleanup() + except ImageError as err: + msger.warning("%s" % err) + + # Move results to the output dir + if not os.path.exists(self.outdir): + os.makedirs(self.outdir) + + for fname in os.listdir(self.workdir): + path = os.path.join(self.workdir, fname) + if os.path.isfile(path): + shutil.move(path, os.path.join(self.outdir, fname)) + + # remove work directory + shutil.rmtree(self.workdir, ignore_errors=True) |