diff options
author | Ed Bartosh <ed.bartosh@linux.intel.com> | 2017-01-30 23:54:29 +0200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-02-02 17:37:34 +0000 |
commit | a8f5ebb26183faa9af6eb72f4dabfcf83aa1e8d4 (patch) | |
tree | 25b50a0fdf69b8c709c0af167460a4f926a9973a /scripts/lib/wic/plugins | |
parent | ffa2f3d7bf883d5add911b7c5d0be2b347733524 (diff) | |
download | openembedded-core-a8f5ebb26183faa9af6eb72f4dabfcf83aa1e8d4.tar.gz openembedded-core-a8f5ebb26183faa9af6eb72f4dabfcf83aa1e8d4.tar.bz2 openembedded-core-a8f5ebb26183faa9af6eb72f4dabfcf83aa1e8d4.zip |
wic: moved content of direct.py to direct_plugin
This move simplifies directory structure and makes
further refactoring easier. The code from direct.py was used
only in direct_plugin, so it's safe to move it there.
[YOCTO #10619]
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
Diffstat (limited to 'scripts/lib/wic/plugins')
-rw-r--r-- | scripts/lib/wic/plugins/imager/direct_plugin.py | 396 |
1 files changed, 380 insertions, 16 deletions
diff --git a/scripts/lib/wic/plugins/imager/direct_plugin.py b/scripts/lib/wic/plugins/imager/direct_plugin.py index e839d2feae..91a9792814 100644 --- a/scripts/lib/wic/plugins/imager/direct_plugin.py +++ b/scripts/lib/wic/plugins/imager/direct_plugin.py @@ -23,16 +23,23 @@ # AUTHORS # Tom Zanussi <tom.zanussi (at] linux.intel.com> # +import os +import shutil +import uuid +import tempfile +import time from time import strftime - from os.path import basename, splitext -from wic.utils import errors -from wic.ksparser import KickStart, KickStartError -from wic import msger -import wic.imager.direct as direct +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): """ @@ -87,16 +94,10 @@ class DirectPlugin(ImagerPlugin): strftime("%Y%m%d%H%M")) krootfs_dir = cls.__rootfs_dir_to_dict(rootfs_dir) - creator = direct.DirectImageCreator(image_name, - ksobj, - oe_builddir, - image_output_dir, - krootfs_dir, - bootimg_dir, - kernel_dir, - native_sysroot, - compressor, - opts.bmap) + creator = DirectImageCreator(image_name, ksobj, oe_builddir, + image_output_dir, krootfs_dir, + bootimg_dir, kernel_dir, native_sysroot, + compressor, opts.bmap) try: creator.create() creator.assemble() @@ -108,4 +109,367 @@ class DirectPlugin(ImagerPlugin): finally: creator.cleanup() - return 0 +disk_methods = { + "do_install_disk":None, +} + +class DiskImage(): + """ + A Disk backed by a file. + """ + def __init__(self, device, size): + self.size = size + self.device = device + self.created = False + + def exists(self): + return os.path.exists(self.device) + + 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, image_name, ksobj, oe_builddir, image_output_dir, + 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 = image_name + self.outdir = image_output_dir + 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_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) + + 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.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) + + 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_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.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) + |