diff options
Diffstat (limited to 'scripts/lib/mic/imager/direct.py')
-rw-r--r-- | scripts/lib/mic/imager/direct.py | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/scripts/lib/mic/imager/direct.py b/scripts/lib/mic/imager/direct.py new file mode 100644 index 0000000000..d24bc684fe --- /dev/null +++ b/scripts/lib/mic/imager/direct.py @@ -0,0 +1,472 @@ +# 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', based +# loosely on the raw image creator from 'mic' +# +# AUTHORS +# Tom Zanussi <tom.zanussi (at] linux.intel.com> +# + +import os +import stat +import shutil + +from mic import kickstart, msger +from mic.utils import fs_related, runner, misc +from mic.utils.partitionedfs import PartitionedMount +from mic.utils.errors import CreatorError, MountError +from mic.imager.baseimager import BaseImageCreator +from mic.utils.oe.misc import * + +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, hdddir, staging_data_dir, + creatoropts=None, pkgmgr=None, compress_image=None, + generate_bmap=None, fstab_entry="uuid"): + """ + Initialize a DirectImageCreator instance. + + This method takes the same arguments as ImageCreator.__init__() + """ + BaseImageCreator.__init__(self, creatoropts, pkgmgr) + + self.__instimage = None + self.__imgdir = None + self.__disks = {} + self.__disk_format = "direct" + self._disk_names = [] + self._ptable_format = self.ks.handler.bootloader.ptable + self.use_uuid = fstab_entry == "uuid" + self.compress_image = compress_image + self.bmap_needed = generate_bmap + + self.oe_builddir = oe_builddir + if image_output_dir: + self.tmpdir = image_output_dir + self.cachedir = "%s/cache" % image_output_dir + self.rootfs_dir = rootfs_dir + self.bootimg_dir = bootimg_dir + self.kernel_dir = kernel_dir + self.native_sysroot = native_sysroot + self.hdddir = hdddir + self.staging_data_dir = staging_data_dir + self.boot_type = "" + + def __write_fstab(self): + """overriden to generate fstab (temporarily) in rootfs. This + is called from mount_instroot, make sure it doesn't get called + from BaseImage.mount()""" + + image_rootfs = self.rootfs_dir + + parts = self._get_parts() + + fstab = image_rootfs + "/etc/fstab" + + self._save_fstab(fstab) + fstab_lines = self._get_fstab(fstab, parts) + self._update_fstab(fstab_lines, parts) + self._write_fstab(fstab, fstab_lines) + + return fstab + + def _update_fstab(self, fstab_lines, parts): + """Assume partition order same as in wks""" + for num, p in enumerate(parts, 1): + if p.mountpoint == "/" or p.mountpoint == "/boot": + continue + if self._ptable_format == 'msdos' and num > 3: + device_name = "/dev/" + p.disk + str(num + 1) + else: + device_name = "/dev/" + p.disk + str(num) + fstab_entry = device_name + "\t" + p.mountpoint + "\t" + p.fstype + "\tdefaults\t0\t0\n" + fstab_lines.append(fstab_entry) + + def _write_fstab(self, fstab, fstab_lines): + fstab = open(fstab, "w") + for line in fstab_lines: + fstab.write(line) + fstab.close() + + def _save_fstab(self, fstab): + """Save the current fstab in rootfs""" + shutil.copyfile(fstab, fstab + ".orig") + + def _restore_fstab(self, fstab): + """Restore the saved fstab in rootfs""" + shutil.move(fstab + ".orig", fstab) + + def _get_fstab(self, fstab, parts): + """Return the desired contents of /etc/fstab.""" + f = open(fstab, "r") + fstab_contents = f.readlines() + f.close() + + return fstab_contents + + 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() + pd = self.ks.handler.partition.parse(args[1:]) + if pd not in self.ks.handler.partition.partitions: + self.ks.handler.partition.partitions.append(pd) + + # 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_boot_type(self): + """ Determine the boot type from fstype and mountpoint. """ + parts = self._get_parts() + + boot_type = "" + + for p in parts: + if p.mountpoint == "/boot": + if p.fstype == "msdos": + boot_type = "pcbios" + else: + boot_type = p.fstype + return boot_type + + # + # Actual implemention + # + def _mount_instroot(self, base_on = None): + """ + For 'wic', we already have our build artifacts and don't want + to loop mount anything to install into, we just create + filesystems from the artifacts directly and combine them into + a partitioned image. + + We still want to reuse as much of the basic mic machinery + though; despite the fact that we don't actually do loop or any + other kind of mounting we still want to do many of the same + things to prepare images, so we basically just adapt to the + basic framework and reinterpret what 'mounting' means in our + context. + + _instroot would normally be something like + /var/tmp/wic/build/imgcreate-s_9AKQ/install_root, for + installing packages, etc. We don't currently need to do that, + so we simplify life by just using /var/tmp/wic/build as our + workdir. + """ + parts = self._get_parts() + + self.__instimage = PartitionedMount(self._instroot) + + fstab = self.__write_fstab() + + self.boot_type = self.get_boot_type() + + if not self.bootimg_dir: + if self.boot_type == "pcbios": + self.bootimg_dir = self.staging_data_dir + elif self.boot_type == "efi": + self.bootimg_dir = self.hdddir + + if self.boot_type == "pcbios": + self._create_syslinux_config() + elif self.boot_type == "efi": + self._create_grubefi_config() + else: + raise CreatorError("Failed to detect boot type (no /boot partition?), " + "please check your kickstart setting.") + + for p in parts: + if p.fstype == "efi": + p.fstype = "msdos" + # need to create the filesystems in order to get their + # sizes before we can add them and do the layout. + # PartitionedMount.mount() actually calls __format_disks() + # to create the disk images and carve out the partitions, + # then self.install() calls PartitionedMount.install() + # which calls __install_partitition() for each partition + # to dd the fs into the partitions. It would be nice to + # be able to use e.g. ExtDiskMount etc to create the + # filesystems, since that's where existing e.g. mkfs code + # is, but those are only created after __format_disks() + # which needs the partition sizes so needs them created + # before its called. Well, the existing setup is geared + # to installing packages into mounted filesystems - maybe + # when/if we need to actually do package selection we + # should modify things to use those objects, but for now + # we can avoid that. + p.prepare(self.workdir, self.oe_builddir, self.boot_type, + self.rootfs_dir, self.bootimg_dir, self.kernel_dir, + self.native_sysroot) + + self.__instimage.add_partition(int(p.size), + p.disk, + p.mountpoint, + p.source_file, + p.fstype, + p.label, + fsopts = p.fsopts, + boot = p.active, + align = p.align, + part_type = p.part_type) + self._restore_fstab(fstab) + self.__instimage.layout_partitions(self._ptable_format) + + self.__imgdir = self.workdir + for disk_name, disk in self.__instimage.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.__instimage.add_disk(disk_name, disk_obj) + + self.__instimage.mount() + + def install(self, repo_urls=None): + """ + Install fs images into partitions + """ + for disk_name, disk in self.__instimage.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "direct") + msger.debug("Installing disk %s as %s with size %s bytes" \ + % (disk_name, full_path, disk['min_size'])) + self.__instimage.install(full_path) + + def configure(self, repodata = None): + """ + Configure the system image according to kickstart. + + For now, it just prepares the image to be bootable by e.g. + creating and installing a bootloader configuration. + """ + if self.boot_type == "pcbios": + self._install_syslinux() + + 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" + + for disk_name, disk in self.__instimage.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "direct") + msg += ' %s\n\n' % full_path + + msg += 'The following build artifacts were used to create the image(s):\n' + msg += ' ROOTFS_DIR: %s\n' % self.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) + + def _get_boot_config(self): + """ + Return the rootdev/root_part_uuid (if specified by + --part-type) + + Assume partition order same as in wks + """ + rootdev = None + root_part_uuid = None + parts = self._get_parts() + for num, p in enumerate(parts, 1): + if p.mountpoint == "/": + if self._ptable_format == 'msdos' and num > 3: + rootdev = "/dev/%s%-d" % (p.disk, num + 1) + else: + rootdev = "/dev/%s%-d" % (p.disk, num) + root_part_uuid = p.part_type + + return (rootdev, root_part_uuid) + + def _create_syslinux_config(self): + hdddir = "%s/hdd/boot" % self.workdir + rm_cmd = "rm -rf " + self.workdir + exec_cmd(rm_cmd) + + install_cmd = "install -d %s" % hdddir + tmp = exec_cmd(install_cmd) + + splash = os.path.join(self.workdir, "/hdd/boot/splash.jpg") + if os.path.exists(splash): + splashline = "menu background splash.jpg" + else: + splashline = "" + + (rootdev, root_part_uuid) = self._get_boot_config() + options = self.ks.handler.bootloader.appendLine + + syslinux_conf = "" + syslinux_conf += "PROMPT 0\n" + timeout = kickstart.get_timeout(self.ks) + if not timeout: + timeout = 0 + syslinux_conf += "TIMEOUT " + str(timeout) + "\n" + syslinux_conf += "\n" + syslinux_conf += "ALLOWOPTIONS 1\n" + syslinux_conf += "SERIAL 0 115200\n" + syslinux_conf += "\n" + if splashline: + syslinux_conf += "%s\n" % splashline + syslinux_conf += "DEFAULT boot\n" + syslinux_conf += "LABEL boot\n" + + kernel = "/vmlinuz" + syslinux_conf += "KERNEL " + kernel + "\n" + + if self._ptable_format == 'msdos': + rootstr = rootdev + else: + if not root_part_uuid: + raise MountError("Cannot find the root GPT partition UUID") + rootstr = "PARTUUID=%s" % root_part_uuid + + syslinux_conf += "APPEND label=boot root=%s %s\n" % (rootstr, options) + + msger.debug("Writing syslinux config %s/hdd/boot/syslinux.cfg" \ + % self.workdir) + cfg = open("%s/hdd/boot/syslinux.cfg" % self.workdir, "w") + cfg.write(syslinux_conf) + cfg.close() + + def _create_grubefi_config(self): + hdddir = "%s/hdd/boot" % self.workdir + rm_cmd = "rm -rf %s" % self.workdir + exec_cmd(rm_cmd) + + install_cmd = "install -d %s/EFI/BOOT" % hdddir + tmp = exec_cmd(install_cmd) + + splash = os.path.join(self.workdir, "/EFI/boot/splash.jpg") + if os.path.exists(splash): + splashline = "menu background splash.jpg" + else: + splashline = "" + + (rootdev, root_part_uuid) = self._get_boot_config() + options = self.ks.handler.bootloader.appendLine + + grubefi_conf = "" + grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n" + grubefi_conf += "default=boot\n" + timeout = kickstart.get_timeout(self.ks) + if not timeout: + timeout = 0 + grubefi_conf += "timeout=%s\n" % timeout + grubefi_conf += "menuentry 'boot'{\n" + + kernel = "/vmlinuz" + + if self._ptable_format == 'msdos': + rootstr = rootdev + else: + if not root_part_uuid: + raise MountError("Cannot find the root GPT partition UUID") + rootstr = "PARTUUID=%s" % root_part_uuid + + grubefi_conf += "linux %s root=%s rootwait %s\n" \ + % (kernel, rootstr, options) + grubefi_conf += "}\n" + if splashline: + syslinux_conf += "%s\n" % splashline + + msger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg" \ + % self.workdir) + cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % self.workdir, "w") + cfg.write(grubefi_conf) + cfg.close() + + def _install_syslinux(self): + mbrfile = "%s/syslinux/" % self.bootimg_dir + if self._ptable_format == 'gpt': + mbrfile += "gptmbr.bin" + else: + mbrfile += "mbr.bin" + + if not os.path.exists(mbrfile): + msger.error("Couldn't find %s. If using the -e option, do you have the right MACHINE set in local.conf? If not, is the bootimg_dir path correct?" % mbrfile) + + for disk_name, disk in self.__instimage.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "direct") + msger.debug("Installing MBR on disk %s as %s with size %s bytes" \ + % (disk_name, full_path, disk['min_size'])) + + rc = runner.show(['dd', 'if=%s' % mbrfile, + 'of=%s' % full_path, 'conv=notrunc']) + if rc != 0: + raise MountError("Unable to set MBR to %s" % full_path) + + def _unmount_instroot(self): + if not self.__instimage is None: + try: + self.__instimage.cleanup() + except MountError, err: + msger.warning("%s" % err) + |