diff options
author | John Klug <john.klug@multitech.com> | 2018-07-31 17:48:08 -0500 |
---|---|---|
committer | John Klug <john.klug@multitech.com> | 2018-07-31 17:48:08 -0500 |
commit | b5dd8c128624cb77576d692b68e24691d4d9a96d (patch) | |
tree | 4a0cc0a718fa98582fd70719a83b826c2d990cf5 /scripts/lib/wic/imager | |
parent | e08c220730d5da161a746d811268eb1550beb856 (diff) | |
download | mlinux-b5dd8c128624cb77576d692b68e24691d4d9a96d.tar.gz mlinux-b5dd8c128624cb77576d692b68e24691d4d9a96d.tar.bz2 mlinux-b5dd8c128624cb77576d692b68e24691d4d9a96d.zip |
mLinux 4
Diffstat (limited to 'scripts/lib/wic/imager')
-rw-r--r-- | scripts/lib/wic/imager/__init__.py | 0 | ||||
-rw-r--r-- | scripts/lib/wic/imager/baseimager.py | 192 | ||||
-rw-r--r-- | scripts/lib/wic/imager/direct.py | 380 |
3 files changed, 572 insertions, 0 deletions
diff --git a/scripts/lib/wic/imager/__init__.py b/scripts/lib/wic/imager/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/scripts/lib/wic/imager/__init__.py diff --git a/scripts/lib/wic/imager/baseimager.py b/scripts/lib/wic/imager/baseimager.py new file mode 100644 index 0000000..760cf8a --- /dev/null +++ b/scripts/lib/wic/imager/baseimager.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python -tt +# +# Copyright (c) 2007 Red Hat Inc. +# Copyright (c) 2009, 2010, 2011 Intel, Inc. +# +# 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. + +from __future__ import with_statement +import os +import tempfile +import shutil + +from wic import msger +from wic.utils.errors import CreatorError +from wic.utils import runner + +class BaseImageCreator(object): + """Base class for image creation. + + BaseImageCreator is the simplest creator class available; it will + create a system image according to the supplied kickstart file. + + e.g. + + import wic.imgcreate as imgcreate + ks = imgcreate.read_kickstart("foo.ks") + imgcreate.ImageCreator(ks, "foo").create() + """ + + def __del__(self): + self.cleanup() + + def __init__(self, createopts=None): + """Initialize an ImageCreator instance. + + ks -- a pykickstart.KickstartParser instance; this instance will be + used to drive the install by e.g. providing the list of packages + to be installed, the system configuration and %post scripts + + name -- a name for the image; used for e.g. image filenames or + filesystem labels + """ + + self.__builddir = None + + self.ks = None + self.name = "target" + self.tmpdir = "/var/tmp/wic" + self.workdir = "/var/tmp/wic/build" + + # setup tmpfs tmpdir when enabletmpfs is True + self.enabletmpfs = False + + if createopts: + # Mapping table for variables that have different names. + optmap = {"outdir" : "destdir", + } + + # update setting from createopts + for key in createopts.keys(): + if key in optmap: + option = optmap[key] + else: + option = key + setattr(self, option, createopts[key]) + + self.destdir = os.path.abspath(os.path.expanduser(self.destdir)) + + self._dep_checks = ["ls", "bash", "cp", "echo"] + + # Output image file names + self.outimage = [] + + # No ks provided when called by convertor, so skip the dependency check + if self.ks: + # If we have btrfs partition we need to check necessary tools + for part in self.ks.partitions: + if part.fstype and part.fstype == "btrfs": + self._dep_checks.append("mkfs.btrfs") + break + + # make sure the specified tmpdir and cachedir exist + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + + + # + # Hooks for subclasses + # + def _create(self): + """Create partitions for the disk image(s) + + This is the hook where subclasses may create the partitions + that will be assembled into disk image(s). + + There is no default implementation. + """ + pass + + def _cleanup(self): + """Undo anything performed in _create(). + + This is the hook where subclasses must undo anything which was + done in _create(). + + There is no default implementation. + + """ + pass + + # + # Actual implementation + # + def __ensure_builddir(self): + if not self.__builddir is None: + return + + try: + self.workdir = os.path.join(self.tmpdir, "build") + if not os.path.exists(self.workdir): + os.makedirs(self.workdir) + self.__builddir = tempfile.mkdtemp(dir=self.workdir, + prefix="imgcreate-") + except OSError as err: + raise CreatorError("Failed create build directory in %s: %s" % + (self.tmpdir, err)) + + def __setup_tmpdir(self): + if not self.enabletmpfs: + return + + runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir) + + def __clean_tmpdir(self): + if not self.enabletmpfs: + return + + runner.show('umount -l %s' % self.workdir) + + def create(self): + """Create partitions for the disk image(s) + + Create the partitions that will be assembled into disk + image(s). + """ + self.__setup_tmpdir() + self.__ensure_builddir() + + self._create() + + def cleanup(self): + """Undo anything performed in create(). + + Note, make sure to call this method once finished with the creator + instance in order to ensure no stale files are left on the host e.g.: + + creator = ImageCreator(ks, name) + try: + creator.create() + finally: + creator.cleanup() + + """ + if not self.__builddir: + return + + self._cleanup() + + shutil.rmtree(self.__builddir, ignore_errors=True) + self.__builddir = None + + self.__clean_tmpdir() + + + def print_outimage_info(self): + msg = "The new image can be found here:\n" + self.outimage.sort() + for path in self.outimage: + msg += ' %s\n' % os.path.abspath(path) + + msger.info(msg) diff --git a/scripts/lib/wic/imager/direct.py b/scripts/lib/wic/imager/direct.py new file mode 100644 index 0000000..a1b4249 --- /dev/null +++ b/scripts/lib/wic/imager/direct.py @@ -0,0 +1,380 @@ +# 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 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.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.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 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.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.bootloader.source and part.mountpoint == "/boot": + self.ks.bootloader.source = part.source + + fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR")) + + shutil.rmtree(self.workdir) + os.mkdir(self.workdir) + + 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.rootfs_dir + 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: + 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(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.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 not self.__image is None: + try: + self.__image.cleanup() + except ImageError, err: + msger.warning("%s" % err) + |