aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/lib/wic/plugins/imager/direct_plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/wic/plugins/imager/direct_plugin.py')
-rw-r--r--scripts/lib/wic/plugins/imager/direct_plugin.py396
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)
+